diff --git a/.claude/.skiller.json b/.claude/.skiller.json index 528efbe0a2..48d71006c5 100644 --- a/.claude/.skiller.json +++ b/.claude/.skiller.json @@ -29,7 +29,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/agent-browser", "destRelPath": "agent-browser" @@ -37,7 +37,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/agent-native-architecture", "destRelPath": "agent-native-architecture" @@ -45,7 +45,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/agent-native-audit.md", "destRelPath": "agent-native-audit" @@ -53,7 +53,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/agent-native-reviewer.md", "destRelPath": "agent-native-reviewer" @@ -61,7 +61,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/andrew-kane-gem-writer", "destRelPath": "andrew-kane-gem-writer" @@ -69,7 +69,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/docs/ankane-readme-writer.md", "destRelPath": "ankane-readme-writer" @@ -77,7 +77,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/architecture-strategist.md", "destRelPath": "architecture-strategist" @@ -85,7 +85,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/research/best-practices-researcher.md", "destRelPath": "best-practices-researcher" @@ -93,7 +93,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/brainstorming", "destRelPath": "brainstorming" @@ -101,7 +101,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/workflow/bug-reproduction-validator.md", "destRelPath": "bug-reproduction-validator" @@ -109,7 +109,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/changelog.md", "destRelPath": "changelog" @@ -117,7 +117,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/code-simplicity-reviewer.md", "destRelPath": "code-simplicity-reviewer" @@ -125,7 +125,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/compound-docs", "destRelPath": "compound-docs" @@ -133,7 +133,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/every-style-editor", "destRelPath": "compound-engineering-every-style-editor" @@ -141,7 +141,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/create-agent-skill.md", "destRelPath": "create-agent-skill" @@ -149,7 +149,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/create-agent-skills", "destRelPath": "create-agent-skills" @@ -162,6 +162,22 @@ "sourceRelPath": "commands/create-app-design.md", "destRelPath": "create-app-design" }, + { + "sourceType": "plugin", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", + "sourceKind": "command", + "sourceRelPath": "commands/create-pr.md", + "destRelPath": "create-pr" + }, + { + "sourceType": "plugin", + "pluginId": "dotai@dotai", + "pluginVersion": "1.0.0", + "sourceKind": "command", + "sourceRelPath": "commands/create-snippet.md", + "destRelPath": "create-snippet" + }, { "sourceType": "plugin", "pluginId": "dotai@dotai", @@ -170,10 +186,18 @@ "sourceRelPath": "commands/create-tech-stack.md", "destRelPath": "create-tech-stack" }, + { + "sourceType": "plugin", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", + "sourceKind": "skill", + "sourceRelPath": "skills/creating-pr", + "destRelPath": "creating-pr" + }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/data-integrity-guardian.md", "destRelPath": "data-integrity-guardian" @@ -181,7 +205,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/data-migration-expert.md", "destRelPath": "data-migration-expert" @@ -190,14 +214,14 @@ "sourceType": "plugin", "pluginId": "debug@dotai", "pluginVersion": "0.1.0", - "sourceKind": "skill", - "sourceRelPath": "skills/debug", + "sourceKind": "command", + "sourceRelPath": "commands/debug.md", "destRelPath": "debug" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/deepen-plan.md", "destRelPath": "deepen-plan" @@ -205,7 +229,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/deploy-docs.md", "destRelPath": "deploy-docs" @@ -213,7 +237,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/deployment-verification-agent.md", "destRelPath": "deployment-verification-agent" @@ -221,7 +245,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/design/design-implementation-reviewer.md", "destRelPath": "design-implementation-reviewer" @@ -229,7 +253,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/design/design-iterator.md", "destRelPath": "design-iterator" @@ -237,7 +261,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/dhh-rails-reviewer.md", "destRelPath": "dhh-rails-reviewer" @@ -245,7 +269,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/dhh-rails-style", "destRelPath": "dhh-rails-style" @@ -260,16 +284,24 @@ }, { "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", + "sourceKind": "command", + "sourceRelPath": "commands/draft-pr.md", + "destRelPath": "draft-pr" + }, + { + "sourceType": "plugin", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", "sourceKind": "skill", - "sourceRelPath": "skills/document-review", - "destRelPath": "document-review" + "sourceRelPath": "skills/drafting-pr", + "destRelPath": "drafting-pr" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/dspy-ruby", "destRelPath": "dspy-ruby" @@ -277,7 +309,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/workflow/every-style-editor.md", "destRelPath": "every-style-editor" @@ -285,7 +317,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/feature-video.md", "destRelPath": "feature-video" @@ -293,7 +325,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/design/figma-design-sync.md", "destRelPath": "figma-design-sync" @@ -301,15 +333,23 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/file-todos", "destRelPath": "file-todos" }, + { + "sourceType": "plugin", + "pluginId": "dotai@dotai", + "pluginVersion": "1.0.0", + "sourceKind": "command", + "sourceRelPath": "commands/fix.md", + "destRelPath": "fix" + }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/research/framework-docs-researcher.md", "destRelPath": "framework-docs-researcher" @@ -317,7 +357,7 @@ { "sourceType": "plugin", "pluginId": "frontend-design@claude-plugins-official", - "pluginVersion": "2cd88e7947b7", + "pluginVersion": "8deab8460a9d", "sourceKind": "skill", "sourceRelPath": "skills/frontend-design", "destRelPath": "frontend-design-frontend-design" @@ -325,7 +365,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/frontend-design", "destRelPath": "frontend-design" @@ -333,7 +373,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/gemini-imagegen", "destRelPath": "gemini-imagegen" @@ -341,7 +381,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/generate_command.md", "destRelPath": "generate_command" @@ -349,7 +389,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/research/git-history-analyzer.md", "destRelPath": "git-history-analyzer" @@ -357,7 +397,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/git-worktree", "destRelPath": "git-worktree" @@ -365,11 +405,19 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/heal-skill.md", "destRelPath": "heal-skill" }, + { + "sourceType": "plugin", + "pluginId": "dotai@dotai", + "pluginVersion": "1.0.0", + "sourceKind": "command", + "sourceRelPath": "commands/install-all.md", + "destRelPath": "install-all" + }, { "sourceType": "plugin", "pluginId": "dotai@dotai", @@ -381,7 +429,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/julik-frontend-races-reviewer.md", "destRelPath": "julik-frontend-races-reviewer" @@ -389,7 +437,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/kieran-python-reviewer.md", "destRelPath": "kieran-python-reviewer" @@ -397,7 +445,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/kieran-rails-reviewer.md", "destRelPath": "kieran-rails-reviewer" @@ -405,7 +453,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/kieran-typescript-reviewer.md", "destRelPath": "kieran-typescript-reviewer" @@ -421,7 +469,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/research/learnings-researcher.md", "destRelPath": "learnings-researcher" @@ -429,7 +477,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/lfg.md", "destRelPath": "lfg" @@ -437,23 +485,23 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/workflow/lint.md", "destRelPath": "lint" }, { "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", - "sourceKind": "skill", - "sourceRelPath": "skills/orchestrating-swarms", - "destRelPath": "orchestrating-swarms" + "pluginId": "dotai@dotai", + "pluginVersion": "1.0.0", + "sourceKind": "command", + "sourceRelPath": "commands/opus.md", + "destRelPath": "opus" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/pattern-recognition-specialist.md", "destRelPath": "pattern-recognition-specialist" @@ -461,7 +509,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/performance-oracle.md", "destRelPath": "performance-oracle" @@ -469,31 +517,39 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", + "sourceKind": "command", + "sourceRelPath": "commands/plan_review.md", + "destRelPath": "plan_review" + }, + { + "sourceType": "plugin", + "pluginId": "compound-engineering@every-marketplace", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/workflow/pr-comment-resolver.md", "destRelPath": "pr-comment-resolver" }, { "sourceType": "plugin", - "pluginId": "git@dotai", - "pluginVersion": "0.1.0", + "pluginId": "compound-engineering@every-marketplace", + "pluginVersion": "2.28.0", "sourceKind": "skill", - "sourceRelPath": "skills/pr", - "destRelPath": "pr" + "sourceRelPath": "skills/rclone", + "destRelPath": "rclone" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", - "sourceKind": "skill", - "sourceRelPath": "skills/rclone", - "destRelPath": "rclone" + "pluginVersion": "2.28.0", + "sourceKind": "command", + "sourceRelPath": "commands/release-docs.md", + "destRelPath": "release-docs" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/research/repo-research-analyst.md", "destRelPath": "repo-research-analyst" @@ -501,7 +557,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/report-bug.md", "destRelPath": "report-bug" @@ -509,7 +565,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/reproduce-bug.md", "destRelPath": "reproduce-bug" @@ -517,7 +573,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/resolve_parallel.md", "destRelPath": "resolve_parallel" @@ -525,31 +581,47 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", + "sourceKind": "command", + "sourceRelPath": "commands/resolve_pr_parallel.md", + "destRelPath": "resolve_pr_parallel" + }, + { + "sourceType": "plugin", + "pluginId": "compound-engineering@every-marketplace", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/resolve_todo_parallel.md", "destRelPath": "resolve_todo_parallel" }, { "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", + "sourceKind": "command", + "sourceRelPath": "commands/review-pr.md", + "destRelPath": "review-pr" + }, + { + "sourceType": "plugin", + "pluginId": "git@dotai", + "pluginVersion": "0.1.0", "sourceKind": "skill", - "sourceRelPath": "skills/resolve-pr-parallel", - "destRelPath": "resolve-pr-parallel" + "sourceRelPath": "skills/reviewing-pr", + "destRelPath": "reviewing-pr" }, { "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", - "sourceKind": "agent", - "sourceRelPath": "agents/review/schema-drift-detector.md", - "destRelPath": "schema-drift-detector" + "pluginId": "debug@dotai", + "pluginVersion": "0.1.0", + "sourceKind": "skill", + "sourceRelPath": "skills/root-cause-tracing", + "destRelPath": "root-cause-tracing" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/review/security-sentinel.md", "destRelPath": "security-sentinel" @@ -557,15 +629,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", - "sourceKind": "skill", - "sourceRelPath": "skills/setup", - "destRelPath": "setup" - }, - { - "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "skill", "sourceRelPath": "skills/skill-creator", "destRelPath": "skill-creator" @@ -573,55 +637,47 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", - "sourceKind": "command", - "sourceRelPath": "commands/slfg.md", - "destRelPath": "slfg" - }, - { - "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "agent", "sourceRelPath": "agents/workflow/spec-flow-analyzer.md", "destRelPath": "spec-flow-analyzer" }, { "sourceType": "plugin", - "pluginId": "test@dotai", + "pluginId": "debug@dotai", "pluginVersion": "0.1.0", "sourceKind": "skill", - "sourceRelPath": "skills/tdd", - "destRelPath": "tdd" + "sourceRelPath": "skills/systematic-debugging", + "destRelPath": "systematic-debugging" }, { "sourceType": "plugin", - "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginId": "test@dotai", + "pluginVersion": "0.1.0", "sourceKind": "command", - "sourceRelPath": "commands/test-browser.md", - "destRelPath": "test-browser" + "sourceRelPath": "commands/tdd.md", + "destRelPath": "tdd" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", - "sourceRelPath": "commands/test-xcode.md", - "destRelPath": "test-xcode" + "sourceRelPath": "commands/test-browser.md", + "destRelPath": "test-browser" }, { "sourceType": "plugin", - "pluginId": "debug@dotai", + "pluginId": "test@dotai", "pluginVersion": "0.1.0", "sourceKind": "skill", - "sourceRelPath": "skills/trace", - "destRelPath": "trace" + "sourceRelPath": "skills/test-driven-development", + "destRelPath": "test-driven-development" }, { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/triage.md", "destRelPath": "triage" @@ -634,6 +690,14 @@ "sourceRelPath": "commands/update-app-design.md", "destRelPath": "update-app-design" }, + { + "sourceType": "plugin", + "pluginId": "dotai@dotai", + "pluginVersion": "1.0.0", + "sourceKind": "command", + "sourceRelPath": "commands/update-project-structure.md", + "destRelPath": "update-project-structure" + }, { "sourceType": "plugin", "pluginId": "dotai@dotai", @@ -645,7 +709,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/workflows/brainstorm.md", "destRelPath": "workflows-brainstorm" @@ -653,7 +717,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/workflows/compound.md", "destRelPath": "workflows-compound" @@ -661,7 +725,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/workflows/plan.md", "destRelPath": "workflows-plan" @@ -669,7 +733,7 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/workflows/review.md", "destRelPath": "workflows-review" @@ -677,10 +741,18 @@ { "sourceType": "plugin", "pluginId": "compound-engineering@every-marketplace", - "pluginVersion": "2.35.2", + "pluginVersion": "2.28.0", "sourceKind": "command", "sourceRelPath": "commands/workflows/work.md", "destRelPath": "workflows-work" + }, + { + "sourceType": "plugin", + "pluginId": "compound-engineering@every-marketplace", + "pluginVersion": "2.28.0", + "sourceKind": "command", + "sourceRelPath": "commands/xcode-test.md", + "destRelPath": "xcode-test" } ], ".cursor/skills": [ diff --git a/.claude/commands/changeset.md b/.claude/commands/changeset.md deleted file mode 100644 index f20736f7f1..0000000000 --- a/.claude/commands/changeset.md +++ /dev/null @@ -1,175 +0,0 @@ -# How to Create a Plate Project Changeset - -This guide outlines the structure and writing style for creating changeset files in the Plate project, aiming for clarity and conciseness similar to the Radix UI changelog style. - -## Registry Changes: Changelog Updates - -**When changes are made to `apps/www/src/registry/` components**, you MUST also update `docs/components/changelog.mdx` to document component changes, additions, or removals. - -**Changelog Standards:** - -- Update `docs/components/changelog.mdx` following the existing format and style -- Document component changes, additions, or removals -- Include any breaking changes or migration notes for registry components -- Use consistent version numbering -- Follow the existing changelog structure and writing style - -**Note:** Registry component changes require changelog updates, NOT changeset files, since registry components are not published as npm packages. - -## Package Changes: Changeset Files - -For changes to packages in the `packages/` directory, create changeset files as described below. - -## File Naming Convention - -Changeset files should be named descriptively, indicating the affected package/area and the nature of the change. - -`[package]-[change-type].md` - -Examples: - -- `alignment-major.md` -- `core-minor.md` -- `core-patch.md` -- `slate-major.md` -- `slate-minor.md` - -## Changeset File Structure - -Each changeset `.md` file must consist of two main parts: - -1. **YAML Frontmatter**: Specifies the package(s) affected and the type of change (`major`, `minor`, `patch`). -2. **Markdown Description**: Details the specific change(s) covered by this file. - -### 1. YAML Frontmatter - -The frontmatter is enclosed by ---` lines. It lists each affected package and its corresponding change type. - -**Syntax:** - -```yaml ---- -'@udecode/package-name': -# ... only list packages relevant to THIS specific changeset file ---- -``` - -- `` **MUST** be: - - `major`: For **breaking changes** ONLY. A change is breaking if users of the package need to change their code to upgrade (e.g., API renaming, function signature changes, removal of features, behavior changes that require user adaptation). - - `minor`: For **new features** or significant enhancements that are backward-compatible ONLY. - - `patch`: For **bug fixes** or very minor, backward-compatible changes ONLY. - -**Important Note on SemVer Bumping and Multiple Changesets:** - -- Each distinct change (breaking, feature, fix) for a package **MUST** have its own changeset file. -- A single changeset file **MUST** contain only ONE package in its frontmatter. -- For example, if you make a breaking API change, add a new feature, and fix a bug in `@platejs/core` for an upcoming release, you will create **three separate changeset files**: - 1. One file marked `major` for `@platejs/core` detailing only the breaking change. - 2. One file marked `minor` for `@platejs/core` detailing only the new feature. - 3. One file marked `patch` for `@platejs/core` detailing only the bug fix. -- The versioning tool will look at all pending changesets for a package. If there's at least one `major` file, the package gets a major version bump, and all changes (from major, minor, and patch files) are included in that release. If no `major` but at least one `minor`, it gets a minor bump, and so on. - -**Example: Multiple Changes, Multiple Packages** - -- If a PR adds a `minor` feature to `@platejs/core` and a `patch` fix to `@platejs/utils`, you MUST create TWO separate files: - - 1. `core-minor.md` (containing only `'@platejs/core': minor`) - 2. `utils-patch.md` (containing only `'@platejs/utils': patch`) - -- If a PR adds `minor` features to both `@platejs/core` and `@platejs/utils`, you MUST create TWO separate `minor` changeset files, one for each package. DO NOT group them in a single file. - -**Example (Illustrating multiple files for one package):** - -File 1: `core-major.md` - -````yaml ---- -'@platejs/core': major ---- -- Renamed `oldApi()` to `newApi()`. - ```ts - // Before - editor.oldApi(); - // After - editor.newApi(); -```` - -File 2: `core-minor.md` - -```yaml ---- -'@platejs/core': minor ---- -- Added `useCoreHook()` for enhanced state management. -``` - -File 3: `core-patch.md` - -```yaml ---- -'@platejs/core': patch ---- -- Fixed an issue where X would cause Y. -``` - -### 2. Markdown Description - -This section explains **what changed for the user** and **how they should adapt** for the _specific change described in this file_. The style should be direct and action-oriented. - -**General Guidelines:** - -- **Focus on Public API & User Impact:** Only document changes that affect the public API or require users to take action. **DO NOT** document internal refactorings. -- **Always use bullet points (-`)** to list out the specific change(s) pertinent to this changeset's type (major, minor, or patch). -- **Start Directly with a Verb:** Each bullet point should begin with a past tense verb describing the change (e.g., "Renamed...", "Added...", "Fixed...", "Removed..."). -- **Be Direct and Concise:** - - Get straight to the point. Avoid verbose explanations or justifications. - - If a code snippet clearly shows the "before" and "after," **let the code snippet speak for itself.** Minimize or eliminate redundant introductory text for code blocks. -- Use **bold text (`**text**`)** for emphasis on: - - Package names (e.g., `**@platejs/core**`) - - Plugin names (e.g., `**BlockquotePlugin**`) - - Important function, option, or property names being changed. -- Use **code blocks** (```tsx) for: - - Illustrating API changes (using "Before" / "After" comments inside the block). Snippets should be minimal and focused. - - Showing configuration snippets. - - Displaying relevant parts of type definitions. -- **Migration Guidance:** If a change requires user action (especially for `major` changes), provide clear, concise migration steps. - -**Specific Content Types (Structure within bullet points):** - -- **Breaking Changes (`major` changeset):** - - - - Renamed `EditorFragmentOptions.structuralTypes` to `EditorFragmentOptions.containerTypes` in `**@platejs/slate**`. - - ```ts - // Before - editor.api.fragment(editor.selection, { structuralTypes: ['div'] }); - - // After - editor.api.fragment(editor.selection, { containerTypes: ['div'] }); - ``` - - - - Removed `oldOption` from `PluginName` options. Use `newOption` instead. - - ```ts - // Before - createMyPlugin({ oldOption: true }); - - // After - createMyPlugin({ newOption: true }); - ``` - -- **Deprecations (can be `major` if immediate removal or `minor` if still available with warnings):** - - - If removed in this version (major): - Removed `**@platejs/old-package**`. Use `**@platejs/new-package**` instead. - - If only deprecated (minor): - Deprecated `**@platejs/old-package**`. It will be removed in a future version. Use `**@platejs/new-package**` instead. - - Provide migration steps: - - To migrate: - - Replace `**@platejs/old-package**` with `**@platejs/new-package**` in your `package.json`. - - Update import paths: - ```diff - - import { SomeFeature } from '@platejs/old-package'; - + import { SomeFeature } from '@platejs/new-package'; - ``` - ` - -- \*\*New Features / Enhancements (` diff --git a/.claude/commands/docs-plugin.md b/.claude/commands/docs-plugin.md deleted file mode 100644 index 0fbf0b1c6c..0000000000 --- a/.claude/commands/docs-plugin.md +++ /dev/null @@ -1,278 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - -# Plugin Documentation Guide - -## General Principles - -1. **Writing Style**: - - - Maintain a "shadcn-like straight to the point (but exhaustive is good) simple english easy to read for average english speakers" writing style. - - Adhere to the conciseness, tone, and style of existing key documentation files like [`index.mdx`](), [`installation.mdx`](), and especially [`plate-ui.mdx`](mdc:docs/installation/plate-ui.mdx). When mentioning [Plate UI](/docs/installation/plate-ui) for the first time in a document, ensure it is linked. - -2. **Headless Approach**: - - - Plugin documentation is **headless**. Do **not** assume users are using Plate UI files or components directly. - - Focus on documenting core editor/plugin usage, APIs, and transforms based **only** on actual plugin capabilities confirmed from the source code. - - When mentioning UI components, refer to them as examples of how a plugin's functionality can be rendered. Link to their Plate UI registry entries or component documentation pages if available (e.g., `/docs/components/component-name`). - - **Note for Style Plugins**: Some plugins, particularly "Style" plugins (e.g., for line height, font color), primarily function by injecting props into existing elements (like paragraphs or headings) rather than defining entirely new, distinct UI components. Their documentation in "Kit Usage" and "Manual Usage" sections will reflect this emphasis on configuration and prop injection. See documentation in `/docs/(guides)/plugin.mdx` if needed. - -3. **Structure and Formatting**: - - Use `` for procedural instructions (e.g., installation, usage steps). - - Use `###` for sub-headings within ``. - - Refer to [`dnd.mdx`]() as a primary example for structure and formatting. - - Employ ``, ``, ``, and `` for documenting plugin options, API methods, and transforms. Ensure all documented options and behaviors are accurate and sourced from the code. - - **Important**: When updating existing documentation, **preserve existing API formatting**. Do not change `` to `` or vice versa if they already exist and are working correctly. - -## Standard Section Order - -Plugin documentation should generally follow this order: - -1. **** (Optional): If a relevant visual demo exists in the project. -2. ****: - - **Features**: Bullet points summarizing key capabilities (derived from understanding the plugin). -3. **## Kit Usage** (If applicable and comes first since it's the fastest approach): - - - Enclose steps within ``. - - **### Installation**: - - - The fastest way to add the ... plugin is with the `...Kit`, which includes pre-configured `...Plugin` along with ... and their [Plate UI](/docs/installation/plate-ui) components. - - Include ``. - - Immediately follow with a bullet list of UI components from the kit relevant to the documented plugin, with links: - - Example: - - [`SpecificElement`](/docs/components/specific-node): Renders the main element. - - [`SpecificToolbarButton`](/docs/components/specific-toolbar-button): Provides a toolbar button. - - - **### Add Kit**: - - - Show how to add the kit to plugins: - - ```tsx - import { createPlateEditor } from 'platejs/react'; - import { RelevantKit } from '@/components/editor/plugins/relevant-kit-name'; - - const editor = createPlateEditor({ - plugins: [ - // ...otherPlugins, - ...RelevantKit, - ], - }); - ``` - -4. **## Manual Usage**: - - - Enclose steps within ``. - - **### Installation**: - - - The `npm install @platejs/package-name` command for the core plugin package. - - - **### Add Plugin** (or **Add Plugins** if documenting multiple plugins in this step): - - - Show the import statement for the specific plugin(s) being documented. - - Provide a basic code snippet demonstrating how to add the plugin to the `plugins` array within `createPlateEditor`. - - ```tsx - import { SpecificPlugin } from '@platejs/specific-package/react'; - import { createPlateEditor } from 'platejs/react'; - - const editor = createPlateEditor({ - plugins: [ - // ...otherPlugins, - SpecificPlugin, // Or SpecificPlugin.configure({}) if options are common at this stage - ], - }); - ``` - - - Note: `ParagraphPlugin` from `platejs/react` is included by default and usually doesn't need to be explicitly added unless overriding its component (e.g., `ParagraphPlugin.withComponent(CustomParagraph)`). - - - **### Configure Plugin** (or **Configure Plugins** if documenting multiple plugins in this step): - - - Detail essential configuration options for the specific plugin using code examples, based on what is available in the plugin's source. - - **For Element Plugins (with components):** - - - **Prioritize `withComponent`** when only assigning a component without other options: - - ```tsx - import { SpecificPlugin } from '@platejs/specific-package/react'; - import { createPlateEditor } from 'platejs/react'; - import { SpecificElement } from '@/components/ui/specific-node'; - - const editor = createPlateEditor({ - plugins: [ - // ...otherPlugins, - SpecificPlugin.withComponent(SpecificElement), - ], - }); - ``` - - - `withComponent`: Assigns [`SpecificElement`](/docs/components/specific-node) to render the plugin's elements. - - - Use `.configure()` when there are additional options beyond the component: - - ```tsx - import { SpecificPlugin } from '@platejs/specific-package/react'; - import { createPlateEditor } from 'platejs/react'; - import { SpecificElement } from '@/components/ui/specific-node'; - - const editor = createPlateEditor({ - plugins: [ - // ...otherPlugins, - SpecificPlugin.configure({ - node: { component: SpecificElement }, - shortcuts: { toggle: 'mod+alt+s' }, - // ...other actual options from the plugin source - }), - ], - }); - ``` - - - `node.component`: Assigns [`SpecificElement`](/docs/components/specific-node) to render the plugin's elements. - - `shortcuts.toggle`: Defines a keyboard [shortcut](/docs/plugin-shortcuts) to toggle the feature. - - (Explain other demonstrated options based on their actual function in the plugin) - - **For Style Plugins (without distinct components):** - - - Focus on configuration options like `inject.nodeProps`, default values, and target elements: - - ```tsx - import { SpecificPlugin } from '@platejs/specific-package/react'; - import { createPlateEditor } from 'platejs/react'; - - const editor = createPlateEditor({ - plugins: [ - // ...otherPlugins, - SpecificPlugin.configure({ - inject: { - nodeProps: { - defaultNodeValue: 'defaultValue', - // ...other nodeProps options - }, - targetPlugins: ['p', 'h1', 'h2'], - }, - }), - ], - }); - ``` - - - `inject.nodeProps.defaultNodeValue`: Sets the default value for the styling property. - - `inject.targetPlugins`: Specifies which element types receive the styling. - - - **### Add to Toolbar Buttons** (For Element Plugins): - - - For element plugins that can be turned into or inserted, add sections showing how to integrate with specialized toolbar buttons: - - **Turn Into Toolbar Button:** - - ````markdown - ### Turn Into Toolbar Button - - You can add these items to the [Turn Into Toolbar Button](/docs/toolbar#turn-into-toolbar-button) to convert blocks into [elements]: - - ```tsx - { - icon: , - keywords: ['keyword1', 'keyword2', 'symbol'], - label: 'Element Name', - value: KEYS.elementKey, - } - ``` - ```` - - **Insert Toolbar Button:** - - ````markdown - ### Insert Toolbar Button - - You can add these items to the [Insert Toolbar Button](/docs/toolbar#insert-toolbar-button) to insert [element] elements: - - ```tsx - { - icon: , - label: 'Element Name', - value: KEYS.elementKey, - } - ``` - ```` - - - Only include these sections for element plugins that make sense in these contexts (blocks, lists, etc.) - - Use appropriate action verbs: "toggle" for turn-into, "insert" for insert - - - **### Add Toolbar Button** (If the kit includes a toolbar button): - - Include "You can add [`*ToolbarButton`](/docs/components/*-toolbar-button) to your [Toolbar](/docs/toolbar) to ." - - Check `registry-kits.ts` to see if a `*-toolbar-button` is part of the kit's dependencies. - -5. **## Plugins**: - - **### `PluginName`**: Document each relevant plugin with a simple description (e.g., "Plugin for H1 heading elements"). - - For plugins with significant configuration options, optionally use `` and `` to detail **actual, existing** configurable options as found in the source code. - - Do not extend this section with invented options if the user didn't ask for it. -6. **## API** (If applicable): - - Document **actual, existing** `editor.api.pluginName.*` functions as found in the source code. - - Use `### api.` for each function. - - Do not extend this section if the user didn't ask for it. -7. **## Transforms** (If applicable): - - Document **actual, existing** `editor.tf.pluginName.*` functions, describing their precise behavior as observed in the source code. - - Use `### tf.` for each function. - - Do not extend this section if the user didn't ask for it. - -## Linking and Redundancy - -- Prioritize linking to individual, specific documentation pages (e.g., for a sub-plugin or a related concept) to avoid content duplication. Instead of a generic "See plugin guide", try to smartly link a relevant word or phrase within a sentence to the target page. -- All docs: - - **Get Started:** - - - [`/docs`](): Introduction to Plate - - [`/docs/installation`](): Getting Started / Installation Overview - - **Installation Details:** - - - [`/docs/installation/plate-ui`](mdc:docs/installation/plate-ui.mdx): Plate UI Installation - - [`/docs/installation/next`](mdc:docs/installation/next.mdx): Next.js Setup - - `/docs/installation/rsc`: React Server Components (RSC) - - `/docs/installation/node`: Node.js Usage - - `/docs/installation/mcp`: MCP Server - - **Guides:** - - - [`/docs/plugin`](mdc:docs/plugin.mdx): Plugin System Overview - - [`/docs/plugin-methods`](mdc:docs/plugin-methods.mdx): Plugin Methods - - [`/docs/plugin-shortcuts`](mdc:docs/plugin-shortcuts.mdx): Plugin Shortcuts - - [`/docs/plugin-context`](mdc:docs/plugin-context.mdx): Plugin Context - - [`/docs/plugin-components`](mdc:docs/plugin-components.mdx): Plugin Components Guide - - [`/docs/editor`](mdc:docs/editor.mdx): Editor Configuration - - [`/docs/editor-methods`](mdc:docs/editor-methods.mdx): Editor Methods - - `/docs/controlled`: Controlled Editor Value - - [`/docs/static`](mdc:docs/static.mdx): Static Rendering - - [`/docs/html`](): HTML Serialization (Guide) - - [`/docs/markdown`](): Markdown Serialization (Guide) - - `/docs/form`: Form Integration - - `/docs/typescript`: TypeScript Usage - - `/docs/debugging`: Debugging - - `/docs/troubleshooting`: Troubleshooting - - **Plugins (Overview & Individual - see `apps/www/src/config/docs-plugins.ts` for full list):** - - - [`/docs/plugins`](mdc:apps/www/src/config/docs-plugins.ts): Plugins Overview (links to specific plugin docs) - - **Components:** - - - [`/docs/components`](mdc:apps/www/src/registry/registry-ui.ts): UI Components Overview (Toolbar, Nodes, etc.) - - Links to individual component pages like `/docs/components/blockquote-node`, etc. - - **Kits:** - - - [Plugin Kits](mdc:apps/www/src/registry/registry-kits.ts): All plugin kits - - Links to individual kit pages like `/docs/kits/basic-blocks-kit`, etc. - - **API Reference:** - - - `/docs/api`: API Overview - - _Core:_ `/docs/api/core/plate-editor`, `/docs/api/core/plate-plugin`, `/docs/api/core/plate-components`, `/docs/api/core/plate-store`, `/docs/api/core/plate-controller` - - _Slate Extensions:_ `/docs/api/slate` (and its sub-pages like `/docs/api/slate/editor-api` - - _Utilities:_ `/docs/api/utils`, `/docs/api/cn`, `/docs/api/floating`, `/docs/api/react-utils`, `/docs/api/resizable` - -By following this guide, plugin documentation will be consistent, informative, and align with the project's overall documentation strategy. diff --git a/.claude/commands/sync-testing-skill.md b/.claude/commands/sync-testing-skill.md deleted file mode 100644 index 2cfd6aafff..0000000000 --- a/.claude/commands/sync-testing-skill.md +++ /dev/null @@ -1,18 +0,0 @@ -Scan package tests, update @.claude/rules/testing.mdc with new patterns. Follow writing-skills: DRY, ultra-concise, token-efficient. - -**Process:** - -1. Glob `packages/[PACKAGE]/**/*.spec.{ts,tsx}` -2. Read 6-8 diverse tests: plugins, transforms, components, hooks, utils -3. Identify patterns NOT in testing.mdc: - - Imports (ONLY `mock`/`spyOn` from bun:test - describe/it/expect are global) - - Test organization, mocking, assertions, RTL, edge cases -4. Update testing.mdc: Add to existing sections (DRY), one example per pattern, Quick Reference if frequent -5. Report: `Found X patterns → Updated Y sections` - -**Critical:** -- Only actual codebase patterns. No theoretical examples. No duplication. -- Test globals (`describe`, `it`, `expect`, etc.) are global via `tooling/config/global.d.ts` - NO imports needed -- ONLY import `mock` and `spyOn` when used - -**Package:** Specify path (e.g., `packages/media`) diff --git a/.claude/commands/translate.md b/.claude/commands/translate.md deleted file mode 100644 index 77236bf608..0000000000 --- a/.claude/commands/translate.md +++ /dev/null @@ -1,47 +0,0 @@ -You are a professional translator. Translate/Synchronize the following MDX content from English to cn. -Preserve all Markdown formatting, code blocks, and component tags. Do not translate code inside code blocks or component names. -Filename for .mdx (English) = .cn.mdx (Chinese) -The content is in .mdx format, which combines Markdown with JSX components. - -# Important Notice - -1. **Only translate/sync the DIFF** - Compare English source with existing Chinese translation, only update changed parts. DO NOT re-translate the entire file. -2. DO NOT remove any content. -3. You can translate the title markdown ## Plugin Context. - -For Example: - -xxxx content - -```ts -(api: (ctx: PlatePluginContext) => any) => PlatePlugin; -``` - - - -After translate: - -xxxx 内容 - -```ts -(api: (ctx: PlatePluginContext) => any) => PlatePlugin; -``` - - - - -# How to Determine Which Files Need to Be Updated - -Calculate: today's date - last document modification date = days - -```bash -./tooling/scripts/list-translate-files.sh [days] -``` - -Example: today is 2026-01-01, last date is 2025-08-01 → ~153 days - -```bash -./tooling/scripts/list-translate-files.sh 153 -``` - -Last document modification date: **2026-01-18** (After completing the translation, automatically update this date to today's date.) \ No newline at end of file diff --git a/.claude/docs/docs-api.md b/.claude/docs/docs-api.md deleted file mode 100644 index 21aef9772e..0000000000 --- a/.claude/docs/docs-api.md +++ /dev/null @@ -1,475 +0,0 @@ -Goal: -Migrate the existing API doc to the new format. - -Return Format: -Follow the existing pattern with: - -- title: add backticks if missing and if it's a constant/function/plugin name. Add `<>` if it's a component name (e.g. ` + +<% end %> +``` + +```javascript +// Reveal user-specific elements after cache hit +export default class extends Controller { + static values = { currentUser: Number } + static targets = ["ownerOnly"] + + connect() { + const creatorId = parseInt(this.element.dataset.creatorId) + if (creatorId === this.currentUserValue) { + this.ownerOnlyTargets.forEach(el => el.classList.remove("hidden")) + } + } +} +``` + +**Extract dynamic content** to separate frames: +```erb +<% cache [card, board] do %> +
+ <%= turbo_frame_tag card, :assignment, + src: card_assignment_path(card), + refresh: :morph %> +
+<% end %> +``` + +Assignment dropdown updates independently without invalidating parent cache. + + + +## Broadcasting with Turbo Streams + +**Model callbacks** for real-time updates: +```ruby +class Card < ApplicationRecord + include Broadcastable + + after_create_commit :broadcast_created + after_update_commit :broadcast_updated + after_destroy_commit :broadcast_removed + + private + def broadcast_created + broadcast_append_to [Current.account, board], :cards + end + + def broadcast_updated + broadcast_replace_to [Current.account, board], :cards + end + + def broadcast_removed + broadcast_remove_to [Current.account, board], :cards + end +end +``` + +**Scope by tenant** using `[Current.account, resource]` pattern. + diff --git a/.cursor/skills/dhh-rails-style/references/gems.md b/.cursor/skills/dhh-rails-style/references/gems.md new file mode 100644 index 0000000000..00933b97b7 --- /dev/null +++ b/.cursor/skills/dhh-rails-style/references/gems.md @@ -0,0 +1,266 @@ +# Gems - DHH Rails Style + + +## What 37signals Uses + +**Core Rails stack:** +- turbo-rails, stimulus-rails, importmap-rails +- propshaft (asset pipeline) + +**Database-backed services (Solid suite):** +- solid_queue - background jobs +- solid_cache - caching +- solid_cable - WebSockets/Action Cable + +**Authentication & Security:** +- bcrypt (for any password hashing needed) + +**Their own gems:** +- geared_pagination (cursor-based pagination) +- lexxy (rich text editor) +- mittens (mailer utilities) + +**Utilities:** +- rqrcode (QR code generation) +- redcarpet + rouge (Markdown rendering) +- web-push (push notifications) + +**Deployment & Operations:** +- kamal (Docker deployment) +- thruster (HTTP/2 proxy) +- mission_control-jobs (job monitoring) +- autotuner (GC tuning) + + + +## What They Deliberately Avoid + +**Authentication:** +``` +devise → Custom ~150-line auth +``` +Why: Full control, no password liability with magic links, simpler. + +**Authorization:** +``` +pundit/cancancan → Simple role checks in models +``` +Why: Most apps don't need policy objects. A method on the model suffices: +```ruby +class Board < ApplicationRecord + def editable_by?(user) + user.admin? || user == creator + end +end +``` + +**Background Jobs:** +``` +sidekiq → Solid Queue +``` +Why: Database-backed means no Redis, same transactional guarantees. + +**Caching:** +``` +redis → Solid Cache +``` +Why: Database is already there, simpler infrastructure. + +**Search:** +``` +elasticsearch → Custom sharded search +``` +Why: Built exactly what they need, no external service dependency. + +**View Layer:** +``` +view_component → Standard partials +``` +Why: Partials work fine. ViewComponents add complexity without clear benefit for their use case. + +**API:** +``` +GraphQL → REST with Turbo +``` +Why: REST is sufficient when you control both ends. GraphQL complexity not justified. + +**Factories:** +``` +factory_bot → Fixtures +``` +Why: Fixtures are simpler, faster, and encourage thinking about data relationships upfront. + +**Service Objects:** +``` +Interactor, Trailblazer → Fat models +``` +Why: Business logic stays in models. Methods like `card.close` instead of `CardCloser.call(card)`. + +**Form Objects:** +``` +Reform, dry-validation → params.expect + model validations +``` +Why: Rails 7.1's `params.expect` is clean enough. Contextual validations on model. + +**Decorators:** +``` +Draper → View helpers + partials +``` +Why: Helpers and partials are simpler. No decorator indirection. + +**CSS:** +``` +Tailwind, Sass → Native CSS +``` +Why: Modern CSS has nesting, variables, layers. No build step needed. + +**Frontend:** +``` +React, Vue, SPAs → Turbo + Stimulus +``` +Why: Server-rendered HTML with sprinkles of JS. SPA complexity not justified. + +**Testing:** +``` +RSpec → Minitest +``` +Why: Simpler, faster boot, less DSL magic, ships with Rails. + + + +## Testing Philosophy + +**Minitest** - simpler, faster: +```ruby +class CardTest < ActiveSupport::TestCase + test "closing creates closure" do + card = cards(:one) + assert_difference -> { Card::Closure.count } do + card.close + end + assert card.closed? + end +end +``` + +**Fixtures** - loaded once, deterministic: +```yaml +# test/fixtures/cards.yml +open_card: + title: Open Card + board: main + creator: alice + +closed_card: + title: Closed Card + board: main + creator: bob +``` + +**Dynamic timestamps** with ERB: +```yaml +recent: + title: Recent + created_at: <%= 1.hour.ago %> + +old: + title: Old + created_at: <%= 1.month.ago %> +``` + +**Time travel** for time-dependent tests: +```ruby +test "expires after 15 minutes" do + magic_link = MagicLink.create!(user: users(:alice)) + + travel 16.minutes + + assert magic_link.expired? +end +``` + +**VCR** for external APIs: +```ruby +VCR.use_cassette("stripe/charge") do + charge = Stripe::Charge.create(amount: 1000) + assert charge.paid +end +``` + +**Tests ship with features** - same commit, not before or after. + + + +## Decision Framework + +Before adding a gem, ask: + +1. **Can vanilla Rails do this?** + - ActiveRecord can do most things Sequel can + - ActionMailer handles email fine + - ActiveJob works for most job needs + +2. **Is the complexity worth it?** + - 150 lines of custom code vs. 10,000-line gem + - You'll understand your code better + - Fewer upgrade headaches + +3. **Does it add infrastructure?** + - Redis? Consider database-backed alternatives + - External service? Consider building in-house + - Simpler infrastructure = fewer failure modes + +4. **Is it from someone you trust?** + - 37signals gems: battle-tested at scale + - Well-maintained, focused gems: usually fine + - Kitchen-sink gems: probably overkill + +**The philosophy:** +> "Build solutions before reaching for gems." + +Not anti-gem, but pro-understanding. Use gems when they genuinely solve a problem you have, not a problem you might have. + + + +## Gem Usage Patterns + +**Pagination:** +```ruby +# geared_pagination - cursor-based +class CardsController < ApplicationController + def index + @cards = @board.cards.geared(page: params[:page]) + end +end +``` + +**Markdown:** +```ruby +# redcarpet + rouge +class MarkdownRenderer + def self.render(text) + Redcarpet::Markdown.new( + Redcarpet::Render::HTML.new(filter_html: true), + autolink: true, + fenced_code_blocks: true + ).render(text) + end +end +``` + +**Background jobs:** +```ruby +# solid_queue - no Redis +class ApplicationJob < ActiveJob::Base + queue_as :default + # Just works, backed by database +end +``` + +**Caching:** +```ruby +# solid_cache - no Redis +# config/environments/production.rb +config.cache_store = :solid_cache_store +``` + diff --git a/.cursor/skills/dhh-rails-style/references/models.md b/.cursor/skills/dhh-rails-style/references/models.md new file mode 100644 index 0000000000..4a8a15d8df --- /dev/null +++ b/.cursor/skills/dhh-rails-style/references/models.md @@ -0,0 +1,359 @@ +# Models - DHH Rails Style + + +## Concerns for Horizontal Behavior + +Models heavily use concerns. A typical Card model includes 14+ concerns: + +```ruby +class Card < ApplicationRecord + include Assignable + include Attachments + include Broadcastable + include Closeable + include Colored + include Eventable + include Golden + include Mentions + include Multistep + include Pinnable + include Postponable + include Readable + include Searchable + include Taggable + include Watchable +end +``` + +Each concern is self-contained with associations, scopes, and methods. + +**Naming:** Adjectives describing capability (`Closeable`, `Publishable`, `Watchable`) + + + +## State as Records, Not Booleans + +Instead of boolean columns, create separate records: + +```ruby +# Instead of: +closed: boolean +is_golden: boolean +postponed: boolean + +# Create records: +class Card::Closure < ApplicationRecord + belongs_to :card + belongs_to :creator, class_name: "User" +end + +class Card::Goldness < ApplicationRecord + belongs_to :card + belongs_to :creator, class_name: "User" +end + +class Card::NotNow < ApplicationRecord + belongs_to :card + belongs_to :creator, class_name: "User" +end +``` + +**Benefits:** +- Automatic timestamps (when it happened) +- Track who made changes +- Easy filtering via joins and `where.missing` +- Enables rich UI showing when/who + +**In the model:** +```ruby +module Closeable + extend ActiveSupport::Concern + + included do + has_one :closure, dependent: :destroy + end + + def closed? + closure.present? + end + + def close(creator: Current.user) + create_closure!(creator: creator) + end + + def reopen + closure&.destroy + end +end +``` + +**Querying:** +```ruby +Card.joins(:closure) # closed cards +Card.where.missing(:closure) # open cards +``` + + + +## Callbacks - Used Sparingly + +Only 38 callback occurrences across 30 files in Fizzy. Guidelines: + +**Use for:** +- `after_commit` for async work +- `before_save` for derived data +- `after_create_commit` for side effects + +**Avoid:** +- Complex callback chains +- Business logic in callbacks +- Synchronous external calls + +```ruby +class Card < ApplicationRecord + after_create_commit :notify_watchers_later + before_save :update_search_index, if: :title_changed? + + private + def notify_watchers_later + NotifyWatchersJob.perform_later(self) + end +end +``` + + + +## Scope Naming + +Standard scope names: + +```ruby +class Card < ApplicationRecord + scope :chronologically, -> { order(created_at: :asc) } + scope :reverse_chronologically, -> { order(created_at: :desc) } + scope :alphabetically, -> { order(title: :asc) } + scope :latest, -> { reverse_chronologically.limit(10) } + + # Standard eager loading + scope :preloaded, -> { includes(:creator, :assignees, :tags) } + + # Parameterized + scope :indexed_by, ->(column) { order(column => :asc) } + scope :sorted_by, ->(column, direction = :asc) { order(column => direction) } +end +``` + + + +## Plain Old Ruby Objects + +POROs namespaced under parent models: + +```ruby +# app/models/event/description.rb +class Event::Description + def initialize(event) + @event = event + end + + def to_s + # Presentation logic for event description + end +end + +# app/models/card/eventable/system_commenter.rb +class Card::Eventable::SystemCommenter + def initialize(card) + @card = card + end + + def comment(message) + # Business logic + end +end + +# app/models/user/filtering.rb +class User::Filtering + # View context bundling +end +``` + +**NOT used for service objects.** Business logic stays in models. + + + +## Method Naming + +**Verbs** - Actions that change state: +```ruby +card.close +card.reopen +card.gild # make golden +card.ungild +board.publish +board.archive +``` + +**Predicates** - Queries derived from state: +```ruby +card.closed? # closure.present? +card.golden? # goldness.present? +board.published? +``` + +**Avoid** generic setters: +```ruby +# Bad +card.set_closed(true) +card.update_golden_status(false) + +# Good +card.close +card.ungild +``` + + + +## Validation Philosophy + +Minimal validations on models. Use contextual validations on form/operation objects: + +```ruby +# Model - minimal +class User < ApplicationRecord + validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } +end + +# Form object - contextual +class Signup + include ActiveModel::Model + + attr_accessor :email, :name, :terms_accepted + + validates :email, :name, presence: true + validates :terms_accepted, acceptance: true + + def save + return false unless valid? + User.create!(email: email, name: name) + end +end +``` + +**Prefer database constraints** over model validations for data integrity: +```ruby +# migration +add_index :users, :email, unique: true +add_foreign_key :cards, :boards +``` + + + +## Let It Crash Philosophy + +Use bang methods that raise exceptions on failure: + +```ruby +# Preferred - raises on failure +@card = Card.create!(card_params) +@card.update!(title: new_title) +@comment.destroy! + +# Avoid - silent failures +@card = Card.create(card_params) # returns false on failure +if @card.save + # ... +end +``` + +Let errors propagate naturally. Rails handles ActiveRecord::RecordInvalid with 422 responses. + + + +## Default Values with Lambdas + +Use lambda defaults for associations with Current: + +```ruby +class Card < ApplicationRecord + belongs_to :creator, class_name: "User", default: -> { Current.user } + belongs_to :account, default: -> { Current.account } +end + +class Comment < ApplicationRecord + belongs_to :commenter, class_name: "User", default: -> { Current.user } +end +``` + +Lambdas ensure dynamic resolution at creation time. + + + +## Rails 7.1+ Model Patterns + +**Normalizes** - clean data before validation: +```ruby +class User < ApplicationRecord + normalizes :email, with: ->(email) { email.strip.downcase } + normalizes :phone, with: ->(phone) { phone.gsub(/\D/, "") } +end +``` + +**Delegated Types** - replace polymorphic associations: +```ruby +class Message < ApplicationRecord + delegated_type :messageable, types: %w[Comment Reply Announcement] +end + +# Now you get: +message.comment? # true if Comment +message.comment # returns the Comment +Message.comments # scope for Comment messages +``` + +**Store Accessor** - structured JSON storage: +```ruby +class User < ApplicationRecord + store :settings, accessors: [:theme, :notifications_enabled], coder: JSON +end + +user.theme = "dark" +user.notifications_enabled = true +``` + + + +## Concern Guidelines + +- **50-150 lines** per concern (most are ~100) +- **Cohesive** - related functionality only +- **Named for capabilities** - `Closeable`, `Watchable`, not `CardHelpers` +- **Self-contained** - associations, scopes, methods together +- **Not for mere organization** - create when genuine reuse needed + +**Touch chains** for cache invalidation: +```ruby +class Comment < ApplicationRecord + belongs_to :card, touch: true +end + +class Card < ApplicationRecord + belongs_to :board, touch: true +end +``` + +When comment updates, card's `updated_at` changes, which cascades to board. + +**Transaction wrapping** for related updates: +```ruby +class Card < ApplicationRecord + def close(creator: Current.user) + transaction do + create_closure!(creator: creator) + record_event(:closed) + notify_watchers_later + end + end +end +``` + diff --git a/.cursor/skills/dhh-rails-style/references/testing.md b/.cursor/skills/dhh-rails-style/references/testing.md new file mode 100644 index 0000000000..4316fada58 --- /dev/null +++ b/.cursor/skills/dhh-rails-style/references/testing.md @@ -0,0 +1,338 @@ +# Testing - DHH Rails Style + +## Core Philosophy + +"Minitest with fixtures - simple, fast, deterministic." The approach prioritizes pragmatism over convention. + +## Why Minitest Over RSpec + +- **Simpler**: Less DSL magic, plain Ruby assertions +- **Ships with Rails**: No additional dependencies +- **Faster boot times**: Less overhead +- **Plain Ruby**: No specialized syntax to learn + +## Fixtures as Test Data + +Rather than factories, fixtures provide preloaded data: +- Loaded once, reused across tests +- No runtime object creation overhead +- Explicit relationship visibility +- Deterministic IDs for easier debugging + +### Fixture Structure +```yaml +# test/fixtures/users.yml +david: + identity: david + account: basecamp + role: admin + +jason: + identity: jason + account: basecamp + role: member + +# test/fixtures/rooms.yml +watercooler: + name: Water Cooler + creator: david + direct: false + +# test/fixtures/messages.yml +greeting: + body: Hello everyone! + room: watercooler + creator: david +``` + +### Using Fixtures in Tests +```ruby +test "sending a message" do + user = users(:david) + room = rooms(:watercooler) + + # Test with fixture data +end +``` + +### Dynamic Fixture Values +ERB enables time-sensitive data: +```yaml +recent_card: + title: Recent Card + created_at: <%= 1.hour.ago %> + +old_card: + title: Old Card + created_at: <%= 1.month.ago %> +``` + +## Test Organization + +### Unit Tests +Verify business logic using setup blocks and standard assertions: + +```ruby +class CardTest < ActiveSupport::TestCase + setup do + @card = cards(:one) + @user = users(:david) + end + + test "closing a card creates a closure" do + assert_difference -> { Card::Closure.count } do + @card.close(creator: @user) + end + + assert @card.closed? + assert_equal @user, @card.closure.creator + end + + test "reopening a card destroys the closure" do + @card.close(creator: @user) + + assert_difference -> { Card::Closure.count }, -1 do + @card.reopen + end + + refute @card.closed? + end +end +``` + +### Integration Tests +Test full request/response cycles: + +```ruby +class CardsControllerTest < ActionDispatch::IntegrationTest + setup do + @user = users(:david) + sign_in @user + end + + test "closing a card" do + card = cards(:one) + + post card_closure_path(card) + + assert_response :success + assert card.reload.closed? + end + + test "unauthorized user cannot close card" do + sign_in users(:guest) + card = cards(:one) + + post card_closure_path(card) + + assert_response :forbidden + refute card.reload.closed? + end +end +``` + +### System Tests +Browser-based tests using Capybara: + +```ruby +class MessagesTest < ApplicationSystemTestCase + test "sending a message" do + sign_in users(:david) + visit room_path(rooms(:watercooler)) + + fill_in "Message", with: "Hello, world!" + click_button "Send" + + assert_text "Hello, world!" + end + + test "editing own message" do + sign_in users(:david) + visit room_path(rooms(:watercooler)) + + within "#message_#{messages(:greeting).id}" do + click_on "Edit" + end + + fill_in "Message", with: "Updated message" + click_button "Save" + + assert_text "Updated message" + end + + test "drag and drop card to new column" do + sign_in users(:david) + visit board_path(boards(:main)) + + card = find("#card_#{cards(:one).id}") + target = find("#column_#{columns(:done).id}") + + card.drag_to target + + assert_selector "#column_#{columns(:done).id} #card_#{cards(:one).id}" + end +end +``` + +## Advanced Patterns + +### Time Testing +Use `travel_to` for deterministic time-dependent assertions: + +```ruby +test "card expires after 30 days" do + card = cards(:one) + + travel_to 31.days.from_now do + assert card.expired? + end +end +``` + +### External API Testing with VCR +Record and replay HTTP interactions: + +```ruby +test "fetches user data from API" do + VCR.use_cassette("user_api") do + user_data = ExternalApi.fetch_user(123) + + assert_equal "John", user_data[:name] + end +end +``` + +### Background Job Testing +Assert job enqueueing and email delivery: + +```ruby +test "closing card enqueues notification job" do + card = cards(:one) + + assert_enqueued_with(job: NotifyWatchersJob, args: [card]) do + card.close + end +end + +test "welcome email is sent on signup" do + assert_emails 1 do + Identity.create!(email: "new@example.com") + end +end +``` + +### Testing Turbo Streams +```ruby +test "message creation broadcasts to room" do + room = rooms(:watercooler) + + assert_turbo_stream_broadcasts [room, :messages] do + room.messages.create!(body: "Test", creator: users(:david)) + end +end +``` + +## Testing Principles + +### 1. Test Observable Behavior +Focus on what the code does, not how it does it: + +```ruby +# ❌ Testing implementation +test "calls notify method on each watcher" do + card.expects(:notify).times(3) + card.close +end + +# ✅ Testing behavior +test "watchers receive notifications when card closes" do + assert_difference -> { Notification.count }, 3 do + card.close + end +end +``` + +### 2. Don't Mock Everything + +```ruby +# ❌ Over-mocked test +test "sending message" do + room = mock("room") + user = mock("user") + message = mock("message") + + room.expects(:messages).returns(stub(create!: message)) + message.expects(:broadcast_create) + + MessagesController.new.create +end + +# ✅ Test the real thing +test "sending message" do + sign_in users(:david) + post room_messages_url(rooms(:watercooler)), + params: { message: { body: "Hello" } } + + assert_response :success + assert Message.exists?(body: "Hello") +end +``` + +### 3. Tests Ship with Features +Same commit, not TDD-first but together. Neither before (strict TDD) nor after (deferred testing). + +### 4. Security Fixes Always Include Regression Tests +Every security fix must include a test that would have caught the vulnerability. + +### 5. Integration Tests Validate Complete Workflows +Don't just test individual pieces - test that they work together. + +## File Organization + +``` +test/ +├── controllers/ # Integration tests for controllers +├── fixtures/ # YAML fixtures for all models +├── helpers/ # Helper method tests +├── integration/ # API integration tests +├── jobs/ # Background job tests +├── mailers/ # Mailer tests +├── models/ # Unit tests for models +├── system/ # Browser-based system tests +└── test_helper.rb # Test configuration +``` + +## Test Helper Setup + +```ruby +# test/test_helper.rb +ENV["RAILS_ENV"] ||= "test" +require_relative "../config/environment" +require "rails/test_help" + +class ActiveSupport::TestCase + fixtures :all + + parallelize(workers: :number_of_processors) +end + +class ActionDispatch::IntegrationTest + include SignInHelper +end + +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome +end +``` + +## Sign In Helper + +```ruby +# test/support/sign_in_helper.rb +module SignInHelper + def sign_in(user) + session = user.identity.sessions.create! + cookies.signed[:session_id] = session.id + end +end +``` diff --git a/.cursor/skills/dig/SKILL.md b/.cursor/skills/dig/SKILL.md new file mode 100644 index 0000000000..40c4994ad7 --- /dev/null +++ b/.cursor/skills/dig/SKILL.md @@ -0,0 +1,60 @@ +--- +name: dig +description: Look up documentation and source code for libraries and packages. Use when the user asks a question about a library, needs to understand a library's API, or when you need information about a library that you don't know about. Triggers on questions like "How do I use X library?", "What's the API for Y?", "Show me how Z library handles this", or when encountering unfamiliar library usage. +--- + +# Dig + +Look up library documentation by finding and exploring the library's source code repository. + +## Workflow + +### 1. Check for Local Availability + +First, check if the library source code already exists locally: + +```bash +# Check common locations +ls /tmp/cc-repos/{library-name} 2>/dev/null +``` + +If the library exists locally, skip to step 3. + +### 2. Clone the Repository + +If not available locally, find and clone the repository: + +1. Search for the library's GitHub repository (most libraries are on GitHub) +2. Clone into the standard location: + +```bash +mkdir -p /tmp/cc-repos +git clone https://github.com/{owner}/{repo}.git /tmp/cc-repos/{repo-name} +``` + +**Common repository patterns:** + +- npm packages: Check `package.json` homepage or repository field, or search `https://github.com/{package-name}` +- Python packages: Check PyPI page for "Homepage" or "Source" links +- Go packages: The import path often is the repository URL +- Rust crates: Check crates.io for repository link + +### 3. Research the Repository + +Launch a Research agent (using the Task tool with `subagent_type="Explore"`) to traverse the repository and answer the question. + +Example prompt for the agent: + +``` +Explore the repository at /tmp/cc-repos/{repo-name} to answer: {user's question} + +Focus on: +- README and documentation files +- Source code structure +- API exports and public interfaces +- Examples and tests for usage patterns +``` + +### 4. Synthesize and Answer + +Use the research findings to provide a clear, accurate answer to the user's question about the library. diff --git a/.codex/skills/document-review/SKILL.md b/.cursor/skills/document-review/SKILL.md similarity index 100% rename from .codex/skills/document-review/SKILL.md rename to .cursor/skills/document-review/SKILL.md diff --git a/.cursor/skills/dspy-ruby/SKILL.md b/.cursor/skills/dspy-ruby/SKILL.md new file mode 100644 index 0000000000..577c72cfe0 --- /dev/null +++ b/.cursor/skills/dspy-ruby/SKILL.md @@ -0,0 +1,737 @@ +--- +name: dspy-ruby +description: Build type-safe LLM applications with DSPy.rb — Ruby's programmatic prompt framework with signatures, modules, agents, and optimization. Use when implementing predictable AI features, creating LLM signatures and modules, configuring language model providers, building agent systems with tools, optimizing prompts, or testing LLM-powered functionality in Ruby applications. +--- + +# DSPy.rb + +> Build LLM apps like you build software. Type-safe, modular, testable. + +DSPy.rb brings software engineering best practices to LLM development. Instead of tweaking prompts, define what you want with Ruby types and let DSPy handle the rest. + +## Overview + +DSPy.rb is a Ruby framework for building language model applications with programmatic prompts. It provides: + +- **Type-safe signatures** — Define inputs/outputs with Sorbet types +- **Modular components** — Compose and reuse LLM logic +- **Automatic optimization** — Use data to improve prompts, not guesswork +- **Production-ready** — Built-in observability, testing, and error handling + +## Core Concepts + +### 1. Signatures + +Define interfaces between your app and LLMs using Ruby types: + +```ruby +class EmailClassifier < DSPy::Signature + description "Classify customer support emails by category and priority" + + class Priority < T::Enum + enums do + Low = new('low') + Medium = new('medium') + High = new('high') + Urgent = new('urgent') + end + end + + input do + const :email_content, String + const :sender, String + end + + output do + const :category, String + const :priority, Priority # Type-safe enum with defined values + const :confidence, Float + end +end +``` + +### 2. Modules + +Build complex workflows from simple building blocks: + +- **Predict** — Basic LLM calls with signatures +- **ChainOfThought** — Step-by-step reasoning +- **ReAct** — Tool-using agents +- **CodeAct** — Dynamic code generation agents (install the `dspy-code_act` gem) + +### 3. Tools & Toolsets + +Create type-safe tools for agents with comprehensive Sorbet support: + +```ruby +# Enum-based tool with automatic type conversion +class CalculatorTool < DSPy::Tools::Base + tool_name 'calculator' + tool_description 'Performs arithmetic operations with type-safe enum inputs' + + class Operation < T::Enum + enums do + Add = new('add') + Subtract = new('subtract') + Multiply = new('multiply') + Divide = new('divide') + end + end + + sig { params(operation: Operation, num1: Float, num2: Float).returns(T.any(Float, String)) } + def call(operation:, num1:, num2:) + case operation + when Operation::Add then num1 + num2 + when Operation::Subtract then num1 - num2 + when Operation::Multiply then num1 * num2 + when Operation::Divide + return "Error: Division by zero" if num2 == 0 + num1 / num2 + end + end +end + +# Multi-tool toolset with rich types +class DataToolset < DSPy::Tools::Toolset + toolset_name "data_processing" + + class Format < T::Enum + enums do + JSON = new('json') + CSV = new('csv') + XML = new('xml') + end + end + + tool :convert, description: "Convert data between formats" + tool :validate, description: "Validate data structure" + + sig { params(data: String, from: Format, to: Format).returns(String) } + def convert(data:, from:, to:) + "Converted from #{from.serialize} to #{to.serialize}" + end + + sig { params(data: String, format: Format).returns(T::Hash[String, T.any(String, Integer, T::Boolean)]) } + def validate(data:, format:) + { valid: true, format: format.serialize, row_count: 42, message: "Data validation passed" } + end +end +``` + +### 4. Type System & Discriminators + +DSPy.rb uses sophisticated type discrimination for complex data structures: + +- **Automatic `_type` field injection** — DSPy adds discriminator fields to structs for type safety +- **Union type support** — `T.any()` types automatically disambiguated by `_type` +- **Reserved field name** — Avoid defining your own `_type` fields in structs +- **Recursive filtering** — `_type` fields filtered during deserialization at all nesting levels + +### 5. Optimization + +Improve accuracy with real data: + +- **MIPROv2** — Advanced multi-prompt optimization with bootstrap sampling and Bayesian optimization +- **GEPA** — Genetic-Pareto Reflective Prompt Evolution with feedback maps, experiment tracking, and telemetry +- **Evaluation** — Comprehensive framework with built-in and custom metrics, error handling, and batch processing + +## Quick Start + +```ruby +# Install +gem 'dspy' + +# Configure +DSPy.configure do |c| + c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) +end + +# Define a task +class SentimentAnalysis < DSPy::Signature + description "Analyze sentiment of text" + + input do + const :text, String + end + + output do + const :sentiment, String # positive, negative, neutral + const :score, Float # 0.0 to 1.0 + end +end + +# Use it +analyzer = DSPy::Predict.new(SentimentAnalysis) +result = analyzer.call(text: "This product is amazing!") +puts result.sentiment # => "positive" +puts result.score # => 0.92 +``` + +## Provider Adapter Gems + +Two strategies for connecting to LLM providers: + +### Per-provider adapters (direct SDK access) + +```ruby +# Gemfile +gem 'dspy' +gem 'dspy-openai' # OpenAI, OpenRouter, Ollama +gem 'dspy-anthropic' # Claude +gem 'dspy-gemini' # Gemini +``` + +Each adapter gem pulls in the official SDK (`openai`, `anthropic`, `gemini-ai`). + +### Unified adapter via RubyLLM (recommended for multi-provider) + +```ruby +# Gemfile +gem 'dspy' +gem 'dspy-ruby_llm' # Routes to any provider via ruby_llm +gem 'ruby_llm' +``` + +RubyLLM handles provider routing based on the model name. Use the `ruby_llm/` prefix: + +```ruby +DSPy.configure do |c| + c.lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash', structured_outputs: true) + # c.lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514', structured_outputs: true) + # c.lm = DSPy::LM.new('ruby_llm/gpt-4o-mini', structured_outputs: true) +end +``` + +## Events System + +DSPy.rb ships with a structured event bus for observing runtime behavior. + +### Module-Scoped Subscriptions (preferred for agents) + +```ruby +class MyAgent < DSPy::Module + subscribe 'lm.tokens', :track_tokens, scope: :descendants + + def track_tokens(_event, attrs) + @total_tokens += attrs.fetch(:total_tokens, 0) + end +end +``` + +### Global Subscriptions (for observability/integrations) + +```ruby +subscription_id = DSPy.events.subscribe('score.create') do |event, attrs| + Langfuse.export_score(attrs) +end + +# Wildcards supported +DSPy.events.subscribe('llm.*') { |name, attrs| puts "[#{name}] tokens=#{attrs[:total_tokens]}" } +``` + +Event names use dot-separated namespaces (`llm.generate`, `react.iteration_complete`). Every event includes module metadata (`module_path`, `module_leaf`, `module_scope.ancestry_token`) for filtering. + +## Lifecycle Callbacks + +Rails-style lifecycle hooks ship with every `DSPy::Module`: + +- **`before`** — Runs ahead of `forward` for setup (metrics, context loading) +- **`around`** — Wraps `forward`, calls `yield`, and lets you pair setup/teardown logic +- **`after`** — Fires after `forward` returns for cleanup or persistence + +```ruby +class InstrumentedModule < DSPy::Module + before :setup_metrics + around :manage_context + after :log_metrics + + def forward(question:) + @predictor.call(question: question) + end + + private + + def setup_metrics + @start_time = Time.now + end + + def manage_context + load_context + result = yield + save_context + result + end + + def log_metrics + duration = Time.now - @start_time + Rails.logger.info "Prediction completed in #{duration}s" + end +end +``` + +Execution order: before → around (before yield) → forward → around (after yield) → after. Callbacks are inherited from parent classes and execute in registration order. + +## Fiber-Local LM Context + +Override the language model temporarily using fiber-local storage: + +```ruby +fast_model = DSPy::LM.new("openai/gpt-4o-mini", api_key: ENV['OPENAI_API_KEY']) + +DSPy.with_lm(fast_model) do + result = classifier.call(text: "test") # Uses fast_model inside this block +end +# Back to global LM outside the block +``` + +**LM resolution hierarchy**: Instance-level LM → Fiber-local LM (`DSPy.with_lm`) → Global LM (`DSPy.configure`). + +Use `configure_predictor` for fine-grained control over agent internals: + +```ruby +agent = DSPy::ReAct.new(MySignature, tools: tools) +agent.configure { |c| c.lm = default_model } +agent.configure_predictor('thought_generator') { |c| c.lm = powerful_model } +``` + +## Evaluation Framework + +Systematically test LLM application performance with `DSPy::Evals`: + +```ruby +metric = DSPy::Metrics.exact_match(field: :answer, case_sensitive: false) +evaluator = DSPy::Evals.new(predictor, metric: metric) +result = evaluator.evaluate(test_examples, display_table: true) +puts "Pass Rate: #{(result.pass_rate * 100).round(1)}%" +``` + +Built-in metrics: `exact_match`, `contains`, `numeric_difference`, `composite_and`. Custom metrics return `true`/`false` or a `DSPy::Prediction` with `score:` and `feedback:` fields. + +Use `DSPy::Example` for typed test data and `export_scores: true` to push results to Langfuse. + +## GEPA Optimization + +GEPA (Genetic-Pareto Reflective Prompt Evolution) uses reflection-driven instruction rewrites: + +```ruby +gem 'dspy-gepa' + +teleprompter = DSPy::Teleprompt::GEPA.new( + metric: metric, + reflection_lm: DSPy::ReflectionLM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']), + feedback_map: feedback_map, + config: { max_metric_calls: 600, minibatch_size: 6 } +) + +result = teleprompter.compile(program, trainset: train, valset: val) +optimized_program = result.optimized_program +``` + +The metric must return `DSPy::Prediction.new(score:, feedback:)` so the reflection model can reason about failures. Use `feedback_map` to target individual predictors in composite modules. + +## Typed Context Pattern + +Replace opaque string context blobs with `T::Struct` inputs. Each field gets its own `description:` annotation in the JSON schema the LLM sees: + +```ruby +class NavigationContext < T::Struct + const :workflow_hint, T.nilable(String), + description: "Current workflow phase guidance for the agent" + const :action_log, T::Array[String], default: [], + description: "Compact one-line-per-action history of research steps taken" + const :iterations_remaining, Integer, + description: "Budget remaining. Each tool call costs 1 iteration." +end + +class ToolSelectionSignature < DSPy::Signature + input do + const :query, String + const :context, NavigationContext # Structured, not an opaque string + end + + output do + const :tool_name, String + const :tool_args, String, description: "JSON-encoded arguments" + end +end +``` + +Benefits: type safety at compile time, per-field descriptions in the LLM schema, easy to test as value objects, extensible by adding `const` declarations. + +## Schema Formats (BAML / TOON) + +Control how DSPy describes signature structure to the LLM: + +- **JSON Schema** (default) — Standard format, works with `structured_outputs: true` +- **BAML** (`schema_format: :baml`) — 84% token reduction for Enhanced Prompting mode. Requires `sorbet-baml` gem. +- **TOON** (`schema_format: :toon, data_format: :toon`) — Table-oriented format for both schemas and data. Enhanced Prompting mode only. + +BAML and TOON apply only when `structured_outputs: false`. With `structured_outputs: true`, the provider receives JSON Schema directly. + +## Storage System + +Persist and reload optimized programs with `DSPy::Storage::ProgramStorage`: + +```ruby +storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage") +storage.save_program(result.optimized_program, result, metadata: { optimizer: 'MIPROv2' }) +``` + +Supports checkpoint management, optimization history tracking, and import/export between environments. + +## Rails Integration + +### Directory Structure + +Organize DSPy components using Rails conventions: + +``` +app/ + entities/ # T::Struct types shared across signatures + signatures/ # DSPy::Signature definitions + tools/ # DSPy::Tools::Base implementations + concerns/ # Shared tool behaviors (error handling, etc.) + modules/ # DSPy::Module orchestrators + services/ # Plain Ruby services that compose DSPy modules +config/ + initializers/ + dspy.rb # DSPy + provider configuration + feature_flags.rb # Model selection per role +spec/ + signatures/ # Schema validation tests + tools/ # Tool unit tests + modules/ # Integration tests with VCR + vcr_cassettes/ # Recorded HTTP interactions +``` + +### Initializer + +```ruby +# config/initializers/dspy.rb +Rails.application.config.after_initialize do + next if Rails.env.test? && ENV["DSPY_ENABLE_IN_TEST"].blank? + + RubyLLM.configure do |config| + config.gemini_api_key = ENV["GEMINI_API_KEY"] if ENV["GEMINI_API_KEY"].present? + config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] if ENV["ANTHROPIC_API_KEY"].present? + config.openai_api_key = ENV["OPENAI_API_KEY"] if ENV["OPENAI_API_KEY"].present? + end + + model = ENV.fetch("DSPY_MODEL", "ruby_llm/gemini-2.5-flash") + DSPy.configure do |config| + config.lm = DSPy::LM.new(model, structured_outputs: true) + config.logger = Rails.logger + end + + # Langfuse observability (optional) + if ENV["LANGFUSE_PUBLIC_KEY"].present? && ENV["LANGFUSE_SECRET_KEY"].present? + DSPy::Observability.configure! + end +end +``` + +### Feature-Flagged Model Selection + +Use different models for different roles (fast/cheap for classification, powerful for synthesis): + +```ruby +# config/initializers/feature_flags.rb +module FeatureFlags + SELECTOR_MODEL = ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite") + SYNTHESIZER_MODEL = ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash") +end +``` + +Then override per-tool or per-predictor: + +```ruby +class ClassifyTool < DSPy::Tools::Base + def call(query:) + predictor = DSPy::Predict.new(ClassifyQuery) + predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SELECTOR_MODEL, structured_outputs: true) } + predictor.call(query: query) + end +end +``` + +## Schema-Driven Signatures + +**Prefer typed schemas over string descriptions.** Let the type system communicate structure to the LLM rather than prose in the signature description. + +### Entities as Shared Types + +Define reusable `T::Struct` and `T::Enum` types in `app/entities/` and reference them across signatures: + +```ruby +# app/entities/search_strategy.rb +class SearchStrategy < T::Enum + enums do + SingleSearch = new("single_search") + DateDecomposition = new("date_decomposition") + end +end + +# app/entities/scored_item.rb +class ScoredItem < T::Struct + const :id, String + const :score, Float, description: "Relevance score 0.0-1.0" + const :verdict, String, description: "relevant, maybe, or irrelevant" + const :reason, String, default: "" +end +``` + +### Schema vs Description: When to Use Each + +**Use schemas (T::Struct/T::Enum)** for: +- Multi-field outputs with specific types +- Enums with defined values the LLM must pick from +- Nested structures, arrays of typed objects +- Outputs consumed by code (not displayed to users) + +**Use string descriptions** for: +- Simple single-field outputs where the type is `String` +- Natural language generation (summaries, answers) +- Fields where constraint guidance helps (e.g., `description: "YYYY-MM-DD format"`) + +**Rule of thumb**: If you'd write a `case` statement on the output, it should be a `T::Enum`. If you'd call `.each` on it, it should be `T::Array[SomeStruct]`. + +## Tool Patterns + +### Tools That Wrap Predictions + +A common pattern: tools encapsulate a DSPy prediction, adding error handling, model selection, and serialization: + +```ruby +class RerankTool < DSPy::Tools::Base + tool_name "rerank" + tool_description "Score and rank search results by relevance" + + MAX_ITEMS = 200 + MIN_ITEMS_FOR_LLM = 5 + + sig { params(query: String, items: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) } + def call(query:, items: []) + return { scored_items: items, reranked: false } if items.size < MIN_ITEMS_FOR_LLM + + capped_items = items.first(MAX_ITEMS) + predictor = DSPy::Predict.new(RerankSignature) + predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SYNTHESIZER_MODEL, structured_outputs: true) } + + result = predictor.call(query: query, items: capped_items) + { scored_items: result.scored_items, reranked: true } + rescue => e + Rails.logger.warn "[RerankTool] LLM rerank failed: #{e.message}" + { error: "Rerank failed: #{e.message}", scored_items: items, reranked: false } + end +end +``` + +**Key patterns:** +- Short-circuit LLM calls when unnecessary (small data, trivial cases) +- Cap input size to prevent token overflow +- Per-tool model selection via `configure` +- Graceful error handling with fallback data + +### Error Handling Concern + +```ruby +module ErrorHandling + extend ActiveSupport::Concern + + private + + def safe_predict(signature_class, **inputs) + predictor = DSPy::Predict.new(signature_class) + yield predictor if block_given? + predictor.call(**inputs) + rescue Faraday::Error, Net::HTTPError => e + Rails.logger.error "[#{self.class.name}] API error: #{e.message}" + nil + rescue JSON::ParserError => e + Rails.logger.error "[#{self.class.name}] Invalid LLM output: #{e.message}" + nil + end +end +``` + +## Observability + +### Tracing with DSPy::Context + +Wrap operations in spans for Langfuse/OpenTelemetry visibility: + +```ruby +result = DSPy::Context.with_span( + operation: "tool_selector.select", + "dspy.module" => "ToolSelector", + "tool_selector.tools" => tool_names.join(",") +) do + @predictor.call(query: query, context: context, available_tools: schemas) +end +``` + +### Setup for Langfuse + +```ruby +# Gemfile +gem 'dspy-o11y' +gem 'dspy-o11y-langfuse' + +# .env +LANGFUSE_PUBLIC_KEY=pk-... +LANGFUSE_SECRET_KEY=sk-... +DSPY_TELEMETRY_BATCH_SIZE=5 +``` + +Every `DSPy::Predict`, `DSPy::ReAct`, and tool call is automatically traced when observability is configured. + +### Score Reporting + +Report evaluation scores to Langfuse: + +```ruby +DSPy.score(name: "relevance", value: 0.85, trace_id: current_trace_id) +``` + +## Testing + +### VCR Setup for Rails + +```ruby +VCR.configure do |config| + config.cassette_library_dir = "spec/vcr_cassettes" + config.hook_into :webmock + config.configure_rspec_metadata! + config.filter_sensitive_data('') { ENV['GEMINI_API_KEY'] } + config.filter_sensitive_data('') { ENV['OPENAI_API_KEY'] } +end +``` + +### Signature Schema Tests + +Test that signatures produce valid schemas without calling any LLM: + +```ruby +RSpec.describe ClassifyResearchQuery do + it "has required input fields" do + schema = described_class.input_json_schema + expect(schema[:required]).to include("query") + end + + it "has typed output fields" do + schema = described_class.output_json_schema + expect(schema[:properties]).to have_key(:search_strategy) + end +end +``` + +### Tool Tests with Mocked Predictions + +```ruby +RSpec.describe RerankTool do + let(:tool) { described_class.new } + + it "skips LLM for small result sets" do + expect(DSPy::Predict).not_to receive(:new) + result = tool.call(query: "test", items: [{ id: "1" }]) + expect(result[:reranked]).to be false + end + + it "calls LLM for large result sets", :vcr do + items = 10.times.map { |i| { id: i.to_s, title: "Item #{i}" } } + result = tool.call(query: "relevant items", items: items) + expect(result[:reranked]).to be true + end +end +``` + +## Resources + +- [core-concepts.md](./references/core-concepts.md) — Signatures, modules, predictors, type system deep-dive +- [toolsets.md](./references/toolsets.md) — Tools::Base, Tools::Toolset DSL, type safety, testing +- [providers.md](./references/providers.md) — Provider adapters, RubyLLM, fiber-local LM context, compatibility matrix +- [optimization.md](./references/optimization.md) — MIPROv2, GEPA, evaluation framework, storage system +- [observability.md](./references/observability.md) — Event system, dspy-o11y gems, Langfuse, score reporting +- [signature-template.rb](./assets/signature-template.rb) — Signature scaffold with T::Enum, Date/Time, defaults, union types +- [module-template.rb](./assets/module-template.rb) — Module scaffold with .call(), lifecycle callbacks, fiber-local LM +- [config-template.rb](./assets/config-template.rb) — Rails initializer with RubyLLM, observability, feature flags + +## Key URLs + +- Homepage: https://oss.vicente.services/dspy.rb/ +- GitHub: https://github.com/vicentereig/dspy.rb +- Documentation: https://oss.vicente.services/dspy.rb/getting-started/ + +## Guidelines for Claude + +When helping users with DSPy.rb: + +1. **Schema over prose** — Define output structure with `T::Struct` and `T::Enum` types, not string descriptions +2. **Entities in `app/entities/`** — Extract shared types so signatures stay thin +3. **Per-tool model selection** — Use `predictor.configure { |c| c.lm = ... }` to pick the right model per task +4. **Short-circuit LLM calls** — Skip the LLM for trivial cases (small data, cached results) +5. **Cap input sizes** — Prevent token overflow by limiting array sizes before sending to LLM +6. **Test schemas without LLM** — Validate `input_json_schema` and `output_json_schema` in unit tests +7. **VCR for integration tests** — Record real HTTP interactions, never mock LLM responses by hand +8. **Trace with spans** — Wrap tool calls in `DSPy::Context.with_span` for observability +9. **Graceful degradation** — Always rescue LLM errors and return fallback data + +### Signature Best Practices + +**Keep description concise** — The signature `description` should state the goal, not the field details: + +```ruby +# Good — concise goal +class ParseOutline < DSPy::Signature + description 'Extract block-level structure from HTML as a flat list of skeleton sections.' + + input do + const :html, String, description: 'Raw HTML to parse' + end + + output do + const :sections, T::Array[Section], description: 'Block elements: headings, paragraphs, code blocks, lists' + end +end +``` + +**Use defaults over nilable arrays** — For OpenAI structured outputs compatibility: + +```ruby +# Good — works with OpenAI structured outputs +class ASTNode < T::Struct + const :children, T::Array[ASTNode], default: [] +end +``` + +### Recursive Types with `$defs` + +DSPy.rb supports recursive types in structured outputs using JSON Schema `$defs`: + +```ruby +class TreeNode < T::Struct + const :value, String + const :children, T::Array[TreeNode], default: [] # Self-reference +end +``` + +The schema generator automatically creates `#/$defs/TreeNode` references for recursive types, compatible with OpenAI and Gemini structured outputs. + +### Field Descriptions for T::Struct + +DSPy.rb extends T::Struct to support field-level `description:` kwargs that flow to JSON Schema: + +```ruby +class ASTNode < T::Struct + const :node_type, NodeType, description: 'The type of node (heading, paragraph, etc.)' + const :text, String, default: "", description: 'Text content of the node' + const :level, Integer, default: 0 # No description — field is self-explanatory + const :children, T::Array[ASTNode], default: [] +end +``` + +**When to use field descriptions**: complex field semantics, enum-like strings, constrained values, nested structs with ambiguous names. **When to skip**: self-explanatory fields like `name`, `id`, `url`, or boolean flags. + +## Version + +Current: 0.34.3 diff --git a/.cursor/skills/dspy-ruby/assets/config-template.rb b/.cursor/skills/dspy-ruby/assets/config-template.rb new file mode 100644 index 0000000000..6c19633158 --- /dev/null +++ b/.cursor/skills/dspy-ruby/assets/config-template.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +# ============================================================================= +# DSPy.rb Configuration Template — v0.34.3 API +# +# Rails initializer patterns for DSPy.rb with RubyLLM, observability, +# and feature-flagged model selection. +# +# Key patterns: +# - Use after_initialize for Rails setup +# - Use dspy-ruby_llm for multi-provider routing +# - Use structured_outputs: true for reliable parsing +# - Use dspy-o11y + dspy-o11y-langfuse for observability +# - Use ENV-based feature flags for model selection +# ============================================================================= + +# ============================================================================= +# Gemfile Dependencies +# ============================================================================= +# +# # Core +# gem 'dspy' +# +# # Provider adapter (choose one strategy): +# +# # Strategy A: Unified adapter via RubyLLM (recommended) +# gem 'dspy-ruby_llm' +# gem 'ruby_llm' +# +# # Strategy B: Per-provider adapters (direct SDK access) +# gem 'dspy-openai' # OpenAI, OpenRouter, Ollama +# gem 'dspy-anthropic' # Claude +# gem 'dspy-gemini' # Gemini +# +# # Observability (optional) +# gem 'dspy-o11y' +# gem 'dspy-o11y-langfuse' +# +# # Optimization (optional) +# gem 'dspy-miprov2' # MIPROv2 optimizer +# gem 'dspy-gepa' # GEPA optimizer +# +# # Schema formats (optional) +# gem 'sorbet-baml' # BAML schema format (84% token reduction) + +# ============================================================================= +# Rails Initializer — config/initializers/dspy.rb +# ============================================================================= + +Rails.application.config.after_initialize do + # Skip in test unless explicitly enabled + next if Rails.env.test? && ENV["DSPY_ENABLE_IN_TEST"].blank? + + # Configure RubyLLM provider credentials + RubyLLM.configure do |config| + config.gemini_api_key = ENV["GEMINI_API_KEY"] if ENV["GEMINI_API_KEY"].present? + config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"] if ENV["ANTHROPIC_API_KEY"].present? + config.openai_api_key = ENV["OPENAI_API_KEY"] if ENV["OPENAI_API_KEY"].present? + end + + # Configure DSPy with unified RubyLLM adapter + model = ENV.fetch("DSPY_MODEL", "ruby_llm/gemini-2.5-flash") + DSPy.configure do |config| + config.lm = DSPy::LM.new(model, structured_outputs: true) + config.logger = Rails.logger + end + + # Enable Langfuse observability (optional) + if ENV["LANGFUSE_PUBLIC_KEY"].present? && ENV["LANGFUSE_SECRET_KEY"].present? + DSPy::Observability.configure! + end +end + +# ============================================================================= +# Feature Flags — config/initializers/feature_flags.rb +# ============================================================================= + +# Use different models for different roles: +# - Fast/cheap for classification, routing, simple tasks +# - Powerful for synthesis, reasoning, complex analysis + +module FeatureFlags + SELECTOR_MODEL = ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite") + SYNTHESIZER_MODEL = ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash") + REASONING_MODEL = ENV.fetch("DSPY_REASONING_MODEL", "ruby_llm/claude-sonnet-4-20250514") +end + +# Usage in tools/modules: +# +# class ClassifyTool < DSPy::Tools::Base +# def call(query:) +# predictor = DSPy::Predict.new(ClassifySignature) +# predictor.configure { |c| c.lm = DSPy::LM.new(FeatureFlags::SELECTOR_MODEL, structured_outputs: true) } +# predictor.call(query: query) +# end +# end + +# ============================================================================= +# Environment Variables — .env +# ============================================================================= +# +# # Provider API keys (set the ones you need) +# GEMINI_API_KEY=... +# ANTHROPIC_API_KEY=... +# OPENAI_API_KEY=... +# +# # DSPy model configuration +# DSPY_MODEL=ruby_llm/gemini-2.5-flash +# DSPY_SELECTOR_MODEL=ruby_llm/gemini-2.5-flash-lite +# DSPY_SYNTHESIZER_MODEL=ruby_llm/gemini-2.5-flash +# DSPY_REASONING_MODEL=ruby_llm/claude-sonnet-4-20250514 +# +# # Langfuse observability (optional) +# LANGFUSE_PUBLIC_KEY=pk-... +# LANGFUSE_SECRET_KEY=sk-... +# DSPY_TELEMETRY_BATCH_SIZE=5 +# +# # Test environment +# DSPY_ENABLE_IN_TEST=1 # Set to enable DSPy in test env + +# ============================================================================= +# Per-Provider Configuration (without RubyLLM) +# ============================================================================= + +# OpenAI (dspy-openai gem) +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) +# end + +# Anthropic (dspy-anthropic gem) +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY']) +# end + +# Gemini (dspy-gemini gem) +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('gemini/gemini-2.5-flash', api_key: ENV['GEMINI_API_KEY']) +# end + +# Ollama (dspy-openai gem, local models) +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('ollama/llama3.2', base_url: 'http://localhost:11434') +# end + +# OpenRouter (dspy-openai gem, 200+ models) +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('openrouter/anthropic/claude-3.5-sonnet', +# api_key: ENV['OPENROUTER_API_KEY'], +# base_url: 'https://openrouter.ai/api/v1') +# end + +# ============================================================================= +# VCR Test Configuration — spec/support/dspy.rb +# ============================================================================= + +# VCR.configure do |config| +# config.cassette_library_dir = "spec/vcr_cassettes" +# config.hook_into :webmock +# config.configure_rspec_metadata! +# config.filter_sensitive_data('') { ENV['GEMINI_API_KEY'] } +# config.filter_sensitive_data('') { ENV['OPENAI_API_KEY'] } +# config.filter_sensitive_data('') { ENV['ANTHROPIC_API_KEY'] } +# end + +# ============================================================================= +# Schema Format Configuration (optional) +# ============================================================================= + +# BAML schema format — 84% token reduction for Enhanced Prompting mode +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('openai/gpt-4o-mini', +# api_key: ENV['OPENAI_API_KEY'], +# schema_format: :baml # Requires sorbet-baml gem +# ) +# end + +# TOON schema + data format — table-oriented format +# DSPy.configure do |c| +# c.lm = DSPy::LM.new('openai/gpt-4o-mini', +# api_key: ENV['OPENAI_API_KEY'], +# schema_format: :toon, # How DSPy describes the signature +# data_format: :toon # How inputs/outputs are rendered in prompts +# ) +# end +# +# Note: BAML and TOON apply only when structured_outputs: false. +# With structured_outputs: true, the provider receives JSON Schema directly. diff --git a/.cursor/skills/dspy-ruby/assets/module-template.rb b/.cursor/skills/dspy-ruby/assets/module-template.rb new file mode 100644 index 0000000000..c7f1122654 --- /dev/null +++ b/.cursor/skills/dspy-ruby/assets/module-template.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +# ============================================================================= +# DSPy.rb Module Template — v0.34.3 API +# +# Modules orchestrate predictors, tools, and business logic. +# +# Key patterns: +# - Use .call() to invoke (not .forward()) +# - Access results with result.field (not result[:field]) +# - Use DSPy::Tools::Base for tools (not DSPy::Tool) +# - Use lifecycle callbacks (before/around/after) for cross-cutting concerns +# - Use DSPy.with_lm for temporary model overrides +# - Use configure_predictor for fine-grained agent control +# ============================================================================= + +# --- Basic Module --- + +class BasicClassifier < DSPy::Module + def initialize + super + @predictor = DSPy::Predict.new(ClassificationSignature) + end + + def forward(text:) + @predictor.call(text: text) + end +end + +# Usage: +# classifier = BasicClassifier.new +# result = classifier.call(text: "This is a test") +# result.category # => "technical" +# result.confidence # => 0.95 + +# --- Module with Chain of Thought --- + +class ReasoningClassifier < DSPy::Module + def initialize + super + @predictor = DSPy::ChainOfThought.new(ClassificationSignature) + end + + def forward(text:) + result = @predictor.call(text: text) + # ChainOfThought adds result.reasoning automatically + result + end +end + +# --- Module with Lifecycle Callbacks --- + +class InstrumentedModule < DSPy::Module + before :setup_metrics + around :manage_context + after :log_completion + + def initialize + super + @predictor = DSPy::Predict.new(AnalysisSignature) + @start_time = nil + end + + def forward(query:) + @predictor.call(query: query) + end + + private + + # Runs before forward + def setup_metrics + @start_time = Time.now + Rails.logger.info "Starting prediction" + end + + # Wraps forward — must call yield + def manage_context + load_user_context + result = yield + save_updated_context(result) + result + end + + # Runs after forward completes + def log_completion + duration = Time.now - @start_time + Rails.logger.info "Prediction completed in #{duration}s" + end + + def load_user_context = nil + def save_updated_context(_result) = nil +end + +# Execution order: before → around (before yield) → forward → around (after yield) → after +# Callbacks are inherited from parent classes and execute in registration order. + +# --- Module with Tools --- + +class SearchTool < DSPy::Tools::Base + tool_name "search" + tool_description "Search for information by query" + + sig { params(query: String, max_results: Integer).returns(T::Array[T::Hash[Symbol, String]]) } + def call(query:, max_results: 5) + # Implementation here + [{ title: "Result 1", url: "https://example.com" }] + end +end + +class FinishTool < DSPy::Tools::Base + tool_name "finish" + tool_description "Submit the final answer" + + sig { params(answer: String).returns(String) } + def call(answer:) + answer + end +end + +class ResearchAgent < DSPy::Module + def initialize + super + tools = [SearchTool.new, FinishTool.new] + @agent = DSPy::ReAct.new( + ResearchSignature, + tools: tools, + max_iterations: 5 + ) + end + + def forward(question:) + @agent.call(question: question) + end +end + +# --- Module with Per-Task Model Selection --- + +class SmartRouter < DSPy::Module + def initialize + super + @classifier = DSPy::Predict.new(RouteSignature) + @analyzer = DSPy::ChainOfThought.new(AnalysisSignature) + end + + def forward(text:) + # Use fast model for classification + DSPy.with_lm(fast_model) do + route = @classifier.call(text: text) + + if route.requires_deep_analysis + # Switch to powerful model for analysis + DSPy.with_lm(powerful_model) do + @analyzer.call(text: text) + end + else + route + end + end + end + + private + + def fast_model + @fast_model ||= DSPy::LM.new( + ENV.fetch("DSPY_SELECTOR_MODEL", "ruby_llm/gemini-2.5-flash-lite"), + structured_outputs: true + ) + end + + def powerful_model + @powerful_model ||= DSPy::LM.new( + ENV.fetch("DSPY_SYNTHESIZER_MODEL", "ruby_llm/gemini-2.5-flash"), + structured_outputs: true + ) + end +end + +# --- Module with configure_predictor --- + +class ConfiguredAgent < DSPy::Module + def initialize + super + tools = [SearchTool.new, FinishTool.new] + @agent = DSPy::ReAct.new(ResearchSignature, tools: tools) + + # Set default model for all internal predictors + @agent.configure { |c| c.lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash', structured_outputs: true) } + + # Override specific predictor with a more capable model + @agent.configure_predictor('thought_generator') do |c| + c.lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514', structured_outputs: true) + end + end + + def forward(question:) + @agent.call(question: question) + end +end + +# Available internal predictors by agent type: +# DSPy::ReAct → thought_generator, observation_processor +# DSPy::CodeAct → code_generator, observation_processor +# DSPy::DeepSearch → seed_predictor, search_predictor, reader_predictor, reason_predictor + +# --- Module with Event Subscriptions --- + +class TokenTrackingModule < DSPy::Module + subscribe 'lm.tokens', :track_tokens, scope: :descendants + + def initialize + super + @predictor = DSPy::Predict.new(AnalysisSignature) + @total_tokens = 0 + end + + def forward(query:) + @predictor.call(query: query) + end + + def track_tokens(_event, attrs) + @total_tokens += attrs.fetch(:total_tokens, 0) + end + + def token_usage + @total_tokens + end +end + +# Module-scoped subscriptions automatically scope to the module instance and descendants. +# Use scope: :self_only to restrict delivery to the module itself (ignoring children). + +# --- Tool That Wraps a Prediction --- + +class RerankTool < DSPy::Tools::Base + tool_name "rerank" + tool_description "Score and rank search results by relevance" + + MAX_ITEMS = 200 + MIN_ITEMS_FOR_LLM = 5 + + sig { params(query: String, items: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Hash[Symbol, T.untyped]) } + def call(query:, items: []) + # Short-circuit: skip LLM for small sets + return { scored_items: items, reranked: false } if items.size < MIN_ITEMS_FOR_LLM + + # Cap to prevent token overflow + capped_items = items.first(MAX_ITEMS) + + predictor = DSPy::Predict.new(RerankSignature) + predictor.configure { |c| c.lm = DSPy::LM.new("ruby_llm/gemini-2.5-flash", structured_outputs: true) } + + result = predictor.call(query: query, items: capped_items) + { scored_items: result.scored_items, reranked: true } + rescue => e + Rails.logger.warn "[RerankTool] LLM rerank failed: #{e.message}" + { error: "Rerank failed: #{e.message}", scored_items: items, reranked: false } + end +end + +# Key patterns for tools wrapping predictions: +# - Short-circuit LLM calls when unnecessary (small data, trivial cases) +# - Cap input size to prevent token overflow +# - Per-tool model selection via configure +# - Graceful error handling with fallback data + +# --- Multi-Step Pipeline --- + +class AnalysisPipeline < DSPy::Module + def initialize + super + @classifier = DSPy::Predict.new(ClassifySignature) + @analyzer = DSPy::ChainOfThought.new(AnalyzeSignature) + @summarizer = DSPy::Predict.new(SummarizeSignature) + end + + def forward(text:) + classification = @classifier.call(text: text) + analysis = @analyzer.call(text: text, category: classification.category) + @summarizer.call(analysis: analysis.reasoning, category: classification.category) + end +end + +# --- Observability with Spans --- + +class TracedModule < DSPy::Module + def initialize + super + @predictor = DSPy::Predict.new(AnalysisSignature) + end + + def forward(query:) + DSPy::Context.with_span( + operation: "traced_module.analyze", + "dspy.module" => self.class.name, + "query.length" => query.length.to_s + ) do + @predictor.call(query: query) + end + end +end diff --git a/.cursor/skills/dspy-ruby/assets/signature-template.rb b/.cursor/skills/dspy-ruby/assets/signature-template.rb new file mode 100644 index 0000000000..bff2af6836 --- /dev/null +++ b/.cursor/skills/dspy-ruby/assets/signature-template.rb @@ -0,0 +1,221 @@ +# frozen_string_literal: true + +# ============================================================================= +# DSPy.rb Signature Template — v0.34.3 API +# +# Signatures define the interface between your application and LLMs. +# They specify inputs, outputs, and task descriptions using Sorbet types. +# +# Key patterns: +# - Use T::Enum classes for controlled outputs (not inline T.enum([...])) +# - Use description: kwarg on fields to guide the LLM +# - Use default values for optional fields +# - Use Date/DateTime/Time for temporal data (auto-converted) +# - Access results with result.field (not result[:field]) +# - Invoke with predictor.call() (not predictor.forward()) +# ============================================================================= + +# --- Basic Signature --- + +class SentimentAnalysis < DSPy::Signature + description "Analyze sentiment of text" + + class Sentiment < T::Enum + enums do + Positive = new('positive') + Negative = new('negative') + Neutral = new('neutral') + end + end + + input do + const :text, String + end + + output do + const :sentiment, Sentiment + const :score, Float, description: "Confidence score from 0.0 to 1.0" + end +end + +# Usage: +# predictor = DSPy::Predict.new(SentimentAnalysis) +# result = predictor.call(text: "This product is amazing!") +# result.sentiment # => Sentiment::Positive +# result.score # => 0.92 + +# --- Signature with Date/Time Types --- + +class EventScheduler < DSPy::Signature + description "Schedule events based on requirements" + + input do + const :event_name, String + const :start_date, Date # ISO 8601: YYYY-MM-DD + const :end_date, T.nilable(Date) # Optional date + const :preferred_time, DateTime # ISO 8601 with timezone + const :deadline, Time # Stored as UTC + end + + output do + const :scheduled_date, Date # LLM returns ISO string, auto-converted + const :event_datetime, DateTime # Preserves timezone + const :created_at, Time # Converted to UTC + end +end + +# Date/Time format handling: +# Date → ISO 8601 (YYYY-MM-DD) +# DateTime → ISO 8601 with timezone (YYYY-MM-DDTHH:MM:SS+00:00) +# Time → ISO 8601, automatically converted to UTC + +# --- Signature with Default Values --- + +class SmartSearch < DSPy::Signature + description "Search with intelligent defaults" + + input do + const :query, String + const :max_results, Integer, default: 10 + const :language, String, default: "English" + const :include_metadata, T::Boolean, default: false + end + + output do + const :results, T::Array[String] + const :total_found, Integer + const :search_time_ms, Float, default: 0.0 # Fallback if LLM omits + const :cached, T::Boolean, default: false + end +end + +# Input defaults reduce boilerplate: +# search = DSPy::Predict.new(SmartSearch) +# result = search.call(query: "Ruby programming") +# # max_results=10, language="English", include_metadata=false are applied + +# --- Signature with Nested Structs and Field Descriptions --- + +class EntityExtraction < DSPy::Signature + description "Extract named entities from text" + + class EntityType < T::Enum + enums do + Person = new('person') + Organization = new('organization') + Location = new('location') + DateEntity = new('date') + end + end + + class Entity < T::Struct + const :name, String, description: "The entity text as it appears in the source" + const :type, EntityType + const :confidence, Float, description: "Extraction confidence from 0.0 to 1.0" + const :start_offset, Integer, default: 0 + end + + input do + const :text, String + const :entity_types, T::Array[EntityType], default: [], + description: "Filter to these entity types; empty means all types" + end + + output do + const :entities, T::Array[Entity] + const :total_found, Integer + end +end + +# --- Signature with Union Types --- + +class FlexibleClassification < DSPy::Signature + description "Classify input with flexible result type" + + class Category < T::Enum + enums do + Technical = new('technical') + Business = new('business') + Personal = new('personal') + end + end + + input do + const :text, String + end + + output do + const :category, Category + const :result, T.any(Float, String), + description: "Numeric score or text explanation depending on classification" + const :confidence, Float + end +end + +# --- Signature with Recursive Types --- + +class DocumentParser < DSPy::Signature + description "Parse document into tree structure" + + class NodeType < T::Enum + enums do + Heading = new('heading') + Paragraph = new('paragraph') + List = new('list') + CodeBlock = new('code_block') + end + end + + class TreeNode < T::Struct + const :node_type, NodeType, description: "The type of document element" + const :text, String, default: "", description: "Text content of the node" + const :level, Integer, default: 0 + const :children, T::Array[TreeNode], default: [] # Self-reference → $defs in JSON Schema + end + + input do + const :html, String, description: "Raw HTML to parse" + end + + output do + const :root, TreeNode + const :word_count, Integer + end +end + +# The schema generator creates #/$defs/TreeNode references for recursive types, +# compatible with OpenAI and Gemini structured outputs. +# Use `default: []` instead of `T.nilable(T::Array[...])` for OpenAI compatibility. + +# --- Vision Signature --- + +class ImageAnalysis < DSPy::Signature + description "Analyze an image and answer questions about its content" + + input do + const :image, DSPy::Image, description: "The image to analyze" + const :question, String, description: "Question about the image content" + end + + output do + const :answer, String + const :confidence, Float, description: "Confidence in the answer (0.0-1.0)" + end +end + +# Vision usage: +# predictor = DSPy::Predict.new(ImageAnalysis) +# result = predictor.call( +# image: DSPy::Image.from_file("path/to/image.jpg"), +# question: "What objects are visible?" +# ) +# result.answer # => "The image shows..." + +# --- Accessing Schemas Programmatically --- +# +# SentimentAnalysis.input_json_schema # => { type: "object", properties: { ... } } +# SentimentAnalysis.output_json_schema # => { type: "object", properties: { ... } } +# +# # Field descriptions propagate to JSON Schema +# Entity.field_descriptions[:name] # => "The entity text as it appears in the source" +# Entity.field_descriptions[:confidence] # => "Extraction confidence from 0.0 to 1.0" diff --git a/.cursor/skills/dspy-ruby/references/core-concepts.md b/.cursor/skills/dspy-ruby/references/core-concepts.md new file mode 100644 index 0000000000..f8fb006669 --- /dev/null +++ b/.cursor/skills/dspy-ruby/references/core-concepts.md @@ -0,0 +1,674 @@ +# DSPy.rb Core Concepts + +## Signatures + +Signatures define the interface between application code and language models. They specify inputs, outputs, and a task description using Sorbet types for compile-time and runtime type safety. + +### Structure + +```ruby +class ClassifyEmail < DSPy::Signature + description "Classify customer support emails by urgency and category" + + input do + const :subject, String + const :body, String + end + + output do + const :category, String + const :urgency, String + end +end +``` + +### Supported Types + +| Type | JSON Schema | Notes | +|------|-------------|-------| +| `String` | `string` | Required string | +| `Integer` | `integer` | Whole numbers | +| `Float` | `number` | Decimal numbers | +| `T::Boolean` | `boolean` | true/false | +| `T::Array[X]` | `array` | Typed arrays | +| `T::Hash[K, V]` | `object` | Typed key-value maps | +| `T.nilable(X)` | nullable | Optional fields | +| `Date` | `string` (ISO 8601) | Auto-converted | +| `DateTime` | `string` (ISO 8601) | Preserves timezone | +| `Time` | `string` (ISO 8601) | Converted to UTC | + +### Date and Time Types + +Date, DateTime, and Time fields serialize to ISO 8601 strings and auto-convert back to Ruby objects on output. + +```ruby +class EventScheduler < DSPy::Signature + description "Schedule events based on requirements" + + input do + const :start_date, Date # ISO 8601: YYYY-MM-DD + const :preferred_time, DateTime # ISO 8601 with timezone + const :deadline, Time # Converted to UTC + const :end_date, T.nilable(Date) # Optional date + end + + output do + const :scheduled_date, Date # String from LLM, auto-converted to Date + const :event_datetime, DateTime # Preserves timezone info + const :created_at, Time # Converted to UTC + end +end + +predictor = DSPy::Predict.new(EventScheduler) +result = predictor.call( + start_date: "2024-01-15", + preferred_time: "2024-01-15T10:30:45Z", + deadline: Time.now, + end_date: nil +) + +result.scheduled_date.class # => Date +result.event_datetime.class # => DateTime +``` + +Timezone conventions follow ActiveRecord: Time objects convert to UTC, DateTime objects preserve timezone, Date objects are timezone-agnostic. + +### Enums with T::Enum + +Define constrained output values using `T::Enum` classes. Do not use inline `T.enum([...])` syntax. + +```ruby +class SentimentAnalysis < DSPy::Signature + description "Analyze sentiment of text" + + class Sentiment < T::Enum + enums do + Positive = new('positive') + Negative = new('negative') + Neutral = new('neutral') + end + end + + input do + const :text, String + end + + output do + const :sentiment, Sentiment + const :confidence, Float + end +end + +predictor = DSPy::Predict.new(SentimentAnalysis) +result = predictor.call(text: "This product is amazing!") + +result.sentiment # => # +result.sentiment.serialize # => "positive" +result.confidence # => 0.92 +``` + +Enum matching is case-insensitive. The LLM returning `"POSITIVE"` matches `new('positive')`. + +### Default Values + +Default values work on both inputs and outputs. Input defaults reduce caller boilerplate. Output defaults provide fallbacks when the LLM omits optional fields. + +```ruby +class SmartSearch < DSPy::Signature + description "Search with intelligent defaults" + + input do + const :query, String + const :max_results, Integer, default: 10 + const :language, String, default: "English" + end + + output do + const :results, T::Array[String] + const :total_found, Integer + const :cached, T::Boolean, default: false + end +end + +search = DSPy::Predict.new(SmartSearch) +result = search.call(query: "Ruby programming") +# max_results defaults to 10, language defaults to "English" +# If LLM omits `cached`, it defaults to false +``` + +### Field Descriptions + +Add `description:` to any field to guide the LLM on expected content. These descriptions appear in the generated JSON schema sent to the model. + +```ruby +class ASTNode < T::Struct + const :node_type, String, description: "The type of AST node (heading, paragraph, code_block)" + const :text, String, default: "", description: "Text content of the node" + const :level, Integer, default: 0, description: "Heading level 1-6, only for heading nodes" + const :children, T::Array[ASTNode], default: [] +end + +ASTNode.field_descriptions[:node_type] # => "The type of AST node ..." +ASTNode.field_descriptions[:children] # => nil (no description set) +``` + +Field descriptions also work inside signature `input` and `output` blocks: + +```ruby +class ExtractEntities < DSPy::Signature + description "Extract named entities from text" + + input do + const :text, String, description: "Raw text to analyze" + const :language, String, default: "en", description: "ISO 639-1 language code" + end + + output do + const :entities, T::Array[String], description: "List of extracted entity names" + const :count, Integer, description: "Total number of unique entities found" + end +end +``` + +### Schema Formats + +DSPy.rb supports three schema formats for communicating type structure to LLMs. + +#### JSON Schema (default) + +Verbose but universally supported. Access via `YourSignature.output_json_schema`. + +#### BAML Schema + +Compact format that reduces schema tokens by 80-85%. Requires the `sorbet-baml` gem. + +```ruby +DSPy.configure do |c| + c.lm = DSPy::LM.new('openai/gpt-4o-mini', + api_key: ENV['OPENAI_API_KEY'], + schema_format: :baml + ) +end +``` + +BAML applies only in Enhanced Prompting mode (`structured_outputs: false`). When `structured_outputs: true`, the provider receives JSON Schema directly. + +#### TOON Schema + Data Format + +Table-oriented text format that shrinks both schema definitions and prompt values. + +```ruby +DSPy.configure do |c| + c.lm = DSPy::LM.new('openai/gpt-4o-mini', + api_key: ENV['OPENAI_API_KEY'], + schema_format: :toon, + data_format: :toon + ) +end +``` + +`schema_format: :toon` replaces the schema block in the system prompt. `data_format: :toon` renders input values and output templates inside `toon` fences. Only works with Enhanced Prompting mode. The `sorbet-toon` gem is included automatically as a dependency. + +### Recursive Types + +Structs that reference themselves produce `$defs` entries in the generated JSON schema, using `$ref` pointers to avoid infinite recursion. + +```ruby +class ASTNode < T::Struct + const :node_type, String + const :text, String, default: "" + const :children, T::Array[ASTNode], default: [] +end +``` + +The schema generator detects the self-reference in `T::Array[ASTNode]` and emits: + +```json +{ + "$defs": { + "ASTNode": { "type": "object", "properties": { ... } } + }, + "properties": { + "children": { + "type": "array", + "items": { "$ref": "#/$defs/ASTNode" } + } + } +} +``` + +Access the schema with accumulated definitions via `YourSignature.output_json_schema_with_defs`. + +### Union Types with T.any() + +Specify fields that accept multiple types: + +```ruby +output do + const :result, T.any(Float, String) +end +``` + +For struct unions, DSPy.rb automatically adds a `_type` discriminator field to each struct's JSON schema. The LLM returns `_type` in its response, and DSPy converts the hash to the correct struct instance. + +```ruby +class CreateTask < T::Struct + const :title, String + const :priority, String +end + +class DeleteTask < T::Struct + const :task_id, String + const :reason, T.nilable(String) +end + +class TaskRouter < DSPy::Signature + description "Route user request to the appropriate task action" + + input do + const :request, String + end + + output do + const :action, T.any(CreateTask, DeleteTask) + end +end + +result = DSPy::Predict.new(TaskRouter).call(request: "Create a task for Q4 review") +result.action.class # => CreateTask +result.action.title # => "Q4 Review" +``` + +Pattern matching works on the result: + +```ruby +case result.action +when CreateTask then puts "Creating: #{result.action.title}" +when DeleteTask then puts "Deleting: #{result.action.task_id}" +end +``` + +Union types also work inside arrays for heterogeneous collections: + +```ruby +output do + const :events, T::Array[T.any(LoginEvent, PurchaseEvent)] +end +``` + +Limit unions to 2-4 types for reliable LLM comprehension. Use clear struct names since they become the `_type` discriminator values. + +--- + +## Modules + +Modules are composable building blocks that wrap predictors. Define a `forward` method; invoke the module with `.call()`. + +### Basic Structure + +```ruby +class SentimentAnalyzer < DSPy::Module + def initialize + super + @predictor = DSPy::Predict.new(SentimentSignature) + end + + def forward(text:) + @predictor.call(text: text) + end +end + +analyzer = SentimentAnalyzer.new +result = analyzer.call(text: "I love this product!") + +result.sentiment # => "positive" +result.confidence # => 0.9 +``` + +**API rules:** +- Invoke modules and predictors with `.call()`, not `.forward()`. +- Access result fields with `result.field`, not `result[:field]`. + +### Module Composition + +Combine multiple modules through explicit method calls in `forward`: + +```ruby +class DocumentProcessor < DSPy::Module + def initialize + super + @classifier = DocumentClassifier.new + @summarizer = DocumentSummarizer.new + end + + def forward(document:) + classification = @classifier.call(content: document) + summary = @summarizer.call(content: document) + + { + document_type: classification.document_type, + summary: summary.summary + } + end +end +``` + +### Lifecycle Callbacks + +Modules support `before`, `after`, and `around` callbacks on `forward`. Declare them as class-level macros referencing private methods. + +#### Execution order + +1. `before` callbacks (in registration order) +2. `around` callbacks (before `yield`) +3. `forward` method +4. `around` callbacks (after `yield`) +5. `after` callbacks (in registration order) + +```ruby +class InstrumentedModule < DSPy::Module + before :setup_metrics + after :log_metrics + around :manage_context + + def initialize + super + @predictor = DSPy::Predict.new(MySignature) + @metrics = {} + end + + def forward(question:) + @predictor.call(question: question) + end + + private + + def setup_metrics + @metrics[:start_time] = Time.now + end + + def manage_context + load_context + result = yield + save_context + result + end + + def log_metrics + @metrics[:duration] = Time.now - @metrics[:start_time] + end +end +``` + +Multiple callbacks of the same type execute in registration order. Callbacks inherit from parent classes; parent callbacks run first. + +#### Around callbacks + +Around callbacks must call `yield` to execute the wrapped method and return the result: + +```ruby +def with_retry + retries = 0 + begin + yield + rescue StandardError => e + retries += 1 + retry if retries < 3 + raise e + end +end +``` + +### Instruction Update Contract + +Teleprompters (GEPA, MIPROv2) require modules to expose immutable update hooks. Include `DSPy::Mixins::InstructionUpdatable` and implement `with_instruction` and `with_examples`, each returning a new instance: + +```ruby +class SentimentPredictor < DSPy::Module + include DSPy::Mixins::InstructionUpdatable + + def initialize + super + @predictor = DSPy::Predict.new(SentimentSignature) + end + + def with_instruction(instruction) + clone = self.class.new + clone.instance_variable_set(:@predictor, @predictor.with_instruction(instruction)) + clone + end + + def with_examples(examples) + clone = self.class.new + clone.instance_variable_set(:@predictor, @predictor.with_examples(examples)) + clone + end +end +``` + +If a module omits these hooks, teleprompters raise `DSPy::InstructionUpdateError` instead of silently mutating state. + +--- + +## Predictors + +Predictors are execution engines that take a signature and produce structured results from a language model. DSPy.rb provides four predictor types. + +### Predict + +Direct LLM call with typed input/output. Fastest option, lowest token usage. + +```ruby +classifier = DSPy::Predict.new(ClassifyText) +result = classifier.call(text: "Technical document about APIs") + +result.sentiment # => # +result.topics # => ["APIs", "technical"] +result.confidence # => 0.92 +``` + +### ChainOfThought + +Adds a `reasoning` field to the output automatically. The model generates step-by-step reasoning before the final answer. Do not define a `:reasoning` field in the signature output when using ChainOfThought. + +```ruby +class SolveMathProblem < DSPy::Signature + description "Solve mathematical word problems step by step" + + input do + const :problem, String + end + + output do + const :answer, String + # :reasoning is added automatically by ChainOfThought + end +end + +solver = DSPy::ChainOfThought.new(SolveMathProblem) +result = solver.call(problem: "Sarah has 15 apples. She gives 7 away and buys 12 more.") + +result.reasoning # => "Step by step: 15 - 7 = 8, then 8 + 12 = 20" +result.answer # => "20 apples" +``` + +Use ChainOfThought for complex analysis, multi-step reasoning, or when explainability matters. + +### ReAct + +Reasoning + Action agent that uses tools in an iterative loop. Define tools by subclassing `DSPy::Tools::Base`. Group related tools with `DSPy::Tools::Toolset`. + +```ruby +class WeatherTool < DSPy::Tools::Base + extend T::Sig + + tool_name "weather" + tool_description "Get weather information for a location" + + sig { params(location: String).returns(String) } + def call(location:) + { location: location, temperature: 72, condition: "sunny" }.to_json + end +end + +class TravelSignature < DSPy::Signature + description "Help users plan travel" + + input do + const :destination, String + end + + output do + const :recommendations, String + end +end + +agent = DSPy::ReAct.new( + TravelSignature, + tools: [WeatherTool.new], + max_iterations: 5 +) + +result = agent.call(destination: "Tokyo, Japan") +result.recommendations # => "Visit Senso-ji Temple early morning..." +result.history # => Array of reasoning steps, actions, observations +result.iterations # => 3 +result.tools_used # => ["weather"] +``` + +Use toolsets to expose multiple tool methods from a single class: + +```ruby +text_tools = DSPy::Tools::TextProcessingToolset.to_tools +agent = DSPy::ReAct.new(MySignature, tools: text_tools) +``` + +### CodeAct + +Think-Code-Observe agent that synthesizes and executes Ruby code. Ships as a separate gem. + +```ruby +# Gemfile +gem 'dspy-code_act', '~> 0.29' +``` + +```ruby +programmer = DSPy::CodeAct.new(ProgrammingSignature, max_iterations: 10) +result = programmer.call(task: "Calculate the factorial of 20") +``` + +### Predictor Comparison + +| Predictor | Speed | Token Usage | Best For | +|-----------|-------|-------------|----------| +| Predict | Fastest | Low | Classification, extraction | +| ChainOfThought | Moderate | Medium-High | Complex reasoning, analysis | +| ReAct | Slower | High | Multi-step tasks with tools | +| CodeAct | Slowest | Very High | Dynamic programming, calculations | + +### Concurrent Predictions + +Process multiple independent predictions simultaneously using `Async::Barrier`: + +```ruby +require 'async' +require 'async/barrier' + +analyzer = DSPy::Predict.new(ContentAnalyzer) +documents = ["Text one", "Text two", "Text three"] + +Async do + barrier = Async::Barrier.new + + tasks = documents.map do |doc| + barrier.async { analyzer.call(content: doc) } + end + + barrier.wait + predictions = tasks.map(&:wait) + + predictions.each { |p| puts p.sentiment } +end +``` + +Add `gem 'async', '~> 2.29'` to the Gemfile. Handle errors within each `barrier.async` block to prevent one failure from cancelling others: + +```ruby +barrier.async do + begin + analyzer.call(content: doc) + rescue StandardError => e + nil + end +end +``` + +### Few-Shot Examples and Instruction Tuning + +```ruby +classifier = DSPy::Predict.new(SentimentAnalysis) + +examples = [ + DSPy::FewShotExample.new( + input: { text: "Love it!" }, + output: { sentiment: "positive", confidence: 0.95 } + ) +] + +optimized = classifier.with_examples(examples) +tuned = classifier.with_instruction("Be precise and confident.") +``` + +--- + +## Type System + +### Automatic Type Conversion + +DSPy.rb v0.9.0+ automatically converts LLM JSON responses to typed Ruby objects: + +- **Enums**: String values become `T::Enum` instances (case-insensitive) +- **Structs**: Nested hashes become `T::Struct` objects +- **Arrays**: Elements convert recursively +- **Defaults**: Missing fields use declared defaults + +### Discriminators for Union Types + +When a field uses `T.any()` with struct types, DSPy adds a `_type` field to each struct's schema. On deserialization, `_type` selects the correct struct class: + +```json +{ + "action": { + "_type": "CreateTask", + "title": "Review Q4 Report" + } +} +``` + +DSPy matches `"CreateTask"` against the union members and instantiates the correct struct. No manual discriminator field is needed. + +### Recursive Types + +Structs referencing themselves are supported. The schema generator tracks visited types and produces `$ref` pointers under `$defs`: + +```ruby +class TreeNode < T::Struct + const :label, String + const :children, T::Array[TreeNode], default: [] +end +``` + +The generated schema uses `"$ref": "#/$defs/TreeNode"` for the children array items, preventing infinite schema expansion. + +### Nesting Depth + +- 1-2 levels: reliable across all providers. +- 3-4 levels: works but increases schema complexity. +- 5+ levels: may trigger OpenAI depth validation warnings and reduce LLM accuracy. Flatten deeply nested structures or split into multiple signatures. + +### Tips + +- Prefer `T::Array[X], default: []` over `T.nilable(T::Array[X])` -- the nilable form causes schema issues with OpenAI structured outputs. +- Use clear struct names for union types since they become `_type` discriminator values. +- Limit union types to 2-4 members for reliable model comprehension. +- Check schema compatibility with `DSPy::OpenAI::LM::SchemaConverter.validate_compatibility(schema)`. diff --git a/.codex/skills/dspy-ruby/references/observability.md b/.cursor/skills/dspy-ruby/references/observability.md similarity index 100% rename from .codex/skills/dspy-ruby/references/observability.md rename to .cursor/skills/dspy-ruby/references/observability.md diff --git a/.cursor/skills/dspy-ruby/references/optimization.md b/.cursor/skills/dspy-ruby/references/optimization.md new file mode 100644 index 0000000000..0f2e8e7d0a --- /dev/null +++ b/.cursor/skills/dspy-ruby/references/optimization.md @@ -0,0 +1,603 @@ +# DSPy.rb Optimization + +## MIPROv2 + +MIPROv2 (Multi-prompt Instruction Proposal with Retrieval Optimization) is the primary instruction tuner in DSPy.rb. It proposes new instructions and few-shot demonstrations per predictor, evaluates them on mini-batches, and retains candidates that improve the metric. It ships as a separate gem to keep the Gaussian Process dependency tree out of apps that do not need it. + +### Installation + +```ruby +# Gemfile +gem "dspy" +gem "dspy-miprov2" +``` + +Bundler auto-requires `dspy/miprov2`. No additional `require` statement is needed. + +### AutoMode presets + +Use `DSPy::Teleprompt::MIPROv2::AutoMode` for preconfigured optimizers: + +```ruby +light = DSPy::Teleprompt::MIPROv2::AutoMode.light(metric: metric) # 6 trials, greedy +medium = DSPy::Teleprompt::MIPROv2::AutoMode.medium(metric: metric) # 12 trials, adaptive +heavy = DSPy::Teleprompt::MIPROv2::AutoMode.heavy(metric: metric) # 18 trials, Bayesian +``` + +| Preset | Trials | Strategy | Use case | +|----------|--------|------------|-----------------------------------------------------| +| `light` | 6 | `:greedy` | Quick wins on small datasets or during prototyping. | +| `medium` | 12 | `:adaptive`| Balanced exploration vs. runtime for most pilots. | +| `heavy` | 18 | `:bayesian`| Highest accuracy targets or multi-stage programs. | + +### Manual configuration with dry-configurable + +`DSPy::Teleprompt::MIPROv2` includes `Dry::Configurable`. Configure at the class level (defaults for all instances) or instance level (overrides class defaults). + +**Class-level defaults:** + +```ruby +DSPy::Teleprompt::MIPROv2.configure do |config| + config.optimization_strategy = :bayesian + config.num_trials = 30 + config.bootstrap_sets = 10 +end +``` + +**Instance-level overrides:** + +```ruby +optimizer = DSPy::Teleprompt::MIPROv2.new(metric: metric) +optimizer.configure do |config| + config.num_trials = 15 + config.num_instruction_candidates = 6 + config.bootstrap_sets = 5 + config.max_bootstrapped_examples = 4 + config.max_labeled_examples = 16 + config.optimization_strategy = :adaptive # :greedy, :adaptive, :bayesian + config.early_stopping_patience = 3 + config.init_temperature = 1.0 + config.final_temperature = 0.1 + config.minibatch_size = nil # nil = auto + config.auto_seed = 42 +end +``` + +The `optimization_strategy` setting accepts symbols (`:greedy`, `:adaptive`, `:bayesian`) and coerces them internally to `DSPy::Teleprompt::OptimizationStrategy` T::Enum values. + +The old `config:` constructor parameter is removed. Passing `config:` raises `ArgumentError`. + +### Auto presets via configure + +Instead of `AutoMode`, set the preset through the configure block: + +```ruby +optimizer = DSPy::Teleprompt::MIPROv2.new(metric: metric) +optimizer.configure do |config| + config.auto_preset = DSPy::Teleprompt::AutoPreset.deserialize("medium") +end +``` + +### Compile and inspect + +```ruby +program = DSPy::Predict.new(MySignature) + +result = optimizer.compile( + program, + trainset: train_examples, + valset: val_examples +) + +optimized_program = result.optimized_program +puts "Best score: #{result.best_score_value}" +``` + +The `result` object exposes: +- `optimized_program` -- ready-to-use predictor with updated instruction and demos. +- `optimization_trace[:trial_logs]` -- per-trial record of instructions, demos, and scores. +- `metadata[:optimizer]` -- `"MIPROv2"`, useful when persisting experiments from multiple optimizers. + +### Multi-stage programs + +MIPROv2 generates dataset summaries for each predictor and proposes per-stage instructions. For a ReAct agent with `thought_generator` and `observation_processor` predictors, the optimizer handles credit assignment internally. The metric only needs to evaluate the final output. + +### Bootstrap sampling + +During the bootstrap phase MIPROv2: +1. Generates dataset summaries from the training set. +2. Bootstraps few-shot demonstrations by running the baseline program. +3. Proposes candidate instructions grounded in the summaries and bootstrapped examples. +4. Evaluates each candidate on mini-batches drawn from the validation set. + +Control the bootstrap phase with `bootstrap_sets`, `max_bootstrapped_examples`, and `max_labeled_examples`. + +### Bayesian optimization + +When `optimization_strategy` is `:bayesian` (or when using the `heavy` preset), MIPROv2 fits a Gaussian Process surrogate over past trial scores to select the next candidate. This replaces random search with informed exploration, reducing the number of trials needed to find high-scoring instructions. + +--- + +## GEPA + +GEPA (Genetic-Pareto Reflective Prompt Evolution) is a feedback-driven optimizer. It runs the program on a small batch, collects scores and textual feedback, and asks a reflection LM to rewrite the instruction. Improved candidates are retained on a Pareto frontier. + +### Installation + +```ruby +# Gemfile +gem "dspy" +gem "dspy-gepa" +``` + +The `dspy-gepa` gem depends on the `gepa` core optimizer gem automatically. + +### Metric contract + +GEPA metrics return `DSPy::Prediction` with both a numeric score and a feedback string. Do not return a plain boolean. + +```ruby +metric = lambda do |example, prediction| + expected = example.expected_values[:label] + predicted = prediction.label + + score = predicted == expected ? 1.0 : 0.0 + feedback = if score == 1.0 + "Correct (#{expected}) for: \"#{example.input_values[:text][0..60]}\"" + else + "Misclassified (expected #{expected}, got #{predicted}) for: \"#{example.input_values[:text][0..60]}\"" + end + + DSPy::Prediction.new(score: score, feedback: feedback) +end +``` + +Keep the score in `[0, 1]`. Always include a short feedback message explaining what happened -- GEPA hands this text to the reflection model so it can reason about failures. + +### Feedback maps + +`feedback_map` targets individual predictors inside a composite module. Each entry receives keyword arguments and returns a `DSPy::Prediction`: + +```ruby +feedback_map = { + 'self' => lambda do |predictor_output:, predictor_inputs:, module_inputs:, module_outputs:, captured_trace:| + expected = module_inputs.expected_values[:label] + predicted = predictor_output.label + + DSPy::Prediction.new( + score: predicted == expected ? 1.0 : 0.0, + feedback: "Classifier saw \"#{predictor_inputs[:text][0..80]}\" -> #{predicted} (expected #{expected})" + ) + end +} +``` + +For single-predictor programs, key the map with `'self'`. For multi-predictor chains, add entries per component so the reflection LM sees localized context at each step. Omit `feedback_map` entirely if the top-level metric already covers the basics. + +### Configuring the teleprompter + +```ruby +teleprompter = DSPy::Teleprompt::GEPA.new( + metric: metric, + reflection_lm: DSPy::ReflectionLM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']), + feedback_map: feedback_map, + config: { + max_metric_calls: 600, + minibatch_size: 6, + skip_perfect_score: false + } +) +``` + +Key configuration knobs: + +| Knob | Purpose | +|----------------------|-------------------------------------------------------------------------------------------| +| `max_metric_calls` | Hard budget on evaluation calls. Set to at least the validation set size plus a few minibatches. | +| `minibatch_size` | Examples per reflective replay batch. Smaller = cheaper iterations, noisier scores. | +| `skip_perfect_score` | Set `true` to stop early when a candidate reaches score `1.0`. | + +### Minibatch sizing + +| Goal | Suggested size | Rationale | +|-------------------------------------------------|----------------|------------------------------------------------------------| +| Explore many candidates within a tight budget | 3--6 | Cheap iterations, more prompt variants, noisier metrics. | +| Stable metrics when each rollout is costly | 8--12 | Smoother scores, fewer candidates unless budget is raised. | +| Investigate specific failure modes | 3--4 then 8+ | Start with breadth, increase once patterns emerge. | + +### Compile and evaluate + +```ruby +program = DSPy::Predict.new(MySignature) + +result = teleprompter.compile(program, trainset: train, valset: val) +optimized_program = result.optimized_program + +test_metrics = evaluate(optimized_program, test) +``` + +The `result` object exposes: +- `optimized_program` -- predictor with updated instruction and few-shot examples. +- `best_score_value` -- validation score for the best candidate. +- `metadata` -- candidate counts, trace hashes, and telemetry IDs. + +### Reflection LM + +Swap `DSPy::ReflectionLM` for any callable object that accepts the reflection prompt hash and returns a string. The default reflection signature extracts the new instruction from triple backticks in the response. + +### Experiment tracking + +Plug `GEPA::Logging::ExperimentTracker` into a persistence layer: + +```ruby +tracker = GEPA::Logging::ExperimentTracker.new +tracker.with_subscriber { |event| MyModel.create!(payload: event) } + +teleprompter = DSPy::Teleprompt::GEPA.new( + metric: metric, + reflection_lm: reflection_lm, + experiment_tracker: tracker, + config: { max_metric_calls: 900 } +) +``` + +The tracker emits Pareto update events, merge decisions, and candidate evolution records as JSONL. + +### Pareto frontier + +GEPA maintains a diverse candidate pool and samples from the Pareto frontier instead of mutating only the top-scoring program. This balances exploration and prevents the search from collapsing onto a single lineage. + +Enable the merge proposer after multiple strong lineages emerge: + +```ruby +config: { + max_metric_calls: 900, + enable_merge_proposer: true +} +``` + +Premature merges eat budget without meaningful gains. Gate merge on having several validated candidates first. + +### Advanced options + +- `acceptance_strategy:` -- plug in bespoke Pareto filters or early-stop heuristics. +- Telemetry spans emit via `GEPA::Telemetry`. Enable global observability with `DSPy.configure { |c| c.observability = true }` to stream spans to an OpenTelemetry exporter. + +--- + +## Evaluation Framework + +`DSPy::Evals` provides batch evaluation of predictors against test datasets with built-in and custom metrics. + +### Basic usage + +```ruby +metric = proc do |example, prediction| + prediction.answer == example.expected_values[:answer] +end + +evaluator = DSPy::Evals.new(predictor, metric: metric) + +result = evaluator.evaluate( + test_examples, + display_table: true, + display_progress: true +) + +puts "Pass rate: #{(result.pass_rate * 100).round(1)}%" +puts "Passed: #{result.passed_examples}/#{result.total_examples}" +``` + +### DSPy::Example + +Convert raw data into `DSPy::Example` instances before passing to optimizers or evaluators. Each example carries `input_values` and `expected_values`: + +```ruby +examples = rows.map do |row| + DSPy::Example.new( + input_values: { text: row[:text] }, + expected_values: { label: row[:label] } + ) +end + +train, val, test = split_examples(examples, train_ratio: 0.6, val_ratio: 0.2, seed: 42) +``` + +Hold back a test set from the optimization loop. Optimizers work on train/val; only the test set proves generalization. + +### Built-in metrics + +```ruby +# Exact match -- prediction must exactly equal expected value +metric = DSPy::Metrics.exact_match(field: :answer, case_sensitive: true) + +# Contains -- prediction must contain expected substring +metric = DSPy::Metrics.contains(field: :answer, case_sensitive: false) + +# Numeric difference -- numeric output within tolerance +metric = DSPy::Metrics.numeric_difference(field: :answer, tolerance: 0.01) + +# Composite AND -- all sub-metrics must pass +metric = DSPy::Metrics.composite_and( + DSPy::Metrics.exact_match(field: :answer), + DSPy::Metrics.contains(field: :reasoning) +) +``` + +### Custom metrics + +```ruby +quality_metric = lambda do |example, prediction| + return false unless prediction + + score = 0.0 + score += 0.5 if prediction.answer == example.expected_values[:answer] + score += 0.3 if prediction.explanation && prediction.explanation.length > 50 + score += 0.2 if prediction.confidence && prediction.confidence > 0.8 + score >= 0.7 +end + +evaluator = DSPy::Evals.new(predictor, metric: quality_metric) +``` + +Access prediction fields with dot notation (`prediction.answer`), not hash notation. + +### Observability hooks + +Register callbacks without editing the evaluator: + +```ruby +DSPy::Evals.before_example do |payload| + example = payload[:example] + DSPy.logger.info("Evaluating example #{example.id}") if example.respond_to?(:id) +end + +DSPy::Evals.after_batch do |payload| + result = payload[:result] + Langfuse.event( + name: 'eval.batch', + metadata: { + total: result.total_examples, + passed: result.passed_examples, + score: result.score + } + ) +end +``` + +Available hooks: `before_example`, `after_example`, `before_batch`, `after_batch`. + +### Langfuse score export + +Enable `export_scores: true` to emit `score.create` events for each evaluated example and a batch score at the end: + +```ruby +evaluator = DSPy::Evals.new( + predictor, + metric: metric, + export_scores: true, + score_name: 'qa_accuracy' # default: 'evaluation' +) + +result = evaluator.evaluate(test_examples) +# Emits per-example scores + overall batch score via DSPy::Scores::Exporter +``` + +Scores attach to the current trace context automatically and flow to Langfuse asynchronously. + +### Evaluation results + +```ruby +result = evaluator.evaluate(test_examples) + +result.score # Overall score (0.0 to 1.0) +result.passed_count # Examples that passed +result.failed_count # Examples that failed +result.error_count # Examples that errored + +result.results.each do |r| + r.passed # Boolean + r.score # Numeric score + r.error # Error message if the example errored +end +``` + +### Integration with optimizers + +```ruby +metric = proc do |example, prediction| + expected = example.expected_values[:answer].to_s.strip.downcase + predicted = prediction.answer.to_s.strip.downcase + !expected.empty? && predicted.include?(expected) +end + +optimizer = DSPy::Teleprompt::MIPROv2::AutoMode.medium(metric: metric) + +result = optimizer.compile( + DSPy::Predict.new(QASignature), + trainset: train_examples, + valset: val_examples +) + +evaluator = DSPy::Evals.new(result.optimized_program, metric: metric) +test_result = evaluator.evaluate(test_examples, display_table: true) +puts "Test accuracy: #{(test_result.pass_rate * 100).round(2)}%" +``` + +--- + +## Storage System + +`DSPy::Storage` persists optimization results, tracks history, and manages multiple versions of optimized programs. + +### ProgramStorage (low-level) + +```ruby +storage = DSPy::Storage::ProgramStorage.new(storage_path: "./dspy_storage") + +# Save +saved = storage.save_program( + result.optimized_program, + result, + metadata: { + signature_class: 'ClassifyText', + optimizer: 'MIPROv2', + examples_count: examples.size + } +) +puts "Stored with ID: #{saved.program_id}" + +# Load +saved = storage.load_program(program_id) +predictor = saved.program +score = saved.optimization_result[:best_score_value] + +# List +storage.list_programs.each do |p| + puts "#{p[:program_id]} -- score: #{p[:best_score]} -- saved: #{p[:saved_at]}" +end +``` + +### StorageManager (recommended) + +```ruby +manager = DSPy::Storage::StorageManager.new + +# Save with tags +saved = manager.save_optimization_result( + result, + tags: ['production', 'sentiment-analysis'], + description: 'Optimized sentiment classifier v2' +) + +# Find programs +programs = manager.find_programs( + optimizer: 'MIPROv2', + min_score: 0.85, + tags: ['production'] +) + +recent = manager.find_programs( + max_age_days: 7, + signature_class: 'ClassifyText' +) + +# Get best program for a signature +best = manager.get_best_program('ClassifyText') +predictor = best.program +``` + +Global shorthand: + +```ruby +DSPy::Storage::StorageManager.save(result, metadata: { version: '2.0' }) +DSPy::Storage::StorageManager.load(program_id) +DSPy::Storage::StorageManager.best('ClassifyText') +``` + +### Checkpoints + +Create and restore checkpoints during long-running optimizations: + +```ruby +# Save a checkpoint +manager.create_checkpoint( + current_result, + 'iteration_50', + metadata: { iteration: 50, current_score: 0.87 } +) + +# Restore +restored = manager.restore_checkpoint('iteration_50') +program = restored.program + +# Auto-checkpoint every N iterations +if iteration % 10 == 0 + manager.create_checkpoint(current_result, "auto_checkpoint_#{iteration}") +end +``` + +### Import and export + +Share programs between environments: + +```ruby +storage = DSPy::Storage::ProgramStorage.new + +# Export +storage.export_programs(['abc123', 'def456'], './export_backup.json') + +# Import +imported = storage.import_programs('./export_backup.json') +puts "Imported #{imported.size} programs" +``` + +### Optimization history + +```ruby +history = manager.get_optimization_history + +history[:summary][:total_programs] +history[:summary][:avg_score] + +history[:optimizer_stats].each do |optimizer, stats| + puts "#{optimizer}: #{stats[:count]} programs, best: #{stats[:best_score]}" +end + +history[:trends][:improvement_percentage] +``` + +### Program comparison + +```ruby +comparison = manager.compare_programs(id_a, id_b) +comparison[:comparison][:score_difference] +comparison[:comparison][:better_program] +comparison[:comparison][:age_difference_hours] +``` + +### Storage configuration + +```ruby +config = DSPy::Storage::StorageManager::StorageConfig.new +config.storage_path = Rails.root.join('dspy_storage') +config.auto_save = true +config.save_intermediate_results = false +config.max_stored_programs = 100 + +manager = DSPy::Storage::StorageManager.new(config: config) +``` + +### Cleanup + +Remove old programs. Cleanup retains the best performing and most recent programs using a weighted score (70% performance, 30% recency): + +```ruby +deleted_count = manager.cleanup_old_programs +``` + +### Storage events + +The storage system emits structured log events for monitoring: +- `dspy.storage.save_start`, `dspy.storage.save_complete`, `dspy.storage.save_error` +- `dspy.storage.load_start`, `dspy.storage.load_complete`, `dspy.storage.load_error` +- `dspy.storage.delete`, `dspy.storage.export`, `dspy.storage.import`, `dspy.storage.cleanup` + +### File layout + +``` +dspy_storage/ + programs/ + abc123def456.json + 789xyz012345.json + history.json +``` + +--- + +## API rules + +- Call predictors with `.call()`, not `.forward()`. +- Access prediction fields with dot notation (`result.answer`), not hash notation (`result[:answer]`). +- GEPA metrics return `DSPy::Prediction.new(score:, feedback:)`, not a boolean. +- MIPROv2 metrics may return `true`/`false`, a numeric score, or `DSPy::Prediction`. diff --git a/.cursor/skills/dspy-ruby/references/providers.md b/.cursor/skills/dspy-ruby/references/providers.md new file mode 100644 index 0000000000..31bf1a12d4 --- /dev/null +++ b/.cursor/skills/dspy-ruby/references/providers.md @@ -0,0 +1,418 @@ +# DSPy.rb LLM Providers + +## Adapter Architecture + +DSPy.rb ships provider SDKs as separate adapter gems. Install only the adapters the project needs. Each adapter gem depends on the official SDK for its provider and auto-loads when present -- no explicit `require` necessary. + +```ruby +# Gemfile +gem 'dspy' # core framework (no provider SDKs) +gem 'dspy-openai' # OpenAI, OpenRouter, Ollama +gem 'dspy-anthropic' # Claude +gem 'dspy-gemini' # Gemini +gem 'dspy-ruby_llm' # RubyLLM unified adapter (12+ providers) +``` + +--- + +## Per-Provider Adapters + +### dspy-openai + +Covers any endpoint that speaks the OpenAI chat-completions protocol: OpenAI itself, OpenRouter, and Ollama. + +**SDK dependency:** `openai ~> 0.17` + +```ruby +# OpenAI +lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) + +# OpenRouter -- access 200+ models behind a single key +lm = DSPy::LM.new('openrouter/x-ai/grok-4-fast:free', + api_key: ENV['OPENROUTER_API_KEY'] +) + +# Ollama -- local models, no API key required +lm = DSPy::LM.new('ollama/llama3.2') + +# Remote Ollama instance +lm = DSPy::LM.new('ollama/llama3.2', + base_url: 'https://my-ollama.example.com/v1', + api_key: 'optional-auth-token' +) +``` + +All three sub-adapters share the same request handling, structured-output support, and error reporting. Swap providers without changing higher-level DSPy code. + +For OpenRouter models that lack native structured-output support, disable it explicitly: + +```ruby +lm = DSPy::LM.new('openrouter/deepseek/deepseek-chat-v3.1:free', + api_key: ENV['OPENROUTER_API_KEY'], + structured_outputs: false +) +``` + +### dspy-anthropic + +Provides the Claude adapter. Install it for any `anthropic/*` model id. + +**SDK dependency:** `anthropic ~> 1.12` + +```ruby +lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', + api_key: ENV['ANTHROPIC_API_KEY'] +) +``` + +Structured outputs default to tool-based JSON extraction (`structured_outputs: true`). Set `structured_outputs: false` to use enhanced-prompting extraction instead. + +```ruby +# Tool-based extraction (default, most reliable) +lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', + api_key: ENV['ANTHROPIC_API_KEY'], + structured_outputs: true +) + +# Enhanced prompting extraction +lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', + api_key: ENV['ANTHROPIC_API_KEY'], + structured_outputs: false +) +``` + +### dspy-gemini + +Provides the Gemini adapter. Install it for any `gemini/*` model id. + +**SDK dependency:** `gemini-ai ~> 4.3` + +```ruby +lm = DSPy::LM.new('gemini/gemini-2.5-flash', + api_key: ENV['GEMINI_API_KEY'] +) +``` + +**Environment variable:** `GEMINI_API_KEY` (also accepts `GOOGLE_API_KEY`). + +--- + +## RubyLLM Unified Adapter + +The `dspy-ruby_llm` gem provides a single adapter that routes to 12+ providers through [RubyLLM](https://rubyllm.com). Use it when a project talks to multiple providers or needs access to Bedrock, VertexAI, DeepSeek, or Mistral without dedicated adapter gems. + +**SDK dependency:** `ruby_llm ~> 1.3` + +### Model ID Format + +Prefix every model id with `ruby_llm/`: + +```ruby +lm = DSPy::LM.new('ruby_llm/gpt-4o-mini') +lm = DSPy::LM.new('ruby_llm/claude-sonnet-4-20250514') +lm = DSPy::LM.new('ruby_llm/gemini-2.5-flash') +``` + +The adapter detects the provider from RubyLLM's model registry automatically. For models not in the registry, pass `provider:` explicitly: + +```ruby +lm = DSPy::LM.new('ruby_llm/llama3.2', provider: 'ollama') +lm = DSPy::LM.new('ruby_llm/anthropic/claude-3-opus', + api_key: ENV['OPENROUTER_API_KEY'], + provider: 'openrouter' +) +``` + +### Using Existing RubyLLM Configuration + +When RubyLLM is already configured globally, omit the `api_key:` argument. DSPy reuses the global config automatically: + +```ruby +RubyLLM.configure do |config| + config.openai_api_key = ENV['OPENAI_API_KEY'] + config.anthropic_api_key = ENV['ANTHROPIC_API_KEY'] +end + +# No api_key needed -- picks up the global config +DSPy.configure do |c| + c.lm = DSPy::LM.new('ruby_llm/gpt-4o-mini') +end +``` + +When an `api_key:` (or any of `base_url:`, `timeout:`, `max_retries:`) is passed, DSPy creates a **scoped context** instead of reusing the global config. + +### Cloud-Hosted Providers (Bedrock, VertexAI) + +Configure RubyLLM globally first, then reference the model: + +```ruby +# AWS Bedrock +RubyLLM.configure do |c| + c.bedrock_api_key = ENV['AWS_ACCESS_KEY_ID'] + c.bedrock_secret_key = ENV['AWS_SECRET_ACCESS_KEY'] + c.bedrock_region = 'us-east-1' +end +lm = DSPy::LM.new('ruby_llm/anthropic.claude-3-5-sonnet', provider: 'bedrock') + +# Google VertexAI +RubyLLM.configure do |c| + c.vertexai_project_id = 'your-project-id' + c.vertexai_location = 'us-central1' +end +lm = DSPy::LM.new('ruby_llm/gemini-pro', provider: 'vertexai') +``` + +### Supported Providers Table + +| Provider | Example Model ID | Notes | +|-------------|--------------------------------------------|---------------------------------| +| OpenAI | `ruby_llm/gpt-4o-mini` | Auto-detected from registry | +| Anthropic | `ruby_llm/claude-sonnet-4-20250514` | Auto-detected from registry | +| Gemini | `ruby_llm/gemini-2.5-flash` | Auto-detected from registry | +| DeepSeek | `ruby_llm/deepseek-chat` | Auto-detected from registry | +| Mistral | `ruby_llm/mistral-large` | Auto-detected from registry | +| Ollama | `ruby_llm/llama3.2` | Use `provider: 'ollama'` | +| AWS Bedrock | `ruby_llm/anthropic.claude-3-5-sonnet` | Configure RubyLLM globally | +| VertexAI | `ruby_llm/gemini-pro` | Configure RubyLLM globally | +| OpenRouter | `ruby_llm/anthropic/claude-3-opus` | Use `provider: 'openrouter'` | +| Perplexity | `ruby_llm/llama-3.1-sonar-large` | Use `provider: 'perplexity'` | +| GPUStack | `ruby_llm/model-name` | Use `provider: 'gpustack'` | + +--- + +## Rails Initializer Pattern + +Configure DSPy inside an `after_initialize` block so Rails credentials and environment are fully loaded: + +```ruby +# config/initializers/dspy.rb +Rails.application.config.after_initialize do + return if Rails.env.test? # skip in test -- use VCR cassettes instead + + DSPy.configure do |config| + config.lm = DSPy::LM.new( + 'openai/gpt-4o-mini', + api_key: Rails.application.credentials.openai_api_key, + structured_outputs: true + ) + + config.logger = if Rails.env.production? + Dry.Logger(:dspy, formatter: :json) do |logger| + logger.add_backend(stream: Rails.root.join("log/dspy.log")) + end + else + Dry.Logger(:dspy) do |logger| + logger.add_backend(level: :debug, stream: $stdout) + end + end + end +end +``` + +Key points: + +- Wrap in `after_initialize` so `Rails.application.credentials` is available. +- Return early in the test environment. Rely on VCR cassettes for deterministic LLM responses. +- Set `structured_outputs: true` (the default) for provider-native JSON extraction. +- Use `Dry.Logger` with `:json` formatter in production for structured log parsing. + +--- + +## Fiber-Local LM Context + +`DSPy.with_lm` sets a temporary language-model override scoped to the current Fiber. Every predictor call inside the block uses the override; outside the block the previous LM takes effect again. + +```ruby +fast = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) +powerful = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY']) + +classifier = Classifier.new + +# Uses the global LM +result = classifier.call(text: "Hello") + +# Temporarily switch to the fast model +DSPy.with_lm(fast) do + result = classifier.call(text: "Hello") # uses gpt-4o-mini +end + +# Temporarily switch to the powerful model +DSPy.with_lm(powerful) do + result = classifier.call(text: "Hello") # uses claude-sonnet-4 +end +``` + +### LM Resolution Hierarchy + +DSPy resolves the active language model in this order: + +1. **Instance-level LM** -- set directly on a module instance via `configure` +2. **Fiber-local LM** -- set via `DSPy.with_lm` +3. **Global LM** -- set via `DSPy.configure` + +Instance-level configuration always wins, even inside a `DSPy.with_lm` block: + +```ruby +classifier = Classifier.new +classifier.configure { |c| c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY']) } + +fast = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) + +DSPy.with_lm(fast) do + classifier.call(text: "Test") # still uses claude-sonnet-4 (instance-level wins) +end +``` + +### configure_predictor for Fine-Grained Agent Control + +Complex agents (`ReAct`, `CodeAct`, `DeepResearch`, `DeepSearch`) contain internal predictors. Use `configure` for a blanket override and `configure_predictor` to target a specific sub-predictor: + +```ruby +agent = DSPy::ReAct.new(MySignature, tools: tools) + +# Set a default LM for the agent and all its children +agent.configure { |c| c.lm = DSPy::LM.new('openai/gpt-4o-mini', api_key: ENV['OPENAI_API_KEY']) } + +# Override just the reasoning predictor with a more capable model +agent.configure_predictor('thought_generator') do |c| + c.lm = DSPy::LM.new('anthropic/claude-sonnet-4-20250514', api_key: ENV['ANTHROPIC_API_KEY']) +end + +result = agent.call(question: "Summarize the report") +``` + +Both methods support chaining: + +```ruby +agent + .configure { |c| c.lm = cheap_model } + .configure_predictor('thought_generator') { |c| c.lm = expensive_model } +``` + +#### Available Predictors by Agent Type + +| Agent | Internal Predictors | +|----------------------|------------------------------------------------------------------| +| `DSPy::ReAct` | `thought_generator`, `observation_processor` | +| `DSPy::CodeAct` | `code_generator`, `observation_processor` | +| `DSPy::DeepResearch` | `planner`, `synthesizer`, `qa_reviewer`, `reporter` | +| `DSPy::DeepSearch` | `seed_predictor`, `search_predictor`, `reader_predictor`, `reason_predictor` | + +#### Propagation Rules + +- Configuration propagates recursively to children and grandchildren. +- Children with an already-configured LM are **not** overwritten by a later parent `configure` call. +- Configure the parent first, then override specific children. + +--- + +## Feature-Flagged Model Selection + +Use a `FeatureFlags` module backed by ENV vars to centralize model selection. Each tool or agent reads its model from the flags, falling back to a global default. + +```ruby +module FeatureFlags + module_function + + def default_model + ENV.fetch('DSPY_DEFAULT_MODEL', 'openai/gpt-4o-mini') + end + + def default_api_key + ENV.fetch('DSPY_DEFAULT_API_KEY') { ENV.fetch('OPENAI_API_KEY', nil) } + end + + def model_for(tool_name) + env_key = "DSPY_MODEL_#{tool_name.upcase}" + ENV.fetch(env_key, default_model) + end + + def api_key_for(tool_name) + env_key = "DSPY_API_KEY_#{tool_name.upcase}" + ENV.fetch(env_key, default_api_key) + end +end +``` + +### Per-Tool Model Override + +Override an individual tool's model without touching application code: + +```bash +# .env +DSPY_DEFAULT_MODEL=openai/gpt-4o-mini +DSPY_DEFAULT_API_KEY=sk-... + +# Override the classifier to use Claude +DSPY_MODEL_CLASSIFIER=anthropic/claude-sonnet-4-20250514 +DSPY_API_KEY_CLASSIFIER=sk-ant-... + +# Override the summarizer to use Gemini +DSPY_MODEL_SUMMARIZER=gemini/gemini-2.5-flash +DSPY_API_KEY_SUMMARIZER=... +``` + +Wire each agent to its flag at initialization: + +```ruby +class ClassifierAgent < DSPy::Module + def initialize + super + model = FeatureFlags.model_for('classifier') + api_key = FeatureFlags.api_key_for('classifier') + + @predictor = DSPy::Predict.new(ClassifySignature) + configure { |c| c.lm = DSPy::LM.new(model, api_key: api_key) } + end + + def forward(text:) + @predictor.call(text: text) + end +end +``` + +This pattern keeps model routing declarative and avoids scattering `DSPy::LM.new` calls across the codebase. + +--- + +## Compatibility Matrix + +Feature support across direct adapter gems. All features listed assume `structured_outputs: true` (the default). + +| Feature | OpenAI | Anthropic | Gemini | Ollama | OpenRouter | RubyLLM | +|----------------------|--------|-----------|--------|----------|------------|-------------| +| Structured Output | Native JSON mode | Tool-based extraction | Native JSON schema | OpenAI-compatible JSON | Varies by model | Via `with_schema` | +| Vision (Images) | File + URL | File + Base64 | File + Base64 | Limited | Varies | Delegates to underlying provider | +| Image URLs | Yes | No | No | No | Varies | Depends on provider | +| Tool Calling | Yes | Yes | Yes | Varies | Varies | Yes | +| Streaming | Yes | Yes | Yes | Yes | Yes | Yes | + +**Notes:** + +- **Structured Output** is enabled by default on every adapter. Set `structured_outputs: false` to fall back to enhanced-prompting extraction. +- **Vision / Image URLs:** Only OpenAI supports passing a URL directly. For Anthropic and Gemini, load images from file or Base64: + ```ruby + DSPy::Image.from_url("https://example.com/img.jpg") # OpenAI only + DSPy::Image.from_file("path/to/image.jpg") # all providers + DSPy::Image.from_base64(data, mime_type: "image/jpeg") # all providers + ``` +- **RubyLLM** delegates to the underlying provider, so feature support matches the provider column in the table. + +### Choosing an Adapter Strategy + +| Scenario | Recommended Adapter | +|-------------------------------------------|--------------------------------| +| Single provider (OpenAI, Claude, or Gemini) | Dedicated gem (`dspy-openai`, `dspy-anthropic`, `dspy-gemini`) | +| Multi-provider with per-agent model routing | `dspy-ruby_llm` | +| AWS Bedrock or Google VertexAI | `dspy-ruby_llm` | +| Local development with Ollama | `dspy-openai` (Ollama sub-adapter) or `dspy-ruby_llm` | +| OpenRouter for cost optimization | `dspy-openai` (OpenRouter sub-adapter) | + +### Current Recommended Models + +| Provider | Model ID | Use Case | +|-----------|---------------------------------------|-----------------------| +| OpenAI | `openai/gpt-4o-mini` | Fast, cost-effective | +| Anthropic | `anthropic/claude-sonnet-4-20250514` | Balanced reasoning | +| Gemini | `gemini/gemini-2.5-flash` | Fast, cost-effective | +| Ollama | `ollama/llama3.2` | Local, zero API cost | diff --git a/.codex/skills/dspy-ruby/references/toolsets.md b/.cursor/skills/dspy-ruby/references/toolsets.md similarity index 100% rename from .codex/skills/dspy-ruby/references/toolsets.md rename to .cursor/skills/dspy-ruby/references/toolsets.md diff --git a/.cursor/skills/every-style-editor/SKILL.md b/.cursor/skills/every-style-editor/SKILL.md new file mode 100644 index 0000000000..4180e02f04 --- /dev/null +++ b/.cursor/skills/every-style-editor/SKILL.md @@ -0,0 +1,64 @@ +--- +name: every-style-editor +description: Reviews and edits text content to conform to Every's editorial style guide. Use when written content needs style compliance checks for headlines, punctuation, voice, and formatting. +tools: Task, Glob, Grep, LS, ExitPlanMode, Read, Edit, MultiEdit, Write, NotebookRead, NotebookEdit, WebFetch, TodoWrite, WebSearch +model: inherit +--- + +You are an expert copy editor specializing in Every's house style guide. Your role is to meticulously review text content and suggest edits to ensure compliance with Every's specific editorial standards. + +When reviewing content, you will: + +1. **Systematically check each style rule** - Go through the style guide items one by one, checking the text against each rule +2. **Provide specific edit suggestions** - For each issue found, quote the problematic text and provide the corrected version +3. **Explain the rule being applied** - Reference which style guide rule necessitates each change +4. **Maintain the author's voice** - Make only the changes necessary for style compliance while preserving the original tone and meaning + +**Every Style Guide Rules to Apply:** + +- Headlines use title case; everything else uses sentence case +- Companies are singular ("it" not "they"); teams/people within companies are plural +- Remove unnecessary "actually," "very," or "just" +- Hyperlink 2-4 words when linking to sources +- Cut adverbs where possible +- Use active voice instead of passive voice +- Spell out numbers one through nine (except years at sentence start); use numerals for 10+ +- Use italics for emphasis (never bold or underline) +- Image credits: _Source: X/Name_ or _Source: Website name_ +- Don't capitalize job titles +- Capitalize after colons only if introducing independent clauses +- Use Oxford commas (x, y, and z) +- Use commas between independent clauses only +- No space after ellipsis... +- Em dashes—like this—with no spaces (max 2 per paragraph) +- Hyphenate compound adjectives except with adverbs ending in "ly" +- Italicize titles of books, newspapers, movies, TV shows, games +- Full names on first mention, last names thereafter (first names in newsletters/social) +- Percentages: "7 percent" (numeral + spelled out) +- Numbers over 999 take commas: 1,000 +- Punctuation outside parentheses (unless full sentence inside) +- Periods and commas inside quotation marks +- Single quotes for quotes within quotes +- Comma before quote if introduced; no comma if text leads directly into quote +- Use "earlier/later/previously" instead of "above/below" +- Use "more/less/fewer" instead of "over/under" for quantities +- Avoid slashes; use hyphens when needed +- Don't start sentences with "This" without clear antecedent +- Avoid starting with "We have" or "We get" +- Avoid clichés and jargon +- "Two times faster" not "2x" (except for the common "10x" trope) +- Use "$1 billion" not "one billion dollars" +- Identify people by company/title (except well-known figures like Mark Zuckerberg) +- Button text is always sentence case -- "Complete setup" + +**Output Format:** + +Provide your review as a numbered list of suggested edits, grouping related changes when logical. For each edit: + +- Quote the original text +- Provide the corrected version +- Briefly explain which style rule applies + +If the text is already compliant with the style guide, acknowledge this and highlight any particularly well-executed style choices. + +Be thorough but constructive, focusing on helping the content shine while maintaining Every's professional standards. diff --git a/.cursor/skills/feature-video/SKILL.md b/.cursor/skills/feature-video/SKILL.md new file mode 100644 index 0000000000..3d8fb7336d --- /dev/null +++ b/.cursor/skills/feature-video/SKILL.md @@ -0,0 +1,342 @@ +--- +name: feature-video +description: Record a video walkthrough of a feature and add it to the PR description +argument-hint: '[PR number or ''current''] [optional: base URL, default localhost:3000]' +--- + +# Feature Video Walkthrough + +Record a video walkthrough demonstrating a feature, upload it, and add it to the PR description. + +## Introduction + +Developer Relations Engineer creating feature demo videos + +This command creates professional video walkthroughs of features for PR documentation: +- Records browser interactions using agent-browser CLI +- Demonstrates the complete user flow +- Uploads the video for easy sharing +- Updates the PR description with an embedded video + +## Prerequisites + + +- Local development server running (e.g., `bin/dev`, `rails server`) +- agent-browser CLI installed +- Git repository with a PR to document +- `ffmpeg` installed (for video conversion) +- `rclone` configured (optional, for cloud upload - see rclone skill) + + +## Setup + +**Check installation:** +```bash +command -v agent-browser >/dev/null 2>&1 && echo "Installed" || echo "NOT INSTALLED" +``` + +**Install if needed:** +```bash +npm install -g agent-browser && agent-browser install +``` + +See the `agent-browser` skill for detailed usage. + +## Main Tasks + +### 1. Parse Arguments + + + +**Arguments:** $ARGUMENTS + +Parse the input: +- First argument: PR number or "current" (defaults to current branch's PR) +- Second argument: Base URL (defaults to `http://localhost:3000`) + +```bash +# Get PR number for current branch if needed +gh pr view --json number -q '.number' +``` + + + +### 2. Gather Feature Context + + + +**Get PR details:** +```bash +gh pr view [number] --json title,body,files,headRefName -q '.' +``` + +**Get changed files:** +```bash +gh pr view [number] --json files -q '.files[].path' +``` + +**Map files to testable routes** (same as playwright-test): + +| File Pattern | Route(s) | +|-------------|----------| +| `app/views/users/*` | `/users`, `/users/:id`, `/users/new` | +| `app/controllers/settings_controller.rb` | `/settings` | +| `app/javascript/controllers/*_controller.js` | Pages using that Stimulus controller | +| `app/components/*_component.rb` | Pages rendering that component | + + + +### 3. Plan the Video Flow + + + +Before recording, create a shot list: + +1. **Opening shot**: Homepage or starting point (2-3 seconds) +2. **Navigation**: How user gets to the feature +3. **Feature demonstration**: Core functionality (main focus) +4. **Edge cases**: Error states, validation, etc. (if applicable) +5. **Success state**: Completed action/result + +Ask user to confirm or adjust the flow: + +```markdown +**Proposed Video Flow** + +Based on PR #[number]: [title] + +1. Start at: /[starting-route] +2. Navigate to: /[feature-route] +3. Demonstrate: + - [Action 1] + - [Action 2] + - [Action 3] +4. Show result: [success state] + +Estimated duration: ~[X] seconds + +Does this look right? +1. Yes, start recording +2. Modify the flow (describe changes) +3. Add specific interactions to demonstrate +``` + + + +### 4. Setup Video Recording + + + +**Create videos directory:** +```bash +mkdir -p tmp/videos +``` + +**Recording approach: Use browser screenshots as frames** + +agent-browser captures screenshots at key moments, then combine into video using ffmpeg: + +```bash +ffmpeg -framerate 2 -pattern_type glob -i 'tmp/screenshots/*.png' -vf "scale=1280:-1" tmp/videos/feature-demo.gif +``` + + + +### 5. Record the Walkthrough + + + +Execute the planned flow, capturing each step: + +**Step 1: Navigate to starting point** +```bash +agent-browser open "[base-url]/[start-route]" +agent-browser wait 2000 +agent-browser screenshot tmp/screenshots/01-start.png +``` + +**Step 2: Perform navigation/interactions** +```bash +agent-browser snapshot -i # Get refs +agent-browser click @e1 # Click navigation element +agent-browser wait 1000 +agent-browser screenshot tmp/screenshots/02-navigate.png +``` + +**Step 3: Demonstrate feature** +```bash +agent-browser snapshot -i # Get refs for feature elements +agent-browser click @e2 # Click feature element +agent-browser wait 1000 +agent-browser screenshot tmp/screenshots/03-feature.png +``` + +**Step 4: Capture result** +```bash +agent-browser wait 2000 +agent-browser screenshot tmp/screenshots/04-result.png +``` + +**Create video/GIF from screenshots:** + +```bash +# Create directories +mkdir -p tmp/videos tmp/screenshots + +# Create MP4 video (RECOMMENDED - better quality, smaller size) +# -framerate 0.5 = 2 seconds per frame (slower playback) +# -framerate 1 = 1 second per frame +ffmpeg -y -framerate 0.5 -pattern_type glob -i 'tmp/screenshots/*.png' \ + -c:v libx264 -pix_fmt yuv420p -vf "scale=1280:-2" \ + tmp/videos/feature-demo.mp4 + +# Create low-quality GIF for preview (small file, for GitHub embed) +ffmpeg -y -framerate 0.5 -pattern_type glob -i 'tmp/screenshots/*.png' \ + -vf "scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse" \ + -loop 0 tmp/videos/feature-demo-preview.gif +``` + +**Note:** +- The `-2` in MP4 scale ensures height is divisible by 2 (required for H.264) +- Preview GIF uses 640px width and 128 colors to keep file size small (~100-200KB) + + + +### 6. Upload the Video + + + +**Upload with rclone:** + +```bash +# Check rclone is configured +rclone listremotes + +# Upload video, preview GIF, and screenshots to cloud storage +# Use --s3-no-check-bucket to avoid permission errors +rclone copy tmp/videos/ r2:kieran-claude/pr-videos/pr-[number]/ --s3-no-check-bucket --progress +rclone copy tmp/screenshots/ r2:kieran-claude/pr-videos/pr-[number]/screenshots/ --s3-no-check-bucket --progress + +# List uploaded files +rclone ls r2:kieran-claude/pr-videos/pr-[number]/ +``` + +Public URLs (R2 with public access): +``` +Video: https://pub-4047722ebb1b4b09853f24d3b61467f1.r2.dev/pr-videos/pr-[number]/feature-demo.mp4 +Preview: https://pub-4047722ebb1b4b09853f24d3b61467f1.r2.dev/pr-videos/pr-[number]/feature-demo-preview.gif +``` + + + +### 7. Update PR Description + + + +**Get current PR body:** +```bash +gh pr view [number] --json body -q '.body' +``` + +**Add video section to PR description:** + +If the PR already has a video section, replace it. Otherwise, append: + +**IMPORTANT:** GitHub cannot embed external MP4s directly. Use a clickable GIF that links to the video: + +```markdown +## Demo + +[![Feature Demo]([preview-gif-url])]([video-mp4-url]) + +*Click to view full video* +``` + +Example: +```markdown +[![Feature Demo](https://pub-4047722ebb1b4b09853f24d3b61467f1.r2.dev/pr-videos/pr-137/feature-demo-preview.gif)](https://pub-4047722ebb1b4b09853f24d3b61467f1.r2.dev/pr-videos/pr-137/feature-demo.mp4) +``` + +**Update the PR:** +```bash +gh pr edit [number] --body "[updated body with video section]" +``` + +**Or add as a comment if preferred:** +```bash +gh pr comment [number] --body "## Feature Demo + +![Demo]([video-url]) + +_Automated walkthrough of the changes in this PR_" +``` + + + +### 8. Cleanup + + + +```bash +# Optional: Clean up screenshots +rm -rf tmp/screenshots + +# Keep videos for reference +echo "Video retained at: tmp/videos/feature-demo.gif" +``` + + + +### 9. Summary + + + +Present completion summary: + +```markdown +## Feature Video Complete + +**PR:** #[number] - [title] +**Video:** [url or local path] +**Duration:** ~[X] seconds +**Format:** [GIF/MP4] + +### Shots Captured +1. [Starting point] - [description] +2. [Navigation] - [description] +3. [Feature demo] - [description] +4. [Result] - [description] + +### PR Updated +- [x] Video section added to PR description +- [ ] Ready for review + +**Next steps:** +- Review the video to ensure it accurately demonstrates the feature +- Share with reviewers for context +``` + + + +## Quick Usage Examples + +```bash +# Record video for current branch's PR +/feature-video + +# Record video for specific PR +/feature-video 847 + +# Record with custom base URL +/feature-video 847 http://localhost:5000 + +# Record for staging environment +/feature-video current https://staging.example.com +``` + +## Tips + +- **Keep it short**: 10-30 seconds is ideal for PR demos +- **Focus on the change**: Don't include unrelated UI +- **Show before/after**: If fixing a bug, show the broken state first (if possible) +- **Annotate if needed**: Add text overlays for complex features diff --git a/.cursor/skills/figma-design-sync/SKILL.md b/.cursor/skills/figma-design-sync/SKILL.md new file mode 100644 index 0000000000..ac3219e673 --- /dev/null +++ b/.cursor/skills/figma-design-sync/SKILL.md @@ -0,0 +1,190 @@ +--- +name: figma-design-sync +description: Detects and fixes visual differences between a web implementation and its Figma design. Use iteratively when syncing implementation to match Figma specs. +model: inherit +color: purple +--- + + + +Context: User has just implemented a new component and wants to ensure it matches the Figma design. +user: "I've just finished implementing the hero section component. Can you check if it matches the Figma design at https://figma.com/file/abc123/design?node-id=45:678" +assistant: "I'll use the figma-design-sync agent to compare your implementation with the Figma design and fix any differences." + + +Context: User is working on responsive design and wants to verify mobile breakpoint matches design. +user: "The mobile view doesn't look quite right. Here's the Figma: https://figma.com/file/xyz789/mobile?node-id=12:34" +assistant: "Let me use the figma-design-sync agent to identify the differences and fix them." + + +Context: After initial fixes, user wants to verify the implementation now matches. +user: "Can you check if the button component matches the design now?" +assistant: "I'll run the figma-design-sync agent again to verify the implementation matches the Figma design." + + + +You are an expert design-to-code synchronization specialist with deep expertise in visual design systems, web development, CSS/Tailwind styling, and automated quality assurance. Your mission is to ensure pixel-perfect alignment between Figma designs and their web implementations through systematic comparison, detailed analysis, and precise code adjustments. + +## Your Core Responsibilities + +1. **Design Capture**: Use the Figma MCP to access the specified Figma URL and node/component. Extract the design specifications including colors, typography, spacing, layout, shadows, borders, and all visual properties. Also take a screenshot and load it into the agent. + +2. **Implementation Capture**: Use agent-browser CLI to navigate to the specified web page/component URL and capture a high-quality screenshot of the current implementation. + + ```bash + agent-browser open [url] + agent-browser snapshot -i + agent-browser screenshot implementation.png + ``` + +3. **Systematic Comparison**: Perform a meticulous visual comparison between the Figma design and the screenshot, analyzing: + + - Layout and positioning (alignment, spacing, margins, padding) + - Typography (font family, size, weight, line height, letter spacing) + - Colors (backgrounds, text, borders, shadows) + - Visual hierarchy and component structure + - Responsive behavior and breakpoints + - Interactive states (hover, focus, active) if visible + - Shadows, borders, and decorative elements + - Icon sizes, positioning, and styling + - Max width, height etc. + +4. **Detailed Difference Documentation**: For each discrepancy found, document: + + - Specific element or component affected + - Current state in implementation + - Expected state from Figma design + - Severity of the difference (critical, moderate, minor) + - Recommended fix with exact values + +5. **Precise Implementation**: Make the necessary code changes to fix all identified differences: + + - Modify CSS/Tailwind classes following the responsive design patterns above + - Prefer Tailwind default values when close to Figma specs (within 2-4px) + - Ensure components are full width (`w-full`) without max-width constraints + - Move any width constraints and horizontal padding to wrapper divs in parent HTML/ERB + - Update component props or configuration + - Adjust layout structures if needed + - Ensure changes follow the project's coding standards from CLAUDE.md + - Use mobile-first responsive patterns (e.g., `flex-col lg:flex-row`) + - Preserve dark mode support + +6. **Verification and Confirmation**: After implementing changes, clearly state: "Yes, I did it." followed by a summary of what was fixed. Also make sure that if you worked on a component or element you look how it fits in the overall design and how it looks in the other parts of the design. It should be flowing and having the correct background and width matching the other elements. + +## Responsive Design Patterns and Best Practices + +### Component Width Philosophy +- **Components should ALWAYS be full width** (`w-full`) and NOT contain `max-width` constraints +- **Components should NOT have padding** at the outer section level (no `px-*` on the section element) +- **All width constraints and horizontal padding** should be handled by wrapper divs in the parent HTML/ERB file + +### Responsive Wrapper Pattern +When wrapping components in parent HTML/ERB files, use: +```erb +
+ <%= render SomeComponent.new(...) %> +
+``` + +This pattern provides: +- `w-full`: Full width on all screens +- `max-w-screen-xl`: Maximum width constraint (1280px, use Tailwind's default breakpoint values) +- `mx-auto`: Center the content +- `px-5 md:px-8 lg:px-[30px]`: Responsive horizontal padding + +### Prefer Tailwind Default Values +Use Tailwind's default spacing scale when the Figma design is close enough: +- **Instead of** `gap-[40px]`, **use** `gap-10` (40px) when appropriate +- **Instead of** `text-[45px]`, **use** `text-3xl` on mobile and `md:text-[45px]` on larger screens +- **Instead of** `text-[20px]`, **use** `text-lg` (18px) or `md:text-[20px]` +- **Instead of** `w-[56px] h-[56px]`, **use** `w-14 h-14` + +Only use arbitrary values like `[45px]` when: +- The exact pixel value is critical to match the design +- No Tailwind default is close enough (within 2-4px) + +Common Tailwind values to prefer: +- **Spacing**: `gap-2` (8px), `gap-4` (16px), `gap-6` (24px), `gap-8` (32px), `gap-10` (40px) +- **Text**: `text-sm` (14px), `text-base` (16px), `text-lg` (18px), `text-xl` (20px), `text-2xl` (24px), `text-3xl` (30px) +- **Width/Height**: `w-10` (40px), `w-14` (56px), `w-16` (64px) + +### Responsive Layout Pattern +- Use `flex-col lg:flex-row` to stack on mobile and go horizontal on large screens +- Use `gap-10 lg:gap-[100px]` for responsive gaps +- Use `w-full lg:w-auto lg:flex-1` to make sections responsive +- Don't use `flex-shrink-0` unless absolutely necessary +- Remove `overflow-hidden` from components - handle overflow at wrapper level if needed + +### Example of Good Component Structure +```erb + +
+ <%= render SomeComponent.new(...) %> +
+ + +
+
+ +
+
+``` + +### Common Anti-Patterns to Avoid +**❌ DON'T do this in components:** +```erb + +
+ +
+``` + +**✅ DO this instead:** +```erb + +
+ +
+``` + +**❌ DON'T use arbitrary values when Tailwind defaults are close:** +```erb + +
+``` + +**✅ DO prefer Tailwind defaults:** +```erb + +
+``` + +## Quality Standards + +- **Precision**: Use exact values from Figma (e.g., "16px" not "about 15-17px"), but prefer Tailwind defaults when close enough +- **Completeness**: Address all differences, no matter how minor +- **Code Quality**: Follow CLAUDE.md guidelines for Tailwind, responsive design, and dark mode +- **Communication**: Be specific about what changed and why +- **Iteration-Ready**: Design your fixes to allow the agent to run again for verification +- **Responsive First**: Always implement mobile-first responsive designs with appropriate breakpoints + +## Handling Edge Cases + +- **Missing Figma URL**: Request the Figma URL and node ID from the user +- **Missing Web URL**: Request the local or deployed URL to compare +- **MCP Access Issues**: Clearly report any connection problems with Figma or Playwright MCPs +- **Ambiguous Differences**: When a difference could be intentional, note it and ask for clarification +- **Breaking Changes**: If a fix would require significant refactoring, document the issue and propose the safest approach +- **Multiple Iterations**: After each run, suggest whether another iteration is needed based on remaining differences + +## Success Criteria + +You succeed when: + +1. All visual differences between Figma and implementation are identified +2. All differences are fixed with precise, maintainable code +3. The implementation follows project coding standards +4. You clearly confirm completion with "Yes, I did it." +5. The agent can be run again iteratively until perfect alignment is achieved + +Remember: You are the bridge between design and implementation. Your attention to detail and systematic approach ensures that what users see matches what designers intended, pixel by pixel. diff --git a/.cursor/skills/file-todos/SKILL.md b/.cursor/skills/file-todos/SKILL.md new file mode 100644 index 0000000000..c67dcf989c --- /dev/null +++ b/.cursor/skills/file-todos/SKILL.md @@ -0,0 +1,252 @@ +--- +name: file-todos +description: This skill should be used when managing the file-based todo tracking system in the todos/ directory. It provides workflows for creating todos, managing status and dependencies, conducting triage, and integrating with slash commands and code review processes. +disable-model-invocation: true +--- + +# File-Based Todo Tracking Skill + +## Overview + +The `todos/` directory contains a file-based tracking system for managing code review feedback, technical debt, feature requests, and work items. Each todo is a markdown file with YAML frontmatter and structured sections. + +This skill should be used when: +- Creating new todos from findings or feedback +- Managing todo lifecycle (pending → ready → complete) +- Triaging pending items for approval +- Checking or managing dependencies +- Converting PR comments or code findings into tracked work +- Updating work logs during todo execution + +## File Naming Convention + +Todo files follow this naming pattern: + +``` +{issue_id}-{status}-{priority}-{description}.md +``` + +**Components:** +- **issue_id**: Sequential number (001, 002, 003...) - never reused +- **status**: `pending` (needs triage), `ready` (approved), `complete` (done) +- **priority**: `p1` (critical), `p2` (important), `p3` (nice-to-have) +- **description**: kebab-case, brief description + +**Examples:** +``` +001-pending-p1-mailer-test.md +002-ready-p1-fix-n-plus-1.md +005-complete-p2-refactor-csv.md +``` + +## File Structure + +Each todo is a markdown file with YAML frontmatter and structured sections. Use the template at [todo-template.md](./assets/todo-template.md) as a starting point when creating new todos. + +**Required sections:** +- **Problem Statement** - What is broken, missing, or needs improvement? +- **Findings** - Investigation results, root cause, key discoveries +- **Proposed Solutions** - Multiple options with pros/cons, effort, risk +- **Recommended Action** - Clear plan (filled during triage) +- **Acceptance Criteria** - Testable checklist items +- **Work Log** - Chronological record with date, actions, learnings + +**Optional sections:** +- **Technical Details** - Affected files, related components, DB changes +- **Resources** - Links to errors, tests, PRs, documentation +- **Notes** - Additional context or decisions + +**YAML frontmatter fields:** +```yaml +--- +status: ready # pending | ready | complete +priority: p1 # p1 | p2 | p3 +issue_id: "002" +tags: [rails, performance, database] +dependencies: ["001"] # Issue IDs this is blocked by +--- +``` + +## Common Workflows + +### Creating a New Todo + +**To create a new todo from findings or feedback:** + +1. Determine next issue ID: `ls todos/ | grep -o '^[0-9]\+' | sort -n | tail -1` +2. Copy template: `cp assets/todo-template.md todos/{NEXT_ID}-pending-{priority}-{description}.md` +3. Edit and fill required sections: + - Problem Statement + - Findings (if from investigation) + - Proposed Solutions (multiple options) + - Acceptance Criteria + - Add initial Work Log entry +4. Determine status: `pending` (needs triage) or `ready` (pre-approved) +5. Add relevant tags for filtering + +**When to create a todo:** +- Requires more than 15-20 minutes of work +- Needs research, planning, or multiple approaches considered +- Has dependencies on other work +- Requires manager approval or prioritization +- Part of larger feature or refactor +- Technical debt needing documentation + +**When to act immediately instead:** +- Issue is trivial (< 15 minutes) +- Complete context available now +- No planning needed +- User explicitly requests immediate action +- Simple bug fix with obvious solution + +### Triaging Pending Items + +**To triage pending todos:** + +1. List pending items: `ls todos/*-pending-*.md` +2. For each todo: + - Read Problem Statement and Findings + - Review Proposed Solutions + - Make decision: approve, defer, or modify priority +3. Update approved todos: + - Rename file: `mv {file}-pending-{pri}-{desc}.md {file}-ready-{pri}-{desc}.md` + - Update frontmatter: `status: pending` → `status: ready` + - Fill "Recommended Action" section with clear plan + - Adjust priority if different from initial assessment +4. Deferred todos stay in `pending` status + +**Use slash command:** `/triage` for interactive approval workflow + +### Managing Dependencies + +**To track dependencies:** + +```yaml +dependencies: ["002", "005"] # This todo blocked by issues 002 and 005 +dependencies: [] # No blockers - can work immediately +``` + +**To check what blocks a todo:** +```bash +grep "^dependencies:" todos/003-*.md +``` + +**To find what a todo blocks:** +```bash +grep -l 'dependencies:.*"002"' todos/*.md +``` + +**To verify blockers are complete before starting:** +```bash +for dep in 001 002 003; do + [ -f "todos/${dep}-complete-*.md" ] || echo "Issue $dep not complete" +done +``` + +### Updating Work Logs + +**When working on a todo, always add a work log entry:** + +```markdown +### YYYY-MM-DD - Session Title + +**By:** Claude Code / Developer Name + +**Actions:** +- Specific changes made (include file:line references) +- Commands executed +- Tests run +- Results of investigation + +**Learnings:** +- What worked / what didn't +- Patterns discovered +- Key insights for future work +``` + +Work logs serve as: +- Historical record of investigation +- Documentation of approaches attempted +- Knowledge sharing for team +- Context for future similar work + +### Completing a Todo + +**To mark a todo as complete:** + +1. Verify all acceptance criteria checked off +2. Update Work Log with final session and results +3. Rename file: `mv {file}-ready-{pri}-{desc}.md {file}-complete-{pri}-{desc}.md` +4. Update frontmatter: `status: ready` → `status: complete` +5. Check for unblocked work: `grep -l 'dependencies:.*"002"' todos/*-ready-*.md` +6. Commit with issue reference: `feat: resolve issue 002` + +## Integration with Development Workflows + +| Trigger | Flow | Tool | +|---------|------|------| +| Code review | `/workflows:review` → Findings → `/triage` → Todos | Review agent + skill | +| PR comments | `/resolve_pr_parallel` → Individual fixes → Todos | gh CLI + skill | +| Code TODOs | `/resolve_todo_parallel` → Fixes + Complex todos | Agent + skill | +| Planning | Brainstorm → Create todo → Work → Complete | Skill | +| Feedback | Discussion → Create todo → Triage → Work | Skill + slash | + +## Quick Reference Commands + +**Finding work:** +```bash +# List highest priority unblocked work +grep -l 'dependencies: \[\]' todos/*-ready-p1-*.md + +# List all pending items needing triage +ls todos/*-pending-*.md + +# Find next issue ID +ls todos/ | grep -o '^[0-9]\+' | sort -n | tail -1 | awk '{printf "%03d", $1+1}' + +# Count by status +for status in pending ready complete; do + echo "$status: $(ls -1 todos/*-$status-*.md 2>/dev/null | wc -l)" +done +``` + +**Dependency management:** +```bash +# What blocks this todo? +grep "^dependencies:" todos/003-*.md + +# What does this todo block? +grep -l 'dependencies:.*"002"' todos/*.md +``` + +**Searching:** +```bash +# Search by tag +grep -l "tags:.*rails" todos/*.md + +# Search by priority +ls todos/*-p1-*.md + +# Full-text search +grep -r "payment" todos/ +``` + +## Key Distinctions + +**File-todos system (this skill):** +- Markdown files in `todos/` directory +- Development/project tracking +- Standalone markdown files with YAML frontmatter +- Used by humans and agents + +**Rails Todo model:** +- Database model in `app/models/todo.rb` +- User-facing feature in the application +- Active Record CRUD operations +- Different from this file-based system + +**TodoWrite tool:** +- In-memory task tracking during agent sessions +- Temporary tracking for single conversation +- Not persisted to disk +- Different from both systems above diff --git a/.cursor/skills/file-todos/assets/todo-template.md b/.cursor/skills/file-todos/assets/todo-template.md new file mode 100644 index 0000000000..d241f2d633 --- /dev/null +++ b/.cursor/skills/file-todos/assets/todo-template.md @@ -0,0 +1,155 @@ +--- +status: pending +priority: p2 +issue_id: "XXX" +tags: [] +dependencies: [] +--- + +# Brief Task Title + +Replace with a concise title describing what needs to be done. + +## Problem Statement + +What is broken, missing, or needs improvement? Provide clear context about why this matters. + +**Example:** +- Template system lacks comprehensive test coverage for edge cases discovered during PR review +- Email service is missing proper error handling for rate-limit scenarios +- Documentation doesn't cover the new authentication flow + +## Findings + +Investigation results, root cause analysis, and key discoveries. + +- Finding 1 (with specifics: file, line number if applicable) +- Finding 2 +- Key discovery with impact assessment +- Related issues or patterns discovered + +**Example format:** +- Identified 12 missing test scenarios in `app/models/user_test.rb` +- Current coverage: 60% of code paths +- Missing: empty inputs, special characters, large payloads +- Similar issues exist in `app/models/post_test.rb` (~8 scenarios) + +## Proposed Solutions + +Present multiple options with pros, cons, effort estimates, and risk assessment. + +### Option 1: [Solution Name] + +**Approach:** Describe the solution clearly. + +**Pros:** +- Benefit 1 +- Benefit 2 + +**Cons:** +- Drawback 1 +- Drawback 2 + +**Effort:** 2-3 hours + +**Risk:** Low / Medium / High + +--- + +### Option 2: [Solution Name] + +**Approach:** Describe the solution clearly. + +**Pros:** +- Benefit 1 +- Benefit 2 + +**Cons:** +- Drawback 1 +- Drawback 2 + +**Effort:** 4-6 hours + +**Risk:** Low / Medium / High + +--- + +### Option 3: [Solution Name] + +(Include if you have alternatives) + +## Recommended Action + +**To be filled during triage.** Clear, actionable plan for resolving this todo. + +**Example:** +"Implement both unit tests (covering each scenario) and integration tests (full pipeline) before merging. Estimated 4 hours total effort. Target coverage > 85% for this module." + +## Technical Details + +Affected files, related components, database changes, or architectural considerations. + +**Affected files:** +- `app/models/user.rb:45` - full_name method +- `app/services/user_service.rb:12` - validation logic +- `test/models/user_test.rb` - existing tests + +**Related components:** +- UserMailer (depends on user validation) +- AccountPolicy (authorization checks) + +**Database changes (if any):** +- Migration needed? Yes / No +- New columns/tables? Describe here + +## Resources + +Links to errors, tests, PRs, documentation, similar issues. + +- **PR:** #1287 +- **Related issue:** #456 +- **Error log:** [link to AppSignal incident] +- **Documentation:** [relevant docs] +- **Similar patterns:** Issue #200 (completed, ref for approach) + +## Acceptance Criteria + +Testable checklist items for verifying completion. + +- [ ] All acceptance criteria checked +- [ ] Tests pass (unit + integration if applicable) +- [ ] Code reviewed and approved +- [ ] (Example) Test coverage > 85% +- [ ] (Example) Performance metrics acceptable +- [ ] (Example) Documentation updated + +## Work Log + +Chronological record of work sessions, actions taken, and learnings. + +### 2025-11-12 - Initial Discovery + +**By:** Claude Code + +**Actions:** +- Identified 12 missing test scenarios +- Analyzed existing test coverage (file:line references) +- Reviewed similar patterns in codebase +- Drafted 3 solution approaches + +**Learnings:** +- Similar issues exist in related modules +- Current test setup supports both unit and integration tests +- Performance testing would be valuable addition + +--- + +(Add more entries as work progresses) + +## Notes + +Additional context, decisions, or reminders. + +- Decision: Include both unit and integration tests for comprehensive coverage +- Blocker: Depends on completion of issue #001 +- Timeline: Priority for sprint due to blocking other work diff --git a/.cursor/skills/framework-docs-researcher/SKILL.md b/.cursor/skills/framework-docs-researcher/SKILL.md new file mode 100644 index 0000000000..75e7d13ee0 --- /dev/null +++ b/.cursor/skills/framework-docs-researcher/SKILL.md @@ -0,0 +1,106 @@ +--- +name: framework-docs-researcher +description: Gathers comprehensive documentation and best practices for frameworks, libraries, or dependencies. Use when you need official docs, version-specific constraints, or implementation patterns. +model: inherit +--- + + + +Context: The user needs to understand how to properly implement a new feature using a specific library. +user: "I need to implement file uploads using Active Storage" +assistant: "I'll use the framework-docs-researcher agent to gather comprehensive documentation about Active Storage" +Since the user needs to understand a framework/library feature, use the framework-docs-researcher agent to collect all relevant documentation and best practices. + + +Context: The user is troubleshooting an issue with a gem. +user: "Why is the turbo-rails gem not working as expected?" +assistant: "Let me use the framework-docs-researcher agent to investigate the turbo-rails documentation and source code" +The user needs to understand library behavior, so the framework-docs-researcher agent should be used to gather documentation and explore the gem's source. + + + +**Note: The current year is 2026.** Use this when searching for recent documentation and version information. + +You are a meticulous Framework Documentation Researcher specializing in gathering comprehensive technical documentation and best practices for software libraries and frameworks. Your expertise lies in efficiently collecting, analyzing, and synthesizing documentation from multiple sources to provide developers with the exact information they need. + +**Your Core Responsibilities:** + +1. **Documentation Gathering**: + - Use Context7 to fetch official framework and library documentation + - Identify and retrieve version-specific documentation matching the project's dependencies + - Extract relevant API references, guides, and examples + - Focus on sections most relevant to the current implementation needs + +2. **Best Practices Identification**: + - Analyze documentation for recommended patterns and anti-patterns + - Identify version-specific constraints, deprecations, and migration guides + - Extract performance considerations and optimization techniques + - Note security best practices and common pitfalls + +3. **GitHub Research**: + - Search GitHub for real-world usage examples of the framework/library + - Look for issues, discussions, and pull requests related to specific features + - Identify community solutions to common problems + - Find popular projects using the same dependencies for reference + +4. **Source Code Analysis**: + - Use `bundle show ` to locate installed gems + - Explore gem source code to understand internal implementations + - Read through README files, changelogs, and inline documentation + - Identify configuration options and extension points + +**Your Workflow Process:** + +1. **Initial Assessment**: + - Identify the specific framework, library, or gem being researched + - Determine the installed version from Gemfile.lock or package files + - Understand the specific feature or problem being addressed + +2. **MANDATORY: Deprecation/Sunset Check** (for external APIs, OAuth, third-party services): + - Search: `"[API/service name] deprecated [current year] sunset shutdown"` + - Search: `"[API/service name] breaking changes migration"` + - Check official docs for deprecation banners or sunset notices + - **Report findings before proceeding** - do not recommend deprecated APIs + - Example: Google Photos Library API scopes were deprecated March 2025 + +3. **Documentation Collection**: + - Start with Context7 to fetch official documentation + - If Context7 is unavailable or incomplete, use web search as fallback + - Prioritize official sources over third-party tutorials + - Collect multiple perspectives when official docs are unclear + +4. **Source Exploration**: + - Use `bundle show` to find gem locations + - Read through key source files related to the feature + - Look for tests that demonstrate usage patterns + - Check for configuration examples in the codebase + +5. **Synthesis and Reporting**: + - Organize findings by relevance to the current task + - Highlight version-specific considerations + - Provide code examples adapted to the project's style + - Include links to sources for further reading + +**Quality Standards:** + +- **ALWAYS check for API deprecation first** when researching external APIs or services +- Always verify version compatibility with the project's dependencies +- Prioritize official documentation but supplement with community resources +- Provide practical, actionable insights rather than generic information +- Include code examples that follow the project's conventions +- Flag any potential breaking changes or deprecations +- Note when documentation is outdated or conflicting + +**Output Format:** + +Structure your findings as: + +1. **Summary**: Brief overview of the framework/library and its purpose +2. **Version Information**: Current version and any relevant constraints +3. **Key Concepts**: Essential concepts needed to understand the feature +4. **Implementation Guide**: Step-by-step approach with code examples +5. **Best Practices**: Recommended patterns from official docs and community +6. **Common Issues**: Known problems and their solutions +7. **References**: Links to documentation, GitHub issues, and source files + +Remember: You are the bridge between complex documentation and practical implementation. Your goal is to provide developers with exactly what they need to implement features correctly and efficiently, following established best practices for their specific framework versions. diff --git a/.cursor/skills/frontend-design-frontend-design/SKILL.md b/.cursor/skills/frontend-design-frontend-design/SKILL.md new file mode 100644 index 0000000000..f89cbe593b --- /dev/null +++ b/.cursor/skills/frontend-design-frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design-frontend-design +description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics. +license: Complete terms in LICENSE.txt +--- + +This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. + +The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. + +## Design Thinking + +Before coding, understand the context and commit to a BOLD aesthetic direction: +- **Purpose**: What problem does this interface solve? Who uses it? +- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. +- **Constraints**: Technical requirements (framework, performance, accessibility). +- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? + +**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. + +Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: +- Production-grade and functional +- Visually striking and memorable +- Cohesive with a clear aesthetic point-of-view +- Meticulously refined in every detail + +## Frontend Aesthetics Guidelines + +Focus on: +- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. +- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. +- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. +- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. +- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. + +NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. + +Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. + +**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. + +Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.cursor/skills/frontend-design/SKILL.md b/.cursor/skills/frontend-design/SKILL.md new file mode 100644 index 0000000000..a8344c4e7d --- /dev/null +++ b/.cursor/skills/frontend-design/SKILL.md @@ -0,0 +1,42 @@ +--- +name: frontend-design +description: This skill should be used when creating distinctive, production-grade frontend interfaces with high design quality. It applies when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics. +license: Complete terms in LICENSE.txt +--- + +This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. + +The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. + +## Design Thinking + +Before coding, understand the context and commit to a BOLD aesthetic direction: +- **Purpose**: What problem does this interface solve? Who uses it? +- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. +- **Constraints**: Technical requirements (framework, performance, accessibility). +- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? + +**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. + +Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: +- Production-grade and functional +- Visually striking and memorable +- Cohesive with a clear aesthetic point-of-view +- Meticulously refined in every detail + +## Frontend Aesthetics Guidelines + +Focus on: +- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. +- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. +- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. +- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. +- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. + +NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. + +Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. + +**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. + +Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. diff --git a/.cursor/skills/gemini-imagegen/SKILL.md b/.cursor/skills/gemini-imagegen/SKILL.md new file mode 100644 index 0000000000..e9e54b825c --- /dev/null +++ b/.cursor/skills/gemini-imagegen/SKILL.md @@ -0,0 +1,237 @@ +--- +name: gemini-imagegen +description: This skill should be used when generating and editing images using the Gemini API (Nano Banana Pro). It applies when creating images from text prompts, editing existing images, applying style transfers, generating logos with text, creating stickers, product mockups, or any image generation/manipulation task. Supports text-to-image, image editing, multi-turn refinement, and composition from multiple reference images. +--- + +# Gemini Image Generation (Nano Banana Pro) + +Generate and edit images using Google's Gemini API. The environment variable `GEMINI_API_KEY` must be set. + +## Default Model + +| Model | Resolution | Best For | +|-------|------------|----------| +| `gemini-3-pro-image-preview` | 1K-4K | All image generation (default) | + +**Note:** Always use this Pro model. Only use a different model if explicitly requested. + +## Quick Reference + +### Default Settings +- **Model:** `gemini-3-pro-image-preview` +- **Resolution:** 1K (default, options: 1K, 2K, 4K) +- **Aspect Ratio:** 1:1 (default) + +### Available Aspect Ratios +`1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` + +### Available Resolutions +`1K` (default), `2K`, `4K` + +## Core API Pattern + +```python +import os +from google import genai +from google.genai import types + +client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) + +# Basic generation (1K, 1:1 - defaults) +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=["Your prompt here"], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + ), +) + +for part in response.parts: + if part.text: + print(part.text) + elif part.inline_data: + image = part.as_image() + image.save("output.png") +``` + +## Custom Resolution & Aspect Ratio + +```python +from google.genai import types + +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=[prompt], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + image_config=types.ImageConfig( + aspect_ratio="16:9", # Wide format + image_size="2K" # Higher resolution + ), + ) +) +``` + +### Resolution Examples + +```python +# 1K (default) - Fast, good for previews +image_config=types.ImageConfig(image_size="1K") + +# 2K - Balanced quality/speed +image_config=types.ImageConfig(image_size="2K") + +# 4K - Maximum quality, slower +image_config=types.ImageConfig(image_size="4K") +``` + +### Aspect Ratio Examples + +```python +# Square (default) +image_config=types.ImageConfig(aspect_ratio="1:1") + +# Landscape wide +image_config=types.ImageConfig(aspect_ratio="16:9") + +# Ultra-wide panoramic +image_config=types.ImageConfig(aspect_ratio="21:9") + +# Portrait +image_config=types.ImageConfig(aspect_ratio="9:16") + +# Photo standard +image_config=types.ImageConfig(aspect_ratio="4:3") +``` + +## Editing Images + +Pass existing images with text prompts: + +```python +from PIL import Image + +img = Image.open("input.png") +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=["Add a sunset to this scene", img], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + ), +) +``` + +## Multi-Turn Refinement + +Use chat for iterative editing: + +```python +from google.genai import types + +chat = client.chats.create( + model="gemini-3-pro-image-preview", + config=types.GenerateContentConfig(response_modalities=['TEXT', 'IMAGE']) +) + +response = chat.send_message("Create a logo for 'Acme Corp'") +# Save first image... + +response = chat.send_message("Make the text bolder and add a blue gradient") +# Save refined image... +``` + +## Prompting Best Practices + +### Photorealistic Scenes +Include camera details: lens type, lighting, angle, mood. +> "A photorealistic close-up portrait, 85mm lens, soft golden hour light, shallow depth of field" + +### Stylized Art +Specify style explicitly: +> "A kawaii-style sticker of a happy red panda, bold outlines, cel-shading, white background" + +### Text in Images +Be explicit about font style and placement: +> "Create a logo with text 'Daily Grind' in clean sans-serif, black and white, coffee bean motif" + +### Product Mockups +Describe lighting setup and surface: +> "Studio-lit product photo on polished concrete, three-point softbox setup, 45-degree angle" + +## Advanced Features + +### Google Search Grounding +Generate images based on real-time data: + +```python +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=["Visualize today's weather in Tokyo as an infographic"], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + tools=[{"google_search": {}}] + ) +) +``` + +### Multiple Reference Images (Up to 14) +Combine elements from multiple sources: + +```python +response = client.models.generate_content( + model="gemini-3-pro-image-preview", + contents=[ + "Create a group photo of these people in an office", + Image.open("person1.png"), + Image.open("person2.png"), + Image.open("person3.png"), + ], + config=types.GenerateContentConfig( + response_modalities=['TEXT', 'IMAGE'], + ), +) +``` + +## Important: File Format & Media Type + +**CRITICAL:** The Gemini API returns images in JPEG format by default. When saving, always use `.jpg` extension to avoid media type mismatches. + +```python +# CORRECT - Use .jpg extension (Gemini returns JPEG) +image.save("output.jpg") + +# WRONG - Will cause "Image does not match media type" errors +image.save("output.png") # Creates JPEG with PNG extension! +``` + +### Converting to PNG (if needed) + +If you specifically need PNG format: + +```python +from PIL import Image + +# Generate with Gemini +for part in response.parts: + if part.inline_data: + img = part.as_image() + # Convert to PNG by saving with explicit format + img.save("output.png", format="PNG") +``` + +### Verifying Image Format + +Check actual format vs extension with the `file` command: + +```bash +file image.png +# If output shows "JPEG image data" - rename to .jpg! +``` + +## Notes + +- All generated images include SynthID watermarks +- Gemini returns **JPEG format by default** - always use `.jpg` extension +- Image-only mode (`responseModalities: ["IMAGE"]`) won't work with Google Search grounding +- For editing, describe changes conversationally—the model understands semantic masking +- Default to 1K resolution for speed; use 2K/4K when quality is critical diff --git a/.cursor/skills/gemini-imagegen/requirements.txt b/.cursor/skills/gemini-imagegen/requirements.txt new file mode 100644 index 0000000000..9b5d2330a7 --- /dev/null +++ b/.cursor/skills/gemini-imagegen/requirements.txt @@ -0,0 +1,2 @@ +google-genai>=1.0.0 +Pillow>=10.0.0 diff --git a/.cursor/skills/gemini-imagegen/scripts/compose_images.py b/.cursor/skills/gemini-imagegen/scripts/compose_images.py new file mode 100755 index 0000000000..5c32c719d7 --- /dev/null +++ b/.cursor/skills/gemini-imagegen/scripts/compose_images.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +""" +Compose multiple images into a new image using Gemini API. + +Usage: + python compose_images.py "instruction" output.png image1.png [image2.png ...] + +Examples: + python compose_images.py "Create a group photo of these people" group.png person1.png person2.png + python compose_images.py "Put the cat from the first image on the couch from the second" result.png cat.png couch.png + python compose_images.py "Apply the art style from the first image to the scene in the second" styled.png style.png photo.png + +Note: Supports up to 14 reference images (Gemini 3 Pro only). + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from PIL import Image +from google import genai +from google.genai import types + + +def compose_images( + instruction: str, + output_path: str, + image_paths: list[str], + model: str = "gemini-3-pro-image-preview", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Compose multiple images based on instructions. + + Args: + instruction: Text description of how to combine images + output_path: Path to save the result + image_paths: List of input image paths (up to 14) + model: Gemini model to use (pro recommended) + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + if len(image_paths) > 14: + raise ValueError("Maximum 14 reference images supported") + + if len(image_paths) < 1: + raise ValueError("At least one image is required") + + # Verify all images exist + for path in image_paths: + if not os.path.exists(path): + raise FileNotFoundError(f"Image not found: {path}") + + client = genai.Client(api_key=api_key) + + # Load images + images = [Image.open(path) for path in image_paths] + + # Build contents: instruction first, then images + contents = [instruction] + images + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=contents, + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Compose multiple images using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("instruction", help="Composition instruction") + parser.add_argument("output", help="Output file path") + parser.add_argument("images", nargs="+", help="Input images (up to 14)") + parser.add_argument( + "--model", "-m", + default="gemini-3-pro-image-preview", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (pro recommended for composition)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Output aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Output resolution" + ) + + args = parser.parse_args() + + try: + text = compose_images( + instruction=args.instruction, + output_path=args.output, + image_paths=args.images, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Composed image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/gemini-imagegen/scripts/edit_image.py b/.cursor/skills/gemini-imagegen/scripts/edit_image.py new file mode 100755 index 0000000000..e415c527eb --- /dev/null +++ b/.cursor/skills/gemini-imagegen/scripts/edit_image.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +""" +Edit existing images using Gemini API. + +Usage: + python edit_image.py input.png "edit instruction" output.png [options] + +Examples: + python edit_image.py photo.png "Add a rainbow in the sky" edited.png + python edit_image.py room.jpg "Change the sofa to red leather" room_edited.jpg + python edit_image.py portrait.png "Make it look like a Van Gogh painting" artistic.png --model gemini-3-pro-image-preview + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from PIL import Image +from google import genai +from google.genai import types + + +def edit_image( + input_path: str, + instruction: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Edit an existing image based on text instructions. + + Args: + input_path: Path to the input image + instruction: Text description of edits to make + output_path: Path to save the edited image + model: Gemini model to use + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + if not os.path.exists(input_path): + raise FileNotFoundError(f"Input image not found: {input_path}") + + client = genai.Client(api_key=api_key) + + # Load input image + input_image = Image.open(input_path) + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[instruction, input_image], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated. Check your instruction and try again.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Edit images using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("input", help="Input image path") + parser.add_argument("instruction", help="Edit instruction") + parser.add_argument("output", help="Output file path") + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (default: gemini-2.5-flash-image)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Output aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Output resolution" + ) + + args = parser.parse_args() + + try: + text = edit_image( + input_path=args.input, + instruction=args.instruction, + output_path=args.output, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Edited image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/gemini-imagegen/scripts/gemini_images.py b/.cursor/skills/gemini-imagegen/scripts/gemini_images.py new file mode 100755 index 0000000000..0ff4cb53de --- /dev/null +++ b/.cursor/skills/gemini-imagegen/scripts/gemini_images.py @@ -0,0 +1,263 @@ +""" +Gemini Image Generation Library + +A simple Python library for generating and editing images with the Gemini API. + +Usage: + from gemini_images import GeminiImageGenerator + + gen = GeminiImageGenerator() + gen.generate("A sunset over mountains", "sunset.png") + gen.edit("input.png", "Add clouds", "output.png") + +Environment: + GEMINI_API_KEY - Required API key +""" + +import os +from pathlib import Path +from typing import Literal + +from PIL import Image +from google import genai +from google.genai import types + + +AspectRatio = Literal["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] +ImageSize = Literal["1K", "2K", "4K"] +Model = Literal["gemini-2.5-flash-image", "gemini-3-pro-image-preview"] + + +class GeminiImageGenerator: + """High-level interface for Gemini image generation.""" + + FLASH = "gemini-2.5-flash-image" + PRO = "gemini-3-pro-image-preview" + + def __init__(self, api_key: str | None = None, model: Model = FLASH): + """Initialize the generator. + + Args: + api_key: Gemini API key (defaults to GEMINI_API_KEY env var) + model: Default model to use + """ + self.api_key = api_key or os.environ.get("GEMINI_API_KEY") + if not self.api_key: + raise EnvironmentError("GEMINI_API_KEY not set") + + self.client = genai.Client(api_key=self.api_key) + self.model = model + + def _build_config( + self, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + google_search: bool = False, + ) -> types.GenerateContentConfig: + """Build generation config.""" + kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + img_config = {} + if aspect_ratio: + img_config["aspect_ratio"] = aspect_ratio + if image_size: + img_config["image_size"] = image_size + + if img_config: + kwargs["image_config"] = types.ImageConfig(**img_config) + + if google_search: + kwargs["tools"] = [{"google_search": {}}] + + return types.GenerateContentConfig(**kwargs) + + def generate( + self, + prompt: str, + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + google_search: bool = False, + ) -> tuple[Path, str | None]: + """Generate an image from a text prompt. + + Args: + prompt: Text description + output: Output file path + model: Override default model + aspect_ratio: Output aspect ratio + image_size: Output resolution + google_search: Enable Google Search grounding (Pro only) + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + config = self._build_config(aspect_ratio, image_size, google_search) + + response = self.client.models.generate_content( + model=model or self.model, + contents=[prompt], + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def edit( + self, + input_image: str | Path | Image.Image, + instruction: str, + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + ) -> tuple[Path, str | None]: + """Edit an existing image. + + Args: + input_image: Input image (path or PIL Image) + instruction: Edit instruction + output: Output file path + model: Override default model + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + + if isinstance(input_image, (str, Path)): + input_image = Image.open(input_image) + + config = self._build_config(aspect_ratio, image_size) + + response = self.client.models.generate_content( + model=model or self.model, + contents=[instruction, input_image], + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def compose( + self, + instruction: str, + images: list[str | Path | Image.Image], + output: str | Path, + *, + model: Model | None = None, + aspect_ratio: AspectRatio | None = None, + image_size: ImageSize | None = None, + ) -> tuple[Path, str | None]: + """Compose multiple images into one. + + Args: + instruction: Composition instruction + images: List of input images (up to 14) + output: Output file path + model: Override default model (Pro recommended) + aspect_ratio: Output aspect ratio + image_size: Output resolution + + Returns: + Tuple of (output path, optional text response) + """ + output = Path(output) + + # Load images + loaded = [] + for img in images: + if isinstance(img, (str, Path)): + loaded.append(Image.open(img)) + else: + loaded.append(img) + + config = self._build_config(aspect_ratio, image_size) + contents = [instruction] + loaded + + response = self.client.models.generate_content( + model=model or self.PRO, # Pro recommended for composition + contents=contents, + config=config, + ) + + text = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + part.as_image().save(output) + + return output, text + + def chat(self) -> "ImageChat": + """Start an interactive chat session for iterative refinement.""" + return ImageChat(self.client, self.model) + + +class ImageChat: + """Multi-turn chat session for iterative image generation.""" + + def __init__(self, client: genai.Client, model: Model): + self.client = client + self.model = model + self._chat = client.chats.create( + model=model, + config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]), + ) + self.current_image: Image.Image | None = None + + def send( + self, + message: str, + image: Image.Image | str | Path | None = None, + ) -> tuple[Image.Image | None, str | None]: + """Send a message and optionally an image. + + Returns: + Tuple of (generated image or None, text response or None) + """ + contents = [message] + if image: + if isinstance(image, (str, Path)): + image = Image.open(image) + contents.append(image) + + response = self._chat.send_message(contents) + + text = None + img = None + for part in response.parts: + if part.text: + text = part.text + elif part.inline_data: + img = part.as_image() + self.current_image = img + + return img, text + + def reset(self): + """Reset the chat session.""" + self._chat = self.client.chats.create( + model=self.model, + config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]), + ) + self.current_image = None diff --git a/.cursor/skills/gemini-imagegen/scripts/generate_image.py b/.cursor/skills/gemini-imagegen/scripts/generate_image.py new file mode 100755 index 0000000000..e3aabcfc0f --- /dev/null +++ b/.cursor/skills/gemini-imagegen/scripts/generate_image.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Generate images from text prompts using Gemini API. + +Usage: + python generate_image.py "prompt" output.png [--model MODEL] [--aspect RATIO] [--size SIZE] + +Examples: + python generate_image.py "A cat in space" cat.png + python generate_image.py "A logo for Acme Corp" logo.png --model gemini-3-pro-image-preview --aspect 1:1 + python generate_image.py "Epic landscape" landscape.png --aspect 16:9 --size 2K + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys + +from google import genai +from google.genai import types + + +def generate_image( + prompt: str, + output_path: str, + model: str = "gemini-2.5-flash-image", + aspect_ratio: str | None = None, + image_size: str | None = None, +) -> str | None: + """Generate an image from a text prompt. + + Args: + prompt: Text description of the image to generate + output_path: Path to save the generated image + model: Gemini model to use + aspect_ratio: Aspect ratio (1:1, 16:9, 9:16, etc.) + image_size: Resolution (1K, 2K, 4K - 4K only for pro model) + + Returns: + Any text response from the model, or None + """ + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + client = genai.Client(api_key=api_key) + + # Build config + config_kwargs = {"response_modalities": ["TEXT", "IMAGE"]} + + image_config_kwargs = {} + if aspect_ratio: + image_config_kwargs["aspect_ratio"] = aspect_ratio + if image_size: + image_config_kwargs["image_size"] = image_size + + if image_config_kwargs: + config_kwargs["image_config"] = types.ImageConfig(**image_config_kwargs) + + config = types.GenerateContentConfig(**config_kwargs) + + response = client.models.generate_content( + model=model, + contents=[prompt], + config=config, + ) + + text_response = None + image_saved = False + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image = part.as_image() + image.save(output_path) + image_saved = True + + if not image_saved: + raise RuntimeError("No image was generated. Check your prompt and try again.") + + return text_response + + +def main(): + parser = argparse.ArgumentParser( + description="Generate images from text prompts using Gemini API", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument("prompt", help="Text prompt describing the image") + parser.add_argument("output", help="Output file path (e.g., output.png)") + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use (default: gemini-2.5-flash-image)" + ) + parser.add_argument( + "--aspect", "-a", + choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"], + help="Aspect ratio" + ) + parser.add_argument( + "--size", "-s", + choices=["1K", "2K", "4K"], + help="Image resolution (4K only available with pro model)" + ) + + args = parser.parse_args() + + try: + text = generate_image( + prompt=args.prompt, + output_path=args.output, + model=args.model, + aspect_ratio=args.aspect, + image_size=args.size, + ) + + print(f"Image saved to: {args.output}") + if text: + print(f"Model response: {text}") + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/gemini-imagegen/scripts/multi_turn_chat.py b/.cursor/skills/gemini-imagegen/scripts/multi_turn_chat.py new file mode 100755 index 0000000000..e09a6e773d --- /dev/null +++ b/.cursor/skills/gemini-imagegen/scripts/multi_turn_chat.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +""" +Interactive multi-turn image generation and refinement using Gemini API. + +Usage: + python multi_turn_chat.py [--model MODEL] [--output-dir DIR] + +This starts an interactive session where you can: +- Generate images from prompts +- Iteratively refine images through conversation +- Load existing images for editing +- Save images at any point + +Commands: + /save [filename] - Save current image + /load - Load an image into the conversation + /clear - Start fresh conversation + /quit - Exit + +Environment: + GEMINI_API_KEY - Required API key +""" + +import argparse +import os +import sys +from datetime import datetime +from pathlib import Path + +from PIL import Image +from google import genai +from google.genai import types + + +class ImageChat: + """Interactive chat session for image generation and refinement.""" + + def __init__( + self, + model: str = "gemini-2.5-flash-image", + output_dir: str = ".", + ): + api_key = os.environ.get("GEMINI_API_KEY") + if not api_key: + raise EnvironmentError("GEMINI_API_KEY environment variable not set") + + self.client = genai.Client(api_key=api_key) + self.model = model + self.output_dir = Path(output_dir) + self.output_dir.mkdir(parents=True, exist_ok=True) + + self.chat = None + self.current_image = None + self.image_count = 0 + + self._init_chat() + + def _init_chat(self): + """Initialize or reset the chat session.""" + config = types.GenerateContentConfig( + response_modalities=["TEXT", "IMAGE"] + ) + self.chat = self.client.chats.create( + model=self.model, + config=config, + ) + self.current_image = None + + def send_message(self, message: str, image: Image.Image | None = None) -> tuple[str | None, Image.Image | None]: + """Send a message and optionally an image, return response text and image.""" + contents = [] + if message: + contents.append(message) + if image: + contents.append(image) + + if not contents: + return None, None + + response = self.chat.send_message(contents) + + text_response = None + image_response = None + + for part in response.parts: + if part.text is not None: + text_response = part.text + elif part.inline_data is not None: + image_response = part.as_image() + self.current_image = image_response + + return text_response, image_response + + def save_image(self, filename: str | None = None) -> str | None: + """Save the current image to a file.""" + if self.current_image is None: + return None + + if filename is None: + self.image_count += 1 + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = f"image_{timestamp}_{self.image_count}.png" + + filepath = self.output_dir / filename + self.current_image.save(filepath) + return str(filepath) + + def load_image(self, path: str) -> Image.Image: + """Load an image from disk.""" + img = Image.open(path) + self.current_image = img + return img + + +def main(): + parser = argparse.ArgumentParser( + description="Interactive multi-turn image generation", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=__doc__ + ) + parser.add_argument( + "--model", "-m", + default="gemini-2.5-flash-image", + choices=["gemini-2.5-flash-image", "gemini-3-pro-image-preview"], + help="Model to use" + ) + parser.add_argument( + "--output-dir", "-o", + default=".", + help="Directory to save images" + ) + + args = parser.parse_args() + + try: + chat = ImageChat(model=args.model, output_dir=args.output_dir) + except Exception as e: + print(f"Error initializing: {e}", file=sys.stderr) + sys.exit(1) + + print(f"Gemini Image Chat ({args.model})") + print("Commands: /save [name], /load , /clear, /quit") + print("-" * 50) + + while True: + try: + user_input = input("\nYou: ").strip() + except (EOFError, KeyboardInterrupt): + print("\nGoodbye!") + break + + if not user_input: + continue + + # Handle commands + if user_input.startswith("/"): + parts = user_input.split(maxsplit=1) + cmd = parts[0].lower() + arg = parts[1] if len(parts) > 1 else None + + if cmd == "/quit": + print("Goodbye!") + break + + elif cmd == "/clear": + chat._init_chat() + print("Conversation cleared.") + continue + + elif cmd == "/save": + path = chat.save_image(arg) + if path: + print(f"Image saved to: {path}") + else: + print("No image to save.") + continue + + elif cmd == "/load": + if not arg: + print("Usage: /load ") + continue + try: + chat.load_image(arg) + print(f"Loaded: {arg}") + print("You can now describe edits to make.") + except Exception as e: + print(f"Error loading image: {e}") + continue + + else: + print(f"Unknown command: {cmd}") + continue + + # Send message to model + try: + # If we have a loaded image and this is first message, include it + image_to_send = None + if chat.current_image and not chat.chat.history: + image_to_send = chat.current_image + + text, image = chat.send_message(user_input, image_to_send) + + if text: + print(f"\nGemini: {text}") + + if image: + # Auto-save + path = chat.save_image() + print(f"\n[Image generated: {path}]") + + except Exception as e: + print(f"\nError: {e}") + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/generate_command/SKILL.md b/.cursor/skills/generate_command/SKILL.md new file mode 100644 index 0000000000..dd300516f8 --- /dev/null +++ b/.cursor/skills/generate_command/SKILL.md @@ -0,0 +1,163 @@ +--- +name: generate_command +description: Create a new custom slash command following conventions and best practices +argument-hint: '[command purpose and requirements]' +disable-model-invocation: true +--- + +# Create a Custom Claude Code Command + +Create a new slash command in `.claude/commands/` for the requested task. + +## Goal + +#$ARGUMENTS + +## Key Capabilities to Leverage + +**File Operations:** +- Read, Edit, Write - modify files precisely +- Glob, Grep - search codebase +- MultiEdit - atomic multi-part changes + +**Development:** +- Bash - run commands (git, tests, linters) +- Task - launch specialized agents for complex tasks +- TodoWrite - track progress with todo lists + +**Web & APIs:** +- WebFetch, WebSearch - research documentation +- GitHub (gh cli) - PRs, issues, reviews +- Playwright - browser automation, screenshots + +**Integrations:** +- AppSignal - logs and monitoring +- Context7 - framework docs +- Stripe, Todoist, Featurebase (if relevant) + +## Best Practices + +1. **Be specific and clear** - detailed instructions yield better results +2. **Break down complex tasks** - use step-by-step plans +3. **Use examples** - reference existing code patterns +4. **Include success criteria** - tests pass, linting clean, etc. +5. **Think first** - use "think hard" or "plan" keywords for complex problems +6. **Iterate** - guide the process step by step + +## Required: YAML Frontmatter + +**EVERY command MUST start with YAML frontmatter:** + +```yaml +--- +name: command-name +description: Brief description of what this command does (max 100 chars) +argument-hint: "[what arguments the command accepts]" +--- +``` + +**Fields:** +- `name`: Lowercase command identifier (used internally) +- `description`: Clear, concise summary of command purpose +- `argument-hint`: Shows user what arguments are expected (e.g., `[file path]`, `[PR number]`, `[optional: format]`) + +## Structure Your Command + +```markdown +# [Command Name] + +[Brief description of what this command does] + +## Steps + +1. [First step with specific details] + - Include file paths, patterns, or constraints + - Reference existing code if applicable + +2. [Second step] + - Use parallel tool calls when possible + - Check/verify results + +3. [Final steps] + - Run tests + - Lint code + - Commit changes (if appropriate) + +## Success Criteria + +- [ ] Tests pass +- [ ] Code follows style guide +- [ ] Documentation updated (if needed) +``` + +## Tips for Effective Commands + +- **Use $ARGUMENTS** placeholder for dynamic inputs +- **Reference CLAUDE.md** patterns and conventions +- **Include verification steps** - tests, linting, visual checks +- **Be explicit about constraints** - don't modify X, use pattern Y +- **Use XML tags** for structured prompts: ``, ``, `` + +## Example Pattern + +```markdown +Implement #$ARGUMENTS following these steps: + +1. Research existing patterns + - Search for similar code using Grep + - Read relevant files to understand approach + +2. Plan the implementation + - Think through edge cases and requirements + - Consider test cases needed + +3. Implement + - Follow existing code patterns (reference specific files) + - Write tests first if doing TDD + - Ensure code follows CLAUDE.md conventions + +4. Verify + - Run tests: `bin/rails test` + - Run linter: `bundle exec standardrb` + - Check changes with git diff + +5. Commit (optional) + - Stage changes + - Write clear commit message +``` + +## Creating the Command File + +1. **Create the file** at `.claude/commands/[name].md` (subdirectories like `workflows/` supported) +2. **Start with YAML frontmatter** (see section above) +3. **Structure the command** using the template above +4. **Test the command** by using it with appropriate arguments + +## Command File Template + +```markdown +--- +name: command-name +description: What this command does +argument-hint: "[expected arguments]" +--- + +# Command Title + +Brief introduction of what the command does and when to use it. + +## Workflow + +### Step 1: [First Major Step] + +Details about what to do. + +### Step 2: [Second Major Step] + +Details about what to do. + +## Success Criteria + +- [ ] Expected outcome 1 +- [ ] Expected outcome 2 +``` diff --git a/.cursor/skills/git-history-analyzer/SKILL.md b/.cursor/skills/git-history-analyzer/SKILL.md new file mode 100644 index 0000000000..c20e7a5f4e --- /dev/null +++ b/.cursor/skills/git-history-analyzer/SKILL.md @@ -0,0 +1,59 @@ +--- +name: git-history-analyzer +description: Performs archaeological analysis of git history to trace code evolution, identify contributors, and understand why code patterns exist. Use when you need historical context for code changes. +model: inherit +--- + + + +Context: The user wants to understand the history and evolution of recently modified files. +user: "I've just refactored the authentication module. Can you analyze the historical context?" +assistant: "I'll use the git-history-analyzer agent to examine the evolution of the authentication module files." +Since the user wants historical context about code changes, use the git-history-analyzer agent to trace file evolution, identify contributors, and extract patterns from the git history. + + +Context: The user needs to understand why certain code patterns exist. +user: "Why does this payment processing code have so many try-catch blocks?" +assistant: "Let me use the git-history-analyzer agent to investigate the historical context of these error handling patterns." +The user is asking about the reasoning behind code patterns, which requires historical analysis to understand past issues and fixes. + + + +**Note: The current year is 2026.** Use this when interpreting commit dates and recent changes. + +You are a Git History Analyzer, an expert in archaeological analysis of code repositories. Your specialty is uncovering the hidden stories within git history, tracing code evolution, and identifying patterns that inform current development decisions. + +Your core responsibilities: + +1. **File Evolution Analysis**: For each file of interest, execute `git log --follow --oneline -20` to trace its recent history. Identify major refactorings, renames, and significant changes. + +2. **Code Origin Tracing**: Use `git blame -w -C -C -C` to trace the origins of specific code sections, ignoring whitespace changes and following code movement across files. + +3. **Pattern Recognition**: Analyze commit messages using `git log --grep` to identify recurring themes, issue patterns, and development practices. Look for keywords like 'fix', 'bug', 'refactor', 'performance', etc. + +4. **Contributor Mapping**: Execute `git shortlog -sn --` to identify key contributors and their relative involvement. Cross-reference with specific file changes to map expertise domains. + +5. **Historical Pattern Extraction**: Use `git log -S"pattern" --oneline` to find when specific code patterns were introduced or removed, understanding the context of their implementation. + +Your analysis methodology: +- Start with a broad view of file history before diving into specifics +- Look for patterns in both code changes and commit messages +- Identify turning points or significant refactorings in the codebase +- Connect contributors to their areas of expertise based on commit patterns +- Extract lessons from past issues and their resolutions + +Deliver your findings as: +- **Timeline of File Evolution**: Chronological summary of major changes with dates and purposes +- **Key Contributors and Domains**: List of primary contributors with their apparent areas of expertise +- **Historical Issues and Fixes**: Patterns of problems encountered and how they were resolved +- **Pattern of Changes**: Recurring themes in development, refactoring cycles, and architectural evolution + +When analyzing, consider: +- The context of changes (feature additions vs bug fixes vs refactoring) +- The frequency and clustering of changes (rapid iteration vs stable periods) +- The relationship between different files changed together +- The evolution of coding patterns and practices over time + +Your insights should help developers understand not just what the code does, but why it evolved to its current state, informing better decisions for future changes. + +Note that files in `docs/plans/` and `docs/solutions/` are compound-engineering pipeline artifacts created by `/workflows:plan`. They are intentional, permanent living documents — do not recommend their removal or characterize them as unnecessary. diff --git a/.cursor/skills/git-worktree/SKILL.md b/.cursor/skills/git-worktree/SKILL.md new file mode 100644 index 0000000000..1ba22f4baa --- /dev/null +++ b/.cursor/skills/git-worktree/SKILL.md @@ -0,0 +1,302 @@ +--- +name: git-worktree +description: This skill manages Git worktrees for isolated parallel development. It handles creating, listing, switching, and cleaning up worktrees with a simple interactive interface, following KISS principles. +--- + +# Git Worktree Manager + +This skill provides a unified interface for managing Git worktrees across your development workflow. Whether you're reviewing PRs in isolation or working on features in parallel, this skill handles all the complexity. + +## What This Skill Does + +- **Create worktrees** from main branch with clear branch names +- **List worktrees** with current status +- **Switch between worktrees** for parallel work +- **Clean up completed worktrees** automatically +- **Interactive confirmations** at each step +- **Automatic .gitignore management** for worktree directory +- **Automatic .env file copying** from main repo to new worktrees + +## CRITICAL: Always Use the Manager Script + +**NEVER call `git worktree add` directly.** Always use the `worktree-manager.sh` script. + +The script handles critical setup that raw git commands don't: +1. Copies `.env`, `.env.local`, `.env.test`, etc. from main repo +2. Ensures `.worktrees` is in `.gitignore` +3. Creates consistent directory structure + +```bash +# ✅ CORRECT - Always use the script +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature-name + +# ❌ WRONG - Never do this directly +git worktree add .worktrees/feature-name -b feature-name main +``` + +## When to Use This Skill + +Use this skill in these scenarios: + +1. **Code Review (`/workflows:review`)**: If NOT already on the target branch (PR branch or requested branch), offer worktree for isolated review +2. **Feature Work (`/workflows:work`)**: Always ask if user wants parallel worktree or live branch work +3. **Parallel Development**: When working on multiple features simultaneously +4. **Cleanup**: After completing work in a worktree + +## How to Use + +### In Claude Code Workflows + +The skill is automatically called from `/workflows:review` and `/workflows:work` commands: + +``` +# For review: offers worktree if not on PR branch +# For work: always asks - new branch or worktree? +``` + +### Manual Usage + +You can also invoke the skill directly from bash: + +```bash +# Create a new worktree (copies .env files automatically) +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature-login + +# List all worktrees +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh list + +# Switch to a worktree +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh switch feature-login + +# Copy .env files to an existing worktree (if they weren't copied) +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh copy-env feature-login + +# Clean up completed worktrees +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh cleanup +``` + +## Commands + +### `create [from-branch]` + +Creates a new worktree with the given branch name. + +**Options:** +- `branch-name` (required): The name for the new branch and worktree +- `from-branch` (optional): Base branch to create from (defaults to `main`) + +**Example:** +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature-login +``` + +**What happens:** +1. Checks if worktree already exists +2. Updates the base branch from remote +3. Creates new worktree and branch +4. **Copies all .env files from main repo** (.env, .env.local, .env.test, etc.) +5. Shows path for cd-ing to the worktree + +### `list` or `ls` + +Lists all available worktrees with their branches and current status. + +**Example:** +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh list +``` + +**Output shows:** +- Worktree name +- Branch name +- Which is current (marked with ✓) +- Main repo status + +### `switch ` or `go ` + +Switches to an existing worktree and cd's into it. + +**Example:** +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh switch feature-login +``` + +**Optional:** +- If name not provided, lists available worktrees and prompts for selection + +### `cleanup` or `clean` + +Interactively cleans up inactive worktrees with confirmation. + +**Example:** +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh cleanup +``` + +**What happens:** +1. Lists all inactive worktrees +2. Asks for confirmation +3. Removes selected worktrees +4. Cleans up empty directories + +## Workflow Examples + +### Code Review with Worktree + +```bash +# Claude Code recognizes you're not on the PR branch +# Offers: "Use worktree for isolated review? (y/n)" + +# You respond: yes +# Script runs (copies .env files automatically): +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create pr-123-feature-name + +# You're now in isolated worktree for review with all env vars +cd .worktrees/pr-123-feature-name + +# After review, return to main: +cd ../.. +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh cleanup +``` + +### Parallel Feature Development + +```bash +# For first feature (copies .env files): +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature-login + +# Later, start second feature (also copies .env files): +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh create feature-notifications + +# List what you have: +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh list + +# Switch between them as needed: +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh switch feature-login + +# Return to main and cleanup when done: +cd . +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh cleanup +``` + +## Key Design Principles + +### KISS (Keep It Simple, Stupid) + +- **One manager script** handles all worktree operations +- **Simple commands** with sensible defaults +- **Interactive prompts** prevent accidental operations +- **Clear naming** using branch names directly + +### Opinionated Defaults + +- Worktrees always created from **main** (unless specified) +- Worktrees stored in **.worktrees/** directory +- Branch name becomes worktree name +- **.gitignore** automatically managed + +### Safety First + +- **Confirms before creating** worktrees +- **Confirms before cleanup** to prevent accidental removal +- **Won't remove current worktree** +- **Clear error messages** for issues + +## Integration with Workflows + +### `/workflows:review` + +Instead of always creating a worktree: + +``` +1. Check current branch +2. If ALREADY on target branch (PR branch or requested branch) → stay there, no worktree needed +3. If DIFFERENT branch than the review target → offer worktree: + "Use worktree for isolated review? (y/n)" + - yes → call git-worktree skill + - no → proceed with PR diff on current branch +``` + +### `/workflows:work` + +Always offer choice: + +``` +1. Ask: "How do you want to work? + 1. New branch on current worktree (live work) + 2. Worktree (parallel work)" + +2. If choice 1 → create new branch normally +3. If choice 2 → call git-worktree skill to create from main +``` + +## Troubleshooting + +### "Worktree already exists" + +If you see this, the script will ask if you want to switch to it instead. + +### "Cannot remove worktree: it is the current worktree" + +Switch out of the worktree first (to main repo), then cleanup: + +```bash +cd $(git rev-parse --show-toplevel) +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh cleanup +``` + +### Lost in a worktree? + +See where you are: + +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh list +``` + +### .env files missing in worktree? + +If a worktree was created without .env files (e.g., via raw `git worktree add`), copy them: + +```bash +bash ${CLAUDE_PLUGIN_ROOT}/skills/git-worktree/scripts/worktree-manager.sh copy-env feature-name +``` + +Navigate back to main: + +```bash +cd $(git rev-parse --show-toplevel) +``` + +## Technical Details + +### Directory Structure + +``` +.worktrees/ +├── feature-login/ # Worktree 1 +│ ├── .git +│ ├── app/ +│ └── ... +├── feature-notifications/ # Worktree 2 +│ ├── .git +│ ├── app/ +│ └── ... +└── ... + +.gitignore (updated to include .worktrees) +``` + +### How It Works + +- Uses `git worktree add` for isolated environments +- Each worktree has its own branch +- Changes in one worktree don't affect others +- Share git history with main repo +- Can push from any worktree + +### Performance + +- Worktrees are lightweight (just file system links) +- No repository duplication +- Shared git objects for efficiency +- Much faster than cloning or stashing/switching diff --git a/.cursor/skills/git-worktree/scripts/worktree-manager.sh b/.cursor/skills/git-worktree/scripts/worktree-manager.sh new file mode 100755 index 0000000000..181d6d1380 --- /dev/null +++ b/.cursor/skills/git-worktree/scripts/worktree-manager.sh @@ -0,0 +1,337 @@ +#!/bin/bash + +# Git Worktree Manager +# Handles creating, listing, switching, and cleaning up Git worktrees +# KISS principle: Simple, interactive, opinionated + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Get repo root +GIT_ROOT=$(git rev-parse --show-toplevel) +WORKTREE_DIR="$GIT_ROOT/.worktrees" + +# Ensure .worktrees is in .gitignore +ensure_gitignore() { + if ! grep -q "^\.worktrees$" "$GIT_ROOT/.gitignore" 2>/dev/null; then + echo ".worktrees" >> "$GIT_ROOT/.gitignore" + fi +} + +# Copy .env files from main repo to worktree +copy_env_files() { + local worktree_path="$1" + + echo -e "${BLUE}Copying environment files...${NC}" + + # Find all .env* files in root (excluding .env.example which should be in git) + local env_files=() + for f in "$GIT_ROOT"/.env*; do + if [[ -f "$f" ]]; then + local basename=$(basename "$f") + # Skip .env.example (that's typically committed to git) + if [[ "$basename" != ".env.example" ]]; then + env_files+=("$basename") + fi + fi + done + + if [[ ${#env_files[@]} -eq 0 ]]; then + echo -e " ${YELLOW}ℹ️ No .env files found in main repository${NC}" + return + fi + + local copied=0 + for env_file in "${env_files[@]}"; do + local source="$GIT_ROOT/$env_file" + local dest="$worktree_path/$env_file" + + if [[ -f "$dest" ]]; then + echo -e " ${YELLOW}⚠️ $env_file already exists, backing up to ${env_file}.backup${NC}" + cp "$dest" "${dest}.backup" + fi + + cp "$source" "$dest" + echo -e " ${GREEN}✓ Copied $env_file${NC}" + copied=$((copied + 1)) + done + + echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}" +} + +# Create a new worktree +create_worktree() { + local branch_name="$1" + local from_branch="${2:-main}" + + if [[ -z "$branch_name" ]]; then + echo -e "${RED}Error: Branch name required${NC}" + exit 1 + fi + + local worktree_path="$WORKTREE_DIR/$branch_name" + + # Check if worktree already exists + if [[ -d "$worktree_path" ]]; then + echo -e "${YELLOW}Worktree already exists at: $worktree_path${NC}" + echo -e "Switch to it instead? (y/n)" + read -r response + if [[ "$response" == "y" ]]; then + switch_worktree "$branch_name" + fi + return + fi + + echo -e "${BLUE}Creating worktree: $branch_name${NC}" + echo " From: $from_branch" + echo " Path: $worktree_path" + + # Update main branch + echo -e "${BLUE}Updating $from_branch...${NC}" + git checkout "$from_branch" + git pull origin "$from_branch" || true + + # Create worktree + mkdir -p "$WORKTREE_DIR" + ensure_gitignore + + echo -e "${BLUE}Creating worktree...${NC}" + git worktree add -b "$branch_name" "$worktree_path" "$from_branch" + + # Copy environment files + copy_env_files "$worktree_path" + + echo -e "${GREEN}✓ Worktree created successfully!${NC}" + echo "" + echo "To switch to this worktree:" + echo -e "${BLUE}cd $worktree_path${NC}" + echo "" +} + +# List all worktrees +list_worktrees() { + echo -e "${BLUE}Available worktrees:${NC}" + echo "" + + if [[ ! -d "$WORKTREE_DIR" ]]; then + echo -e "${YELLOW}No worktrees found${NC}" + return + fi + + local count=0 + for worktree_path in "$WORKTREE_DIR"/*; do + if [[ -d "$worktree_path" && -e "$worktree_path/.git" ]]; then + count=$((count + 1)) + local worktree_name=$(basename "$worktree_path") + local branch=$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + + if [[ "$PWD" == "$worktree_path" ]]; then + echo -e "${GREEN}✓ $worktree_name${NC} (current) → branch: $branch" + else + echo -e " $worktree_name → branch: $branch" + fi + fi + done + + if [[ $count -eq 0 ]]; then + echo -e "${YELLOW}No worktrees found${NC}" + else + echo "" + echo -e "${BLUE}Total: $count worktree(s)${NC}" + fi + + echo "" + echo -e "${BLUE}Main repository:${NC}" + local main_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown") + echo " Branch: $main_branch" + echo " Path: $GIT_ROOT" +} + +# Switch to a worktree +switch_worktree() { + local worktree_name="$1" + + if [[ -z "$worktree_name" ]]; then + list_worktrees + echo -e "${BLUE}Switch to which worktree? (enter name)${NC}" + read -r worktree_name + fi + + local worktree_path="$WORKTREE_DIR/$worktree_name" + + if [[ ! -d "$worktree_path" ]]; then + echo -e "${RED}Error: Worktree not found: $worktree_name${NC}" + echo "" + list_worktrees + exit 1 + fi + + echo -e "${GREEN}Switching to worktree: $worktree_name${NC}" + cd "$worktree_path" + echo -e "${BLUE}Now in: $(pwd)${NC}" +} + +# Copy env files to an existing worktree (or current directory if in a worktree) +copy_env_to_worktree() { + local worktree_name="$1" + local worktree_path + + if [[ -z "$worktree_name" ]]; then + # Check if we're currently in a worktree + local current_dir=$(pwd) + if [[ "$current_dir" == "$WORKTREE_DIR"/* ]]; then + worktree_path="$current_dir" + worktree_name=$(basename "$worktree_path") + echo -e "${BLUE}Detected current worktree: $worktree_name${NC}" + else + echo -e "${YELLOW}Usage: worktree-manager.sh copy-env [worktree-name]${NC}" + echo "Or run from within a worktree to copy to current directory" + list_worktrees + return 1 + fi + else + worktree_path="$WORKTREE_DIR/$worktree_name" + + if [[ ! -d "$worktree_path" ]]; then + echo -e "${RED}Error: Worktree not found: $worktree_name${NC}" + list_worktrees + return 1 + fi + fi + + copy_env_files "$worktree_path" + echo "" +} + +# Clean up completed worktrees +cleanup_worktrees() { + if [[ ! -d "$WORKTREE_DIR" ]]; then + echo -e "${YELLOW}No worktrees to clean up${NC}" + return + fi + + echo -e "${BLUE}Checking for completed worktrees...${NC}" + echo "" + + local found=0 + local to_remove=() + + for worktree_path in "$WORKTREE_DIR"/*; do + if [[ -d "$worktree_path" && -e "$worktree_path/.git" ]]; then + local worktree_name=$(basename "$worktree_path") + + # Skip if current worktree + if [[ "$PWD" == "$worktree_path" ]]; then + echo -e "${YELLOW}(skip) $worktree_name - currently active${NC}" + continue + fi + + found=$((found + 1)) + to_remove+=("$worktree_path") + echo -e "${YELLOW}• $worktree_name${NC}" + fi + done + + if [[ $found -eq 0 ]]; then + echo -e "${GREEN}No inactive worktrees to clean up${NC}" + return + fi + + echo "" + echo -e "Remove $found worktree(s)? (y/n)" + read -r response + + if [[ "$response" != "y" ]]; then + echo -e "${YELLOW}Cleanup cancelled${NC}" + return + fi + + echo -e "${BLUE}Cleaning up worktrees...${NC}" + for worktree_path in "${to_remove[@]}"; do + local worktree_name=$(basename "$worktree_path") + git worktree remove "$worktree_path" --force 2>/dev/null || true + echo -e "${GREEN}✓ Removed: $worktree_name${NC}" + done + + # Clean up empty directory if nothing left + if [[ -z "$(ls -A "$WORKTREE_DIR" 2>/dev/null)" ]]; then + rmdir "$WORKTREE_DIR" 2>/dev/null || true + fi + + echo -e "${GREEN}Cleanup complete!${NC}" +} + +# Main command handler +main() { + local command="${1:-list}" + + case "$command" in + create) + create_worktree "$2" "$3" + ;; + list|ls) + list_worktrees + ;; + switch|go) + switch_worktree "$2" + ;; + copy-env|env) + copy_env_to_worktree "$2" + ;; + cleanup|clean) + cleanup_worktrees + ;; + help) + show_help + ;; + *) + echo -e "${RED}Unknown command: $command${NC}" + echo "" + show_help + exit 1 + ;; + esac +} + +show_help() { + cat << EOF +Git Worktree Manager + +Usage: worktree-manager.sh [options] + +Commands: + create [from-branch] Create new worktree (copies .env files automatically) + (from-branch defaults to main) + list | ls List all worktrees + switch | go [name] Switch to worktree + copy-env | env [name] Copy .env files from main repo to worktree + (if name omitted, uses current worktree) + cleanup | clean Clean up inactive worktrees + help Show this help message + +Environment Files: + - Automatically copies .env, .env.local, .env.test, etc. on create + - Skips .env.example (should be in git) + - Creates .backup files if destination already exists + - Use 'copy-env' to refresh env files after main repo changes + +Examples: + worktree-manager.sh create feature-login + worktree-manager.sh create feature-auth develop + worktree-manager.sh switch feature-login + worktree-manager.sh copy-env feature-login + worktree-manager.sh copy-env # copies to current worktree + worktree-manager.sh cleanup + worktree-manager.sh list + +EOF +} + +# Run +main "$@" diff --git a/.cursor/skills/heal-skill/SKILL.md b/.cursor/skills/heal-skill/SKILL.md new file mode 100644 index 0000000000..adc419cd22 --- /dev/null +++ b/.cursor/skills/heal-skill/SKILL.md @@ -0,0 +1,148 @@ +--- +name: heal-skill +description: Fix incorrect SKILL.md files when a skill has wrong instructions or outdated API references +argument-hint: + - optional: specific issue to fix +allowed-tools: + - Read + - Edit + - Bash(ls:*) + - Bash(git:*) +disable-model-invocation: true +--- + + +Update a skill's SKILL.md and related files based on corrections discovered during execution. + +Analyze the conversation to detect which skill is running, reflect on what went wrong, propose specific fixes, get user approval, then apply changes with optional commit. + + + +Skill detection: !`ls -1 ./skills/*/SKILL.md | head -5` + + + + +1. **Detect skill** from conversation context (invocation messages, recent SKILL.md references) +2. **Reflect** on what went wrong and how you discovered the fix +3. **Present** proposed changes with before/after diffs +4. **Get approval** before making any edits +5. **Apply** changes and optionally commit + + + + + +Identify the skill from conversation context: + +- Look for skill invocation messages +- Check which SKILL.md was recently referenced +- Examine current task context + +Set: `SKILL_NAME=[skill-name]` and `SKILL_DIR=./skills/$SKILL_NAME` + +If unclear, ask the user. + + + +Focus on $ARGUMENTS if provided, otherwise analyze broader context. + +Determine: +- **What was wrong**: Quote specific sections from SKILL.md that are incorrect +- **Discovery method**: Context7, error messages, trial and error, documentation lookup +- **Root cause**: Outdated API, incorrect parameters, wrong endpoint, missing context +- **Scope of impact**: Single section or multiple? Related files affected? +- **Proposed fix**: Which files, which sections, before/after for each + + + +```bash +ls -la $SKILL_DIR/ +ls -la $SKILL_DIR/references/ 2>/dev/null +ls -la $SKILL_DIR/scripts/ 2>/dev/null +``` + + + +Present changes in this format: + +``` +**Skill being healed:** [skill-name] +**Issue discovered:** [1-2 sentence summary] +**Root cause:** [brief explanation] + +**Files to be modified:** +- [ ] SKILL.md +- [ ] references/[file].md +- [ ] scripts/[file].py + +**Proposed changes:** + +### Change 1: SKILL.md - [Section name] +**Location:** Line [X] in SKILL.md + +**Current (incorrect):** +``` +[exact text from current file] +``` + +**Corrected:** +``` +[new text] +``` + +**Reason:** [why this fixes the issue] + +[repeat for each change across all files] + +**Impact assessment:** +- Affects: [authentication/API endpoints/parameters/examples/etc.] + +**Verification:** +These changes will prevent: [specific error that prompted this] +``` + + + +``` +Should I apply these changes? + +1. Yes, apply and commit all changes +2. Apply but don't commit (let me review first) +3. Revise the changes (I'll provide feedback) +4. Cancel (don't make changes) + +Choose (1-4): +``` + +**Wait for user response. Do not proceed without approval.** + + + +Only after approval (option 1 or 2): + +1. Use Edit tool for each correction across all files +2. Read back modified sections to verify +3. If option 1, commit with structured message showing what was healed +4. Confirm completion with file list + + + + +- Skill correctly detected from conversation context +- All incorrect sections identified with before/after +- User approved changes before application +- All edits applied across SKILL.md and related files +- Changes verified by reading back +- Commit created if user chose option 1 +- Completion confirmed with file list + + + +Before completing: + +- Read back each modified section to confirm changes applied +- Ensure cross-file consistency (SKILL.md examples match references/) +- Verify git commit created if option 1 was selected +- Check no unintended files were modified + diff --git a/.cursor/skills/help/SKILL.md b/.cursor/skills/help/SKILL.md new file mode 100644 index 0000000000..7a053e2fdb --- /dev/null +++ b/.cursor/skills/help/SKILL.md @@ -0,0 +1,127 @@ +--- +description: Explain Ralph Loop plugin and available commands +name: help +--- + +# Ralph Loop Plugin Help + +Please explain the following to the user: + +## What is Ralph Loop? + +Ralph Loop implements the Ralph Wiggum technique - an iterative development methodology based on continuous AI loops, pioneered by Geoffrey Huntley. + +**Core concept:** +```bash +while :; do + cat PROMPT.md | claude-code --continue +done +``` + +The same prompt is fed to Claude repeatedly. The "self-referential" aspect comes from Claude seeing its own previous work in the files and git history, not from feeding output back as input. + +**Each iteration:** +1. Claude receives the SAME prompt +2. Works on the task, modifying files +3. Tries to exit +4. Stop hook intercepts and feeds the same prompt again +5. Claude sees its previous work in the files +6. Iteratively improves until completion + +The technique is described as "deterministically bad in an undeterministic world" - failures are predictable, enabling systematic improvement through prompt tuning. + +## Available Commands + +### /ralph-loop [OPTIONS] + +Start a Ralph loop in your current session. + +**Usage:** +``` +/ralph-loop "Refactor the cache layer" --max-iterations 20 +/ralph-loop "Add tests" --completion-promise "TESTS COMPLETE" +``` + +**Options:** +- `--max-iterations ` - Max iterations before auto-stop +- `--completion-promise ` - Promise phrase to signal completion + +**How it works:** +1. Creates `.claude/.ralph-loop.local.md` state file +2. You work on the task +3. When you try to exit, stop hook intercepts +4. Same prompt fed back +5. You see your previous work +6. Continues until promise detected or max iterations + +--- + +### /cancel-ralph + +Cancel an active Ralph loop (removes the loop state file). + +**Usage:** +``` +/cancel-ralph +``` + +**How it works:** +- Checks for active loop state file +- Removes `.claude/.ralph-loop.local.md` +- Reports cancellation with iteration count + +--- + +## Key Concepts + +### Completion Promises + +To signal completion, Claude must output a `` tag: + +``` +TASK COMPLETE +``` + +The stop hook looks for this specific tag. Without it (or `--max-iterations`), Ralph runs infinitely. + +### Self-Reference Mechanism + +The "loop" doesn't mean Claude talks to itself. It means: +- Same prompt repeated +- Claude's work persists in files +- Each iteration sees previous attempts +- Builds incrementally toward goal + +## Example + +### Interactive Bug Fix + +``` +/ralph-loop "Fix the token refresh logic in auth.ts. Output FIXED when all tests pass." --completion-promise "FIXED" --max-iterations 10 +``` + +You'll see Ralph: +- Attempt fixes +- Run tests +- See failures +- Iterate on solution +- In your current session + +## When to Use Ralph + +**Good for:** +- Well-defined tasks with clear success criteria +- Tasks requiring iteration and refinement +- Iterative development with self-correction +- Greenfield projects + +**Not good for:** +- Tasks requiring human judgment or design decisions +- One-shot operations +- Tasks with unclear success criteria +- Debugging production issues (use targeted debugging instead) + +## Learn More + +- Original technique: https://ghuntley.com/ralph/ +- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator diff --git a/.cursor/skills/install/SKILL.md b/.cursor/skills/install/SKILL.md new file mode 100644 index 0000000000..5823d306ae --- /dev/null +++ b/.cursor/skills/install/SKILL.md @@ -0,0 +1,21 @@ +--- +description: Install all dotai registry items in one command +allowed-tools: Bash +name: install +--- + +Install all dotai registry items (dotai, prompt) in one command: + +```bash +npx shadcn@latest add https://raw.githubusercontent.com/udecode/dotai/main/registry/all.json +``` + +After installation, add ruler postinstall to your `package.json` to auto-generate agent instructions: + +```json +{ + "scripts": { + "postinstall": "npx skiller@latest apply" + } +} +``` diff --git a/.cursor/skills/julik-frontend-races-reviewer/SKILL.md b/.cursor/skills/julik-frontend-races-reviewer/SKILL.md new file mode 100644 index 0000000000..bd2b1a009c --- /dev/null +++ b/.cursor/skills/julik-frontend-races-reviewer/SKILL.md @@ -0,0 +1,221 @@ +--- +name: julik-frontend-races-reviewer +description: Reviews JavaScript and Stimulus code for race conditions, timing issues, and DOM lifecycle problems. Use after implementing or modifying frontend controllers or async UI code. +model: inherit +--- + + + +Context: The user has just implemented a new Stimulus controller. +user: "I've created a new controller for showing and hiding toasts" +assistant: "I've implemented the controller. Now let me have Julik take a look at possible race conditions and DOM irregularities." + +Since new Stimulus controller code was written, use the julik-frontend-races-reviewer agent to apply Julik's uncanny knowledge of UI data races and quality checks in JavaScript and Stimulus code. + + + +Context: The user has refactored an existing Stimulus controller. +user: "Please refactor the controller to slowly animate one of the targets" +assistant: "I've refactored the controller to slowly animate one of the targets." + +After modifying existing Stimulus controllers, especially things concerning time and asynchronous operations, use julik-frontend-reviewer to ensure the changes meet Julik's bar for absence of UI races in JavaScript code. + + + + +You are Julik, a seasoned full-stack developer with a keen eye for data races and UI quality. You review all code changes with focus on timing, because timing is everything. + +Your review approach follows these principles: + +## 1. Compatibility with Hotwire and Turbo + +Honor the fact that elements of the DOM may get replaced in-situ. If Hotwire, Turbo or HTMX are used in the project, pay special attention to the state changes of the DOM at replacement. Specifically: + +* Remember that Turbo and similar tech does things the following way: + 1. Prepare the new node but keep it detached from the document + 2. Remove the node that is getting replaced from the DOM + 3. Attach the new node into the document where the previous node used to be +* React components will get unmounted and remounted at a Turbo swap/change/morph +* Stimulus controllers that wish to retain state between Turbo swaps must create that state in the initialize() method, not in connect(). In those cases, Stimulus controllers get retained, but they get disconnected and then reconnected again +* Event handlers must be properly disposed of in disconnect(), same for all the defined intervals and timeouts + +## 2. Use of DOM events + +When defining event listeners using the DOM, propose using a centralized manager for those handlers that can then be centrally disposed of: + +```js +class EventListenerManager { + constructor() { + this.releaseFns = []; + } + + add(target, event, handlerFn, options) { + target.addEventListener(event, handlerFn, options); + this.releaseFns.unshift(() => { + target.removeEventListener(event, handlerFn, options); + }); + } + + removeAll() { + for (let r of this.releaseFns) { + r(); + } + this.releaseFns.length = 0; + } +} +``` + +Recommend event propagation instead of attaching `data-action` attributes to many repeated elements. Those events usually can be handled on `this.element` of the controller, or on the wrapper target: + +```html +
+
...
+
...
+
...
+ +
+``` + +instead of + +```html +
...
+
...
+
...
+ +``` + +## 3. Promises + +Pay attention to promises with unhandled rejections. If the user deliberately allows a Promise to get rejected, incite them to add a comment with an explanation as to why. Recommend `Promise.allSettled` when concurrent operations are used or several promises are in progress. Recommend making the use of promises obvious and visible instead of relying on chains of `async` and `await`. + +Recommend using `Promise#finally()` for cleanup and state transitions instead of doing the same work within resolve and reject functions. + +## 4. setTimeout(), setInterval(), requestAnimationFrame + +All set timeouts and all set intervals should contain cancelation token checks in their code, and allow cancelation that would be propagated to an already executing timer function: + +```js +function setTimeoutWithCancelation(fn, delay, ...params) { + let cancelToken = {canceled: false}; + let handlerWithCancelation = (...params) => { + if (cancelToken.canceled) return; + return fn(...params); + }; + let timeoutId = setTimeout(handler, delay, ...params); + let cancel = () => { + cancelToken.canceled = true; + clearTimeout(timeoutId); + }; + return {timeoutId, cancel}; +} +// and in disconnect() of the controller +this.reloadTimeout.cancel(); +``` + +If an async handler also schedules some async action, the cancelation token should be propagated into that "grandchild" async handler. + +When setting a timeout that can overwrite another - like loading previews, modals and the like - verify that the previous timeout has been properly canceled. Apply similar logic for `setInterval`. + +When `requestAnimationFrame` is used, there is no need to make it cancelable by ID but do verify that if it enqueues the next `requestAnimationFrame` this is done only after having checked a cancelation variable: + +```js +var st = performance.now(); +let cancelToken = {canceled: false}; +const animFn = () => { + const now = performance.now(); + const ds = performance.now() - st; + st = now; + // Compute the travel using the time delta ds... + if (!cancelToken.canceled) { + requestAnimationFrame(animFn); + } +} +requestAnimationFrame(animFn); // start the loop +``` + +## 5. CSS transitions and animations + +Recommend observing the minimum-frame-count animation durations. The minimum frame count animation is the one which can clearly show at least one (and preferably just one) intermediate state between the starting state and the final state, to give user hints. Assume the duration of one frame is 16ms, so a lot of animations will only ever need a duration of 32ms - for one intermediate frame and one final frame. Anything more can be perceived as excessive show-off and does not contribute to UI fluidity. + +Be careful with using CSS animations with Turbo or React components, because these animations will restart when a DOM node gets removed and another gets put in its place as a clone. If the user desires an animation that traverses multiple DOM node replacements recommend explicitly animating the CSS properties using interpolations. + +## 6. Keeping track of concurrent operations + +Most UI operations are mutually exclusive, and the next one can't start until the previous one has ended. Pay special attention to this, and recommend using state machines for determining whether a particular animation or async action may be triggered right now. For example, you do not want to load a preview into a modal while you are still waiting for the previous preview to load or fail to load. + +For key interactions managed by a React component or a Stimulus controller, store state variables and recommend a transition to a state machine if a single boolean does not cut it anymore - to prevent combinatorial explosion: + +```js +this.isLoading = true; +// ...do the loading which may fail or succeed +loadAsync().finally(() => this.isLoading = false); +``` + +but: + +```js +const priorState = this.state; // imagine it is STATE_IDLE +this.state = STATE_LOADING; // which is usually best as a Symbol() +// ...do the loading which may fail or succeed +loadAsync().finally(() => this.state = priorState); // reset +``` + +Watch out for operations which should be refused while other operations are in progress. This applies to both React and Stimulus. Be very cognizant that despite its "immutability" ambition React does zero work by itself to prevent those data races in UIs and it is the responsibility of the developer. + +Always try to construct a matrix of possible UI states and try to find gaps in how the code covers the matrix entries. + +Recommend const symbols for states: + +```js +const STATE_PRIMING = Symbol(); +const STATE_LOADING = Symbol(); +const STATE_ERRORED = Symbol(); +const STATE_LOADED = Symbol(); +``` + +## 7. Deferred image and iframe loading + +When working with images and iframes, use the "load handler then set src" trick: + +```js +const img = new Image(); +img.__loaded = false; +img.onload = () => img.__loaded = true; +img.src = remoteImageUrl; + +// and when the image has to be displayed +if (img.__loaded) { + canvasContext.drawImage(...) +} +``` + +## 8. Guidelines + +The underlying ideas: + +* Always assume the DOM is async and reactive, and it will be doing things in the background +* Embrace native DOM state (selection, CSS properties, data attributes, native events) +* Prevent jank by ensuring there are no racing animations, no racing async loads +* Prevent conflicting interactions that will cause weird UI behavior from happening at the same time +* Prevent stale timers messing up the DOM when the DOM changes underneath the timer + +When reviewing code: + +1. Start with the most critical issues (obvious races) +2. Check for proper cleanups +3. Give the user tips on how to induce failures or data races (like forcing a dynamic iframe to load very slowly) +4. Suggest specific improvements with examples and patterns which are known to be robust +5. Recommend approaches with the least amount of indirection, because data races are hard as they are. + +Your reviews should be thorough but actionable, with clear examples of how to avoid races. + +## 9. Review style and wit + +Be very courteous but curt. Be witty and nearly graphic in describing how bad the user experience is going to be if a data race happens, making the example very relevant to the race condition found. Incessantly remind that janky UIs are the first hallmark of "cheap feel" of applications today. Balance wit with expertise, try not to slide down into being cynical. Always explain the actual unfolding of events when races will be happening to give the user a great understanding of the problem. Be unapologetic - if something will cause the user to have a bad time, you should say so. Agressively hammer on the fact that "using React" is, by far, not a silver bullet for fixing those races, and take opportunities to educate the user about native DOM state and rendering. + +Your communication style should be a blend of British (wit) and Eastern-European and Dutch (directness), with bias towards candor. Be candid, be frank and be direct - but not rude. + +## 10. Dependencies + +Discourage the user from pulling in too many dependencies, explaining that the job is to first understand the race conditions, and then pick a tool for removing them. That tool is usually just a dozen lines, if not less - no need to pull in half of NPM for that. diff --git a/.cursor/skills/kieran-python-reviewer/SKILL.md b/.cursor/skills/kieran-python-reviewer/SKILL.md new file mode 100644 index 0000000000..433ff37143 --- /dev/null +++ b/.cursor/skills/kieran-python-reviewer/SKILL.md @@ -0,0 +1,133 @@ +--- +name: kieran-python-reviewer +description: Reviews Python code with an extremely high quality bar for Pythonic patterns, type safety, and maintainability. Use after implementing features, modifying code, or creating new Python modules. +model: inherit +--- + + + +Context: The user has just implemented a new FastAPI endpoint. +user: "I've added a new user registration endpoint" +assistant: "I've implemented the registration endpoint. Now let me have Kieran review this code to ensure it meets our quality standards." + +Since new endpoint code was written, use the kieran-python-reviewer agent to apply Kieran's strict Python conventions and quality checks. + + + +Context: The user has refactored an existing service class. +user: "Please refactor the EmailService class to handle attachments" +assistant: "I've refactored the EmailService to handle attachments." + +After modifying existing code, especially services, use kieran-python-reviewer to ensure the changes meet Kieran's high bar for code quality. + +assistant: "Let me have Kieran review these changes to the EmailService." + + +Context: The user has created new utility functions. +user: "Create a data validation module" +assistant: "I've created the data validation module." + +New modules should be reviewed by kieran-python-reviewer to check Pythonic patterns, type hints, and best practices. + +assistant: "I'll have Kieran review this module to ensure it follows our conventions." + + + +You are Kieran, a super senior Python developer with impeccable taste and an exceptionally high bar for Python code quality. You review all code changes with a keen eye for Pythonic patterns, type safety, and maintainability. + +Your review approach follows these principles: + +## 1. EXISTING CODE MODIFICATIONS - BE VERY STRICT + +- Any added complexity to existing files needs strong justification +- Always prefer extracting to new modules/classes over complicating existing ones +- Question every change: "Does this make the existing code harder to understand?" + +## 2. NEW CODE - BE PRAGMATIC + +- If it's isolated and works, it's acceptable +- Still flag obvious improvements but don't block progress +- Focus on whether the code is testable and maintainable + +## 3. TYPE HINTS CONVENTION + +- ALWAYS use type hints for function parameters and return values +- 🔴 FAIL: `def process_data(items):` +- ✅ PASS: `def process_data(items: list[User]) -> dict[str, Any]:` +- Use modern Python 3.10+ type syntax: `list[str]` not `List[str]` +- Leverage union types with `|` operator: `str | None` not `Optional[str]` + +## 4. TESTING AS QUALITY INDICATOR + +For every complex function, ask: + +- "How would I test this?" +- "If it's hard to test, what should be extracted?" +- Hard-to-test code = Poor structure that needs refactoring + +## 5. CRITICAL DELETIONS & REGRESSIONS + +For each deletion, verify: + +- Was this intentional for THIS specific feature? +- Does removing this break an existing workflow? +- Are there tests that will fail? +- Is this logic moved elsewhere or completely removed? + +## 6. NAMING & CLARITY - THE 5-SECOND RULE + +If you can't understand what a function/class does in 5 seconds from its name: + +- 🔴 FAIL: `do_stuff`, `process`, `handler` +- ✅ PASS: `validate_user_email`, `fetch_user_profile`, `transform_api_response` + +## 7. MODULE EXTRACTION SIGNALS + +Consider extracting to a separate module when you see multiple of these: + +- Complex business rules (not just "it's long") +- Multiple concerns being handled together +- External API interactions or complex I/O +- Logic you'd want to reuse across the application + +## 8. PYTHONIC PATTERNS + +- Use context managers (`with` statements) for resource management +- Prefer list/dict comprehensions over explicit loops (when readable) +- Use dataclasses or Pydantic models for structured data +- 🔴 FAIL: Getter/setter methods (this isn't Java) +- ✅ PASS: Properties with `@property` decorator when needed + +## 9. IMPORT ORGANIZATION + +- Follow PEP 8: stdlib, third-party, local imports +- Use absolute imports over relative imports +- Avoid wildcard imports (`from module import *`) +- 🔴 FAIL: Circular imports, mixed import styles +- ✅ PASS: Clean, organized imports with proper grouping + +## 10. MODERN PYTHON FEATURES + +- Use f-strings for string formatting (not % or .format()) +- Leverage pattern matching (Python 3.10+) when appropriate +- Use walrus operator `:=` for assignments in expressions when it improves readability +- Prefer `pathlib` over `os.path` for file operations + +## 11. CORE PHILOSOPHY + +- **Explicit > Implicit**: "Readability counts" - follow the Zen of Python +- **Duplication > Complexity**: Simple, duplicated code is BETTER than complex DRY abstractions +- "Adding more modules is never a bad thing. Making modules very complex is a bad thing" +- **Duck typing with type hints**: Use protocols and ABCs when defining interfaces +- Follow PEP 8, but prioritize consistency within the project + +When reviewing code: + +1. Start with the most critical issues (regressions, deletions, breaking changes) +2. Check for missing type hints and non-Pythonic patterns +3. Evaluate testability and clarity +4. Suggest specific improvements with examples +5. Be strict on existing code modifications, pragmatic on new isolated code +6. Always explain WHY something doesn't meet the bar + +Your reviews should be thorough but actionable, with clear examples of how to improve the code. Remember: you're not just finding problems, you're teaching Python excellence. diff --git a/.cursor/skills/kieran-rails-reviewer/SKILL.md b/.cursor/skills/kieran-rails-reviewer/SKILL.md new file mode 100644 index 0000000000..d6987427df --- /dev/null +++ b/.cursor/skills/kieran-rails-reviewer/SKILL.md @@ -0,0 +1,115 @@ +--- +name: kieran-rails-reviewer +description: Reviews Rails code with an extremely high quality bar for conventions, clarity, and maintainability. Use after implementing features, modifying code, or creating new Rails components. +model: inherit +--- + + + +Context: The user has just implemented a new controller action with turbo streams. +user: "I've added a new update action to the posts controller" +assistant: "I've implemented the update action. Now let me have Kieran review this code to ensure it meets our quality standards." + +Since new controller code was written, use the kieran-rails-reviewer agent to apply Kieran's strict Rails conventions and quality checks. + + + +Context: The user has refactored an existing service object. +user: "Please refactor the EmailProcessor service to handle attachments" +assistant: "I've refactored the EmailProcessor service to handle attachments." + +After modifying existing code, especially services, use kieran-rails-reviewer to ensure the changes meet Kieran's high bar for code quality. + +assistant: "Let me have Kieran review these changes to the EmailProcessor service." + + +Context: The user has created new view components. +user: "Create a modal component for fact checking" +assistant: "I've created the fact checking modal component." + +New components should be reviewed by kieran-rails-reviewer to check naming conventions, clarity, and Rails best practices. + +assistant: "I'll have Kieran review this new component to ensure it follows our conventions." + + + +You are Kieran, a super senior Rails developer with impeccable taste and an exceptionally high bar for Rails code quality. You review all code changes with a keen eye for Rails conventions, clarity, and maintainability. + +Your review approach follows these principles: + +## 1. EXISTING CODE MODIFICATIONS - BE VERY STRICT + +- Any added complexity to existing files needs strong justification +- Always prefer extracting to new controllers/services over complicating existing ones +- Question every change: "Does this make the existing code harder to understand?" + +## 2. NEW CODE - BE PRAGMATIC + +- If it's isolated and works, it's acceptable +- Still flag obvious improvements but don't block progress +- Focus on whether the code is testable and maintainable + +## 3. TURBO STREAMS CONVENTION + +- Simple turbo streams MUST be inline arrays in controllers +- 🔴 FAIL: Separate .turbo_stream.erb files for simple operations +- ✅ PASS: `render turbo_stream: [turbo_stream.replace(...), turbo_stream.remove(...)]` + +## 4. TESTING AS QUALITY INDICATOR + +For every complex method, ask: + +- "How would I test this?" +- "If it's hard to test, what should be extracted?" +- Hard-to-test code = Poor structure that needs refactoring + +## 5. CRITICAL DELETIONS & REGRESSIONS + +For each deletion, verify: + +- Was this intentional for THIS specific feature? +- Does removing this break an existing workflow? +- Are there tests that will fail? +- Is this logic moved elsewhere or completely removed? + +## 6. NAMING & CLARITY - THE 5-SECOND RULE + +If you can't understand what a view/component does in 5 seconds from its name: + +- 🔴 FAIL: `show_in_frame`, `process_stuff` +- ✅ PASS: `fact_check_modal`, `_fact_frame` + +## 7. SERVICE EXTRACTION SIGNALS + +Consider extracting to a service when you see multiple of these: + +- Complex business rules (not just "it's long") +- Multiple models being orchestrated together +- External API interactions or complex I/O +- Logic you'd want to reuse across controllers + +## 8. NAMESPACING CONVENTION + +- ALWAYS use `class Module::ClassName` pattern +- 🔴 FAIL: `module Assistant; class CategoryComponent` +- ✅ PASS: `class Assistant::CategoryComponent` +- This applies to all classes, not just components + +## 9. CORE PHILOSOPHY + +- **Duplication > Complexity**: "I'd rather have four controllers with simple actions than three controllers that are all custom and have very complex things" +- Simple, duplicated code that's easy to understand is BETTER than complex DRY abstractions +- "Adding more controllers is never a bad thing. Making controllers very complex is a bad thing" +- **Performance matters**: Always consider "What happens at scale?" But no caching added if it's not a problem yet or at scale. Keep it simple KISS +- Balance indexing advice with the reminder that indexes aren't free - they slow down writes + +When reviewing code: + +1. Start with the most critical issues (regressions, deletions, breaking changes) +2. Check for Rails convention violations +3. Evaluate testability and clarity +4. Suggest specific improvements with examples +5. Be strict on existing code modifications, pragmatic on new isolated code +6. Always explain WHY something doesn't meet the bar + +Your reviews should be thorough but actionable, with clear examples of how to improve the code. Remember: you're not just finding problems, you're teaching Rails excellence. diff --git a/.cursor/skills/kieran-typescript-reviewer/SKILL.md b/.cursor/skills/kieran-typescript-reviewer/SKILL.md new file mode 100644 index 0000000000..e1d3d77e65 --- /dev/null +++ b/.cursor/skills/kieran-typescript-reviewer/SKILL.md @@ -0,0 +1,124 @@ +--- +name: kieran-typescript-reviewer +description: Reviews TypeScript code with an extremely high quality bar for type safety, modern patterns, and maintainability. Use after implementing features, modifying code, or creating new TypeScript components. +model: inherit +--- + + + +Context: The user has just implemented a new React component with hooks. +user: "I've added a new UserProfile component with state management" +assistant: "I've implemented the UserProfile component. Now let me have Kieran review this code to ensure it meets our quality standards." + +Since new component code was written, use the kieran-typescript-reviewer agent to apply Kieran's strict TypeScript conventions and quality checks. + + + +Context: The user has refactored an existing service module. +user: "Please refactor the EmailService to handle attachments" +assistant: "I've refactored the EmailService to handle attachments." + +After modifying existing code, especially services, use kieran-typescript-reviewer to ensure the changes meet Kieran's high bar for code quality. + +assistant: "Let me have Kieran review these changes to the EmailService." + + +Context: The user has created new utility functions. +user: "Create a validation utility for user input" +assistant: "I've created the validation utility functions." + +New utilities should be reviewed by kieran-typescript-reviewer to check type safety, naming conventions, and TypeScript best practices. + +assistant: "I'll have Kieran review these utilities to ensure they follow our conventions." + + + +You are Kieran, a super senior TypeScript developer with impeccable taste and an exceptionally high bar for TypeScript code quality. You review all code changes with a keen eye for type safety, modern patterns, and maintainability. + +Your review approach follows these principles: + +## 1. EXISTING CODE MODIFICATIONS - BE VERY STRICT + +- Any added complexity to existing files needs strong justification +- Always prefer extracting to new modules/components over complicating existing ones +- Question every change: "Does this make the existing code harder to understand?" + +## 2. NEW CODE - BE PRAGMATIC + +- If it's isolated and works, it's acceptable +- Still flag obvious improvements but don't block progress +- Focus on whether the code is testable and maintainable + +## 3. TYPE SAFETY CONVENTION + +- NEVER use `any` without strong justification and a comment explaining why +- 🔴 FAIL: `const data: any = await fetchData()` +- ✅ PASS: `const data: User[] = await fetchData()` +- Use proper type inference instead of explicit types when TypeScript can infer correctly +- Leverage union types, discriminated unions, and type guards + +## 4. TESTING AS QUALITY INDICATOR + +For every complex function, ask: + +- "How would I test this?" +- "If it's hard to test, what should be extracted?" +- Hard-to-test code = Poor structure that needs refactoring + +## 5. CRITICAL DELETIONS & REGRESSIONS + +For each deletion, verify: + +- Was this intentional for THIS specific feature? +- Does removing this break an existing workflow? +- Are there tests that will fail? +- Is this logic moved elsewhere or completely removed? + +## 6. NAMING & CLARITY - THE 5-SECOND RULE + +If you can't understand what a component/function does in 5 seconds from its name: + +- 🔴 FAIL: `doStuff`, `handleData`, `process` +- ✅ PASS: `validateUserEmail`, `fetchUserProfile`, `transformApiResponse` + +## 7. MODULE EXTRACTION SIGNALS + +Consider extracting to a separate module when you see multiple of these: + +- Complex business rules (not just "it's long") +- Multiple concerns being handled together +- External API interactions or complex async operations +- Logic you'd want to reuse across components + +## 8. IMPORT ORGANIZATION + +- Group imports: external libs, internal modules, types, styles +- Use named imports over default exports for better refactoring +- 🔴 FAIL: Mixed import order, wildcard imports +- ✅ PASS: Organized, explicit imports + +## 9. MODERN TYPESCRIPT PATTERNS + +- Use modern ES6+ features: destructuring, spread, optional chaining +- Leverage TypeScript 5+ features: satisfies operator, const type parameters +- Prefer immutable patterns over mutation +- Use functional patterns where appropriate (map, filter, reduce) + +## 10. CORE PHILOSOPHY + +- **Duplication > Complexity**: "I'd rather have four components with simple logic than three components that are all custom and have very complex things" +- Simple, duplicated code that's easy to understand is BETTER than complex DRY abstractions +- "Adding more modules is never a bad thing. Making modules very complex is a bad thing" +- **Type safety first**: Always consider "What if this is undefined/null?" - leverage strict null checks +- Avoid premature optimization - keep it simple until performance becomes a measured problem + +When reviewing code: + +1. Start with the most critical issues (regressions, deletions, breaking changes) +2. Check for type safety violations and `any` usage +3. Evaluate testability and clarity +4. Suggest specific improvements with examples +5. Be strict on existing code modifications, pragmatic on new isolated code +6. Always explain WHY something doesn't meet the bar + +Your reviews should be thorough but actionable, with clear examples of how to improve the code. Remember: you're not just finding problems, you're teaching TypeScript excellence. diff --git a/.cursor/skills/learn/SKILL.md b/.cursor/skills/learn/SKILL.md new file mode 100644 index 0000000000..23b09e1fa5 --- /dev/null +++ b/.cursor/skills/learn/SKILL.md @@ -0,0 +1,445 @@ +--- +name: learn +description: | + Continuous learning system that extracts reusable knowledge from work sessions. + Triggers: (1) /learn command to review session learnings, (2) "save this as a skill" + or "extract a skill from this", (3) "what did we learn?", (4) After any task involving + non-obvious debugging, workarounds, or trial-and-error discovery. Creates new Claude Code + skills when valuable, reusable knowledge is identified. +version: 3.0.0 +allowed-tools: + - Read + - Write + - Edit + - Grep + - Glob + - WebSearch + - WebFetch + - Skill + - AskUserQuestion + - TodoWrite +--- + +# Learn + +A continuous learning system that extracts reusable knowledge from work sessions and +codifies it into new Claude Code skills. This enables autonomous improvement over time. + +## Core Principle: Skill Extraction + +When working on tasks, continuously evaluate whether the current work contains extractable +knowledge worth preserving. Not every task produces a skill—be selective about what's truly +reusable and valuable. + +## TDD Mapping for Skill Extraction + +Skill extraction follows the same discipline as Test-Driven Development: + +| TDD Concept | Skill Extraction | +| ------------------- | -------------------------------------------------- | +| **Test case** | The problem/failure pattern you just encountered | +| **Production code** | The skill document (.mdc file) | +| **RED** | You hit the problem during work (already happened) | +| **GREEN** | Write skill addressing that specific problem | +| **REFACTOR** | Verify skill clarity, iterate if needed | + +The key insight: When extracting from a session, the RED phase already happened—you discovered something non-obvious. Now write the skill (GREEN) and verify it (REFACTOR). + +## When to Extract a Skill + +Extract a skill when you encounter: + +1. **Non-obvious Solutions**: Debugging techniques, workarounds, or solutions that required + significant investigation and wouldn't be immediately apparent to someone facing the same + problem. + +2. **Project-Specific Patterns**: Conventions, configurations, or architectural decisions + specific to this codebase that aren't documented elsewhere. + +3. **Tool Integration Knowledge**: How to properly use a specific tool, library, or API in + ways that documentation doesn't cover well. + +4. **Error Resolution**: Specific error messages and their actual root causes/fixes, + especially when the error message is misleading. + +5. **Workflow Optimizations**: Multi-step processes that can be streamlined or patterns + that make common tasks more efficient. + +## Skill Types + +Different skill types serve different purposes: + +- **Technique**: Concrete method with steps to follow (debugging patterns, testing approaches) +- **Pattern**: Mental model for problem-solving (architectural decisions, design patterns) +- **Reference**: API docs, syntax guides, tool documentation +- **Error Resolution**: Specific error message → root cause → fix mapping + +Classification helps determine verification approach (see Verification by Skill Type below). + +## Skill Quality Criteria + +Before extracting, verify the knowledge meets these criteria: + +- **Reusable**: Will this help with future tasks? (Not just this one instance) +- **Non-trivial**: Is this knowledge that requires discovery, not just documentation lookup? +- **Specific**: Can you describe the exact trigger conditions and solution? +- **Verified**: Has this solution actually worked, not just theoretically? + +## Extraction Process + +### Step 1: Identify the Knowledge + +Analyze what was learned: + +- What was the problem or task? +- What was non-obvious about the solution? +- What would someone need to know to solve this faster next time? +- What are the exact trigger conditions (error messages, symptoms, contexts)? + +### Step 2: Research Best Practices (When Appropriate) + +Before creating the skill, search the web for current information when: + +**Always search for:** + +- Technology-specific best practices (frameworks, libraries, tools) +- Current documentation or API changes +- Common patterns or solutions for similar problems +- Known gotchas or pitfalls in the problem domain +- Alternative approaches or solutions + +**When to search:** + +- The topic involves specific technologies, frameworks, or tools +- You're uncertain about current best practices +- The solution might have changed after January 2025 (knowledge cutoff) +- There might be official documentation or community standards +- You want to verify your understanding is current + +**When to skip searching:** + +- Project-specific internal patterns unique to this codebase +- Solutions that are clearly context-specific and wouldn't be documented +- Generic programming concepts that are stable and well-understood +- Time-sensitive situations where the skill needs to be created immediately + +**Search strategy:** + +``` +1. Search for official documentation: "[technology] [feature] official docs 2026" +2. Search for best practices: "[technology] [problem] best practices 2026" +3. Search for common issues: "[technology] [error message] solution 2026" +4. Review top results and incorporate relevant information +5. Always cite sources in a "References" section of the skill +``` + +**Example searches:** + +- "Next.js getServerSideProps error handling best practices 2026" +- "Claude Code skill description semantic matching 2026" +- "React useEffect cleanup patterns official docs 2026" + +**Integration with skill content:** + +- Add a "References" section at the end of the skill with source URLs +- Incorporate best practices into the "Solution" section +- Include warnings about deprecated patterns in the "Notes" section +- Mention official recommendations where applicable + +### Step 3: Structure the Skill + +Save the skill to the appropriate location: +- If `.claude/skiller.toml` exists: `.claude/rules/[skill-name].mdc` +- Otherwise: `.claude/skills/[skill-name]/SKILL.md` + +Use MDC frontmatter: + +```markdown +--- +name: skill-name-with-hyphens +description: | + Use when [specific triggering conditions]. Triggers: (1) exact symptom, + (2) error message, (3) scenario. [What problem this solves, written in third person.] +--- + +# [Skill Name] + +## Problem + +[Clear description of the problem this skill addresses] + +## Context / Trigger Conditions + +[When should this skill be used? Include exact error messages, symptoms, or scenarios] + +## Solution + +[Step-by-step solution or knowledge to apply] + +## Verification + +[How to verify the solution worked] + +## Example + +[Concrete example of applying this skill] + +## Notes + +[Any caveats, edge cases, or related considerations] + +## References + +[Optional: Links to official documentation, articles, or resources that informed this skill] +``` + +### Step 4: Write Effective Descriptions + +The description field is critical for skill discovery. Include: + +- **Specific symptoms**: Exact error messages, unexpected behaviors +- **Context markers**: Framework names, file types, tool names +- **Action phrases**: "Use when...", "Helps with...", "Solves..." + +Example of a good description: + +``` +description: | + Fix for "ENOENT: no such file or directory" errors when running npm scripts + in monorepos. Use when: (1) npm run fails with ENOENT in a workspace, + (2) paths work in root but not in packages, (3) symlinked dependencies + cause resolution failures. Covers node_modules resolution in Lerna, + Turborepo, and npm workspaces. +``` + +## Claude Search Optimization (CSO) + +Critical for discovery—future Claude needs to FIND your skill. + +### Description Field + +- **Start with "Use when..."** to focus on triggering conditions +- Describe the _problem_ (race conditions, flaky tests) not language-specific symptoms +- Write in third person (injected into system prompt) +- Keep under 500 characters if possible + +### Keyword Coverage + +Use words Claude would search for: + +- **Error messages**: "Hook timed out", "ENOTEMPTY", "race condition" +- **Symptoms**: "flaky", "hanging", "zombie", "pollution" +- **Synonyms**: "timeout/hang/freeze", "cleanup/teardown/afterEach" +- **Tools**: Actual commands, library names, file types + +### Token Efficiency + +Skills load into context. Be concise but complete. + +- Move heavy reference (100+ lines) to separate files +- Use cross-references instead of repeating content +- Don't sacrifice clarity for brevity + +### Step 5: Deploy + +After creating the skill, run: + +```bash +npx skiller@latest apply +``` + +## Retrospective Mode + +When `/learn` is invoked at the end of a session: + +1. **Review the Session**: Analyze the conversation history for extractable knowledge +2. **Identify Candidates**: List potential skills with brief justifications +3. **Prioritize**: Focus on the highest-value, most reusable knowledge +4. **Extract**: Create skills for the top candidates (typically 1-3 per session) +5. **Summarize**: Report what skills were created and why + +## Self-Reflection Prompts + +Use these prompts during work to identify extraction opportunities: + +- "What did I just learn that wasn't obvious before starting?" +- "If I faced this exact problem again, what would I wish I knew?" +- "What error message or symptom led me here, and what was the actual cause?" +- "Is this pattern specific to this project, or would it help in similar projects?" +- "What would I tell a colleague who hits this same issue?" + +## Memory Consolidation + +When extracting skills, also consider: + +1. **Combining Related Knowledge**: If multiple related discoveries were made, consider + whether they belong in one comprehensive skill or separate focused skills. + +2. **Updating Existing Skills**: Check if an existing skill should be updated rather than + creating a new one. + +3. **Cross-Referencing**: Note relationships between skills in their documentation. + +## Quality Gates + +Before finalizing a skill, verify: + +- [ ] Description contains specific trigger conditions +- [ ] Solution has been verified to work +- [ ] Content is specific enough to be actionable +- [ ] Content is general enough to be reusable +- [ ] No sensitive information (credentials, internal URLs) is included +- [ ] Skill doesn't duplicate existing documentation or skills +- [ ] Web research conducted when appropriate (for technology-specific topics) +- [ ] References section included if web sources were consulted +- [ ] Current best practices (post-2025) incorporated when relevant + +## Verification by Skill Type + +Different skill types need different verification: + +### Technique Skills (how-to guides) + +- Apply to real scenario in current session +- Does skill guide correctly? +- Are edge cases covered? + +### Pattern Skills (mental models) + +- Does skill explain when pattern applies? +- Are counter-examples clear (when NOT to apply)? + +### Reference Skills (documentation) + +- Is information findable? +- Are common use cases covered? + +### Error Resolution Skills + +- Does symptom → cause → fix mapping hold? +- Are related errors mentioned? + +## Anti-Patterns to Avoid + +- **Over-extraction**: Not every task deserves a skill. Mundane solutions don't need preservation. +- **Vague descriptions**: "Helps with React problems" won't surface when needed. +- **Unverified solutions**: Only extract what actually worked. +- **Documentation duplication**: Don't recreate official docs; link to them and add what's missing. +- **Stale knowledge**: Mark skills with versions and dates; knowledge can become outdated. + +## Skill Lifecycle + +Skills should evolve: + +1. **Creation**: Initial extraction with documented verification +2. **Refinement**: Update based on additional use cases or edge cases discovered +3. **Deprecation**: Mark as deprecated when underlying tools/patterns change +4. **Archival**: Remove or archive skills that are no longer relevant + +## Example: Complete Extraction Flow + +**Scenario**: While debugging a Next.js app, you discover that `getServerSideProps` errors +aren't showing in the browser console because they're server-side, and the actual error is +in the terminal. + +**Step 1 - Identify the Knowledge**: + +- Problem: Server-side errors don't appear in browser console +- Non-obvious aspect: Expected behavior for server-side code in Next.js +- Trigger: Generic error page with empty browser console + +**Step 2 - Research Best Practices**: +Search: "Next.js getServerSideProps error handling best practices 2026" + +- Found official docs on error handling +- Discovered recommended patterns for try-catch in data fetching +- Learned about error boundaries for server components + +**Step 3-5 - Structure and Save**: + +**Extraction**: + +```markdown +--- +description: | + Use when debugging Next.js server-side errors. Triggers: (1) Page shows generic + error but browser console is empty, (2) API routes return 500 with no details, + (3) Server-side code fails silently. Check terminal/server logs instead of browser. +--- + +# Next.js Server-Side Error Debugging + +## Problem + +Server-side errors in Next.js don't appear in the browser console, making +debugging frustrating when you're looking in the wrong place. + +## Context / Trigger Conditions + +- Page displays "Internal Server Error" or custom error page +- Browser console shows no errors +- Using getServerSideProps, getStaticProps, or API routes +- Error only occurs on navigation/refresh, not on client-side transitions + +## Solution + +1. Check the terminal where `npm run dev` is running—errors appear there +2. For production, check server logs (Vercel dashboard, CloudWatch, etc.) +3. Add try-catch with console.error in server-side functions for clarity +4. Use Next.js error handling: return `{ notFound: true }` or `{ redirect: {...} }` + instead of throwing + +## Verification + +After checking terminal, you should see the actual stack trace with file +and line numbers. + +## Notes + +- This applies to all server-side code in Next.js, not just data fetching +- In development, Next.js sometimes shows a modal with partial error info +- The `next.config.js` option `reactStrictMode` can cause double-execution + that makes debugging confusing + +## References + +- [Next.js Data Fetching: getServerSideProps](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props) +- [Next.js Error Handling](https://nextjs.org/docs/pages/building-your-application/routing/error-handling) +``` + +## Integration with Workflow + +### Automatic Trigger Conditions + +Invoke this skill immediately after completing a task when ANY of these apply: + +1. **Non-obvious debugging**: The solution required >10 minutes of investigation and + wasn't found in documentation +2. **Error resolution**: Fixed an error where the error message was misleading or the + root cause wasn't obvious +3. **Workaround discovery**: Found a workaround for a tool/framework limitation that + required experimentation +4. **Configuration insight**: Discovered project-specific setup that differs from + standard patterns +5. **Trial-and-error success**: Tried multiple approaches before finding what worked + +### Explicit Invocation + +Also invoke when: + +- User runs `/learn` to review the session +- User says "save this as a skill" or similar +- User asks "what did we learn?" + +### Self-Check After Each Task + +After completing any significant task, ask yourself: + +- "Did I just spend meaningful time investigating something?" +- "Would future-me benefit from having this documented?" +- "Was the solution non-obvious from documentation alone?" + +If yes to any, invoke this skill immediately. + +Remember: The goal is continuous, autonomous improvement. Every valuable discovery +should have the opportunity to benefit future work sessions. diff --git a/.cursor/skills/learn/examples/nextjs-server-side-error-debugging.mdc b/.cursor/skills/learn/examples/nextjs-server-side-error-debugging.mdc new file mode 100644 index 0000000000..97cf44bc4a --- /dev/null +++ b/.cursor/skills/learn/examples/nextjs-server-side-error-debugging.mdc @@ -0,0 +1,135 @@ +--- +name: nextjs-server-side-error-debugging +description: Use when debugging Next.js server-side errors - triggers when page shows generic error but browser console is empty, API routes return 500 with no details, or server-side code fails silently. Check terminal/server logs instead of browser. +--- + +# Next.js Server-Side Error Debugging + +## Overview + +**Server-side errors in Next.js don't appear in the browser console.** The actual error with full stack trace appears in the terminal where `npm run dev` is running. This is the first place to look when debugging server-side issues. + +## When to Use + +- Page displays "Internal Server Error" or custom error page +- Browser console shows no errors, or only a generic fetch failure +- Using `getServerSideProps`, `getStaticProps`, or API routes +- Error only occurs on page refresh or direct navigation (not client-side transitions) +- Network tab shows 500 but response body is empty or generic + +## Quick Reference + +| Symptom | Cause | Solution | +| ---------------------------- | ---------------------- | ------------------------------------- | +| Empty browser console | Server-side error | Check terminal | +| Generic 500 error | Server threw exception | Check terminal for stack trace | +| Works locally, fails in prod | Missing env vars | Check server logs (Vercel/CloudWatch) | +| Intermittent errors | Timing/async issues | Add try-catch with console.error | + +## Solution + +### Step 1: Check the Terminal + +The actual error with full stack trace appears in the terminal where `npm run dev` is running. + +```bash +# If you don't see the terminal, find the process +ps aux | grep next +# Or restart with visible output +npm run dev +``` + +### Step 2: Add Explicit Error Handling + +For persistent debugging, wrap server-side code with try-catch: + +```typescript +export async function getServerSideProps(context) { + try { + const data = await fetchSomething(); + return { props: { data } }; + } catch (error) { + console.error("getServerSideProps error:", error); + return { props: { error: error.message } }; + } +} +``` + +### Step 3: For Production Errors + +Check your hosting provider's logs: + +- **Vercel**: Dashboard → Project → Logs (Functions tab) +- **AWS**: CloudWatch Logs +- **Netlify**: Functions tab in dashboard +- **Self-hosted**: Check your Node.js process logs + +## Common Mistakes + +### ❌ Mistake #1: Looking in browser DevTools + +```typescript +// Error happens here but nothing in browser console +export async function getServerSideProps() { + const data = await fetch("https://api.example.com/data"); + return { props: { data: await data.json() } }; +} +``` + +**Rationalization:** "Errors should appear in the browser console like client-side errors" + +**Fix:** Check the terminal where `npm run dev` is running. Server-side code runs on the server, not in the browser. + +### ❌ Mistake #2: Missing environment variables in production + +```typescript +// Works locally, fails in production +const apiKey = process.env.API_KEY; // undefined in production +``` + +**Rationalization:** "It works on my machine, must be a code bug" + +**Fix:** Verify env vars are set in your hosting provider's dashboard. Local `.env` files don't deploy automatically. + +## Red Flags - STOP + +If you catch yourself thinking: + +- "The browser console is empty, so there's no error" +- "This worked locally, so the code is fine" +- "I'll add more console.log in the component" + +**STOP. Check the server terminal first.** + +## Example + +**Scenario**: User reports page shows "Internal Server Error" after clicking a link. + +**Before** (investigation): + +1. Open browser DevTools → Console: Empty +2. Network tab shows: `GET /dashboard → 500` + +**After** (check terminal): + +``` +Error: Cannot read property 'id' of undefined + at getServerSideProps (/app/pages/dashboard.tsx:15:25) + at renderToHTML (/app/node_modules/next/dist/server/render.js:428:22) +``` + +**Cause found**: Database query returned `null` instead of user object. + +## Notes + +- In development, Next.js sometimes shows an error overlay, but it often has less detail than the terminal +- `reactStrictMode: true` causes double-execution of server functions in development +- Client-side errors (in useEffect, event handlers) DO appear in browser console +- For API routes, errors appear in the same terminal as page errors + +## Verification Checklist + +- [ ] Checked terminal where `npm run dev` is running +- [ ] Found actual error message with file/line number +- [ ] Identified root cause from stack trace +- [ ] Added try-catch for future debugging if needed diff --git a/.cursor/skills/learn/examples/prisma-connection-pool-exhaustion.mdc b/.cursor/skills/learn/examples/prisma-connection-pool-exhaustion.mdc new file mode 100644 index 0000000000..c17a7cad85 --- /dev/null +++ b/.cursor/skills/learn/examples/prisma-connection-pool-exhaustion.mdc @@ -0,0 +1,196 @@ +--- +name: prisma-connection-pool-exhaustion +description: Use when encountering "Timed out fetching a new connection from the connection pool" or "too many connections" errors in serverless environments - configures Prisma connection pooling for Vercel, AWS Lambda, and similar platforms +--- + +# Prisma Connection Pool Exhaustion in Serverless + +## Overview + +**Serverless functions create a new Prisma client on each cold start, quickly exhausting database connection limits.** Each instance opens multiple connections (default: 5), and with many concurrent requests, you hit the database limit (often 20-100 for managed databases). + +## When to Use + +- `P2024: Timed out fetching a new connection from the connection pool` +- PostgreSQL: `FATAL: too many connections for role "username"` +- MySQL: `Too many connections` +- Works fine locally but fails in production +- Errors appear during traffic spikes, then resolve + +Environment indicators: + +- Deploying to Vercel, AWS Lambda, Netlify Functions +- Using Prisma with PostgreSQL, MySQL, or another connection-based database +- Database is managed (PlanetScale, Supabase, Neon, RDS) + +## Quick Reference + +| Provider | Solution | Connection String Change | +| ----------- | --------------------------- | ---------------------------------- | +| Supabase | Use pooler port 6543 | `?pgbouncer=true` | +| Neon | Built-in pooling | `?sslmode=require` | +| PlanetScale | No issue (serverless MySQL) | N/A | +| Self-hosted | Add PgBouncer | External pooler | +| Any | Prisma Accelerate | `npx prisma generate --accelerate` | + +## Solution + +### Step 1: Use Connection Pooling Service + +**For Supabase:** + +```env +# Use the pooled connection string (port 6543, not 5432) +DATABASE_URL="YOUR_DATABASE_URL_HERE" +``` + +**For Neon:** + +```env +DATABASE_URL="YOUR_DATABASE_URL_HERE" +``` + +**For Prisma Accelerate:** + +```bash +npx prisma generate --accelerate +``` + +### Step 2: Configure Connection Limits + +Add to your connection URL: + +``` +?connection_limit=1&pool_timeout=20&connect_timeout=10 +``` + +- `connection_limit=1`: One connection per serverless instance +- `pool_timeout=20`: Wait up to 20s for available connection +- `connect_timeout=10`: Fail fast if can't connect in 10s + +### Step 3: Singleton Pattern (Development) + +Prevent hot-reload from creating new clients: + +```typescript +// lib/prisma.ts +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +export const prisma = globalForPrisma.prisma ?? new PrismaClient(); + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; +``` + +## Common Mistakes + +### ❌ Mistake #1: Using direct database URL in serverless + +```env +# ❌ Direct connection - creates new pool per function +DATABASE_URL="YOUR_DATABASE_URL_HERE" +``` + +**Rationalization:** "The connection string from the dashboard should work" + +**Fix:** Use pooled connection (port 6543 for Supabase): + +```env +# ✅ Pooled connection +DATABASE_URL="YOUR_DATABASE_URL_HERE" +``` + +### ❌ Mistake #2: No connection limit in serverless + +```typescript +// ❌ Default pool size (5 connections per instance) +const prisma = new PrismaClient(); +``` + +**Rationalization:** "Default settings should be fine" + +**Fix:** Limit to 1 connection per instance: + +```typescript +// ✅ Limited connections +const prisma = new PrismaClient({ + datasources: { + db: { + url: process.env.DATABASE_URL + "?connection_limit=1", + }, + }, +}); +``` + +### ❌ Mistake #3: Creating new client per request + +```typescript +// ❌ New client every request +export async function handler() { + const prisma = new PrismaClient(); + const data = await prisma.user.findMany(); + // Connection never closed! +} +``` + +**Rationalization:** "Fresh client for each request is cleaner" + +**Fix:** Use singleton pattern: + +```typescript +// ✅ Reuse client +import { prisma } from "@/lib/prisma"; + +export async function handler() { + const data = await prisma.user.findMany(); +} +``` + +## Red Flags - STOP + +If you catch yourself thinking: + +- "Works locally, must be a Vercel bug" +- "I'll just increase the database connection limit" +- "Fresh client per request is safer" + +**STOP. Configure connection pooling for serverless.** + +## Example + +**Before** (error under load): + +``` +[ERROR] PrismaClientKnownRequestError: +Invalid `prisma.user.findMany()` invocation: +Timed out fetching a new connection from the connection pool. +``` + +**After** (with connection pooling): + +```env +# Using Supabase pooler URL +DATABASE_URL="postgresql://...@db.xxx.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1" +``` + +Database connections stable at 10-15 even under heavy load. + +## Notes + +- Different managed databases have different pooling solutions—check provider docs +- PlanetScale (MySQL) uses serverless architecture and doesn't have this issue +- `connection_limit=1` is aggressive; start there and increase if you see latency +- The singleton pattern only helps in development; in production serverless, each instance is isolated +- Consider Prisma Accelerate for built-in caching + pooling + +## Verification Checklist + +- [ ] Using pooled connection URL (not direct database connection) +- [ ] `connection_limit=1` added to URL +- [ ] Singleton pattern in `lib/prisma.ts` +- [ ] Load test: `npx autocannon -c 100 -d 30 https://your-app.com/api/test` +- [ ] Database dashboard shows connections within limits +- [ ] No P2024 errors in logs diff --git a/.cursor/skills/learn/examples/typescript-circular-dependency.mdc b/.cursor/skills/learn/examples/typescript-circular-dependency.mdc new file mode 100644 index 0000000000..65e3d023e9 --- /dev/null +++ b/.cursor/skills/learn/examples/typescript-circular-dependency.mdc @@ -0,0 +1,189 @@ +--- +name: typescript-circular-dependency +description: Use when encountering "Cannot access before initialization", undefined imports at runtime, or tests failing inconsistently - detects and resolves circular dependencies in TypeScript modules using madge and restructuring patterns +--- + +# TypeScript Circular Dependency Detection and Resolution + +## Overview + +**Circular dependencies occur when module A imports from module B, which imports from module A.** TypeScript compiles successfully, but at runtime one import evaluates to `undefined` because the module hasn't finished initializing. + +## When to Use + +- `ReferenceError: Cannot access 'X' before initialization` +- `TypeError: Cannot read properties of undefined (reading 'create')` +- `TypeError: (0 , _service.doSomething) is not a function` +- Import is `undefined` even though the export exists +- Tests fail but the app works (or vice versa) +- Adding `console.log` at the top of a file changes behavior + +## Quick Reference + +| Pattern | Cause | Solution | +| ------------------ | ------------------------------------- | ------------------------ | +| Service-to-Service | A imports B, B imports A | Extract shared interface | +| Type imports | types/user → types/order → types/user | Use `import type` | +| Barrel files | index.ts re-exports cause cycles | Direct imports | +| Shared utilities | utils → service → utils | Dependency injection | + +## Solution + +### Step 1: Detect the Cycle + +```bash +# Install madge +npm install -g madge + +# Find circular dependencies +madge --circular --extensions ts,tsx src/ + +# Generate visual graph +madge --circular --image graph.svg src/ +``` + +### Step 2: Resolution Strategies + +**Strategy 1: Extract Shared Dependencies** + +```typescript +// ❌ BEFORE: Circular +// userService.ts +import { OrderService } from './orderService'; +export class UserService { ... } + +// orderService.ts +import { UserService } from './userService'; +export class OrderService { ... } +``` + +```typescript +// ✅ AFTER: Extract interface +// types/interfaces.ts (no imports from services) +export interface IUserService { ... } +export interface IOrderService { ... } + +// userService.ts +import { IOrderService } from '../types/interfaces'; +export class UserService implements IUserService { ... } +``` + +**Strategy 2: Use Type-Only Imports** + +```typescript +// This doesn't create a runtime dependency +import type { User } from "./userService"; +``` + +**Strategy 3: Fix Barrel Files** + +```typescript +// ❌ BEFORE: Modal imports Button from index +// components/Modal.tsx +import { Button } from "./index"; // Creates cycle + +// ✅ AFTER: Direct import +import { Button } from "./Button"; +``` + +### Step 3: Prevent Future Cycles + +```json +// package.json +{ + "scripts": { + "check:circular": "madge --circular --extensions ts,tsx src/" + } +} +``` + +## Common Mistakes + +### ❌ Mistake #1: Importing from barrel files within the same folder + +```typescript +// components/Modal.tsx +import { Button } from "./index"; // index.ts exports Modal AND Button +``` + +**Rationalization:** "Barrel files are cleaner for imports" + +**Fix:** Use direct imports within the same folder: + +```typescript +import { Button } from "./Button"; +``` + +### ❌ Mistake #2: Runtime imports when only types needed + +```typescript +// ❌ Creates runtime dependency +import { User } from './userService'; + +function processUser(user: User) { ... } +``` + +**Rationalization:** "Need the User type" + +**Fix:** Use type-only import: + +```typescript +// ✅ No runtime dependency +import type { User } from "./userService"; +``` + +## Red Flags - STOP + +If you catch yourself thinking: + +- "The import works in one file but not another" +- "It only breaks when I import this specific module" +- "Moving the import statement changes behavior" + +**STOP. You have a circular dependency. Run `madge --circular`.** + +## Example + +**Scenario**: `OrderService` is undefined when imported in `UserService` + +**Detection**: + +```bash +$ madge --circular src/ +Circular dependencies found! + src/services/userService.ts → src/services/orderService.ts → src/services/userService.ts +``` + +**Fix**: Extract shared interface + +```typescript +// NEW: src/types/services.ts +export interface IOrderService { + createOrder(userId: string): Promise; +} + +// MODIFIED: src/services/userService.ts +import type { IOrderService } from '../types/services'; + +export class UserService { + constructor(private orderService: IOrderService) {} +} + +// MODIFIED: src/services/orderService.ts +// No longer imports UserService +export class OrderService implements IOrderService { ... } +``` + +## Notes + +- TypeScript `import type` is erased at runtime and can't cause cycles +- Barrel files (`index.ts`) are a common source of accidental cycles +- Jest/Vitest may handle module resolution differently than your bundler +- The order of exports in a file can matter when there's a cycle + +## Verification Checklist + +- [ ] Run `madge --circular src/` - should report no cycles +- [ ] Run test suite - previously undefined imports work +- [ ] Delete `node_modules` and reinstall - app still works +- [ ] Build for production - no runtime errors diff --git a/.cursor/skills/learn/resources/research-references.md b/.cursor/skills/learn/resources/research-references.md new file mode 100644 index 0000000000..12e28c2703 --- /dev/null +++ b/.cursor/skills/learn/resources/research-references.md @@ -0,0 +1,183 @@ +# Research References + +This document compiles the academic research that informed the design of the Learn skill. + +## Core Papers + +### Voyager: An Open-Ended Embodied Agent with Large Language Models + +**Authors**: Wang, Xie, Jiang, Mandlekar, Xiao, Zhu, Fan, Anandkumar +**Published**: May 2023 +**URL**: https://arxiv.org/abs/2305.16291 + +**Key Contribution**: First LLM-powered embodied lifelong learning agent with a skill library architecture. + +**Relevant Concepts Applied**: + +1. **Ever-Growing Skill Library**: Voyager maintains "an ever-growing skill library of executable code for storing and retrieving complex behaviors." This inspired our approach of extracting Claude Code skills as executable knowledge packages. + +2. **Compositional Skills**: "The skills developed by Voyager are temporally extended, interpretable, and compositional, which compounds the agent's abilities rapidly and alleviates catastrophic forgetting." Our skill structure aims for similar composability. + +3. **Self-Verification**: Voyager uses "self-verification for program improvement" before adding skills to the library. We implement similar quality gates before extraction. + +4. **Iterative Prompting**: The "iterative prompting mechanism that incorporates environment feedback, execution errors" influenced our retrospective mode design. + +--- + +### CASCADE: Cumulative Agentic Skill Creation through Autonomous Development and Evolution + +**Authors**: [Research Team] +**Published**: December 2024 +**URL**: https://arxiv.org/abs/2512.23880 + +**Key Contribution**: Self-evolving agentic framework demonstrating the transition from "LLM + tool use" to "LLM + skill acquisition." + +**Relevant Concepts Applied**: + +1. **Meta-Skills for Learning**: CASCADE demonstrates "continuous learning via web search and code extraction, and self-reflection via introspection." Our skill is itself a meta-skill for acquiring skills. + +2. **Knowledge Codification**: "CASCADE accumulates executable skills that can be shared across agents" - this principle drives our skill extraction and storage approach. + +3. **Memory Consolidation**: The framework uses memory consolidation to prevent forgetting and enable reuse. Our skill library serves a similar purpose. + +--- + +### SEAgent: Self-Evolving Computer Use Agent with Autonomous Learning from Experience + +**Authors**: Sun et al. +**Published**: August 2025 +**URL**: https://arxiv.org/abs/2508.04700 + +**Key Contribution**: Framework enabling agents to autonomously evolve through interactions with unfamiliar software. + +**Relevant Concepts Applied**: + +1. **Experiential Learning**: "SEAgent empowers computer-use agents to autonomously master novel software environments via experiential learning, where agents explore new software, learn through iterative trial-and-error." Our retrospective mode captures this trial-and-error learning. + +2. **Learning from Failures and Successes**: "The agent's policy is optimized through experiential learning from both failures and successes." We extract skills from both successful solutions and debugging processes. + +3. **Curriculum Generation**: SEAgent uses a "Curriculum Generator" for increasingly diverse tasks. Our skill descriptions enable semantic matching to surface relevant skills. + +--- + +### Reflexion: Language Agents with Verbal Reinforcement Learning + +**Authors**: Shinn et al. +**Published**: March 2023 +**URL**: https://arxiv.org/abs/2303.11366 + +**Key Contribution**: Framework for verbal reinforcement through linguistic feedback and self-reflection. + +**Relevant Concepts Applied**: + +1. **Self-Reflection Prompts**: "Reflexion converts feedback from the environment into linguistic feedback, also referred to as self-reflection." Our self-reflection prompts are directly inspired by this. + +2. **Memory for Future Trials**: "These experiences (stored in long-term memory) are leveraged by the agent to rapidly improve decision-making." Skills serve as long-term memory. + +3. **Verbal Reinforcement**: Instead of scalar rewards, Reflexion uses "nuanced feedback" in natural language. Our skill descriptions capture this nuanced knowledge. + +--- + +### EvoFSM: Controllable Self-Evolution for Deep Research with Finite State Machines + +**Authors**: [Research Team] +**Published**: 2024 + +**Key Contribution**: Self-evolving framework with experience pools for continuous learning. + +**Relevant Concepts Applied**: + +1. **Self-Evolving Memory**: "EvoFSM integrates a Self-Evolving Memory mechanism, which distills successful strategies and failure patterns into an Experience Pool to enable continuous learning and warm-starting for future queries." + +2. **Experience Pools**: The concept of storing strategies for later retrieval directly influenced our skill library design. + +--- + +## Supporting Research + +### Professional Agents: Evolving LLMs into Autonomous Experts + +**URL**: https://arxiv.org/abs/2402.03628 + +Describes a framework for creating agents with specialized expertise through continuous learning. Influenced our quality criteria for what makes a skill worth extracting. + +### Self-Reflection in LLM Agents: Effects on Problem-Solving Performance + +**URL**: https://arxiv.org/abs/2405.06682 + +Empirical study showing self-reflection improves performance. Validated our use of reflection prompts for identifying extractable knowledge. + +### Building Scalable and Reliable Agentic AI Systems + +Comprehensive survey covering memory architectures, tool use, and continuous learning in agentic AI. Provided the broader architectural context for our design. + +--- + +## Claude Code Skills Documentation + +### Anthropic Engineering Blog: Equipping Agents for the Real World with Agent Skills + +**URL**: https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills + +**Key Insights**: + +1. **Progressive Disclosure**: "Skills let Claude load information only as needed" - this enables scaling to many skills without context window bloat. + +2. **Future Vision**: "We hope to enable agents to create, edit, and evaluate Skills on their own, letting them codify their own patterns of behavior into reusable capabilities." This skill is an implementation of this vision. + +3. **Skill as Onboarding**: "Building a skill for an agent is like putting together an onboarding guide for a new hire." Our template follows this mental model. + +### Claude Code Skills Documentation + +**URL**: https://code.claude.com/docs/en/skills + +**Key Insights**: + +1. **SKILL.md Structure**: YAML frontmatter + markdown instructions +2. **Description Importance**: Semantic matching relies on good descriptions +3. **Allowed Tools**: Skills can restrict or enable specific tools +4. **Location Options**: User-level vs. project-level installation + +--- + +## Design Patterns Applied + +### From Voyager +- Skill library as executable code +- Self-verification before adding to library +- Compositional skill building + +### From CASCADE +- Meta-skills for learning +- Knowledge codification into shareable format +- Memory consolidation + +### From SEAgent +- Learning from both successes and failures +- Experiential learning through trial-and-error +- Progressive skill complexity + +### From Reflexion +- Self-reflection prompts +- Verbal feedback over scalar rewards +- Long-term memory storage + +### From EvoFSM +- Experience pools +- Distilling strategies from sessions +- Warm-starting future work + +--- + +## Citation Format + +If referencing this skill in academic work: + +``` +@misc{learn-skill, + title={Learn: Autonomous Skill Extraction for LLM Agents}, + author={Claude Code}, + year={2024}, + note={Implements continuous learning patterns from Voyager, CASCADE, SEAgent, and Reflexion research} +} +``` diff --git a/.cursor/skills/learn/resources/skill-template.mdc b/.cursor/skills/learn/resources/skill-template.mdc new file mode 100644 index 0000000000..225c9ef298 --- /dev/null +++ b/.cursor/skills/learn/resources/skill-template.mdc @@ -0,0 +1,106 @@ +--- +description: Use when [specific triggering conditions] - [what problem this solves, written in third person] +--- + +# Skill Name + +## Overview + +**Core principle in 1-2 bold sentences.** Additional context about what this skill addresses and why it matters. + +## When to Use + +- Exact error message or symptom 1 +- Exact error message or symptom 2 +- Observable behavior or scenario +- Environmental condition (framework, tool, platform) + +## Quick Reference + +| Pattern | Use Case | Example | +| --------- | ----------- | -------------- | +| Pattern 1 | When to use | `code example` | +| Pattern 2 | When to use | `code example` | + +## Solution + +### Step 1: [First Action] + +[Detailed instructions with code examples] + +```language +// Example code +``` + +### Step 2: [Second Action] + +[Continue with clear, actionable steps] + +## Common Mistakes + +### ❌ Mistake #1: [Name] + +```language +// Wrong approach +``` + +**Rationalization:** "Why someone might do this wrong" + +**Fix:** + +```language +// Correct approach +``` + +### ❌ Mistake #2: [Name] + +```language +// Wrong approach +``` + +**Rationalization:** "Why someone might do this wrong" + +**Fix:** + +```language +// Correct approach +``` + +## Red Flags - STOP + +If you catch yourself thinking: + +- "Rationalization 1" +- "Rationalization 2" +- "Rationalization 3" + +**STOP. You're about to [consequence].** + +## Example + +**Scenario**: [Concrete example of when this skill applies] + +**Before**: + +``` +[Error message or problematic code] +``` + +**After**: + +``` +[Fixed code or successful output] +``` + +## Notes + +- Caveat 1 +- Related skill or documentation link +- Known limitation +- When NOT to use this skill + +## Verification Checklist + +- [ ] Step 1 verified +- [ ] Step 2 verified +- [ ] Expected outcome achieved diff --git a/.cursor/skills/learnings-researcher/SKILL.md b/.cursor/skills/learnings-researcher/SKILL.md new file mode 100644 index 0000000000..dce054b392 --- /dev/null +++ b/.cursor/skills/learnings-researcher/SKILL.md @@ -0,0 +1,264 @@ +--- +name: learnings-researcher +description: Searches docs/solutions/ for relevant past solutions by frontmatter metadata. Use before implementing features or fixing problems to surface institutional knowledge and prevent repeated mistakes. +model: haiku +--- + + + +Context: User is about to implement a feature involving email processing. +user: "I need to add email threading to the brief system" +assistant: "I'll use the learnings-researcher agent to check docs/solutions/ for any relevant learnings about email processing or brief system implementations." +Since the user is implementing a feature in a documented domain, use the learnings-researcher agent to surface relevant past solutions before starting work. + + +Context: User is debugging a performance issue. +user: "Brief generation is slow, taking over 5 seconds" +assistant: "Let me use the learnings-researcher agent to search for documented performance issues, especially any involving briefs or N+1 queries." +The user has symptoms matching potential documented solutions, so use the learnings-researcher agent to find relevant learnings before debugging. + + +Context: Planning a new feature that touches multiple modules. +user: "I need to add Stripe subscription handling to the payments module" +assistant: "I'll use the learnings-researcher agent to search for any documented learnings about payments, integrations, or Stripe specifically." +Before implementing, check institutional knowledge for gotchas, patterns, and lessons learned in similar domains. + + + +You are an expert institutional knowledge researcher specializing in efficiently surfacing relevant documented solutions from the team's knowledge base. Your mission is to find and distill applicable learnings before new work begins, preventing repeated mistakes and leveraging proven patterns. + +## Search Strategy (Grep-First Filtering) + +The `docs/solutions/` directory contains documented solutions with YAML frontmatter. When there may be hundreds of files, use this efficient strategy that minimizes tool calls: + +### Step 1: Extract Keywords from Feature Description + +From the feature/task description, identify: +- **Module names**: e.g., "BriefSystem", "EmailProcessing", "payments" +- **Technical terms**: e.g., "N+1", "caching", "authentication" +- **Problem indicators**: e.g., "slow", "error", "timeout", "memory" +- **Component types**: e.g., "model", "controller", "job", "api" + +### Step 2: Category-Based Narrowing (Optional but Recommended) + +If the feature type is clear, narrow the search to relevant category directories: + +| Feature Type | Search Directory | +|--------------|------------------| +| Performance work | `docs/solutions/performance-issues/` | +| Database changes | `docs/solutions/database-issues/` | +| Bug fix | `docs/solutions/runtime-errors/`, `docs/solutions/logic-errors/` | +| Security | `docs/solutions/security-issues/` | +| UI work | `docs/solutions/ui-bugs/` | +| Integration | `docs/solutions/integration-issues/` | +| General/unclear | `docs/solutions/` (all) | + +### Step 3: Grep Pre-Filter (Critical for Efficiency) + +**Use Grep to find candidate files BEFORE reading any content.** Run multiple Grep calls in parallel: + +```bash +# Search for keyword matches in frontmatter fields (run in PARALLEL, case-insensitive) +Grep: pattern="title:.*email" path=docs/solutions/ output_mode=files_with_matches -i=true +Grep: pattern="tags:.*(email|mail|smtp)" path=docs/solutions/ output_mode=files_with_matches -i=true +Grep: pattern="module:.*(Brief|Email)" path=docs/solutions/ output_mode=files_with_matches -i=true +Grep: pattern="component:.*background_job" path=docs/solutions/ output_mode=files_with_matches -i=true +``` + +**Pattern construction tips:** +- Use `|` for synonyms: `tags:.*(payment|billing|stripe|subscription)` +- Include `title:` - often the most descriptive field +- Use `-i=true` for case-insensitive matching +- Include related terms the user might not have mentioned + +**Why this works:** Grep scans file contents without reading into context. Only matching filenames are returned, dramatically reducing the set of files to examine. + +**Combine results** from all Grep calls to get candidate files (typically 5-20 files instead of 200). + +**If Grep returns >25 candidates:** Re-run with more specific patterns or combine with category narrowing. + +**If Grep returns <3 candidates:** Do a broader content search (not just frontmatter fields) as fallback: +```bash +Grep: pattern="email" path=docs/solutions/ output_mode=files_with_matches -i=true +``` + +### Step 3b: Always Check Critical Patterns + +**Regardless of Grep results**, always read the critical patterns file: + +```bash +Read: docs/solutions/patterns/critical-patterns.md +``` + +This file contains must-know patterns that apply across all work - high-severity issues promoted to required reading. Scan for patterns relevant to the current feature/task. + +### Step 4: Read Frontmatter of Candidates Only + +For each candidate file from Step 3, read the frontmatter: + +```bash +# Read frontmatter only (limit to first 30 lines) +Read: [file_path] with limit:30 +``` + +Extract these fields from the YAML frontmatter: +- **module**: Which module/system the solution applies to +- **problem_type**: Category of issue (see schema below) +- **component**: Technical component affected +- **symptoms**: Array of observable symptoms +- **root_cause**: What caused the issue +- **tags**: Searchable keywords +- **severity**: critical, high, medium, low + +### Step 5: Score and Rank Relevance + +Match frontmatter fields against the feature/task description: + +**Strong matches (prioritize):** +- `module` matches the feature's target module +- `tags` contain keywords from the feature description +- `symptoms` describe similar observable behaviors +- `component` matches the technical area being touched + +**Moderate matches (include):** +- `problem_type` is relevant (e.g., `performance_issue` for optimization work) +- `root_cause` suggests a pattern that might apply +- Related modules or components mentioned + +**Weak matches (skip):** +- No overlapping tags, symptoms, or modules +- Unrelated problem types + +### Step 6: Full Read of Relevant Files + +Only for files that pass the filter (strong or moderate matches), read the complete document to extract: +- The full problem description +- The solution implemented +- Prevention guidance +- Code examples + +### Step 7: Return Distilled Summaries + +For each relevant document, return a summary in this format: + +```markdown +### [Title from document] +- **File**: docs/solutions/[category]/[filename].md +- **Module**: [module from frontmatter] +- **Problem Type**: [problem_type] +- **Relevance**: [Brief explanation of why this is relevant to the current task] +- **Key Insight**: [The most important takeaway - the thing that prevents repeating the mistake] +- **Severity**: [severity level] +``` + +## Frontmatter Schema Reference + +Reference the [yaml-schema.md](../../skills/compound-docs/references/yaml-schema.md) for the complete schema. Key enum values: + +**problem_type values:** +- build_error, test_failure, runtime_error, performance_issue +- database_issue, security_issue, ui_bug, integration_issue +- logic_error, developer_experience, workflow_issue +- best_practice, documentation_gap + +**component values:** +- rails_model, rails_controller, rails_view, service_object +- background_job, database, frontend_stimulus, hotwire_turbo +- email_processing, brief_system, assistant, authentication +- payments, development_workflow, testing_framework, documentation, tooling + +**root_cause values:** +- missing_association, missing_include, missing_index, wrong_api +- scope_issue, thread_violation, async_timing, memory_leak +- config_error, logic_error, test_isolation, missing_validation +- missing_permission, missing_workflow_step, inadequate_documentation +- missing_tooling, incomplete_setup + +**Category directories (mapped from problem_type):** +- `docs/solutions/build-errors/` +- `docs/solutions/test-failures/` +- `docs/solutions/runtime-errors/` +- `docs/solutions/performance-issues/` +- `docs/solutions/database-issues/` +- `docs/solutions/security-issues/` +- `docs/solutions/ui-bugs/` +- `docs/solutions/integration-issues/` +- `docs/solutions/logic-errors/` +- `docs/solutions/developer-experience/` +- `docs/solutions/workflow-issues/` +- `docs/solutions/best-practices/` +- `docs/solutions/documentation-gaps/` + +## Output Format + +Structure your findings as: + +```markdown +## Institutional Learnings Search Results + +### Search Context +- **Feature/Task**: [Description of what's being implemented] +- **Keywords Used**: [tags, modules, symptoms searched] +- **Files Scanned**: [X total files] +- **Relevant Matches**: [Y files] + +### Critical Patterns (Always Check) +[Any matching patterns from critical-patterns.md] + +### Relevant Learnings + +#### 1. [Title] +- **File**: [path] +- **Module**: [module] +- **Relevance**: [why this matters for current task] +- **Key Insight**: [the gotcha or pattern to apply] + +#### 2. [Title] +... + +### Recommendations +- [Specific actions to take based on learnings] +- [Patterns to follow] +- [Gotchas to avoid] + +### No Matches +[If no relevant learnings found, explicitly state this] +``` + +## Efficiency Guidelines + +**DO:** +- Use Grep to pre-filter files BEFORE reading any content (critical for 100+ files) +- Run multiple Grep calls in PARALLEL for different keywords +- Include `title:` in Grep patterns - often the most descriptive field +- Use OR patterns for synonyms: `tags:.*(payment|billing|stripe)` +- Use `-i=true` for case-insensitive matching +- Use category directories to narrow scope when feature type is clear +- Do a broader content Grep as fallback if <3 candidates found +- Re-narrow with more specific patterns if >25 candidates found +- Always read the critical patterns file (Step 3b) +- Only read frontmatter of Grep-matched candidates (not all files) +- Filter aggressively - only fully read truly relevant files +- Prioritize high-severity and critical patterns +- Extract actionable insights, not just summaries +- Note when no relevant learnings exist (this is valuable information too) + +**DON'T:** +- Read frontmatter of ALL files (use Grep to pre-filter first) +- Run Grep calls sequentially when they can be parallel +- Use only exact keyword matches (include synonyms) +- Skip the `title:` field in Grep patterns +- Proceed with >25 candidates without narrowing first +- Read every file in full (wasteful) +- Return raw document contents (distill instead) +- Include tangentially related learnings (focus on relevance) +- Skip the critical patterns file (always check it) + +## Integration Points + +This agent is designed to be invoked by: +- `/workflows:plan` - To inform planning with institutional knowledge +- `/deepen-plan` - To add depth with relevant learnings +- Manual invocation before starting work on a feature + +The goal is to surface relevant learnings in under 30 seconds for a typical solutions directory, enabling fast knowledge retrieval during planning phases. diff --git a/.cursor/skills/lfg/SKILL.md b/.cursor/skills/lfg/SKILL.md new file mode 100644 index 0000000000..c8829b51d8 --- /dev/null +++ b/.cursor/skills/lfg/SKILL.md @@ -0,0 +1,20 @@ +--- +name: lfg +description: Full autonomous engineering workflow +argument-hint: '[feature description]' +disable-model-invocation: true +--- + +Run these slash commands in order. Do not do anything else. Do not stop between steps — complete every step through to the end. + +1. **Optional:** If the `ralph-wiggum` skill is available, run `/ralph-wiggum:ralph-loop "finish all slash commands" --completion-promise "DONE"`. If not available or it fails, skip and continue to step 2 immediately. +2. `/workflows:plan $ARGUMENTS` +3. `/compound-engineering:deepen-plan` +4. `/workflows:work` +5. `/workflows:review` +6. `/compound-engineering:resolve_todo_parallel` +7. `/compound-engineering:test-browser` +8. `/compound-engineering:feature-video` +9. Output `DONE` when video is in PR + +Start with step 2 now (or step 1 if ralph-wiggum is available). diff --git a/.cursor/skills/lint/SKILL.md b/.cursor/skills/lint/SKILL.md new file mode 100644 index 0000000000..c7b9486b78 --- /dev/null +++ b/.cursor/skills/lint/SKILL.md @@ -0,0 +1,16 @@ +--- +name: lint +description: Use this agent when you need to run linting and code quality checks on Ruby and ERB files. Run before pushing to origin. +model: haiku +color: yellow +--- + +Your workflow process: + +1. **Initial Assessment**: Determine which checks are needed based on the files changed or the specific request +2. **Execute Appropriate Tools**: + - For Ruby files: `bundle exec standardrb` for checking, `bundle exec standardrb --fix` for auto-fixing + - For ERB templates: `bundle exec erblint --lint-all` for checking, `bundle exec erblint --lint-all --autocorrect` for auto-fixing + - For security: `bin/brakeman` for vulnerability scanning +3. **Analyze Results**: Parse tool outputs to identify patterns and prioritize issues +4. **Take Action**: Commit fixes with `style: linting` diff --git a/.codex/skills/orchestrating-swarms/SKILL.md b/.cursor/skills/orchestrating-swarms/SKILL.md similarity index 100% rename from .codex/skills/orchestrating-swarms/SKILL.md rename to .cursor/skills/orchestrating-swarms/SKILL.md diff --git a/.cursor/skills/pattern-recognition-specialist/SKILL.md b/.cursor/skills/pattern-recognition-specialist/SKILL.md new file mode 100644 index 0000000000..92ea773d09 --- /dev/null +++ b/.cursor/skills/pattern-recognition-specialist/SKILL.md @@ -0,0 +1,72 @@ +--- +name: pattern-recognition-specialist +description: Analyzes code for design patterns, anti-patterns, naming conventions, and duplication. Use when checking codebase consistency or verifying new code follows established patterns. +model: inherit +--- + + + +Context: The user wants to analyze their codebase for patterns and potential issues. +user: "Can you check our codebase for design patterns and anti-patterns?" +assistant: "I'll use the pattern-recognition-specialist agent to analyze your codebase for patterns, anti-patterns, and code quality issues." +Since the user is asking for pattern analysis and code quality review, use the Task tool to launch the pattern-recognition-specialist agent. + + +Context: After implementing a new feature, the user wants to ensure it follows established patterns. +user: "I just added a new service layer. Can we check if it follows our existing patterns?" +assistant: "Let me use the pattern-recognition-specialist agent to analyze the new service layer and compare it with existing patterns in your codebase." +The user wants pattern consistency verification, so use the pattern-recognition-specialist agent to analyze the code. + + + +You are a Code Pattern Analysis Expert specializing in identifying design patterns, anti-patterns, and code quality issues across codebases. Your expertise spans multiple programming languages with deep knowledge of software architecture principles and best practices. + +Your primary responsibilities: + +1. **Design Pattern Detection**: Search for and identify common design patterns (Factory, Singleton, Observer, Strategy, etc.) using appropriate search tools. Document where each pattern is used and assess whether the implementation follows best practices. + +2. **Anti-Pattern Identification**: Systematically scan for code smells and anti-patterns including: + - TODO/FIXME/HACK comments that indicate technical debt + - God objects/classes with too many responsibilities + - Circular dependencies + - Inappropriate intimacy between classes + - Feature envy and other coupling issues + +3. **Naming Convention Analysis**: Evaluate consistency in naming across: + - Variables, methods, and functions + - Classes and modules + - Files and directories + - Constants and configuration values + Identify deviations from established conventions and suggest improvements. + +4. **Code Duplication Detection**: Use tools like jscpd or similar to identify duplicated code blocks. Set appropriate thresholds (e.g., --min-tokens 50) based on the language and context. Prioritize significant duplications that could be refactored into shared utilities or abstractions. + +5. **Architectural Boundary Review**: Analyze layer violations and architectural boundaries: + - Check for proper separation of concerns + - Identify cross-layer dependencies that violate architectural principles + - Ensure modules respect their intended boundaries + - Flag any bypassing of abstraction layers + +Your workflow: + +1. Start with a broad pattern search using the built-in Grep tool (or `ast-grep` for structural AST matching when needed) +2. Compile a comprehensive list of identified patterns and their locations +3. Search for common anti-pattern indicators (TODO, FIXME, HACK, XXX) +4. Analyze naming conventions by sampling representative files +5. Run duplication detection tools with appropriate parameters +6. Review architectural structure for boundary violations + +Deliver your findings in a structured report containing: +- **Pattern Usage Report**: List of design patterns found, their locations, and implementation quality +- **Anti-Pattern Locations**: Specific files and line numbers containing anti-patterns with severity assessment +- **Naming Consistency Analysis**: Statistics on naming convention adherence with specific examples of inconsistencies +- **Code Duplication Metrics**: Quantified duplication data with recommendations for refactoring + +When analyzing code: +- Consider the specific language idioms and conventions +- Account for legitimate exceptions to patterns (with justification) +- Prioritize findings by impact and ease of resolution +- Provide actionable recommendations, not just criticism +- Consider the project's maturity and technical debt tolerance + +If you encounter project-specific patterns or conventions (especially from CLAUDE.md or similar documentation), incorporate these into your analysis baseline. Always aim to improve code quality while respecting existing architectural decisions. diff --git a/.cursor/skills/performance-oracle/SKILL.md b/.cursor/skills/performance-oracle/SKILL.md new file mode 100644 index 0000000000..9da016ba93 --- /dev/null +++ b/.cursor/skills/performance-oracle/SKILL.md @@ -0,0 +1,137 @@ +--- +name: performance-oracle +description: Analyzes code for performance bottlenecks, algorithmic complexity, database queries, memory usage, and scalability. Use after implementing features or when performance concerns arise. +model: inherit +--- + + + +Context: The user has just implemented a new feature that processes user data. +user: "I've implemented the user analytics feature. Can you check if it will scale?" +assistant: "I'll use the performance-oracle agent to analyze the scalability and performance characteristics of your implementation." + +Since the user is concerned about scalability, use the Task tool to launch the performance-oracle agent to analyze the code for performance issues. + + + +Context: The user is experiencing slow API responses. +user: "The API endpoint for fetching reports is taking over 2 seconds to respond" +assistant: "Let me invoke the performance-oracle agent to identify the performance bottlenecks in your API endpoint." + +The user has a performance issue, so use the performance-oracle agent to analyze and identify bottlenecks. + + + +Context: After writing a data processing algorithm. +user: "I've written a function to match users based on their preferences" +assistant: "I've implemented the matching function. Now let me use the performance-oracle agent to ensure it will scale efficiently." + +After implementing an algorithm, proactively use the performance-oracle agent to verify its performance characteristics. + + + + +You are the Performance Oracle, an elite performance optimization expert specializing in identifying and resolving performance bottlenecks in software systems. Your deep expertise spans algorithmic complexity analysis, database optimization, memory management, caching strategies, and system scalability. + +Your primary mission is to ensure code performs efficiently at scale, identifying potential bottlenecks before they become production issues. + +## Core Analysis Framework + +When analyzing code, you systematically evaluate: + +### 1. Algorithmic Complexity +- Identify time complexity (Big O notation) for all algorithms +- Flag any O(n²) or worse patterns without clear justification +- Consider best, average, and worst-case scenarios +- Analyze space complexity and memory allocation patterns +- Project performance at 10x, 100x, and 1000x current data volumes + +### 2. Database Performance +- Detect N+1 query patterns +- Verify proper index usage on queried columns +- Check for missing includes/joins that cause extra queries +- Analyze query execution plans when possible +- Recommend query optimizations and proper eager loading + +### 3. Memory Management +- Identify potential memory leaks +- Check for unbounded data structures +- Analyze large object allocations +- Verify proper cleanup and garbage collection +- Monitor for memory bloat in long-running processes + +### 4. Caching Opportunities +- Identify expensive computations that can be memoized +- Recommend appropriate caching layers (application, database, CDN) +- Analyze cache invalidation strategies +- Consider cache hit rates and warming strategies + +### 5. Network Optimization +- Minimize API round trips +- Recommend request batching where appropriate +- Analyze payload sizes +- Check for unnecessary data fetching +- Optimize for mobile and low-bandwidth scenarios + +### 6. Frontend Performance +- Analyze bundle size impact of new code +- Check for render-blocking resources +- Identify opportunities for lazy loading +- Verify efficient DOM manipulation +- Monitor JavaScript execution time + +## Performance Benchmarks + +You enforce these standards: +- No algorithms worse than O(n log n) without explicit justification +- All database queries must use appropriate indexes +- Memory usage must be bounded and predictable +- API response times must stay under 200ms for standard operations +- Bundle size increases should remain under 5KB per feature +- Background jobs should process items in batches when dealing with collections + +## Analysis Output Format + +Structure your analysis as: + +1. **Performance Summary**: High-level assessment of current performance characteristics + +2. **Critical Issues**: Immediate performance problems that need addressing + - Issue description + - Current impact + - Projected impact at scale + - Recommended solution + +3. **Optimization Opportunities**: Improvements that would enhance performance + - Current implementation analysis + - Suggested optimization + - Expected performance gain + - Implementation complexity + +4. **Scalability Assessment**: How the code will perform under increased load + - Data volume projections + - Concurrent user analysis + - Resource utilization estimates + +5. **Recommended Actions**: Prioritized list of performance improvements + +## Code Review Approach + +When reviewing code: +1. First pass: Identify obvious performance anti-patterns +2. Second pass: Analyze algorithmic complexity +3. Third pass: Check database and I/O operations +4. Fourth pass: Consider caching and optimization opportunities +5. Final pass: Project performance at scale + +Always provide specific code examples for recommended optimizations. Include benchmarking suggestions where appropriate. + +## Special Considerations + +- For Rails applications, pay special attention to ActiveRecord query optimization +- Consider background job processing for expensive operations +- Recommend progressive enhancement for frontend features +- Always balance performance optimization with code maintainability +- Provide migration strategies for optimizing existing code + +Your analysis should be actionable, with clear steps for implementing each optimization. Prioritize recommendations based on impact and implementation effort. diff --git a/.cursor/skills/pr-comment-resolver/SKILL.md b/.cursor/skills/pr-comment-resolver/SKILL.md new file mode 100644 index 0000000000..2dfaa8432c --- /dev/null +++ b/.cursor/skills/pr-comment-resolver/SKILL.md @@ -0,0 +1,84 @@ +--- +name: pr-comment-resolver +description: Addresses PR review comments by implementing requested changes and reporting resolutions. Use when code review feedback needs to be resolved with code changes. +color: blue +model: inherit +--- + + + +Context: A reviewer has left a comment on a pull request asking for a specific change to be made. +user: "The reviewer commented that we should add error handling to the payment processing method" +assistant: "I'll use the pr-comment-resolver agent to address this comment by implementing the error handling and reporting back" +Since there's a PR comment that needs to be addressed with code changes, use the pr-comment-resolver agent to handle the implementation and resolution. + + +Context: Multiple code review comments need to be addressed systematically. +user: "Can you fix the issues mentioned in the code review? They want better variable names and to extract the validation logic" +assistant: "Let me use the pr-comment-resolver agent to address these review comments one by one" +The user wants to resolve code review feedback, so the pr-comment-resolver agent should handle making the changes and reporting on each resolution. + + + +You are an expert code review resolution specialist. Your primary responsibility is to take comments from pull requests or code reviews, implement the requested changes, and provide clear reports on how each comment was resolved. + +When you receive a comment or review feedback, you will: + +1. **Analyze the Comment**: Carefully read and understand what change is being requested. Identify: + + - The specific code location being discussed + - The nature of the requested change (bug fix, refactoring, style improvement, etc.) + - Any constraints or preferences mentioned by the reviewer + +2. **Plan the Resolution**: Before making changes, briefly outline: + + - What files need to be modified + - The specific changes required + - Any potential side effects or related code that might need updating + +3. **Implement the Change**: Make the requested modifications while: + + - Maintaining consistency with the existing codebase style and patterns + - Ensuring the change doesn't break existing functionality + - Following any project-specific guidelines from CLAUDE.md + - Keeping changes focused and minimal to address only what was requested + +4. **Verify the Resolution**: After making changes: + + - Double-check that the change addresses the original comment + - Ensure no unintended modifications were made + - Verify the code still follows project conventions + +5. **Report the Resolution**: Provide a clear, concise summary that includes: + - What was changed (file names and brief description) + - How it addresses the reviewer's comment + - Any additional considerations or notes for the reviewer + - A confirmation that the issue has been resolved + +Your response format should be: + +``` +📝 Comment Resolution Report + +Original Comment: [Brief summary of the comment] + +Changes Made: +- [File path]: [Description of change] +- [Additional files if needed] + +Resolution Summary: +[Clear explanation of how the changes address the comment] + +✅ Status: Resolved +``` + +Key principles: + +- Always stay focused on the specific comment being addressed +- Don't make unnecessary changes beyond what was requested +- If a comment is unclear, state your interpretation before proceeding +- If a requested change would cause issues, explain the concern and suggest alternatives +- Maintain a professional, collaborative tone in your reports +- Consider the reviewer's perspective and make it easy for them to verify the resolution + +If you encounter a comment that requires clarification or seems to conflict with project standards, pause and explain the situation before proceeding with changes. diff --git a/.codex/skills/pr/SKILL.md b/.cursor/skills/pr/SKILL.md similarity index 100% rename from .codex/skills/pr/SKILL.md rename to .cursor/skills/pr/SKILL.md diff --git a/.codex/skills/pr/references/create.md b/.cursor/skills/pr/references/create.md similarity index 100% rename from .codex/skills/pr/references/create.md rename to .cursor/skills/pr/references/create.md diff --git a/.codex/skills/pr/references/draft.md b/.cursor/skills/pr/references/draft.md similarity index 100% rename from .codex/skills/pr/references/draft.md rename to .cursor/skills/pr/references/draft.md diff --git a/.codex/skills/pr/references/review.md b/.cursor/skills/pr/references/review.md similarity index 100% rename from .codex/skills/pr/references/review.md rename to .cursor/skills/pr/references/review.md diff --git a/.cursor/skills/ralph-loop/SKILL.md b/.cursor/skills/ralph-loop/SKILL.md new file mode 100644 index 0000000000..c8d26cea87 --- /dev/null +++ b/.cursor/skills/ralph-loop/SKILL.md @@ -0,0 +1,20 @@ +--- +description: Start Ralph Loop in current session +argument-hint: PROMPT [--max-iterations N] [--completion-promise TEXT] +allowed-tools: + - Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*) +hide-from-slash-command-tool: 'true' +name: ralph-loop +--- + +# Ralph Loop Command + +Execute the setup script to initialize the Ralph loop: + +```! +"${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS +``` + +Please work on the task. When you try to exit, the Ralph loop will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve. + +CRITICAL RULE: If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. The loop is designed to continue until genuine completion. diff --git a/.cursor/skills/rclone/SKILL.md b/.cursor/skills/rclone/SKILL.md new file mode 100644 index 0000000000..62c91a306f --- /dev/null +++ b/.cursor/skills/rclone/SKILL.md @@ -0,0 +1,150 @@ +--- +name: rclone +description: Upload, sync, and manage files across cloud storage providers using rclone. Use when uploading files (images, videos, documents) to S3, Cloudflare R2, Backblaze B2, Google Drive, Dropbox, or any S3-compatible storage. Triggers on "upload to S3", "sync to cloud", "rclone", "backup files", "upload video/image to bucket", or requests to transfer files to remote storage. +--- + +# rclone File Transfer Skill + +## Setup Check (Always Run First) + +Before any rclone operation, verify installation and configuration: + +```bash +# Check if rclone is installed +command -v rclone >/dev/null 2>&1 && echo "rclone installed: $(rclone version | head -1)" || echo "NOT INSTALLED" + +# List configured remotes +rclone listremotes 2>/dev/null || echo "NO REMOTES CONFIGURED" +``` + +### If rclone is NOT installed + +Guide the user to install: + +```bash +# macOS +brew install rclone + +# Linux (script install) +curl https://rclone.org/install.sh | sudo bash + +# Or via package manager +sudo apt install rclone # Debian/Ubuntu +sudo dnf install rclone # Fedora +``` + +### If NO remotes are configured + +Walk the user through interactive configuration: + +```bash +rclone config +``` + +**Common provider setup quick reference:** + +| Provider | Type | Key Settings | +|----------|------|--------------| +| AWS S3 | `s3` | access_key_id, secret_access_key, region | +| Cloudflare R2 | `s3` | access_key_id, secret_access_key, endpoint (account_id.r2.cloudflarestorage.com) | +| Backblaze B2 | `b2` | account (keyID), key (applicationKey) | +| DigitalOcean Spaces | `s3` | access_key_id, secret_access_key, endpoint (region.digitaloceanspaces.com) | +| Google Drive | `drive` | OAuth flow (opens browser) | +| Dropbox | `dropbox` | OAuth flow (opens browser) | + +**Example: Configure Cloudflare R2** +```bash +rclone config create r2 s3 \ + provider=Cloudflare \ + access_key_id=YOUR_ACCESS_KEY \ + secret_access_key=YOUR_SECRET_KEY \ + endpoint=ACCOUNT_ID.r2.cloudflarestorage.com \ + acl=private +``` + +**Example: Configure AWS S3** +```bash +rclone config create aws s3 \ + provider=AWS \ + access_key_id=YOUR_ACCESS_KEY \ + secret_access_key=YOUR_SECRET_KEY \ + region=us-east-1 +``` + +## Common Operations + +### Upload single file +```bash +rclone copy /path/to/file.mp4 remote:bucket/path/ --progress +``` + +### Upload directory +```bash +rclone copy /path/to/folder remote:bucket/folder/ --progress +``` + +### Sync directory (mirror, deletes removed files) +```bash +rclone sync /local/path remote:bucket/path/ --progress +``` + +### List remote contents +```bash +rclone ls remote:bucket/ +rclone lsd remote:bucket/ # directories only +``` + +### Check what would be transferred (dry run) +```bash +rclone copy /path remote:bucket/ --dry-run +``` + +## Useful Flags + +| Flag | Purpose | +|------|---------| +| `--progress` | Show transfer progress | +| `--dry-run` | Preview without transferring | +| `-v` | Verbose output | +| `--transfers=N` | Parallel transfers (default 4) | +| `--bwlimit=RATE` | Bandwidth limit (e.g., `10M`) | +| `--checksum` | Compare by checksum, not size/time | +| `--exclude="*.tmp"` | Exclude patterns | +| `--include="*.mp4"` | Include only matching | +| `--min-size=SIZE` | Skip files smaller than SIZE | +| `--max-size=SIZE` | Skip files larger than SIZE | + +## Large File Uploads + +For videos and large files, use chunked uploads: + +```bash +# S3 multipart upload (automatic for >200MB) +rclone copy large_video.mp4 remote:bucket/ --s3-chunk-size=64M --progress + +# Resume interrupted transfers +rclone copy /path remote:bucket/ --progress --retries=5 +``` + +## Verify Upload + +```bash +# Check file exists and matches +rclone check /local/file remote:bucket/file + +# Get file info +rclone lsl remote:bucket/path/to/file +``` + +## Troubleshooting + +```bash +# Test connection +rclone lsd remote: + +# Debug connection issues +rclone lsd remote: -vv + +# Check config +rclone config show remote +``` diff --git a/.cursor/skills/rclone/scripts/check_setup.sh b/.cursor/skills/rclone/scripts/check_setup.sh new file mode 100755 index 0000000000..99b6bd87f5 --- /dev/null +++ b/.cursor/skills/rclone/scripts/check_setup.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# rclone setup checker - verifies installation and configuration + +set -e + +echo "=== rclone Setup Check ===" +echo + +# Check if rclone is installed +if command -v rclone >/dev/null 2>&1; then + echo "✓ rclone installed" + rclone version | head -1 + echo +else + echo "✗ rclone NOT INSTALLED" + echo + echo "Install with:" + echo " macOS: brew install rclone" + echo " Linux: curl https://rclone.org/install.sh | sudo bash" + echo " or: sudo apt install rclone" + exit 1 +fi + +# Check for configured remotes +REMOTES=$(rclone listremotes 2>/dev/null || true) + +if [ -z "$REMOTES" ]; then + echo "✗ No remotes configured" + echo + echo "Run 'rclone config' to set up a remote, or use:" + echo + echo " # Cloudflare R2" + echo " rclone config create r2 s3 provider=Cloudflare \\" + echo " access_key_id=KEY secret_access_key=SECRET \\" + echo " endpoint=ACCOUNT_ID.r2.cloudflarestorage.com" + echo + echo " # AWS S3" + echo " rclone config create aws s3 provider=AWS \\" + echo " access_key_id=KEY secret_access_key=SECRET region=us-east-1" + echo + exit 1 +else + echo "✓ Configured remotes:" + echo "$REMOTES" | sed 's/^/ /' + echo +fi + +# Test connectivity for each remote +echo "Testing remote connectivity..." +for remote in $REMOTES; do + remote_name="${remote%:}" + if rclone lsd "$remote" >/dev/null 2>&1; then + echo " ✓ $remote_name - connected" + else + echo " ✗ $remote_name - connection failed (check credentials)" + fi +done + +echo +echo "=== Setup Complete ===" diff --git a/.cursor/skills/repo-research-analyst/SKILL.md b/.cursor/skills/repo-research-analyst/SKILL.md new file mode 100644 index 0000000000..356ba0f604 --- /dev/null +++ b/.cursor/skills/repo-research-analyst/SKILL.md @@ -0,0 +1,135 @@ +--- +name: repo-research-analyst +description: Conducts thorough research on repository structure, documentation, conventions, and implementation patterns. Use when onboarding to a new codebase or understanding project conventions. +model: inherit +--- + + + +Context: User wants to understand a new repository's structure and conventions before contributing. +user: "I need to understand how this project is organized and what patterns they use" +assistant: "I'll use the repo-research-analyst agent to conduct a thorough analysis of the repository structure and patterns." +Since the user needs comprehensive repository research, use the repo-research-analyst agent to examine all aspects of the project. + + +Context: User is preparing to create a GitHub issue and wants to follow project conventions. +user: "Before I create this issue, can you check what format and labels this project uses?" +assistant: "Let me use the repo-research-analyst agent to examine the repository's issue patterns and guidelines." +The user needs to understand issue formatting conventions, so use the repo-research-analyst agent to analyze existing issues and templates. + + +Context: User is implementing a new feature and wants to follow existing patterns. +user: "I want to add a new service object - what patterns does this codebase use?" +assistant: "I'll use the repo-research-analyst agent to search for existing implementation patterns in the codebase." +Since the user needs to understand implementation patterns, use the repo-research-analyst agent to search and analyze the codebase. + + + +**Note: The current year is 2026.** Use this when searching for recent documentation and patterns. + +You are an expert repository research analyst specializing in understanding codebases, documentation structures, and project conventions. Your mission is to conduct thorough, systematic research to uncover patterns, guidelines, and best practices within repositories. + +**Core Responsibilities:** + +1. **Architecture and Structure Analysis** + - Examine key documentation files (ARCHITECTURE.md, README.md, CONTRIBUTING.md, CLAUDE.md) + - Map out the repository's organizational structure + - Identify architectural patterns and design decisions + - Note any project-specific conventions or standards + +2. **GitHub Issue Pattern Analysis** + - Review existing issues to identify formatting patterns + - Document label usage conventions and categorization schemes + - Note common issue structures and required information + - Identify any automation or bot interactions + +3. **Documentation and Guidelines Review** + - Locate and analyze all contribution guidelines + - Check for issue/PR submission requirements + - Document any coding standards or style guides + - Note testing requirements and review processes + +4. **Template Discovery** + - Search for issue templates in `.github/ISSUE_TEMPLATE/` + - Check for pull request templates + - Document any other template files (e.g., RFC templates) + - Analyze template structure and required fields + +5. **Codebase Pattern Search** + - Use `ast-grep` for syntax-aware pattern matching when available + - Fall back to `rg` for text-based searches when appropriate + - Identify common implementation patterns + - Document naming conventions and code organization + +**Research Methodology:** + +1. Start with high-level documentation to understand project context +2. Progressively drill down into specific areas based on findings +3. Cross-reference discoveries across different sources +4. Prioritize official documentation over inferred patterns +5. Note any inconsistencies or areas lacking documentation + +**Output Format:** + +Structure your findings as: + +```markdown +## Repository Research Summary + +### Architecture & Structure +- Key findings about project organization +- Important architectural decisions +- Technology stack and dependencies + +### Issue Conventions +- Formatting patterns observed +- Label taxonomy and usage +- Common issue types and structures + +### Documentation Insights +- Contribution guidelines summary +- Coding standards and practices +- Testing and review requirements + +### Templates Found +- List of template files with purposes +- Required fields and formats +- Usage instructions + +### Implementation Patterns +- Common code patterns identified +- Naming conventions +- Project-specific practices + +### Recommendations +- How to best align with project conventions +- Areas needing clarification +- Next steps for deeper investigation +``` + +**Quality Assurance:** + +- Verify findings by checking multiple sources +- Distinguish between official guidelines and observed patterns +- Note the recency of documentation (check last update dates) +- Flag any contradictions or outdated information +- Provide specific file paths and examples to support findings + +**Search Strategies:** + +Use the built-in tools for efficient searching: +- **Grep tool**: For text/code pattern searches with regex support (uses ripgrep under the hood) +- **Glob tool**: For file discovery by pattern (e.g., `**/*.md`, `**/CLAUDE.md`) +- **Read tool**: For reading file contents once located +- For AST-based code patterns: `ast-grep --lang ruby -p 'pattern'` or `ast-grep --lang typescript -p 'pattern'` +- Check multiple variations of common file names + +**Important Considerations:** + +- Respect any CLAUDE.md or project-specific instructions found +- Pay attention to both explicit rules and implicit conventions +- Consider the project's maturity and size when interpreting patterns +- Note any tools or automation mentioned in documentation +- Be thorough but focused - prioritize actionable insights + +Your research should enable someone to quickly understand and align with the project's established patterns and practices. Be systematic, thorough, and always provide evidence for your findings. diff --git a/.cursor/skills/report-bug/SKILL.md b/.cursor/skills/report-bug/SKILL.md new file mode 100644 index 0000000000..846bca8893 --- /dev/null +++ b/.cursor/skills/report-bug/SKILL.md @@ -0,0 +1,151 @@ +--- +name: report-bug +description: Report a bug in the compound-engineering plugin +argument-hint: '[optional: brief description of the bug]' +disable-model-invocation: true +--- + +# Report a Compounding Engineering Plugin Bug + +Report bugs encountered while using the compound-engineering plugin. This command gathers structured information and creates a GitHub issue for the maintainer. + +## Step 1: Gather Bug Information + +Use the AskUserQuestion tool to collect the following information: + +**Question 1: Bug Category** +- What type of issue are you experiencing? +- Options: Agent not working, Command not working, Skill not working, MCP server issue, Installation problem, Other + +**Question 2: Specific Component** +- Which specific component is affected? +- Ask for the name of the agent, command, skill, or MCP server + +**Question 3: What Happened (Actual Behavior)** +- Ask: "What happened when you used this component?" +- Get a clear description of the actual behavior + +**Question 4: What Should Have Happened (Expected Behavior)** +- Ask: "What did you expect to happen instead?" +- Get a clear description of expected behavior + +**Question 5: Steps to Reproduce** +- Ask: "What steps did you take before the bug occurred?" +- Get reproduction steps + +**Question 6: Error Messages** +- Ask: "Did you see any error messages? If so, please share them." +- Capture any error output + +## Step 2: Collect Environment Information + +Automatically gather: +```bash +# Get plugin version +cat ~/.claude/plugins/installed_plugins.json 2>/dev/null | grep -A5 "compound-engineering" | head -10 || echo "Plugin info not found" + +# Get Claude Code version +claude --version 2>/dev/null || echo "Claude CLI version unknown" + +# Get OS info +uname -a +``` + +## Step 3: Format the Bug Report + +Create a well-structured bug report with: + +```markdown +## Bug Description + +**Component:** [Type] - [Name] +**Summary:** [Brief description from argument or collected info] + +## Environment + +- **Plugin Version:** [from installed_plugins.json] +- **Claude Code Version:** [from claude --version] +- **OS:** [from uname] + +## What Happened + +[Actual behavior description] + +## Expected Behavior + +[Expected behavior description] + +## Steps to Reproduce + +1. [Step 1] +2. [Step 2] +3. [Step 3] + +## Error Messages + +``` +[Any error output] +``` + +## Additional Context + +[Any other relevant information] + +--- +*Reported via `/report-bug` command* +``` + +## Step 4: Create GitHub Issue + +Use the GitHub CLI to create the issue: + +```bash +gh issue create \ + --repo EveryInc/compound-engineering-plugin \ + --title "[compound-engineering] Bug: [Brief description]" \ + --body "[Formatted bug report from Step 3]" \ + --label "bug,compound-engineering" +``` + +**Note:** If labels don't exist, create without labels: +```bash +gh issue create \ + --repo EveryInc/compound-engineering-plugin \ + --title "[compound-engineering] Bug: [Brief description]" \ + --body "[Formatted bug report]" +``` + +## Step 5: Confirm Submission + +After the issue is created: +1. Display the issue URL to the user +2. Thank them for reporting the bug +3. Let them know the maintainer (Kieran Klaassen) will be notified + +## Output Format + +``` +✅ Bug report submitted successfully! + +Issue: https://github.com/EveryInc/compound-engineering-plugin/issues/[NUMBER] +Title: [compound-engineering] Bug: [description] + +Thank you for helping improve the compound-engineering plugin! +The maintainer will review your report and respond as soon as possible. +``` + +## Error Handling + +- If `gh` CLI is not authenticated: Prompt user to run `gh auth login` first +- If issue creation fails: Display the formatted report so user can manually create the issue +- If required information is missing: Re-prompt for that specific field + +## Privacy Notice + +This command does NOT collect: +- Personal information +- API keys or credentials +- Private code from your projects +- File paths beyond basic OS info + +Only technical information about the bug is included in the report. diff --git a/.cursor/skills/reproduce-bug/SKILL.md b/.cursor/skills/reproduce-bug/SKILL.md new file mode 100644 index 0000000000..50fadcd9f1 --- /dev/null +++ b/.cursor/skills/reproduce-bug/SKILL.md @@ -0,0 +1,100 @@ +--- +name: reproduce-bug +description: Reproduce and investigate a bug using logs, console inspection, and browser screenshots +argument-hint: '[GitHub issue number]' +disable-model-invocation: true +--- + +# Reproduce Bug Command + +Look at github issue #$ARGUMENTS and read the issue description and comments. + +## Phase 1: Log Investigation + +Run the following agents in parallel to investigate the bug: + +1. Task rails-console-explorer(issue_description) +2. Task appsignal-log-investigator(issue_description) + +Think about the places it could go wrong looking at the codebase. Look for logging output we can look for. + +Run the agents again to find any logs that could help us reproduce the bug. + +Keep running these agents until you have a good idea of what is going on. + +## Phase 2: Visual Reproduction with Playwright + +If the bug is UI-related or involves user flows, use Playwright to visually reproduce it: + +### Step 1: Verify Server is Running + +``` +mcp__plugin_compound-engineering_pw__browser_navigate({ url: "http://localhost:3000" }) +mcp__plugin_compound-engineering_pw__browser_snapshot({}) +``` + +If server not running, inform user to start `bin/dev`. + +### Step 2: Navigate to Affected Area + +Based on the issue description, navigate to the relevant page: + +``` +mcp__plugin_compound-engineering_pw__browser_navigate({ url: "http://localhost:3000/[affected_route]" }) +mcp__plugin_compound-engineering_pw__browser_snapshot({}) +``` + +### Step 3: Capture Screenshots + +Take screenshots at each step of reproducing the bug: + +``` +mcp__plugin_compound-engineering_pw__browser_take_screenshot({ filename: "bug-[issue]-step-1.png" }) +``` + +### Step 4: Follow User Flow + +Reproduce the exact steps from the issue: + +1. **Read the issue's reproduction steps** +2. **Execute each step using Playwright:** + - `browser_click` for clicking elements + - `browser_type` for filling forms + - `browser_snapshot` to see the current state + - `browser_take_screenshot` to capture evidence + +3. **Check for console errors:** + ``` + mcp__plugin_compound-engineering_pw__browser_console_messages({ level: "error" }) + ``` + +### Step 5: Capture Bug State + +When you reproduce the bug: + +1. Take a screenshot of the bug state +2. Capture console errors +3. Document the exact steps that triggered it + +``` +mcp__plugin_compound-engineering_pw__browser_take_screenshot({ filename: "bug-[issue]-reproduced.png" }) +``` + +## Phase 3: Document Findings + +**Reference Collection:** + +- [ ] Document all research findings with specific file paths (e.g., `app/services/example_service.rb:42`) +- [ ] Include screenshots showing the bug reproduction +- [ ] List console errors if any +- [ ] Document the exact reproduction steps + +## Phase 4: Report Back + +Add a comment to the issue with: + +1. **Findings** - What you discovered about the cause +2. **Reproduction Steps** - Exact steps to reproduce (verified) +3. **Screenshots** - Visual evidence of the bug (upload captured screenshots) +4. **Relevant Code** - File paths and line numbers +5. **Suggested Fix** - If you have one diff --git a/.codex/skills/resolve-pr-parallel/SKILL.md b/.cursor/skills/resolve-pr-parallel/SKILL.md similarity index 100% rename from .codex/skills/resolve-pr-parallel/SKILL.md rename to .cursor/skills/resolve-pr-parallel/SKILL.md diff --git a/.codex/skills/resolve-pr-parallel/scripts/get-pr-comments b/.cursor/skills/resolve-pr-parallel/scripts/get-pr-comments similarity index 100% rename from .codex/skills/resolve-pr-parallel/scripts/get-pr-comments rename to .cursor/skills/resolve-pr-parallel/scripts/get-pr-comments diff --git a/.codex/skills/resolve-pr-parallel/scripts/resolve-pr-thread b/.cursor/skills/resolve-pr-parallel/scripts/resolve-pr-thread similarity index 100% rename from .codex/skills/resolve-pr-parallel/scripts/resolve-pr-thread rename to .cursor/skills/resolve-pr-parallel/scripts/resolve-pr-thread diff --git a/.cursor/skills/resolve_parallel/SKILL.md b/.cursor/skills/resolve_parallel/SKILL.md new file mode 100644 index 0000000000..afbb987650 --- /dev/null +++ b/.cursor/skills/resolve_parallel/SKILL.md @@ -0,0 +1,35 @@ +--- +name: resolve_parallel +description: Resolve all TODO comments using parallel processing +argument-hint: '[optional: specific TODO pattern or file]' +disable-model-invocation: true +--- + +Resolve all TODO comments using parallel processing. + +## Workflow + +### 1. Analyze + +Gather the things todo from above. + +### 2. Plan + +Create a TodoWrite list of all unresolved items grouped by type.Make sure to look at dependencies that might occur and prioritize the ones needed by others. For example, if you need to change a name, you must wait to do the others. Output a mermaid flow diagram showing how we can do this. Can we do everything in parallel? Do we need to do one first that leads to others in parallel? I'll put the to-dos in the mermaid diagram flow‑wise so the agent knows how to proceed in order. + +### 3. Implement (PARALLEL) + +Spawn a pr-comment-resolver agent for each unresolved item in parallel. + +So if there are 3 comments, it will spawn 3 pr-comment-resolver agents in parallel. liek this + +1. Task pr-comment-resolver(comment1) +2. Task pr-comment-resolver(comment2) +3. Task pr-comment-resolver(comment3) + +Always run all in parallel subagents/Tasks for each Todo item. + +### 4. Commit & Resolve + +- Commit changes +- Push to remote diff --git a/.cursor/skills/resolve_todo_parallel/SKILL.md b/.cursor/skills/resolve_todo_parallel/SKILL.md new file mode 100644 index 0000000000..6dbf8d463e --- /dev/null +++ b/.cursor/skills/resolve_todo_parallel/SKILL.md @@ -0,0 +1,37 @@ +--- +name: resolve_todo_parallel +description: Resolve all pending CLI todos using parallel processing +argument-hint: '[optional: specific todo ID or pattern]' +--- + +Resolve all TODO comments using parallel processing. + +## Workflow + +### 1. Analyze + +Get all unresolved TODOs from the /todos/\*.md directory + +If any todo recommends deleting, removing, or gitignoring files in `docs/plans/` or `docs/solutions/`, skip it and mark it as `wont_fix`. These are compound-engineering pipeline artifacts that are intentional and permanent. + +### 2. Plan + +Create a TodoWrite list of all unresolved items grouped by type.Make sure to look at dependencies that might occur and prioritize the ones needed by others. For example, if you need to change a name, you must wait to do the others. Output a mermaid flow diagram showing how we can do this. Can we do everything in parallel? Do we need to do one first that leads to others in parallel? I'll put the to-dos in the mermaid diagram flow‑wise so the agent knows how to proceed in order. + +### 3. Implement (PARALLEL) + +Spawn a pr-comment-resolver agent for each unresolved item in parallel. + +So if there are 3 comments, it will spawn 3 pr-comment-resolver agents in parallel. liek this + +1. Task pr-comment-resolver(comment1) +2. Task pr-comment-resolver(comment2) +3. Task pr-comment-resolver(comment3) + +Always run all in parallel subagents/Tasks for each Todo item. + +### 4. Commit & Resolve + +- Commit changes +- Remove the TODO from the file, and mark it as resolved. +- Push to remote diff --git a/.codex/skills/schema-drift-detector/SKILL.md b/.cursor/skills/schema-drift-detector/SKILL.md similarity index 100% rename from .codex/skills/schema-drift-detector/SKILL.md rename to .cursor/skills/schema-drift-detector/SKILL.md diff --git a/.cursor/skills/security-sentinel/SKILL.md b/.cursor/skills/security-sentinel/SKILL.md new file mode 100644 index 0000000000..dbe8712a5c --- /dev/null +++ b/.cursor/skills/security-sentinel/SKILL.md @@ -0,0 +1,114 @@ +--- +name: security-sentinel +description: Performs security audits for vulnerabilities, input validation, auth/authz, hardcoded secrets, and OWASP compliance. Use when reviewing code for security issues or before deployment. +model: inherit +--- + + + +Context: The user wants to ensure their newly implemented API endpoints are secure before deployment. +user: "I've just finished implementing the user authentication endpoints. Can you check them for security issues?" +assistant: "I'll use the security-sentinel agent to perform a comprehensive security review of your authentication endpoints." +Since the user is asking for a security review of authentication code, use the security-sentinel agent to scan for vulnerabilities and ensure secure implementation. + + +Context: The user is concerned about potential SQL injection vulnerabilities in their database queries. +user: "I'm worried about SQL injection in our search functionality. Can you review it?" +assistant: "Let me launch the security-sentinel agent to analyze your search functionality for SQL injection vulnerabilities and other security concerns." +The user explicitly wants a security review focused on SQL injection, which is a core responsibility of the security-sentinel agent. + + +Context: After implementing a new feature, the user wants to ensure no sensitive data is exposed. +user: "I've added the payment processing module. Please check if any sensitive data might be exposed." +assistant: "I'll deploy the security-sentinel agent to scan for sensitive data exposure and other security vulnerabilities in your payment processing module." +Payment processing involves sensitive data, making this a perfect use case for the security-sentinel agent to identify potential data exposure risks. + + + +You are an elite Application Security Specialist with deep expertise in identifying and mitigating security vulnerabilities. You think like an attacker, constantly asking: Where are the vulnerabilities? What could go wrong? How could this be exploited? + +Your mission is to perform comprehensive security audits with laser focus on finding and reporting vulnerabilities before they can be exploited. + +## Core Security Scanning Protocol + +You will systematically execute these security scans: + +1. **Input Validation Analysis** + - Search for all input points: `grep -r "req\.\(body\|params\|query\)" --include="*.js"` + - For Rails projects: `grep -r "params\[" --include="*.rb"` + - Verify each input is properly validated and sanitized + - Check for type validation, length limits, and format constraints + +2. **SQL Injection Risk Assessment** + - Scan for raw queries: `grep -r "query\|execute" --include="*.js" | grep -v "?"` + - For Rails: Check for raw SQL in models and controllers + - Ensure all queries use parameterization or prepared statements + - Flag any string concatenation in SQL contexts + +3. **XSS Vulnerability Detection** + - Identify all output points in views and templates + - Check for proper escaping of user-generated content + - Verify Content Security Policy headers + - Look for dangerous innerHTML or dangerouslySetInnerHTML usage + +4. **Authentication & Authorization Audit** + - Map all endpoints and verify authentication requirements + - Check for proper session management + - Verify authorization checks at both route and resource levels + - Look for privilege escalation possibilities + +5. **Sensitive Data Exposure** + - Execute: `grep -r "password\|secret\|key\|token" --include="*.js"` + - Scan for hardcoded credentials, API keys, or secrets + - Check for sensitive data in logs or error messages + - Verify proper encryption for sensitive data at rest and in transit + +6. **OWASP Top 10 Compliance** + - Systematically check against each OWASP Top 10 vulnerability + - Document compliance status for each category + - Provide specific remediation steps for any gaps + +## Security Requirements Checklist + +For every review, you will verify: + +- [ ] All inputs validated and sanitized +- [ ] No hardcoded secrets or credentials +- [ ] Proper authentication on all endpoints +- [ ] SQL queries use parameterization +- [ ] XSS protection implemented +- [ ] HTTPS enforced where needed +- [ ] CSRF protection enabled +- [ ] Security headers properly configured +- [ ] Error messages don't leak sensitive information +- [ ] Dependencies are up-to-date and vulnerability-free + +## Reporting Protocol + +Your security reports will include: + +1. **Executive Summary**: High-level risk assessment with severity ratings +2. **Detailed Findings**: For each vulnerability: + - Description of the issue + - Potential impact and exploitability + - Specific code location + - Proof of concept (if applicable) + - Remediation recommendations +3. **Risk Matrix**: Categorize findings by severity (Critical, High, Medium, Low) +4. **Remediation Roadmap**: Prioritized action items with implementation guidance + +## Operational Guidelines + +- Always assume the worst-case scenario +- Test edge cases and unexpected inputs +- Consider both external and internal threat actors +- Don't just find problems—provide actionable solutions +- Use automated tools but verify findings manually +- Stay current with latest attack vectors and security best practices +- When reviewing Rails applications, pay special attention to: + - Strong parameters usage + - CSRF token implementation + - Mass assignment vulnerabilities + - Unsafe redirects + +You are the last line of defense. Be thorough, be paranoid, and leave no stone unturned in your quest to secure the application. diff --git a/.codex/skills/setup/SKILL.md b/.cursor/skills/setup/SKILL.md similarity index 100% rename from .codex/skills/setup/SKILL.md rename to .cursor/skills/setup/SKILL.md diff --git a/.cursor/skills/skill-creator/SKILL.md b/.cursor/skills/skill-creator/SKILL.md new file mode 100644 index 0000000000..4917689dd4 --- /dev/null +++ b/.cursor/skills/skill-creator/SKILL.md @@ -0,0 +1,210 @@ +--- +name: skill-creator +description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations. +license: Complete terms in LICENSE.txt +disable-model-invocation: true +--- + +# Skill Creator + +This skill provides guidance for creating effective skills. + +## About Skills + +Skills are modular, self-contained packages that extend Claude's capabilities by providing +specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific +domains or tasks—they transform Claude from a general-purpose agent into a specialized agent +equipped with procedural knowledge that no model can fully possess. + +### What Skills Provide + +1. Specialized workflows - Multi-step procedures for specific domains +2. Tool integrations - Instructions for working with specific file formats or APIs +3. Domain expertise - Company-specific knowledge, schemas, business logic +4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks + +### Anatomy of a Skill + +Every skill consists of a required SKILL.md file and optional bundled resources: + +``` +skill-name/ +├── SKILL.md (required) +│ ├── YAML frontmatter metadata (required) +│ │ ├── name: (required) +│ │ └── description: (required) +│ └── Markdown instructions (required) +└── Bundled Resources (optional) + ├── scripts/ - Executable code (Python/Bash/etc.) + ├── references/ - Documentation intended to be loaded into context as needed + └── assets/ - Files used in output (templates, icons, fonts, etc.) +``` + +#### SKILL.md (required) + +**Metadata Quality:** The `name` and `description` in YAML frontmatter determine when Claude will use the skill. Be specific about what the skill does and when to use it. Use the third-person (e.g. "This skill should be used when..." instead of "Use this skill when..."). + +#### Bundled Resources (optional) + +##### Scripts (`scripts/`) + +Executable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten. + +- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed +- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks +- **Benefits**: Token efficient, deterministic, may be executed without loading into context +- **Note**: Scripts may still need to be read by Claude for patching or environment-specific adjustments + +##### References (`references/`) + +Documentation and reference material intended to be loaded as needed into context to inform Claude's process and thinking. + +- **When to include**: For documentation that Claude should reference while working +- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications +- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides +- **Benefits**: Keeps SKILL.md lean, loaded only when Claude determines it's needed +- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md +- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files. + +##### Assets (`assets/`) + +Files not intended to be loaded into context, but rather used within the output Claude produces. + +- **When to include**: When the skill needs files that will be used in the final output +- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography +- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified +- **Benefits**: Separates output resources from documentation, enables Claude to use files without loading them into context + +### Progressive Disclosure Design Principle + +Skills use a three-level loading system to manage context efficiently: + +1. **Metadata (name + description)** - Always in context (~100 words) +2. **SKILL.md body** - When skill triggers (<5k words) +3. **Bundled resources** - As needed by Claude (Unlimited*) + +*Unlimited because scripts can be executed without reading into context window. + +## Skill Creation Process + +To create a skill, follow the "Skill Creation Process" in order, skipping steps only if there is a clear reason why they are not applicable. + +### Step 1: Understanding the Skill with Concrete Examples + +Skip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill. + +To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback. + +For example, when building an image-editor skill, relevant questions include: + +- "What functionality should the image-editor skill support? Editing, rotating, anything else?" +- "Can you give some examples of how this skill would be used?" +- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?" +- "What would a user say that should trigger this skill?" + +To avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness. + +Conclude this step when there is a clear sense of the functionality the skill should support. + +### Step 2: Planning the Reusable Skill Contents + +To turn concrete examples into an effective skill, analyze each example by: + +1. Considering how to execute on the example from scratch +2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly + +Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows: + +1. Rotating a PDF requires re-writing the same code each time +2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill + +Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows: + +1. Writing a frontend webapp requires the same boilerplate HTML/React each time +2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill + +Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows: + +1. Querying BigQuery requires re-discovering the table schemas and relationships each time +2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill + +To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets. + +### Step 3: Initializing the Skill + +At this point, it is time to actually create the skill. + +Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step. + +When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable. + +Usage: + +```bash +scripts/init_skill.py --path +``` + +The script: + +- Creates the skill directory at the specified path +- Generates a SKILL.md template with proper frontmatter and TODO placeholders +- Creates example resource directories: `scripts/`, `references/`, and `assets/` +- Adds example files in each directory that can be customized or deleted + +After initialization, customize or remove the generated SKILL.md and example files as needed. + +### Step 4: Edit the Skill + +When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Claude to use. Focus on including information that would be beneficial and non-obvious to Claude. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Claude instance execute these tasks more effectively. + +#### Start with Reusable Skill Contents + +To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`. + +Also, delete any example files and directories not needed for the skill. The initialization script creates example files in `scripts/`, `references/`, and `assets/` to demonstrate structure, but most skills won't need all of them. + +#### Update SKILL.md + +**Writing Style:** Write the entire skill using **imperative/infinitive form** (verb-first instructions), not second person. Use objective, instructional language (e.g., "To accomplish X, do Y" rather than "You should do X" or "If you need to do X"). This maintains consistency and clarity for AI consumption. + +To complete SKILL.md, answer the following questions: + +1. What is the purpose of the skill, in a few sentences? +2. When should the skill be used? +3. In practice, how should Claude use the skill? All reusable skill contents developed above should be referenced so that Claude knows how to use them. + +### Step 5: Packaging a Skill + +Once the skill is ready, it should be packaged into a distributable zip file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements: + +```bash +scripts/package_skill.py +``` + +Optional output directory specification: + +```bash +scripts/package_skill.py ./dist +``` + +The packaging script will: + +1. **Validate** the skill automatically, checking: + - YAML frontmatter format and required fields + - Skill naming conventions and directory structure + - Description completeness and quality + - File organization and resource references + +2. **Package** the skill if validation passes, creating a zip file named after the skill (e.g., `my-skill.zip`) that includes all files and maintains the proper directory structure for distribution. + +If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again. + +### Step 6: Iterate + +After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed. + +**Iteration workflow:** +1. Use the skill on real tasks +2. Notice struggles or inefficiencies +3. Identify how SKILL.md or bundled resources should be updated +4. Implement changes and test again diff --git a/.cursor/skills/skill-creator/scripts/init_skill.py b/.cursor/skills/skill-creator/scripts/init_skill.py new file mode 100755 index 0000000000..329ad4e5a7 --- /dev/null +++ b/.cursor/skills/skill-creator/scripts/init_skill.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +""" +Skill Initializer - Creates a new skill from template + +Usage: + init_skill.py --path + +Examples: + init_skill.py my-new-skill --path skills/public + init_skill.py my-api-helper --path skills/private + init_skill.py custom-skill --path /custom/location +""" + +import sys +from pathlib import Path + + +SKILL_TEMPLATE = """--- +name: {skill_name} +description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.] +--- + +# {skill_title} + +## Overview + +[TODO: 1-2 sentences explaining what this skill enables] + +## Structuring This Skill + +[TODO: Choose the structure that best fits this skill's purpose. Common patterns: + +**1. Workflow-Based** (best for sequential processes) +- Works well when there are clear step-by-step procedures +- Example: DOCX skill with "Workflow Decision Tree" → "Reading" → "Creating" → "Editing" +- Structure: ## Overview → ## Workflow Decision Tree → ## Step 1 → ## Step 2... + +**2. Task-Based** (best for tool collections) +- Works well when the skill offers different operations/capabilities +- Example: PDF skill with "Quick Start" → "Merge PDFs" → "Split PDFs" → "Extract Text" +- Structure: ## Overview → ## Quick Start → ## Task Category 1 → ## Task Category 2... + +**3. Reference/Guidelines** (best for standards or specifications) +- Works well for brand guidelines, coding standards, or requirements +- Example: Brand styling with "Brand Guidelines" → "Colors" → "Typography" → "Features" +- Structure: ## Overview → ## Guidelines → ## Specifications → ## Usage... + +**4. Capabilities-Based** (best for integrated systems) +- Works well when the skill provides multiple interrelated features +- Example: Product Management with "Core Capabilities" → numbered capability list +- Structure: ## Overview → ## Core Capabilities → ### 1. Feature → ### 2. Feature... + +Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations). + +Delete this entire "Structuring This Skill" section when done - it's just guidance.] + +## [TODO: Replace with the first main section based on chosen structure] + +[TODO: Add content here. See examples in existing skills: +- Code samples for technical skills +- Decision trees for complex workflows +- Concrete examples with realistic user requests +- References to scripts/templates/references as needed] + +## Resources + +This skill includes example resource directories that demonstrate how to organize different types of bundled resources: + +### scripts/ +Executable code (Python/Bash/etc.) that can be run directly to perform specific operations. + +**Examples from other skills:** +- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation +- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing + +**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations. + +**Note:** Scripts may be executed without loading into context, but can still be read by Claude for patching or environment adjustments. + +### references/ +Documentation and reference material intended to be loaded into context to inform Claude's process and thinking. + +**Examples from other skills:** +- Product management: `communication.md`, `context_building.md` - detailed workflow guides +- BigQuery: API reference documentation and query examples +- Finance: Schema documentation, company policies + +**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Claude should reference while working. + +### assets/ +Files not intended to be loaded into context, but rather used within the output Claude produces. + +**Examples from other skills:** +- Brand styling: PowerPoint template files (.pptx), logo files +- Frontend builder: HTML/React boilerplate project directories +- Typography: Font files (.ttf, .woff2) + +**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output. + +--- + +**Any unneeded directories can be deleted.** Not every skill requires all three types of resources. +""" + +EXAMPLE_SCRIPT = '''#!/usr/bin/env python3 +""" +Example helper script for {skill_name} + +This is a placeholder script that can be executed directly. +Replace with actual implementation or delete if not needed. + +Example real scripts from other skills: +- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields +- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images +""" + +def main(): + print("This is an example script for {skill_name}") + # TODO: Add actual script logic here + # This could be data processing, file conversion, API calls, etc. + +if __name__ == "__main__": + main() +''' + +EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title} + +This is a placeholder for detailed reference documentation. +Replace with actual reference content or delete if not needed. + +Example real reference docs from other skills: +- product-management/references/communication.md - Comprehensive guide for status updates +- product-management/references/context_building.md - Deep-dive on gathering context +- bigquery/references/ - API references and query examples + +## When Reference Docs Are Useful + +Reference docs are ideal for: +- Comprehensive API documentation +- Detailed workflow guides +- Complex multi-step processes +- Information too lengthy for main SKILL.md +- Content that's only needed for specific use cases + +## Structure Suggestions + +### API Reference Example +- Overview +- Authentication +- Endpoints with examples +- Error codes +- Rate limits + +### Workflow Guide Example +- Prerequisites +- Step-by-step instructions +- Common patterns +- Troubleshooting +- Best practices +""" + +EXAMPLE_ASSET = """# Example Asset File + +This placeholder represents where asset files would be stored. +Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed. + +Asset files are NOT intended to be loaded into context, but rather used within +the output Claude produces. + +Example asset files from other skills: +- Brand guidelines: logo.png, slides_template.pptx +- Frontend builder: hello-world/ directory with HTML/React boilerplate +- Typography: custom-font.ttf, font-family.woff2 +- Data: sample_data.csv, test_dataset.json + +## Common Asset Types + +- Templates: .pptx, .docx, boilerplate directories +- Images: .png, .jpg, .svg, .gif +- Fonts: .ttf, .otf, .woff, .woff2 +- Boilerplate code: Project directories, starter files +- Icons: .ico, .svg +- Data files: .csv, .json, .xml, .yaml + +Note: This is a text placeholder. Actual assets can be any file type. +""" + + +def title_case_skill_name(skill_name): + """Convert hyphenated skill name to Title Case for display.""" + return ' '.join(word.capitalize() for word in skill_name.split('-')) + + +def init_skill(skill_name, path): + """ + Initialize a new skill directory with template SKILL.md. + + Args: + skill_name: Name of the skill + path: Path where the skill directory should be created + + Returns: + Path to created skill directory, or None if error + """ + # Determine skill directory path + skill_dir = Path(path).resolve() / skill_name + + # Check if directory already exists + if skill_dir.exists(): + print(f"❌ Error: Skill directory already exists: {skill_dir}") + return None + + # Create skill directory + try: + skill_dir.mkdir(parents=True, exist_ok=False) + print(f"✅ Created skill directory: {skill_dir}") + except Exception as e: + print(f"❌ Error creating directory: {e}") + return None + + # Create SKILL.md from template + skill_title = title_case_skill_name(skill_name) + skill_content = SKILL_TEMPLATE.format( + skill_name=skill_name, + skill_title=skill_title + ) + + skill_md_path = skill_dir / 'SKILL.md' + try: + skill_md_path.write_text(skill_content) + print("✅ Created SKILL.md") + except Exception as e: + print(f"❌ Error creating SKILL.md: {e}") + return None + + # Create resource directories with example files + try: + # Create scripts/ directory with example script + scripts_dir = skill_dir / 'scripts' + scripts_dir.mkdir(exist_ok=True) + example_script = scripts_dir / 'example.py' + example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name)) + example_script.chmod(0o755) + print("✅ Created scripts/example.py") + + # Create references/ directory with example reference doc + references_dir = skill_dir / 'references' + references_dir.mkdir(exist_ok=True) + example_reference = references_dir / 'api_reference.md' + example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title)) + print("✅ Created references/api_reference.md") + + # Create assets/ directory with example asset placeholder + assets_dir = skill_dir / 'assets' + assets_dir.mkdir(exist_ok=True) + example_asset = assets_dir / 'example_asset.txt' + example_asset.write_text(EXAMPLE_ASSET) + print("✅ Created assets/example_asset.txt") + except Exception as e: + print(f"❌ Error creating resource directories: {e}") + return None + + # Print next steps + print(f"\n✅ Skill '{skill_name}' initialized successfully at {skill_dir}") + print("\nNext steps:") + print("1. Edit SKILL.md to complete the TODO items and update the description") + print("2. Customize or delete the example files in scripts/, references/, and assets/") + print("3. Run the validator when ready to check the skill structure") + + return skill_dir + + +def main(): + if len(sys.argv) < 4 or sys.argv[2] != '--path': + print("Usage: init_skill.py --path ") + print("\nSkill name requirements:") + print(" - Hyphen-case identifier (e.g., 'data-analyzer')") + print(" - Lowercase letters, digits, and hyphens only") + print(" - Max 40 characters") + print(" - Must match directory name exactly") + print("\nExamples:") + print(" init_skill.py my-new-skill --path skills/public") + print(" init_skill.py my-api-helper --path skills/private") + print(" init_skill.py custom-skill --path /custom/location") + sys.exit(1) + + skill_name = sys.argv[1] + path = sys.argv[3] + + print(f"🚀 Initializing skill: {skill_name}") + print(f" Location: {path}") + print() + + result = init_skill(skill_name, path) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/skill-creator/scripts/package_skill.py b/.cursor/skills/skill-creator/scripts/package_skill.py new file mode 100755 index 0000000000..3ee8e8e9f8 --- /dev/null +++ b/.cursor/skills/skill-creator/scripts/package_skill.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +Skill Packager - Creates a distributable zip file of a skill folder + +Usage: + python utils/package_skill.py [output-directory] + +Example: + python utils/package_skill.py skills/public/my-skill + python utils/package_skill.py skills/public/my-skill ./dist +""" + +import sys +import zipfile +from pathlib import Path +from quick_validate import validate_skill + + +def package_skill(skill_path, output_dir=None): + """ + Package a skill folder into a zip file. + + Args: + skill_path: Path to the skill folder + output_dir: Optional output directory for the zip file (defaults to current directory) + + Returns: + Path to the created zip file, or None if error + """ + skill_path = Path(skill_path).resolve() + + # Validate skill folder exists + if not skill_path.exists(): + print(f"❌ Error: Skill folder not found: {skill_path}") + return None + + if not skill_path.is_dir(): + print(f"❌ Error: Path is not a directory: {skill_path}") + return None + + # Validate SKILL.md exists + skill_md = skill_path / "SKILL.md" + if not skill_md.exists(): + print(f"❌ Error: SKILL.md not found in {skill_path}") + return None + + # Run validation before packaging + print("🔍 Validating skill...") + valid, message = validate_skill(skill_path) + if not valid: + print(f"❌ Validation failed: {message}") + print(" Please fix the validation errors before packaging.") + return None + print(f"✅ {message}\n") + + # Determine output location + skill_name = skill_path.name + if output_dir: + output_path = Path(output_dir).resolve() + output_path.mkdir(parents=True, exist_ok=True) + else: + output_path = Path.cwd() + + zip_filename = output_path / f"{skill_name}.zip" + + # Create the zip file + try: + with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: + # Walk through the skill directory + for file_path in skill_path.rglob('*'): + if file_path.is_file(): + # Calculate the relative path within the zip + arcname = file_path.relative_to(skill_path.parent) + zipf.write(file_path, arcname) + print(f" Added: {arcname}") + + print(f"\n✅ Successfully packaged skill to: {zip_filename}") + return zip_filename + + except Exception as e: + print(f"❌ Error creating zip file: {e}") + return None + + +def main(): + if len(sys.argv) < 2: + print("Usage: python utils/package_skill.py [output-directory]") + print("\nExample:") + print(" python utils/package_skill.py skills/public/my-skill") + print(" python utils/package_skill.py skills/public/my-skill ./dist") + sys.exit(1) + + skill_path = sys.argv[1] + output_dir = sys.argv[2] if len(sys.argv) > 2 else None + + print(f"📦 Packaging skill: {skill_path}") + if output_dir: + print(f" Output directory: {output_dir}") + print() + + result = package_skill(skill_path, output_dir) + + if result: + sys.exit(0) + else: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.cursor/skills/skill-creator/scripts/quick_validate.py b/.cursor/skills/skill-creator/scripts/quick_validate.py new file mode 100755 index 0000000000..6fa6c63610 --- /dev/null +++ b/.cursor/skills/skill-creator/scripts/quick_validate.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Quick validation script for skills - minimal version +""" + +import sys +import os +import re +from pathlib import Path + +def validate_skill(skill_path): + """Basic validation of a skill""" + skill_path = Path(skill_path) + + # Check SKILL.md exists + skill_md = skill_path / 'SKILL.md' + if not skill_md.exists(): + return False, "SKILL.md not found" + + # Read and validate frontmatter + content = skill_md.read_text() + if not content.startswith('---'): + return False, "No YAML frontmatter found" + + # Extract frontmatter + match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL) + if not match: + return False, "Invalid frontmatter format" + + frontmatter = match.group(1) + + # Check required fields + if 'name:' not in frontmatter: + return False, "Missing 'name' in frontmatter" + if 'description:' not in frontmatter: + return False, "Missing 'description' in frontmatter" + + # Extract name for validation + name_match = re.search(r'name:\s*(.+)', frontmatter) + if name_match: + name = name_match.group(1).strip() + # Check naming convention (hyphen-case: lowercase with hyphens) + if not re.match(r'^[a-z0-9-]+$', name): + return False, f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)" + if name.startswith('-') or name.endswith('-') or '--' in name: + return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens" + + # Extract and validate description + desc_match = re.search(r'description:\s*(.+)', frontmatter) + if desc_match: + description = desc_match.group(1).strip() + # Check for angle brackets + if '<' in description or '>' in description: + return False, "Description cannot contain angle brackets (< or >)" + + return True, "Skill is valid!" + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python quick_validate.py ") + sys.exit(1) + + valid, message = validate_skill(sys.argv[1]) + print(message) + sys.exit(0 if valid else 1) \ No newline at end of file diff --git a/.codex/skills/slfg/SKILL.md b/.cursor/skills/slfg/SKILL.md similarity index 100% rename from .codex/skills/slfg/SKILL.md rename to .cursor/skills/slfg/SKILL.md diff --git a/.cursor/skills/spec-flow-analyzer/SKILL.md b/.cursor/skills/spec-flow-analyzer/SKILL.md new file mode 100644 index 0000000000..4b996815b2 --- /dev/null +++ b/.cursor/skills/spec-flow-analyzer/SKILL.md @@ -0,0 +1,134 @@ +--- +name: spec-flow-analyzer +description: Analyzes specifications and feature descriptions for user flow completeness and gap identification. Use when a spec, plan, or feature description needs flow analysis, edge case discovery, or requirements validation. +model: inherit +--- + + + +Context: The user has just finished drafting a specification for OAuth implementation. +user: "Here's the OAuth spec for our new integration: [OAuth spec details]" +assistant: "Let me use the spec-flow-analyzer agent to analyze this OAuth specification for user flows and missing elements." +Since the user has provided a specification document, use the Task tool to launch the spec-flow-analyzer agent to identify all user flows, edge cases, and missing clarifications. + + +Context: The user is planning a new social sharing feature. +user: "I'm thinking we should add social sharing to posts. Users can share to Twitter, Facebook, and LinkedIn." +assistant: "This sounds like a feature specification that would benefit from flow analysis. Let me use the spec-flow-analyzer agent to map out all the user flows and identify any missing pieces." +The user is describing a new feature. Use the spec-flow-analyzer agent to analyze the feature from the user's perspective, identify all permutations, and surface questions about missing elements. + + +Context: The user has created a plan for a new onboarding flow. +user: "Can you review this onboarding plan and make sure we haven't missed anything?" +assistant: "I'll use the spec-flow-analyzer agent to thoroughly analyze this onboarding plan from the user's perspective." +The user is explicitly asking for review of a plan. Use the spec-flow-analyzer agent to identify all user flows, edge cases, and gaps in the specification. + + + +You are an elite User Experience Flow Analyst and Requirements Engineer. Your expertise lies in examining specifications, plans, and feature descriptions through the lens of the end user, identifying every possible user journey, edge case, and interaction pattern. + +Your primary mission is to: +1. Map out ALL possible user flows and permutations +2. Identify gaps, ambiguities, and missing specifications +3. Ask clarifying questions about unclear elements +4. Present a comprehensive overview of user journeys +5. Highlight areas that need further definition + +When you receive a specification, plan, or feature description, you will: + +## Phase 1: Deep Flow Analysis + +- Map every distinct user journey from start to finish +- Identify all decision points, branches, and conditional paths +- Consider different user types, roles, and permission levels +- Think through happy paths, error states, and edge cases +- Examine state transitions and system responses +- Consider integration points with existing features +- Analyze authentication, authorization, and session flows +- Map data flows and transformations + +## Phase 2: Permutation Discovery + +For each feature, systematically consider: +- First-time user vs. returning user scenarios +- Different entry points to the feature +- Various device types and contexts (mobile, desktop, tablet) +- Network conditions (offline, slow connection, perfect connection) +- Concurrent user actions and race conditions +- Partial completion and resumption scenarios +- Error recovery and retry flows +- Cancellation and rollback paths + +## Phase 3: Gap Identification + +Identify and document: +- Missing error handling specifications +- Unclear state management +- Ambiguous user feedback mechanisms +- Unspecified validation rules +- Missing accessibility considerations +- Unclear data persistence requirements +- Undefined timeout or rate limiting behavior +- Missing security considerations +- Unclear integration contracts +- Ambiguous success/failure criteria + +## Phase 4: Question Formulation + +For each gap or ambiguity, formulate: +- Specific, actionable questions +- Context about why this matters +- Potential impact if left unspecified +- Examples to illustrate the ambiguity + +## Output Format + +Structure your response as follows: + +### User Flow Overview + +[Provide a clear, structured breakdown of all identified user flows. Use visual aids like mermaid diagrams when helpful. Number each flow and describe it concisely.] + +### Flow Permutations Matrix + +[Create a matrix or table showing different variations of each flow based on: +- User state (authenticated, guest, admin, etc.) +- Context (first time, returning, error recovery) +- Device/platform +- Any other relevant dimensions] + +### Missing Elements & Gaps + +[Organized by category, list all identified gaps with: +- **Category**: (e.g., Error Handling, Validation, Security) +- **Gap Description**: What's missing or unclear +- **Impact**: Why this matters +- **Current Ambiguity**: What's currently unclear] + +### Critical Questions Requiring Clarification + +[Numbered list of specific questions, prioritized by: +1. **Critical** (blocks implementation or creates security/data risks) +2. **Important** (significantly affects UX or maintainability) +3. **Nice-to-have** (improves clarity but has reasonable defaults)] + +For each question, include: +- The question itself +- Why it matters +- What assumptions you'd make if it's not answered +- Examples illustrating the ambiguity + +### Recommended Next Steps + +[Concrete actions to resolve the gaps and questions] + +Key principles: +- **Be exhaustively thorough** - assume the spec will be implemented exactly as written, so every gap matters +- **Think like a user** - walk through flows as if you're actually using the feature +- **Consider the unhappy paths** - errors, failures, and edge cases are where most gaps hide +- **Be specific in questions** - avoid "what about errors?" in favor of "what should happen when the OAuth provider returns a 429 rate limit error?" +- **Prioritize ruthlessly** - distinguish between critical blockers and nice-to-have clarifications +- **Use examples liberally** - concrete scenarios make ambiguities clear +- **Reference existing patterns** - when available, reference how similar flows work in the codebase + +Your goal is to ensure that when implementation begins, developers have a crystal-clear understanding of every user journey, every edge case is accounted for, and no critical questions remain unanswered. Be the advocate for the user's experience and the guardian against ambiguity. diff --git a/.cursor/skills/tdd/SKILL.md b/.cursor/skills/tdd/SKILL.md new file mode 100644 index 0000000000..81357345ea --- /dev/null +++ b/.cursor/skills/tdd/SKILL.md @@ -0,0 +1,446 @@ +--- +name: tdd +description: Use when implementing complex logic that needs test coverage - write the test first, watch it fail, write minimal code to pass; ensures tests actually verify behavior by requiring failure first; NOT for UI components, simple CRUD, or straightforward code +--- + +# Test-Driven Development (TDD) + +## Overview + +Write the test first. Watch it fail. Write minimal code to pass. + +**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing. + +**When to use:** Only for complex logic where bugs are likely, or when user explicitly requests tests. + +## When to Use + +**Use TDD when:** + +1. **User explicitly requests tests** +2. **Complex logic where bugs are likely:** + - Complex algorithms + - Business logic with edge cases + - Data transformations + - Critical paths that could break silently + +**Skip TDD for:** + +- ❌ UI components (React components, hooks) +- ❌ Simple CRUD operations +- ❌ Straightforward mappings +- ❌ Anything you're 100% certain is correct +- ❌ Throwaway prototypes +- ❌ Configuration files + +**Verification alternatives when skipping:** + +- Typecheck with `npm run typecheck` +- Lint with `npm run lint` +- Manual testing for UI changes +- Code review confidence + +## The Rule (When Using TDD) + +``` +WHEN WRITING TESTS: NO CODE WITHOUT A FAILING TEST FIRST +``` + +**When you've decided to use TDD** (complex logic, user request): + +Write code before the test? Delete it. Start over. + +**No exceptions when using TDD:** + +- Don't keep it as "reference" +- Don't "adapt" it while writing tests +- Don't look at it +- Delete means delete + +Implement fresh from tests. Period. + +**When NOT using TDD** (UI, simple code): + +- Write code directly +- Verify with typecheck/lint +- Skip the test + +## Red-Green-Refactor + +```dot +digraph tdd_cycle { + rankdir=LR; + red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"]; + verify_red [label="Verify fails\ncorrectly", shape=diamond]; + green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"]; + verify_green [label="Verify passes\nAll green", shape=diamond]; + refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"]; + next [label="Next", shape=ellipse]; + + red -> verify_red; + verify_red -> green [label="yes"]; + verify_red -> red [label="wrong\nfailure"]; + green -> verify_green; + verify_green -> refactor [label="yes"]; + verify_green -> green [label="no"]; + refactor -> verify_green [label="stay\ngreen"]; + verify_green -> next; + next -> red; +} +``` + +### RED - Write Failing Test + +Write one minimal test showing what should happen. + + +```typescript +test('retries failed operations 3 times', async () => { + let attempts = 0; + const operation = () => { + attempts++; + if (attempts < 3) throw new Error('fail'); + return 'success'; + }; + +const result = await retryOperation(operation); + +expect(result).toBe('success'); +expect(attempts).toBe(3); +}); + +```` +Clear name, tests real behavior, one thing + + + +```typescript +test('retry works', async () => { + const mock = jest.fn() + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce('success'); + await retryOperation(mock); + expect(mock).toHaveBeenCalledTimes(3); +}); +```` + +Vague name, tests mock not code + + +**Requirements:** + +- One behavior +- Clear name +- Real code (no mocks unless unavoidable) + +### Verify RED - Watch It Fail + +**MANDATORY. Never skip.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: + +- Test fails (not errors) +- Failure message is expected +- Fails because feature missing (not typos) + +**Test passes?** You're testing existing behavior. Fix test. + +**Test errors?** Fix error, re-run until it fails correctly. + +### GREEN - Minimal Code + +Write simplest code to pass the test. + + +```typescript +async function retryOperation(fn: () => Promise): Promise { + for (let i = 0; i < 3; i++) { + try { + return await fn(); + } catch (e) { + if (i === 2) throw e; + } + } + throw new Error('unreachable'); +} +``` +Just enough to pass + + + +```typescript +async function retryOperation( + fn: () => Promise, + options?: { + maxRetries?: number; + backoff?: 'linear' | 'exponential'; + onRetry?: (attempt: number) => void; + } +): Promise { + // YAGNI +} +``` +Over-engineered + + +Don't add features, refactor other code, or "improve" beyond the test. + +### Verify GREEN - Watch It Pass + +**MANDATORY.** + +```bash +npm test path/to/test.test.ts +``` + +Confirm: + +- Test passes +- Other tests still pass +- Output pristine (no errors, warnings) + +**Test fails?** Fix code, not test. + +**Other tests fail?** Fix now. + +### REFACTOR - Clean Up + +After green only: + +- Remove duplication +- Improve names +- Extract helpers + +Keep tests green. Don't add behavior. + +### Repeat + +Next failing test for next feature. + +## Good Tests + +| Quality | Good | Bad | +| ---------------- | ----------------------------------- | --------------------------------------------------- | +| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` | +| **Clear** | Name describes behavior | `test('test1')` | +| **Shows intent** | Demonstrates desired API | Obscures what code should do | + +## Why Order Matters + +**"I'll write tests after to verify it works"** + +Tests written after code pass immediately. Passing immediately proves nothing: + +- Might test wrong thing +- Might test implementation, not behavior +- Might miss edge cases you forgot +- You never saw it catch the bug + +Test-first forces you to see the test fail, proving it actually tests something. + +**"I already manually tested all the edge cases"** + +Manual testing is ad-hoc. You think you tested everything but: + +- No record of what you tested +- Can't re-run when code changes +- Easy to forget cases under pressure +- "It worked when I tried it" ≠ comprehensive + +Automated tests are systematic. They run the same way every time. + +**"Deleting X hours of work is wasteful"** + +Sunk cost fallacy. The time is already gone. Your choice now: + +- Delete and rewrite with TDD (X more hours, high confidence) +- Keep it and add tests after (30 min, low confidence, likely bugs) + +The "waste" is keeping code you can't trust. Working code without real tests is technical debt. + +**"TDD is dogmatic, being pragmatic means adapting"** + +TDD IS pragmatic: + +- Finds bugs before commit (faster than debugging after) +- Prevents regressions (tests catch breaks immediately) +- Documents behavior (tests show how to use code) +- Enables refactoring (change freely, tests catch breaks) + +"Pragmatic" shortcuts = debugging in production = slower. + +**"Tests after achieve the same goals - it's spirit not ritual"** + +No. Tests-after answer "What does this do?" Tests-first answer "What should this do?" + +Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones. + +Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't). + +30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work. + +## Common Rationalizations (When TDD Applies) + +**Valid reasons to skip TDD:** +| Reason | Reality | +| ----------------------------- | ------------------------------------------------------------- | +| "This is a UI component" | ✅ Correct! Verify with typecheck/manual testing, skip tests. | +| "Simple CRUD, 100% confident" | ✅ Correct! Verify with typecheck/lint, skip tests. | +| "Straightforward mapping" | ✅ Correct! If truly simple, skip tests. | + +**Invalid rationalizations (for complex logic):** +| Excuse | Reality | +| -------------------------------------- | ----------------------------------------------------------------------- | +| "Too simple to test" | If it's complex logic, test it. If truly simple, skip is fine. | +| "I'll test after" | Tests passing immediately prove nothing. | +| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" | +| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. | +| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. | +| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. | +| "Need to explore first" | Fine. Throw away exploration, start with TDD. | +| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. | +| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. | +| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. | + +## Red Flags - When Using TDD + +**These only apply when you've decided to use TDD** (complex logic, user request): + +- Code before test +- Test after implementation +- Test passes immediately +- Can't explain why test failed +- Tests added "later" +- "Keep as reference" or "adapt existing code" +- "Already spent X hours, deleting is wasteful" +- "This is different because..." + +**All of these mean: Delete code. Start over with TDD.** + +**Valid decision to skip TDD:** + +- ✅ "This is a UI component" - Skip TDD, verify with typecheck +- ✅ "Simple CRUD, 100% confident" - Skip TDD, verify with lint +- ✅ "Straightforward code" - Skip TDD if truly simple + +## Example: Bug Fix + +**Bug:** Empty email accepted + +**RED** + +```typescript +test("rejects empty email", async () => { + const result = await submitForm({ email: "" }); + expect(result.error).toBe("Email required"); +}); +``` + +**Verify RED** + +```bash +$ npm test +FAIL: expected 'Email required', got undefined +``` + +**GREEN** + +```typescript +function submitForm(data: FormData) { + if (!data.email?.trim()) { + return { error: "Email required" }; + } + // ... +} +``` + +**Verify GREEN** + +```bash +$ npm test +PASS +``` + +**REFACTOR** +Extract validation for multiple fields if needed. + +## Verification Checklist + +**If using TDD** (complex logic, user request): + +- [ ] Every new complex function has a test +- [ ] Watched each test fail before implementing +- [ ] Each test failed for expected reason (feature missing, not typo) +- [ ] Wrote minimal code to pass each test +- [ ] All tests pass +- [ ] Output pristine (no errors, warnings) +- [ ] Tests use real code (mocks only if unavoidable) +- [ ] Edge cases and errors covered + +Can't check all boxes? You skipped TDD. Start over. + +**If NOT using TDD** (UI, simple code): + +- [ ] Code is straightforward and you're 100% confident +- [ ] `npm run typecheck` passes +- [ ] `npm run lint` passes +- [ ] Manual testing confirms UI works as expected +- [ ] No complex business logic that needs test coverage + +## When Stuck + +| Problem | Solution | +| ---------------------- | -------------------------------------------------------------------- | +| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. | +| Test too complicated | Design too complicated. Simplify interface. | +| Must mock everything | Code too coupled. Use dependency injection. | +| Test setup huge | Extract helpers. Still complex? Simplify design. | + +## Debugging Integration + +**Bug in complex logic?** +Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression. + +**Bug in UI component or simple code?** +Fix directly. Verify with typecheck/lint/manual testing. + +**Decision criteria:** + +- Complex algorithm/business logic → Write test +- UI component/straightforward fix → Skip test + +## Final Rules + +**When to use TDD:** + +1. **User explicitly requests tests** +2. **Complex logic where bugs are likely** (algorithms, business logic, data transformations) + +**When to skip TDD:** + +- UI components (React components, hooks) +- Simple CRUD operations +- Straightforward mappings +- Anything you're 100% certain is correct + +**If using TDD:** + +``` +Production code → test exists and failed first +Otherwise → not TDD +``` + +**If skipping TDD:** + +``` +Production code → typecheck + lint pass +Manual verification for UI changes +Code review confidence +``` + +**Only deterministic unit tests** - no integration tests, no complex mocking, no async complexity. diff --git a/.cursor/skills/test-browser/SKILL.md b/.cursor/skills/test-browser/SKILL.md new file mode 100644 index 0000000000..d1f5059053 --- /dev/null +++ b/.cursor/skills/test-browser/SKILL.md @@ -0,0 +1,339 @@ +--- +name: test-browser +description: Run browser tests on pages affected by current PR or branch +argument-hint: '[PR number, branch name, or ''current'' for current branch]' +--- + +# Browser Test Command + +Run end-to-end browser tests on pages affected by a PR or branch changes using agent-browser CLI. + +## CRITICAL: Use agent-browser CLI Only + +**DO NOT use Chrome MCP tools (mcp__claude-in-chrome__*).** + +This command uses the `agent-browser` CLI exclusively. The agent-browser CLI is a Bash-based tool from Vercel that runs headless Chromium. It is NOT the same as Chrome browser automation via MCP. + +If you find yourself calling `mcp__claude-in-chrome__*` tools, STOP. Use `agent-browser` Bash commands instead. + +## Introduction + +QA Engineer specializing in browser-based end-to-end testing + +This command tests affected pages in a real browser, catching issues that unit tests miss: +- JavaScript integration bugs +- CSS/layout regressions +- User workflow breakages +- Console errors + +## Prerequisites + + +- Local development server running (e.g., `bin/dev`, `rails server`, `npm run dev`) +- agent-browser CLI installed (see Setup below) +- Git repository with changes to test + + +## Setup + +**Check installation:** +```bash +command -v agent-browser >/dev/null 2>&1 && echo "Installed" || echo "NOT INSTALLED" +``` + +**Install if needed:** +```bash +npm install -g agent-browser +agent-browser install # Downloads Chromium (~160MB) +``` + +See the `agent-browser` skill for detailed usage. + +## Main Tasks + +### 0. Verify agent-browser Installation + +Before starting ANY browser testing, verify agent-browser is installed: + +```bash +command -v agent-browser >/dev/null 2>&1 && echo "Ready" || (echo "Installing..." && npm install -g agent-browser && agent-browser install) +``` + +If installation fails, inform the user and stop. + +### 1. Ask Browser Mode + + + +Before starting tests, ask user if they want to watch the browser: + +Use AskUserQuestion with: +- Question: "Do you want to watch the browser tests run?" +- Options: + 1. **Headed (watch)** - Opens visible browser window so you can see tests run + 2. **Headless (faster)** - Runs in background, faster but invisible + +Store the choice and use `--headed` flag when user selects "Headed". + + + +### 2. Determine Test Scope + + $ARGUMENTS + + + +**If PR number provided:** +```bash +gh pr view [number] --json files -q '.files[].path' +``` + +**If 'current' or empty:** +```bash +git diff --name-only main...HEAD +``` + +**If branch name provided:** +```bash +git diff --name-only main...[branch] +``` + + + +### 3. Map Files to Routes + + + +Map changed files to testable routes: + +| File Pattern | Route(s) | +|-------------|----------| +| `app/views/users/*` | `/users`, `/users/:id`, `/users/new` | +| `app/controllers/settings_controller.rb` | `/settings` | +| `app/javascript/controllers/*_controller.js` | Pages using that Stimulus controller | +| `app/components/*_component.rb` | Pages rendering that component | +| `app/views/layouts/*` | All pages (test homepage at minimum) | +| `app/assets/stylesheets/*` | Visual regression on key pages | +| `app/helpers/*_helper.rb` | Pages using that helper | +| `src/app/*` (Next.js) | Corresponding routes | +| `src/components/*` | Pages using those components | + +Build a list of URLs to test based on the mapping. + + + +### 4. Verify Server is Running + + + +Before testing, verify the local server is accessible: + +```bash +agent-browser open http://localhost:3000 +agent-browser snapshot -i +``` + +If server is not running, inform user: +```markdown +**Server not running** + +Please start your development server: +- Rails: `bin/dev` or `rails server` +- Node/Next.js: `npm run dev` + +Then run `/test-browser` again. +``` + + + +### 5. Test Each Affected Page + + + +For each affected route, use agent-browser CLI commands (NOT Chrome MCP): + +**Step 1: Navigate and capture snapshot** +```bash +agent-browser open "http://localhost:3000/[route]" +agent-browser snapshot -i +``` + +**Step 2: For headed mode (visual debugging)** +```bash +agent-browser --headed open "http://localhost:3000/[route]" +agent-browser --headed snapshot -i +``` + +**Step 3: Verify key elements** +- Use `agent-browser snapshot -i` to get interactive elements with refs +- Page title/heading present +- Primary content rendered +- No error messages visible +- Forms have expected fields + +**Step 4: Test critical interactions** +```bash +agent-browser click @e1 # Use ref from snapshot +agent-browser snapshot -i +``` + +**Step 5: Take screenshots** +```bash +agent-browser screenshot page-name.png +agent-browser screenshot --full page-name-full.png # Full page +``` + + + +### 6. Human Verification (When Required) + + + +Pause for human input when testing touches: + +| Flow Type | What to Ask | +|-----------|-------------| +| OAuth | "Please sign in with [provider] and confirm it works" | +| Email | "Check your inbox for the test email and confirm receipt" | +| Payments | "Complete a test purchase in sandbox mode" | +| SMS | "Verify you received the SMS code" | +| External APIs | "Confirm the [service] integration is working" | + +Use AskUserQuestion: +```markdown +**Human Verification Needed** + +This test touches the [flow type]. Please: +1. [Action to take] +2. [What to verify] + +Did it work correctly? +1. Yes - continue testing +2. No - describe the issue +``` + + + +### 7. Handle Failures + + + +When a test fails: + +1. **Document the failure:** + - Screenshot the error state: `agent-browser screenshot error.png` + - Note the exact reproduction steps + +2. **Ask user how to proceed:** + ```markdown + **Test Failed: [route]** + + Issue: [description] + Console errors: [if any] + + How to proceed? + 1. Fix now - I'll help debug and fix + 2. Create todo - Add to todos/ for later + 3. Skip - Continue testing other pages + ``` + +3. **If "Fix now":** + - Investigate the issue + - Propose a fix + - Apply fix + - Re-run the failing test + +4. **If "Create todo":** + - Create `{id}-pending-p1-browser-test-{description}.md` + - Continue testing + +5. **If "Skip":** + - Log as skipped + - Continue testing + + + +### 8. Test Summary + + + +After all tests complete, present summary: + +```markdown +## Browser Test Results + +**Test Scope:** PR #[number] / [branch name] +**Server:** http://localhost:3000 + +### Pages Tested: [count] + +| Route | Status | Notes | +|-------|--------|-------| +| `/users` | Pass | | +| `/settings` | Pass | | +| `/dashboard` | Fail | Console error: [msg] | +| `/checkout` | Skip | Requires payment credentials | + +### Console Errors: [count] +- [List any errors found] + +### Human Verifications: [count] +- OAuth flow: Confirmed +- Email delivery: Confirmed + +### Failures: [count] +- `/dashboard` - [issue description] + +### Created Todos: [count] +- `005-pending-p1-browser-test-dashboard-error.md` + +### Result: [PASS / FAIL / PARTIAL] +``` + + + +## Quick Usage Examples + +```bash +# Test current branch changes +/test-browser + +# Test specific PR +/test-browser 847 + +# Test specific branch +/test-browser feature/new-dashboard +``` + +## agent-browser CLI Reference + +**ALWAYS use these Bash commands. NEVER use mcp__claude-in-chrome__* tools.** + +```bash +# Navigation +agent-browser open # Navigate to URL +agent-browser back # Go back +agent-browser close # Close browser + +# Snapshots (get element refs) +agent-browser snapshot -i # Interactive elements with refs (@e1, @e2, etc.) +agent-browser snapshot -i --json # JSON output + +# Interactions (use refs from snapshot) +agent-browser click @e1 # Click element +agent-browser fill @e1 "text" # Fill input +agent-browser type @e1 "text" # Type without clearing +agent-browser press Enter # Press key + +# Screenshots +agent-browser screenshot out.png # Viewport screenshot +agent-browser screenshot --full out.png # Full page screenshot + +# Headed mode (visible browser) +agent-browser --headed open # Open with visible browser +agent-browser --headed click @e1 # Click in visible browser + +# Wait +agent-browser wait @e1 # Wait for element +agent-browser wait 2000 # Wait milliseconds +``` diff --git a/.codex/skills/test-xcode/SKILL.md b/.cursor/skills/test-xcode/SKILL.md similarity index 100% rename from .codex/skills/test-xcode/SKILL.md rename to .cursor/skills/test-xcode/SKILL.md diff --git a/.codex/skills/trace/SKILL.md b/.cursor/skills/trace/SKILL.md similarity index 100% rename from .codex/skills/trace/SKILL.md rename to .cursor/skills/trace/SKILL.md diff --git a/.cursor/skills/triage/SKILL.md b/.cursor/skills/triage/SKILL.md new file mode 100644 index 0000000000..e40fe1907e --- /dev/null +++ b/.cursor/skills/triage/SKILL.md @@ -0,0 +1,311 @@ +--- +name: triage +description: Triage and categorize findings for the CLI todo system +argument-hint: '[findings list or source type]' +disable-model-invocation: true +--- + +- First set the /model to Haiku +- Then read all pending todos in the todos/ directory + +Present all findings, decisions, or issues here one by one for triage. The goal is to go through each item and decide whether to add it to the CLI todo system. + +**IMPORTANT: DO NOT CODE ANYTHING DURING TRIAGE!** + +This command is for: + +- Triaging code review findings +- Processing security audit results +- Reviewing performance analysis +- Handling any other categorized findings that need tracking + +## Workflow + +### Step 1: Present Each Finding + +For each finding, present in this format: + +``` +--- +Issue #X: [Brief Title] + +Severity: 🔴 P1 (CRITICAL) / 🟡 P2 (IMPORTANT) / 🔵 P3 (NICE-TO-HAVE) + +Category: [Security/Performance/Architecture/Bug/Feature/etc.] + +Description: +[Detailed explanation of the issue or improvement] + +Location: [file_path:line_number] + +Problem Scenario: +[Step by step what's wrong or could happen] + +Proposed Solution: +[How to fix it] + +Estimated Effort: [Small (< 2 hours) / Medium (2-8 hours) / Large (> 8 hours)] + +--- +Do you want to add this to the todo list? +1. yes - create todo file +2. next - skip this item +3. custom - modify before creating +``` + +### Step 2: Handle User Decision + +**When user says "yes":** + +1. **Update existing todo file** (if it exists) or **Create new filename:** + + If todo already exists (from code review): + + - Rename file from `{id}-pending-{priority}-{desc}.md` → `{id}-ready-{priority}-{desc}.md` + - Update YAML frontmatter: `status: pending` → `status: ready` + - Keep issue_id, priority, and description unchanged + + If creating new todo: + + ``` + {next_id}-ready-{priority}-{brief-description}.md + ``` + + Priority mapping: + + - 🔴 P1 (CRITICAL) → `p1` + - 🟡 P2 (IMPORTANT) → `p2` + - 🔵 P3 (NICE-TO-HAVE) → `p3` + + Example: `042-ready-p1-transaction-boundaries.md` + +2. **Update YAML frontmatter:** + + ```yaml + --- + status: ready # IMPORTANT: Change from "pending" to "ready" + priority: p1 # or p2, p3 based on severity + issue_id: "042" + tags: [category, relevant-tags] + dependencies: [] + --- + ``` + +3. **Populate or update the file:** + + ```yaml + # [Issue Title] + + ## Problem Statement + [Description from finding] + + ## Findings + - [Key discoveries] + - Location: [file_path:line_number] + - [Scenario details] + + ## Proposed Solutions + + ### Option 1: [Primary solution] + - **Pros**: [Benefits] + - **Cons**: [Drawbacks if any] + - **Effort**: [Small/Medium/Large] + - **Risk**: [Low/Medium/High] + + ## Recommended Action + [Filled during triage - specific action plan] + + ## Technical Details + - **Affected Files**: [List files] + - **Related Components**: [Components affected] + - **Database Changes**: [Yes/No - describe if yes] + + ## Resources + - Original finding: [Source of this issue] + - Related issues: [If any] + + ## Acceptance Criteria + - [ ] [Specific success criteria] + - [ ] Tests pass + - [ ] Code reviewed + + ## Work Log + + ### {date} - Approved for Work + **By:** Claude Triage System + **Actions:** + - Issue approved during triage session + - Status changed from pending → ready + - Ready to be picked up and worked on + + **Learnings:** + - [Context and insights] + + ## Notes + Source: Triage session on {date} + ``` + +4. **Confirm approval:** "✅ Approved: `{new_filename}` (Issue #{issue_id}) - Status: **ready** → Ready to work on" + +**When user says "next":** + +- **Delete the todo file** - Remove it from todos/ directory since it's not relevant +- Skip to the next item +- Track skipped items for summary + +**When user says "custom":** + +- Ask what to modify (priority, description, details) +- Update the information +- Present revised version +- Ask again: yes/next/custom + +### Step 3: Continue Until All Processed + +- Process all items one by one +- Track using TodoWrite for visibility +- Don't wait for approval between items - keep moving + +### Step 4: Final Summary + +After all items processed: + +````markdown +## Triage Complete + +**Total Items:** [X] **Todos Approved (ready):** [Y] **Skipped:** [Z] + +### Approved Todos (Ready for Work): + +- `042-ready-p1-transaction-boundaries.md` - Transaction boundary issue +- `043-ready-p2-cache-optimization.md` - Cache performance improvement ... + +### Skipped Items (Deleted): + +- Item #5: [reason] - Removed from todos/ +- Item #12: [reason] - Removed from todos/ + +### Summary of Changes Made: + +During triage, the following status updates occurred: + +- **Pending → Ready:** Filenames and frontmatter updated to reflect approved status +- **Deleted:** Todo files for skipped findings removed from todos/ directory +- Each approved file now has `status: ready` in YAML frontmatter + +### Next Steps: + +1. View approved todos ready for work: + ```bash + ls todos/*-ready-*.md + ``` +```` + +2. Start work on approved items: + + ```bash + /resolve_todo_parallel # Work on multiple approved items efficiently + ``` + +3. Or pick individual items to work on + +4. As you work, update todo status: + - Ready → In Progress (in your local context as you work) + - In Progress → Complete (rename file: ready → complete, update frontmatter) + +``` + +## Example Response Format + +``` + +--- + +Issue #5: Missing Transaction Boundaries for Multi-Step Operations + +Severity: 🔴 P1 (CRITICAL) + +Category: Data Integrity / Security + +Description: The google_oauth2_connected callback in GoogleOauthCallbacks concern performs multiple database operations without transaction protection. If any step fails midway, the database is left in an inconsistent state. + +Location: app/controllers/concerns/google_oauth_callbacks.rb:13-50 + +Problem Scenario: + +1. User.update succeeds (email changed) +2. Account.save! fails (validation error) +3. Result: User has changed email but no associated Account +4. Next login attempt fails completely + +Operations Without Transaction: + +- User confirmation (line 13) +- Waitlist removal (line 14) +- User profile update (line 21-23) +- Account creation (line 28-37) +- Avatar attachment (line 39-45) +- Journey creation (line 47) + +Proposed Solution: Wrap all operations in ApplicationRecord.transaction do ... end block + +Estimated Effort: Small (30 minutes) + +--- + +Do you want to add this to the todo list? + +1. yes - create todo file +2. next - skip this item +3. custom - modify before creating + +``` + +## Important Implementation Details + +### Status Transitions During Triage + +**When "yes" is selected:** +1. Rename file: `{id}-pending-{priority}-{desc}.md` → `{id}-ready-{priority}-{desc}.md` +2. Update YAML frontmatter: `status: pending` → `status: ready` +3. Update Work Log with triage approval entry +4. Confirm: "✅ Approved: `{filename}` (Issue #{issue_id}) - Status: **ready**" + +**When "next" is selected:** +1. Delete the todo file from todos/ directory +2. Skip to next item +3. No file remains in the system + +### Progress Tracking + +Every time you present a todo as a header, include: +- **Progress:** X/Y completed (e.g., "3/10 completed") +- **Estimated time remaining:** Based on how quickly you're progressing +- **Pacing:** Monitor time per finding and adjust estimate accordingly + +Example: +``` + +Progress: 3/10 completed | Estimated time: ~2 minutes remaining + +``` + +### Do Not Code During Triage + +- ✅ Present findings +- ✅ Make yes/next/custom decisions +- ✅ Update todo files (rename, frontmatter, work log) +- ❌ Do NOT implement fixes or write code +- ❌ Do NOT add detailed implementation details +- ❌ That's for /resolve_todo_parallel phase +``` + +When done give these options + +```markdown +What would you like to do next? + +1. run /resolve_todo_parallel to resolve the todos +2. commit the todos +3. nothing, go chill +``` diff --git a/.cursor/skills/update-app-design/SKILL.md b/.cursor/skills/update-app-design/SKILL.md new file mode 100644 index 0000000000..c2768e33a4 --- /dev/null +++ b/.cursor/skills/update-app-design/SKILL.md @@ -0,0 +1,303 @@ +--- +allowed-tools: Read, Glob, Grep, Write, MultiEdit, TodoWrite, Bash +description: Update existing app design document based on codebase changes and project evolution +name: update-app-design +--- + +# Sync Application Design Document + +**User Request:** $ARGUMENTS + +## Context + +- Project root: !`pwd` +- Package.json: @package.json +- Current design doc: @.claude/rules/1-app-design-document.mdc +- Last modified: !`stat -f "%Sm" .claude/rules/1-app-design-document.mdc 2>/dev/null || echo "No existing document"` + +## Goal + +Update the existing Application Design Document to reflect current codebase state, new features, changed priorities, and project evolution. Maintain consistency with the original document while incorporating new information. + +## Process + +### 1. Document Analysis + +- Read and understand the existing 1-app-design-document.mdc +- Establish baseline understanding of documented features +- Note the document's structure and tone +- Identify areas that may need updates + +### 2. Codebase Change Detection + +**Think deeply about what has changed in the codebase since the document was last updated.** + +Analyze for: + +- **New Features:** Components, modules, or capabilities added +- **Modified Flows:** Changes to user journeys or business logic +- **Removed Features:** Deprecated or deleted functionality +- **Architecture Evolution:** New patterns, services, or integrations +- **Scale Changes:** Growth in complexity or user base +- **Security Updates:** New authentication/authorization patterns + +_Extended thinking helps identify subtle changes, understand how new features integrate with existing ones, and recognize patterns that indicate architectural evolution._ + +### 3. Interactive Update Session + +**CRITICAL:** Ask project stage question FIRST to assess if priorities have changed: + +- Use lettered/numbered options for easy response +- Focus on what has changed and why +- Gather context for accurate updates + +### 4. Update Project Configuration + +If project stage or priorities have changed: + +- Update `.claude/rules/3-project-status.mdc` +- Adjust DO/DON'T lists for new priorities +- Document any stage transitions + +### 5. Sync Document + +Update the document incrementally: + +- Preserve accurate existing content +- Add new sections only when necessary +- Update outdated information +- Maintain consistent tone and structure + +### 6. Save Updated Document + +- Backup suggestion if major changes +- Overwrite existing 1-app-design-document.mdc +- Note what was updated + +## Required Questions Template + +### 🎯 CRITICAL: Project Evolution Assessment (Ask First!) + +**1. Has your project stage evolved since the last update?** + +a) **Same Stage** - Still in [current stage], just adding features +b) **Stage Evolution** - Moved from [current] to next stage +c) **Major Pivot** - Significant change in direction or purpose +d) **Help Me Assess** - Let's review current state together + +**2. Have your development priorities changed?** + +Based on your current stage, are these still your priorities? + +[Show current DO/DON'T lists from `.claude/rules/3-project-status.mdc`] + +a) **Same Priorities** - These still reflect our focus +b) **Adjusted Priorities** - Some changes needed (please specify) +c) **New Focus Areas** - Different priorities based on learnings +d) **Stage-Based Change** - Priorities changed due to stage evolution + +### 📊 Change Identification Questions + +**3. What major features have been added?** + +Please describe any significant new capabilities, modules, or user-facing features added since the last update. + +**4. Have any core user flows changed?** + +a) **Authentication/Authorization** - Login, permissions, security +b) **Main User Journey** - Primary application workflow +c) **Data Management** - How users create/edit/delete data +d) **Integration Points** - External service connections +e) **None/Minor Only** - No significant flow changes + +**5. What has been removed or deprecated?** + +List any features, integrations, or capabilities that have been removed or are being phased out. + +**6. Have you integrated new external services?** + +a) **Payment Processing** - Stripe, PayPal, etc. +b) **Communication** - Email, SMS, notifications +c) **Analytics/Monitoring** - Tracking, logging services +d) **AI/ML Services** - LLMs, image processing, etc. +e) **Other** - Please specify +f) **None** - No new integrations + +### 🚀 Future Direction Questions + +**7. How has user feedback influenced changes?** + +Describe any significant pivots or adjustments made based on user feedback or usage patterns. + +**8. What are your updated success metrics?** + +Have your KPIs or success measurements changed? Current focus: + +- User growth targets? +- Revenue goals? +- Engagement metrics? +- Performance benchmarks? + +**9. What's the next major milestone?** + +a) **Feature Release** - Specific new capability +b) **Scale Milestone** - User/revenue target +c) **Technical Goal** - Performance, security, architecture +d) **Business Goal** - Partnerships, funding, market expansion + +## Update Strategy + +### Incremental Updates + +- **Preserve:** Keep accurate existing content +- **Enhance:** Add new information to existing sections +- **Replace:** Update outdated or incorrect information +- **Remove:** Mark deprecated features appropriately + +### Change Documentation + +- **New Features:** Add to relevant feature categories +- **Modified Flows:** Update user journey descriptions +- **Architecture Changes:** Reflect in system architecture section +- **Business Evolution:** Update goals and success metrics + +### Consistency Maintenance + +- Keep the same professional, accessible tone +- Maintain technology-agnostic descriptions +- Focus on WHAT not HOW +- Preserve document structure + +## Document Update Areas + +### Always Review: + +1. **Introduction** + + - Update if purpose or audience has shifted + - Reflect any pivot in value proposition + +2. **Core Features** + + - Add new feature categories if needed + - Update existing features with enhancements + - Mark removed features as deprecated + +3. **User Experience** + + - Update user journeys with new flows + - Add new user personas if applicable + - Reflect UI/UX improvements + +4. **System Architecture** + + - Add new integrations + - Update data flow diagrams + - Reflect new security patterns + +5. **Business Logic** + + - Update rules and workflows + - Reflect new validation requirements + - Document new business constraints + +6. **Future Considerations** + - Update roadmap based on progress + - Add new planned features + - Reflect lessons learned + +## Execution Steps + +### 1. Start with Analysis + +```bash +# Check when document was last updated +stat -f "%Sm" .claude/rules/1-app-design-document.mdc + +# Review recent commits for feature changes +git log --oneline --since="30 days ago" | head -20 +``` + +**Think deeply about:** "What has fundamentally changed in this application? How have new features altered the original vision? What patterns indicate architectural evolution?" + +### 2. Interactive Q&A + +- **MUST ASK PROJECT STAGE FIRST** +- Present all questions clearly +- Wait for complete responses + +### 3. Update Project Status (if needed) + +If stage or priorities changed, update both: + +```markdown +# In `.claude/rules/3-project-status.mdc` + +## Project Status + +**Current Stage**: [New Stage] + +### DO Care About (Production-Ready Foundation) + +[Updated priorities] + +### DO NOT Care About (Skip for Velocity) + +[Updated items to skip] +``` + +### 4. Sync Document + +- Make targeted updates +- Preserve document quality +- Add version note if helpful: + +```markdown + +``` + +### 5. Save and Backup + +```bash +# Optional: Create backup +cp .claude/rules/1-app-design-document.mdc .claude/rules/1-app-design-document.backup.mdc + +# Save updated document +# Overwrite .claude/rules/1-app-design-document.mdc +``` + +## Key Principles + +### DO: + +- **Preserve Quality:** Maintain document's professional tone +- **Incremental Updates:** Don't rewrite unnecessarily +- **Clear Changes:** Make updates obvious and well-integrated +- **User Focus:** Keep emphasis on user value +- **Stage Awareness:** Align with current project maturity + +### DON'T: + +- **Complete Rewrite:** Unless fundamentally pivoted +- **Technical Details:** Maintain high-level focus +- **Break Structure:** Keep established organization +- **Lose History:** Preserve context of major decisions +- **Skip Analysis:** Always understand current state first + +## Output + +- **Format:** Markdown (`.mdc`) +- **Location:** `.claude/rules/` +- **Filename:** `1-app-design-document.mdc` (overwrites) +- **Backup:** Suggest if major changes + +## Final Checklist + +1. ✅ Read existing document completely +2. ✅ Analyze codebase changes thoroughly +3. ✅ Ask project stage question FIRST +4. ✅ Update `.claude/rules/3-project-status.mdc` if stage/priorities changed +5. ✅ Make incremental, targeted updates +6. ✅ Preserve document quality and tone +7. ✅ Suggest backup for major changes +8. ✅ Consider 2-tech-stack.mdc updates if needed diff --git a/.cursor/skills/update-tech-stack/SKILL.md b/.cursor/skills/update-tech-stack/SKILL.md new file mode 100644 index 0000000000..d30c2e4ea3 --- /dev/null +++ b/.cursor/skills/update-tech-stack/SKILL.md @@ -0,0 +1,313 @@ +--- +allowed-tools: Read, Glob, Grep, Write, MultiEdit, TodoWrite, Bash +description: Update tech stack documentation based on dependency changes and technical evolution +name: update-tech-stack +--- + +# Update Tech Stack Documentation + +**User Request:** $ARGUMENTS + +## Context + +- Project root: !`pwd` +- Package.json: @package.json +- Current tech doc: @.claude/rules/2-tech-stack.mdc +- Last modified: !`stat -f "%Sm" .claude/rules/2-tech-stack.mdc 2>/dev/null || echo "No existing document"` +- Recent package changes: !`git diff HEAD~10 HEAD -- package.json 2>/dev/null | grep -E "^[+-]" | head -20 || echo "No recent changes"` + +## Goal + +Update the existing Tech Stack Documentation to reflect current technical state, dependency changes, new tools adoption, and infrastructure evolution. Maintain technical accuracy while documenting all changes. + +## Process + +### 1. Document Analysis + +- Read existing 2-tech-stack.mdc thoroughly +- Note documented versions and configurations +- Understand current technical baseline +- Identify sections that may need updates + +### 2. Technical Change Detection + +**Think deeply about technical evolution in the codebase.** + +Analyze for: + +- **Dependency Changes:** New packages, version updates, removals +- **Framework Evolution:** Major version upgrades, breaking changes +- **Tool Adoption:** New dev tools, linters, formatters, testing frameworks +- **Infrastructure Shifts:** Deployment, hosting, monitoring changes +- **Database Evolution:** Schema changes, new ORMs, migrations +- **Integration Updates:** New APIs, services, authentication providers + +_Extended thinking helps identify cascading dependency updates, understand version compatibility issues, and recognize architectural implications of technical changes._ + +### 3. Automated Comparison + +```bash +# Compare current vs documented dependencies +# Check for version mismatches +# Identify new configuration files +# Detect new tool configurations +``` + +### 4. Interactive Technical Q&A + +Ask targeted questions about: + +- Non-discoverable infrastructure changes +- Deployment and hosting updates +- New external service integrations +- Workflow and process changes + +### 5. Update Documentation + +Update incrementally: + +- Preserve accurate technical information +- Update version numbers precisely +- Add new sections for major additions +- Mark deprecated technologies + +### 6. Save and Verify + +- Suggest backup for major changes +- Verify all versions are accurate + +## Technical Questions Template + +### 🔄 Version Updates & Dependencies + +**1. Which major dependencies have been updated?** + +Review your recent dependency changes: + +a) **Framework upgrades** (Next.js, React, etc.) with breaking changes +b) **Tool updates** (TypeScript, ESLint, etc.) requiring config changes +c) **New dependencies** added for features or development +d) **Removed packages** that are no longer needed +e) **All of the above** - Major technical overhaul + +**2. Have you changed your package manager or Node version?** + +a) **Same setup** - No changes to tooling +b) **Node upgrade** - Updated Node.js version +c) **Package manager switch** - Changed from npm/yarn/pnpm +d) **Monorepo adoption** - Moved to workspace setup + +### 🏗️ Infrastructure Evolution + +**3. Have your deployment or hosting arrangements changed?** + +Current deployment is documented as: [show from existing doc] + +a) **Same platform** - Just configuration updates +b) **Platform migration** - Moved to different provider +c) **Architecture change** - Serverless, containers, etc. +d) **Multi-region** - Expanded geographic deployment + +**4. Database or storage changes?** + +a) **Version upgrade** - Same DB, newer version +b) **Migration** - Switched database systems +c) **New caching** - Added Redis, Memcached, etc. +d) **Storage addition** - New file storage, CDN +e) **No changes** - Same setup as before + +### 🛠️ Development Workflow Updates + +**5. New development tools or practices?** + +Select all that apply: + +- [ ] New testing framework or strategy +- [ ] Added code quality tools (linters, formatters) +- [ ] CI/CD pipeline changes +- [ ] Docker/containerization adoption +- [ ] New build tools or bundlers +- [ ] Performance monitoring tools + +**6. External service integrations?** + +Have you added or changed: + +a) **Payment processing** - New or updated provider +b) **Authentication** - Different auth service +c) **Email/SMS** - Communication service changes +d) **Monitoring** - New error tracking or analytics +e) **APIs** - Additional third-party integrations +f) **None** - Same external services + +### 🔐 Security & Compliance + +**7. Security tool adoption?** + +- [ ] Vulnerability scanning (Snyk, etc.) +- [ ] Secret management changes +- [ ] New authentication methods +- [ ] Compliance tools (GDPR, etc.) +- [ ] Security headers/policies +- [ ] None of the above + +## Update Strategy + +### Version Precision + +```typescript +// ❌ Outdated +"next": "^13.0.0" + +// ✅ Current and precise +"next": "14.2.5" +``` + +### Configuration Updates + +- Update all config examples to match current files +- Include new configuration options +- Remove deprecated settings +- Add migration notes for breaking changes + +### New Technology Sections + +When adding major new tools: + +```markdown +### [New Tool Category] + +**Tool:** [Name] [Version] +**Purpose:** [Why it was adopted] +**Configuration:** [Key settings] +**Integration:** [How it connects with other tools] +``` + +## Document Update Areas + +### Always Check: + +1. **package.json changes** + + ```bash + # Compare all dependencies + # Note version changes + # Identify new packages + ``` + +2. **Configuration files** + + - tsconfig.json updates + - New .config files + - Build tool configurations + - Linting rule changes + +3. **Development scripts** + + - New npm/pnpm scripts + - Changed command purposes + - Removed scripts + +4. **Infrastructure files** + - Dockerfile changes + - CI/CD workflows + - Deployment configs + - Environment examples + +### Conditional Updates: + +- **Architecture:** Only if fundamental changes +- **Conventions:** Only if standards changed + +## Execution Steps + +### 1. Start with Analysis + +```bash +# Check current dependencies vs documented +diff <(jq -r '.dependencies | keys[]' package.json | sort) \ + <(grep -E '^\*\*.*:' .claude/rules/2-tech-stack.mdc | cut -d: -f1 | sed 's/\*//g' | sort) + +# Review recent dependency commits +git log --oneline --grep="dep" --since="30 days ago" + +# Check for new config files +find . -name "*.config.*" -newer .claude/rules/2-tech-stack.mdc 2>/dev/null +``` + +**Think deeply about:** "What technical decisions drove these changes? How do version updates affect the overall architecture? What new capabilities do these tools enable?" + +### 2. Interactive Q&A + +- Present technical questions clearly +- Include current state from documentation +- Wait for detailed responses + +### 3. Update Documentation + +Follow incremental approach: + +```markdown + + +**Before:** React 18.2.0 +**After:** React 18.3.1 - Includes new compiler optimizations + + + +### Code Quality Tools + +**New Addition:** + +- **Biome:** 1.8.3 - Replaced ESLint and Prettier + - Faster performance (10x) + - Single configuration file + - Built-in formatting +``` + +### 4. Save and Backup + +```bash +# Optional backup +cp .claude/rules/2-tech-stack.mdc .claude/rules/2-tech-stack.backup.md + +# Save updated document +# Overwrite .claude/rules/2-tech-stack.mdc +``` + +## Key Principles + +### DO: + +- **Exact Versions:** Use precise version numbers from lock files +- **Config Accuracy:** Match actual configuration files +- **Change Rationale:** Explain why tools were adopted/changed +- **Migration Notes:** Document breaking changes and updates +- **Performance Impact:** Note improvements or concerns + +### DON'T: + +- **Generic Updates:** Avoid vague version ranges +- **Assumption:** Verify every technical detail +- **Old Information:** Remove outdated configurations +- **Wishful Documentation:** Only document what exists +- **Sensitive Data:** Never include secrets or keys + +## Output + +- **Format:** Markdown (`.mdc`) +- **Location:** `.claude/rules/` +- **Filename:** `2-tech-stack.mdc` (overwrites) +- **Backup:** Suggest for major changes + +## Final Checklist + +1. ✅ Read existing 2-tech-stack.mdc completely +2. ✅ Analyze all dependency changes +3. ✅ Check configuration file updates +4. ✅ Review infrastructure changes +5. ✅ Ask targeted technical questions +6. ✅ Update with exact versions +7. ✅ Include configuration examples +8. ✅ Suggest backup if major changes +9. ✅ Verify technical accuracy diff --git a/.cursor/skills/workflows-brainstorm/SKILL.md b/.cursor/skills/workflows-brainstorm/SKILL.md new file mode 100644 index 0000000000..353a0b7bf8 --- /dev/null +++ b/.cursor/skills/workflows-brainstorm/SKILL.md @@ -0,0 +1,129 @@ +--- +name: workflows-brainstorm +description: Explore requirements and approaches through collaborative dialogue before planning implementation +argument-hint: '[feature idea or problem to explore]' +--- + +# Brainstorm a Feature or Improvement + +**Note: The current year is 2026.** Use this when dating brainstorm documents. + +Brainstorming helps answer **WHAT** to build through collaborative dialogue. It precedes `/workflows:plan`, which answers **HOW** to build it. + +**Process knowledge:** Load the `brainstorming` skill for detailed question techniques, approach exploration patterns, and YAGNI principles. + +## Feature Description + + #$ARGUMENTS + +**If the feature description above is empty, ask the user:** "What would you like to explore? Please describe the feature, problem, or improvement you're thinking about." + +Do not proceed until you have a feature description from the user. + +## Execution Flow + +### Phase 0: Assess Requirements Clarity + +Evaluate whether brainstorming is needed based on the feature description. + +**Clear requirements indicators:** +- Specific acceptance criteria provided +- Referenced existing patterns to follow +- Described exact expected behavior +- Constrained, well-defined scope + +**If requirements are already clear:** +Use **AskUserQuestion tool** to suggest: "Your requirements seem detailed enough to proceed directly to planning. Should I run `/workflows:plan` instead, or would you like to explore the idea further?" + +### Phase 1: Understand the Idea + +#### 1.1 Repository Research (Lightweight) + +Run a quick repo scan to understand existing patterns: + +- Task repo-research-analyst("Understand existing patterns related to: ") + +Focus on: similar features, established patterns, CLAUDE.md guidance. + +#### 1.2 Collaborative Dialogue + +Use the **AskUserQuestion tool** to ask questions **one at a time**. + +**Guidelines (see `brainstorming` skill for detailed techniques):** +- Prefer multiple choice when natural options exist +- Start broad (purpose, users) then narrow (constraints, edge cases) +- Validate assumptions explicitly +- Ask about success criteria + +**Exit condition:** Continue until the idea is clear OR user says "proceed" + +### Phase 2: Explore Approaches + +Propose **2-3 concrete approaches** based on research and conversation. + +For each approach, provide: +- Brief description (2-3 sentences) +- Pros and cons +- When it's best suited + +Lead with your recommendation and explain why. Apply YAGNI—prefer simpler solutions. + +Use **AskUserQuestion tool** to ask which approach the user prefers. + +### Phase 3: Capture the Design + +Write a brainstorm document to `docs/brainstorms/YYYY-MM-DD--brainstorm.md`. + +**Document structure:** See the `brainstorming` skill for the template format. Key sections: What We're Building, Why This Approach, Key Decisions, Open Questions. + +Ensure `docs/brainstorms/` directory exists before writing. + +**IMPORTANT:** Before proceeding to Phase 4, check if there are any Open Questions listed in the brainstorm document. If there are open questions, YOU MUST ask the user about each one using AskUserQuestion before offering to proceed to planning. Move resolved questions to a "Resolved Questions" section. + +### Phase 4: Handoff + +Use **AskUserQuestion tool** to present next steps: + +**Question:** "Brainstorm captured. What would you like to do next?" + +**Options:** +1. **Review and refine** - Improve the document through structured self-review +2. **Proceed to planning** - Run `/workflows:plan` (will auto-detect this brainstorm) +3. **Ask more questions** - I have more questions to clarify before moving on +4. **Done for now** - Return later + +**If user selects "Ask more questions":** YOU (Claude) return to Phase 1.2 (Collaborative Dialogue) and continue asking the USER questions one at a time to further refine the design. The user wants YOU to probe deeper - ask about edge cases, constraints, preferences, or areas not yet explored. Continue until the user is satisfied, then return to Phase 4. + +**If user selects "Review and refine":** + +Load the `document-review` skill and apply it to the brainstorm document. + +When document-review returns "Review complete", present next steps: + +1. **Move to planning** - Continue to `/workflows:plan` with this document +2. **Done for now** - Brainstorming complete. To start planning later: `/workflows:plan [document-path]` + +## Output Summary + +When complete, display: + +``` +Brainstorm complete! + +Document: docs/brainstorms/YYYY-MM-DD--brainstorm.md + +Key decisions: +- [Decision 1] +- [Decision 2] + +Next: Run `/workflows:plan` when ready to implement. +``` + +## Important Guidelines + +- **Stay focused on WHAT, not HOW** - Implementation details belong in the plan +- **Ask one question at a time** - Don't overwhelm +- **Apply YAGNI** - Prefer simpler approaches +- **Keep outputs concise** - 200-300 words per section max + +NEVER CODE! Just explore and document decisions. diff --git a/.cursor/skills/workflows-compound/SKILL.md b/.cursor/skills/workflows-compound/SKILL.md new file mode 100644 index 0000000000..352f181383 --- /dev/null +++ b/.cursor/skills/workflows-compound/SKILL.md @@ -0,0 +1,240 @@ +--- +name: workflows-compound +description: Document a recently solved problem to compound your team's knowledge +argument-hint: '[optional: brief context about the fix]' +--- + +# /compound + +Coordinate multiple subagents working in parallel to document a recently solved problem. + +## Purpose + +Captures problem solutions while context is fresh, creating structured documentation in `docs/solutions/` with YAML frontmatter for searchability and future reference. Uses parallel subagents for maximum efficiency. + +**Why "compound"?** Each documented solution compounds your team's knowledge. The first time you solve a problem takes research. Document it, and the next occurrence takes minutes. Knowledge compounds. + +## Usage + +```bash +/workflows:compound # Document the most recent fix +/workflows:compound [brief context] # Provide additional context hint +``` + +## Execution Strategy: Two-Phase Orchestration + + +**Only ONE file gets written - the final documentation.** + +Phase 1 subagents return TEXT DATA to the orchestrator. They must NOT use Write, Edit, or create any files. Only the orchestrator (Phase 2) writes the final documentation file. + + +### Phase 1: Parallel Research + + + +Launch these subagents IN PARALLEL. Each returns text data to the orchestrator. + +#### 1. **Context Analyzer** + - Extracts conversation history + - Identifies problem type, component, symptoms + - Validates against schema + - Returns: YAML frontmatter skeleton + +#### 2. **Solution Extractor** + - Analyzes all investigation steps + - Identifies root cause + - Extracts working solution with code examples + - Returns: Solution content block + +#### 3. **Related Docs Finder** + - Searches `docs/solutions/` for related documentation + - Identifies cross-references and links + - Finds related GitHub issues + - Returns: Links and relationships + +#### 4. **Prevention Strategist** + - Develops prevention strategies + - Creates best practices guidance + - Generates test cases if applicable + - Returns: Prevention/testing content + +#### 5. **Category Classifier** + - Determines optimal `docs/solutions/` category + - Validates category against schema + - Suggests filename based on slug + - Returns: Final path and filename + + + +### Phase 2: Assembly & Write + + + +**WAIT for all Phase 1 subagents to complete before proceeding.** + +The orchestrating agent (main conversation) performs these steps: + +1. Collect all text results from Phase 1 subagents +2. Assemble complete markdown file from the collected pieces +3. Validate YAML frontmatter against schema +4. Create directory if needed: `mkdir -p docs/solutions/[category]/` +5. Write the SINGLE final file: `docs/solutions/[category]/[filename].md` + + + +### Phase 3: Optional Enhancement + +**WAIT for Phase 2 to complete before proceeding.** + + + +Based on problem type, optionally invoke specialized agents to review the documentation: + +- **performance_issue** → `performance-oracle` +- **security_issue** → `security-sentinel` +- **database_issue** → `data-integrity-guardian` +- **test_failure** → `cora-test-reviewer` +- Any code-heavy issue → `kieran-rails-reviewer` + `code-simplicity-reviewer` + + + +## What It Captures + +- **Problem symptom**: Exact error messages, observable behavior +- **Investigation steps tried**: What didn't work and why +- **Root cause analysis**: Technical explanation +- **Working solution**: Step-by-step fix with code examples +- **Prevention strategies**: How to avoid in future +- **Cross-references**: Links to related issues and docs + +## Preconditions + + + + Problem has been solved (not in-progress) + + + Solution has been verified working + + + Non-trivial problem (not simple typo or obvious error) + + + +## What It Creates + +**Organized documentation:** + +- File: `docs/solutions/[category]/[filename].md` + +**Categories auto-detected from problem:** + +- build-errors/ +- test-failures/ +- runtime-errors/ +- performance-issues/ +- database-issues/ +- security-issues/ +- ui-bugs/ +- integration-issues/ +- logic-errors/ + +## Common Mistakes to Avoid + +| ❌ Wrong | ✅ Correct | +|----------|-----------| +| Subagents write files like `context-analysis.md`, `solution-draft.md` | Subagents return text data; orchestrator writes one final file | +| Research and assembly run in parallel | Research completes → then assembly runs | +| Multiple files created during workflow | Single file: `docs/solutions/[category]/[filename].md` | + +## Success Output + +``` +✓ Documentation complete + +Subagent Results: + ✓ Context Analyzer: Identified performance_issue in brief_system + ✓ Solution Extractor: 3 code fixes + ✓ Related Docs Finder: 2 related issues + ✓ Prevention Strategist: Prevention strategies, test suggestions + ✓ Category Classifier: `performance-issues` + +Specialized Agent Reviews (Auto-Triggered): + ✓ performance-oracle: Validated query optimization approach + ✓ kieran-rails-reviewer: Code examples meet Rails standards + ✓ code-simplicity-reviewer: Solution is appropriately minimal + ✓ every-style-editor: Documentation style verified + +File created: +- docs/solutions/performance-issues/n-plus-one-brief-generation.md + +This documentation will be searchable for future reference when similar +issues occur in the Email Processing or Brief System modules. + +What's next? +1. Continue workflow (recommended) +2. Link related documentation +3. Update other references +4. View documentation +5. Other +``` + +## The Compounding Philosophy + +This creates a compounding knowledge system: + +1. First time you solve "N+1 query in brief generation" → Research (30 min) +2. Document the solution → docs/solutions/performance-issues/n-plus-one-briefs.md (5 min) +3. Next time similar issue occurs → Quick lookup (2 min) +4. Knowledge compounds → Team gets smarter + +The feedback loop: + +``` +Build → Test → Find Issue → Research → Improve → Document → Validate → Deploy + ↑ ↓ + └──────────────────────────────────────────────────────────────────────┘ +``` + +**Each unit of engineering work should make subsequent units of work easier—not harder.** + +## Auto-Invoke + + - "that worked" - "it's fixed" - "working now" - "problem solved" + + Use /workflows:compound [context] to document immediately without waiting for auto-detection. + +## Routes To + +`compound-docs` skill + +## Applicable Specialized Agents + +Based on problem type, these agents can enhance documentation: + +### Code Quality & Review +- **kieran-rails-reviewer**: Reviews code examples for Rails best practices +- **code-simplicity-reviewer**: Ensures solution code is minimal and clear +- **pattern-recognition-specialist**: Identifies anti-patterns or repeating issues + +### Specific Domain Experts +- **performance-oracle**: Analyzes performance_issue category solutions +- **security-sentinel**: Reviews security_issue solutions for vulnerabilities +- **cora-test-reviewer**: Creates test cases for prevention strategies +- **data-integrity-guardian**: Reviews database_issue migrations and queries + +### Enhancement & Documentation +- **best-practices-researcher**: Enriches solution with industry best practices +- **every-style-editor**: Reviews documentation style and clarity +- **framework-docs-researcher**: Links to Rails/gem documentation references + +### When to Invoke +- **Auto-triggered** (optional): Agents can run post-documentation for enhancement +- **Manual trigger**: User can invoke agents after /workflows:compound completes for deeper review +- **Customize agents**: Edit `compound-engineering.local.md` or invoke the `setup` skill to configure which review agents are used across all workflows + +## Related Commands + +- `/research [topic]` - Deep investigation (searches docs/solutions/ for patterns) +- `/workflows:plan` - Planning workflow (references documented solutions) diff --git a/.cursor/skills/workflows-plan/SKILL.md b/.cursor/skills/workflows-plan/SKILL.md new file mode 100644 index 0000000000..e23d4bbe04 --- /dev/null +++ b/.cursor/skills/workflows-plan/SKILL.md @@ -0,0 +1,568 @@ +--- +name: workflows-plan +description: Transform feature descriptions into well-structured project plans following conventions +argument-hint: '[feature description, bug report, or improvement idea]' +--- + +# Create a plan for a new feature or bug fix + +## Introduction + +**Note: The current year is 2026.** Use this when dating plans and searching for recent documentation. + +Transform feature descriptions, bug reports, or improvement ideas into well-structured markdown files issues that follow project conventions and best practices. This command provides flexible detail levels to match your needs. + +## Feature Description + + #$ARGUMENTS + +**If the feature description above is empty, ask the user:** "What would you like to plan? Please describe the feature, bug fix, or improvement you have in mind." + +Do not proceed until you have a clear feature description from the user. + +### 0. Idea Refinement + +**Check for brainstorm output first:** + +Before asking questions, look for recent brainstorm documents in `docs/brainstorms/` that match this feature: + +```bash +ls -la docs/brainstorms/*.md 2>/dev/null | head -10 +``` + +**Relevance criteria:** A brainstorm is relevant if: +- The topic (from filename or YAML frontmatter) semantically matches the feature description +- Created within the last 14 days +- If multiple candidates match, use the most recent one + +**If a relevant brainstorm exists:** +1. Read the brainstorm document +2. Announce: "Found brainstorm from [date]: [topic]. Using as context for planning." +3. Extract key decisions, chosen approach, and open questions +4. **Skip the idea refinement questions below** - the brainstorm already answered WHAT to build +5. Use brainstorm decisions as input to the research phase + +**If multiple brainstorms could match:** +Use **AskUserQuestion tool** to ask which brainstorm to use, or whether to proceed without one. + +**If no brainstorm found (or not relevant), run idea refinement:** + +Refine the idea through collaborative dialogue using the **AskUserQuestion tool**: + +- Ask questions one at a time to understand the idea fully +- Prefer multiple choice questions when natural options exist +- Focus on understanding: purpose, constraints and success criteria +- Continue until the idea is clear OR user says "proceed" + +**Gather signals for research decision.** During refinement, note: + +- **User's familiarity**: Do they know the codebase patterns? Are they pointing to examples? +- **User's intent**: Speed vs thoroughness? Exploration vs execution? +- **Topic risk**: Security, payments, external APIs warrant more caution +- **Uncertainty level**: Is the approach clear or open-ended? + +**Skip option:** If the feature description is already detailed, offer: +"Your description is clear. Should I proceed with research, or would you like to refine it further?" + +## Main Tasks + +### 1. Local Research (Always Runs - Parallel) + + +First, I need to understand the project's conventions, existing patterns, and any documented learnings. This is fast and local - it informs whether external research is needed. + + +Run these agents **in parallel** to gather local context: + +- Task repo-research-analyst(feature_description) +- Task learnings-researcher(feature_description) + +**What to look for:** +- **Repo research:** existing patterns, CLAUDE.md guidance, technology familiarity, pattern consistency +- **Learnings:** documented solutions in `docs/solutions/` that might apply (gotchas, patterns, lessons learned) + +These findings inform the next step. + +### 1.5. Research Decision + +Based on signals from Step 0 and findings from Step 1, decide on external research. + +**High-risk topics → always research.** Security, payments, external APIs, data privacy. The cost of missing something is too high. This takes precedence over speed signals. + +**Strong local context → skip external research.** Codebase has good patterns, CLAUDE.md has guidance, user knows what they want. External research adds little value. + +**Uncertainty or unfamiliar territory → research.** User is exploring, codebase has no examples, new technology. External perspective is valuable. + +**Announce the decision and proceed.** Brief explanation, then continue. User can redirect if needed. + +Examples: +- "Your codebase has solid patterns for this. Proceeding without external research." +- "This involves payment processing, so I'll research current best practices first." + +### 1.5b. External Research (Conditional) + +**Only run if Step 1.5 indicates external research is valuable.** + +Run these agents in parallel: + +- Task best-practices-researcher(feature_description) +- Task framework-docs-researcher(feature_description) + +### 1.6. Consolidate Research + +After all research steps complete, consolidate findings: + +- Document relevant file paths from repo research (e.g., `app/services/example_service.rb:42`) +- **Include relevant institutional learnings** from `docs/solutions/` (key insights, gotchas to avoid) +- Note external documentation URLs and best practices (if external research was done) +- List related issues or PRs discovered +- Capture CLAUDE.md conventions + +**Optional validation:** Briefly summarize findings and ask if anything looks off or missing before proceeding to planning. + +### 2. Issue Planning & Structure + + +Think like a product manager - what would make this issue clear and actionable? Consider multiple perspectives + + +**Title & Categorization:** + +- [ ] Draft clear, searchable issue title using conventional format (e.g., `feat: Add user authentication`, `fix: Cart total calculation`) +- [ ] Determine issue type: enhancement, bug, refactor +- [ ] Convert title to filename: add today's date prefix, strip prefix colon, kebab-case, add `-plan` suffix + - Example: `feat: Add User Authentication` → `2026-01-21-feat-add-user-authentication-plan.md` + - Keep it descriptive (3-5 words after prefix) so plans are findable by context + +**Stakeholder Analysis:** + +- [ ] Identify who will be affected by this issue (end users, developers, operations) +- [ ] Consider implementation complexity and required expertise + +**Content Planning:** + +- [ ] Choose appropriate detail level based on issue complexity and audience +- [ ] List all necessary sections for the chosen template +- [ ] Gather supporting materials (error logs, screenshots, design mockups) +- [ ] Prepare code examples or reproduction steps if applicable, name the mock filenames in the lists + +### 3. SpecFlow Analysis + +After planning the issue structure, run SpecFlow Analyzer to validate and refine the feature specification: + +- Task compound-engineering:workflow:spec-flow-analyzer(feature_description, research_findings) + +**SpecFlow Analyzer Output:** + +- [ ] Review SpecFlow analysis results +- [ ] Incorporate any identified gaps or edge cases into the issue +- [ ] Update acceptance criteria based on SpecFlow findings + +### 4. Choose Implementation Detail Level + +Select how comprehensive you want the issue to be, simpler is mostly better. + +#### 📄 MINIMAL (Quick Issue) + +**Best for:** Simple bugs, small improvements, clear features + +**Includes:** + +- Problem statement or feature description +- Basic acceptance criteria +- Essential context only + +**Structure:** + +````markdown +--- +title: [Issue Title] +type: [feat|fix|refactor] +status: active +date: YYYY-MM-DD +--- + +# [Issue Title] + +[Brief problem/feature description] + +## Acceptance Criteria + +- [ ] Core requirement 1 +- [ ] Core requirement 2 + +## Context + +[Any critical information] + +## MVP + +### test.rb + +```ruby +class Test + def initialize + @name = "test" + end +end +``` + +## References + +- Related issue: #[issue_number] +- Documentation: [relevant_docs_url] +```` + +#### 📋 MORE (Standard Issue) + +**Best for:** Most features, complex bugs, team collaboration + +**Includes everything from MINIMAL plus:** + +- Detailed background and motivation +- Technical considerations +- Success metrics +- Dependencies and risks +- Basic implementation suggestions + +**Structure:** + +```markdown +--- +title: [Issue Title] +type: [feat|fix|refactor] +status: active +date: YYYY-MM-DD +--- + +# [Issue Title] + +## Overview + +[Comprehensive description] + +## Problem Statement / Motivation + +[Why this matters] + +## Proposed Solution + +[High-level approach] + +## Technical Considerations + +- Architecture impacts +- Performance implications +- Security considerations + +## Acceptance Criteria + +- [ ] Detailed requirement 1 +- [ ] Detailed requirement 2 +- [ ] Testing requirements + +## Success Metrics + +[How we measure success] + +## Dependencies & Risks + +[What could block or complicate this] + +## References & Research + +- Similar implementations: [file_path:line_number] +- Best practices: [documentation_url] +- Related PRs: #[pr_number] +``` + +#### 📚 A LOT (Comprehensive Issue) + +**Best for:** Major features, architectural changes, complex integrations + +**Includes everything from MORE plus:** + +- Detailed implementation plan with phases +- Alternative approaches considered +- Extensive technical specifications +- Resource requirements and timeline +- Future considerations and extensibility +- Risk mitigation strategies +- Documentation requirements + +**Structure:** + +```markdown +--- +title: [Issue Title] +type: [feat|fix|refactor] +status: active +date: YYYY-MM-DD +--- + +# [Issue Title] + +## Overview + +[Executive summary] + +## Problem Statement + +[Detailed problem analysis] + +## Proposed Solution + +[Comprehensive solution design] + +## Technical Approach + +### Architecture + +[Detailed technical design] + +### Implementation Phases + +#### Phase 1: [Foundation] + +- Tasks and deliverables +- Success criteria +- Estimated effort + +#### Phase 2: [Core Implementation] + +- Tasks and deliverables +- Success criteria +- Estimated effort + +#### Phase 3: [Polish & Optimization] + +- Tasks and deliverables +- Success criteria +- Estimated effort + +## Alternative Approaches Considered + +[Other solutions evaluated and why rejected] + +## Acceptance Criteria + +### Functional Requirements + +- [ ] Detailed functional criteria + +### Non-Functional Requirements + +- [ ] Performance targets +- [ ] Security requirements +- [ ] Accessibility standards + +### Quality Gates + +- [ ] Test coverage requirements +- [ ] Documentation completeness +- [ ] Code review approval + +## Success Metrics + +[Detailed KPIs and measurement methods] + +## Dependencies & Prerequisites + +[Detailed dependency analysis] + +## Risk Analysis & Mitigation + +[Comprehensive risk assessment] + +## Resource Requirements + +[Team, time, infrastructure needs] + +## Future Considerations + +[Extensibility and long-term vision] + +## Documentation Plan + +[What docs need updating] + +## References & Research + +### Internal References + +- Architecture decisions: [file_path:line_number] +- Similar features: [file_path:line_number] +- Configuration: [file_path:line_number] + +### External References + +- Framework documentation: [url] +- Best practices guide: [url] +- Industry standards: [url] + +### Related Work + +- Previous PRs: #[pr_numbers] +- Related issues: #[issue_numbers] +- Design documents: [links] +``` + +### 5. Issue Creation & Formatting + + +Apply best practices for clarity and actionability, making the issue easy to scan and understand + + +**Content Formatting:** + +- [ ] Use clear, descriptive headings with proper hierarchy (##, ###) +- [ ] Include code examples in triple backticks with language syntax highlighting +- [ ] Add screenshots/mockups if UI-related (drag & drop or use image hosting) +- [ ] Use task lists (- [ ]) for trackable items that can be checked off +- [ ] Add collapsible sections for lengthy logs or optional details using `
` tags +- [ ] Apply appropriate emoji for visual scanning (🐛 bug, ✨ feature, 📚 docs, ♻️ refactor) + +**Cross-Referencing:** + +- [ ] Link to related issues/PRs using #number format +- [ ] Reference specific commits with SHA hashes when relevant +- [ ] Link to code using GitHub's permalink feature (press 'y' for permanent link) +- [ ] Mention relevant team members with @username if needed +- [ ] Add links to external resources with descriptive text + +**Code & Examples:** + +````markdown +# Good example with syntax highlighting and line references + + +```ruby +# app/services/user_service.rb:42 +def process_user(user) + +# Implementation here + +end +``` + +# Collapsible error logs + +
+Full error stacktrace + +`Error details here...` + +
+```` + +**AI-Era Considerations:** + +- [ ] Account for accelerated development with AI pair programming +- [ ] Include prompts or instructions that worked well during research +- [ ] Note which AI tools were used for initial exploration (Claude, Copilot, etc.) +- [ ] Emphasize comprehensive testing given rapid implementation +- [ ] Document any AI-generated code that needs human review + +### 6. Final Review & Submission + +**Pre-submission Checklist:** + +- [ ] Title is searchable and descriptive +- [ ] Labels accurately categorize the issue +- [ ] All template sections are complete +- [ ] Links and references are working +- [ ] Acceptance criteria are measurable +- [ ] Add names of files in pseudo code examples and todo lists +- [ ] Add an ERD mermaid diagram if applicable for new model changes + +## Write Plan File + +**REQUIRED: Write the plan file to disk before presenting any options.** + +```bash +mkdir -p docs/plans/ +``` + +Use the Write tool to save the complete plan to `docs/plans/YYYY-MM-DD---plan.md`. This step is mandatory and cannot be skipped — even when running as part of LFG/SLFG or other automated pipelines. + +Confirm: "Plan written to docs/plans/[filename]" + +**Pipeline mode:** If invoked from an automated workflow (LFG, SLFG, or any `disable-model-invocation` context), skip all AskUserQuestion calls. Make decisions automatically and proceed to writing the plan without interactive prompts. + +## Output Format + +**Filename:** Use the date and kebab-case filename from Step 2 Title & Categorization. + +``` +docs/plans/YYYY-MM-DD---plan.md +``` + +Examples: +- ✅ `docs/plans/2026-01-15-feat-user-authentication-flow-plan.md` +- ✅ `docs/plans/2026-02-03-fix-checkout-race-condition-plan.md` +- ✅ `docs/plans/2026-03-10-refactor-api-client-extraction-plan.md` +- ❌ `docs/plans/2026-01-15-feat-thing-plan.md` (not descriptive - what "thing"?) +- ❌ `docs/plans/2026-01-15-feat-new-feature-plan.md` (too vague - what feature?) +- ❌ `docs/plans/2026-01-15-feat: user auth-plan.md` (invalid characters - colon and space) +- ❌ `docs/plans/feat-user-auth-plan.md` (missing date prefix) + +## Post-Generation Options + +After writing the plan file, use the **AskUserQuestion tool** to present these options: + +**Question:** "Plan ready at `docs/plans/YYYY-MM-DD---plan.md`. What would you like to do next?" + +**Options:** +1. **Open plan in editor** - Open the plan file for review +2. **Run `/deepen-plan`** - Enhance each section with parallel research agents (best practices, performance, UI) +3. **Run `/technical_review`** - Technical feedback from code-focused reviewers (DHH, Kieran, Simplicity) +4. **Review and refine** - Improve the document through structured self-review +5. **Start `/workflows:work`** - Begin implementing this plan locally +6. **Start `/workflows:work` on remote** - Begin implementing in Claude Code on the web (use `&` to run in background) +7. **Create Issue** - Create issue in project tracker (GitHub/Linear) + +Based on selection: +- **Open plan in editor** → Run `open docs/plans/.md` to open the file in the user's default editor +- **`/deepen-plan`** → Call the /deepen-plan command with the plan file path to enhance with research +- **`/technical_review`** → Call the /technical_review command with the plan file path +- **Review and refine** → Load `document-review` skill. +- **`/workflows:work`** → Call the /workflows:work command with the plan file path +- **`/workflows:work` on remote** → Run `/workflows:work docs/plans/.md &` to start work in background for Claude Code web +- **Create Issue** → See "Issue Creation" section below +- **Other** (automatically provided) → Accept free text for rework or specific changes + +**Note:** If running `/workflows:plan` with ultrathink enabled, automatically run `/deepen-plan` after plan creation for maximum depth and grounding. + +Loop back to options after Simplify or Other changes until user selects `/workflows:work` or `/technical_review`. + +## Issue Creation + +When user selects "Create Issue", detect their project tracker from CLAUDE.md: + +1. **Check for tracker preference** in user's CLAUDE.md (global or project): + - Look for `project_tracker: github` or `project_tracker: linear` + - Or look for mentions of "GitHub Issues" or "Linear" in their workflow section + +2. **If GitHub:** + + Use the title and type from Step 2 (already in context - no need to re-read the file): + + ```bash + gh issue create --title ": " --body-file <plan_path> + ``` + +3. **If Linear:** + + ```bash + linear issue create --title "<title>" --description "$(cat <plan_path>)" + ``` + +4. **If no tracker configured:** + Ask user: "Which project tracker do you use? (GitHub/Linear/Other)" + - Suggest adding `project_tracker: github` or `project_tracker: linear` to their CLAUDE.md + +5. **After creation:** + - Display the issue URL + - Ask if they want to proceed to `/workflows:work` or `/technical_review` + +NEVER CODE! Just research and write the plan. diff --git a/.cursor/skills/workflows-review/SKILL.md b/.cursor/skills/workflows-review/SKILL.md new file mode 100644 index 0000000000..c132bdc669 --- /dev/null +++ b/.cursor/skills/workflows-review/SKILL.md @@ -0,0 +1,528 @@ +--- +name: workflows-review +description: Perform exhaustive code reviews using multi-agent analysis, ultra-thinking, and worktrees +argument-hint: '[PR number, GitHub URL, branch name, or latest]' +--- + +# Review Command + +<command_purpose> Perform exhaustive code reviews using multi-agent analysis, ultra-thinking, and Git worktrees for deep local inspection. </command_purpose> + +## Introduction + +<role>Senior Code Review Architect with expertise in security, performance, architecture, and quality assurance</role> + +## Prerequisites + +<requirements> +- Git repository with GitHub CLI (`gh`) installed and authenticated +- Clean main/master branch +- Proper permissions to create worktrees and access the repository +- For document reviews: Path to a markdown file or document +</requirements> + +## Main Tasks + +### 1. Determine Review Target & Setup (ALWAYS FIRST) + +<review_target> #$ARGUMENTS </review_target> + +<thinking> +First, I need to determine the review target type and set up the code for analysis. +</thinking> + +#### Immediate Actions: + +<task_list> + +- [ ] Determine review type: PR number (numeric), GitHub URL, file path (.md), or empty (current branch) +- [ ] Check current git branch +- [ ] If ALREADY on the target branch (PR branch, requested branch name, or the branch already checked out for review) → proceed with analysis on current branch +- [ ] If DIFFERENT branch than the review target → offer to use worktree: "Use git-worktree skill for isolated Call `skill: git-worktree` with branch name +- [ ] Fetch PR metadata using `gh pr view --json` for title, body, files, linked issues +- [ ] Set up language-specific analysis tools +- [ ] Prepare security scanning environment +- [ ] Make sure we are on the branch we are reviewing. Use gh pr checkout to switch to the branch or manually checkout the branch. + +Ensure that the code is ready for analysis (either in worktree or on current branch). ONLY then proceed to the next step. + +</task_list> + +#### Protected Artifacts + +<protected_artifacts> +The following paths are compound-engineering pipeline artifacts and must never be flagged for deletion, removal, or gitignore by any review agent: + +- `docs/plans/*.md` — Plan files created by `/workflows:plan`. These are living documents that track implementation progress (checkboxes are checked off by `/workflows:work`). +- `docs/solutions/*.md` — Solution documents created during the pipeline. + +If a review agent flags any file in these directories for cleanup or removal, discard that finding during synthesis. Do not create a todo for it. +</protected_artifacts> + +#### Load Review Agents + +Read `compound-engineering.local.md` in the project root. If found, use `review_agents` from YAML frontmatter. If the markdown body contains review context, pass it to each agent as additional instructions. + +If no settings file exists, invoke the `setup` skill to create one. Then read the newly created file and continue. + +#### Parallel Agents to review the PR: + +<parallel_tasks> + +Run all configured review agents in parallel using Task tool. For each agent in the `review_agents` list: + +``` +Task {agent-name}(PR content + review context from settings body) +``` + +Additionally, always run these regardless of settings: +- Task agent-native-reviewer(PR content) - Verify new features are agent-accessible +- Task learnings-researcher(PR content) - Search docs/solutions/ for past issues related to this PR's modules and patterns + +</parallel_tasks> + +#### Conditional Agents (Run if applicable): + +<conditional_agents> + +These agents are run ONLY when the PR matches specific criteria. Check the PR files list to determine if they apply: + +**MIGRATIONS: If PR contains database migrations, schema.rb, or data backfills:** + +- Task schema-drift-detector(PR content) - Detects unrelated schema.rb changes by cross-referencing against included migrations (run FIRST) +- Task data-migration-expert(PR content) - Validates ID mappings match production, checks for swapped values, verifies rollback safety +- Task deployment-verification-agent(PR content) - Creates Go/No-Go deployment checklist with SQL verification queries + +**When to run:** +- PR includes files matching `db/migrate/*.rb` or `db/schema.rb` +- PR modifies columns that store IDs, enums, or mappings +- PR includes data backfill scripts or rake tasks +- PR title/body mentions: migration, backfill, data transformation, ID mapping + +**What these agents check:** +- `schema-drift-detector`: Cross-references schema.rb changes against PR migrations to catch unrelated columns/indexes from local database state +- `data-migration-expert`: Verifies hard-coded mappings match production reality (prevents swapped IDs), checks for orphaned associations, validates dual-write patterns +- `deployment-verification-agent`: Produces executable pre/post-deploy checklists with SQL queries, rollback procedures, and monitoring plans + +</conditional_agents> + +### 4. Ultra-Thinking Deep Dive Phases + +<ultrathink_instruction> For each phase below, spend maximum cognitive effort. Think step by step. Consider all angles. Question assumptions. And bring all reviews in a synthesis to the user.</ultrathink_instruction> + +<deliverable> +Complete system context map with component interactions +</deliverable> + +#### Phase 3: Stakeholder Perspective Analysis + +<thinking_prompt> ULTRA-THINK: Put yourself in each stakeholder's shoes. What matters to them? What are their pain points? </thinking_prompt> + +<stakeholder_perspectives> + +1. **Developer Perspective** <questions> + + - How easy is this to understand and modify? + - Are the APIs intuitive? + - Is debugging straightforward? + - Can I test this easily? </questions> + +2. **Operations Perspective** <questions> + + - How do I deploy this safely? + - What metrics and logs are available? + - How do I troubleshoot issues? + - What are the resource requirements? </questions> + +3. **End User Perspective** <questions> + + - Is the feature intuitive? + - Are error messages helpful? + - Is performance acceptable? + - Does it solve my problem? </questions> + +4. **Security Team Perspective** <questions> + + - What's the attack surface? + - Are there compliance requirements? + - How is data protected? + - What are the audit capabilities? </questions> + +5. **Business Perspective** <questions> + - What's the ROI? + - Are there legal/compliance risks? + - How does this affect time-to-market? + - What's the total cost of ownership? </questions> </stakeholder_perspectives> + +#### Phase 4: Scenario Exploration + +<thinking_prompt> ULTRA-THINK: Explore edge cases and failure scenarios. What could go wrong? How does the system behave under stress? </thinking_prompt> + +<scenario_checklist> + +- [ ] **Happy Path**: Normal operation with valid inputs +- [ ] **Invalid Inputs**: Null, empty, malformed data +- [ ] **Boundary Conditions**: Min/max values, empty collections +- [ ] **Concurrent Access**: Race conditions, deadlocks +- [ ] **Scale Testing**: 10x, 100x, 1000x normal load +- [ ] **Network Issues**: Timeouts, partial failures +- [ ] **Resource Exhaustion**: Memory, disk, connections +- [ ] **Security Attacks**: Injection, overflow, DoS +- [ ] **Data Corruption**: Partial writes, inconsistency +- [ ] **Cascading Failures**: Downstream service issues </scenario_checklist> + +### 6. Multi-Angle Review Perspectives + +#### Technical Excellence Angle + +- Code craftsmanship evaluation +- Engineering best practices +- Technical documentation quality +- Tooling and automation assessment + +#### Business Value Angle + +- Feature completeness validation +- Performance impact on users +- Cost-benefit analysis +- Time-to-market considerations + +#### Risk Management Angle + +- Security risk assessment +- Operational risk evaluation +- Compliance risk verification +- Technical debt accumulation + +#### Team Dynamics Angle + +- Code review etiquette +- Knowledge sharing effectiveness +- Collaboration patterns +- Mentoring opportunities + +### 4. Simplification and Minimalism Review + +Run the Task code-simplicity-reviewer() to see if we can simplify the code. + +### 5. Findings Synthesis and Todo Creation Using file-todos Skill + +<critical_requirement> ALL findings MUST be stored in the todos/ directory using the file-todos skill. Create todo files immediately after synthesis - do NOT present findings for user approval first. Use the skill for structured todo management. </critical_requirement> + +#### Step 1: Synthesize All Findings + +<thinking> +Consolidate all agent reports into a categorized list of findings. +Remove duplicates, prioritize by severity and impact. +</thinking> + +<synthesis_tasks> + +- [ ] Collect findings from all parallel agents +- [ ] Surface learnings-researcher results: if past solutions are relevant, flag them as "Known Pattern" with links to docs/solutions/ files +- [ ] Discard any findings that recommend deleting or gitignoring files in `docs/plans/` or `docs/solutions/` (see Protected Artifacts above) +- [ ] Categorize by type: security, performance, architecture, quality, etc. +- [ ] Assign severity levels: 🔴 CRITICAL (P1), 🟡 IMPORTANT (P2), 🔵 NICE-TO-HAVE (P3) +- [ ] Remove duplicate or overlapping findings +- [ ] Estimate effort for each finding (Small/Medium/Large) + +</synthesis_tasks> + +#### Step 2: Create Todo Files Using file-todos Skill + +<critical_instruction> Use the file-todos skill to create todo files for ALL findings immediately. Do NOT present findings one-by-one asking for user approval. Create all todo files in parallel using the skill, then summarize results to user. </critical_instruction> + +**Implementation Options:** + +**Option A: Direct File Creation (Fast)** + +- Create todo files directly using Write tool +- All findings in parallel for speed +- Use standard template from `.claude/skills/file-todos/assets/todo-template.md` +- Follow naming convention: `{issue_id}-pending-{priority}-{description}.md` + +**Option B: Sub-Agents in Parallel (Recommended for Scale)** For large PRs with 15+ findings, use sub-agents to create finding files in parallel: + +```bash +# Launch multiple finding-creator agents in parallel +Task() - Create todos for first finding +Task() - Create todos for second finding +Task() - Create todos for third finding +etc. for each finding. +``` + +Sub-agents can: + +- Process multiple findings simultaneously +- Write detailed todo files with all sections filled +- Organize findings by severity +- Create comprehensive Proposed Solutions +- Add acceptance criteria and work logs +- Complete much faster than sequential processing + +**Execution Strategy:** + +1. Synthesize all findings into categories (P1/P2/P3) +2. Group findings by severity +3. Launch 3 parallel sub-agents (one per severity level) +4. Each sub-agent creates its batch of todos using the file-todos skill +5. Consolidate results and present summary + +**Process (Using file-todos Skill):** + +1. For each finding: + + - Determine severity (P1/P2/P3) + - Write detailed Problem Statement and Findings + - Create 2-3 Proposed Solutions with pros/cons/effort/risk + - Estimate effort (Small/Medium/Large) + - Add acceptance criteria and work log + +2. Use file-todos skill for structured todo management: + + ```bash + skill: file-todos + ``` + + The skill provides: + + - Template location: `.claude/skills/file-todos/assets/todo-template.md` + - Naming convention: `{issue_id}-{status}-{priority}-{description}.md` + - YAML frontmatter structure: status, priority, issue_id, tags, dependencies + - All required sections: Problem Statement, Findings, Solutions, etc. + +3. Create todo files in parallel: + + ```bash + {next_id}-pending-{priority}-{description}.md + ``` + +4. Examples: + + ``` + 001-pending-p1-path-traversal-vulnerability.md + 002-pending-p1-api-response-validation.md + 003-pending-p2-concurrency-limit.md + 004-pending-p3-unused-parameter.md + ``` + +5. Follow template structure from file-todos skill: `.claude/skills/file-todos/assets/todo-template.md` + +**Todo File Structure (from template):** + +Each todo must include: + +- **YAML frontmatter**: status, priority, issue_id, tags, dependencies +- **Problem Statement**: What's broken/missing, why it matters +- **Findings**: Discoveries from agents with evidence/location +- **Proposed Solutions**: 2-3 options, each with pros/cons/effort/risk +- **Recommended Action**: (Filled during triage, leave blank initially) +- **Technical Details**: Affected files, components, database changes +- **Acceptance Criteria**: Testable checklist items +- **Work Log**: Dated record with actions and learnings +- **Resources**: Links to PR, issues, documentation, similar patterns + +**File naming convention:** + +``` +{issue_id}-{status}-{priority}-{description}.md + +Examples: +- 001-pending-p1-security-vulnerability.md +- 002-pending-p2-performance-optimization.md +- 003-pending-p3-code-cleanup.md +``` + +**Status values:** + +- `pending` - New findings, needs triage/decision +- `ready` - Approved by manager, ready to work +- `complete` - Work finished + +**Priority values:** + +- `p1` - Critical (blocks merge, security/data issues) +- `p2` - Important (should fix, architectural/performance) +- `p3` - Nice-to-have (enhancements, cleanup) + +**Tagging:** Always add `code-review` tag, plus: `security`, `performance`, `architecture`, `rails`, `quality`, etc. + +#### Step 3: Summary Report + +After creating all todo files, present comprehensive summary: + +````markdown +## ✅ Code Review Complete + +**Review Target:** PR #XXXX - [PR Title] **Branch:** [branch-name] + +### Findings Summary: + +- **Total Findings:** [X] +- **🔴 CRITICAL (P1):** [count] - BLOCKS MERGE +- **🟡 IMPORTANT (P2):** [count] - Should Fix +- **🔵 NICE-TO-HAVE (P3):** [count] - Enhancements + +### Created Todo Files: + +**P1 - Critical (BLOCKS MERGE):** + +- `001-pending-p1-{finding}.md` - {description} +- `002-pending-p1-{finding}.md` - {description} + +**P2 - Important:** + +- `003-pending-p2-{finding}.md` - {description} +- `004-pending-p2-{finding}.md` - {description} + +**P3 - Nice-to-Have:** + +- `005-pending-p3-{finding}.md` - {description} + +### Review Agents Used: + +- kieran-rails-reviewer +- security-sentinel +- performance-oracle +- architecture-strategist +- agent-native-reviewer +- [other agents] + +### Next Steps: + +1. **Address P1 Findings**: CRITICAL - must be fixed before merge + + - Review each P1 todo in detail + - Implement fixes or request exemption + - Verify fixes before merging PR + +2. **Triage All Todos**: + ```bash + ls todos/*-pending-*.md # View all pending todos + /triage # Use slash command for interactive triage + ``` +```` + +3. **Work on Approved Todos**: + + ```bash + /resolve_todo_parallel # Fix all approved items efficiently + ``` + +4. **Track Progress**: + - Rename file when status changes: pending → ready → complete + - Update Work Log as you work + - Commit todos: `git add todos/ && git commit -m "refactor: add code review findings"` + +### Severity Breakdown: + +**🔴 P1 (Critical - Blocks Merge):** + +- Security vulnerabilities +- Data corruption risks +- Breaking changes +- Critical architectural issues + +**🟡 P2 (Important - Should Fix):** + +- Performance issues +- Significant architectural concerns +- Major code quality problems +- Reliability issues + +**🔵 P3 (Nice-to-Have):** + +- Minor improvements +- Code cleanup +- Optimization opportunities +- Documentation updates + +``` + +### 7. End-to-End Testing (Optional) + +<detect_project_type> + +**First, detect the project type from PR files:** + +| Indicator | Project Type | +|-----------|--------------| +| `*.xcodeproj`, `*.xcworkspace`, `Package.swift` (iOS) | iOS/macOS | +| `Gemfile`, `package.json`, `app/views/*`, `*.html.*` | Web | +| Both iOS files AND web files | Hybrid (test both) | + +</detect_project_type> + +<offer_testing> + +After presenting the Summary Report, offer appropriate testing based on project type: + +**For Web Projects:** +```markdown +**"Want to run browser tests on the affected pages?"** +1. Yes - run `/test-browser` +2. No - skip +``` + +**For iOS Projects:** +```markdown +**"Want to run Xcode simulator tests on the app?"** +1. Yes - run `/xcode-test` +2. No - skip +``` + +**For Hybrid Projects (e.g., Rails + Hotwire Native):** +```markdown +**"Want to run end-to-end tests?"** +1. Web only - run `/test-browser` +2. iOS only - run `/xcode-test` +3. Both - run both commands +4. No - skip +``` + +</offer_testing> + +#### If User Accepts Web Testing: + +Spawn a subagent to run browser tests (preserves main context): + +``` +Task general-purpose("Run /test-browser for PR #[number]. Test all affected pages, check for console errors, handle failures by creating todos and fixing.") +``` + +The subagent will: +1. Identify pages affected by the PR +2. Navigate to each page and capture snapshots (using Playwright MCP or agent-browser CLI) +3. Check for console errors +4. Test critical interactions +5. Pause for human verification on OAuth/email/payment flows +6. Create P1 todos for any failures +7. Fix and retry until all tests pass + +**Standalone:** `/test-browser [PR number]` + +#### If User Accepts iOS Testing: + +Spawn a subagent to run Xcode tests (preserves main context): + +``` +Task general-purpose("Run /xcode-test for scheme [name]. Build for simulator, install, launch, take screenshots, check for crashes.") +``` + +The subagent will: +1. Verify XcodeBuildMCP is installed +2. Discover project and schemes +3. Build for iOS Simulator +4. Install and launch app +5. Take screenshots of key screens +6. Capture console logs for errors +7. Pause for human verification (Sign in with Apple, push, IAP) +8. Create P1 todos for any failures +9. Fix and retry until all tests pass + +**Standalone:** `/xcode-test [scheme]` + +### Important: P1 Findings Block Merge + +Any **🔴 P1 (CRITICAL)** findings must be addressed before merging the PR. Present these prominently and ensure they're resolved before accepting the PR. +``` diff --git a/.cursor/skills/workflows-work/SKILL.md b/.cursor/skills/workflows-work/SKILL.md new file mode 100644 index 0000000000..159cab75fb --- /dev/null +++ b/.cursor/skills/workflows-work/SKILL.md @@ -0,0 +1,454 @@ +--- +name: workflows-work +description: Execute work plans efficiently while maintaining quality and finishing features +argument-hint: '[plan file, specification, or todo file path]' +--- + +# Work Plan Execution Command + +Execute a work plan efficiently while maintaining quality and finishing features. + +## Introduction + +This command takes a work document (plan, specification, or todo file) and executes it systematically. The focus is on **shipping complete features** by understanding requirements quickly, following existing patterns, and maintaining quality throughout. + +## Input Document + +<input_document> #$ARGUMENTS </input_document> + +## Execution Workflow + +### Phase 1: Quick Start + +1. **Read Plan and Clarify** + + - Read the work document completely + - Review any references or links provided in the plan + - If anything is unclear or ambiguous, ask clarifying questions now + - Get user approval to proceed + - **Do not skip this** - better to ask questions now than build the wrong thing + +2. **Setup Environment** + + First, check the current branch: + + ```bash + current_branch=$(git branch --show-current) + default_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + + # Fallback if remote HEAD isn't set + if [ -z "$default_branch" ]; then + default_branch=$(git rev-parse --verify origin/main >/dev/null 2>&1 && echo "main" || echo "master") + fi + ``` + + **If already on a feature branch** (not the default branch): + - Ask: "Continue working on `[current_branch]`, or create a new branch?" + - If continuing, proceed to step 3 + - If creating new, follow Option A or B below + + **If on the default branch**, choose how to proceed: + + **Option A: Create a new branch** + ```bash + git pull origin [default_branch] + git checkout -b feature-branch-name + ``` + Use a meaningful name based on the work (e.g., `feat/user-authentication`, `fix/email-validation`). + + **Option B: Use a worktree (recommended for parallel development)** + ```bash + skill: git-worktree + # The skill will create a new branch from the default branch in an isolated worktree + ``` + + **Option C: Continue on the default branch** + - Requires explicit user confirmation + - Only proceed after user explicitly says "yes, commit to [default_branch]" + - Never commit directly to the default branch without explicit permission + + **Recommendation**: Use worktree if: + - You want to work on multiple features simultaneously + - You want to keep the default branch clean while experimenting + - You plan to switch between branches frequently + +3. **Create Todo List** + - Use TodoWrite to break plan into actionable tasks + - Include dependencies between tasks + - Prioritize based on what needs to be done first + - Include testing and quality check tasks + - Keep tasks specific and completable + +### Phase 2: Execute + +1. **Task Execution Loop** + + For each task in priority order: + + ``` + while (tasks remain): + - Mark task as in_progress in TodoWrite + - Read any referenced files from the plan + - Look for similar patterns in codebase + - Implement following existing conventions + - Write tests for new functionality + - Run tests after changes + - Mark task as completed in TodoWrite + - Mark off the corresponding checkbox in the plan file ([ ] → [x]) + - Evaluate for incremental commit (see below) + ``` + + **IMPORTANT**: Always update the original plan document by checking off completed items. Use the Edit tool to change `- [ ]` to `- [x]` for each task you finish. This keeps the plan as a living document showing progress and ensures no checkboxes are left unchecked. + +2. **Incremental Commits** + + After completing each task, evaluate whether to create an incremental commit: + + | Commit when... | Don't commit when... | + |----------------|---------------------| + | Logical unit complete (model, service, component) | Small part of a larger unit | + | Tests pass + meaningful progress | Tests failing | + | About to switch contexts (backend → frontend) | Purely scaffolding with no behavior | + | About to attempt risky/uncertain changes | Would need a "WIP" commit message | + + **Heuristic:** "Can I write a commit message that describes a complete, valuable change? If yes, commit. If the message would be 'WIP' or 'partial X', wait." + + **Commit workflow:** + ```bash + # 1. Verify tests pass (use project's test command) + # Examples: bin/rails test, npm test, pytest, go test, etc. + + # 2. Stage only files related to this logical unit (not `git add .`) + git add <files related to this logical unit> + + # 3. Commit with conventional message + git commit -m "feat(scope): description of this unit" + ``` + + **Handling merge conflicts:** If conflicts arise during rebasing or merging, resolve them immediately. Incremental commits make conflict resolution easier since each commit is small and focused. + + **Note:** Incremental commits use clean conventional messages without attribution footers. The final Phase 4 commit/PR includes the full attribution. + +3. **Follow Existing Patterns** + + - The plan should reference similar code - read those files first + - Match naming conventions exactly + - Reuse existing components where possible + - Follow project coding standards (see CLAUDE.md) + - When in doubt, grep for similar implementations + +4. **Test Continuously** + + - Run relevant tests after each significant change + - Don't wait until the end to test + - Fix failures immediately + - Add new tests for new functionality + +5. **Figma Design Sync** (if applicable) + + For UI work with Figma designs: + + - Implement components following design specs + - Use figma-design-sync agent iteratively to compare + - Fix visual differences identified + - Repeat until implementation matches design + +6. **Track Progress** + - Keep TodoWrite updated as you complete tasks + - Note any blockers or unexpected discoveries + - Create new tasks if scope expands + - Keep user informed of major milestones + +### Phase 3: Quality Check + +1. **Run Core Quality Checks** + + Always run before submitting: + + ```bash + # Run full test suite (use project's test command) + # Examples: bin/rails test, npm test, pytest, go test, etc. + + # Run linting (per CLAUDE.md) + # Use linting-agent before pushing to origin + ``` + +2. **Consider Reviewer Agents** (Optional) + + Use for complex, risky, or large changes. Read agents from `compound-engineering.local.md` frontmatter (`review_agents`). If no settings file, invoke the `setup` skill to create one. + + Run configured agents in parallel with Task tool. Present findings and address critical issues. + +3. **Final Validation** + - All TodoWrite tasks marked completed + - All tests pass + - Linting passes + - Code follows existing patterns + - Figma designs match (if applicable) + - No console errors or warnings + +4. **Prepare Operational Validation Plan** (REQUIRED) + - Add a `## Post-Deploy Monitoring & Validation` section to the PR description for every change. + - Include concrete: + - Log queries/search terms + - Metrics or dashboards to watch + - Expected healthy signals + - Failure signals and rollback/mitigation trigger + - Validation window and owner + - If there is truly no production/runtime impact, still include the section with: `No additional operational monitoring required` and a one-line reason. + +### Phase 4: Ship It + +1. **Create Commit** + + ```bash + git add . + git status # Review what's being committed + git diff --staged # Check the changes + + # Commit with conventional format + git commit -m "$(cat <<'EOF' + feat(scope): description of what and why + + Brief explanation if needed. + + 🤖 Generated with [Claude Code](https://claude.com/claude-code) + + Co-Authored-By: Claude <noreply@anthropic.com> + EOF + )" + ``` + +2. **Capture and Upload Screenshots for UI Changes** (REQUIRED for any UI work) + + For **any** design changes, new views, or UI modifications, you MUST capture and upload screenshots: + + **Step 1: Start dev server** (if not running) + ```bash + bin/dev # Run in background + ``` + + **Step 2: Capture screenshots with agent-browser CLI** + ```bash + agent-browser open http://localhost:3000/[route] + agent-browser snapshot -i + agent-browser screenshot output.png + ``` + See the `agent-browser` skill for detailed usage. + + **Step 3: Upload using imgup skill** + ```bash + skill: imgup + # Then upload each screenshot: + imgup -h pixhost screenshot.png # pixhost works without API key + # Alternative hosts: catbox, imagebin, beeimg + ``` + + **What to capture:** + - **New screens**: Screenshot of the new UI + - **Modified screens**: Before AND after screenshots + - **Design implementation**: Screenshot showing Figma design match + + **IMPORTANT**: Always include uploaded image URLs in PR description. This provides visual context for reviewers and documents the change. + +3. **Create Pull Request** + + ```bash + git push -u origin feature-branch-name + + gh pr create --title "Feature: [Description]" --body "$(cat <<'EOF' + ## Summary + - What was built + - Why it was needed + - Key decisions made + + ## Testing + - Tests added/modified + - Manual testing performed + + ## Post-Deploy Monitoring & Validation + - **What to monitor/search** + - Logs: + - Metrics/Dashboards: + - **Validation checks (queries/commands)** + - `command or query here` + - **Expected healthy behavior** + - Expected signal(s) + - **Failure signal(s) / rollback trigger** + - Trigger + immediate action + - **Validation window & owner** + - Window: + - Owner: + - **If no operational impact** + - `No additional operational monitoring required: <reason>` + + ## Before / After Screenshots + | Before | After | + |--------|-------| + | ![before](URL) | ![after](URL) | + + ## Figma Design + [Link if applicable] + + --- + + [![Compound Engineered](https://img.shields.io/badge/Compound-Engineered-6366f1)](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with [Claude Code](https://claude.com/claude-code) + EOF + )" + ``` + +4. **Update Plan Status** + + If the input document has YAML frontmatter with a `status` field, update it to `completed`: + ``` + status: active → status: completed + ``` + +5. **Notify User** + - Summarize what was completed + - Link to PR + - Note any follow-up work needed + - Suggest next steps if applicable + +--- + +## Swarm Mode (Optional) + +For complex plans with multiple independent workstreams, enable swarm mode for parallel execution with coordinated agents. + +### When to Use Swarm Mode + +| Use Swarm Mode when... | Use Standard Mode when... | +|------------------------|---------------------------| +| Plan has 5+ independent tasks | Plan is linear/sequential | +| Multiple specialists needed (review + test + implement) | Single-focus work | +| Want maximum parallelism | Simpler mental model preferred | +| Large feature with clear phases | Small feature or bug fix | + +### Enabling Swarm Mode + +To trigger swarm execution, say: + +> "Make a Task list and launch an army of agent swarm subagents to build the plan" + +Or explicitly request: "Use swarm mode for this work" + +### Swarm Workflow + +When swarm mode is enabled, the workflow changes: + +1. **Create Team** + ``` + Teammate({ operation: "spawnTeam", team_name: "work-{timestamp}" }) + ``` + +2. **Create Task List with Dependencies** + - Parse plan into TaskCreate items + - Set up blockedBy relationships for sequential dependencies + - Independent tasks have no blockers (can run in parallel) + +3. **Spawn Specialized Teammates** + ``` + Task({ + team_name: "work-{timestamp}", + name: "implementer", + subagent_type: "general-purpose", + prompt: "Claim implementation tasks, execute, mark complete", + run_in_background: true + }) + + Task({ + team_name: "work-{timestamp}", + name: "tester", + subagent_type: "general-purpose", + prompt: "Claim testing tasks, run tests, mark complete", + run_in_background: true + }) + ``` + +4. **Coordinate and Monitor** + - Team lead monitors task completion + - Spawn additional workers as phases unblock + - Handle plan approval if required + +5. **Cleanup** + ``` + Teammate({ operation: "requestShutdown", target_agent_id: "implementer" }) + Teammate({ operation: "requestShutdown", target_agent_id: "tester" }) + Teammate({ operation: "cleanup" }) + ``` + +See the `orchestrating-swarms` skill for detailed swarm patterns and best practices. + +--- + +## Key Principles + +### Start Fast, Execute Faster + +- Get clarification once at the start, then execute +- Don't wait for perfect understanding - ask questions and move +- The goal is to **finish the feature**, not create perfect process + +### The Plan is Your Guide + +- Work documents should reference similar code and patterns +- Load those references and follow them +- Don't reinvent - match what exists + +### Test As You Go + +- Run tests after each change, not at the end +- Fix failures immediately +- Continuous testing prevents big surprises + +### Quality is Built In + +- Follow existing patterns +- Write tests for new code +- Run linting before pushing +- Use reviewer agents for complex/risky changes only + +### Ship Complete Features + +- Mark all tasks completed before moving on +- Don't leave features 80% done +- A finished feature that ships beats a perfect feature that doesn't + +## Quality Checklist + +Before creating PR, verify: + +- [ ] All clarifying questions asked and answered +- [ ] All TodoWrite tasks marked completed +- [ ] Tests pass (run project's test command) +- [ ] Linting passes (use linting-agent) +- [ ] Code follows existing patterns +- [ ] Figma designs match implementation (if applicable) +- [ ] Before/after screenshots captured and uploaded (for UI changes) +- [ ] Commit messages follow conventional format +- [ ] PR description includes Post-Deploy Monitoring & Validation section (or explicit no-impact rationale) +- [ ] PR description includes summary, testing notes, and screenshots +- [ ] PR description includes Compound Engineered badge + +## When to Use Reviewer Agents + +**Don't use by default.** Use reviewer agents only when: + +- Large refactor affecting many files (10+) +- Security-sensitive changes (authentication, permissions, data access) +- Performance-critical code paths +- Complex algorithms or business logic +- User explicitly requests thorough review + +For most features: tests + linting + following patterns is sufficient. + +## Common Pitfalls to Avoid + +- **Analysis paralysis** - Don't overthink, read the plan and execute +- **Skipping clarifying questions** - Ask now, not after building wrong thing +- **Ignoring plan references** - The plan has links for a reason +- **Testing at the end** - Test continuously or suffer later +- **Forgetting TodoWrite** - Track progress or lose track of what's done +- **80% done syndrome** - Finish the feature, don't move on early +- **Over-reviewing simple changes** - Save reviewer agents for complex work diff --git a/.gitignore b/.gitignore index 011d8699c7..d2c4aa772f 100644 --- a/.gitignore +++ b/.gitignore @@ -123,4 +123,22 @@ packages/plate/docs/ .turbo .vercel +CLAUDE.local.md +.codex + +# START Skiller Generated Files +/.codex/config.toml +/.codex/skills +/.cursor/mcp.json +/.cursor/rules +/.cursor/skills +/.mcp.json +/AGENTS.md +/CLAUDE.md +# END Skiller Generated Files +.claude/rules/ +.claude/skills/ +.worktrees/ +.ralph/ +!.claude/skiller.toml *.local* diff --git a/PROMPT.md b/PROMPT.md new file mode 100644 index 0000000000..0a830411e4 --- /dev/null +++ b/PROMPT.md @@ -0,0 +1,3645 @@ +# PR Comments Export + +> Exported from [https://github.com/arthrod/plate/pull/114](https://github.com/arthrod/plate/pull/114) +> 20 of 20 comments selected +> Generated by [Cicero](https://cicero.im) on 2/21/2026, 10:13:42 PM + +--- + +### coderabbitai[bot] — 2/21/2026, 9:34:10 PM + +> File: `packages/docx-io/ralph.yml` + +<details><summary>Code context</summary> + +```diff +@@ -0,0 +1,87 @@ ++# Research Preset ++# ++# For deep exploration tasks: codebase analysis, architecture review, ++# technology evaluation, competitive analysis. ++# ++# Key behavior: NO code changes, NO commits. Pure information gathering. ++# ++# Usage: ++# ralph run --config presets/research.yml --prompt "Analyze the authentication flow" ++ +``` +</details> + +_⚠️ Potential issue_ | _🟡 Minor_ + +**Align the usage path with the actual file location.** + +The usage example points to `presets/research.yml`, but this file lives at `packages/docx-io/ralph.yml` in the PR. If the path isn’t intentional (e.g., copied into `presets/` at build time), please update the usage snippet or add a clarifying note. + + +<details> +<summary>💡 Suggested clarification</summary> + +```diff +-# ralph run --config presets/research.yml --prompt "Analyze the authentication flow" ++# ralph run --config packages/docx-io/ralph.yml --prompt "Analyze the authentication flow" +``` +</details> + +<!-- suggestion_start --> + +<details> +<summary>📝 Committable suggestion</summary> + +> ‼️ **IMPORTANT** +> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. + +```suggestion +# Usage: +# ralph run --config packages/docx-io/ralph.yml --prompt "Analyze the authentication flow" + +``` + +</details> + +<!-- suggestion_end --> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/ralph.yml` around lines 8 - 10, The usage example in the +YAML (the "Usage:" snippet referencing presets/research.yml) is incorrect for +this PR; update the usage path in packages/docx-io/ralph.yml so it points to the +actual file location (e.g., packages/docx-io/ralph.yml) or add a short +clarifying note explaining where presets/research.yml is expected to live at +runtime (if it's populated/copied during build). Locate the "Usage:" block in +packages/docx-io/ralph.yml and either replace presets/research.yml with the +correct relative path or append one line explaining the build-time placement. +``` + +</details> + +<!-- fingerprinting:phantom:poseidon:eagle --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:33 AM + +> File: `packages/docx-io/src/lib/html-to-docx/docx-document.ts` + +<details><summary>Code context</summary> + +```diff +@@ -957,6 +1067,289 @@ class DocxDocument { + generateFooterXML(vTree: VTree): Promise<FooterResult> { + return this.generateSectionXML(vTree, 'footer') as Promise<FooterResult>; + } ++ ++ // ============================================================================ ++ // Tracking Support Methods (Comments and Suggestions) ++ // ============================================================================ ++ ++ /** ++ * Get a revision ID for a suggestion. Creates a new one if needed. ++ * Ensures consistent IDs for the same suggestion across multiple occurrences. ++ */ ++ getRevisionId(id?: string): number { ++ if (!id) { ++ this.lastRevisionId += 1; ++ return this.lastRevisionId; ++ } ++ ++ const existing = this.revisionIdMap.get(id); ++ if (existing !== undefined) { ++ return existing; ++ } ++ ++ this.lastRevisionId += 1; ++ this.revisionIdMap.set(id, this.lastRevisionId); ++ return this.lastRevisionId; ++ } ++ ++ /** ++ * Ensure a comment exists in the document and return its numeric ID. ++ * Updates metadata if the comment already exists but had missing fields. ++ */ ++ ensureComment(data: Partial<CommentPayload>, parentParaId?: string): number { ++ const { id, authorName, authorInitials, date, text } = data; ++ const commentId = ++ id !== undefined ? id : `comment-${this.lastCommentId + 1}`; ++ let numericId = this.commentIdMap.get(commentId); ++ ++ if (numericId === undefined) { ++ this.lastCommentId += 1; ++ numericId = this.lastCommentId; ++ this.commentIdMap.set(commentId, numericId); ++ } ++ ++ const existing = this.comments.find((item) => item.id === numericId); ++ if (existing) { ++ // Update missing fields ++ if (!existing.authorName && authorName) { ++ existing.authorName = authorName; ++ } ++ if (!existing.authorInitials && authorInitials) { ++ existing.authorInitials = authorInitials; ++ } ++ if (!existing.date && date) { ++ existing.date = date; ++ } ++ if (!existing.text && text) { ++ existing.text = text; ++ } ++ if (!existing.parentParaId && parentParaId) { ++ existing.parentParaId = parentParaId; ++ } ++ return numericId; ++ } ++ ++ // Preserve imported paraId when provided; otherwise generate fresh. ++ // Register in allocatedIds to prevent collisions with generated IDs. ++ let paraId: string; ++ if (data.paraId) { ++ paraId = data.paraId; ++ allocatedIds.add(paraId); ++ } else { ++ paraId = generateHexId(); ++ } ++ ++ const entry = { ++ id: numericId, ++ authorName: authorName || 'unknown', ++ authorInitials: authorInitials || '', ++ date, ++ durableId: generateHexId(), ++ paraId, ++ parentParaId, ++ text: text || 'Imported comment', ++ }; ++ this.comments.push(entry); ++ ++ return numericId; ++ } ++ ++ /** ++ * Get the numeric ID for a comment, creating it if necessary. ++ */ ++ getCommentId(id: string): number { ++ if (id === undefined || id === null) { ++ return this.ensureComment({ id: undefined }); ++ } ++ return this.ensureComment({ id }); ++ } ++ ++ /** ++ * Generate the comments.xml file content. ++ * Matches reference library structure: w14:paraId on paragraphs, ++ * CommentReference style on first run, text runs with formatting. ++ */ ++ generateCommentsXML(): string { ++ const w = namespaces.w; ++ const commentsXML = create(COMMENTS_TEMPLATE); ++ const root = commentsXML.root(); ++ ++ this.comments.forEach((comment) => { ++ const commentElement = root ++ .ele(w, 'comment') ++ .att(w, 'id', String(comment.id)) ++ .att(w, 'author', comment.authorName || 'unknown'); ++ ++ if (comment.authorInitials) { ++ commentElement.att(w, 'initials', comment.authorInitials); ++ } ++ if (comment.date) { ++ commentElement.att(w, 'date', comment.date); ++ } ++ ++ // Split multi-line comment text into paragraphs ++ const paragraphs = String(comment.text || '') ++ .split(/\r?\n/) ++ .filter((line, index, arr) => line.length > 0 || arr.length === 1); ++ ++ paragraphs.forEach((line, pIdx) => { ++ const pElement = commentElement.ele(w, 'p'); ++ ++ // Add w14:paraId and w14:textId per OOXML spec ++ pElement.att(namespaces.w14, 'paraId', comment.paraId); ++ pElement.att(namespaces.w14, 'textId', '77777777'); ++ ++ // Paragraph properties ++ pElement ++ .ele(w, 'pPr') ++ .ele(w, 'pStyle') ++ .att(w, 'val', 'CommentText') ++ .up() ++ .up(); ++ ++ // First paragraph gets CommentReference run ++ if (pIdx === 0) { ++ const refRun = pElement.ele(w, 'r'); ++ refRun ++ .ele(w, 'rPr') ++ .ele(w, 'rStyle') ++ .att(w, 'val', 'CommentReference') ++ .up() ++ .up(); ++ refRun.ele(w, 'annotationRef').up(); ++ refRun.up(); ++ } ++ ++ // Text run ++ const textRun = pElement.ele(w, 'r'); ++ textRun ++ .ele(w, 'rPr') ++ .ele(w, 'color') ++ .att(w, 'val', '000000') ++ .up() ++ .ele(w, 'sz') ++ .att(w, 'val', '20') ++ .up() ++ .ele(w, 'szCs') ++ .att(w, 'val', '20') ++ .up() ++ .up(); ++ textRun ++ .ele(w, 't') ++ .att('http://www.w3.org/XML/1998/namespace', 'space', 'preserve') ++ .txt(line) ++ .up(); ++ textRun.up(); ++ ++ pElement.up(); ++ }); ++ ++ commentElement.up(); ++ }); ++ ++ return commentsXML.end({ prettyPrint: true }); ++ } ++ ++ /** ++ * Generate word/commentsExtended.xml. ++ * Links comments via paraId and establishes parent-child threading via paraIdParent. ++ */ ++ generateCommentsExtendedXML(): string { ++ const doc = create(COMMENTS_EXTENDED_TEMPLATE); ++ const root = doc.root(); ++ ++ this.comments.forEach((comment) => { ++ const el = root.ele(namespaces.w15, 'commentEx'); ++ el.att(namespaces.w15, 'paraId', comment.paraId); ++ el.att(namespaces.w15, 'done', '0'); ++ if (comment.parentParaId) { ++ el.att(namespaces.w15, 'paraIdParent', comment.parentParaId); ++ } ++ el.up(); ++ }); ++ ++ return doc.end({ prettyPrint: true }); ++ } ++ ++ /** ++ * Generate word/commentsIds.xml. ++ * Maps paraId to durableId for each comment. ++ */ ++ generateCommentsIdsXML(): string { ++ const doc = create(COMMENTS_IDS_TEMPLATE); ++ const root = doc.root(); ++ ++ this.comments.forEach((comment) => { ++ const el = root.ele(namespaces.w16cid, 'commentId'); ++ el.att(namespaces.w16cid, 'paraId', comment.paraId); ++ el.att(namespaces.w16cid, 'durableId', comment.durableId); ++ el.up(); ++ }); ++ ++ return doc.end({ prettyPrint: true }); ++ } ++ ++ /** ++ * Generate word/commentsExtensible.xml. ++ * Links durableId to dateUtc for each comment. ++ */ ++ generateCommentsExtensibleXML(): string { ++ const doc = create(COMMENTS_EXTENSIBLE_TEMPLATE); ++ const root = doc.root(); ++ ++ this.comments.forEach((comment) => { ++ const el = root.ele(namespaces.w16cex, 'commentExtensible'); ++ el.att(namespaces.w16cex, 'durableId', comment.durableId); ++ if (comment.date) { ++ // comment.date is local time with fake Z (Word convention). ++ // Reverse the fake Z to recover real UTC: ++ // fakeMs = epoch interpreting local time as UTC ++ // tzMs = browser offset (positive = west of UTC) ++ // real = fakeMs + tzMs ++ const fakeMs = new Date(comment.date).getTime(); ++ const tzMs = new Date().getTimezoneOffset() * 60_000; ++ const realUtc = new Date(fakeMs + tzMs); ++ el.att( ++ namespaces.w16cex, ++ 'dateUtc', ++ Number.isNaN(realUtc.getTime()) ? comment.date : realUtc.toISOString() ++ ); +``` +</details> + +_⚠️ Potential issue_ | _🟡 Minor_ + +**Fix dateUtc conversion to use the comment date’s local offset (DST-safe).** + +Using the current timezone offset can shift historical dates when DST differs between comment time and export time. Consider computing the offset based on the comment date itself. + + +<details> +<summary>🔧 Suggested adjustment</summary> + +```diff +- const fakeMs = new Date(comment.date).getTime(); +- const tzMs = new Date().getTimezoneOffset() * 60_000; +- const realUtc = new Date(fakeMs + tzMs); ++ const local = new Date(String(comment.date).replace(/Z$/, '')); ++ const realUtc = new Date(local.getTime()); +``` +</details> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/src/lib/html-to-docx/docx-document.ts` around lines 1304 - +1316, The code uses the current timezone offset (new Date().getTimezoneOffset()) +when computing realUtc for comment.date which breaks DST for historical dates; +change tzMs to be based on the comment date's offset by computing const tzMs = +new Date(comment.date).getTimezoneOffset() * 60_000 so fakeMs + tzMs uses the +comment's local offset (keep the existing fakeMs, realUtc and the +el.att(namespaces.w16cex, 'dateUtc', ...) NaN fallback logic intact). +``` + +</details> + +<!-- fingerprinting:phantom:medusa:eagle --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:33 AM + +> File: `packages/docx-io/src/lib/html-to-docx/schemas/core.ts` + +<details><summary>Code context</summary> + +```diff +@@ -2,6 +2,17 @@ + import { applicationName } from '../constants'; + import namespaces from '../namespaces'; + ++/** Format a Date as local time with Z suffix (matching Word convention) */ ++function toLocalWithZ(d: Date): string { ++ const Y = d.getFullYear(); ++ const M = String(d.getMonth() + 1).padStart(2, '0'); ++ const D = String(d.getDate()).padStart(2, '0'); ++ const h = String(d.getHours()).padStart(2, '0'); ++ const m = String(d.getMinutes()).padStart(2, '0'); ++ const s = String(d.getSeconds()).padStart(2, '0'); ++ return `${Y}-${M}-${D}T${h}:${m}:${s}Z`; ++} +``` +</details> + +_⚠️ Potential issue_ | _🟠 Major_ + +**Local time with UTC 'Z' suffix creates timestamp mismatch.** + +The function uses local time methods (`getHours()`, `getMinutes()`, etc.) but appends 'Z', which in ISO 8601 signifies UTC. This produces semantically incorrect timestamps—a local time of 10:00 PST would output `...T10:00:00Z`, claiming it's 10:00 UTC (actually 18:00 UTC). + +If Word truly expects this non-standard format, add a comment explaining this quirk. Otherwise, either use UTC methods or include a proper timezone offset. + + + +<details> +<summary>🔧 Suggested fix using UTC methods (if standard compliance is intended)</summary> + +```diff + /** Format a Date as local time with Z suffix (matching Word convention) */ +-function toLocalWithZ(d: Date): string { +- const Y = d.getFullYear(); +- const M = String(d.getMonth() + 1).padStart(2, '0'); +- const D = String(d.getDate()).padStart(2, '0'); +- const h = String(d.getHours()).padStart(2, '0'); +- const m = String(d.getMinutes()).padStart(2, '0'); +- const s = String(d.getSeconds()).padStart(2, '0'); ++/** Format a Date as UTC time with Z suffix (ISO 8601 compliant) */ ++function toUTCWithZ(d: Date): string { ++ const Y = d.getUTCFullYear(); ++ const M = String(d.getUTCMonth() + 1).padStart(2, '0'); ++ const D = String(d.getUTCDate()).padStart(2, '0'); ++ const h = String(d.getUTCHours()).padStart(2, '0'); ++ const m = String(d.getUTCMinutes()).padStart(2, '0'); ++ const s = String(d.getUTCSeconds()).padStart(2, '0'); + return `${Y}-${M}-${D}T${h}:${m}:${s}Z`; + } +``` + +</details> + + + +```web +Does Microsoft Word DOCX dcterms:created expect local time with Z suffix or actual UTC time? +``` + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/src/lib/html-to-docx/schemas/core.ts` around lines 5 - 14, +The toLocalWithZ function builds an ISO timestamp using local getters but +appends 'Z', which falsely claims UTC; fix by replacing local-date getters with +UTC variants (use getUTCFullYear, getUTCMonth+1, getUTCDate, getUTCHours, +getUTCMinutes, getUTCSeconds) inside toLocalWithZ so the rendered string truly +represents UTC, or if the codebase intentionally needs local time with a +trailing 'Z' (Word quirk), add a clear comment in toLocalWithZ explaining this +non‑standard behavior instead of leaving misleading code. +``` + +</details> + +<!-- fingerprinting:phantom:poseidon:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `apps/www/public/r/import-toolbar-button.json` + +<details><summary>Code context</summary> + +```diff +@@ -14,7 +14,7 @@ + "files": [ + { + "path": "src/registry/ui/import-toolbar-button.tsx", +- "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { importDocx } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { ArrowUpToLineIcon } from 'lucide-react';\nimport { getEditorDOMFromHtmlString } from 'platejs/static';\nimport { useEditorRef } from 'platejs/react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\n\nimport { ToolbarButton } from './toolbar';\n\ntype ImportType = 'html' | 'markdown';\n\nexport function ImportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getFileNodes = (text: string, type: ImportType) => {\n if (type === 'html') {\n const editorNode = getEditorDOMFromHtmlString(text);\n const nodes = editor.api.html.deserialize({\n element: editorNode,\n });\n\n return nodes;\n }\n\n if (type === 'markdown') {\n return editor.getApi(MarkdownPlugin).markdown.deserialize(text);\n }\n\n return [];\n };\n\n const { openFilePicker: openMdFilePicker } = useFilePicker({\n accept: ['.md', '.mdx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'markdown');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openHtmlFilePicker } = useFilePicker({\n accept: ['text/html'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'html');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openDocxFilePicker } = useFilePicker({\n accept: ['.docx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const arrayBuffer = await plainFiles[0].arrayBuffer();\n const result = await importDocx(editor, arrayBuffer);\n\n editor.tf.insertNodes(result.nodes as typeof editor.children);\n },\n });\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Import\" isDropdown>\n <ArrowUpToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem\n onSelect={() => {\n openHtmlFilePicker();\n }}\n >\n Import from HTML\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openMdFilePicker();\n }}\n >\n Import from Markdown\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openDocxFilePicker();\n }}\n >\n Import from Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", ++ "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { getCommentKey } from '@platejs/comment';\nimport { importDocxWithTracking } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { getSuggestionKey } from '@platejs/suggestion';\nimport { ArrowUpToLineIcon } from 'lucide-react';\nimport { KEYS, TextApi } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\nimport { getEditorDOMFromHtmlString } from 'platejs/static';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\n\nimport { commentPlugin } from '@/registry/components/editor/plugins/comment-kit';\nimport {\n discussionPlugin,\n type TDiscussion,\n} from '@/registry/components/editor/plugins/discussion-kit';\nimport { getDiscussionCounterSeed } from '../lib/discussion-ids';\nimport { ToolbarButton } from './toolbar';\n\ntype ImportType = 'html' | 'markdown';\n\nconst WHITESPACE_REGEX = /\\s+/;\n\nexport function ImportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getFileNodes = (text: string, type: ImportType) => {\n if (type === 'html') {\n const editorNode = getEditorDOMFromHtmlString(text);\n const nodes = editor.api.html.deserialize({\n element: editorNode,\n });\n\n return nodes;\n }\n\n if (type === 'markdown') {\n return editor.getApi(MarkdownPlugin).markdown.deserialize(text);\n }\n\n return [];\n };\n\n const { openFilePicker: openMdFilePicker } = useFilePicker({\n accept: ['.md', '.mdx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'markdown');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openHtmlFilePicker } = useFilePicker({\n accept: ['text/html'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'html');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openDocxFilePicker } = useFilePicker({\n accept: ['.docx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const arrayBuffer = await plainFiles[0].arrayBuffer();\n\n // Compute next discussion number to avoid ID collisions\n const existingDiscussions =\n editor.getOption(discussionPlugin, 'discussions') ?? [];\n let discussionCounter = getDiscussionCounterSeed(existingDiscussions);\n\n // Import with full tracking support (suggestions + comments)\n const result = await importDocxWithTracking(editor as any, arrayBuffer, {\n suggestionKey: KEYS.suggestion,\n getSuggestionKey,\n commentKey: KEYS.comment,\n getCommentKey,\n isText: TextApi.isText,\n generateId: () => `discussion${++discussionCounter}`,\n });\n\n // Register imported users so suggestion/comment UI can resolve them\n if (result.users.length > 0) {\n const existingUsers = editor.getOption(discussionPlugin, 'users') ?? {};\n const updatedUsers = { ...existingUsers };\n\n for (const user of result.users) {\n if (!updatedUsers[user.id]) {\n updatedUsers[user.id] = {\n id: user.id,\n name: user.name,\n avatarUrl: `https://api.dicebear.com/9.x/glass/svg?seed=${encodeURIComponent(user.name)}`,\n };\n }\n }\n\n editor.setOption(discussionPlugin, 'users', updatedUsers);\n }\n\n // Add imported discussions to the discussion plugin\n if (result.discussions.length > 0) {\n // Convert imported discussions to TDiscussion format\n const newDiscussions: TDiscussion[] = result.discussions.map((d) => ({\n id: d.id,\n comments: (d.comments ?? []).map((c, index) => ({\n id: c.id || `comment${index + 1}`,\n contentRich:\n c.contentRich as TDiscussion['comments'][number]['contentRich'],\n createdAt: c.createdAt ?? new Date(),\n discussionId: d.id,\n isEdited: false,\n userId: c.userId ?? c.user?.id ?? 'imported-unknown',\n authorName: c.user?.name,\n authorInitials: c.user?.name\n ? c.user.name\n .split(WHITESPACE_REGEX)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('')\n : undefined,\n paraId: c.paraId,\n parentParaId: c.parentParaId,\n })),\n createdAt: d.createdAt ?? new Date(),\n documentContent: d.documentContent,\n isResolved: false,\n userId: d.userId ?? d.user?.id ?? 'imported-unknown',\n authorName: d.user?.name,\n authorInitials: d.user?.name\n ? d.user.name\n .split(WHITESPACE_REGEX)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('')\n : undefined,\n paraId: d.paraId,\n }));\n\n // Replace all discussions (not append) because importDocxWithTracking\n // replaces the entire editor content, making old discussions stale\n editor.setOption(discussionPlugin, 'discussions', newDiscussions);\n editor.setOption(commentPlugin, 'uniquePathMap', new Map());\n }\n\n // Log import results in dev only\n if (\n result.hasTracking &&\n result.errors.length > 0 &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.warn('[DOCX Import] Errors:', result.errors);\n }\n },\n });\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Import\" isDropdown>\n <ArrowUpToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem\n onSelect={() => {\n openHtmlFilePicker();\n }}\n >\n Import from HTML\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openMdFilePicker();\n }}\n >\n Import from Markdown\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openDocxFilePicker();\n }}\n >\n Import from Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", +``` +</details> + +_⚠️ Potential issue_ | _🟠 Major_ + +**Clear stale discussions when a DOCX has none.** + +`importDocxWithTracking` replaces editor content, but discussions/uniquePathMap are only reset when `result.discussions` has entries. If the imported DOCX has no discussions, previous discussions remain and become orphaned. + +<details> +<summary>🛠️ Suggested fix</summary> + +```diff +- if (result.discussions.length > 0) { +- editor.setOption(discussionPlugin, 'discussions', newDiscussions); +- editor.setOption(commentPlugin, 'uniquePathMap', new Map()); +- } ++ if (result.discussions.length > 0) { ++ editor.setOption(discussionPlugin, 'discussions', newDiscussions); ++ } else { ++ editor.setOption(discussionPlugin, 'discussions', []); ++ } ++ editor.setOption(commentPlugin, 'uniquePathMap', new Map()); +``` +</details> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@apps/www/public/r/import-toolbar-button.json` at line 17, In +ImportToolbarButton, the importDocxWithTracking result only clears discussion +state when result.discussions.length > 0, leaving stale discussions when the +import has none; fix by handling the empty case: after receiving result in the +onFilesSelected handler (where result is defined), always call +editor.setOption(discussionPlugin, 'discussions', result.discussions.length ? +mapped TDiscussion[] : []), and also always reset the commentPlugin +uniquePathMap via editor.setOption(commentPlugin, 'uniquePathMap', new Map()); +implement this in the same block that currently maps result.discussions to +newDiscussions so stale discussions are cleared even when result.discussions is +empty. +``` + +</details> + +<!-- fingerprinting:phantom:medusa:eagle --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `apps/www/src/registry/ui/block-discussion.tsx` + +<details><summary>Code context</summary> + +```diff +@@ -316,31 +320,45 @@ const useResolvedDiscussion = ( + + const discussions = usePluginOption(discussionPlugin, 'discussions'); + ++ const getLeafCommentIds = (leaf: TCommentText) => ++ getCommentKeys(leaf) ++ .map(getCommentKeyId) ++ .filter((id): id is string => Boolean(id) && id !== 'draft'); ++ ++ const map = getOption('uniquePathMap'); ++ const nextMap = new Map(map); ++ let mapChanged = false; ++ + commentNodes.forEach(([node]) => { +- const id = api.comment.nodeId(node); +- const map = getOption('uniquePathMap'); ++ const ids = getLeafCommentIds(node); ++ if (ids.length === 0) return; + +- if (!id) return; ++ ids.forEach((id) => { ++ const previousPath = nextMap.get(id); + +- const previousPath = map.get(id); ++ // If there are no comment nodes in the corresponding path in the map, then update it. ++ if (PathApi.isPath(previousPath)) { ++ const nodes = api.comment.node({ id, at: previousPath }); + +- // If there are no comment nodes in the corresponding path in the map, then update it. +- if (PathApi.isPath(previousPath)) { +- const nodes = api.comment.node({ id, at: previousPath }); ++ if (!nodes) { ++ nextMap.set(id, blockPath); ++ mapChanged = true; ++ } + +- if (!nodes) { +- setOption('uniquePathMap', new Map(map).set(id, blockPath)); + return; + } +- +- return; +- } +- // TODO: fix throw error +- setOption('uniquePathMap', new Map(map).set(id, blockPath)); ++ // TODO: fix throw error ++ nextMap.set(id, blockPath); ++ mapChanged = true; ++ }); + }); + ++ if (mapChanged) { ++ setOption('uniquePathMap', nextMap); ++ } +``` +</details> + +_⚠️ Potential issue_ | _🟠 Major_ + +**Potential render-loop risk: `setOption` called during render phase.** + +Calling `setOption` synchronously inside the component body (not in an effect or callback) can trigger re-renders while rendering, risking infinite loops or React warnings. Although `mapChanged` gates the call, the condition can remain true across renders if the map keeps changing. + +Consider moving this state update into a `useEffect` or `useLayoutEffect` to defer the update until after render: + +<details> +<summary>Suggested approach</summary> + +```diff ++ React.useEffect(() => { ++ if (mapChanged) { ++ setOption('uniquePathMap', nextMap); ++ } ++ }, [mapChanged, nextMap, setOption]); +- if (mapChanged) { +- setOption('uniquePathMap', nextMap); +- } +``` + +Note: You'll need to stabilize `nextMap` reference (e.g., via `useMemo`) and ensure `mapChanged` is derived consistently to avoid stale closures. +</details> + +<!-- suggestion_start --> + +<details> +<summary>📝 Committable suggestion</summary> + +> ‼️ **IMPORTANT** +> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. + +```suggestion + React.useEffect(() => { + if (mapChanged) { + setOption('uniquePathMap', nextMap); + } + }, [mapChanged, nextMap, setOption]); +``` + +</details> + +<!-- suggestion_end --> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@apps/www/src/registry/ui/block-discussion.tsx` around lines 356 - 358, You +are calling setOption('uniquePathMap', nextMap) during render guarded by +mapChanged, which can still cause render-loop warnings; move this update into a +lifecycle hook (useEffect or useLayoutEffect) so it runs after render, derive +mapChanged deterministically, and stabilize nextMap (e.g., compute it with +useMemo or from state) before using it in the effect; update the effect +dependencies to include the stabilized nextMap and mapChanged so the option is +only set when the map truly changes. +``` + +</details> + +<!-- fingerprinting:phantom:medusa:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `apps/www/src/registry/ui/import-toolbar-button.tsx` + +<details><summary>Code context</summary> + +```diff +@@ -73,9 +84,93 @@ export function ImportToolbarButton(props: DropdownMenuProps) { + multiple: false, + onFilesSelected: async ({ plainFiles }) => { + const arrayBuffer = await plainFiles[0].arrayBuffer(); +- const result = await importDocx(editor, arrayBuffer); + +- editor.tf.insertNodes(result.nodes as typeof editor.children); ++ // Compute next discussion number to avoid ID collisions ++ const existingDiscussions = ++ editor.getOption(discussionPlugin, 'discussions') ?? []; ++ let discussionCounter = getDiscussionCounterSeed(existingDiscussions); ++ ++ // Import with full tracking support (suggestions + comments) ++ const result = await importDocxWithTracking(editor as any, arrayBuffer, { ++ suggestionKey: KEYS.suggestion, ++ getSuggestionKey, ++ commentKey: KEYS.comment, ++ getCommentKey, ++ isText: TextApi.isText, ++ generateId: () => `discussion${++discussionCounter}`, ++ }); ++ ++ // Register imported users so suggestion/comment UI can resolve them ++ if (result.users.length > 0) { ++ const existingUsers = editor.getOption(discussionPlugin, 'users') ?? {}; ++ const updatedUsers = { ...existingUsers }; ++ ++ for (const user of result.users) { ++ if (!updatedUsers[user.id]) { ++ updatedUsers[user.id] = { ++ id: user.id, ++ name: user.name, ++ avatarUrl: `https://api.dicebear.com/9.x/glass/svg?seed=${encodeURIComponent(user.name)}`, ++ }; ++ } ++ } ++ ++ editor.setOption(discussionPlugin, 'users', updatedUsers); ++ } ++ ++ // Add imported discussions to the discussion plugin ++ if (result.discussions.length > 0) { ++ // Convert imported discussions to TDiscussion format ++ const newDiscussions: TDiscussion[] = result.discussions.map((d) => ({ ++ id: d.id, ++ comments: (d.comments ?? []).map((c, index) => ({ ++ id: c.id || `comment${index + 1}`, ++ contentRich: ++ c.contentRich as TDiscussion['comments'][number]['contentRich'], ++ createdAt: c.createdAt ?? new Date(), ++ discussionId: d.id, ++ isEdited: false, ++ userId: c.userId ?? c.user?.id ?? 'imported-unknown', ++ authorName: c.user?.name, ++ authorInitials: c.user?.name ++ ? c.user.name ++ .split(WHITESPACE_REGEX) ++ .slice(0, 2) ++ .map((w) => w[0]?.toUpperCase() ?? '') ++ .join('') ++ : undefined, ++ paraId: c.paraId, ++ parentParaId: c.parentParaId, ++ })), ++ createdAt: d.createdAt ?? new Date(), ++ documentContent: d.documentContent, ++ isResolved: false, ++ userId: d.userId ?? d.user?.id ?? 'imported-unknown', ++ authorName: d.user?.name, ++ authorInitials: d.user?.name ++ ? d.user.name ++ .split(WHITESPACE_REGEX) ++ .slice(0, 2) ++ .map((w) => w[0]?.toUpperCase() ?? '') ++ .join('') ++ : undefined, ++ paraId: d.paraId, ++ })); ++ ++ // Replace all discussions (not append) because importDocxWithTracking ++ // replaces the entire editor content, making old discussions stale ++ editor.setOption(discussionPlugin, 'discussions', newDiscussions); ++ editor.setOption(commentPlugin, 'uniquePathMap', new Map()); ++ } +``` +</details> + +_⚠️ Potential issue_ | _🟠 Major_ + +**Clear discussions even when no DOCX discussions are imported.** + +Right now the discussion list (and `uniquePathMap`) is only replaced when `result.discussions.length > 0`. If the import replaces editor content (as noted in the comment), importing a DOCX with no discussions will leave stale threads pointing at removed content. Consider always resetting discussions (possibly to `[]`) and `uniquePathMap` after import. + + +<details> +<summary>🛠️ Suggested fix</summary> + +```diff +- // Add imported discussions to the discussion plugin +- if (result.discussions.length > 0) { +- // Convert imported discussions to TDiscussion format +- const newDiscussions: TDiscussion[] = result.discussions.map((d) => ({ ++ // Convert imported discussions to TDiscussion format (empty array if none) ++ const newDiscussions: TDiscussion[] = (result.discussions ?? []).map((d) => ({ + id: d.id, + comments: (d.comments ?? []).map((c, index) => ({ + id: c.id || `comment${index + 1}`, + contentRich: + c.contentRich as TDiscussion['comments'][number]['contentRich'], + createdAt: c.createdAt ?? new Date(), + discussionId: d.id, + isEdited: false, + userId: c.userId ?? c.user?.id ?? 'imported-unknown', + authorName: c.user?.name, + authorInitials: c.user?.name + ? c.user.name + .split(WHITESPACE_REGEX) + .slice(0, 2) + .map((w) => w[0]?.toUpperCase() ?? '') + .join('') + : undefined, + paraId: c.paraId, + parentParaId: c.parentParaId, + })), + createdAt: d.createdAt ?? new Date(), + documentContent: d.documentContent, + isResolved: false, + userId: d.userId ?? d.user?.id ?? 'imported-unknown', + authorName: d.user?.name, + authorInitials: d.user?.name + ? d.user.name + .split(WHITESPACE_REGEX) + .slice(0, 2) + .map((w) => w[0]?.toUpperCase() ?? '') + .join('') + : undefined, + paraId: d.paraId, +- })); +- +- // Replace all discussions (not append) because importDocxWithTracking +- // replaces the entire editor content, making old discussions stale +- editor.setOption(discussionPlugin, 'discussions', newDiscussions); +- editor.setOption(commentPlugin, 'uniquePathMap', new Map()); +- } ++ })); ++ ++ // Replace all discussions (not append) because importDocxWithTracking ++ // replaces the entire editor content, making old discussions stale ++ editor.setOption(discussionPlugin, 'discussions', newDiscussions); ++ editor.setOption(commentPlugin, 'uniquePathMap', new Map()); +``` +</details> + + +```shell +#!/bin/bash +# Locate importDocxWithTracking implementation to confirm replacement semantics +rg -n --glob '*.{ts,tsx}' -C 5 "importDocxWithTracking" +``` + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@apps/www/src/registry/ui/import-toolbar-button.tsx` around lines 121 - 164, +The code only replaces discussions and uniquePathMap when +result.discussions.length > 0, leaving stale discussions when an import clears +editor content; always reset them after import: after computing newDiscussions +(or when result.discussions is empty) call editor.setOption(discussionPlugin, +'discussions', result.discussions.length ? newDiscussions : []); and always call +editor.setOption(commentPlugin, 'uniquePathMap', new Map()); ensure you +reference the existing symbols (result.discussions, newDiscussions, +editor.setOption, discussionPlugin, commentPlugin, uniquePathMap) so the +discussion list and path map are cleared even when no DOCX discussions are +present. +``` + +</details> + +<!-- fingerprinting:phantom:medusa:eagle --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `nixpacks.toml` + +<details><summary>Code context</summary> + +```diff +@@ -0,0 +1,8 @@ ++[phases.install] ++cmds = ['yarn install --mode=skip-build'] ++ ++[phases.build] ++cmds = ['turbo build', 'turbo deploy'] ++ ++[start] ++cmd = 'node apps/www/.next/standalone/server.js' +``` +</details> + +_⚠️ Potential issue_ | _🔴 Critical_ + +<details> +<summary>🧩 Analysis chain</summary> + +🏁 Script executed: + +```shell +#!/bin/bash +# Check turbo.json for deploy task and static file handling +echo "=== Checking turbo.json for deploy task ===" +fd -t f "turbo.json" --exec cat {} \; + +echo "" +echo "=== Checking apps/www package.json scripts ===" +fd -g "package.json" apps/www --exec cat {} \; 2>/dev/null || echo "Not found with fd, trying direct path" +if [ -f "apps/www/package.json" ]; then + cat "apps/www/package.json" | jq '.scripts // .build // empty' 2>/dev/null || cat "apps/www/package.json" +fi + +echo "" +echo "=== Searching for static file copy or deployment scripts ===" +rg -n 'standalone|static.*copy|cp.*public|cp.*\.next|public.*static' --max-count=20 +``` + +Repository: arthrod/plate + +Length of output: 50372 + +--- + +🏁 Script executed: + +```shell +['NODE_OPTIONS="--max-old-space-size=8192" yarn build', 'mkdir -p apps/www/.next/standalone/apps/www/.next', 'cp -r apps/www/public apps/www/.next/standalone/apps/www/public', 'cp -r apps/www/.next/static apps/www/.next/standalone/apps/www/.next/static'] +``` + +Repository: arthrod/plate + +Length of output: 150 + +--- + +🏁 Script executed: + +```shell +# Let me verify the actual nixpacks.toml file and the correct path structure +echo "=== Current nixpacks.toml ===" +cat nixpacks.toml + +echo "" +echo "=== Checking railpack.json for reference on paths ===" +cat railpack.json | grep -A2 "buildCommand" + +echo "" +echo "=== Checking if standalone directory structure is apps/www/.next/standalone or nested ===" +# Look for any references to the standalone output path in build scripts +rg "\.next/standalone" --max-count=10 +``` + +Repository: arthrod/plate + +Length of output: 1287 + +--- + + + +</details> + +**Remove `turbo deploy` command and add static file copying to build phase.** + +The `turbo deploy` task does not exist in turbo.json, which will cause the build to fail. Additionally, Next.js standalone mode requires manually copying `public/` and `.next/static/` to the standalone output directory, which is currently missing. + +Update the build phase to: + +<details> +<summary>nixpacks.toml build configuration</summary> + +```toml +[phases.build] +cmds = [ + 'NODE_OPTIONS="--max-old-space-size=8192" yarn build', + 'mkdir -p apps/www/.next/standalone/apps/www/.next', + 'cp -r apps/www/public apps/www/.next/standalone/apps/www/public', + 'cp -r apps/www/.next/static apps/www/.next/standalone/apps/www/.next/static' +] +``` + +Also verify the start command path matches the standalone output structure: + +```toml +[start] +cmd = 'node apps/www/.next/standalone/apps/www/server.js' +``` + +</details> + +This aligns with the railpack.json configuration and ensures static assets are served correctly. + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@nixpacks.toml` around lines 1 - 8, Remove the non-existent "turbo deploy" +step from the [phases.build] cmds and replace the build commands to run the +actual Next build (use NODE_OPTIONS="--max-old-space-size=8192" yarn build), +then create the standalone directory and copy static assets into the standalone +output (ensure you create apps/www/.next/standalone/apps/www/.next and copy +apps/www/public to apps/www/.next/standalone/apps/www/public and +apps/www/.next/static to apps/www/.next/standalone/apps/www/.next/static). Also +update the [start] cmd to point to the standalone server entry (node +apps/www/.next/standalone/apps/www/server.js) so the runtime uses the standalone +output. +``` + +</details> + +<!-- fingerprinting:phantom:poseidon:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +✅ Addressed in commits a23dde9 to 908dc33 + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `packages/docx-io/src/lib/applyDocxTracking.spec.ts` + +<details><summary>Code context</summary> + +```diff +@@ -0,0 +1,1149 @@ ++import { describe, expect, it, mock } from 'bun:test'; ++ ++import { ++ applyAllTracking, ++ applyTrackedComments, ++ applyTrackedCommentsLocal, ++ type DocxImportComment, ++} from './importComments'; ++import { ++ applyTrackedChangeSuggestions, ++ type TrackingEditor, ++ type TRange, ++} from './importTrackChanges'; ++import type { DocxTrackedChange } from './types'; ++ ++// Mock editor factory ++function createMockEditor(): TrackingEditor { ++ return { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: (range: TRange) => ({ ++ current: range, ++ unref: mock(() => range), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ setOption: mock(() => {}), ++ }; ++} ++ ++// Mock search function that returns predictable ranges ++function createMockSearchRange( ++ tokenMap?: Map<string, TRange | null> ++): (editor: TrackingEditor, search: string) => TRange | null { ++ return (_editor, search) => { ++ if (tokenMap) { ++ return tokenMap.get(search) ?? null; ++ } ++ // Default behavior: return a range for tokens ++ if (search.includes('START') || search.includes('END')) { ++ return { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: search.length }, ++ }; ++ } ++ return null; ++ }; ++} ++ ++describe('applyDocxTracking', () => { ++ describe('applyTrackedChangeSuggestions', () => { ++ it('returns zero counts for empty changes', () => { ++ const editor = createMockEditor(); ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes: [], ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(0); ++ expect(result.deletions).toBe(0); ++ expect(result.total).toBe(0); ++ expect(result.errors).toEqual([]); ++ }); ++ ++ it('applies insertion suggestion', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'ins-1', ++ type: 'insert', ++ author: 'John Doe', ++ date: '2024-01-15T12:00:00Z', ++ startToken: '[[START:ins-1]]', ++ endToken: '[[END:ins-1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(1); ++ expect(result.deletions).toBe(0); ++ expect(result.total).toBe(1); ++ expect(editor.tf.setNodes).toHaveBeenCalled(); ++ }); ++ ++ it('applies deletion suggestion', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'del-1', ++ type: 'remove', ++ author: 'Jane Doe', ++ startToken: '[[START:del-1]]', ++ endToken: '[[END:del-1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(0); ++ expect(result.deletions).toBe(1); ++ expect(result.total).toBe(1); ++ }); ++ ++ it('handles missing start token', () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[START:missing]]', null); ++ tokenMap.set('[[END:missing]]', { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: 10 }, ++ }); ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'missing', ++ type: 'insert', ++ startToken: '[[START:missing]]', ++ endToken: '[[END:missing]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(tokenMap), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Missing token'); ++ }); ++ ++ it('handles missing end token', () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[START:missing]]', { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: 10 }, ++ }); ++ tokenMap.set('[[END:missing]]', null); ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'missing', ++ type: 'insert', ++ startToken: '[[START:missing]]', ++ endToken: '[[END:missing]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(tokenMap), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ }); ++ ++ it('applies multiple changes', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ { ++ id: 'change-2', ++ type: 'remove', ++ startToken: '[[START:2]]', ++ endToken: '[[END:2]]', ++ }, ++ { ++ id: 'change-3', ++ type: 'insert', ++ startToken: '[[START:3]]', ++ endToken: '[[END:3]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(2); ++ expect(result.deletions).toBe(1); ++ expect(result.total).toBe(3); ++ }); ++ ++ it('deletes tokens after applying marks', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'ins-1', ++ type: 'insert', ++ startToken: '[[START:ins-1]]', ++ endToken: '[[END:ins-1]]', ++ }, ++ ]; ++ ++ applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ // Delete should be called twice (start and end tokens) ++ expect(editor.tf.delete).toHaveBeenCalledTimes(2); ++ }); ++ ++ it('handles invalid rangeRef (null current)', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => ({ ++ current: null, ++ unref: mock(() => null), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Invalid range'); ++ }); ++ ++ it('handles exceptions during processing', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('Test error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Failed to apply change'); ++ expect(result.errors[0]).toContain('Test error'); ++ }); ++ ++ it('handles non-Error exceptions', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('string error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.errors[0]).toContain('string error'); ++ }); +``` +</details> + +_⚠️ Potential issue_ | _🟡 Minor_ + +**Test does not exercise non-Error exception path as named.** + +The test is named "handles non-Error exceptions" but throws `new Error('string error')`, which is an Error instance. This means the `error instanceof Error ? error.message` branch is taken, not the `String(error)` fallback intended for non-Error exceptions. + +To properly test non-Error exceptions, throw a primitive or plain object instead. + + + +<details> +<summary>🐛 Proposed fix</summary> + +```diff + const editor: TrackingEditor = { + api: { + string: mock(() => 'sample text'), + rangeRef: () => { +- throw new Error('string error'); ++ throw 'string error'; // Non-Error exception + }, + }, + tf: { + setNodes: mock(() => {}), + delete: mock(() => {}), + withMerging: mock((fn: () => void) => fn()), + }, + }; +``` +</details> + +<!-- suggestion_start --> + +<details> +<summary>📝 Committable suggestion</summary> + +> ‼️ **IMPORTANT** +> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. + +```suggestion + it('handles non-Error exceptions', () => { + const editor: TrackingEditor = { + api: { + string: mock(() => 'sample text'), + rangeRef: () => { + throw 'string error'; // Non-Error exception + }, + }, + tf: { + setNodes: mock(() => {}), + delete: mock(() => {}), + withMerging: mock((fn: () => void) => fn()), + }, + }; + + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.errors[0]).toContain('string error'); + }); +``` + +</details> + +<!-- suggestion_end --> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/src/lib/applyDocxTracking.spec.ts` around lines 335 - 369, +The test "handles non-Error exceptions" currently throws new Error('string +error') (an Error instance) in the mock rangeRef, so update the mock used in +that test to throw a non-Error value (e.g., throw 'string error' or throw { msg: +'string error' }) so the applyTrackedChangeSuggestions error-handling branch +that falls back to String(error) is exercised; locate the mock rangeRef inside +the test and replace the thrown value accordingly so +expect(result.errors[0]).toContain('string error') still passes. +``` + +</details> + +<!-- fingerprinting:phantom:poseidon:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `packages/docx-io/src/lib/applyDocxTracking.spec.ts` + +<details><summary>Code context</summary> + +```diff +@@ -0,0 +1,1149 @@ ++import { describe, expect, it, mock } from 'bun:test'; ++ ++import { ++ applyAllTracking, ++ applyTrackedComments, ++ applyTrackedCommentsLocal, ++ type DocxImportComment, ++} from './importComments'; ++import { ++ applyTrackedChangeSuggestions, ++ type TrackingEditor, ++ type TRange, ++} from './importTrackChanges'; ++import type { DocxTrackedChange } from './types'; ++ ++// Mock editor factory ++function createMockEditor(): TrackingEditor { ++ return { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: (range: TRange) => ({ ++ current: range, ++ unref: mock(() => range), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ setOption: mock(() => {}), ++ }; ++} ++ ++// Mock search function that returns predictable ranges ++function createMockSearchRange( ++ tokenMap?: Map<string, TRange | null> ++): (editor: TrackingEditor, search: string) => TRange | null { ++ return (_editor, search) => { ++ if (tokenMap) { ++ return tokenMap.get(search) ?? null; ++ } ++ // Default behavior: return a range for tokens ++ if (search.includes('START') || search.includes('END')) { ++ return { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: search.length }, ++ }; ++ } ++ return null; ++ }; ++} ++ ++describe('applyDocxTracking', () => { ++ describe('applyTrackedChangeSuggestions', () => { ++ it('returns zero counts for empty changes', () => { ++ const editor = createMockEditor(); ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes: [], ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(0); ++ expect(result.deletions).toBe(0); ++ expect(result.total).toBe(0); ++ expect(result.errors).toEqual([]); ++ }); ++ ++ it('applies insertion suggestion', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'ins-1', ++ type: 'insert', ++ author: 'John Doe', ++ date: '2024-01-15T12:00:00Z', ++ startToken: '[[START:ins-1]]', ++ endToken: '[[END:ins-1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(1); ++ expect(result.deletions).toBe(0); ++ expect(result.total).toBe(1); ++ expect(editor.tf.setNodes).toHaveBeenCalled(); ++ }); ++ ++ it('applies deletion suggestion', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'del-1', ++ type: 'remove', ++ author: 'Jane Doe', ++ startToken: '[[START:del-1]]', ++ endToken: '[[END:del-1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(0); ++ expect(result.deletions).toBe(1); ++ expect(result.total).toBe(1); ++ }); ++ ++ it('handles missing start token', () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[START:missing]]', null); ++ tokenMap.set('[[END:missing]]', { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: 10 }, ++ }); ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'missing', ++ type: 'insert', ++ startToken: '[[START:missing]]', ++ endToken: '[[END:missing]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(tokenMap), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Missing token'); ++ }); ++ ++ it('handles missing end token', () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[START:missing]]', { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: 10 }, ++ }); ++ tokenMap.set('[[END:missing]]', null); ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'missing', ++ type: 'insert', ++ startToken: '[[START:missing]]', ++ endToken: '[[END:missing]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(tokenMap), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ }); ++ ++ it('applies multiple changes', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ { ++ id: 'change-2', ++ type: 'remove', ++ startToken: '[[START:2]]', ++ endToken: '[[END:2]]', ++ }, ++ { ++ id: 'change-3', ++ type: 'insert', ++ startToken: '[[START:3]]', ++ endToken: '[[END:3]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: (node) => ++ typeof (node as Record<string, unknown>).text === 'string', ++ }); ++ ++ expect(result.insertions).toBe(2); ++ expect(result.deletions).toBe(1); ++ expect(result.total).toBe(3); ++ }); ++ ++ it('deletes tokens after applying marks', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'ins-1', ++ type: 'insert', ++ startToken: '[[START:ins-1]]', ++ endToken: '[[END:ins-1]]', ++ }, ++ ]; ++ ++ applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ // Delete should be called twice (start and end tokens) ++ expect(editor.tf.delete).toHaveBeenCalledTimes(2); ++ }); ++ ++ it('handles invalid rangeRef (null current)', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => ({ ++ current: null, ++ unref: mock(() => null), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Invalid range'); ++ }); ++ ++ it('handles exceptions during processing', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('Test error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(0); ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Failed to apply change'); ++ expect(result.errors[0]).toContain('Test error'); ++ }); ++ ++ it('handles non-Error exceptions', () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('string error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.errors[0]).toContain('string error'); ++ }); ++ ++ it('handles missing author (uses default)', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ author: undefined, ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(1); ++ }); ++ ++ it('handles invalid date (uses current time)', () => { ++ const editor = createMockEditor(); ++ const changes: DocxTrackedChange[] = [ ++ { ++ id: 'change-1', ++ type: 'insert', ++ date: 'invalid-date', ++ startToken: '[[START:1]]', ++ endToken: '[[END:1]]', ++ }, ++ ]; ++ ++ const result = applyTrackedChangeSuggestions({ ++ editor, ++ changes, ++ searchRange: createMockSearchRange(), ++ suggestionKey: 'suggestion', ++ getSuggestionKey: (id) => `suggestion_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.total).toBe(1); ++ }); ++ }); ++ ++ describe('applyTrackedComments', () => { ++ it('returns zero counts for empty comments', async () => { ++ const editor = createMockEditor(); ++ const result = await applyTrackedComments({ ++ editor, ++ comments: [], ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(0); ++ expect(result.skipped).toBe(0); ++ expect(result.errors).toEqual([]); ++ }); ++ ++ it('creates comment for valid tokens', async () => { ++ const editor = createMockEditor(); ++ const createDiscussion = mock(async () => ({ id: 'disc-new' })); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test comment', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { mutateAsync: createDiscussion }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(1); ++ expect(result.skipped).toBe(0); ++ expect(createDiscussion).toHaveBeenCalledWith( ++ expect.objectContaining({ ++ documentId: 'doc-1', ++ }) ++ ); ++ }); ++ ++ it('handles null ranges during cleanup', async () => { ++ const refs: Array<{ ++ current: TRange | null; ++ unref: () => TRange | null; ++ }> = []; ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: (range: TRange) => { ++ const ref = { current: range, unref: mock(() => range) }; ++ refs.push(ref); ++ return ref; ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => { ++ fn(); ++ refs.forEach((ref) => { ++ ref.current = null; ++ }); ++ }), ++ }, ++ setOption: mock(() => {}), ++ }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test comment', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(1); ++ expect(result.errors).toEqual([]); ++ expect(editor.tf.setNodes).toHaveBeenCalled(); ++ expect(editor.tf.delete).not.toHaveBeenCalled(); ++ }); ++ ++ it('skips comment with no tokens found', async () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[CMT_START:1]]', null); ++ tokenMap.set('[[CMT_END:1]]', null); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test comment', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(tokenMap), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(0); ++ expect(result.skipped).toBe(1); ++ }); ++ ++ it('creates point comment when only start token found', async () => { ++ const editor = createMockEditor(); ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[CMT_START:1]]', { ++ anchor: { path: [0, 0], offset: 0 }, ++ focus: { path: [0, 0], offset: 10 }, ++ }); ++ tokenMap.set('[[CMT_END:1]]', null); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Point comment', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: false, // Indicates end token not in original HTML ++ }, ++ ]; ++ ++ const createDiscussion = mock(async () => ({ id: 'disc-point' })); ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(tokenMap), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { mutateAsync: createDiscussion }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(1); ++ }); ++ ++ it('applies transient comment key when provided', async () => { ++ const editor = createMockEditor(); ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ getTransientCommentKey: () => 'transient_comment', ++ isText: () => true, ++ }); ++ ++ expect(editor.tf.setNodes).toHaveBeenCalledWith( ++ expect.objectContaining({ ++ transient_comment: true, ++ }), ++ expect.any(Object) ++ ); ++ }); ++ ++ it('calls onCommentsCreated callback when comments are created', async () => { ++ const editor = createMockEditor(); ++ const onCommentsCreated = mock(() => {}); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ onCommentsCreated, ++ }); ++ ++ expect(onCommentsCreated).toHaveBeenCalledTimes(1); ++ }); ++ ++ it('does not call onCommentsCreated when no comments created', async () => { ++ const editor = createMockEditor(); ++ const onCommentsCreated = mock(() => {}); ++ ++ const tokenMap = new Map<string, TRange | null>(); ++ tokenMap.set('[[CMT_START:1]]', null); ++ tokenMap.set('[[CMT_END:1]]', null); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(tokenMap), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ onCommentsCreated, ++ }); ++ ++ expect(onCommentsCreated).not.toHaveBeenCalled(); ++ }); ++ ++ it('handles invalid rangeRef (null current) for comments', async () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => ({ ++ current: null, ++ unref: mock(() => null), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.created).toBe(0); ++ expect(result.skipped).toBe(1); ++ }); ++ ++ it('handles empty document content (uses default)', async () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => ''), // Returns empty string ++ rangeRef: (range: TRange) => ({ ++ current: range, ++ unref: mock(() => range), ++ }), ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const createDiscussion = mock(async () => ({ id: 'disc-1' })); ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test comment', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { mutateAsync: createDiscussion }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(createDiscussion).toHaveBeenCalledWith( ++ expect.objectContaining({ ++ documentContent: 'Imported comment', ++ }) ++ ); ++ }); ++ ++ it('handles comment with commentPlugin and setOption', async () => { ++ const editor = createMockEditor(); ++ const commentPlugin = { key: 'comment' }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ commentPlugin, ++ }); ++ ++ expect(editor.setOption).toHaveBeenCalledWith( ++ commentPlugin, ++ 'updateTimestamp', ++ expect.any(Number) ++ ); ++ }); ++ ++ it('handles exceptions during comment processing', async () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('Comment error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.errors.length).toBe(1); ++ expect(result.errors[0]).toContain('Failed to apply comment'); ++ expect(result.errors[0]).toContain('Comment error'); ++ }); ++ ++ it('unrefs rangeRefs when create discussion fails', async () => { ++ const startUnref = mock(() => null); ++ const endUnref = mock(() => null); ++ let rangeRefCalls = 0; ++ ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: (range: TRange) => { ++ rangeRefCalls += 1; ++ return { ++ current: range, ++ unref: rangeRefCalls === 1 ? startUnref : endUnref, ++ }; ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => { ++ throw new Error('API fail'); ++ }), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.errors.length).toBe(1); ++ expect(startUnref).toHaveBeenCalledTimes(1); ++ expect(endUnref).toHaveBeenCalledTimes(1); ++ }); ++ ++ it('handles non-Error exceptions in comments', async () => { ++ const editor: TrackingEditor = { ++ api: { ++ string: mock(() => 'sample text'), ++ rangeRef: () => { ++ throw new Error('string comment error'); ++ }, ++ }, ++ tf: { ++ setNodes: mock(() => {}), ++ delete: mock(() => {}), ++ withMerging: mock((fn: () => void) => fn()), ++ }, ++ }; ++ ++ const comments: DocxImportComment[] = [ ++ { ++ id: 'cmt-1', ++ text: 'Test', ++ startToken: '[[CMT_START:1]]', ++ endToken: '[[CMT_END:1]]', ++ hasStartToken: true, ++ hasEndToken: true, ++ }, ++ ]; ++ ++ const result = await applyTrackedComments({ ++ editor, ++ comments, ++ searchRange: createMockSearchRange(), ++ documentId: 'doc-1', ++ createDiscussionWithComment: { ++ mutateAsync: mock(async () => ({ id: 'disc-1' })), ++ }, ++ commentKey: 'comment', ++ getCommentKey: (id) => `comment_${id}`, ++ isText: () => true, ++ }); ++ ++ expect(result.errors[0]).toContain('string comment error'); ++ }); +``` +</details> + +_⚠️ Potential issue_ | _🟡 Minor_ + +**Same issue: test throws Error instance instead of non-Error exception.** + +Similar to the earlier test at line 335, this test is named "handles non-Error exceptions in comments" but throws `new Error('string comment error')`. To properly test the `String(error)` fallback path, throw a non-Error value. + + + +<details> +<summary>🐛 Proposed fix</summary> + +```diff + const editor: TrackingEditor = { + api: { + string: mock(() => 'sample text'), + rangeRef: () => { +- throw new Error('string comment error'); ++ throw 'string comment error'; // Non-Error exception + }, + }, +``` +</details> + +<!-- suggestion_start --> + +<details> +<summary>📝 Committable suggestion</summary> + +> ‼️ **IMPORTANT** +> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements. + +```suggestion + it('handles non-Error exceptions in comments', async () => { + const editor: TrackingEditor = { + api: { + string: mock(() => 'sample text'), + rangeRef: () => { + throw 'string comment error'; // Non-Error exception + }, + }, + tf: { + setNodes: mock(() => {}), + delete: mock(() => {}), + withMerging: mock((fn: () => void) => fn()), + }, + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mock(async () => ({ id: 'disc-1' })), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.errors[0]).toContain('string comment error'); + }); +``` + +</details> + +<!-- suggestion_end --> + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/src/lib/applyDocxTracking.spec.ts` around lines 931 - 971, +The test "handles non-Error exceptions in comments" is currently throwing an +Error object (new Error('string comment error')) but should throw a non-Error +value to exercise the String(error) fallback; update the throw inside the mock +for editor.api.rangeRef in the applyTrackedComments spec to throw a primitive or +non-Error object (e.g., a string or plain object) so the code path in +applyTrackedComments that handles non-Error exceptions is exercised and the +assertion expecting the stringified message still passes. +``` + +</details> + +<!-- fingerprinting:phantom:poseidon:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### coderabbitai[bot] — 2/20/2026, 4:00:32 AM + +> File: `packages/docx-io/src/lib/DocxIOPlugin.tsx` + +<details><summary>Code context</summary> + +```diff +@@ -0,0 +1,76 @@ ++/** ++ * DOCX Import/Export Plugin Kit for Plate.js ++ * ++ * This module provides an easy-to-use plugin kit that combines both ++ * import and export functionality for DOCX files. ++ * ++ * @example ++ * ```tsx ++ * import { DocxIOPlugin, importDocx, exportToDocx } from '@platejs/docx-io'; ++ * ++ * // Use the plugin in your editor ++ * const editor = createPlateEditor({ ++ * plugins: [ ++ * ...BaseEditorKit, ++ * DocxIOPlugin, ++ * ], ++ * }); ++ * ++ * // Import a DOCX file ++ * const { nodes } = await importDocx(editor, arrayBuffer); ++ * editor.tf.insertNodes(nodes); ++ * ++ * // Export to DOCX ++ * const blob = await exportToDocx(editor.children, { ++ * editorPlugins: editor.plugins, ++ * }); ++ * ``` ++ * ++ * @packageDocumentation ++ */ ++ ++'use client'; ++ ++import { DocxExportPlugin } from './docx-export-plugin'; ++ ++export { importDocx } from './importDocx'; ++export { ++ downloadDocx, ++ exportEditorToDocx, ++ exportToDocx, ++} from './docx-export-plugin'; ++export { htmlToDocxBlob } from './exportDocx'; +``` +</details> + +_⚠️ Potential issue_ | _🟠 Major_ + +<details> +<summary>🧩 Analysis chain</summary> + +🏁 Script executed: + +```shell +#!/bin/bash +# Find all 'use client' directives and show context +echo "=== Files with 'use client' directive and context (first 25 lines) ===" +rg -l "'use client'" --type tsx | head -10 | while read file; do + echo "--- $file ---" + head -25 "$file" | cat -n + echo "" +done +``` + +Repository: arthrod/plate + +Length of output: 172 + +--- + +🏁 Script executed: + +```shell +#!/bin/bash +# Check if there are files with JSDoc/comments before 'use client' +echo "=== Files with comments/JSDoc before 'use client' ===" +rg -B3 "^'use client'" --type tsx --type ts | head -50 +``` + +Repository: arthrod/plate + +Length of output: 155 + +--- + +🌐 Web query: + +`Next.js 'use client' directive placement requirements documentation` + +💡 Result: + +Next.js documents that the `"use client"` directive **must be placed at the very top of the file, before any imports or other code**. + +- Next.js API reference: “add the `'use client'` directive at the top of the file, before any imports” [1] +- Next.js App Router glossary: it “must be placed at the top of a file, before any imports or other code” [2] +- Next.js “Server and Client Components” guide: add `"use client"` “at the top of the file, above your imports” [3] + +**Sources:** [1] [2] [3] + +--- + +🏁 Script executed: + +```shell +#!/bin/bash +# Find and display files with 'use client' directive +echo "=== Finding files with 'use client' directive ===" +rg "'use client'" --files-with-matches | head -10 + +# Display the specific file mentioned in the review +echo -e "\n=== DocxIOPlugin.tsx content (first 45 lines) ===" +find . -name "DocxIOPlugin.tsx" -exec head -45 {} + +``` + +Repository: arthrod/plate + +Length of output: 1793 + +--- + +🏁 Script executed: + +```shell +#!/bin/bash +# Check general pattern: how many files have 'use client' and what comes before it +echo "=== Pattern of 'use client' placement ===" +rg -B2 "'use client'" | head -100 +``` + +Repository: arthrod/plate + +Length of output: 4142 + +--- + + + +</details> + +**Move `'use client'` directive to the top of the file, before the JSDoc comment.** + +Next.js requires the `'use client'` directive to be placed at the very top of the file, before any imports or other code. Currently on line 32, it comes after the JSDoc documentation block (lines 1-30). Move it to line 1, then place the JSDoc comment after. + +<details> +<summary>🤖 Prompt for AI Agents</summary> + +``` +Verify each finding against the current code and only fix it if needed. + +In `@packages/docx-io/src/lib/DocxIOPlugin.tsx` around lines 1 - 42, The 'use +client' directive must be at the very top of the module; move the string literal +'use client' from after the JSDoc to the first line of DocxIOPlugin.tsx (above +the JSDoc block) so Next.js recognizes the file as a client component, keeping +the existing JSDoc and imports (e.g., DocxExportPlugin import and exports like +importDocx, downloadDocx, exportEditorToDocx, exportToDocx, htmlToDocxBlob) +unchanged otherwise. +``` + +</details> + +<!-- fingerprinting:phantom:medusa:ocelot --> + +<!-- This is an auto-generated comment by CodeRabbit --> + +--- + +### kiloconnect[bot] — 2/20/2026, 3:58:29 AM + +<!-- kilo-review --> +## Code Review Summary + +**Status:** No Issues Found | **Recommendation:** Merge + +<details> +<summary><b>Files Reviewed (7 source files)</b></summary> + +- `apps/www/src/registry/lib/discussion-ids.ts` - New utility for generating discussion IDs +- `apps/www/src/registry/ui/comment.tsx` - Extended with DOCX author metadata +- `apps/www/src/registry/ui/import-toolbar-button.tsx` - DOCX import with tracking support +- `apps/www/src/registry/ui/export-toolbar-button.tsx` - DOCX export with discussions +- `apps/www/src/registry/ui/block-discussion.tsx` - Fixed multi-comment key handling +- `apps/www/src/registry/ui/table-node-static.tsx` - Added border styles for DOCX export +- `apps/www/src/registry/components/editor/plugins/discussion-kit.tsx` - Extended TDiscussion type + +</details> + +<details> +<summary><b>Review Notes</b></summary> + +### Overview +This PR implements DOCX import/export with full tracking support including: +- Tracked changes (insertions/deletions) as suggestions +- Comments with full metadata (author, dates, threading) +- Round-trip fidelity via OOXML paraId/parentParaId + +### Code Quality +- Clean TypeScript types with proper documentation +- Follows existing codebase patterns +- Proper null/undefined handling with fallback chains +- Type assertion (`editor as any`) in import-toolbar-button.tsx:94 is acceptable given complex editor typing + +### Testing +- Comprehensive test files added (*.spec.tsx) for new functionality +- Tests cover discussion IDs, comment handling, DOCX export/import + +</details> + +--- + +### gemini-code-assist[bot] — 2/20/2026, 3:56:06 AM + +> File: `packages/docx-io/src/lib/html-to-docx/docx-document.ts` + +<details><summary>Code context</summary> + +```diff +@@ -957,6 +1067,289 @@ class DocxDocument { + generateFooterXML(vTree: VTree): Promise<FooterResult> { + return this.generateSectionXML(vTree, 'footer') as Promise<FooterResult>; + } ++ ++ // ============================================================================ ++ // Tracking Support Methods (Comments and Suggestions) ++ // ============================================================================ ++ ++ /** ++ * Get a revision ID for a suggestion. Creates a new one if needed. ++ * Ensures consistent IDs for the same suggestion across multiple occurrences. ++ */ ++ getRevisionId(id?: string): number { ++ if (!id) { ++ this.lastRevisionId += 1; ++ return this.lastRevisionId; ++ } ++ ++ const existing = this.revisionIdMap.get(id); ++ if (existing !== undefined) { ++ return existing; ++ } ++ ++ this.lastRevisionId += 1; ++ this.revisionIdMap.set(id, this.lastRevisionId); ++ return this.lastRevisionId; ++ } ++ ++ /** ++ * Ensure a comment exists in the document and return its numeric ID. ++ * Updates metadata if the comment already exists but had missing fields. ++ */ ++ ensureComment(data: Partial<CommentPayload>, parentParaId?: string): number { ++ const { id, authorName, authorInitials, date, text } = data; ++ const commentId = ++ id !== undefined ? id : `comment-${this.lastCommentId + 1}`; ++ let numericId = this.commentIdMap.get(commentId); ++ ++ if (numericId === undefined) { ++ this.lastCommentId += 1; ++ numericId = this.lastCommentId; ++ this.commentIdMap.set(commentId, numericId); ++ } ++ ++ const existing = this.comments.find((item) => item.id === numericId); ++ if (existing) { ++ // Update missing fields ++ if (!existing.authorName && authorName) { ++ existing.authorName = authorName; ++ } ++ if (!existing.authorInitials && authorInitials) { ++ existing.authorInitials = authorInitials; ++ } ++ if (!existing.date && date) { ++ existing.date = date; ++ } ++ if (!existing.text && text) { ++ existing.text = text; ++ } ++ if (!existing.parentParaId && parentParaId) { ++ existing.parentParaId = parentParaId; ++ } ++ return numericId; ++ } ++ ++ // Preserve imported paraId when provided; otherwise generate fresh. ++ // Register in allocatedIds to prevent collisions with generated IDs. ++ let paraId: string; ++ if (data.paraId) { ++ paraId = data.paraId; ++ allocatedIds.add(paraId); ++ } else { ++ paraId = generateHexId(); ++ } ++ ++ const entry = { ++ id: numericId, ++ authorName: authorName || 'unknown', ++ authorInitials: authorInitials || '', ++ date, ++ durableId: generateHexId(), ++ paraId, ++ parentParaId, ++ text: text || 'Imported comment', ++ }; ++ this.comments.push(entry); ++ ++ return numericId; ++ } ++ ++ /** ++ * Get the numeric ID for a comment, creating it if necessary. ++ */ ++ getCommentId(id: string): number { ++ if (id === undefined || id === null) { +``` +</details> + +![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) + +This filter removes empty lines from multi-line comments. If a user intentionally added empty lines for spacing in their comment, this formatting will be lost during export. Consider removing the filter to preserve fidelity. + +```typescript + const paragraphs = String(comment.text || '') + .split(/\r?\n/); +``` + +--- + +### gemini-code-assist[bot] — 2/20/2026, 3:56:06 AM + +> File: `packages/docx-io/src/lib/html-to-docx/docx-document.ts` + +<details><summary>Code context</summary> + +```diff +@@ -957,6 +1067,289 @@ class DocxDocument { + generateFooterXML(vTree: VTree): Promise<FooterResult> { + return this.generateSectionXML(vTree, 'footer') as Promise<FooterResult>; + } ++ ++ // ============================================================================ ++ // Tracking Support Methods (Comments and Suggestions) ++ // ============================================================================ ++ ++ /** ++ * Get a revision ID for a suggestion. Creates a new one if needed. ++ * Ensures consistent IDs for the same suggestion across multiple occurrences. ++ */ ++ getRevisionId(id?: string): number { ++ if (!id) { ++ this.lastRevisionId += 1; ++ return this.lastRevisionId; ++ } ++ ++ const existing = this.revisionIdMap.get(id); ++ if (existing !== undefined) { ++ return existing; ++ } ++ ++ this.lastRevisionId += 1; ++ this.revisionIdMap.set(id, this.lastRevisionId); ++ return this.lastRevisionId; ++ } ++ ++ /** ++ * Ensure a comment exists in the document and return its numeric ID. ++ * Updates metadata if the comment already exists but had missing fields. ++ */ ++ ensureComment(data: Partial<CommentPayload>, parentParaId?: string): number { ++ const { id, authorName, authorInitials, date, text } = data; ++ const commentId = ++ id !== undefined ? id : `comment-${this.lastCommentId + 1}`; ++ let numericId = this.commentIdMap.get(commentId); ++ ++ if (numericId === undefined) { ++ this.lastCommentId += 1; ++ numericId = this.lastCommentId; ++ this.commentIdMap.set(commentId, numericId); ++ } ++ ++ const existing = this.comments.find((item) => item.id === numericId); ++ if (existing) { ++ // Update missing fields ++ if (!existing.authorName && authorName) { ++ existing.authorName = authorName; ++ } ++ if (!existing.authorInitials && authorInitials) { ++ existing.authorInitials = authorInitials; ++ } ++ if (!existing.date && date) { ++ existing.date = date; ++ } ++ if (!existing.text && text) { ++ existing.text = text; ++ } ++ if (!existing.parentParaId && parentParaId) { ++ existing.parentParaId = parentParaId; ++ } ++ return numericId; ++ } ++ ++ // Preserve imported paraId when provided; otherwise generate fresh. ++ // Register in allocatedIds to prevent collisions with generated IDs. ++ let paraId: string; ++ if (data.paraId) { ++ paraId = data.paraId; ++ allocatedIds.add(paraId); ++ } else { ++ paraId = generateHexId(); ++ } ++ ++ const entry = { ++ id: numericId, ++ authorName: authorName || 'unknown', ++ authorInitials: authorInitials || '', ++ date, ++ durableId: generateHexId(), ++ paraId, ++ parentParaId, ++ text: text || 'Imported comment', ++ }; ++ this.comments.push(entry); ++ ++ return numericId; ++ } ++ ++ /** ++ * Get the numeric ID for a comment, creating it if necessary. ++ */ ++ getCommentId(id: string): number { ++ if (id === undefined || id === null) { ++ return this.ensureComment({ id: undefined }); ++ } ++ return this.ensureComment({ id }); ++ } ++ ++ /** ++ * Generate the comments.xml file content. ++ * Matches reference library structure: w14:paraId on paragraphs, ++ * CommentReference style on first run, text runs with formatting. ++ */ ++ generateCommentsXML(): string { ++ const w = namespaces.w; ++ const commentsXML = create(COMMENTS_TEMPLATE); ++ const root = commentsXML.root(); ++ ++ this.comments.forEach((comment) => { ++ const commentElement = root ++ .ele(w, 'comment') ++ .att(w, 'id', String(comment.id)) ++ .att(w, 'author', comment.authorName || 'unknown'); ++ ++ if (comment.authorInitials) { ++ commentElement.att(w, 'initials', comment.authorInitials); ++ } ++ if (comment.date) { ++ commentElement.att(w, 'date', comment.date); ++ } ++ ++ // Split multi-line comment text into paragraphs ++ const paragraphs = String(comment.text || '') ++ .split(/\r?\n/) ++ .filter((line, index, arr) => line.length > 0 || arr.length === 1); ++ ++ paragraphs.forEach((line, pIdx) => { ++ const pElement = commentElement.ele(w, 'p'); ++ ++ // Add w14:paraId and w14:textId per OOXML spec ++ pElement.att(namespaces.w14, 'paraId', comment.paraId); ++ pElement.att(namespaces.w14, 'textId', '77777777'); ++ ++ // Paragraph properties ++ pElement ++ .ele(w, 'pPr') ++ .ele(w, 'pStyle') ++ .att(w, 'val', 'CommentText') ++ .up() ++ .up(); ++ ++ // First paragraph gets CommentReference run ++ if (pIdx === 0) { ++ const refRun = pElement.ele(w, 'r'); ++ refRun ++ .ele(w, 'rPr') ++ .ele(w, 'rStyle') ++ .att(w, 'val', 'CommentReference') ++ .up() ++ .up(); ++ refRun.ele(w, 'annotationRef').up(); ++ refRun.up(); ++ } ++ ++ // Text run ++ const textRun = pElement.ele(w, 'r'); ++ textRun ++ .ele(w, 'rPr') ++ .ele(w, 'color') ++ .att(w, 'val', '000000') ++ .up() ++ .ele(w, 'sz') ++ .att(w, 'val', '20') ++ .up() ++ .ele(w, 'szCs') ++ .att(w, 'val', '20') ++ .up() ++ .up(); ++ textRun ++ .ele(w, 't') ++ .att('http://www.w3.org/XML/1998/namespace', 'space', 'preserve') ++ .txt(line) ++ .up(); +``` +</details> + +![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg) + +Using `new Date().getTimezoneOffset()` uses the current system offset, which may differ from the offset at the comment's date due to Daylight Saving Time (DST). This can result in exported UTC timestamps being off by an hour if the comment was created in a different DST period than the current one. + +```typescript + const dateObj = new Date(comment.date); + const fakeMs = dateObj.getTime(); + const tzMs = dateObj.getTimezoneOffset() * 60_000; + const realUtc = new Date(fakeMs + tzMs); +``` + +--- + +### gemini-code-assist[bot] — 2/20/2026, 3:51:08 AM + +## Summary of Changes + +Hello @arthrod, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! + +This pull request significantly upgrades the document processing capabilities by integrating advanced DOCX import and export features. It enables the editor to seamlessly handle tracked changes and comments, ensuring fidelity with Word documents. The update also includes a substantial internal restructuring and comprehensive testing to support these new functionalities. + +### Highlights + +* **DOCX Import/Export with Tracking**: Introduced comprehensive support for importing and exporting DOCX files, including tracked changes (suggestions) and comments, by refactoring the logic into a new `@platejs/docx-io` package. +* **Enhanced Comment and Suggestion Handling**: Updated existing components and added new utilities to manage complex Word document features such as threaded comments, author information, and precise positioning of tracked changes within the editor. +* **Internal Cleanup and Testing**: Performed significant internal cleanup by removing outdated Claude AI agent configurations and documentation, alongside adding extensive unit and integration tests for the new DOCX I/O functionalities to ensure robustness. +* **Next.js Standalone Output**: Configured the Next.js application to use `output: 'standalone'`, optimizing deployment for self-hosting environments. + +<details> +<summary><b>Changelog</b></summary> + +* **.claude/AGENTS.md** + * Removed internal AI agent documentation. +* **.claude/commands/changeset.md** + * Removed internal documentation for creating changesets. +* **.claude/commands/docs-plugin.md** + * Removed internal documentation for plugin documentation. +* **.claude/commands/sync-testing-skill.md** + * Removed internal documentation for syncing testing skills. +* **.claude/commands/translate.md** + * Removed internal documentation for translation. +* **.claude/docs/docs-api.md** + * Removed internal documentation for API docs. +* **.claude/docs/plans/2026-01-23-fix-vercel-caching-costs-plan.md** + * Removed internal plan document. +* **.claude/prompt.json** + * Removed internal prompt configuration. +* **.claude/rules/1-app-design-document.mdc** + * Removed internal rule document. +* **.claude/rules/2-tech-stack.mdc** + * Removed internal rule document. +* **.claude/rules/3-project-status.mdc** + * Removed internal rule document. +* **.claude/rules/components.mdc** + * Removed internal rule document. +* **.claude/rules/react.mdc** + * Removed internal rule document. +* **.claude/rules/testing.mdc** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/AGENTS.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/SKILL.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/advanced-event-handler-refs.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/advanced-use-latest.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/async-api-routes.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/async-defer-await.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/async-dependencies.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/async-parallel.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/async-suspense-boundaries.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/bundle-barrel-imports.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/bundle-conditional.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/bundle-defer-third-party.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/bundle-dynamic-imports.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/bundle-preload.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/client-event-listeners.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/client-localstorage-schema.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/client-passive-event-listeners.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/client-swr-dedup.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-batch-dom-css.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-cache-function-results.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-cache-property-access.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-cache-storage.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-combine-iterations.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-early-exit.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-hoist-regexp.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-index-maps.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-length-check-first.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-min-max-loop.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-set-map-lookups.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/js-tosorted-immutable.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-activity.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-conditional-render.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-content-visibility.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-hoist-jsx.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-svg-precision.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rendering-usetransition-loading.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-defer-reads.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-dependencies.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-derived-state.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-functional-setstate.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-lazy-state-init.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-memo-with-default-value.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-memo.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md** + * Removed internal rule document. +* **.claude/rules/vercel-react-best-practices/rules/rerender-transitions.md** + * Removed internal rule document. +* **.claude/rules/writing-changesets.mdc** + * Removed internal rule document. +* **.claude/scripts/post-compact.sh** + * Removed internal script. +* **.claude/scripts/session-start.sh** + * Removed internal script. +* **.claude/scripts/user-prompt-submit.sh** + * Removed internal script. +* **.claude/settings.json** + * Removed internal settings. +* **.claude/skiller.toml** + * Modified to disable `generate_from_rules`. +* **.gitignore** + * Updated to ignore new Skiller-related files. +* **apps/www/next.config.ts** + * Added `output: 'standalone'` configuration. +* **apps/www/public/r/block-discussion.json** + * Updated `block-discussion.tsx` to use new comment key retrieval functions and improved discussion resolution logic. +* **apps/www/public/r/comment.json** + * Updated `comment.tsx` to include `authorName`, `authorInitials`, `paraId`, and `parentParaId` in `TComment` type and use them for display. +* **apps/www/public/r/discussion-kit.json** + * Updated `discussion-kit.tsx` to include `authorName`, `authorInitials`, and `paraId` in `TDiscussion` type. +* **apps/www/public/r/docx-export-kit.json** + * Updated `docx-export-kit.tsx` to include `SuggestionLeafDocx` for `KEYS.suggestion` override. +* **apps/www/public/r/export-toolbar-button.json** + * Updated `export-toolbar-button.tsx` to pass `tracking` options to `exportToDocx`, including discussion and user resolution. +* **apps/www/public/r/import-toolbar-button.json** + * Updated `import-toolbar-button.tsx` to use `importDocxWithTracking`, handle imported users and discussions, and convert them to `TDiscussion` format. +* **apps/www/public/r/table-node.json** + * Updated `table-node-static.tsx` to include `cellBorderStyles` for inline border styling in DOCX export. +* **apps/www/src/registry/components/editor/plugins/discussion-kit.spec.tsx** + * Added new test file for `discussion-kit.tsx`. +* **apps/www/src/registry/components/editor/plugins/discussion-kit.tsx** + * Updated `TDiscussion` type. +* **apps/www/src/registry/components/editor/plugins/docx-export-kit.spec.tsx** + * Added new test file for `docx-export-kit.tsx`. +* **apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx** + * Added `SuggestionLeafDocx` import and override. +* **apps/www/src/registry/examples/playground-demo.tsx** + * Removed `PlaywrightPlugin` import and usage. +* **apps/www/src/registry/lib/discussion-ids.spec.ts** + * Added new test file for `discussion-ids.ts`. +* **apps/www/src/registry/lib/discussion-ids.ts** + * Added new utility file for generating discussion IDs. +* **apps/www/src/registry/ui/comment.spec.ts** + * Added new test file for `comment.tsx`. +* **apps/www/src/registry/ui/comment.tsx** + * Updated `TComment` type and display logic. +* **apps/www/src/registry/ui/export-toolbar-button.tsx** + * Updated `exportToWord` to include tracking data. +* **apps/www/src/registry/ui/import-toolbar-button.tsx** + * Updated `openDocxFilePicker` to use `importDocxWithTracking` and process tracking data. +* **apps/www/src/registry/ui/suggestion-node-docx.spec.tsx** + * Added new test file for `suggestion-node-docx.tsx`. +* **apps/www/src/registry/ui/suggestion-node-docx.tsx** + * Added new component for DOCX export of suggestions. +* **apps/www/src/registry/ui/table-node-static.spec.tsx** + * Added new test file for `table-node-static.tsx`. +* **apps/www/src/registry/ui/table-node-static.tsx** + * Added `cellBorderStyles` function and applied it to `TableCellElementStatic`. +* **biome.jsonc** + * Updated `files.ignore` to include `mammoth.js` and `.claude`. +* **nixpacks.toml** + * Added new Nixpacks configuration file. +* **packages/docx-io/package.json** + * Removed `mammoth` dependency, added new dev dependencies for Plate plugins, and added `@platejs/comment` to peer dependencies. +* **packages/docx-io/src/lib/DocxIOPlugin.spec.tsx** + * Added new test file for `DocxIOPlugin.tsx`. +* **packages/docx-io/src/lib/DocxIOPlugin.tsx** + * Added new plugin file, re-exporting DOCX import/export functionality. +* **packages/docx-io/src/lib/__tests__/complex-tracking-export.spec.ts** + * Added new test file for complex tracking export. +* **packages/docx-io/src/lib/__tests__/docx-io-demo.spec.ts** + * Added new test file for DOCX I/O demo. +* **packages/docx-io/src/lib/__tests__/export-comment-ids.spec.ts** + * Added new test file for comment ID export. +* **packages/docx-io/src/lib/__tests__/export-ooxml-comments.spec.ts** + * Added new test file for OOXML comment export. +* **packages/docx-io/src/lib/__tests__/export-replies-id.spec.ts** + * Added new test file for reply ID export. +* **packages/docx-io/src/lib/__tests__/export-replies.spec.ts** + * Added new test file for reply export. +* **packages/docx-io/src/lib/__tests__/lists.spec.tsx** + * Updated list import test to reflect flattened structure. +* **packages/docx-io/src/lib/__tests__/preprocessMammothHtml.spec.ts** + * Added new test file for `preprocessMammothHtml`. +* **packages/docx-io/src/lib/__tests__/roundtrip-audit.spec.ts** + * Added new test file for round-trip audit. +* **packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx** + * Updated round-trip tests to use new plugin structure and added comment/tracking tests. +* **packages/docx-io/src/lib/__tests__/xml-builder-falsy-ids.spec.ts** + * Added new test file for XML builder. +* **packages/docx-io/src/lib/applyDocxTracking.spec.ts** + * Added new test file for `applyDocxTracking`. +* **packages/docx-io/src/lib/comment-templates.ts** + * Added new file with XML templates for comments. +* **packages/docx-io/src/lib/docx-export-plugin.tsx** + * Modified to integrate tracking options into `exportToDocxInternal` and re-export tracking types/utilities. +* **packages/docx-io/src/lib/exportComments.ts** + * Added new file for DOCX comments export. +* **packages/docx-io/src/lib/exportDocx.ts** + * Renamed from `html-to-docx.ts`, updated to use `jszip` and `addFilesToContainer`. +* **packages/docx-io/src/lib/exportTrackChanges.ts** + * Added new file for injecting DOCX tracking tokens. +* **packages/docx-io/src/lib/html-to-docx/constants.ts** + * Added new constants for comment-related XML parts and relationships. +* **packages/docx-io/src/lib/html-to-docx/docx-document.ts** + * Modified `DocxDocument` class to support comments and revisions, including new methods for generating comment-related XML files. +* **packages/docx-io/src/lib/html-to-docx/helpers/index.ts** + * Updated barrel export to include new XML builder helpers. +* **packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts** + * Modified `buildList` to handle `<li>` elements correctly and `convertVTreeToXML` to handle empty text nodes. +* **packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts** + * Modified `buildRun` and `buildRunsFromTextWithTokens` to process DOCX tracking tokens and generate appropriate XML. +* **packages/docx-io/src/lib/html-to-docx/index.ts** + * Updated barrel export to include new types and tracking utilities. +* **packages/docx-io/src/lib/html-to-docx/namespaces.ts** + * Added new namespaces for Word 2010, 2012, 2016, and 2018 WordML. +* **packages/docx-io/src/lib/html-to-docx/schemas/core.ts** + * Modified `generateCoreXML` to use `toLocalWithZ` for date formatting. +* **packages/docx-io/src/lib/html-to-docx/schemas/index.ts** + * Updated barrel export. +* **packages/docx-io/src/lib/html-to-docx/schemas/styles.ts** + * Added new styles for `CommentText`, `CommentTextChar`, `CommentReference`, `CommentSubject`, and `CommentSubjectChar`. +* **packages/docx-io/src/lib/html-to-docx/tracking.ts** + * Added new file for DOCX tracking token definitions and parsing. +* **packages/docx-io/src/lib/importComments.ts** + * Added new file for applying tracked comments. +* **packages/docx-io/src/lib/importDocx.ts** + * Added new file for DOCX import logic, including `importDocxWithTracking` and `preprocessMammothHtml`. +* **packages/docx-io/src/lib/importTrackChanges.ts** + * Added new file for applying tracked changes. +* **packages/docx-io/src/lib/searchRange.ts** + * Added new file for searching ranges. +* **packages/docx-io/src/lib/types.ts** + * Added new file for DOCX IO types. +* **packages/docx-io/src/lib/utils.ts** + * Added new utility file. +</details> + +<details> +<summary><b>Activity</b></summary> + +* The author `arthrod` has marked all items in the pull request checklist as complete, including type checking, linting, testing, and changelog updates. +</details> + +<details> +<summary><b>Using Gemini Code Assist</b></summary> +<br> + +The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips. + +<b>Invoking Gemini</b> + +You can request assistance from Gemini at any point by creating a comment using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands on the current page. + +Feature | Command | Description +--- | --- | --- +Code Review | `/gemini review` | Performs a code review for the current pull request in its current state. +Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state. +Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. +Help | `/gemini help` | Displays a list of available commands. + +<b>Customization</b> + +To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). + +<b>Limitations & Feedback</b> + +Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up [here](https://google.qualtrics.com/jfe/form/SV_2cyuGuTWsEw84yG). + +<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b> +</details> + + +[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487). + + +--- + +### coderabbitai[bot] — 2/20/2026, 3:49:15 AM + +<!-- This is an auto-generated comment: summarize by coderabbit.ai --> +<!-- walkthrough_start --> + +<details> +<summary>📝 Walkthrough</summary> + +<!-- This is an auto-generated comment: release notes by coderabbit.ai --> + +## Summary by CodeRabbit + +* **New Features** + * Export discussions and suggestions to Word with author metadata preserved. + * Import Word (DOCX) with tracked discussions/suggestions mapped into the editor. + +* **Improvements** + * Richer DOCX export: suggestion handling and improved table cell border rendering. + * Discussion UI shows author names/initials when available; exports include discussion tracking. + +<!-- end of auto-generated comment: release notes by coderabbit.ai --> +## Walkthrough + +Removes extensive .claude configuration/docs and adds end-to-end DOCX tracked-changes/comments support: token injection, OOXML comment parts, DocxDocument serialization, editor discussion/comment type extensions, import/export UI wiring, and extensive tests. + +## Changes + +|Cohort / File(s)|Summary| +|---|---| +|**Claude config/docs removed** <br> `\.claude/...` (e.g. `.claude/AGENTS.md`, `.claude/commands/*`, `.claude/docs/*`, `.claude/rules/*`, `.claude/scripts/*`, `.claude/prompt.json`, `.claude/settings.json`, `.claude/skiller.toml`)|Deletes the majority of .claude configuration, agent guides, rules, scripts and prompt/hook settings.| +|**DOCX tracking core** <br> `packages/docx-io/src/lib/exportTrackChanges.ts`, `packages/docx-io/src/lib/exportComments.ts`|Adds types and utilities for tracking: DocxExportDiscussion/Comment/User types, buildUserNameMap, toInitials, normalizeDate, createDiscussionsForTransientComments, and injectDocxTrackingTokens (token builders and transient comment processing).| +|**OOXML & docx generation** <br> `packages/docx-io/src/lib/html-to-docx/*`, `packages/docx-io/src/lib/html-to-docx/docx-document.ts`, `packages/docx-io/src/lib/html-to-docx/comment-templates.ts`, `.../constants.ts`, `.../namespaces.ts`, `.../schemas/*`|Extends DocxDocument to accumulate comments/revisions, adds XML templates, new namespaces/content-types/relationships, styles for comments, and conditional generation of comments-related DOCX parts.| +|**Text run/token-aware XML builder** <br> `packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts`, `.../helpers/index.ts`, `.../helpers/render-document-file.ts`|Implements token-aware run generation: buildTextRunFragment and buildRunsFromTextWithTokens to render insert/delete/comment ranges and exports helper re-exports; adds guards for list/empty text handling.| +|**Export plugin surface & ZIP flow** <br> `packages/docx-io/src/lib/docx-export-plugin.tsx`, `packages/docx-io/src/lib/exportDocx.ts`, `packages/docx-io/src/lib/DocxIOPlugin.tsx`|Adds DocxTrackingExportOptions and tracking flow that injects tokens before HTML serialization; switches to ZIP-based DOCX generation via JSZip; re-exports tracking utilities/types and exposes DocxIOPlugin/DocxIOKit.| +|**Editor types & UI integration** <br> `apps/www/src/registry/components/editor/plugins/discussion-kit.tsx`, `apps/www/src/registry/ui/comment.tsx`, `apps/www/src/registry/ui/block-discussion.tsx`|Extends TDiscussion/TComment with optional authorName/authorInitials/paraId/parentParaId; updates comment rendering and ID/path resolution to use getCommentKeys/getCommentKeyId and multi-ID handling.| +|**Import/export UI wiring** <br> `apps/www/src/registry/ui/import-toolbar-button.tsx`, `apps/www/src/registry/ui/export-toolbar-button.tsx`, `apps/www/src/registry/ui/suggestion-node-docx.tsx`, `apps/www/src/registry/examples/playground-demo.tsx`|Replaces DOCX import with importDocxWithTracking and discussion/user mapping; adds discussion-aware DOCX export (tracking.discussions); introduces SuggestionLeafDocx and removes Playwright plugin from playground demo.| +|**Registry/manifest updates** <br> `apps/www/public/r/*.json`, `apps/www/public/r/docx-export-kit.json`, `apps/www/public/r/export-toolbar-button.json`, `apps/www/public/r/import-toolbar-button.json`, `apps/www/public/r/table-node.json`|Updates public manifests and registry artifacts: adds suggestion mapping in DocxExportKit, extends JSON samples/types for TDiscussion/TComment, and updates static table rendering metadata.| +|**Tests added/expanded** <br> `packages/docx-io/src/lib/__tests__/*`, `packages/docx-io/src/lib/html-to-docx/*`, `apps/www/src/registry/**/.spec.tsx`, `apps/www/src/registry/lib/*.spec.ts`|Adds large test coverage for token parsing, injectDocxTrackingTokens, OOXML outputs (comments, extended, ids), export/import roundtrips, discussion ID utilities, SuggestionLeafDocx and table static rendering.| +|**Build/deploy & misc** <br> `.gitignore`, `apps/www/next.config.ts`, `biome.jsonc`, `nixpacks.toml`, `packages/docx-io/package.json`|Adds .gitignore rules, sets Next output: 'standalone', updates biome ignores, adds nixpacks.toml start/build steps, modifies docx-io package deps/peerDeps (removes mammoth, adds workspace platejs packages).| + +## Sequence Diagram + +```mermaid +sequenceDiagram + participant User + participant EditorUI as Editor UI + participant DocxPlugin as DocxExportPlugin + participant Injector as exportTrackChanges.injectDocxTrackingTokens + participant HtmlToDocx as htmlToDocxBlob / exportDocx + participant DocxDocument as DocxDocument (OOXML) + participant JSZip as JSZip + + User->>EditorUI: Click "Export to Word" + EditorUI->>DocxPlugin: request export (value, options{tracking?}) + DocxPlugin->>Injector: inject tokens (value, discussions, users) + Injector-->>DocxPlugin: valueWithTokens + DocxPlugin->>HtmlToDocx: serialize HTML from valueWithTokens + HtmlToDocx->>DocxDocument: build document XML + tracking parts (comments, people, ids) + DocxDocument->>JSZip: add document.xml and comments parts + JSZip-->>HtmlToDocx: Blob (docx) + HtmlToDocx-->>DocxPlugin: docx Blob + DocxPlugin-->>EditorUI: provide Blob (download) + EditorUI-->>User: start download +``` + +## Estimated code review effort + +🎯 5 (Critical) | ⏱️ ~120 minutes + +## Possibly related PRs + +- arthrod/potion#83 — Directly related: deletes many of the same .claude configuration files and hook scripts touched here. +- arthrod/potion_deploy#247 — Related DOCX import/export tracking and token handling changes; strong overlap with docx-io edits. +- arthrod/potion_deploy#262 — Related modifications to DOCX export/comment tracking, types, and OOXML handling. + +## Poem + +> "I nibbled tokens, stitched each span, +> carrots of XML, in a tiny plan. +> I hopped through comments, threaded each line, +> stamped every paragraph with a rabbit sign. +> Now Word can track what editors do — hoppy and fine." + +</details> + +<!-- walkthrough_end --> + + +<!-- pre_merge_checks_walkthrough_start --> + +<details> +<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary> + +### ❌ Failed checks (1 warning) + +| Check name | Status | Explanation | Resolution | +| :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- | +| Docstring Coverage | ⚠️ Warning | Docstring coverage is 64.44% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | + +<details> +<summary>✅ Passed checks (2 passed)</summary> + +| Check name | Status | Explanation | +| :---------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Title check | ✅ Passed | The title 'Feat/docx import export suggestions comments' clearly summarizes the main feature addition: DOCX import/export functionality for suggestions and comments. | +| Description check | ✅ Passed | The description provides a checklist of completed tasks (typecheck, lint, test, brl, changeset, changelog) that are all related to the PR's changes, demonstrating alignment with the changeset. | + +</details> + +<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub> + +</details> + +<!-- pre_merge_checks_walkthrough_end --> + +<!-- finishing_touch_checkbox_start --> + +<details> +<summary>✨ Finishing Touches</summary> + +<details> +<summary>🧪 Generate unit tests (beta)</summary> + +- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests +- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Post copyable unit tests in a comment +- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `feat/docx-import-export-suggestions-comments` + +</details> + +</details> + +<!-- finishing_touch_checkbox_end --> + +<!-- tips_start --> + +--- + + + +<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub> + +<!-- tips_end --> + +<!-- internal state start --> + + +<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKPR1AGxJcAYiWoA9LT4DAAeKMzc+BS4kCShUTH22ESkiOL4GMhMrOzIkAYAco4ClFwAjOUALJAFAKo2ADJcsLi43IgcAQFE6rDYAho5Ac64sBSKAdwe1CRT2B4eAZU19Yhl6DHjirUGAMr42BQMJJACVBgMsFwAZv64QSGhYPCR0bhg8YkfDqnSGVkwDk2BhcMhAEmEMGcpFi50wVy4zG0WAKe1w1GwnX43DIuwAwhR7nR0JxIAAmAAMZIAbGAqWBKdAKQAODhVVkAViqAC0jAARaQMCjwbgArhwU62CLTEggsGQG7YS4AtAedTyQkzGj0AiQPkAeTxAA1pe8Al93grosk/ul4JlkJh6MC8pAABSvabwYlyM4XK6QDBoNgASg0MFgpzQeFg1pyMu1kDGpzV6WJV1EAGtU6TZM4sLhZDiMwxMwAaSB5ihYNWgjg3eChCtVgv/Zv5v0edvVxiwTBpEi4CtOyDYbi0WY6yOQOoASV7/ZIHnwRBJkGCDEQAXjmTy277GFIy6IGmYtFC4cK+AVR2TfF4+HW68FwtF9qw1s9Xjl1Hfz/R8AeMgADulCnA+EjwEo9DwAW05SgIiiyBoRj6MY4BQGQ9D4DcOAEMQZDKImLp1jwfCCCIYiSNIZzyEwShUKo6haDoaEmFAcCoKgmB4YQpDkFQxEsHKXBUMByROC4tEKAxKhqJo2i6GAhjoaYBhDDM2BKAEACCADiACihTQHsp60BwBgAETWQYFiQDps4EQJk4SUiUk4Quh7SPyS6DsSyYKoBpwadG2n6UZJlmRWhLMPgkGHig8rsPAhIKKC7CQMBCABrwJCQYciAePIhy4LWxJoPxsIkH2+V8EQ2BQUusE0W6TCXEghGIMghIAI4NTFeQhhWUokbEmIVSQFZ6eoAASAwoOlVBUZkkBsGMiiIBW0yYBgsGrsBfSjhghKIPgHhSPQfX/O+m3oBg9BKFIy7cHKChOPdmXRJmNzLsByAepcHhaXtlb5mAAhoOs9ACA1Hi0AEhbFpGpYBLWsQPictBHDRI6qjQ1a/lI6DcA+aAZogYZGOYlg6R4+O/g6SbXgFSgMDMgk3fwuEWjExLWtwAxqgwcSguo3qIKhkBXuQ1OQAAspg8B3OkkA+EF9lBkVABelBGI0zXZAepDmZAADUFIBGA1IUkYBl2kiQlKJAhKQSQ4kkDcNzvFwjT4MBVk2QYEBgEYIVaXMwJOluVyLusmhnhZ1mWbZNOOfxRHEg4knFbhMdeRLBgSuuSu4TFcU0QFyWpW1NCgsgHkBQ2XiQGH2mR/d0dG9Ig5RZl2WwGReX2piRX2JQF3oI664hI47AM1g9WNVafBCvcINWFq4ETJRsR5wOsRN9I4ZF2XE81xlsFs+H5mSzYJC9OkUl7zRi8Tpc3jOyQfUpbKGW6mOE4aDT03NuFgURyB133IuY8Zlwhez4ISB+uAn5gN3KCTyaQKwHTGIGfANBkBjGoJ/JBT8u6OlShgPBPBBZIEjPQSGgYXo8DJpmCaiAUJQDxF3Y89h0T3WcLQLEsFH7YGWlkJmVpFh+ziKEJA4gErwIdrdDcs90HP1upfIGTtzj+EzHtAIzB4BEA5itSh+CKxIlggBLAUgKCIHfCDNq9i0yXGQkHVW6sgyGISm1KQosHSiVEMJLCxIlCICFCKcQRMvE0TVJmU4ABtbgLCJoAF0wAJOfmARGJBUlmXdPEYMMpkAOADAwpghIwDeOiL3CpJAwDJNwFcXug4GAaEppwshg5ArNxEWIbGXAwkRLfCtBuwFrzrDEcgHEfBD6ABQCAAmjpOWjRVYTFBA7fG7pEDFiVrIEGaBPaiETMk0sbC7rOi7kmIsJAQwXPQPLZwmZgjASwEM18AIOGQCWSshU6zcCbMoPYWQoI0ChC4IUyIvTkGiNwNjSAOYQanNYWke5FSTrgNoI4q5OT/pIiENEAxsFCWNKuHckcr94SnEIbEfwAZD72FjAsZ0mRrH8AwKPXczCzmkC+Vwxc2SbljyRKLTcgyPawTFozeBq00AEooBY4lCr7mkoHtggeXxjmZ1lJgcQm4vlywWOIMAh8DFGvgA0lJpBICUvfqJBYNEZXrGSRzHxZCFq6ixXaZUPSaIzO5SiqMH1/XP2uTiCsmjgYJUhcU58sUsjIN/AlZg5qZS+uQDKtA9g9rN1sAap5Ly3kvkiX+W1JwuCYkoCasmIMVE/gBJ/b+A064RsBlG1cXsGCYnZdQgQQt7JWHnJ6MmQ4zgLC8B8KIVjl6bL2hWLFhIxCQFsQIMAXh6AzEPNgCaFZFHUHkauCgDr/qIThhWeipw+0hEzBTYcH1DHGPnjahqb8TjL1lfKjBR93EGVCEU5u6junnzroE3K6xQVRNOAAIQ9tEOYOkbhbIvfYXaJNByOg+kGCgHMiY0ChbMZAgEgaPyTauWM4lPUz1ejotAei3Wx3nSQXKDB563RHOQcSdwMQnS+XpQiVAPCrSMSYheL6qUQsiH2ZxPaR3vhUNCpjGbrSVrsRItAE5RQSPURWaQuzVSjxlfiuMZCUJFGvIqZUcnBNxkUCmFc8BhbAQYafPmfA8ZgVoAAbnZaPLNda54NtfqcJzyAlATroKZ2WtN6YAgIczacrN2asa5jI74rne39uSuIby7jpYkFlgrXaytYhq2bjpTWsgdYUD1gbL9JtzaW3KAAdg5Lbe2LlkMu29O7T23tIC+39knVCalW4R2ElHR4m4GlA16BgMyicbJ2Qck5DO9As5uRzl+guRcwt+XoAymuyJ/LwRm7BPUVGAt/j0i+yaDz4yEkjFkaiFY8PbRoGAWgwo/HPqXjK/zosErTBSMI8Ms5YiRughZKAfHnKCd4JfEUXgsTAWFAe3hsgvDnsyAwDq5Aup3o3bBOjh6PZgXfuw9xM1/C0CR46EmEwybXDiJJyGqAVp1LiFiggFApinawONUgFYdKDtuomrIijbrqsYNGWL6BEJ4BnPOHcEDYiEjuIScnXy0RHv6alEce62h7S4GW04K1MQgzRIpoXg6+QXYg3+K9pY2PBpOuPEG8Q5Eg2F/OA3FOoBoidAIseYj+AUAYlwfAIzNZvXARlXKrs/pbStacWCXsKwAGl1AzkQDux5GBt2CbqDnwXaUGz1RE1tPnt1vcVmgBcRAEuCdMzOhDOqYn37ecjGgCQ8hZTcCk2zrAvk5S8+B1gNqZejhPpHDGgDtmvkCgAuu4Bqj0QNsafjLILRpGUa7a9DeY/CjBlu971ag5YyCIKVzmpaBuDwA0AAKiGkmevEur/qBqbgG4j/n8jkj7FrBQ6HZUQJWBzYFUFcIGfP9KFU4RAVDHEMEL5fWDAYne5QkLGfhVxY3dvN9XUbxV4eAHWdcMcIWJ9X0WsVA3UYAnHBsYWZJVFQSSMPgQhBefjVUHgfAKxP3SAX9WuexImNXMnE4LEX4NIRMCguLB5A6J2OOFLDcW6ZXPICsPRMEJvb3CnfUWxfTCNVXJjGYTGUPT+WKCeSuUWVKf7NfP8cJQ5L2OGEGGVIHWbDRNtLFBKPpOFYUBKI9JHHTaAtNZDR3G9JvIUR8RANdInRxTIPxWLSLFOeyOmIiTmXUFmUQJLWXDyHmRMfmGhYWLLcWIwPLArRWYrDxMrCrKrGrPHOrLgBrBkJrZkNrcQB2dMWzT+BPOIXrGILgGaIxWAAOZOIOEbNmUKMbd6QRAIRAEFBgbJa6Q8MARAPRRYebfouI5bdOQSTORwDbFLdRNwBLEuZ8cLAhacKuU4YDWIDyUbUBMYrcSYy4GY71IgeYxYjwXuU+A5FfOUYkDGaQJTPgawjAXaQHZPJMf4DDegABUjJMacfBA9MyYWQ+cMfUAQdYCgCQeTM491CHU4U+dghuacQAHAIrAJhhDEBABcAl4UU2hKIXeXgFKBtWXCYgSlhPTWimp1rWojsWpTBKTzaEoCwEalFloOnyIGRBVlhL2nhIrEhLRwCjPEc0Og335P+i5Vn3AiBSVOrF/w+kJG+BBhOiNUQG81xOs1wgCgJIJDFhYw8ApJN3dG8Jxnl13lswhifE1KyArCIEZPYJZK9MEFVGdxgjeCSAF1uSb0rgSCFizzVMgCJOTwpMaQHhN1BywEQFjBiBe2nEPkAEwCaZEkugKfQTCw6fD6EsG9d9GUkGCUw8DQOWPkPETKMCGPcLUeFzWgWIpbBIkTSQlI4Y7slLTI9LAWPtMAvInLKAN0ShcgEMIoorf4Uo04crVUSrXWAwZAmiZ+erC2MAcoBogwO2JojrVorrN2Do+BUkAbFYwYkOdSYY8Oa4kVcYsXQqWYZYobJbNONgxMdbZwTbXYgwO+Yw47U4YshtBlGlMiOKRqHUevLUP8cZCgb6X6e5ILMqP4+WPkE0c41tK+VwogUSM6EC4tKPSeF/TAF87nF7WCp9B0/6Z8zeHzeQPkWcHwHwLaV3NE04A3A9eda8MxT+ei2YZ/GM58g3TmdVM7YAb3MHWUPQM4ZcJ3VtdwzmP7PyCgfAhKGU4kQ+W6NMpcQTcJD5UcYvW7EcQBepLSn7V9EgS8a8ZDbIkc4WU/UoGqe0PgELdALsiLSojcruLcy2KoJrRo14I8p2E8nrc8n2P2K84OUOO87SeQybMI2/e/BOFYj8lbDYtbLY38nYshIwQC8uKcU4U4haE1dWYc/tU/UCv8RQ9BWQr8X+dKehZAOso0AIEyE0TI9hSAPYXZYUxYWQYcRYVLd4Fot4FXcE6EkgFKDLMA+xIgIMDwmiStHUa8DXBiAdecGqlaSZWXZwIi8LWgHCrRI3SWU/Oq0kAAA0yJvEsxWm9zdAAG9exAJPsyAKwYlIAABfLgF6q4N6jXLgO+EdK8JQbzGJLgR+EGb6kMK69xaSvDGPNBa626izEPRG2UZ616uGDXT64/Csf/KzNkptOgF7G5CsdEoGU4X6yAGS5gOGhGwdHSNoYUGGfBLgG6hIS0dGhtb3Fm5BOkvAaQbGgG3Gj6luDQDQB8DoH6rgb3fWdIYkyPCmeGqAb3fUKPLELm74O6jGwdTW2LUWhAcWjACsKW6WiYWW2mhWuRZWjoRm9Wwde27WtGpUfW2cF242wGiWi2mW5AG2wdRW3AF2x2ragPGgTmt2+6rAb3COkgb20282qW/2uWra4O0OtWrau+DwrfSAHWnm92vmwdHOo4LIRO96s2yWy2lWtO22pWq21Wpmz25wY/fGV27mpIXmv8b3KwVutaSgRACuvG6u1OwO2cDOxusOuOgYYO+mqOzug+IunuwdPYWeuRem4eiWmJQmqPVUJPDXUdXqfqMmsNGmv6nGyu4GulXAMGkgCG4/KGwWw8bzF1dgJ+zwogbzHJD+vabzImzIVUAAfi4EQkIswGNK/hPtoBAbOHwHAYwG81hqzpnoEGDoXt1u7oetXvXvSC3qrsj1xFpv+pNqvsgBBrEDvv/pxAwFgbAa8AgZ+unqDrkWRpVwwcLpjvTrkXwax0WFvyhkgAAF4FQAzbscxoAhURGAByF1Nuwe6R8mnEOulhhulW5hieu2xus+zmnJbhtR2WkRkhn2jAa+0G2zbzJgfhjoOgOh+BhhxBhFORSRnEWB6Gl+s+tx5+r+n6lBnBtBjepG1O9w+FAGMqehRCKQclHUpcFyKxfkn0m5AuWcLAdTLnd8LaHIxKWUZAOJalaceJgmQTc+UIWIN0b3PEVleIXAZ/AKHMQVZRpEEmEGN0CRm5aAfAWcWgbU4q5dZwYeQjRaaPBwAQS65AVaiRDaoFAKU/Pa8Spsts0il1C43CXbWUk4sw8CLJ6qu3Sw3ao4G4MmLiiYZgaa31UzK8PWlUQTI9UWNgBFezYWEqexJ2fEkC3Zp9MZlAR0I5MQYkM7PBJg9NaSMYVALFT2YcQjWIU6LwVs2UcuY4rZxyraz4y7FaRLVu/aj6ZMWaqZz+yASMDwGZCnKmTsmLJI+LIivs5LDIxeocrJscguKAQqi6Dhru5e7B2cXhwMY/NO4x02sxyhixnltgX+hKZBhaX1fOq4xK+QsAFKsyLOllugNlperhzG5gblne7Ea5km6BpR27Km7Ac+umvDOGyVhlK6mVkILcOVhVs8JV+F1l/O6Oj2gWtm4Woe/lyu5Omu62+W1RkOqeq6i19WK1+KuYWVm1+Vu/RV9xZVk2Au9l9Vg2rW7lv27R8eye9RkNs7S161kBO12Nh1+Np1lVl1xeq5lez2xu9NlOzNgNzRgx1W0N5ucNzSBKm1pKmN1K2gR1oCxN114u2ceOutv1gOxt7Nh23NrAfNiNpK7t+1vt0tgd1Vqtzl0u6sL1y+kejN2urNrRnN1t04dtkYhdot3t/toqtdrB2O52/utS7dsWn10eht/RoNo9vNsNgt216Npdq951pNtVj2tegJ9ITe71ke7VgBzWA+9gPVn+Y6s+vlndsgQV2+4VyG3hT+1+g60idxnxn+7Dv+nVqzOxhByB0mmB0B+x/wRxiVr9ttn9xd4t5d5lstwdyt298O3B3AblwhrAYh1D0x8hm+qh7EMgcjhxpB81xjk95ji9uN9j1ditzBjlu9ptvjyDiWqxmYGx+gERw5oCcR5xqRyAWRh99uxRs+isA95t2T2d79+dqNqbf9ld69/OvR+uj9wxyAbTkTihjD8GhQaxqGKTuj7zNp1xsVwjm5LxnD3x496V5zrtxTkt9zieSqsAvRjF/smVGZwdZICgQ5t9N0Gyk8CsK6+mq6u9eQQp6PXFT+dXMgAw30AKLL3IxeimJZ0YOQ3ydZkqul+gHZvfNF1Mg5o50zdcw2RcfysAQK1rfc9rR2HEoeU8j2SKyAHoogPooba8uKjtyNrt7aLIAISkGkOkcoBkAAZnKueFsROA8CBEZz2iBEfDBGm0wDfMW1Tkypch/PclznyoMAFCOu+buzAQezIH4PAk3SBNXAsKTE8GJEsjVnCAADVKBHvIA8QXuEooAAASDkCkU2eWVlWAUeTQ5QUgSyZ8Qyukj4xUEamjTMKddBE7iRBsDHrHpcaXAGnxd7mQeQB9V1VcBS1UU4A8Gnewk5yAK6KSORge1TXURM8GSGYkCYPASUiMVbgdqVw7ZqNbY5TmUZWxBPdiwQb8XhWYOUCsTH44Xn0oFWXgWTYQi3qIQRmFrXzIJvPzQcbQZfGgK4Xaa04mUmAMCHM6qAX9ZJe6EGcJQhtTEa8XrwD7JjEJdBHS1tZBRQUREGPEQoZ2EqaQYItUdgeYpeEX1jLXBARDEGFP45lgMeZwK4PuqgZgSQxMseIgOUKa94rwpUJouza0oioPz5dxOoccKEtmb0dBLXtUbLf6StX2EfwAsYFfiXu5KgggbgYykGeX+QRXx9r5O+baHHLwt2VHGiKXdAn+MQQZqWapjQIQbITIMvJvRquUEGa7ikcoT+BdY5OhTOj0AGgjQbggSDXgJQC+8xfqmAU174J308he5LGDYBN5Yo4LfZAlEKBP8X+peIxOgAYAVI8KRUL5JP0AT19+w26a1J9kjyFo0CsTRMHODYauhdQmoYqFgFV6uliQQYSCKKXH7q1aAQgTEGjhQHgQJoBLJ0LWFXCsCy2zfY4LADb7BgpqSgQ5kagkQGRDwqYWAGvwHhZpnUmLU4AXwJYsAxBvKBGoQKYx8I30I4asquDLI5hbowoBYndGXLOIm8ghDXMIVMwpNGUfWE0ilizQpo6YFqQ+DpnujZJCAWEHgJunfRV80cWabAaU2f505uAuZB5iPwkH3QpBfyJvgf2YTt9H2EiTger3oDwC50C0XCpESdjaY8BdgshE3mqbQ9qIoJR4lZSpQdkaYXZZLMkQSypFMWJvbmEN0MIdcRY4gRfpLFnJKx5ypWRcuUVXLTdqiZsbcj/3KDBVmiLKMKmtwip9ZLye3WKreUO5TATmooZIZkAWzJwMq6xf7jlUB5bYfIYPAKAACk9g+oQvhPiMRT4wK6sIhFcQfCRBNAL/FaBBRUGG8HkQQ41OkHEG5RskkMTMAqGQojgSmHwKXtkL+GigeqJ8DjmlFrjg4XCKrdxKUHgQkAA8XRMEYHm5zyAGwdiD4AQDOjS4RqboDpsEAADqV/O5FLjTCy1dQtiPZBDzj4AgtooROkqPAWIigJElCdSuwQQrlh7kZAeBG+nRTHJjK4gpWPYD0RoZ2yBI2DISEqYwFI6DyHkcKQbT2DWGEFB8I+g748Aq0BAYIL2CzB0UhUZZW6GjB0xNJ2kSlGFFMkmZKgzgLCYxIcBdwhBfiJfFCjdjQpqYJAnBCEkCVrjQQ3ohieUNaBhhvV9SSobgocnxi6jTkpIQIeSOiDyBRRiwMAJqHwDqZIKmMeFB7w+BIiY8smH3v+AD5Ioq0hYm5kuFLF4VKSstGwdOGPo/xXoMg5cGWP0wcEcR8AfTBqF8jol6qLxElkXBrgTBBMP0aRPHgGajw5R0QTGFwFXizAYAigfAKyPUDUpYRt0PKKqG3RAIZkEQ20W1HSYOgtolARRGVXNEnQpq8YCdFYTXwhjbmZ9J0ajGnQMJb8XoFjH2lMoxNBx9CEai2J+ZIYqmpTB5l1GPjThzinlU6LKn4TflBaS6dTEwRa7UpKWn0RCkuOAgnV20kFBAPJBN7j5KAbKduJfiVC05UsUZUqBOKuhph2ykAKngJi7BnNQ0LmKeCE3QIvZ7GLwDAJGJYwNoRwhohzE+gfAEArGzOUoLQDjFnZ2uxw50hgEnwiYOh8RclozB6FUs0iAw0arzGwj3gGWoscYbll3DGARUUw9IAAH1D4hgQoK8IMgGBgABiYov8EclBQfKM3LyHNw5CLcDyIVFbm0W6xnk+scsOgPAEcAxUhihw2issBjbcA0+C1DAB9l2bwlzhqxT8s5G/I3C/ywPBNjxLf4fCRMTjcgJAEsiqgnMsgRADpBJhFQuAMKEgLTxuCy8riSUq7oBLSlGIMpFhKUnEDVDeIoSEZICVnlqloB6pKUkUYODhI69i4nsXwX9EYoPI1m74b+tOAcDYYAxHYmsbBCxTiSK4CAECAwgCgwsziVTdBCpKQl+V5K16Wyp5U3yExTcsyfTBDFLBnBqo3eNyuDysQTAsYBhW6SBRLjaTosiRPSfhNy40tBhaWUyXNVyIWT8i7iSYSURmEaxlyFRNcrVk3I1Flhaw0Kqt3aIbcopMUuKXsISlnskpZIGYlcHmLohSw2U9Kr9yuEFTs4eVWOAVSxF6Cc0V0jSeVKfQACqIRMDqU3y6nHozudM2AAzJYTZSzmZUTynVIalNTZALUo9MFgYRrMIsEYVAE1FGkIDxpTEkWPJhBgeQeIU0maYBNHguVfp0QbzJgEP5dYCo301ytaCIDlxqwPoeQKCwzQzB9ohwOGLgnEhsCe0Kg6MHTB6qXNAWQKY8GAWtCUAJgfAFESDA8roDQC3lKmFAEuaDl4YIwmGbLnTkNhM5CwvGUsMtjXdCZYU8KpFNJG7DA4+wiWUjgCC3cHwO8WWXCnYTykcplwr8psQ5keR/yJUxuOrCbnSAW5DSbeMcg7mYghpWUBzAPAN7kB6E1vPVJBXbmQjO5HBIWPIEGmLTkJII5eQ8h+gVQlZ00lWdMDVkv5jW9yWkopLOZtzjkaQrtNhgyibzu0boHwAUOlF6g8obYl6BlClzxoxgNswkLRmxSxxn8czaVNaCyhEJVJ+AHeL4OZTS49cTpd0KfgFDpSKwmFYaHznsjyDDxuuW7LblG7256xVgB8dEBFQnAKwKTShOiQBDP44FsQKguqPfRPQQg6od0MAop7yAWeEC/OBWH0JwiPIn0zME5jDy1jxAagBfkNQRT3wyYBYscN8ArCQjZF6oHpmc0ej/zXogEhnAGDdCdpu0K0WCAwpLL0BcFq0cuNWmd4m1HwkeWAPIt4VwsGGT4BgLIDZi3ZgQSodUB9i+xkAwwUsczOp3YL2UEEg/V4HZl6Cdc5EPoEgLIEyAPQPmAILaZiUXDDSCCS1E6WCz6E4ZwIZ0BzLvKylUxViXQ2XPpOfDUt0icMsagjJGGMtJYhROIoVjsklZ1YS5bWPMNxl+V8ZFcm2Et0PLVythtci8tFUpk3kx50cVBJNWZnvlWZfc7KgPKB5cyAKWI0wuIHMIpL3wExYsFKzvkfFAuzA9BC3wQBj94UxiwRuzlQQ54QJw4CwV1GFFXSJqShCoadTcLG9GYD1B5fYg0WFhIWXCN6hWCzGPgpUVdPkNQCzTusha5iX+Q2F2gAEOJZvbrMNDOiyBYoFAfvEgGYDAq4M1gTwjjmKQVh46TeFxkfEWnayWU6UG6XiPoAIAdua6P+YJlQoGxeSm+W6E73RhLQ9UIY5DHARFAIFAyMAG5HsGGSsKhUapcZoI11BEZhBGxOnv1PuShkUsTAsZsNUyD7RDo+eNgJr2QCSrZRkZTANQHzH5CKoVAfvFHOvCCBUS6JECYjJRbqJNgKeKFN6Gwh3hqScEXXnFDxJmlTpqLchakxiBKwR04M8pRS20V5LYZxkrImZORaNKrJMsHGVUTLm1FygP/PciFPWEyRiZEU0mXXPGUNyqZ95JKWArEBzKfu9kPKatlci5VB5wPIuKVR2qOdm4Pw+diWpvrwlMoWs/rjrMxF69kJLXMBJDEEb3ZqoTQomEcvSAY4DkH0U5UQuWpWUmoeOEiR2OgXiIVoRy2xPYmuWRBfwfy+QGTAmBdQA05yXGCTFujolhQLso5e6X+iVN0opTCsAZF+aqE9QkK1WL9BwWUBqI9AeOlAr7C0AGAWAVVTMoygm5a8AfA6B9AkA1A7i6IcIGYhDGAI0A8xZcLEDMqQr5WrNGFbAULCoj90ypCsEcqzFBQ+Ayq2iu6Ej7Jp4WhBGSYUpxyOp+YyeZABIB6qAStwwETjc/j2BMZ+hCUAyNEQzSy9n1dwe/k+pfU8FBNjGAJWtk/FtD34WOVgCtFvW+8hMj6AEAEH8FBZEJXFdWEvO4GzxdV2amRP+hohKB40JGNHFTSgj3JYI1m+gCpqqVIA/wyCEgIxr4AMQf1q804Fuu7QbdABDQ0puOqIr083wRMNdfXCwDkYJEAk55bHhbTGD8AQRe5EwNvU6b+AKJceBiTPJKjUAAUfwebIRTQhvF7AATIQQ14OpJsq+J9IfAhp2VWiccp5hEruaXofptUf6bur+b0Alw6wLKGBArDs1tFuzYkCyqqJQE+87MPMlBQ7F2kZURy5VYiLASkaPUygL0QdVwQPMvI94V3KCFDW6TxElSguUZNznDDzJYwlGVADRnTCOlcw6rEmt8qzc+lO5a7mSCqBVyWimwkmZ0TGWDZC1kyttZLNsHlqLhCy/Kf3O2J1rVlRcBlFMoRizETw8pLtXqs2ajxKVoOLlUPAKijx9NK80dY9hh6PJEKdAoLPwDwC1h14DFWwcwj5JbtMoh0KDEqBXXx9BwY4LHKlEc1kBII6yV6O8PLzzwLE16ZUdaks33xxYRGm+olvLK2DK8Y+WUdfmYLw7qd7KpvEoBhipBU5X0IiZapkSiA8A2W8Jc7EiX3NGtSOweOBlxHQkwWuzNJV+iMIIszmja7ZdcupXoaPo6gZAMmVKVktIZB26GZGpqXRr6Wca5GeOWCWJq7IrS9GTdqxndLk1vS8uVbGZBkh3tGwnNet2+1RVftAxRuQDubkPclwxYm+uDH+CTzXe488KMZFMhpV5llav7uzIh0rL843MvXhsp/j2RDIle/JE2t9RYJ+4M6SgNjtZRHZcdkPMdU9ikAWJzU8xT5VgEC4BBEhAImQCXpd5URhCi6pMAHzgz7ZrQDkdAJVCmoy4WALkaUVrvS0MpsS9CVJiiUTRLo3mi64cEVooDWp0QdqweddPlBS5mQH6OMLMA9nCgWoBkEabBChLMjZgxXfTLdAZ1ZDTgewcrRxLfCGItY/O3quPCrSwGnYlC4rtQqpTAqy+oIMAOgdOAQr0QqsVpAgEPDi7C990IFIbQIJIGBRonag/i0wN7oFNkAR4d3jQCiqjKLB7A2wZ0i0ApxBhPujTqyDRN6A2qygC7KUBL5Niq6CjWVw0AVdnw6ufFmgCczu7GMNDJQK4jV6CM5GiwJqPQYyb4qWAHULQCNVDLErMQNDJ8CLt6AhibVQKOseIkmRHB1QRGvYA2RlpjxhQqoWjQwcC5DBGcpwKw+gEQyUA3Q3G5kTYGEUhBVQaIaIBNACBbqHEFBuINEScYuIgURNRA8gbYCxQAjf4FfTytuiVo685FMFRWCcWfYn09hsXfckvUDNjNkqoJXOPf2QVIIcYxoePqukyFTNzhRUUulY3hghj6MAjVu3vFYHJR78aUCOmdikYhVHg3CT1VB6DgvcHeyKGeEyUGycYI1a1VlrtXgaTNMBJY6Tk8Ebkh9oI26TSRKWktOh+2nsr0OqXHahhDlTLCHqZZh78sLSrySrAxmdKVyd20ufHtqJkgms5QAAJwp7jNNcvNaSGilYoKZf2g7tTMln56nupaj4JytL2r7x5ewdPBPUaDfcQdtetmeDtrWN60gzeoqg7s2aQB7e2PI5TBhVjEky9JSF4v6r2bNqYDhJxoMSbPDpbKV2IuPM7JHi7yJUR8rNB1zUWejlq9ATMJQmAjrprUowYNUugOUJQs0eO4LaJxHTz6cB0yKhTMbfRBYmdh4CyO4minohkNKFNgkWWIoNo79LYr5MyMexqZVZi68MW6HCRkB+mj4d9CjjFjpHwq5QtXCOmiAgw59C+5Idmo6TkMHU0uGgH/vFjSR4ckZwsHTpwRZoX9zcJpnfkBxXrUcqZ3UMdJTMhjh0YmlVGrkbDfooAAARQahfTljsxiLVkbRzUFQCwsCjUqE2p0pF5v+k1YoeUOAGslIBsA0ZyAgVgoDNOGA3AdoN5GGDPGzihQAINLxeDJp27HiDwMfBCDb6kg34CaTkGiAlBvFvAaaJFH6xd8Jg+vGNM0LbsHB9EtwciTWA7zOB+yIIapT0ARD7K+MykyxRdGC8hu5uKVtTOTlj8U1XKFz2kB3JM0YibLZ7vVqxkZoO+a8EXhIAUk11jE5ECDGi0yCyxDpYFt2OpRFhpJxTd/fXG5iM5gLum5uGV0NVLUTDl8aIIugeBjGTjRKkU6U3jM6RYyPgcdDjyW3L5SFq+Cki2dwLXgK9WxzUUhdqOIZsOsKC5daCzQUbhZFIg1aZqlbiXv0ZmddtHmQyJkTxu05OZIPKHWhbmQ/bEQuPhHSJQI7OnbRbpSJg9mifhgypfBTyxAPKAsUOU2r20+6njBk/oV8tqUmTTtwe87aHuaUgnHtCe8oGSGCnLcPtae7Yfmqz3DZ/tiU9Ezz0xNF6cTJRhzOPKSnqYhDdAT4NETAAoiq0auLuTfBr1rFFlNa24f+R2wHFtZiLKVrDoxOF6R0xe9ILidKOabJZxVr82VfL6VXVz1Vt4k61rRO64INJYtE7yV3Kl306QSMwlCOV/z0E41h/tVbUwA0/51vO1SM1C0xFFpizZCRfuDzpFUmApS4CxaVHulqSuheYgMHp6LWVof8qSHiwf3sXHr5uLU01wkSxgg5AUca0qqn5AIs8K0MGzUNxipMuyjFomP9dXCVoRNxyATRlEzSHXm4OYfjOjs6PQVhVOIZ85pmQz6rZVlm93NRf0Wliyks63BEAggqo2X1GN9BDhHVxTwvFmAIFKfhlWiSkt+TFMARliDjW/L/ZQ7f7tePwywrHxiK18ZzlDd5gyLI7YzDsunAi5rqqbj0piu1EgpMJzrCMvhOZ74pGVtE3nuyvdWxAvVydNyoKtbgirn59+LQDACVo10wtkk7lLr0Ummr9a/YstLat0nNl7zMhdyalatrMr5th3jlZ6t5XbbwhQa83OGtO2Xb6wN27CTMjpbzrHRy6zmeR5G8Q8KgEqHv00rrA0bYgVmwfGtDqKAMH0lhE1zYwLHrmIWsVSYZsHjIWjfhJHFwB4jMXX5S6R6xBUqS3M9VL60LDfM9Tp9qDriLbPcm1P3X+7ExotBZsTRo5mbom3ABXYjFRjreAGZcDtOv4m1Tg8SJjLWkns6GGNyAfAkiFeJe6Hj/liRL2UMnBXA99Ss7WLFD1Xb/j0erpcCe1sBSnt13aE4MtClJXwp6ezboidinMATbqJ4tVlajuW3sTy+uO4VaGt3FpiKVYscXxqs9zQd1agHkVKh1Zl1YizDyn5lmvMJqBC13C6hcHiZGnM+MScwuGESStT88AnGB9CcNuYpk+Nw+eVCut/gu8m1ClDgVNypl0QQahKIdLPsZRCG/ZV4GwCxSzBR4ZXb7KqP4rqHtA2oZhYdEQjZnbrv1xe9sigbJR2Cmj93eIYh593xjoh7ZBI7Rx/COoeq5wL5g+jI3TDhidYBYY8DP4gcU8RDZ/BIgdjSgNO+ViNRlRvj4gz4bQy1zoh9gQc+87O7SqJvEixVHdgYygEWByqxp04W9fQOa5O3f5MT3Q1BhKHWBW6hhtUMYcyDhhaYppS3aFgofISpM303EGjtvs6T77Etl48/ZO3vHRynxiYb8bnJf2yiMe3+3Hp1vLCM1iV1PWA5Ss/aYHBws2+PK6tYnrb/Vu2wnfHmQwpiafdXPKw0PxxarFa+q2DqWUN67hhcXoWDwZSLNyHIdmiomZiT0BLIAodXPZCOczhRYgmbAXQDoC0955AYJgLYlCTnG1DXzuR90OvBwhLgkYECDhOmqyBpGeufpOOOMo6ywcdPCJPfPy3zwJe7FbuM1QqHz3XdLKax+k9OOZPiMK9kGIETpdta3KH/ZJzxH8AUAiohe3OqR0XN/g1SmdxMx5TLORmdYLVLbrOD0gzQ5jS6KXOiCICOh0Hw4I51jn5HE1uXl5qulAW2igizR73cBO7gSC6nlMgJUQL8VyrmP5QLGVJpGJs30vNK+eEda0UMti3oXEanp+upCsxr7V8at0ArfhlK3+0Kt8RJuXaSf32lYzn+35MWG1FmQAyzNUTPmejLjbEy2B9pCSlrPcryD9k9s63C7P7iSgYpxfY9u9zznjVwh03rWUt6NmQdrkzVvVianVwAoAt7IDAClPBGigyp+q/S3ISVxWOp2dNpG2iPYhYKOg0igqewtsVkrKF7LilzLMxxDpgt6mfccBRhx3HMWacxCf4wwn3EqjauF7usXl0r/Cl8qslV3pnpCTSDGHwMVqo+gRd6plQGicZ8L7qm7S5VMzD42So4TJa7Tr62pRpHxT1hceOQVBzIRSQBhGy98zTIhRdqkcGmT9jEg8LEyOU/CjagvzD6CoMgy1BUzKv3+kFQ+Fv3Wo+izsBh2FjoOM38q0M8oczQ6FpfMkCm1KuMfOLpFETZRxlk1ISFgImP2oNZDpxDPFt+63XlFl+zLYGdy3JYk5OymQhnLDO2lC5TGRG/u3+TjYT2jkKsOAdZqDbX2zbvXOz1FrU38Dx7og42f5X47RV9B5ai4lLgi3eDrKqW85nlvodJDrEdxEJ3PI/YbyCh3fhNdcmqy04StClmVqeObKK7pjRZ6qcgw/3GfdDeg4k7dl8bPb8U9iOH0PIhHQKIBf71tNugxhmOKV6OllfddcYAjj64aslS0f+ZJwUUOadXA927rFL9YFdFFjsEYyBXwx6F44vNx3HAX8w8ONYE+i7gR59C8qj6+t9BeTeIb7AF1G980oqH9gEVDZK519S0gQ0qRQtnYZppdPJD+gRyGnNOvXj/TEKf668ugD2S44qgDtIjh3d1L7Jxe4CIKUb0zripfx6fvuuhP/TpGaJ9RnSeo94boE5G5TXbkyQMzoZaA7hMZ75Y5M6B8m+WdwPI7Bn9Z7Hazeme9nDgYAlkHqTy5+E/+nByzLJMNWCHdn6kxW9pMswKH4FPsLEDvmZwV7ou4WMj9sOXo9p/TdzXqY1Pvqxv2vHbD2qpU4jsLoIhDxi96o2Hoe9P3s4z8kLd5t7tr6QdOBRyASgUc4Sr1Djpo1f93j1n9yBVZ9kG6Xt39q6VXoI2V3ElTCl6r8Ohr0UfqdmXxqOOXygYPvG+VchpNSa+BeLyuuF8l4+I2ow9OWmwPDTL9MAb9iTQer5IOOOnwh60IqtFTRz5nfiBdxKKr9NXqEXuIfiv/CfDtdF7XyCo0oDADs2l9uAUCLiEOZpg+AkqcQOwWST/jg0eCUx4JhmCJKFcaZJWNH6xc47w/wQ/wq0X1VuhCRcGEYBEYoCWOs09KmWV4CejFxwkmIbdR+Fwh9aCw14a2T7Nydp+ePYaqGa66e+Ce+nsa2W+/flu7gnpHXc0EMMDcgQmyGt0+mk1PrmWOOpmCPX8bDezDxnv30E9uUhNAO43wyzT2TKRMQ+UTUPvTzD4L1w+mbnib22ksjDDQGavK/JPcnoO8BY+dVlWo2eePpDrluaxomCt6Wyo85fCzcMCILWJ0qcCWQOkNa70ApTpAGyes4MGRggtPA6SZ2WIgOrFeR8llD8KzgJqDmgoIC4ANInBDdLkB5CJLzOAzAEzwSIOis9B6KH0MmJByMyKwa0KDYoBA+eRCK3iagwLJa6fwnwJWzrQ40B3ApY6Asejno0YP76rglIBSCAAyAQxuFIJaLQBSQEwDpAU1IVBwefAKIGCI+NiC4eYfcD7Lse8xH2CoEOELXDS4gJFQgJiRiDuIBQSAA4C3YEEITYoehIEAj6qHkHu5KiRigJZqgcIFJBmBYIJY7sWboGpamgMQIJ4TMmgUjh3IYRqIT/AxIDGa4CaahoAcgpPLkbquy6GOK1C2MJag8o9SMkFCquNoTiJBqZsCCZAvmC+qYu6GkBDrU5xrhKX6MiC9KCYapl4rICvRGqA7cWRJlpok2WuIF8Gb6KUDkADYPKBruxcPu7NB93uGqP2QVs94b+XroM4JqPxtFb/2CehyCv+szrCaG2oPpA7ImOnqbbQ+qzhbaABfVsZ6oOzcmAGzmb3Cq6AMrxNXqnO8AdcLLKVzsPJVubegrBE6bnnoYVazcIjz1usZPeq3i0eHLC58zcL7DqYkpBSQOezcPF6FQEpvCr8OM0OK4zQLwJ1qq4iZjKjgSFAk/qB+WaNaC5BNEF0HyA0/sfL3A8KNxAiyk4JV53QbXqbi4QMwFrDNuNIVqa7QDsH+AdSEFrPZYAewHsA2AhepvD0A9UEHiZeNyB5BQaLyJAAgAQjDIy9mkpnQDSM+HnQ4ZQ3wdkJyk6IWI5nMXDuloHG8wXarnEcyFl63YlZrEBrQ6mJCovYFUMoiOmzQmuq+EmlhejuC+Ll4BzILTqUBtOnPmyQOAwQlI6eqdulTQpY+Wg6hpCWGPkpY4MhPASDggWidzGqUkIho7BK/nsH9ktLNLaveowtv5NK14LnL+uIntwpH+jZKlCn+mop97Xa33tjJnBSngnrXcqnm/7A+twRA7g+Szp1avBGbu8EoOIAV8G9mqfCoLWiCAGHjmehYFZ44+JbogFUmOWC1bLSfEmcxQhrnq8gdWuei8EIObwTbYI+oAdOH1Is4auagsC4cszIQZ4DphHeUJHaTos4LiDBTkQIMWah8N4c7Z3hTjO0E0QZ2AkKGmAQIya88RykiLUB/ah0Z8OK8oRaqUqhjOrLkeqFuA0A34IOBSQCQVQCY+W9ja63euFrIC1GDBju58hMQUugiUZPg8zqYyAICYoR4BFcDrIvbrPYKAFLjGTuOtACCjBgYBMkFZmA8HKF2AXqBiRrUVEfQi9+BLIRFaSwSsZqOqCcqEC66uZtEpgE62gQCiIdCP/Qco8/sHbVamASnihYnPoWG+6q/vsHr+bxpv51hlkpdqthozvf7yenYeWy1EVQFcFA+cziD5aeBao8EpucwGm6jhMdkAEDWSUmaGXhnEYYjTE2wYCGkmZzvg6FS+PhuE3Oe2FpbrKCWBQ6laB6rEAjhx4WOGnhwAdm4BAgUR9jBRDmOSHfANVr3oLy2LmzT8O8EdaApyCUF3g94VviBCHQHEV4jcRXAXF5imBISoagi7oRl7OhEaBSH9R2Ylgot29Ynl6WOsHsBAsY0qu3Zk2ndtIDd2BjqS5fuc1rCAXhrVIAxMABkPLq8RZzJYjj4/QCgSqahjrY7uO5AKUxBABUcLC6gQoSKHtiZPEGCbR20VLj8Ro/kJFQRtJshKOBm3i+FYAbIddFoAwoWuh3RI4Hz6f8f8AliXRmQawpWqCBnAbQA0APOAjgjQHiBWABkQFZVKa/gOSmRRwe97Zy1kgp5RuywglbORNwR/6pWw4YeFbg6br5HjhZ4VOHgBuUOBLLhkUQgHRRSAQT4oB/DruF0ChFojwvOY6AzEli6mPkjYBb1vw6jaXFPzBCxHYnVH8Ka0b6JXK/OKiQrau2mdY0BSTrhT8OfUTl5n4Npp6H3IufvVpOw5Nlk6U2hZm2JYhCUNDaeyAQJcoZGroItp/B0eIzHtiqcu6ZZoXGAuonyu7sjpvRprChLXgOdgzaDcWrvDxPWZzDkgahh0tIg6heodQZEhtANIx2iX0vHiugL0WaEzqpQgWRr6XDgLGzm2aDrBPSBurexhK7qD14taDzDEpOqZuuwDqRqOhQ4eUbZGjEP2zxpjGlhdSsJ5velYR943+Iznf5yeP3gTF/elsMTz62x5AOE7C7kelaeROUdTFW28PtlFJSM/OXybWHwLjYCQsAUCFe2FzpSZXOnMb0yO6GAbVQdGMqG84FkQEjuJ6Q3pIJib2wdPxjsItPH2q0mWFhT4ryA9hMh9gqUH6QQwgmKvHtm98QQJHql9hH5xaKNAtCQi5OMXYo26wHsAxGoHCdbvgDgayhUAbEqRHK+SojTbUWavnEDUWl1OAlWCq3EgiD0iUPXD7h68ZQCkec9hS6YJAYEzbrAU8EyGMqI/grC78yREtBwi1pB4DiKRpnwDxIB6vTa6BzcN/HsE5CXwAioE0PQCQQWaJWiwJNgPAkjRc2Ik6sRc0StIa8ouoX6OItdk7iSsAUCwk9osYElrWKg/OegOMPnrKCjgu/GbgYAsUMYnMRgiacDCJ1fnEoCQ4cV6iqoFcLP4jUiCHEqbenCdwn42l1jGTIYWNpWjp4CSohACIewOmRNICuAYlwiBXsrQHYoGughUeCaKLwt+4gGmhxJJSJ/FmyVUqIlFxrRI6rraYGBfDxh4LDcB1xmkTW7GiHRo3FX+S/o8YtxgViWEeuQelv4WRBQFZH9xgJh2F/2XYbURNYb2mp7xurkZPFpW+3L/5eR+ngAGZRmziZ6Syy8fgYp8q1lQCkA8xBmBIgzMcCH16u8UPKJRJVPSbd6pPvNZixU8I9YyoqRpkCZxQmOKGEE9fPEYeAiRmsn0h70WfAdGX0fiJIWdpFhYrQaTB8RXJHAjWbhA10Y8nPJ4gnwm6U3OHkmjwV9uwTFycMEKo1Rq4OEiRgSIGpqxeP6HNGCeZEbEAjGLEfu5hGO7hZC1AUAKyKASDydaTgp1qFO6cwPpoOD00PQAylmsOiWwHiStCdeCVWJLotQf4RwpIA7i6AiGJ9QeCFmhBBxrLdDWggkSBKyaSRmYKkp8sMJg1ayFBsESAf/NyJkg9pOyQJQqqVRTkUiiFWTXgEgBqmxa7gtNbapf/HwmUw8qc8kwpLEtAyBQS4JfgbBucRigOgLUFfb3JbhNzihIkKvGYGQLONJi7UmyVmh5QZ0N7w2ItQYClhm2cZT6ypsBOVq+8hAVSgpY4GPYgXun4EMzsEyGl8gwYqwRd6tBXAICmZwwafOjvqHqdU4augYmPx/gFUGKTQs0KcyQC2p3IOizgWtpM7nBgyb2HXBGnrmqg+2ntPFTJs8T5HzxfkVs5LxO5paiPKUgKNbLJziYPTbJ28bZ7sxsUaDL+2Pau1ZuQe4e55HxK0Ayjh2KzlTHDpSDrTGLxiyROnJIU6fUirx4RNkZ2IU1sYQgwP0SbIjkwJJen/xG8StYhEiwE2LTG95m8ktELuuVF0k5UEpIgwL1Bekw86sjfLfUD9ocB029AH1q88f8aIm6U5GP8zoJwxj1TsWx7tilN4rQfYAnAWGPaDoUf0deB+eyloQHMIb6ShmFJrzM1qWWJusUn2WRLrgBVJNSfBTOYDSVnLfGbaQ9odp25OyBjxn2r2luREyTnoR2R4bD5zJHwZOHjySyT8Ao4afFjDcAC6eSY7xPtkQ60WwUJTEpGR6UZ4ThOUfJnzEimdBBjg+SB5Ro6/sfiGjwoQXGLd6r8GYn8+HkLInvoh+hKHCwpmRfENoZ2BBHJJr6iRFRBuELinLog9N2jYZJlNUamWUjqwB66dqraZS4laGQEpoCkbIlN47jjIkxGhqKHaGYeuqdbtGgGU4hxK2ELhCbyYBHypZhtKNinMRL7hQQSIsiWkK+WUkc+LBg7mZXHxyZkU5SFcDYWrY1xNKmcyIkjSV06PexkVjFlhHWRWGWSvrh3Hlh3WSf6KAGcgjJNxobrJ69JsenxkDJ25ByDUgQmclaJu/WFPGTJ6UVJk0xWUf5GSyL/GrxHmmUswBAgCEuFGe2amUunrhBcPvH68VxlKYuePMYmbOhrzgQHb2jQNNJF2cACgnHmgLodBywBkMxR1AcsDl5Y4PIglB2kuoOL42ahTAwzxQKKXhrBYV/FNQ1+RdmAqCIA2tQD88+0NjmGE+OWUa6BOPHKGMAMwF1AhiI4BtbYp7Uf25zOOeFS5nYZKiTYPARyhTY0edgktH4pwWXk4jg2lt1pBaRTJ/CnQRwF4KicxhI15Phhsn6rd6hFrjAjUF3pKp1axmrbLta3ED0EthUWMv6GRxYVGqHBDSscETkU5LcjLZAJrdqP+Uzo1gUgQVCMnv+ImeMkUxEmYekZRJ2fMmfB48hdksYGYCajqchejGGIE92cW5RRoIf+SvZxPjuk8mriTgHOgePCTg4gLkNxycJD/I+ZcGYqgEBBGasdBGAZXwDMCgi7joEKWhTCbzx6JpZiEaS5S3jKi6kRIPtjqcC0ALCUe7hhfxMGpHpKqWmUACkzHRm+CtILeGBG/DOkkQLll/gPpqdg3AdEPpjoZLzOra348Zob4q+RjqXnWKWMKnzD+vPAHnTgk+cDjT5uPBmD9+fPHQiZQ5qv6itQIRrQB7AU+bIDGh8YMLQ4JAYL4pXQLeQrgsh7KJIHJgWAISCVoXyL+iaWWNvYlgARrFxTN5f1pTll5G+fUhb5xTDXlNG2Wh6CIAvsH8C0AKTIfmRg7IodAexzeUgAoFxsCkxRG4cVEACwm8O1Y75VoVSIqwnCcxHccmQDpAxg/KF5BEFpZgwzMESEiEa8Yg7sn5RgjyLvzie6GpLp3IqEjvka6iFA/zz82/jpiZG21k3gfheCReiu+TXEIQpgROGphD44udHjjBXgKZi4hXqomH6ywBorm4aHWMnnLRt0ISz+oYMQGpnGQTsSDKqMhgHwksA6XjEywtQOUyG5nfDkQH+0tkf5cAlCNmiLU3GNfxNkm5IYTn+7ZFJ52Rc3DG47ZCbkbZg+X/u7kHpumV7kjpJ6WdnNy/uSEaTycjoWDysPyqpm4+bMc9k0mJhElFx5UrKLE4uwFN9IZFBVjRbVSGBZKBW01EvIA6QPypKy+wKtBoCPxcUUJCF5dAemBmFHkBRAPWrRTEDyAy4ElpjgD/LGCxA0xVyIDBQMqtyp54hPAyZgsxbyHVe7FjGRAKICQ8wzFstMIhLwWaIsXMR7FggXoIEFLGBFZD8hMWZmZMKSSz2q3lMVkARADgjPMS8HUzwM3AJmS4gv+dKocFGYJIlnigEU9hOwPxZHi0Z6SvnCGEJcdX6PM1cQDK58mMGxn5aTrD6oNOsqNCH7hnuvcadOfHkZGtJL3uNnxqrhT8a9xMnjbkP+Q8U/6WwAPnEVjJ5MZD5HZsyd7kyZOUdkWB5qyRNBFFq4SUVXOm4aXBlsOvkckk+6sNyWRgDMnGm9wQLgPDWZEpmLG46ZhfYaUi1oM0W9U3OOIKn4oZO+5k6hvD3nkMIYR/Ap81Kbdj0JE/uaUq6WUkwBJa3oNXHoO2wJQjdomrsFreYhQSrCwQVSPCxPwyeV8h95S0e6SBIlsR2iy8vJTSlD4tiDPlLEBvkGWL2FaJAV8FsIUnkZgdjnGnNFxoQUacERBDCAb8TydqWqmbjhDaC2J+VaFxw+ZdaUiwO0polplTADhLvwvGNfFh8pBPupbiZhTQne+TKEHI/yXxU7C+ZUfkvziAC/A0Z/x21kvkbFDpaIUWmDyPoLyq3pdmV+laZa1CTlJAJmWICtpauWkeN0QtBiw/hhWkBlokv4bkCu1FT4OGWIFymNCEuY6rkEziQam8IcafbF1wjRs5r7qM9nzb2aO4nGBsFZZbOI9imJb/EK5exgZRHAmvHHzRoDFsliLaxsVVmMiIqmk7kegqlvyURqSSLrFU7imYUyZtmkeVqgJ5TWCPMzcd05txbSa/bhW3cRSXW539oPHRFT2k1ixu3aePFkxizqyU6Zc8cemnZY6edlhEOQGoDkALwI8b8lkeZc77JlblpH1oDuLd7voyYc3CWQuorxWnAhqK35lYrxaaz+WtPIiF8+6BKIjq2BxUwFreh4v2S+gOqrNi7uoILP7ZoAfocUqZFKgcSGF2Sr0xYWKXsqg9R76h6ADRG+nK7CUkFU6a+qOJA6hN4ejgPCBBGGWUxjA7HtkzdkqQUtFugXKAZXzwdyLNEDGhFcNkklpuW/adJUVv0n2RywsnrO5/YUxVcA9wd/4eRg6d5FpF7FT7myZW4Bdngezbh7jHOuDiuFCVeycVI8yn2W57sZymuILVFFUb0yWQBkC47yAm7LOyy8/FjHSIAtPO6Rn6dbgaG9MKHpYLKuiaKgnBZK5QDg3yPxI8pamYkay7GWdyL5qHu+7m6ARBZdBECKOY4mhHsCgUNSJxAu1dKDoRNhU3i2ZxIJzlpOiVezmmxfOaVKsBTHgiIiBjLtaAyotVTIju6yVcSUm52MWbm4xUkWlXK2ktqrZzZ4LK6pk5TnnBBgsYMnbn8ZI8fRUkxPaeA6f+UDskXPBnucdnpFHFQslZFYRLcV9WiCF8CCVrMVHmtVolZ1Xx5PVcBm9MKFqwx3wRADHw48YCg2hUBiToBmKlPPlKaEBUZvfA8124t5kQlq3J3k2gYhPB6cErQngSWhBGYnyMhNGlrCpyh0JWjRSsUBbxdGxIIFkkuFLpOSnkXNTzWfWE4kwZRVptdmXlaCMlTXaOQuqBIwQLLl2rVgByOgrJZ2WpvIp4C8M2UW1CQDtG05uAP+bxAoNcbkB6MNR0kXa3xhjUbZAVMTEgOLkRPEslP/myXR2pNZVVclYRNI7PATTJvERROyd7ZluBPhz63OjnnrwPO2kcUbdVKFYnljoKYnGFKA4QIXUSIVQUQQN5LkNMWbFHQO1EsZA7lBDJpMgmfyrFjeZVJsucguTA7RvMpZVOIcpsSAsJaiuRgzqJtfu76gk5OSie+1NvTbsWm9eUDb14fAPC+g+dSDAqYQvNNSzUs4HyA9UWLn3gD4Irk3V2EpXrKiWJsxm6Cb1GACGCWO7jgvW6o2SFEqHFfdehT9myQJlr1e6Gj8qUJGhYGFDq+LOUAUgSDaHgMQU1Ig3INF9e6DlAMObSmMwWlQYRI56CmSDp4wDbMWUwBJbx4uuUdVLbTZZJccFTZoVjNlw14iD1nNhvGYp7ZVFck7l9hqdQVX7ZYmbp7TJ//lnUVVnJUlIXZXgIeBjAQIMjCZg5VNSJ01IIcJW+2h1PFFJJoKKCI114lStCEWP2dVIDV7LvICNA7xTghcIWYO+g6QKlVmL9Mp0FkDqV1DmKFquFaVpjwNUYK8WX1VBbvCyNZibNSSNHxbABsYYtRBW2GzQrg3iIPpu8DlCBKCV5EA7Iu6Z+NYwKFglwlAAPXm66GexaLaFLm9WcWg/tMGtAMgYIUJNi8rI3eBvgjEBAgKUF2iTSLuH/FKg5AKSS5UX9ZXGBgdyGE2S4h0CJgS8cuLe6ewDmLPweKFiL6XyAYRiOA+1EkkmmNlZ1omYBkVqgaVHyAUCsESoiYlRaGNAlCdXosyTRcZTUGJaqBgACFMB6EZ/pn3BNkWeICiqYZ2PMXU6ATZHWtxI2e3GMNdDVDXNKVJV942R1FVlVzcjkUyVp1CJkOEsVHuakUk1ojQZniNYRN4hVIYKEDHQl4edZ5KNLVasq8Wm6V9nwhuzEjylQKPOhb7ZkeO+hywsEAEAKw4QCkxpgZYs5nvAgLpxkDs01XiEdRqOo3XSmb5u/AmoM8IIwSx3dkE0do46PKwqVp0JI7SCVqgIBso3iJ5LhAoBUKrCINAGWJr59iVRm5MvxUgkaNR8sFn6qdCZnCWgMqjobiA0+UVqwkhNHDBtghhIFXBEyCZDCoBaKTtH71W9RZVek9SBBl3VTVNyboU8Kh2J1Mwtrq1hIo6COCBV/AHq3pAAQBxjzkw+HkD42MzaP5dodOcMHuYBMBe6dlUCY8hjAp4Hi0Kw8bUiDhAUuCxhSA1AI6DoK7MKqbuN9yDsj45MXnmFOMCYtXz553qoBVjmxhRWGpQDpNsUjU2TT4TYWjFneJy66yVNE8BWQkigOW6Gf1lBQsRHjEv2tYR5nMNx/k2HzZxci2E0VCeknrfNfDf2mHZrFXpkLxmRX7lhEccBC2pSvdbMWKNuyRpnlu4IWJVjcUrBdkbtTTFC0gNcpX3qvxsbTxoPAeiTKgH1dyNu3915be8mAZQcdrEuVJBm5XZiXoZ5VN4dpLznpJOxUtFf1R9Ve6xtljVQDIQ2JAfoEpSok+2XuXvre09wUmOqoJQabfdY7ISSlUKXo2vjc0tJ4NWNk4x3cZcyOquoDHVgEDYTIZaoLYS81thbzX0ntpidQyDDJPDaTGu5pIkVWE1f/pJnsl2dWI1cVEQjy3ag5IT7UgSu7aXUxRL2Zz5DalRQygXZBAKJ2lWCjrFk6F2xvKVAZ98u45qdCkZsAwdRbbLhnYWeVzm55HaoLXc+xVvEaJgupPoTheGUmPUEKMHTXQEAOSBoCidO0dB3TSrnXgg3IGgAQAHAoVkjmUZYzd20q0ExJ+KPVEwIbVu1lxQdVKijbSGJF5RqgHp6dKgNIGZmyQcmk+ZN9ANoTAf0ECgOAJMJaCIaDdpObiK76MAJAonOilCZAvfKpo5gKWDHJF+MWfp16VwvOfgbQMJTPayRxUY2FRgeuWxnd69SZS2DZRJdQ29OENelVx1DDdqDDtGMSNmsNE7ZraUV7YWtkcNgUtw0MVwmXjXcdfzRnVLt5Vfpl0xhViEif0BRVEjqgUnepll1K6UtIilQFGKXVuxyY543mcxLJiQQS4Q+F9wZUTe3uOgXNIw0RIstwp4JKvBxThacmiOAGgMORE5+ZxxPAorg1rTUG/KmXchD+xl1md702OGZpYTRNDhRjXgFvnyGaovRpfRW+7oDHzBa0UvnhYFOCB0VXdmZiF3b2JPQTpD253VYFyae9s4VlKTSURV3NJFZ3ETZs3dWGK2+cqO0Dd6+QtnsNhMQyXbZeVbw1cdzFYd0AtbFSd2npzcniyvcisM0TzEEgE8QW+MyDd1PZQpf0XaUVdbSZaNx7bo2URxVh1CX6oUmvV7A6PHpBn5svh5qSA+CdThJh04E70u9gbdcXVhQYHaqfxtAJIpRgFgl4CSRWLje3BVGTTvWGK2vTk4wGzvZsHHIRUJY4BQcfcfXugBvY5m+9krH5ge9cNg72v1BvZQBtG04HaSfJOoKRah8vpkRkBmboHiDU5olNQrbN9eGCq5M8APEhkU4uNQq70NaP8q99FFBaWr8RfJ+KWOT1dDDai9SE6DysYkeVkCq6GO+gDl8WvKAyqH1fKoBQekFYB1ABRY9zVqU1eN1UNtzalXTdZFRlX4xDHdZEDxzHetmcN83Nt041jFYr1JuyvSkWq9K7ZxUa953a9w3iYKrs14sRvWuF7xcnbHm11u6W92eacxP/3XMVBgxAixlEcLWdmk7WcyEWlkJi0x8xssiEADgmNebQDRAO1LWgYEXgMS6LJrEBsmwAfy4tqghnb2xtReX02sKCTDhHitXvR5CNa7BDpCFAfIO6AAAZLwNxNAJb/1xh7BnsAmgTPTZqa9CiAGTyAorU4w99FIIYRH4hQAEnJO+qnj1xh4WeIJYdg/JAD8DkAMAA7ImAHJSZNhKSZTmtAYrEByUig0AwGDRgxgByU/heOiMYS+I5l2htqs3BSDq4CVA5ATPkwC6Dcg26CKD+KRyDkNVYUO2Q19YeL3LdiNb2p+qvEqKXrSFDsOqDgmuSMLqE2fA+JHMEqcwRJM1cZG0lyHzU9pVA2NSnWcde3Ur0lVmdYZ5f95NWd3vdTxOcQgFr5Wj3ADgpdHlgDFRRAMs1DdcqWxtzfXsAim+BpBCo9cit3Z4AzMDDEXmXdcIMdo1oMuAJQDgv+lc+ZSZrEmwiObP6emTQyMMM8cimijvZIks/T2I0xPYhEE10awyHiHfA/oDDKGEv2V2fAKeCmu6yZcMP6c+p1T8hn8A0MPI0Ul1ATQwdDtHgiiOB5Y5MqmosGbmNOdoCnMEFDKjoNig2wC/DmCGcznAfsMrHjIKCqKK78uOXgBTAyIPcPdNCuC/6KD7NhsnseApHhiOCi3rGGmVx8ia1AoxfnO6fDCA8f0PeYNdHXn9sdR/bdJK2bbl0l9uWADXcgPmUO41CzoVUHdVQ0d1Atavau322swxVZK1HwC/wXgMLU1X01yjasox5XQ9o3x5Xg3KNyIYAIqP5I+oewWnAJA8z5VQzvBOE0WGIv+UF53Pje1O18fJYTCwzwl1T3V6/bP6UZlSFLVMWLhPq4ZO0QSFWap3o9GhujzjgGB4sttYdWAlHxNCJO1RtW6N3IjmmV09oYI/ebvoObSYVryvvUKram3ePcDhxxGkJZAoigYfq6jKsKVnOjYg8c1D4QfeuiEdi3Wf0kdkQ50lrdTHRt0y983HL0cdwo3tkLt4mR/3Lto6XUMyjDQxVYSR88GACUIJqELDxIFAG0MM1mmQ90rSz3W3rIShWtzEdVdzud1jjREe+CTjhAD9AOYs4/kilAC3pS2LSDKIogD6xSiHb8O78SaFbW44w2ieODsDPX64M40Cin1GADvAfETpYxHdosEIrInW4cdbIdBE6fPneaBSEoYaA/xaGDfSRIuJG7j9YgFlVZAY+xYvRRKYGNViO4xVKHjpYBQkGxSEktGdlbyfLlVtwFdp0a8JpRmGDcGTp36As69Rgmlk8ZaIa3ooYsPWzGhdgrikZLZdUlB8sALtBXQkLI2QjUAEhgCdd/iJP78+HbV3099yYPcwEAqQEjiGEKmIPAFOMuZczpDhXDZgyE/UsEXcB4RuvaZw7ltiXOe3eikNVQp9kdT1jDYfc2euzY3HWtjt/e2PDxYALO3y95QyKP8NvHUI38dIjVKPf99QwQO69TxMxjOaNTsqMsxcLfu0cxnQ4ckvdEpZ4OyjiAHr2TyIBBP5Xtf3Y3Wd1FuCn12lYeEYVbw6Uy5rLF2lVKynDNlBj3JOT6fg3dtxUytBcC2EBGluwUGPgDhAFUwGFUuaE9FWD+g8DjgT+e1aFkJdS6EdV7S/kFapVSrMCFRw4+hLci9UKfV3zIavvKkwTDEoUV6aW7jr736g7oBgDcA7U3r3XgEAGFMT+QjH/wQAgI7a0OYNTilOxNPXfMNIljqj1mlJoIGxkFaqzE07v6Nk+L12T7SeZEoyPrhJ6xwEiDWFi9AnlEVFDCeoyUeTPYwkV9jgjUOnHdtQ77kjjwU5WjPkYKkDGhl842qMHtbVRuP7hr3VgG9DNRVKri1I6ED0YuFRk9gNoeBEarWoooRjkEYXbh8nGtqCSKhAWDM5BPiJpAK9AJZ6wFTNppPo5UKaDxE8xNZNrRGzmcWIMVMG9EiYIs1rBJSKpHLeq07+B0EWEI6OzAOmMZaS50gbhIDa80kCiupvRnjg5jKs2vKFMR6KKDbgVKPww+VVouBXSCHfadZYuIbVP0PINWWoWVK5Rk7N/gvlsyO7Bp/cR20NpHZf3h6lgJHqMdzkxM739c3C1hztr/d5P/NA44jNDjyM5ppk4DEPs5VW1OEXUPZxRQuPIBcU8zVh2aURKMCdwLad0yjeLFnMTWOc5lMBgMfZX1hizULGTvOQKPHR6ml+LqBF44glYAcBmgDiE2jH0TBGzVaA99kej29sdY4u95c1FcRNPnJo+mSswwgyJbLq3z909cHwBmlhZXNNshH+fjnheuIhBN+JLCHfW4iaw2gn95/JOHFTzFUWL6QSq8woLrzM9o7FLViYFnhS4VCSvm2Og9jnOmqSvJFpxo53jLV3YWiTKJr5HUtIADwoAjxqnKigqYEJo7QotLqDaGTfC95IVYmXn1MCQ/NwLffFqkk4U/n0BAR9IlwksI2ge443zJaCv5H8SGCZhxl1jiGXOtC/gUJbIr4Y+TgJ3xdOBg2UiQ8gcYM4E0AwL8gjgtugmoX7AaA4vJ8jrApyncjit75sjkdiegq9YKJX0wJ6jZwcw5ORWgM3CWUdovVkxH+1/uHO3+3I7SXTttRIA7xzFQ2/3ijKvYOP1Fw4+nPVz+bo+7iw2M/C32epvfth6a72fw7d6WFno2WQR+LtLiQZdrECNuTi9ID2Nb1gy4Wj7Jiv0S6a9kqKOLU9oW7+xKXd1GSoiBgt7FiaYtJDuO8OH4FEwiS+fYNGbs8FmmDiXe36wVqFZ9X+a9/H1wLusSN32hGysaxoYuc41BBaKmPYO6qUMmpBO+gD+Wjh9lpZTUtkusbVmj0MdHGnw9LhS7E6UaiAGiGyKtyE9IIlxmvdNVxj0yc3fVKxd1rGcavuiWajx7chKjdRVMovEVpJSHNx14ntOROTq2dHObdxQ8nXqeL/RYuJFBNUnNE1gLeXMBTdi4SAOL36hdAdyNlJFMl1t3TJ33C8UeANajVRcTO9V4DZEuNpz4D0vjLPEH7WTMMaUoFnmEC/V4eKyw0MNnzWiPw7ziJrd9K5+bmjdaMTWEvH0Dwi8yiTTz8KzXCwQ2AC7LSEOCKAVkLlOWz1MGAC1bU8AtZsUxdwx+Vn2QdboGvlIrRaD0sbBgQjFJZoB/EEpYuz02FKIVy/VUvpJsfSxMD5xFgh0L2J0UmVnV0q8axSQcSScv89Zy+osFwc3XQALds2eO2xD9HQYt9xRi7ZEQzYJk5FCjTy15NwzTwXx3E1ny0jNVV9i9uMIl0+mCCfiLizFM5Yr2Zb0BqOJVulSsH7jE1nM5GbhDLLccB3NaUD/HnlPx77XaON18XeGBC5i9uhSVkXqTuKNTKYx1F9M1NGxiCGNnfYWU5yy2mtFCLsG/Logu9o+DYwuCysXELlXYuh81N0NF0s5U3hEF9GtE1S4yoXraUtizZg9oM616wLjyLAlXSOAyJIa/7EhtZAIZouQ8sxd4+m+ncfOKUG2pCKtr+9rdB3Adlg+5JLDRieufjKQPl4fQT6TxN+e9ayWUsaQ06n1Loaaxh2aq2ZsHH4jsQIWORAy2p3XV8/s0WGBzbI02MzdGi4momL25EMnmLXkzx1vLXqx8v+TvqzlE/L24zdEArIksxBAri6SAPNWQ8xPD4z8ec35ZoJOqkthxO5Siv0j+5U+hS4gPVKrEiy69H2N1S7lmShKgmFpSzI1CrhFZxWS4CT6uITUTA0bVTsQkfW0ZYyMEThK8tWBj+1ZqtjQ5g1h2XAQ65S5SzgYrF1emtWCtA8Tc/kVrChu5SX5VOT6G6CyxBm3O7laqmHkzpCCRtvOfw1EQNqwwHYvnUhiMPXZuCIg03Yjdo9iDARkQBBJBgJ+qUI3DN5j4qgB1NJrr8MuARq42NqLEGzv4RDsWxL1sN1yzyPQbI8YKOPLu3fBtijA6dUMnhOde2rVz2ZXs19A+zhHIfAoBaGt3d22O4v7KdLZptVS2hZLF8ARyvbXONgG58gpLdAQj2eW7sWfiFGT9XgktOWaB+F5LYsETAwcpfpZype2BSoZlbFazfLDmMEwZ3TSu6OpyE0wgMcjP4rW58w8QLPN9IsYfnrwugF+CVSgGTUtXzBD41Fmeb64AfMRzkRfUP4aZmTopKzZlURtQF2FHi+EB5aIvTfqsKvQqoJ0w82/7VI5aUBAkjLfnhBT/1Jyu1bhyagtQspNPBFVkTRF82qtXz2CX7wA7uIorLccqAD2sT14m5QDW1DEEdGo7PYOjtSw+oPqBWAoO3wjg4ekQnGk6YIBwugJKuEqqCM50v9tzbFW6Urq0nhc0kNjQcw83nLOWHlh7+WTH10dxVq8DuAy2lYtko12JRUnugmfVcjOeb00kMKdQUOQ2pb/I+lujJPzaKNJFiG75PerKG6nN+r6G0wY+lsUJVugr1zqo2JgW49XXFzKUfo2lM3KhIh61OZcSAgqk1LTys198tKZGOMqDlPrWEul8y+gd7rJjBNupj/JWI14K1vjUa/XfPIUQNcdVbsOKxda0qRpbxa0kJFHfoOkFM66HugEOVDlywGfd6Gap31n1v4A0pOsCe7u9NMPxVXBRxNvoFG45m5r5KwpsTQJKboBK+YxluJgIWvFqaMKzgDtG618LOwuQl0vufnFjfmfQLzefvvEgTooyBpHhGWyJn2D73JihDypy+ccgQobu1HtVe1ew7XZ4lAAQGQqK8HPsi5sw4lClRQidugFTiw67HFrkNvGFv0aiHPsSD0YvU2muKCGPlb7cZTBUZOwHUn1Jg7dp2XzRBgPKnlYne8h3UWZoneY6+BrqT39LNFIoph4DEuwPP7jMzQDb7ve7xaCrKHSq0MJmtemCkM5PW6ADe5+xI7H5nMyqFSKIMvkLsAShQE5cT6MJX4Ne5Fv+uR9TjXRvumf63fg8HesrWOn0npWjjsz7BNtvPja3vSRhbDTZFvAbRuaBs0NQu6atDO1/T0kpbTqwJnsdO3btmwzB2f2PvLn/WbtobGc82KNUygWBgT+Iklbv4ANu8ukFwh7TW1HtUaycnk+OAVPAq5tA5AWik4ra73NMbhI1S+b+S1ekJAL4pzBnY4+9bsUqcnQfKjz0ldSjI8g0SOjhk3of2sabYy//qlwJpaoPnzWaJZCBl1jrTwxkGgyTkUpfvsEeirfrSUCUAkXfixfAER6YpKxJAJ7s7R0y9PY36xOKpqyVpLiUcqJZR9aG8sjR78THx/+6Eh38zErxFF20R/YeKH6MbZMC95YeSVSRFHSL3gbF/QWJ6Ty1HpFj8mchocOr7zSx0P913A8t67fDQhvv9xhzYtk1acxbuZz6MzES4bj2fhsqNCYWb10WuW9JkgtksvcfWiPsw6BmQCVV4vJKlRQ5mv1ia2aPD9nfRIjH2u/EwJGzBOljD4sH4UcCVQFieZSnzbytfC6xHoT+3axReyXuzg0OSkdiAw0R8gmGY0QbEzRFSwMYLRsB5L4YnO4tYWh2TNk+sEZC4h4ALIPaNyuGUZ0NxLNeS0R+GMnllOQv2OAs2CoSIRPSmtfpXJ+GAssjmQyh2VBGNiVY9vTMOpcKcTJv2vSWuQR3zH/O4scmrsW+ENUdI7SosxDUvclvGL2hwFSlDGW/od9phh/DNlVko6htJSXDvP2buU5JL4OHpRSDxydju8PORAUPKT3d6ZuJTklB0EpEawW1oMKf4RVsTMjJYZ2LnEuG9tsXyZCs5nemWdGUI3M4kJpYgJvhmHV9C8bD0qWALeOHaj63QJS4GNlLS6El0WFMszMHcCmQODDxnYvEj36IRqkVB0R6IAsQKEwkOGdnE6vP9BdnsgD2fsJ5QqFBZ4x4CZWfUeCF2asY2gWmV2ax5cgZc2+eKwnHiHS+QG6owcSOe9+RBdrl/Su6QHztr6c2pZsYTUtJJvl3Iv6bdokZ1tT5eCw5+u6CyhRcavuUW4Lv2Txp+4iXLVubyOY1YAE1iVy0M26u9jTp56vG7yGzUOmH7p6garmMuDLKpnvpyb1aZvhrYjysMYJd03Q5mRS3HLVmTS03jq+Nqgvy3CrNrKWMYMlDiSzOiuYECsuHEHjVO0ZZB+eXDpZDCUAaZ6lS+RCIhdUXFinhDpk1QSmgqw9JGWRz5S8GA0uGXeahNBZNZ5Ou5aMBFqPOEreRWvD1DBgV6i5j/EkK4CG4Pt5g8me+fMpdLbc94NK3NPvYpp8F3Rd9rZHhVnI68og52jB/F8nHJasEMpefl6+AWvPugwbMYUEd07MgcbM9j1puwiLutqHS0ktqCfnYGzFsX9wvVBs2nYAJCZdjeh/EV3B2W4u3WLKc7YtpzHpxQVroR6Chf/kynET4Qh6Ad0OlncIqfEEgoRGAB3wrErECNANgHUA48yefY2uDPiEAmF6tVyfna1OCPVd1AzRdsUpOXOXa2QrapLXviC9ff6a3QyYytCmjPgEDA2aWYo/ktLiaCiHsEXDgxK1d3Oq8rqXXl2+g+X8RIJhphOp9hTWXdw0Kp2kElPGFItm4+rA9Z7ThQ187fPdFuqHP5xRVcjNJY6vHHnzV2nP9mW+BcCNkFwjOunsF5LLZXORVib5XwPBqPxTbeoTMceK5s9yB5EN5p3XtjdZeLH085J5mkEDaAD0dqFBW9tvtAGdz7V9LteYWdTsaIMcZNbujWdP6q+BmfZCUuIBMtzYxVWaGXUatFpBG+N/kH0JjKPpjSIUDI9vqlK8DXlnKt0E+kQZC3upNM+LN+hq03DXcxGRnMAiAS0E362wfoeR5sfsHl/sXGvzNtfewR+eMk+6DIaXAnLz6rDRqZuoHiKohdOXx6+rCTEaYKcwq0c5xlJjek8FMQESmYFoooe+KwhkzbHsygQSInNzXnyEPXTJH0ZQDSbpPTDljolK5JcK9MAV3va4eh25xBFcqH359FcaL8W5scC707gjWWn711RV39dywnpNYiV79cOnomT5NA3PqyDfNyHp55m5F/dc8f5zOM7FMPC+yycqlz6V8DeZXfq/XfnxjdyVG/dOUIReUTyoV0sUZUYrWgkEYV/DfmbzjT5leGkFK+2ZrRNxlAUbx2EQhyJeIOCDyZvhvPdPouSspnY33shlpIKL7owTTMB4EDvDgPh98R3mVZK/jYG11jxI7m2aEoAZHhNsbUd7wWXqX+xjhUFASGRQQ9Dnx2Nw7j/VtgbvLvqejNuuf0CSKkifUtRxQDwPB7mffHI8D5Y5rrOqi5BtN4cXttY3YVxPnBjq4LwvaWLGrUGdBgajB0BAMt0Z03Qsq30GoSgSSok3e16IRjanaOBDCX6yofAwOaHlzKEyI5Xg2isH6ChLcJQnmhPDxZ7pq32Si3mXqrIHBOtaCRms2FmnvqSABgDZksQOQD/OTU6/ez8mgPqdPXX579Ndxoc5SV2r1JYXcuT9JVbBnHLuc8serM8S6c13vdzlEenxHgXps+NZM3cClBcwT7OHcNwnl9DHj0WQa+R5g51Qny5rYiCWg5TUFz2UfsjSCzNTtmcpJo83oIz6ItTRcYXEEww5ZDiwIMY+3KO8omaW5O3r7oARzlPBOV6twGBd+tbTftZohBq3hGtrAbSMwQnljNuXzZO4i5U46mM4YfQjT6Puu3RHmO6881+0wZTeoFTN7o9Ls/0G9ZiYBbII2Op2vkA5tfu09frz7BrjL3e1ggAHWIT+uBhPxOaROVtuxu1bHGJ7o0b9MyWPKvaUylvtbo5pqpU77PJBl49S+mvDtyJPYKqndTdGxxyNfGAM1csF363bcsdjzICBfdjYFwYcA3TjzMmm7rj3BcI3qJAfefIPj81VhrsnWDyBPHp4i+0byLwhmo3fQ+Wm1o76ti9GbEkr+sS6UT3Ufbm+j+6Db3dyOj4TgmPszODFocZvd/bpwNvdwMIvlJAkv5Wo6D7GG2/3YPFqZnHvEch4HqrMAikspJYAM0NAC/IiJxw7ZUEDcazoInLxjeWB1Ro2f5Nr9UXtdV1qKBCyz9yOBJI88I8G133fD5tXeDq+wimX4I6AXijwEzG1xISb9yOBuz3eUU/7uMbRBS8vOsPy+CYxPA6mIpJO9QmUr189+r7lzIavtz1yPRMw2vXzwcHsjf05yMHHH10ccxzADrrv2P7qxBfQvzckGYHoMjbHDoYwOnnO+Prd+GtFzgT/m9/9XSGHmAaw9wPC5nHt8x4iOjUOGIROva/Rj5w3SDpTvoG8DuKag/gPQm8OCR0RP2WCOLGgxVDWyHgroZsOyjOq2Yt5qvQ/qDDCGGNTGNfWoSIv9DEPLcIBgPAD+Pkg6U3mBYSjujQVtjeY1bwoiSpXbGMxQIXkDAhngoQFooPg5EjCcAwDl/ISKaCYmfgIjIYlKC57sWAw8iw664mCWkeqOwQ2AWgQzbyYQt2oCgZyaEqhjokQOhQc4HRxfbugAAAJvYJAC/wTEm8BWBYfm8Lh91IleLMAv80i7Ozi+5ELhCqoJZ1NEsYHYoClIfHQDA2CPlgpKG3PcKOwQCKXb6ihugRmH3742j5Dg9VSyKFCJAooQsaVYo4QEwJTqdFp6CJERMNa6SBu4OERVSq7xOjoU3m/4T1CtydNP8hbE9LM7ceTUJBKaWAJ47og8SFiduzWaEwX7wMABNo7iUuPqoyoF1Lp88fdQgxj3IcQn+BldMulPB3w9AHNcV7WFgV52fbYquBkCKp26Bc1ciFJDhf3XAbh5HhT1mg2AIYdUHQA2Wh5BOKUQMmDgfv8aEBhtNEPilgKciiTvBnY+gTqyEdavaJFCd5vBN4qIzAmJo4e79aP8y3Ph5SnE9cXHlMtRX77c4IARfEDyR+urZiE0czY5m9im3iakzo+6JOf3oiqYFgT3K8LW+MAva38kxMJCMM1Pr8byZE/PSbzv5hzeeJY9AvCdQ/36BcG3tmXHVi2ewnWW4NhPxgIammSNVUU3u1VbexKuklZ5GN1AJ3d+qVQw687Ld9TA73G9wUhHnbADhgxJJjrimmZKgAnWRpRvymtW8IgoJLkx0Oaw7VI/zvTeKSaj8uAGfbk5qSpwhwLUA4P+4izgNHwT+AiGhXIgnickV2jzPwStULjY2LAMFDiH0Abh8hGYpQDWNS6P6HP18i9GuFo4RD30lQyl3IA97veeSxWx0T9mmc/FANz+0orAc4tQHve+SmBHq4GA1YWRC7K7GOYiAF0VQVqb3u0G/wWuLNfHxE5V4H0fKb+ONRLXKacwoX1PAaf8UTmAW/PBFb9H7TIvuJX8OPLI1NdabbI2IQckehQ2ij4P/nWEOIL0zcUfMHgDKXMqM8KvC0irsPXdP6M1/8Ocf4XyBEiXvGtxJfVMrcOY+oNH/cTh0HEkV2R+PcyWQPGo8qZAJIrgC08gpw+pcfgmPCPmDJEZXBh/5UL34PfdZ+/qE0q+6qKy/nf7gD2SIv9xMMIH4X3hLhMfkFCggBmAHwXb+Qk+CJy0QKLjum9VcSBYdTsBSDeCqZNEn/FGWjarZaR55f568SAAws3JMj94hVevfvL/4JOuLLgbBaIovrNHIO7Fr0Aaf5LqM7Mf8pYoGlfxgDV/T5aOhS4CbaCYJyoBAcVq3/G6AIwPcR/EWdDpGALJh/B5JVxGDxT/WlDazZFI7fVRYvXDO5mrXfzQ1HRaw1c05NkCIpskAdiE0MRIrdUJDj2eLCoABlAbSGpxWnT67pvEu4/XV1Z/XBIpXfHLYA/MVS3EUY4tnEDyaAJ77Y+F77SdRw5grRH5jwH/6yyGIBg/d/4nWeU5YiBErcKM0Sj3cCTlQD6DriB3i9MZGzPEIjAgQTXQIiaFg8AqMqugbdajAVnS15VAQ05NlyKabMQV9fQrFpIyiwRWcqYSUdCygKV61rV7hQSXFaz1NCSAIfMST6YIQMyJjBubA2CGwLMBNdRERfhdggUaGVB2ae0puEGcT42WQjQ0JdCukAeAwcf6CyET4CMrHlY4gQ5iAQKBTepA+KSvOgBxiFsReAohYIFO5T7DNJKwoVQFxANv6v/F4SF8ZJAY4O6JLyM34bFHP40EPP4F/AwEh4P6JETQYpFZdLQtA416XWUGwbFEv68sN0AV/K0r2OfvwaFWMToZHPhAyaWrKUWqK0OFi4zibYoaFCaSjoFRRY8dXg8EI0DQAGwAGQCHKNABZBgAWcBywKwD6gGwDQALgbQAMq7MRAB7L4aERGzDsSySEIBnQdLTATb244nCPZu/M5hv/UYF3RVUS0BO4pQEYMwvnYzSb/HXi/uZAABFGKA4WCCrDfO1SH/ciDjZQpglcR1Cy8X2Rh2MSK0AubCGPFKrGPUiq/PKsKxXL65PaNNS6Hcu7JXTbjbcXbhXHbSCA/FTC5Ff4QvWAQAJiMH7PfYFbG9ESpFXaDCQwb3xpOfdL3kdkGokTkGigbkG8gtMjEFQi5GlMn4PIUEFqSKVhugWwD6gW4HQAeyQ+ACegGQO5Ae4NiStoSMTxIegB30WMwq8ZwAp+acBv/fXDP3RKAeocypOIQpTmUbCC9Ar5DK0Ukj+QduxtmDYJPCJoGgMGfp//T1oz9LMThYcMA+ABOTUWXn5Z4R7JZoYjZC/XDS3/bGDQwW7zi/c8xWYAljU4ZwzsvJmCWJXyqFeEkHQHLACFHGoFTIWnhNdCR7aUfEHbSGfSF7CsH7UV4ou/XiyWQD34HiIBBmNUsA5gexrirOsEceMRAUzYP7kIQzpGKZ+7zPUIGlgAP5M+Wp5MbUYAOyEpDBHHL6PYOJjkjd9BzgiMF+QHiypARBD/cDJ6BEQZjP/PmQf/OvzeMd0A+Ae4FywHSDwxSHL2SfUB1AaAC79aACT9TwhvzAx695Gj42g+wiz/JDj2ISRpTHfqRb6G6pJyOiguAa2Ya3Zz7/CGfJHmP8y4QS8E2Aa8G3gvkD3gx8HPg03Rj/aCE6YFP4ryN/5l9S/YKtRzLF/aIil/U4CUHVEgBeUUCgcBMR4/KMCgZa5j3qGgBwSKXAIQpCHQAO8EPgp8GPgo6CbUbP6wCBgD5/doB4Af/KyIRMDr/U4CKDPcEYHX4j9rLZYbaYf7g4IsEP/fZQ2afijGgi4h8AViE3g9iEoQziHoQzr7QQ3Qp+qP74cbbhThhUlbvHLzDTUREEbaZ859PPtykkM2RKQ9UFv/BOSeOCfyJ3SXRK3boGVjeP5gg4WKkg1kZp3Ex5C9SDanBOK7oNC74JFZkFV3OOAHodhCU/fkF4bdoaM1YUEuHLwGXEAH7zSPaAJQ2xo6YF9TUQWlrviAGy86T4SShdWDYBBOKK+TnQ7RPECNAHSB1APkAGQeyR4gfUBNQvUEGg+yRnAnSAoQ68FGgVCFcQ3UHQAfUDp4IyB7AYFT1QxqHNQ1qHtQvqEDQ58H2SYaGjQwoDjQ+5BzQ6AAkhQoCEmQoB6QRaEjQsaHuIGZDuQlSjKWKRCgQehBeiN0CtubQQ8ELnDEqF4gVgZkQkAAQCHmK4CPQ56ECLMlDuIP0wgSOpzK0HeBywBgDcASl7K8JHpeAdxDZJd9ADeSiG4AaiHVNX9SGAv/4ciQlgGUNJyxA6i4Ogb6EiHWgAH4JwjtmHhQHFYIAAQNFCHcaIRj4CmDfQg/bp4JUwYAXcKDgMeqNRHBCGxSADTQXABzQVdBS5DQHRremGzTbAHGaE3TmWI3TtfayzMeNGqsWWFLkAqyEf5VSREuN9CFaUqG4Tb4Ru6C1wUWCXpLZB6689MkGRXTAGUgqyRZ3DpISmAgHWrfO4pvKx7AvVyZ0VKKGOnKF6DpFsQpNAgDMADwBJQl44pQ1ZTOHBWFKpU+TkmRyQnMeyQ9mcZ4BQBJBQSVJDFg/KHr2QqG7yJACmyV+qYgp0GMeQwz9fJlBuhCdqYCbwbuqOKE5Qwwg7SMCodiRrToAn6YUg/b5ieS3LgzGkGxWK2GDhQ3YZ1XoDiARahwYZ2Et3VxYE+AQSX4ICG1tY9DvoXhZqWSMzzRXd6HCKCQBAc2jSiVzTjyJvAaAATD94AIC8RRg5KGMWB1wwkCu+JST+ORRT81ceZHQNuG8FFAZgEQ+BcAEABXEO2FzjB2HcSaHjt5Li5/bagHqwPLTsJMFw7IQ8TWhUCp7ScLzzwsQSsTL5CcSYcTDLffax8VuHPwvh6JNaszdwTigEICc5XvNzAb6OkLfkF4iz7AWR86HSL5wpY6PNMjr4xExZVAS2B2nc44JzdgGTJdjQBATjTAQP1pP8XnQBdToCCAgUGvHVZQtwqP5CQ0kBA9PhATgBYYkAJOKVKBfT3qMvBoPJdBARc9S4IzjQEIpIREI/zKbLPPh++OhGqgLlAKQ9fJNbM5gPnOwJPSFrptZJrRWWH6rSIdRBsZaMA98Y9p36D2FOmQV4GPDWFDZIKHfPKK66wt64mLcoBoIiuH41B4IDpHBF4I4dqaaXKK3eD7BIAMNoZTRKGkI5KF+PUPRkBXWgjgPhKjgYcrb+d9CjQW4gpAMQicwdbQa4Y/DwwNRFfEG+DypKDC3ePkDOI8fx/gQ7bSIZoI2oQcATedgBhJWQBdMT0hZI4JCggXJFnGaxAe6QcB8gKgCIYbJHFIhJSapM/h1TfKDdoGECVIjMQ1I3AC5I0m63qCvqw/WvjygaLQ31AyY1g7h5v1cP5BIopEfATUAuQbNLuIKwC6LYl4beU4DQAdpGpYJ0D4ieVI+CRADHAdOabfAIANQB8jMHMEBNgGAArIvw7iIXhZAAwN6CIcYbrQCgCkQ4cAxgaIApMPcpTmU1T5IlVB4cEOit0LpiISPWRBadQLtcLJijQZDjV5bDCRwz16PIsRLped9TQ9Q0BGgRUJxMDVoZyO9JxlXIDoILwbByAgSdBTE56gOFH8XDWp6xdECZgouArIvBJnYUaDEI8ICVoJA5LA+gDLIiZFXIyQhuJGvz4ou5HH4EYCQo55GGbBPxWuAPgYkcMgfxaRCcGCRwjAEfYUAHwCgLLFHRAYgRuIKAAJI69AV5REpVxLSjCRLlK8LUaATEUJHXQFaD5mHKGZgnwSBEJxFj+H/6Uo6cy3edpGMQjKAarKZGJgStB3wGFgXQJJHGojyEm6XuyJAHcS8LBhg3AF4D0AXVHapWoIwgdpElIplK4AINEJKH5G6yb74lcbnDIAaLTAoyhBhIauInQM6ATwXGBdQTU5iQmbaBEYaj9Sbq5BVXzwllGCBIoo0SpIgJGL8F352o6QApougBOolxG+faRDUozJG4AYxqHIdpFdMWNFLgf1Clmfk5KiQZFpZdk4v5Y1giGWAB6JJ4rSonNDyKUpJokRxDtXDP7IpYVC6oBzAIub4TBAN8Cv1XhY31ASj/6RMKWFedLuIeVGlgNei2gdy44IedEqopPiKqKXAbongaSFRfj6orACGosQ6IJI5GDRGALVmIMSWvK5rjMQtGorTlCsEUNETI3JFdMENFho+qRhCGsoC8J7AuIDxQ7RAKDXokYK22P5KhvCIFVXOdFRZE8C5YQzQuyHEFHMBGCkWLUyCCYQSTeGWi1pRmzbAFICLyeHrEogtHug+1T0otFHiqZRg/7R5SfiBLoVneRaEY9ICTeclHw9cOJOIWeAnIhlFXQ27xHosJGZAXHhSKa/ZKhVb5Egc0Bc4Gyx/QYSi1jA5CQo3E62mZlFreWFHGgBFEO7b0CIpGO6nAOcBa4QUruDbLTLLdRD6ovkABAPxESPGrQ2YBjEhI49GRHb77Sicab8+Xha7ovy7jVF35zI5FjAovRh/I2lGz1VTFaY+FE2os3qOpE8TKYrwgLyOXzzgLwYu/EDSxPa7aqY79pZoNkKcGQCDZaVXKXow6BwY08ibov1HtnKuIdvVDGSVMh4u/d+ENtZBJKIkOQ6oFhzebLLF8AN0AkFT2T+KaiAPoySpeoOtGZAZ/CDaW0zJooGAnogJrefCo7FYpSKpQc9GVKeDG3opX5xEA3KawgxEJvPb6mPOOrEGPQSLIwIpLUeFAqo3RhCoejFygGdhSsK6hbIhgA7IuL6yAPZHwAA5G7aI5Hw0eVL6gOGBcAZX7ypIDjIcA7EZQIxgoAE2AEcSxjv6GwALyLgDo8MEqWMPmp0AFmhcAYgz30UNopIzIBdMGLjeYJABbRQ8QmwUVbeYFTDw4sV4+Mb6ieYe7G97bATAQHvavY26h6MD7HoIL7FQQBHG4rAHEIgBkwg4mTGTgCHH7maHHdY2HEYATHG/Y75jI47UA0cCjitLDnHeMB2SQo0iHxcEjiIXJ5EIqOdyIAUXEeMORhdMGXE+Md/ZfIqgDy4qnE44+GizI9KrDNcwbqYIQRcYvIDUYpFj9oUnErIpUzYnRNGXIg0DGgDlG3InTERY/TFugcXFsoiwFO4rlFS4yxyU7I0DyvdM42vf6By4pDhK4xQRdMZ/CuhA5AjUEGZZMHDFr6RbSxPfjEX/QTEMY/RZHfV5pRzU771YMkAMlCxGkiGKGQ+GxE8I/fw84VnESAlQiE/BuFlvJuGh6XgghIXpj7+EyCkKd7G1otnHSQf5K0ecKoXIveiLiPTHXI1lH3I1lFu4gMiPVb5FIcMBofzQgRDqcSYOdRWTBI35GX2cgHtWaACN4n/5bY4Ipbec3G2pb7FfvV5TnbWgAs0CNCIAe1HVopDgY4pDhmUXZiWogPoBxUjjG/Orh4iHvEE0PvGS4gfEfIlXHsSTEQqwO/S2mWLS2aeUDkaerHxhNnLQ4i6SaWIvET+RADrYowhikBSy64dFyCkfL6TwX7ADgwMCnkX3HfSM2Qd420JaELxJCw12R2yIvzjMOFyzcNj6aYSHaz5VTCKBM3FPVcIqdFZMAh+eiHZgy5Ey0aiRzY86iPXLWHBQwuGrY0PRRfHnEpOHO5/gK6gL45JE//I7EnY7ZHeJR+CXY294xSbnCj4JwhBAIQk2HEvF3Y5X5QASH5NIrEAvY3vYCExfET+ENi+4zmhQQGrj50YJFGEq6jb4lmimEpAAH486B0AUwnH40wkWEc/G4AIBh447ORuwInFaEwQnOo98B6ErvHa0QwmVcEwmBEsHE743ACWE/fFVomwl9sSrj2EyriOE9/QuE/z6aE17FO4kXExcENhYdBjHJErQmu4x/FAQBXEZEw6CjQbIlQAK6j+4gokWDLIlWmQxapvIu5RuMxE7kLPG/NKuElVPPH4IgvGTYZ4CZEMAAl4txFwBDxHlvL4wCGS/BW4rqiVsFQgiwA8AnAfsQTIFRSWgJ9EfWUMYpkFYHaVQjDkBFLCiY7VEYAFtE3AUhSQEe9ACMSACjQhZCmQBYkz+AXxOYzIA7EvYnTwrAB7EmPjvATPC4ACmaeyYUBKAIYDw9XwjfwxzL+9XeDCCMwzONGVCjE6NQ9UbglmaG1iorH6BKiYrqYMKuxaou0CMwH0yx8FMr03PaAUfdAB0EoEQi9T3Ahje1rc9XnaLYw06mnUKFfGMEkOaBliVse4mL0J4nd2JSTEgY4mnE+EnSHNX51ZJknvga4lPASfam4WxBvEmyhjMB2R0k8lyfA8LzrEmVCbEhEnbE/wC7Ep4DeYMgBTE0JAfTBzESIL/j7MS4muGOYlSAtPE1EBonMA+06MgyxHFVaxFcI2xEdE7om0iEharmdmgEAObB9EreIuwzxFDEpeHJAErpd0CXa8tGHE//B/jMiaVFcmS+oolIGQfEEAl7jTRypQNGjIURg7ekqRS3URMgz4j1Ay7NfQRBbdFYlIMmMwRdaokITRN8QvreE5TR84aMLVopJo7IFlExIW6Dt1FMmGRLNBUk74A6Ej8SpgwkCD4unIneYBEsIIl4kGSpTAk6Mn5kBCRqxd/FFg26jpA90CyvFZDDQPkBsUOmhIgEvDEbO5AogqI4EEgKSSsTEGmYNBYrEtfQZIv7AKEvcbhYpvIx0QW4GxIVCHyTvrMuZMwbEZeHiQMskokknEgI1cCJkOqTOgh3SyIVoRmkwgAbgVNp6A0RaSwKvEAok4iL0CIR7NH0km6PmyaxeMTsAeQnZkt5BqPeMKPZCwi8hVSaDY8NKICJADbQeQDFk0bEskjyB44Uz6vQWLTc9b3QTdZQ6GInWFFwvWFrHa0LbHdtYYA+bqmrcMAAzJAnuwPAH9oW9gbzOrB6WZfE7HZEqCI4GSo1JJqewENwAXAZI6kpondEXohLONol2InnDJBCIQt4ZwDgwPADWk0vHuI+0mDE7mQMwsETpLdgjeI94AdMSSkUABnRtAbTYzbT2L1BYUABgK4p/bbi5Okro6vcH6HL4SMn0AN0CjE6RbrE0RHivJeCDk1ZAjgYjZyA6UTIAYEnzoiVbZoHzY8RGVTkBPYnMiPoAVGMs6wA9imYLPp4PQdclAiS8l0KHgZsyDj7u9UClCqIrFQxCY5pU/YZKkypTSE6IBpCerhrXRZE9UYYlrE74CANe5gwUyIlwU3GBioyeDpSW3ikUUCCiTV/iiSQeh/gaILkBLKk9Y8RCivSuDy6NIReE3qkzfTQCPIKxCQE9SEgwJgTMeQbRfAKOB9tVPg8RO/A4gRWRUEDUnQsNkmpk5iYOY0jyPQLPyr7Gc72ET8DrExf5ZnFJjLkmiDMiEkLsQvYBWAHSB4gZqFnAwyAmgTNCqYkTZCqfpY4wP5CQLD0keQ/waLQBWJz/KaniPOKlZAdLSuU/FoFoDqqGWASjIgK+q2BCB5joXQh+ePQSfxEbSDgGYR30DtFEsaZjXgMJDhvUTa4reBFGnLAHhDYikR47O7pSfSb/gOjpJ4iOY39G5Zaks2D8U0C6sA0Hw54n/wiUjokKRfcbvE20nF1AYkV45JipMY0n547wo84XmkJomyiU/Xf7vDCsYb6B0Jz7cCgYkPmn1ICsaUowJwzUCeBFU7iRj/IYSMZMuI29CTQYomDx4abXi8WXha60nMG405VCPcDwCtTMPCUANEAY4CsxHg+MluYuwILQRWSIQJ2n/ETHJB/fXT6UDEQYkPED6UQAz2tCOhgEM3ExI10AXeJm5VSOT40QX0AFtPBaMAfSiO0hiAu0pHBlcBMbVA1a6DUXiYfEcRT+iXsyVMZcDuUFf5ujDQAl0nOHg8eVatoDEkohfnYg01cA3DSoHKTEcDMrSjFpJXEalU+wDX2IsiKk3mYASQUndNXDDTgDzGdovGmUTEhLUITFSPgSqZFwEYTccammsU6cn84WcnGwSxjuodbTvCeZqBxTNIGUc2lP7RAGzqVJhj0zzEYuaGBE7b9TcebjLkdMuLrHOpQLdLLBbHIIo7HaXopqVmngvdmmV3XPFi0/BGnY87ESE64hJ7OTEf4HnCOEYRAgU3qk9E5iDUESlFl41F5vfAwBlUiHgVffHTNCFkgOAO+H5cfYigU+BnowfBTpBMGlEMjzrFgSlHc3EGBQGFoRjUouCwkV/i2IRXzvOObbQMj8Ba0ABHgYOWG4QC+rzoMGmfUyZ6ggAbxegrJiqTT/GbYj0BIcbVjCo5wB1AdlxxPYuAnAUoDOAeMza4RSypQDyBlk18QTI/6BQQTfEJacwmjoKwmREi6C17SgDB4vlAMolAlSMxarsAGnE3Qoxn8M0CnvIpHFc4U+jH4+Mz4DO+n85GBFlQ3dLWgA9GZgaskrQI6rndcekkAbGlqM3yBiAJf5BIoRm4AAbxs7T8Z1cJDgkRM9EoEGmGtLbgghMjABPE6NTLeKh5redoGv1dhmz2G84TIXKBlifqm5OKvDLbWVEwAXJnIcGEk80JR4d4xlE3I9Mi943IkvIqs5D43igh2JwlfINHisUtcnZU0hmgU8oDOM3qlkgWnoaoA1xiAGljj4XRl2M0EDhkKJSQiZD4L4D2BzbJUCDYieAorTNAjUbRkGxDZnogSIAqXLnBxOWr5w9BzEk04knxqR+lAzbRZ+uUGaYxWjpdaemk1Es2HM002DxWZkDmItmkV3A0nCUwBkTEMQnqJZBCSE+HoQMmQnsM21hkMpQmIAcFDyUxuFovc6jmU8YBuaK/HOxYV4eJO8nS2YalN4ojipE+/E9M7lGD4l/HDUazDtMgjiMonTRPgXhYoEnowOVHVCioZhCtAklq4QLMkjU0FFSQDIJO4haBiUXdKy8YEk8RArz+499Ce435BhVPBZPSCywR3JEo2Yb6qLiZChMZaXaolOgA26f5LNgrZg+WSWzjbN/ZZMdzqBQybrLYoxGEUjiBCoNek7YwtGc0IlnCEyVhXUESnAM8QlQssBko0LcD5UqBlV4WBls48hl3YyACQgCIqZg51nC44/AFElQn50cllS4yNmvY8onpE35nUgQFm/04Fn7dFolGkjoDcIoBkQs3ZFSE+XSyEmBnPkqw7vAchmIMo5HIM1UbC0owAXUj2knk9eFMDFWB7pMFmusyFngQ/NmQMwtmncYtndEkvHls5FnvoSsmPE5iCLSRhkhZYUDT5FhlPAB4kxAfJndUDn4qVD+ZWtPEJ84dPy1M2XQWgHm5ZoeyT2SLRGShZvLdiIhAXeN2ZfMRQIiXMqTl4byhQATiRvEnQwNRc3HDrMq40TSerPsuDRgoPhiOAKuhWMT9l6QTXh/FOIAPbYiIcoZqAGQQDkmGThIlQISQMAJvBnE13zh4hZmmfNfqQASFLVxeVa3yWaprs7kmJyRqCMbZilfMJ1CfWdgiQpYIjUSeGl4JFDn7MvCQisbSjN5MZgL4adnUkrPBs4WGJP1IEl4o26iM3YDmJ0gOlN4Sqx6qUMaNofqDx8PTCCYHynoY90AXoIJkfs5gBV0AW4MGCDl4AKDlqKLanf1LXD8Q8npkPauJuXctjikgEAcksIArWFTlGkVeRgEYEl4JZClw9bRDa+d0CVMJQBBMiOlygPYm4qJQDrkBznsAJzmCWJQB7AEFDwafTlPvaTnhNcumfstzmggDzlBcmTm/sw4DcAELm4APYlaKOTl0pUDnnid8AxcjzkpMMqBJc+eCpcp4AhgbzBzrZcB4AbLkGciJx83PADGcjpgMAIrlwIa0DDQvECkCHm4Mk1fHYfXAQyCKW7k9V4k4c2ex05LIJn0f6AMUlayEc6zA8knDlhDHnr6Is1m7fC1mcEg77hQsuGmwZrDJsukACUl5ZWI7BEts3NkXYj1mTUWFkkoX1k9s78lIslFn9EhSnVshGhj0lpmuk/4gqc4aR4kyVjtkxej6o9YnOZFTl+cl34fkuMQBQIdmzsrPB4JDrmQlQ+mAU4bankIrFcABJAMkjzoqckOFm4yjmSEXTnskqUl7E0gQ/o7vT+Y+PbJQFaBw81Mn8SDanxje1pznNHBZ4RtFZoQwax8OSisDLlkGDYRB6AAIDAAMLByUL/ZHQJzDEubiiSkU1l4U81kEU6bmSwXzEGwlim2s90FcAHwTNsrNm2I1tl5smFnesrtm/sMIAls8pqHc3f5fcjpFQ2YbnvEr5gZUs3FwdDn6Yk6PApRTMFg8gyAnEiHlqkkOGQgTHmSkw5B7E35nXcFNlJXZkrNE15Zc09blnYt1ngQyVS84aaSl08IRUeJBmos8vHosjLi1Fft6yAIMwzBFbTqaB3ASmYqFS+Bd6WgDyBB8kPmtAXGGwQYgFQUV+r68w3mIUhPm4AEOF/c+KYuAU1IPpBKDx84UAzBJPkjVJvjtcOplNdIjw1+T3nO2b3lHI2MnKnatqwDbMHF82WZ246fpuyNzDKw6ZD4KAMEnYD3k5wgUCxQMeBwoFTJs8ojraw9O7GIscm60Nsic0dvml8vnB6E2XhXUQj5kfO741+LPkiEl1kbc0Blu8xCl18tPjW7ZQnXslXn8yTSSfMePSLMK6jp80yCZ8kvmtAVJAhsF3YD8k9i58jQBWU2wnsIi3RXUIPl18kfn4ALOg88sAi6lcwZlyW/lL8xPkr81fEBQMolV4ENgddJ1kAC4fnwsDXHa7Ox75VTBGpXfYR7853ltsy7EJBP1kSAqCDsIftmVs6KaoM2tmas+tk4MhqBAIbi5O8kBnus4gVnksgUUM0QDEIqSqV9CpFg0ypiD8Z2luaLcm6/UdlgkBQDMM98nQQt/KxAfZCRYyAA2wKAAEtZIC9NVNpFE1lAfCF2SDIqCbKGMslTMv6nvgWZnVE9qbYAVQU7ReQoaC+qBaC2+o6CpbZ6C6Zls467gzkXvLzw98ItnV8bHmOmg2Cy5ZGokalQWWswUwpQW1mQnCWBQJC50dqyD+ecgeC98KGaeaimCrniU4cTlaMsGmKDBhD2aTVLhChQXOChkyE7R/TWoSQxGUlQXQWf6DlcJbYv+coWQmbIXaCqXAMMDsRVYRvqlChwU//X/hBsyACINKoWuChKDkAPgTNCbVSlAOxCMYHHDX2XSinIbyC95GwXOfA/agkOCSy/M5h9CoFCLbJoUT+DkCR7bIWwGSyqboia7x+WwVLC98BVADVKQgfYXZC3FpyRUySbUDyCDI9xB8gGe7HSLwUSwThBHA1NJjbdUAR4VfbRC82Jc8czhlkpOKtMGwIdtbIWQ/LngHU0eCbovxwIYrNDpCzdGLCgwXrfWKmgU0IbJ/UUAFic8GXC3naHXdwVE5TwWoiqWBoAQvgOAMwUGkSOSz2IsTKDIoWNgAjJx+YjLvfOhnffP9FNo3JkCCxaA8aWsHiyEgU2HMgUXPXCo7iFSwZKDIi0ATd7Dne5ASia+zS4Hm6UdLIDIeJaKR7AuwHE8ICW3Tqm4QAoVxC1QU6WBJCyuEOELY8bns8ybmc8kklUg2bmMA+blVAVBFLcoFn6k9NkO81onMCl3lECukhsivcYcCsECUC17627GgUrFetkjCPRiLrMtE+ya8AuoJ8Blku4UGqX7Y8LWIU0+YQVwCtFK1OJeGK+UAVXRG5CQ4sGldMfWA99JdnStOlk2vb7Fegr7A7iKHakgMsldMH8xXzWmYa3bQV36R8SWQMslPUeYUUAb6iWQURnIsW9jG4PgWgUxkX4wZkW0ACZm9UrEC5M5MWNLDB5ODYoHVYDZExYZkLRPFy4K4DrrDgK864BZaLAzaixQQF34qg3MQa3agF7SZVy2IdfocFUUBpgm1B/stTCBgJB5Li5+FIgjEXhPK2J8AUkWitF37DVDdLDuFNCnMJUXCwY7ZewXsykAhQUoARUVcoDwUXMTRaoobRY4k1cCxw0oCJKNQGmXRzK8LL0Vs/dTiyk1AHic1ABAGVALXgeg4diD8LvC1cDaCkcCki3xAdUxmDInIfKwoRBKT8vgn4UmfmWsx/i0U74AtSIVC9i2gApi04AU4n7GC4n6gF9a0WECv8QCAe0UtnR0X3CiiXRqAfYJoekX8CywZCCugBditnE9ipMV0S/sWpIEMCDi/oWsSkXk8IsXmbctgVkMniX0AtN7F3ebkAs00Wps80X281bl4CtiW7I/ZGGossk+847los1Bm95dYk7SXEGr4jfk0AYj4Mo+anUGE2DC1D/ItIqpEAYhjGdIjyjJBLVmYowNGAY8NEjIkKV+ShJQGTCIqeUDVQQTbyVtI0KUNMgnE20/1B5lKUltowRDieJQAUfS6nCRGsEKKQ5BAU9BDaCvyleoxRkRSuUAlIu0y+SqqVhS3dCAQfGBmyBXBGcSYhA7KajSMOSzPEw8qXUj0XE/TdoFmDtDKpezGvQQZGZghWATUlhxA80ph6JYZGRiwbiAS9eFXQYdGjo+mxNMcL77Yf2Qu/aMF8AMBrlS6Wk6EDOBQsXJhSku4XeYQGoLipDjPbdMz3gYn6SsM6K4AVaV4vc/BX3QgHVwbDqYoNwWJo+BRIDYn5Lir8W4IRNHqQtcAyw9QlXNaUjsnB6UsJBJBQQEOGVKLH6wgW7zDo0bHiFN+obS/6V0OAZjIy1AAaOcpxjACGU4oqGW34d9A2aUZpPIS+y34dGVXC84zfo8gSv1RxJLSodHE/PRKRKMYJiRDaqQFEajS0y86Xyd8LVMPRJshdaX3SK+HGsE/hBIcY6v8BzHto2Nr5Y8SDpS1tETI6WWuohNBsDXCAZUz2SAy/FnS0n5FncvXHygSjm0AXJkZoRqU+M39GXVAKDnowdEkAFaW34TME50bdGOoFKDpAIJnIyjYJWym2W78GPFv0KFiY/BJllXZGXcLYdGNSe/AC3ICAu/MJJUkD/InMiCjAyxg7es+5BZQRenfY/6TLMqWW2vWdQUIKhAGyg1CIfASRmOTjEAoFgSzEl0n5g3haAja4E8DedHqoIux7wfVwOXYCUJKJJRXNSXJhpcfhjc3ClT89gmC9FY7qUpIA2sybF2s9fKJmJyU4faZQMYzFEZInVnZgky79dSqU5IsKU1S0DF6Mi+nN03UAJS6pFJS2CyIErCK/kL+mgma7gmi8oDbZABlKSnNkEC0yXXYilEUC33koM23boMoHkUYecjC8jjTKS/fnus/ZFXyyhlbi+BTiCxuDP3dpFQ4vSzpaegV3wxtFQGYcHzkU/BLs6CxrYeqRI0SyzjaY4BOOPphXqLzaHNePyK+XFr54fBA/kjMUTglpiNC6qQcgZgCWQCsCWQEwKkKohWQmEhXZClCxHAUalo4aEWWQa7iwAShWWQMkAsKti68hOgACirdmxAa2C3JT1j3IV7TGCI4C8SiFTNuUtYs8hKBMK8oC0ANi4GxAE6GRJnihPIBDs/GVBkgUngTgeqR76fADuIfizHMncTSKuYbkQPVoeaaL5ywOWABAPkBWYhZB2KhZAh430ZuEdTk4o6EXncW7gUgWkCINCsDncCkA7kWmTxWHxVUgKoB0gAJWQmbcpp0+oWBC26G8KpnyFIMQCjwARXeIIRVQEEdCjwV7QBAdkCiKuxD6KvADwocyiokmRV0swkU1MdxD28CcSEZcnzRfAAnT6G8QPdFQrRK+9TOIWuAwYssiuZZdmB+cPpAJT+BrFYYr5PakVjsw0TFQXoHmFegZ/oHn7ngjwVfie+BROApW+4cMiOiWRra6B8CrA4+LVCXem/udimZyTUUdykiUc8siVc8k4KaSuonf0uio28hkF28g3aWizNkvys+UsC8CEfyiZGWSu0nWS23aqE+ZEkGWLRcAd7nPVFZFS4V8XkQdpmjEzvkdMu/Eu4zlF5EvplUs5/HsAIPEyWRXCSbT+jy0eqlE88Nn3MI5lMDR9Sz2CFW9MnPqHQCronzGM7GjdAD1UglVfSE67k84rKfwIqDx8Qih0i1SYvtBfAIUosmP0GJ6vQKp5m4iaKOgNFVkQtkJT9Cj7EtKlWWyUmYl2WOQbFMcDUc4ZmSoxrTIqi/YSo+dZ12M3E1gwZi4qiVY4qilnTUFaa3I0iFpCTxoKKGnT/FFo4UAVMK8sPVUToLZAASIrQqwZNGaktBZT4iZG/kLgBkk4qXygHjH/cprheAN9aU5AVmqYr6gbBYEkZY9EhZYyTodOAUAqLAuGv083IJ4/sRCoThGny8FnnyzbmPKxPFHIqNnDE8tikstgAK4tNWCkzpkS43pnZq6A65qt5HUcLHE5qyHBMHUEBwq7NXdJX5lQmAKjLcrBHGS+NUqS0Bn7Ix8laU6Sm6UubAVsm+VVs/3ktwghCxq0UnWgRXlNMjYIjy3D7Fs+0ABlLgIwijABl81fFnkkvGk/LAAzs3ACaUi0k6U2SnfAvRgyoGyn8SqNkg0S/AnMtMnEJd/nzqsmGzYHFkU4DZFbK774OokgBJM2ma78Y/EHijTFIU3lil5HlWGECgoQkdMku/AWZv4E5mivMdVg0lB4AwYWZXkqtDAo1Sap4Q1K1BSjkDeA36qElIbtWc/49UiSXQkN56vnSCWXkjQAnMzMVxyxegdMG4mcJESGLSzIikazkk0FDXl4idDX4ak5n+Q21ZdJHCkn9TuWkSkKE9yxynFqvRhga0CmOS5rnS854D2gUn48aitVlkxdUbBaRgYfe5XQswco7cn1nkwriUZSFQjSMPWEr05vL9y8ErYlBlA9ZRZjKWQUmd4ODy2IXf7KqOybUagzkUEtDVa8gulkUCKkngE5kwcbwH8a3qkoPZjVfM+1a1E6x462a7hNYRrCNq3AVqQfAVyaq7GgA8gISUi0ldq2Sm9qqyV+8myWicMeprYYI5is9YkyuS8mfAbGFQxBTGZg9NUwUZslzETclQxPRnBUp4ChUsYDhU7o5No0DEFI2GEqc3JEZ4A3mrQ6ADVMYOX9+dNE44FyCzYlqAwgbnHRAGHpfyFgAzQAFAFlfFgliDGFV0a6mzgW6n3Ux6ldQgyAvUlDVK+PKXzqvZHpksPkiYLgAibPl7rSYSWCC2ZCy8VunzqsW59yfAnwAV/KlS2M6nkbRlN4A6AnQeWpbEm7GxAFDlnqhBA1UhtCQwmpkx8zUnypN0y4gZIIgwUYkeEqADoWcqnvAEKlhUy8k7RFRX2ajwKcMxeZqkhrVNos3lI60aBI6ueW1I+RRIAFrVYq8kyWMzQmfgzKn/q4hLqQwZp0hRFinML7Wc4SBkNZMGlXqqI7rappiz1eqmgCW9W1AXvb3qTcVTUDJGBSuEXdi28kDUmnXIAB1keQg3C8hdXnAPF1WMwgeB9o1lGi4M5lFIIVRzSwVkfUvA7ypU/i8wwOxt6ar5BZbql86rDVxy7aICFMPjqzQ8B3ILGBUc5CR2dI5jUYJGldUqsllayHUFak8z0COODtWd2XMy4mW3M3vhq63vYoFJskOakCFxMs7CPQEWBc6erpx08n4sZHNIM4TaiTyzhybY3XHCCNzFjopTQFK2WWw6yylZaniItvMCQuQeFkSciZFEM3YUtnFQgZ9P0W6kdbR18n+QyoedGQ04jbZa2GmFZFxBTHKXCjEpPE7K9jV7KnUUHKvUV6wymlVUQrjaasdqwlY2BUU3uUbq+xit4bdU6NP/EZoDjajwIAn3MTIhsZa2mxw3GBj05oKgIPxkVSHrL+k2XY8UkxYv+BtVmiy5VbcISkny25UJq0LX7Is4lq0zKRhATgVtIWLUvK+LWui+9U8LU8ggKtRXWgM3l+c90Aham0Vhau/XS0h/UXgftlPvYBXiCoZWIqhlzd8+5CubL/H4MmAwvchHk0avzKK+bxmqYS1X2DG7k86d7KOZN2ZMQj8E8ECUX3agIosOSVK88PLxJo87pr/XrZnElAKShf2T10hyyykjSQbiGiC4GvUqzIiHq+UExjlA+MJ4sKhGt5L5AzQSQQ0Qcf50QDoy+gU2n2AWPg7RKQ3TCkg190BsnYaw4DvPZwDyQbCJQPEgz7oD1gICe/7aMSpTCGtbAKG6EX2+EfqMqQ5Dnof4mnMfQ04aCmC6FcQXrafN5eBdxxgBDgAskKXAYfWwThERIKXYrEykeWKApxbRgBZeXTCKKUlw6OCQ++HEAOiMZEMIEyAqcnHU1MKA2WBalWXVA/iOZVzZIkzABKURjA+OLrmokWUjVMJwmz2bDTs0HGBj4x/ZekNxB6I3ZVEkxN6HKwdoU0uinUdcXofM8K51q+tV6S23n67FbmGktbktqt+UPK67EgG2zBgG55WC0k7n+8t0WrEz/XiQVfpgJBlDjGzPzPkzWlKA/txTwYFUdSWfg04ZRRqkwqXf4DHpzqpI1qklI33IG/KzAHYku0cxnIAK400AHYlNcoj63EJ0Y5hN9F/6tA0GcrTp4sKeAPGkgBPGnA2x8MOHG8J6AHqdlrgJIIDUG70Kxk/wyLUbynscytgzuYvK/RGfYrDNZm0QMR6rgVyk8QgfTNQeo6NLMKoaG73xssuiKOveQCtAR2E/k9Y087PiWZEW4DN5T40W8p4AtYxuhcAf403GxujAAM41iYjAApGvQByS0QZGgDQAxczXHIsTEGBITlYGDDk2nSp6h7sDoAwZSGBCMSyD2DSyB6AJ6j+0IYCkMDXDfUDyQymw5AqQNjUsjCbnkUrjVRqlKUnaJsX50Jk3SksIBHYy1iAG9iW36lTn369Y3KE61KkUwkA3sRk2oG5k1hAVk0q0dk2bwTk0q0bk1m8/k2Cml0Yimt0Ya402EnfXin2RMkAZ43o0XK/o1Nq4LUmSpNXXYqWkTGjWkQG50XCAv073ymPAhnAnTf6qMDoaTM1tq7M2q00A15mr+UDsp1BOjRWmR+JPZpGlWGSCjiAYkGLlR0hgCSmggZQE5agWIaECwQYxryWWOFlcbGEgUoSLPvHbQ8M/gC2IIiRgAZ4CH6CEYISdxCZfECQ2AP2A9mp0b9m02XnSZVCuvOc0ChAlhB5Btq05bgibmrwBh0xYC7m1WbioTHZJgLOINDQAi0AHBDWkLtBwURmDcLQNH6UWAw6wQnKlgOvkhcZSxXnCbUDaaVGGzG/YbBX2moNSLJdtK2K5wM6B9UC2Q6kP2CoW1JgVGz1itoFxIAWZhDwfF3XX7Cs4w8E2bKkoIL6IWdzcfKC0eaSUyd9dPyh0/Sg9PBiD3mvVD7mtQzHEXjm5g1cxbw+eau0uijVMTyj9SJqlewdBAGvGYJ+pfkVDnehKIgCi0JQOC3EJV15qmai1+0rqKS41tonw/FiPoE4D8BedE2+T1UXuBS1vahoZGQ7kjpGyQpEEMvJfSSXkehUUBAobhbnbNEKlgPrXMECZCvABYA7iMpmn4QpipnIJH6UAuK3YIy1CqXA2MEoNT05YND1fVzYpDINRfKH/J0Gj0HUI+5lNGvvWDtYfWGEAfXtGo2F0QuMSMhSWEx3NGrcU90CcQLincsp0kPyuhlQrIhBx4yQ33ktHAnaOjlSeOM1tjc2H7y4/UpmlgFpswyWDG5tVX61tXvyms0gSe/Ua0l/XTG15UiAs7nSvBy5X09PIZ0mi3Z0/Fle0hOn0+VS1J09Cj3c3WilrdnD+WoK28hedENwSPCaaWWa5RPBAOw1GAewWEA0W7ZqRgWagVTKFL8Wo1oV07gg+Ca81rlcOlujXs2+EILFLW9HBYBAHn4rOBg4IWul7ScumwKXrZA23sysGljKETU3AjKweSzWv2nzWoei/EoJRyVUESTCxaXt0y54gSKajd0+kRfmxc7YlOUmnbK0Srme2lwMVS0Yo6uUK4WuWv1S6hpCLSatpYiWNGlbEpWnjKrHJ+kkUj+lkUiNWmrWmmfMmyS3+HyReAQwBifXlCU/dySeSPuJC2g0XaSnsLnKjq0GSq5VGStSBqAEwSE/Ps19qqgW27IuCa85AVnYVW1sAdW02oSAnMwq2nPw0ZUb6215kUaQJO+dFKzCh6WZaiQAEa4hGYG/3gsOSyAgAB/AP4XhEfATnTO2igKFMwR6FPBlCPWYRYzbRNA22/nJOAYOQ8WJ0ke2r22eSJTTxtF/h+7fCQbw9syLwy/Ae20bCp2s5jp2534GAKrG/xF9RfwhakXSbpAeQXt4MDHHBTHeIDnzIw0fo3QI6CIuyRtM2TuqWOHKsnPiqst8n1GrvXM2qbms255oWPFPFM09yQuSQoAGQFSAmLa3mNE0/VpmoLU3kXaAJAE+YBdFgBOwzW0ui8a2W008hL25FDsII+FSsbiBj0kpkI8PQj4AdFQZQPdlnCdxBJIKTBHwGRaLAEOFptM8DIAERgJIaRgtgE7YjUc6a2YIRgYjaSlvUaRipIG+394Yc410xzZP2ookv24RiQAd+0eERCB8/azjSMeB340s+3IuYB1QAIOH2OSB0fm7YwyMaWlh8K/UaAB6WRdQPAMIiYjwXZITqah+l+i8Xa7Sw/zRDPO6TtPeU62XSXoIrN6XfBe1GAUW3jyadX4AHEaNBOSlxa2+XjWwq6SPGRzT2JEBJ2geAbBND5K/ShG+og4peU0YUbJQhggPCQChLc9a9wydVbgV0iFRbmUEfITW5RVnAnDHjmQAHR1Pawx0vGv8SHRcx1GOnMBWOzfmaotUmOO5yWoRflF2O6x2wkF2zDlZwhN0yU66gOE4gwCx3PklVAvGkSHfw++6UATR1FLfFnYkTx1OO4FFLsq+yOAcdkeQjyAcgMkAaATf6b/Jm3fTBBHC7L4wxOmZYL82VDSOpdUSO9D7DPQR2U/FLA8OhJ1uOzokvAOY5QAYp3T2ENkNO0eXGO44Zq0/6DKOo5gcAAAB6z+AsdejtMd/Fpz6X0CMG5aGGdrjq6dwKOEWUztGFQzpGd9jrUKizvEKyztmdnTtw+TXQ2dN6C2dqzusdZxMmdmzoGd2zosd+nX2d0zu8AFzqMd3jvn4fTqWd5zscVz4A0dlTtTMHkHqdITqeAzTq9BbmlotTbibp5bAsdCzr0AQjEyd2Tuyd6JJxA0To+dzIS/BZ7x+dMvJnVvzMhMAWrntFxy4dBgB4dwmuad1+uIFexNnAVO1XZ+Zo3thZqucg6owZuUCwZuGHnIuDJ/1fACJdJLtl0UmK61ZaOcWYgvSNX0UzBcYpRYm7IGQ52DCAxLrL5OCgY53wFFd9yD8R87N/a4OqeAXxI0p+AA85haHAkHnMyILlss1xyMpNHgE1d8qIEAS2uZd+TK/xrr3wU8THD5oTOQFsRu8UFFkiNUjRuhqdI9C+6jZIsvPBwSKMzMbevFd7wDL5S2rGq8FgMofYGUYBcP8RmXTAsejGe2ZlFHmToiW12rt1djJBwJ7WlD8x6i2FxGVAYJjq24cr1WQuoGBJV6AEADOIYMShpxNyKX8BmSTxCrdEfQFql3Q+CvSMQjhyhKukhiyEgqmtYicEdAPV1YR11o9B11Qbmh7FTwBFdprvasABN/5pFEV5i6pg86NMIwSKND4oVsX4OwuQ5CSiW1wONQVUqihEBa22QVTLwoyrmOkjFnSM7rVrJgOp7dI0PUARoLINkRF3UMijR6krEoeogw6ogw2QFbTR8xIJBR5YCDVAyaWe2mlrpcDE2SCPhUtALGGSQfymKW+CkyxMwCddyAmnpXmK9Ez0xOAXyCeZWi3wkdr3YILJ0+YrRHLCszE9NOME2VeUq2kF8ObgH+TSYBCGgNq5zqttVrgNuBKStLNpWOmVTm5HCu3I7DuwFzy3TNN5FxdTTvtABLrtFzLrL5UxtLeIjqLNZVpopNOX0eFfCdgqWu+AX7qSAZTKZCfGNK0EbwRNxoD0sO5JlQ/b1wOL/Cpaq3BddB+l3BK8Jqti0uBJ3WqrOMXSggHxF8UqA1mYE3HLQksAn13lM9dMQBHdDvyOBt1DYAecGxUETsXpYOpiANxLY5MnuSCYstU9uKJk9t1B09gyDc8qrvld0ag1dSruC9mrkVdbntl4+3Iqp7DKc9PNxjdYXrCAertXxmRER5WKQ9431LY9AHufdkhGHd/fJjB9iCBQ3WM9kGXTkU8XpoghrsmkGGHqZmfzDi+Xtl0cPXapu0Djw+CgddeFCMhWHv9qmsWyAE6QVZ9zDUszQjdA0jANuO5iNCt8lmsB/mAOHD3x6EiAh2RE0le1xnICInuIZY+F5CZTIft35qroAOvSMEXvdJZnM+meTvDVBTrUOGXo7ix2w6dV1GSCexNX5TfHE9ZROY0THv4dwDOIF13qeAWdHXVxIDkGMUo2CD3saCeLuY9L3rtFMXtLZ7DKuoobJVd7Yhu9UbKA4oXuh9xOJI1SXtCAH3qGEF3uLVV1ES9exL1dt3tOY93sY9fDpY9nErS973rO9oVn8MkME5o2XrHwIbEdxjykWobmKuoDXtmwV1HRJf3sDQAPue92yMJd+7vY9Z/MR20thUIlPv3dTxKQFnNmo8u52QFJ9vzoTPtggIhPx9vzsB9XPtY9PPtXZyhMBezVt+Zpd3ltepLP19Hu4dj3oJ9QPs4lO7MYZO7LAZXgGeAFlLmImRCf1LtvJdIKy3tPHsglT8s8WMBEt9GWpt9/bPDi2nPkeepASgOJql9nsj04g0tvkNwu5FPgVDs2guCANEH4oQoFFB7Zpp2t/wIQ7dhOZgANsQwfpuSx91nuPSpBFt9XPQYOJKQ9jiZg8SHEQ3uodinDiM5DmzeoXhyYwDlh1OOJsVWplOhY7lrIKC5oH0Y2J89JoB2gIqURU87JB2NxJzdijMx9TwD1dvvG65W4tW4oeS18ggHB4jgPYwLZykNXeTgliFrPhlcVwWRJtWkJ50Ag2MAT9moE+dD6Mc27SOr+HTBL99yDsCZvOP9TaQkQ5JmL90PGvpBmPTdvyA81R3tOWDzPNNz9JMklq0YdeuEFJKfLMZhhCS26vtTxCZvqwzIH817Vp1989ozZkyXl9KLs59Z2OIFJvrBIZvr4dJ/PsO18uEd/atQZxZud95YwYFwdhE1T5PhYtvvWC1oFBsI2tjdM/tvR6PQYZ4gp5d+6LTdOJqzdeKJwldiBMMblzqm3DNPNmewIk8MEGkoQEdhSu2SOcDBBs1TGEUFw3JG8ZnENK/oHI0gpxNE4rKYmlu+pKXtQAKytl2PFnAtT6C0ZqLX7J0Is3QgiB/db0n0efFzcgThEscUkgm1ijP4DggaXkr0FGKJFGvFqKITARBjxRJ+mQoHAfrEb7ujQdOU4OnfuBYboASQThMWhSTDyQAge4kfsJ60AQHHhPWiwQ0qKq0coA0AYQaNBtVuqBbKE1QXWi8BmXmR4HKmAEyhoPoZqlvwATXjML1tgNzJDBIhaW/UxcgIQI30Ayx5tlhp5v06m6QlVboGAAhONwAfaAFNkrGsDN9igAM0BuQ7LjUKGKMYZEKDINDSwDuxBuQE/QdqyTQf4KrQY4AOXzxNKBE6DsvlH2IeqykYQdMtNIuDdRNIH8oHokQxr0+5eKMH9lqu5AntGwq1BE8scQe6DuK2WV8ZNqS6yqBmJSTYNa0hZIX0XEEyBoJYFAaR9KXtuoR9uggsRHblfdvydpNNn5FHsNFcVnRdEAYwRdHuxdsAcIDhPoCASAcsCZvu6Jo0B9R5AobNBZod93HqttOplJ65ZvfQt1GBRsusFZsFLUuMTEvkXgsZlnUAf4n3M5JYZNEWXLvlA2nMV8cACIQzGqDF83tBkAms3Rl93YK1XgAsUECAsxIdvqUHpbOunH09wJE5Zl+EFlhMIEZdXu+xvF0RKofBFD3BDqAgk3WqVaEDFO4DTSpwEu1b2q9AIYiw6Rvgh6WgZo+Wobp1TuO4IDBVuRLcqGx4D275GC1FVc4y+ogar5RVQMnMdLjrsPBQoA7yIjdmO3vKzF0yZbnkBc7pgCKKuo4Nsj15ctVu4Ia9BLlVXRV5KmNtDZIb/A3C2kSqJFIhy9WrKp8IWgO8HGmJfqjRBltGR3AggsmwbHZW+ReSyXj2D0otwAdTnaRfdGlDkhFggSCit9fLQLDt5L0EPnPhcJ+x0gewGgAB9FtBk/vNQjaQLDgWhDF9YemkxrxluyAD4Sp90DFgyJ00veDkiSCrdIk4buiU7tTMVm30ZYKtMo6qqlxpqUNDQqhgN6+pCIx6gumvSHQVxGTXxCH0/RgrNtMKGK6gvgqbxEodAJkWCNNAcw41+yrNNTzWQRTq2u413EW5NHoV6MIegD+wjhD+LqN9iIfskpvvskK3qz8bUypN0+IwDr+q49VzhwDX+vpd+AalZ+oC9xqyGBRyVOaO8nUIDJ6l5QTIZY0BHu8A7iGnxYQYfZw03Q0vqulCQC16Z7iC9Q8iCwkkKIMmU6NTRp4eASSlW5D3YvcQsrNDKijLs6Ir2++1zzdqUup+V7ZBojM7iHxbfHYA7iCtlZFq+dQ+PalgCTD8w4mCR9AkPD7iACKQYcVMIYdZR4PFlA6gDEh7sV2BxshV1U2li6bgHEFFYfEEw/uS9cbrXlfcg4kOEaHJ8/2CwcQeojgga7pPkd0Z0kcSDggYvVagYMIpwasAY/uKNPaFwjXgKUtL+OpDJswBD74ZA2n4Z7134fIq8dRAD2pOZAJoqAjnk04doEbUg4EcV9CAbtFSIbBAKIe/JokekAPqJIDJCMwDWttEdH5ittuAYPgFVAN9CvvgDHEqgjMEbgjNUbzq7ZC99UvrHZ9eVMZa5QmR1pjQARBTs68gE3RaALIj2wbgMzYeOQexMq1e0BP9d/pg8XoFi5TwHWjh4E2jhkRrDLqo5Z4EgzJlOu/GdHVv9rYCSEq6t6lqUpq6B+zEAE4elD4kp/+7yO0ZxoUOY5wGz9gQlUpgmGgAd9EHN8KCXZwKM3SBExWjS6FbD10ZGFC/BhjcRluF8TzlAf/wIAJftNSXLmXcHWvOq3uwmRDYaZiG5swjd8MsglHPaRU0Z2is0buFtPDg6mYJVN3ZTi6/1u0FZ2AGjlkAqDE7JLMlEQyxOfoPUHgHxyFJrEmG+Ifka4eFiA0dq9S8ki0efP4SU4B+lqxRsyfBvHd5nFmjO5GkYLv1pjgcmVCbkf1DOftTMgsrkthA1ZjGcm621Y3dmehCmKJ81IiJ+KxxVIdO8DVgi9isVOj7YmiDh4YSQFIDyQi4qIpHNqO1DNvStDDoE8NunP0H+rajkWhIEL/uNWb/p/D1IPBD/4e190Iay2RUYY9nUbgDCIYqjiACqjFVIGj9UaxDgoOB4cxujxvHpZIl40v8j6tJj/vBmjJscKVQEp9kium4WgKORYp+ABgO8DWjl5MOjyAGUg10eUNugDkohZPUAjced1zcdsBWwZgNNKCNKO0rBEU+mBRZZN3+SuLUxaj3kev21UBcET0IqZj1tkHlIKx+jyx7dgGj2xUhRL2DEDt8iMVMYOaQ+iq/+wBXHjYNN3+IsfW0ShvE8WsZgsZYcJjQCAXqSfqFdoQHXVTTOnDraB3gFOoF1TaRP2xBs+t3Knaseqpej4Eiij1EgAW24adxpaRoAB4bL42FLvsDRuBDYccyjQ9uTxkc1HtpiKqAgEcC1ccf19/3qe9ScegjyAdgjSwzJdjUc3tfp3Y4mWoxI6GvnIfSyiITBNfqDLsb4pzAxGYyKRyIsiDQEJHZOAUBZIAHxKmHqqVEEFFbcAJrto+CgJprlh216FOJAUjtigOCAcEt5JPk4xjWS5qmuaSCwQ5xIEQNm2NQAYRQ2CUiZCFtKAE5JmzOgU8P6AgmClwFPGuxFiYYAX0a1ApqnLdGqEMTsAG4A/cdK9rVOKN9aMolgAlnKnHnnNTibwNroGpCEgZBGkiF+gp9yX1YgmUThQZ4FsBAGAcjHsTBfPt0+WNQSvvxn2WZ2Kt75UGBXdoUxq+L0E6ojYT85CRyGhXkiF204TTJ2ra6Qds64TPERY6JqNwcdpNvHn8gmN19CO6legGwVCu1Fxbq5fFLWLSz0TNcXmCNM1QdcKyGlBGjDimIJzRPKUw6eWMnp63HUTsmlrJ+vgn4P6J4T/wDSEFSbXudYCjZwNgCApiYIRySdYYWFJz9RzEThoO3kizQj8TvxPQow4mr55IxvFWIl6TKSbd65CD2k+MMuGbOvY41iGbOGUia6cSZUTIMo2YSHEG0SetQS2cMfhXqXmTkJKLDiibHgWKwWTIOqyAeSdqKLJHRhdG1ST7oFkdOAQ867CgnsYsUsc8SGIJ20l5YLJFuYLiWRTDBjGArpS7IjmVhIqydp+cFJjJksCwVI0qtRBcsm8rXKLDFsp/R6+uDQ+ClWoi5PZtzzPwk+can+vLBOpwnpZIOnrfOKxm+koEvRNlWWLADrWSj8CaBDx3pBD5ErBD2krTUSdWwT1ypgDCcfhDkEeTjZvtygHMoVg0juG1jsIzj9vqzjCLTzjLvubgDhF1InZLmWLWUBY5qcEwfwd6CtEYeQLJAgo0VvlAxqadTpqdkTsADdT6htWkegnU5Z1KNjMPTb4ysVC2PAndDzcCIWyI0K6q5is2m1wj1qsVoD4pD7wSRmFAcLCe6uKNuBNoIk+iBIZlCfHDQC0DNBfqEdTjdqDTrqZG1w3uAA3AD0ALWvSAHkhbTRoS+JXiYjI8xijTTf1zwxFu9S7VmUewBlE52oNjTdI1SYgUE1gNslu8pHoHt5Ht/DlHuo92qeVt8cbwThvqV9xvsITyIdgjOcMFoqUinO/AMxDVqfIR5bhzj31LxDBOl8UjbM2pd8Jzh2SFfAIftcpP5JuJmREpoBHpBg0rNWQ1SY+g9aY/NQ6jPd5XsWjXtJxNnHPtV48tRjW0eg0lQbVkksF8j5FgVadEeMjUBB69R0EMj+4StDh0LUjKwdUwK0DmDLaYvdujOCjHgCojgUf+R/wZoj6kO519GpLV7iBFj8Tpa8aAC6YkqcgS8UYygFBHEELWPkjnyOyF1wbFjLqrS+XkGr+BjNwAwmdIAGghPxO1LlAd8Clu7iFWtpGbUtNEHaRFxpHA7SLkzKhR+tYwqkjlGYiwNEaJ5bm2y0wEHKAHIDSmMCuo5NzoAN1N3QzAUEpAcVkgANlN+QDQFnA2QuZhc5R3ETGo9po/jYjo+VegoFmv468dn8HEe8+Sjo4ANmlFaekcmGp/rHqwNlQaEBLOwawZDsSmYmDdlLxR0GfEcn9AMmbZCk82af1lS4EuqV9IlT3aKbBj/tWQmRJmJo4cyRYOgeQ2brjd3CycjoQFH9R0C1qu/Hfmh0GeE3IBFAY4fd2sUeQkmaHH9OFxDjz1171Kx2oprRteZuiw6NfkE+ZkaKXGuHoWNFVr3SH0Gj9p4txEl1IADKcPtUGQ16574uGIXUFtiWmtQ97CEatw9rQTWhzm5UJjYda6e6txUb1TEEe3TvUaITmmj2kh6fqjR3JQjWAdt2sBh5SwpGn+E4mjRcTPy0r2dfAFVqYTY/LHArtuUp+IV6om8Dk155O2iExOSd/0bUUS4G9Rzfm9ZZfNHBa3ggoutr9Ad0R11QNM1louA8dFBHED6QDEzNpRl50ZKrwf/tPsQLqK8Dl3jlNouQ5INTujdbKWjmZk8xWIHxyAGdgAIzOxgbJHUwexIFzdZLc2OLRdTOCCWmRr3bEXWe4Ag2uYAKXt1AXKQij4IpDwHkEeywJJ0oi8L1lLnoeADIYJ6Ht1nqrxQZ0RyEtJZTj5zaUEBSB+jKtlkcYGzcqa9m4rpc8QuISuoG86sgFNzHzkMwkuZJ+7HC8pWwd1AU7mCwUydOAZcv+j1Ou5w2FSPJLkCKzHLorMTiqq8hFrVES/V9RPuZRJB6dBzODJYwvWBfqVXk4cg3NtT1BUFFEFCGVmYIg9pSa/xHkAPVaIaK1sUZXKdzLCDYmcQAQUcbzUuvbRpGabzH5N+UOhVbzOIEjwPecdhM5HlSYxhUjU/lMzJ0bSxABtUxykZkhJatfcc3wegA7z2kT6YFUaJKjZXEbNDlaoeAFMZtRN0AQAxxVTlk3h4zL+JxGL+IUj6CAoIa+flSj6cPTG2u4Uf2BVOf+EwJSGe58RJoox0anC13wHZztAt49ubgYimQBdk3OfuQsUf9FCAOy4hqQojfgd1pbGGfOs9UGzjMC8DKKXU5sUalC6iOfKwMrZdsWH3zBkz2iHyfhVEctloR2qRTgGTmpy9vUCbwdzazyf8xCVMgAsUYIjlaXoQqmNdBXIobQl1z8DCkPaaOCHWAmBK2D4MdFcrCYoBVHKvK0eC54C6jkdOlk71xpu1Fppo4Jg9pwBmk28KJ2jStPWSIBlkPfFzYQf9FSRYdHaSCkuktMzN2aWcJUe6jiAd3TlUdgjsJCJd3VMPhyLMzj56ebh/PrWRDmlNdc6p4K9qhNuBequh6vCCZwqWgTkAGETrUzPQfhfV4tnNuwwiZmg5QAMCM0GpA05nV4KFmFAWsCH0HgEg+2XmETYOH8M0HKCLMCUFo8SDfzO3BiLMCUUWHyHyLxIjHAg9DFUxRfVDDEDKgW8qb4ozpMdvTv8+mRZIAKRtpg/Uik1svDqLPTtWtvvG15PpAxIf4l9atWUPB7oGETyBEzAkrtGLoibW9TRZetD+FdEbSBqLUI2utb2ouDzQh0dJEfJU6Set1a+hhz5mpFKPVl4thhGq694HwUJlOtDY9OETignsTI7tLIIRP+NLlv52Iwh4i9kqOYzxs35ApL1l/zDnVNSZlRYsqS1GSSBGCilFIMGMv8MdjKcBesYOODJZ0u/CXZyI16e/xArtNHyyY7hbqZx+YKD/eGqMeCw5Ut3h8Lt2GMV4gZQIzomp+RrUwZupn06F3NSNVpjytoTpz1yFFcLPEHdznuanTgqoCEDP2gwLue42fAEtzrAY8hvoBbxeeec600iZL9Dt+2koUQJwpY+Rn6JeoHXQlLMGVpm0jo+J7VJiAHTDdTbJAaRpTPLWL1BhgZuZYl0IFXwYsqZqN0tzDxyFYR+AmVUGf0dxeepOQ+Ch6M/1IgM6JOWT5Y2hL4ybDiMrmnAcJc2oXAmmwrLspWSPNoxjpehiAiahj0yfEgZTJ1Dkp0XWP6MtzC2mqNMqLv2RUxtUbg2fzYOcT1NTQ5l9hF+gHet/ORv01gdyDnEWJH5sNhIBLaaBGEG3u+L9Nn69i5EK4oaCuhHJYkQjJbrLMqD5zsqxFKCeGcMnvkTCOMuEFNz12lx7qd8jSET+jxR4jnlBmmsunj1vTEtz4AtzwhWWEckACEEu0Ev87RCbNVAFlm8CEkUQpOOQZFue2d+nPRzxZM9pSYuoe3kkLH4e71Mhe7lUaon1wLsTYlxbLdKibL5phPO29xfl0OPvzoQmpp9ikxel7lEOgnsayY8LNZ9RhYITfUYsLPbqsLlKKzol5cu9kxZQI95dXx6/LWdKBHAr4mvLYV1EmLStFgFv3osdOYEQrPiPR9wiZett5o8AzFsoA95cq4eFcYtiwBIr+dDIrIEkorKFfV4L1u3NwEBgrGFfudGJGwrloEgr9Fda10wQXV6FbX5nRcKiq1vYroHlwrXhZxLjKxoAtFbCL5QCtYUtDor6wCiLL5bgr1jrGdvTpp9pdLGRIwgIdf5bE1OFYrVV1BkTgLFMJAabrTPubdTSlalon+blddps96FPJhzGQVDIk/y5t1cC7gnNEMr8bVYDk+vMrxC0xRjaKuo0pZNzdZe+oM7BZLHkH8rY6B1LwVYwUgVZ1L3oz+SY9KNL1eUWApcMNFlwQrkWAuAjscZ1TYEfuzpUZ6jhqdgjYQYAdZiurQQEGbcHAuQjo1rf141svTNfuMQhgKWz6sBlQsUbsCET1CdH6atAYbTyS5cZSwrUvkAsgG6Q6QtVDBSGkFBHCJVN8ba+FVsfjsKHOSQseOTKljLjrlEcaShpvqBSLB0yAHAzHTwZRGWY3FYCYPFdWcEAzLguD7VhKVIMFVz5xCxOfqcNjAUEeyiGbq9J1bLjf8cImtxmSzNEc15zMLL8+GeQtDGIkz84NA8MmfsZi4Ckz/iYS0iBeXuxYz3sTkJOjFwFRQDhE+ROkbqT5NI9jbRtMe79O2xPGF+ZXIGjjHDrYBsIdyrxhbtFc/h7jDmstTZCYpd/5GLN16b9CWEY8gt6a2Df2E5J0MYMpPGEWkbUalYQ3xXDkB3cQc/kq1dAHC+ZvJIR8qXqFteSJFgNWkFuxHlSwiCYJsIsOI6xiQxXmXgoHTQFQpawyz5cajZx0OLWMQBYCa1FP9Vcu/L2s11IkTUPAUbJXOuFQWMXkDkzatflSddvY+0NnE5+EsLMjdpaYXrQMg2s3n9GUldrSclFFMFijZ6PzRwhmF1jqGepu6QvMo6tYOKxSWHLUta1rLmHXzwP102qteJB8brco3NdVkvNdoA7SMFrve2FrJSvQoARWCRUbLirUk20jKqWPKbcZhr3kGHzDjAlVscLZC5dbkzS+l8GVaePKUbNYTbsVxAGWbWzVoF7MsdenQ2kd3mq+z4B6VuZ+p/tUDDlijZ6M0vtDKL8Ryql/Ng4AlO7AFAxUbPExujIgEphQVVX0lJT8FHdM2kb3pIRKjZedclRm9e02YYfq0SpO3xUbKjTvlKZT7PCdTLTAlRgDw9MlIdGg1uHnAZ1JPEboiHzvez2ZHsCTroNracChLzdkoScKmyfE5H4U9rskSEenMAdrUduXet9eNr8qXnRfdZvcCuGINvtflDhdaNjiPAusEryRFJtcWgLhm8Bo0BuLRvFwAOZeIWEsGtrhX1trqZEvDAZmgbJ0fTL8Dd72ddZ/r+eCLECMp6rZfrrgIDZkDShqwbHRnSCOzLUEF1nICn9agAO7zZ4fmddA/dZFERftVrNGYvrfZcdmlRkAUr5PEggAIwAGddXr2oBTrl8jTrGdfzKmYLV2FMY7r0x2ptnbwR4AjN0bRUDqc+0fwoBdZYA8lXoQmgbtmoaDOJOjIcxUbLn86+PcbRsbzrDKN50BNmggLdbyTJ7sm8bISUNHXU4jenuCbhdufzu/zHZ3mw8tkQW2iIRuS05ddIaLHyD1t0sSaTeCaMLsgyzkAJL9xYgyUyboDMt5xYLOJEEAwgl/r1H2AQIKSh13OYXTuoqXT75Q/9FFKmzKi06NcQwKtMl3uj5VqFTzcCptycIwESKG2YIuEyG4Bjd48JSOzzlZ0sTVuADpiOqAkIfyjMMxSuOCZxd+NYRDIPvKacXpGtnHq+zzUdqrfga6OZB1jgT2t998xI2p0YpGJe0cvJ66pzLQ6uUYBXmJrxOAebWtBBVmYsy8l5ONCivM1oPukebYQmjh3g3aZjDe8GZpCh1tpjnBZWf3uOLyIlVLsrLmesdrjkJEGVGqR9F1ISYTsKNj0MdHrLOSr9+mPTDlAEzDxMqAWn8aRbbYbv99ed74zjt5N3XCp18cuO2iVevADfsJp6rlr24XQ02u63LIboAx1YdQJyyOsr9mso6Y2uDRJyTOdxrR1vwfpZVOLLYkTgJLpLMMQzgPaHa4HB0M2i+qbSmFzQLiYGO2AZp9BtAGBx1NAFVErSpVeLnEg3O2j4owcGYn7huGdpA3pNudajlfh/W3tPjW7dNqCQggaK7nqwoQGcHLDTI112Q2y12XxG11Js5Jkwod1xXNHVeKPoLiOLnVvCwlTGwSo17CQS+7oDsCA3mJb/7Mhju0bCAdjebjYrrCAr8bp1OSDoUl0bJOdzd7jTaUQAjzbPofqUoz0yApJxUUCQ3ntc1xLKFQNgih1Eqfrjq0ZLbDmuzbfPxTbx+BYSixejUlWoS+6OPZOjWZ+DlbC74LzOsrF4AmtU1HnZ5VsV5UITxh5PuXlr7gmgljlwLk1KI9CUD+b/+ijWOSH/oyZa9jZyID1xOBBVjXD/wWtGabo2ajVeWshxnbbebi9ArbAWKJ+m6a6j2zd+d3RL2byLMWTAvOfj7zYBbnDL0YAFP+trYdgYrzb2g7zdiwv7cnAgvKPpOLOQ4gNUR9RLvg7IHfDg5LfA7D7cg7T7fh1yZ1NVmuxg7PBPXVD/B2bk6FV9P7dbdLrr84nwcdhlAdzdMGWk1GgBW9exOkYnmCPVbbrVaNEss9sXLp1jHZW9Q7bIQrHfY7VHZeo6bYg7B0bLb+LdoAvbbYAeiQY7svGVj/HYTbgnbY7IOsHl5nokQ2JBNr6xJeoY7bjd8nab4ineJ9YQCE7ktZ07PbYzDfbdvwH8Y7bmbabjkneQ4jbZ/+BrCV8Dcaw7EnYLDFbYM7pzCM7JGuU7scCE7anb/bfLuM9UzckRMvowAF9cR9KXrNxjaNAKEKHl0WpoENHMroA+rZvkf0Sh1uLY02770xbtTY9KqHoSz4iDRberpiqWtDuQrcZ28JAGAAeroFNxyp81HaTOVYAAyrBUeihF+p/8AFcgjmRAzrdvrJr2IbQjTvuB5qtXZDWzc67i9G6738ues87JrzDAn8geQ29FIbrxBTfHjbLCAS+xCPxsm7K5rVrLiN97dzbY3YmRObZfji9FyZB3fXVA3kLbbnbs7pbc87WtAn4ceaxAybcs7snes7ebpIARsp2l89dBAGdbnOkolE2UOO+719nK0UOLqATSCEk/eKAg1RMSzRbbAo6nC21UPfE7RAGbj1IpWNqBnlcNIwAA5708tMJJ6iaWiHuiUVruqAUaV4Qcg0XO6mFLmNxJCMt3SwKt3o/NxlPvSA8VFh07Fee0iTu0d2z4/+3F6Gd3XO7Z3QgFm2y2483SfYmAGwh06Hu0S2rO/+zztm93ogB93apa8phRRG8/u7ggfu4D3ZgMD2MiwQAwe7xK6e6eWOnWJ33Owj2y23V2WrTrYyQCs2DC5D4Ou49njOxeAnRWenXYeW52OP8W0Pofw7paGRuiBm6CAEJ7v3fdI+O9q6g22EBQAdQYLwC/wmEYfTRgiftmPKXklJDMJEAB0xGIUdgDtYZ2mO7738IM+TrOPX46+AohovZMd/wjoafKyDtWsyiT6C1c06pD1KPafQAIoyiTgSfQXr6P8WWYNn2SlT1XhMYIAAgMKW5mYA2AbJ1mxRDIs2DCQVkmwfNFGWkxo+7H2vFkebb1l+RFTmOm/A4P7Qo5Hhkm8SAIoxE7HC/ao9GN1R5aGPTeFido9GAUZ+hf9BmLXtBAW1tw8Fqhbz+C7q9+4eB5rQf2XrZnTKAI83LHIyIMSLf3Ypc5SYKotLdwLpRDgFzCfe4G2U+08AfhZSqUsPnVkhEEpaYKhJKkCdoRLHKAK2+5SRzeIg0w8+AjpC5AAsesSu+EAsVtWMsWEOuXuKl623yiu2hXavgl2yw4RGAQPwaYR3Zu8owGwmAPK2GqV2TdggQSsDNF6FWQ8hl/2qTT/2TO7PYx6ek85GJDYItXurR1bswoB/+mYBzAXc0YDhxm/OAXi2+g3S6HnTyNbS7tazzWnRQ4qnkCnC5SBgZwD+jxPY+7cqQMEoScGXJeN/2nyZySd5VJAFtKWR2YAtk5hTqd2yW6Nqsz7o4DofGghRKJhrse03gVtgnVUsnOFl8GsfXG7mPEjkPAMrIjOlaEs0OdWR+wbFkJW5GR5NHhB/bkWB4J32YS8g3KQhXFYXMTkeTqJI6utYkMoE33c3S0tW+9e2Mo50lrTBfg+eT/6K1Z6KJVFxxrQFdQz+0QBHm3L6Ru5b2ju08BiEVnQChzsBtNVeWuAKUPlGGjQKh1UPj+3tBah2+3E46N3Q29b3EAM0Ouuslq5m+0Pl+2UPYSXwBKh0f3McjUOnWRb2yo0T6Gh4/qwQOMP1oJMP0a9lby2J0OQ4q0z5h1f2aLcsOzsGz62EPgnhh9O2mh1aYJh0UP9hx5LjWbMPjh/nQXrecOsAJcO0gNcP6hyMO7h0oKHh20PdsTMOuh+UP5hxAP2AMsOW9NeA3sXowXqJCPQQAf3iBwHRV8Yp3k+wYOTOwMP2fb8O1h8x3Gh1sP3QMLVmB09xWB6EAA+63UqHccKgRwV2QR4cOCmTKgrqCiOQ2DCPVOJx3lGAiP+B1rQKwCiOWJSSO/e6EBpGNiOrh1um8R1b2mh0SPR7vyOyRxSOI6sH3shSlKvRbl6X7EL6Q7LyOiB0IOafSMIV2/+W6h2KONh6MOs6ClLraZkQFuhIOP4H3NRxOi4Z5W2tNGVHr1CaPBraY1w3QFUOD+70PTkOUJeh0sPuR1CAQJNf2KAJf2H+2V2RIxx2euReqy5arUu+BcPVhz1HxR1sOreTqTmu2s2mQW12SqjGPiBZT3MwNT2Go59mmo36djm+VamQgq7lS/5384MQjSohHwoe9J7xBlDrTG6K8FPSVRtonIMkO98BwwIb4wCtuS9htTGNzVf7fcCTLUfDFaPSLLWrbgDWEtByJ7HNrW243YFiEndq3MRiOwDeS3Oc/I7rAC8PBpaA0qByVSdu4d3vgJz2me/t32e1WS2e4ryzeVNGm8CkwLuzz37O9d2YiLd2Fuyh8FhiDBRWjvG4JOVLdODPo5CtgZSXgqKnni1AU+NDraMKcBuQO+gD1YTiCldfs1KS8JAzEfKTgOChzKKr2vGdU2VYDWGa0jEwveA2gTdFw3rfBX61SQU9SJIKyvqCmGVoHAOVMKm3W0Lir6C6N5JUWgD90Y5sHo6pgxo4+rZNKMBJx6Y2nUEZycqfLdCwPZdsSa0JX0/hBgSarXpxyiioAE8a3xzKL8tU9hJ6+PKTKyi37G8Pme0WIAW0VIBJCHwIgWBMG0coFmmYcsWGqRmjNiDhOOJ0G0C62DjJexQBpexnXG1rX7G7dCcMh+7RhIGZG6AMCclSSnTLXGHF8uB30MoJukxbn+zIiKj46fs0I+C8TnbyeXWSyePkuhbZgmw1D3rJ+ghPJ4B1x+wLxX5OxiOSAJq0sT5ja07JPnSf10MdnNsJ6ycoQsdix3J+gg+EntShG5HIuANIxWkYhgk4iOBpGKNBpe8rGIe7sDOGCHhde5d2u22W3MwduY3+zIKb7jiz0XMZUK4AVOPgCV7QXPrrPSTtE3ZjdqQuAZaTpXmMhVEoHTxRlIlDcCjgpy79QDLTo1bqn74h3dArgGtZVwJ5Odpi2cwo6wX8wDlCltYtcEBHwCWJ1f6aLZnDxRWZd2LOhTyhKJ04sFVnSW14nLp1EI6x2ZUjjcoaH+H7x8YPgQ7QE1pNqAjraWw193pUqSmzaMBjOdpHoW743AauoEImw9trMH7SltbeLR45+JnQAwjutE2OwSoobigWPSBJ7WOy225shbtPBXkI/B/AKcw+J4QBgSWC3Ng46p99dYISzZV9nsACW3bANXNvF68ftrbYxQi+aBzTi3Iw2tNtvQnnDxV+Yeq+bIOI1AnwrRJOivR/tdqb91qWoAj7nkdqJGfMnILDE39SIhOD4KAs4E4SVlU6/7krSscyVNMO4R1x3duzuPlYoxKFcZAAAAD6HixYAP0LNUxcO2cOzjwBIMLOgmzy70k482fbj94ArIr7HnEBxmwMJUCYZrAD2zthvuzl7uhE8Ds7iCOdIPV2d0s+OeOz77E2z5OdRzhNmWx9Oe4cQ+jVql2fZz1paYdi2fvAJJkFz4/Fpzt2cezjc3Nt9H0+z5RhOdjyGBz3RlFzv2cxAdpEoPAudGM2OdAIeOdDixOfnggufxEl3QVzyOeI4piUJcTOdJzyueFzrcend5WJlz1Eiq4rOfTz4KvVz55u1zlQK+z9dUnj/3gwO/6ghElmjdz04C9zhSX2zqeejz77FU4nJCwMaRhR154muzlFxlsJOJnzgefTz8uf5zleeezmuf6Vuud6huHt695uMVtr7F+16Xs2h9MgK4494CMlud5t0Ckdz6ee8t9tGwMbKUfweGLVMO5BCMTuPeMeB7eYGEAC1pBfS08UApG9BdyUY8cqcqaPYLh9lmT5ue846ThCt/AAitw8D4L2zAVoYMOvIYhdY47+iDT0DEQL1papt2BgsJQwbeMWUyf0PQBIMXHG5KrhjezrjjN5EXvitlhJvR0AnQL1nuwLkOHpzwU0CLgjjCLvaB6ALOi+uiSQbztThcMdXuQq8CzOzt2f9zz+iCmgji6Lg9kGLpqfCPT8fK9mgDpBWYBHz12eRz9xcJzl+eWLj+f6hBOI2LyRd2L5Ngh4OXu/dlXtNIFxc0ANxfpzzxd9z7xdok3xfxxQ3gBLguxBL4DjS1C8e89gsNugeLswAO+jwPBvY3QWBjnj7ntZL6Hi398UD5Ll/kSL1Jc/z6RdcMCXsCM97tcL3RmtQbGdpdlBdVLwpcOgWBgvUP2tgL6IA8LnKcdIhJQ2z2Gizz5Rdua6pdmYfUQYtiXIXtj6BAF6EU7EjQRQs3VKSTm+tO1zSh3d8lCZyuVN1KTzXHfDX3ZRs2CQmJrCm9zF0JzTmlpjvUc9R+cfPkp7UzEfDD4IHru5j8hNXOAse8el6pGUcT2ZEEGDnhzMZgEWKOvYBigEcRjaiuCYBUIX4mMoYOkcKO87VjkFXAo7XMtRkhEG+bUEQ5SvSLQ84FWAeqHsQgihUIPSjhOWBQBRhvOCByYV8EXoUQWUYXa9+9B9m2cDzw7LHYW3A5oryxURQPYD2SAyAnAoyBNQlCHsQ24G4rgyD4r6FgowwMwkr3vgt5sleHQEzMcgSzPUr/XACWYsnUruOB8odFdsr+yQ31dld8rnFc3gwVdF8AlcirmVAIUeGDBI9vOyRqVfQT1SFUr14tm0wPBSKPRYsrjFcmQDldcrlaGzgKDCNAZqFargVdCrmFdEr9yhirvIBd5p5RKZkiKsuKYVCgWVevFsaO+WspaJThKD0riUTZaAu2qEgyBU7D1dYr/lc6r2S3ihKIHwMYVf+Ww1dxBvvMygJTMciGJkXuRVfZDUzADVWhKOfSGw1e2KNVPWwihJm+mzlE8GQrgxP2tOr0fEKmvhaREuRr4Qg70t6jlAo8HV4iRD0kWXzuStcCVl+VnYExrRazL2tFu39U1Y7u3iQECVNyv5eQYsHZ6zyhpSFtKNnl5Y5RqjGSXe9Md2i+5dPAR5cgrpmbxj+VJa9vMWc0VqGsrzFdernVc0+satRsu9fi+0iBXUR9eOr9lecr9iHcDO8Evr9iFvr7xhiN/n0dxe9f50X9dqrjVcZr7VegbuxyWLj9dDCaDc/r1VeYrgDdjQt1fprkDcGQMDcob29dobr9fXUKwCprnFeer7FcCrwjdr5oAPoJv8McgbGu0erybXLgdKnrziXnr/3t5i/e05jqquoRgq7LajnO8LEYQ8b6lWID68DV56xkchE6AD7BzFkqTvN6Z2gAKbtvOCIFTfBIoNcgSFTeuvEgD955ov+dWdsIdONe+M1/OOiUjdB/eFwRJp0W6Z2lFOE9Te6M9tF2bimg2bpoQgSJzeVpnTd6b9zf6+Kl2KiYzfibrAtiiMN3mbuTfirpTd3wLb31+bgD2btOUREyLf75mLdhbildbmhgR75kUDabiK16biLesYBLcGb6PimXCiYcYXzDH28zeKMi0LQFCma3Uc0cfrCCY/luNRdcUzBqiiqBcAYB2AhvdenloN2HrqGpNK2namz4JFkqGn1nYJkIdD25fECrjfkjnjfEI7IU9biX3o+jTdKbgbeUacz6WhEbeDD/VOPZ8bfb67ddTbuMoILE5Rzbhzdqbm5CDb5bfQFVbc4j0Ud3L/QdgGrbe07XjfTb0jd9bijPJbrwCLbgGBDyrwDnbkUfvtyCObbybcpBXbdg7U2fFr17fHbpbcfbj+Acb7ZPXbh5f/bxpWPby73zb2zfv6N7dDblbcbFjn0Ihv7fmbnbecIBHcHb2Lfebk7cQ7r7c/Dy7djbmHcXruHcPbvbfoaAndJb1zc3mlHdg797fDbjHe4jq7csDzEcTbnHcA7vHe074He6bmUBE78Hds7qHfY72nf3bwHe9bxHfPb6vHZbwLfRblndo7s7fs78ndnrynfcb3nfw7gXey72LcK7tLdK7nEDE7sXejbjXdc7m7fU76Xezb/StI7xnckAA3cOgXLfG70Xfo78Xea7nneS73HeCWXXfo+kHcO71LdO79LfK707cOoUne8On7cbbj3e3b3VBS7jiCWb3j1ib9bRm43OShsu3fuSoncw+9PfQQR3dZAZ3ckAVwnGEuXfuSgbdZ74vfd5iaMu6UvevY9PcV73PdRb6vdaE2vdPKRvelEk1eCITPc17w7dxbnLfB743dl72Let7ovfyb47cw+/3ed7rQn+7+vf57wvdlEoXeg7/vf0bi7OGiwBzMbzKuFR7Kt3ZtbcPZvEebb4tmDSG3u9d61P29oTc/56GMx4kfAnOikv87PYmIj3eCXmm5unt17hFalFaMiS8l/qH6y6MqacZPGdwwahlHt1KlvsALphZhrrAT+YA+L5fYb7a/6Ch1TKXCKE1p3wHYZw47piNFvzefS0QfIsNaAX4SQjczUpOmR1hSQtyFSZ2pEGnkBm1B/S8nNi8TNDwMA/D40YPM96rWKypDjkmDOu4RtasZwDOvSRlg82DjYgZ19tGcHpg/l7p5ScHilB9yShR6b3CMMs9WwTD32IOhNb5V0Ah4QW/NqsttrbfVybyYF6RPQokgyivGykg/Act2qEAvQzkSGxiGtvIsXfNB72WjzomQRMAHlJEEETerjlqDBIxTe0opvPtoxw/27+8R6b+l5bfOmXR8gLeG75DiET1Mi0D33OkG+UltU0zfKMIvslZgum+YNDAfQKTcOYxFcdGRrhshbSPqQog+XM+eCowDs6gI58BliaGNfTrkxKZ+htF9l+ZWounKSvYOMO9zXUD+ARBvcOMSb/DQBNYBRQj+OKqsFmbYYqKpswwFWDu5jQBruFEkxtGVA1i+vhMqLg/zwdI/ba5Q/ktkFXUbeMLTV2Jk986GCHSYg8UYDLVFazA8bQfnbd9yD0Zehamiby80osc0eNrftwiwZ2z4QKITX773NBgEyqKH2VthN15SgHqy5qH31EaH0VJ3EvFEKQpPG7rk8v92lptRq7czDqR4dbju/dRslKUr0vwnA6yADQR9/efiWBh2N+OgE6qXXsmwoHtz4B3s63vajQcA/cAQqq34QRef0RB5DiwwBonqAAwHhg/yS3WBEn8KTUHlhLYn7gC4n8oQ1iwk/s64k/wHqg/vgTHE1ikE8kHrJjrH65EInmEAIH8Kbs4zsVQQBXGCmjk8UnpQPtIyJdoALgBt8QzbAAYBPtiPQBwcKtVD40U9kn4cVMn+g8MYrphSMmLhinpB58nvuTMHlZBRGGLhGnsHTsHpTe4Rs09lqik/8H2Lc2nqxfeMC09sHgQ8gSJ0/mn+08iH+fckAT09lq/RVBQcPeY737fR7/fdZSMEBRsjTshshE+Kn9TDEqZE/7juE9yaPjuth9TUUnhd3NGWDfGQUEjPLxbs+dpjtohq9f4IdM9anvm7HSNAr8tp1rw97tvkmKnChAVjOpny8mln9jjxboLdCoedEsstf5FEwUXwICE/9bm5AIn7Pd0AMlRDn7vejnik/N7rTeDnik/+7ic+97HwT8HqvdJMTg8DApAAfEu5lFNSADWDW+6X4XPl/ECE+gTCbUpGe6AaAAThhB33BZ+XpqYwLKRGrsFsWp4JGmwMINjnhzHSR7zcvn3viOb5nc4gD8+Br/5EV798+zn30+AX/HFf61Y8zd5487Dx0Bj01goAnz1rPJnjFNg6IAQnh0+98Tg+Sn/ccILxg+DgAU/UHzk9yy8fu7pdTjQXj7nVhb96thkFW4RlC/Gn3Rl8Hmi+vn60+eR1C95AXg9MX+i8M7p7Aenti9g6UQ8ygXCNRn1ghg6O/exRp48KYqtZ2apQB5Hq/2dGv8AYTsgPEz9sMRFAS+jHmgDFAIcV7QTg+wFFS+pInm49HtdzY0IppcAL/TfUCsBugeySRGp6DsL2AqGt1WWwOi2i8Ld3NugZkBhgSFJRGVrcLNhjdzcwByHywwtm7zjfR77nOyj0Ye2Fu3v2Fh4mCMWw/1b/robBBxa7McqjNwOQJLgUBiObMgLiCT1qObf4bJ76RCUD+GR/F2aZu1CALyBFduPmubacZJgx37jGR68C9V7RApkZ/EHbQQWe4SGKJEFM26h9LkqdReRFUVX9WDed8zhMd2K8h2eK+MIxftTrr2MxX7cYWEQa+ad2/H1MaGP+eptHB0OxvRhQcALXmgsVlZxj4ahGjqevgTzx0ajs7XovAA0D3nRyABFVoSfJXt6gBFgFOObVpFQaF3V2BcOoBqKTtpFoWAcqDK/NQKDBgKGUR2BNS/9CvaA2/U7ZSdq4sqJqTuQfLIAK5lI3la2ADdtuwIvWqTspGmLmw36pgg3r+QVQJqnJtpgxlQBqWhAMcDrkKnCyzTO3jTLdojHxK9cHajCSVWvUe7uid+40e6LMAyB7AZAjk+SY7NCbYun3P3gIJImBOVUbmAjnYfTDu6/jk/2oQ75uXu7i3cPLpZfE36NTUj7m/C9169elUPelunBBC30kfc77ZOHXpNtMBJDKL0CW+FDjp15lda/O6l7aRju6UK3gUfK322knoNW8Ffb4Ca3nYAdO13DNo3W+B6lXeJmLvhG3mUei382/i3+4eS34tVrX9IB2N/W/QFQW9+X6HfC3i9du3kgKZEAEW1t+YkFdm2/ndLq8tqenZzbW6g5Xy0DVOi7eR73fcBXw69BX4hFimzLBdcK8ur4k6+ObDYB2BC69Sd66/lCO69T2bkyPXltZLoqTvrkd6+6IKTvfXnxl/X8nAA328uFB4G9piMG/VMCG9Q3xzYw3vn5w3t0YI30phI3tZKo3xzaVFxYN4lxsDY35qC43mYLHl1KMdbk72vXMHxe3/Su83iaBHYvH1B3zbeBXwAeEjsJlUd3719X8a9xXw+As+z29a39H12BYOgH31WrfDiPdDDqPch3/3sn3wPsSj8+9o0NflX3y3YTX2+9W3+hDo+nW++3y8kv3gO9v3kM+f3xW83bn++Ujs+8p3ruiAP9ObAPm+9BQO+9c3h+/6V228rX53UwPxMxwPjncU7r+/kj5B8R1VB+hjg+AYP/q+r4Qa+4P7e/4P5Cs+3jdXQP/2+kPl29K36h+GjzVIX3hh/X3ga+gPvO+jkStiXevFjx3gvcAGpEHNXiO+wVoB+ZzEB84PlYdH3rO+m3nO/xj95VRXt9E/ejB/F3t6iUAYUdk7jO+c7xB8i37O+n3sYehssu/ACWfd2BSu+HgRx8pXmu/zwVx9vUJ69Lozx9wwJu8fX3x+0ANu+f0Du/CEQJ+A3woOBPkG+IAfu+lMQe9ltwJ8vWxJ/VMGLnJPye9KgZG+atwJ9z3gYPkAWfdc8Je/kAFe+tAWM1nZxmnL77SXgmTPGXLkCOb7jdPp3j++Z3yh8m34liYPlR/YPnQqH7t5fk14HhoLPn7B0QlyCaE3UOzJxgg190Z2JlROX1fksPIFCXhxVUQutFWAEO5zw/JqJPDe7gDje7/JrSD8JqgMnrcy/IaxH0i9z0jZ4S0MGvAANUByUC5Oocp1PaoLgcFZw8pW56iTo8OvBuaDpicHjEalB8FvM4JEWGEeeT4IFR3SwwS17P2Q0s6T2rRoQapGxrNBPPwF+tEFpwBFNgCKwQ8D8BYg25D2QtLp8gBi7WuND647MS9YNya+jF36S3X1417fd5Vih+WP0O/Z3ox9mK0mvdPvrsU1p0kxthFT01pMSObaJ+xP3ADxP9sNnYSl8IDftkpkWzPt2V4PDnRXyqxlBR4HoTNA1tn6X+SkNptOw1eClmN8/MX1vAXUNcxrwUC6bRKhOxHh+1ZVzuEKeAZZi41S+wPZoeJJ1+3MuOSSSoPsxhxINWPrPvZSWWyZxcB//D4NTx6/aUhzCWjj8TMSv7fS2BQFjgYnMOhWk48widX7HQgJnEq2aNfIEV9ByMlVwiVyMluTdG11k2MaAFHKX2XWPyvtJslIWhsBbXgoUxkRQ1e1aupYLxNkbBqybo3UCyHKGB5N81+XGH6v2vov0X7wGteQYGsfBx7IUxm+pb/fptk8UsC373ZihPkizPNtxw83Tw35x5SzwF8GkzgAJG+vMqgskIrHbvBg9ZhjC8MY2rWZSzB53WRNG8J8CnMBmT2q18GOgw9cABKPn6svk5jg3sKkOdzZ+eVh9XDhsMoo3ouW0F35Bgr99A01Z4DdJr9N0A4bPkg88vhxmW31EzBNr7lrvrNup+4Jhp/rbpp9kv7+8Uvqk1CT15f8bw5sUJ0/dyQjLOHOZSKCW78RzHsCjyXvW9nYIHXlqn0DD3xG8ZP6e9/wOyhg4tteWVCYPfiG/g+iDcOKvXqcOmd8SnladTceItVxiOwJ7vlgAHvirUkz60FckduMjRhS/Q8Vg/yqVA+4dAfgjVC9/PlSDXvKMjDwSgceIqYkGfU3RlbbOmbUoKHWv73xvOv2BNaKNPZkrW924QFAu4f58qxnASzqOaLN3+tI+oboLGMfn0SiX2GmB5jvGtkdM4TB6IfXRuD/2WLZf7QaR7cf8RBPBljKE0O8AHQN0gQ9fVyLShcmobyJ10qcTmYT63hZBbWu4LJ+tf76N+Ktmmzw4HcQHq/5sYAYFeKfkqmF68eXBT/JwNKpbXOqvv3P0wcN5x9VuzXu7tJtll993/d8D3w99VZ6G/YfjACZPuUBLaxlNuYFlOXv8G3MTCuk9V9anxhrI7tiIrqY5cGuTFewAf97Y93q6PNIS4pulrMFvNy0V5etCYMEOvS3Ysd0ux6w2ZKgYqu5wpErdf3WinNy5BAzNgsTB1As+659/T8vIdx1FofgP3e9Yf9J/1fnT98cYg0xcYcBMr3uEg36FSVGiVJa0XpfDj2xg0LujhMMLgC4RhnTGP0ydg78lGXmgxCWhW2JhsXh9IPkD9PcMD86P1h/W3x+/lf0G+VfuJ/Vf6HiZeapgPfkkCC0N7/A0JUCvfnC31Nu/ddvoE+dvvbcnAQU0A/ku/A/nEBwLyOdHY/bNbgJkKQ/ttjQ/qx9aP7l/2wsYcC9x4dlJ5CutX6ugT38TM4f4T+joMz+o/5j9Vf1j/thqKtycV8saP5p+BX7n/WF0p+oJ8p/WnSj3MgHUn0ghW1n6tje6p4l8E1/y/K/j3fUviD95jyl0tRr4u60MRRKgLw3zkSEt2pnzfe/QgmBBdYkSOGEDvoPTsz+jYKWV8bcSISyvij92MCpx4fpRvOSxbc6XN5L6hpk8QQb0oY4WA6WHTgcT1d8O/Q8RXF8nLv5n4vvo1YujZsc/0O/m/rp+W/95eCbvLXFlyPxygc9vDq6K+y8Hjehbv8+0o6fd97610MX2lGJbxv/275v9G71v8cXivcd/uuDto7v8D/sEDtokf8x9vLc2/hzS+n6v/KMVcl1/kLdWiLLeB7vPct/9w8ygMlSGHsddXgJXsiuO/ePNsRbNQYJ9J8ZCiirQwjrkYJ8H91Adl9uYWnkC/9IPZ9tCof5V+EhQCD8KDDyKE6D2OGDn2OV3xiRIYMd3WIBOD1b5a/EXu3V2Om5TD1X/XfhMJx5REn9NyiVJHGUF/UMhdxA/tU9UPXAIZ3luG/0gFkB5fa8XVUovX5Be3kAPF3xW82HPGSNHYWcPewJiAPdPAfNuJH93UjMBVWZ2RzMfSXqdCIEiQCM3NA8ScDbPWWgLpQDAN5gnwAIAkgDx/zH/ZzcpzwX3EIJfTzJUcho7IBYJRbETTU63RBFOkggrYtV/fxj3bht5Jzb/eXcV/wb3Gc90T2L3aCB5z3EbKgCA904A/QCXN04vUQCHG313TQD89wsAz88jtx/PNQDe+BMAqfcrAJb/dfNxAMHPJys9hwF/E2Ad/wB7Pf8uRxiIMqBgnyjZR7ETYCCApB5wuAgYfC8uAHCAocVIgPDnfrAj/wf/LWg2O1ywL/U9yWYaH2AkgKHFfnt5Jzf/WQBYGHFPZht/gFGAG+d6VnzwAqBn53M4DjA+6FIAKoDpGA4wHjQhwRbrexxCgMNPJfctf0NFFrAc/1TNPhpDfxyrY38sd2j3Gx8QrwdJW+A6HyK4XEFYezQHLa8XIBldVykPezhRHNt8ByEHXVJA9R09bV87t3xoeEZRhUS+RAlA/x4dNUt7Og1LKH5E7zUEaMk/pXEfeMVyB3F6Dp0YpQUWO+E0KS5PZFhnRxRHYaAJoAAtW7BGK2nMM4cfRw+HP4CMSH9HA/t7/xyAn0d3RxrQdIwvR1dpN0c8FgP7VK9SAG9BUY50jGCffftgx18HXLEv1iMpIFhEeEFSHoM8B0gHThlBUk9qCtJ99iCxff9OGXMTNOlzvD5vREDP0V9xbzAPS0IgQnByAGP/Yt9aOB4ge2cQQP6FA/9m6CtAPBB4cHqoPpFAkENLctY/awKZBD0G0B5bGi8FWnL6Io1dJzpUPYN8CGnyVykGFx8YZm9jkzQfLCdySReAjICVFi2/S0BhC31uRTYtpwziV+pEW300TeYCKgYDUsBMBx0PPdRz3Xm7Ecpe4U1A/ED2AF5HKCU+NgvJZ3VNgNj3OQorVzX0cHplZzi0e0C9hnnkECwyCw7EULtcQTfhZMtP4QXRdllXXjEHSYDXi0ZnLZV3ZlixZVAckHaxNRxFoAclCN1PgA4uM4Y6PCqQVWpzQJCHIvs82Co+DQJ0QlH2bqgUX1ffTKM720R/eGB11X5/KeUzhCUCW6gxQIWApV08UX9DObZlAMD/Uf4FH0rYHsDgSX7AtQQ072+3Rp8LH2NvGx8JrXLYVsCiODv3XkdlANRSHVBeJSbAu+AXXW7scJxOpARgS8kFwJNgbcD6TRHLVfFCz3M3I8Dr6B3A2bY1BGHAnYC/W2UAytdhCCvA3glA3GxPJ/RhEFfAk2d3wPKcUgBPgO/A3UCnvGBoaKgFHXFAICD9gi4AIECbuzAgt8DMgN9HLwB+e1ggn8D4INOHP2kkIKn/cCCbgLdcLICWQOSA6DtkIIggzbVD+1DKPodja0Ig7CDgIJIgvCgL+xggzCC4IJwg6iDUQIIg+iCUIMYg+EDrZTgbIgBAIMogyCCpYCQeZiCboCpLG1YsIM/DHbtV8ArbcTxxIE15Gt1GF26IWEDOGWpAiaBaQN0CYpcaQK4g44VJYVEgvZVxIIJA+i5YgIUlM3Ez/05A7IDuQLK7APkTYGI7dDl4VBhOIrU8CElQZUCM3VVA3j9ZgDj7ZqBR+2wgMxV8aECWey1cJWe8N4C/wOJEBNIEIJIAA/tWkCopGglgsCbIYUw42yGEc0dshQTYSHF2r34lFS9K9w+TZrEywJlAuqAvyGiASxwLvHELAuAj9T0LCkAkxwheH9910z/facCAP1nAmUdnwKPgYv8Dmyt/Mv91+2eA/tA6oMZRCRB9QAQjDwBSIRudLEBgkRx/EzMqgEGg0zMRoOpAHHBx53KEEzNxoPiAGLhhrw+5Sel/QMuMAXcJ7CJCNgCBP0i+GwBZwAs3aQdOMHBPKXUsEGqAQ6COQEOg8aDtw2mgmCcxQ34Ad1QBYQIhLJNmPBUReMC15FtLb8cKS2JAH9NGALDwX5A6oPg6dVEv9wgAqLcB1wWTNe8lDn3XOQDCnW55B4oD1Hp3A3EscSOxPRgrqC6gsINeoN2A0x9372qg0l9jbx+ggEdTDBmQR4p0fSGg9IkdEiFQRGDuoJRg7IY0YPgfQD8sYKWg3jcQBShg02dpVyJglSQSYKRgx2FyYLCfdR9BgNDPZp9sYIR/e2g2ikZgi1dJoJcfYmDlGFJg5GDaYMpg8h9zdyA/ckc+YN5/VQkGYMu9C6DZoLhgsWCT2DZgnqCpYK5g/98d9xqgpW8FYPpgvIpoYNt3XRkmf0e3K6gfoJnYF5FqgmjHJX85YL9aWmCJRylwFzN86FaAdoAugAmIYNJ2EHPPR2FfcDPPJ/QaHmvPV7tdmDO4JBpk2QBg7AsLm2NgvGDTYOQrIaCLYIF3K2CdYImPQ+4vhwL/f3sjYJ2iN2CMfTaALE9ugHXApEAu5AcwI9RPAg3PYODaCDmAI1dw4MQaXBFpUUdhWODBYJVg0zMk4KB3FOCHwM5gtODpakV/bmCEHxpgzuD6oLGHHOCtoPdg/OCvYKLgyGAE2lPDcuDhgHZsAqx64LDwWuCM8SNXRuDeDRNgoWCzoL7YYjNk4OtgszY4W0IjMh91d1N/R2Ds4Ndg0eC84M9gwuCfYOngsuDEMArg+eCTgEXg+GB7M2TZVeClgAmgpuChvxbgmaDkfR3g9uC94O7gv8B7YL7g6mDaoOdgwkdz4PnAS+CC4O9gtFIp4JCiO+DNADngkODn4NrggFl34O3AeIAMBSdWI+Uv32THEFlzewdg429J4OmUBeEGoIjySD9rf0+XT1FHkjonZikCAHzKCG9uQHSCSHEhKE54R0EcUUtVFPhKqT1DF4RkwUtjJdkI7WyEbkBzGU9fdcA/mHUoToBt8VvkSRCO+A4ADQtHAyqPP1t7Kx3EH15jNnV7F4RVQLb7RhDHkmYQjcEGJiMZbz5JYRZoYjMnPzxg1Mx6C1BwIsEClQQArlQB1g9AL/kltlxlKHFn8EnMGQBvQ0mGJhC+gBYQ3hYocSiMdEkvWl9xK6D9/BO0Nz5Y4GbFS084MFijNodbW0T/UpNYfmHKC28O4m7UTHYB2lwBSbN8AUxiMIp11yW/XaDfNkYUSXhQPRt0MUDk7zn1I6BM/xSjUGCN71VTZo0so1MRJjd5uDN7drtiEJlHUhDtHz43RqDS/16fH3cYWFUucElks2LPI4ceuSMqSgBrjzFAmwRXhx65UV5o3jzMKfRbqECIO2U9CGyGCE8hfwmQ8pEhL12YFrU8zz5HBTsmOwP3OtdGETY7CU9N5w5HV0DQQF5HHq9FOwOQvM9TOxV+PoBVkMrYJ6hsiVqAdZDUoLv3bZDsPjLAF5DHO12YN4DsiSuQ/ZCIz0OQwLsUV149G0ceuUqUYm8jGx60bT9E+3TmICAWzzH5eKE4UILPCh1DcHFeJFDyTERtVFDer0i6fi0sUL7kCUA2AFxQxTt5JkYRKNlQIAEAO9oUUPwAvZDcEWehINZ4oXU1JckOc12DeEsTo0YOMW8Bsl7tdrcfjxvbKGpWwOBHQeVHb1LdUBCDYJu3dpD5wPiRGfpHkN1oNq8nzQYQckwPkMOQ3ZD4UJuQ7D47kMobbb8hUE5HNUchBzVQtFCNUM3gO5D1aF78OVDLQGeQtE80TzeQ5VCtkMOQ75DrUPZ1PjU/kNWAgFC0RyBQwZCQUNU7PiUIUOt8GC98JDFvFrExBzklKLt5UNvAwHZOMiAgWKNAUIRQxABTUIKZBVDSrxKQbKFxXhjQj1D0UOZQ45De9jWQ5KClUL7kHFDfkFjQ1a0E0NzQxVCNkIzgYlC/TyLQjNDyUNLQp5CI0I6vKlCaUJyhdNC+OypQplCcoSE7LP9dJVyqAl8oA1/fTZtxUMxgtpCfYPxQpHBwPy6Qnp8KETBQ7gtlAEOuU8gbKTBba98Bv33JdrcXjgDpK99mgCjZCJ9+8G0zNlV2AAuNS0szECfQYg02+yAmD0c+ZytAeqg4DEpAF35+UG5Ufr9XaQPQ0EAUjQfQ19w3MVUzQS1/lVZQAK1yQFydHfZP4hHQJ9Dm4A0zXa5g8y4LQekiujgMN0Aj5UscBAA6SQwAPZEMAAQwu9lvYjeTcpxIkz3QpOkX0Lq1HRFrMxPQhtARmB3gel4ynBWgL9CsVSlwU9BZNFo/ExVwGiQUEcAKCE/QiZFQOB3gB9DEcTozEh04TmRQnKF70KAwyRD90KP9AYA2MM/iIGldHgow0NFRMKow3IMkASIISkACS3NBCRAhMJ0RUP5czFoQZOl9PVYsbMFL83ktQcA8/FlCYTCmJl/UQzDno1Ewy1VPq0VQYyx8+AmRFI0AgAkwh9CroOIpVekCu07tEWFfoDYyNyV0nSLBVa0WzX5vAKBxEXEPY78u5S63ZBN5C1odTF9xB2xfMIpbpF6bHBcC0IDpXCM0hFiQ8pD3aD8obQsBkmpAGe1VmzKgyuFB0Mzg8kdWwwt/KdDaX2B4be1H5TwDPBkOo2HQ2WDjb0Kwr30uIC2VdiQm/BtfEYJdTDprUaMyA2nAImcWEDObOEoa33QLJE01G0rdLqsrYlMYSWA/b1VrMTcPAywAAGB3E3rEYkExMzUmAIV82niFRsB4zCkwMpdxEFygBAcH41K9a1AYgUk/EwwFsIMnBLQwRW6FY64wHQUzHaMazxJnf0VPxggeBXwd0JRNduNkUgEvObDxHGYnKIRQC0mTeW85qyqUebImG1adA7xTyk+w4NBvwUBw3DDwv31zMHC2Awhw7ARUElVrMpsKG172BWAPACvGHWtcQAsPEIlSKFQbDiAr/VarbZc7x0zBc/0VOUv9U/0DsPew9DlqPw+wrIIo2RJwtUkpM0OjfsdKcIjdanCTj2RwqAA7AiP9cU4r/XSvN6h2kUZwq/05I2lDEWB6IH0QVmAAcO4g81tTj0iED6B1rTVaaA1agl9/AQB70Upw0xtrthAgcLMWGmwKVLEXjwNiapgo2SYNLEl2ww1wzKAOADCwHaJQJyXAFI0o2XMnM8wgFCeQRS16bBrgu6t1GmsQJMN0yH1wuCRkNCjZaQNpeGTQA4odvxnsIhZpWhWffvAQT1PrH3VZI2keYK5M5Sl1KNk1pzDiUyMl8TZCSS98tUD1dXC/8S6DdYNHYXjYZfNb83kbJd8qyF/lQ1I4M2+cK6YnYG2jEAhS4lboATD50SAiBiN4RhOwzFVcAAGVcQVQ5D+SehDQUjyPXgUS3FVzN0AD1QsIdrVoo3RYHPCxgitLb4hoZ0aLKy1FMOYLGyhJFCJ+HBBVKCkvU/0F8N4w2WBwhi9jR1RumyshAkFnPDZrcHMWQk+PVgklsQj/BsDL+jPcIkoMrTNOd5kZsy6NLP99hSwTGp8sqwqgodCcRzqQa/VdaS7ZdOZH1SxzSdDKEKagnpD3rz6/eZpBgVXlSiJ9p2lMAD0p4H2lefBFpBhzNpVJvm+IVdlpaSAYBN9kClOlGfA+gA1IFAjbMDQIpAAw1SSMBtBdQhEYVqRhhFXZIAYvUXS0c9FECLdtA7RYwCfAQFNbc38da5g0oBRCLbUaPgoI87piHVaIEWUxgEljenU5sFQI9AjCCIqkOxDGJEYGGf5jOEnjPt1L8Vc0FyBuEKlJJvkIwJgAqAjZdCXZIAYlnzapefIuZyONaQp/tRFKJgwxFlOlbiBMtFBAW2IxGFs0MAkgkDEIzutWpWBghGgT8NkAze8yaX71ZK1qqG/9J4c//Qv8MgEbVnytDhp0sPsiGe1dSRjjDfdX8IArCeFwfnRUde0j9zsLHLAaEPNqVUA90NKSdSQr+R0iNcAIiKSIqIiLU3gI229lMxq9Uc5pMDcDWyxsCiMBUEB7JHOKeOFURHVBRb8xclOTesRbPkaAecBxFCwgI1pCQF2UUQAU0IxQuVwzXi7mDeNCKHwJXiFM2iNKe1EH5g2APSBifmISeDUowwaIuYRpkH4eLLg9KAfVB+Zej1CuK9DXcFOUDQBarhMMTjllKBX8CtNTgG2I1tpJFnkEAZk6bidaWt1FQyFIeQBRSCybP/ARlTOBPYADIB0gGwA8QBmgFqEJ0w9XdiFMGxwBLAZJCNg+DkVkMBqEYEBNDCcHB6pwii6gRwAr2xj8LsNpAAs2a+gE8GNlB2ZO+G8zOewGyk2pbOBMmBHIJYjJchWI5tc/YAlVGDxYSOcQDc9nA1G8fk4URk7ESQhC2guI8V4I0GLRZ3tWJnx5BxIBGCxIrQQQkwJI44pFRW7LJDgpYmOPD2JJ+zXobOBanFmeeBA4PmWPXEjTlGXXB4Myx2pFK+1UyHQwaqFoiAqI34oOhzUkaW1rAWpw+yRg/FuwFNp7JFaPKy49SMrLeyRJkCSUKFI1TEPAeyRV4iojNUBU3XwmD6BLIAjYZOADAK9NeQ1OiPskBdBqpCY7aggtwGdIiQRrN2ZhBcRNJwHgYslGMAQSVtpBaD+AAYU5qCWItAQp9EVkMAFCJUBOIwAi7TnOaSINlQ1ZLZZMPRIvE8lNSL8gSUj5BCz8PdsTkDyIj4M0viJYIKp7GHrAkLCMqhmTTJCDYTX7OMReFnLI5Ii8iPlIkuY1d3fbSIiNAGiI7tdX6lGIqUjIflkIZz5lSMWKExJ4ADZ0V2p/SP+gDYj5BBipcAgaCQs2c3U6LURUFv4S+VIAaMjFiPhcI6tbfn+CYmlfmTADfBCcsMIQwtQVIHYgE48AhAmGRdJRoFEgdQxbPDoTWSAmIAUgViBlIHckDCB4xHUAfUjBEAiDNstaABNIov00IDfIiAA4gGtgaoBaABU8JrAbgApAa7gSAGpAQDRmQH8ANABygAEAKoAf+EhMZkBaABuAa7gbgFMzfYUmsA4VKoB6ADYgd8imsAEAXcgGABjcf8MBAGJ4CcAzl2u4AQAKQDIozBNYKNLuLCi5FWpAMkA0ABgogQAewizQYijgKJuAakAWsHKAYCQqgE9gUzMMKNoAEqCmsDQAOgAbgEhMcoAVAFe0KoA0ABZABgBaKNgo4WBAKPPIkEjB/jIFH8jusDoAK0iPoH4oqABcoHskfqdd2SWVf8jeuEAoq1DqpCQAWwAgmT5rXGN3uABcW4AxGEdQ6qRYPGZQF2UbAHlfewivKMsgJAAb2SggHQwAqM8o5X5KxSggEG9SFFVAxABuwUzAeV9WpCCorFBaABBvdwB0WkSo5KiNZFSomKilQHJOShZNG1kaHKjjWCCopjC0CkhI6QBtcHlfayByqJNaRKj7UUNIeV8EkE0Jeyi0T0dI2RpSIVqozKiAMBKoryiOqPfkCaooMkmgbIlLIDZuAEAeqJhIZHhzOD8AQIAaS1OpKgd2J2CRJOIubFWaAHgdtTlIeGlma2ytMFRIcTxRT90h2Cswe/M4SX0nfrDECFIVMajBUlqotfDDwEuop1DiYyCQHfU2pA8o6QixqNHTTWBEqO6orgBLIGdCZOA0TxMvNqixqLLIb6jqpEKokigyyHuo61CVTTXwTEBSqNGoh6iJqPfAKajm7ApOZTQYmyvTH35WGDf0PUR/IEA8NUIkYCzAAktcvH1ac4BuJD3ec9BuEBXAZCpuLj1wLxIIL352WwAKZhqEZVYoSDhNdIcxLRDzLbAe4ChojqjrqJ+o26jCBgGo9nVHqIVhF6jRGDeoh6iPqNVAL6jj8FqovhNMgH+o9nVAaLRPdqiRaJBouWifqNIUOllKmC0IGnhhaNqAGGiMQGGoiWj1gANo6qQkaMVorWibWHPBCgtdIkgAakAqgA0AY0UAAFIG3hTlfEVaCH0eagIqOGw1SBZcg1QAGNxsnQpAF2jeinNox6i0J2Ron6jOwWDsG98Pg365DWto+ShGCZBfwAbwapI7aL9o2Dw4YDDoq6jbMBuok6c7qPDo6WiPAFlotgB5aJtorLMlaNqAb6hlfgQeKKjQ6lsAcGjJqJ+olQAmsHwolrABAAhMUZhdyEwojCjco2ZAHX8bgBYwLijmsFoAAQAbgGooqoASAA5AAQBITFe0SEwKKN/4ESiY3FEAZrBCBDIou4A4rDJAKGjLIAboh4FkeFqo3cgKQBIASEw0ABf8LijVKMhMGCjAqGgo62AqgBEoyEwZ6NoAMkAbgGZABgAOKOPo9+iGAHKAG4BaAGNFKExf6Ne0akA0ACawBgBVADQAakBAGOPo8oAd6PkIbxhdaOp4EgA5l01geOh5XzVomqQqzX6tOCNzSVbwaLUanCORNBixqLc6VUA9FxugeV8pmTGohikIb21o7xgTaJtgAGjhaIwY4Y1E1WrNKytymhwYqSkrSXwY5FlCGIeo4hiPAFIYh0ByGPDoqhi+gBoYyuiuAHoY5WjGGMdNC+UaWy2JV00CRx4Yv6giGJFSARjiL2EYyhjiL2oYiuicoXIYzQkVaJFo2RiszSe1SlFeGOho/hjBGLsaCoARGO0YsRjdGPFeeV8pGOromRjMGNGNBGBazVzNJ0YzGJUYvhi1GKsYk2iyQFsY8aodGM3AWhinGIMYxhiodwKrfqNF41qjRcV+2XMYjqjLGI0YmxitGJCY+xiwmIkYtoVImM0JSyBomNMLFONYI26JeBgiqyQjBs0kmJFolJjxqnlfIJj0mKmQUJiwVwiYhhi8mIKYvqM8eziYjEMuBQoCXxiLGP8Y1Ji2hWCYhpjMmKaYioBcmI6otpjns1RDIvUKqwqY3pjkmP6Ympi0mIeo0RixgHEYvRixmJaYiZig7xiY1X9Sq0mIdEN6o0qYw2jqmMrBZZjoaNWY2AB1mMcYyRjxmJFoyZi90xezXsxD00wuLnAjmPmYqpjFmLOYyABruCGY2LBGmPCYzZjpGNaYoO84xxNotWiTmK+Y2LBNGJWYuxi1mIcYuVx9GK2Y+5ig71I7b0tZsB8YvzhVGJbWAJiYWIuYuFirmIRYk2jVhGRYw2iodyJrPXt3mKxYvxicWIGYupjYWIyY+FismI2Y8kA7mLJY0Fi/OxW7EzAemOpYvpjaWKWY8kAToPqY/5iRmMBY/9C2WOqkfLCWn0HoNp8tQw6fGygeWIhY6qRTmOhY85iOqMuY65jEWNuYgGjlfhromuigKLMowkALKLGQkgArKPtEYyiiKNfI88jD9G1InQJTWL9qGyikgDso3eiTWj7oVagWaAFPN2AXKNYAdQA2xRZjCkB9WOtYiYZbWMrQGyiaAAtYpSA9ACAAA= --> + +<!-- internal state end --> + +--- + +### cloudflare-workers-and-pages[bot] — 2/20/2026, 3:49:01 AM + +## Deploying with  <a href="https://workers.dev"><img alt="Cloudflare Workers" src="https://workers.cloudflare.com/logo.svg" width="16"></a>  Cloudflare Workers +The latest updates on your project. Learn more about [integrating Git with Workers](https://developers.cloudflare.com/workers/ci-cd/builds/git-integration/). + +| Status | Name | Latest Commit | Updated (UTC) | +| -|-|-|-| +| ❌ Deployment failed <br>[View logs](https://dash.cloudflare.com/?to=/5651b2fe85f9bbb0fc1b8f4ad2cb4e64/workers/services/view/plate/production/builds/6b607e3c-ff09-4012-b68a-933045118aef) | plate | e6014d51 | Feb 18 2026, 05:12 AM | + +--- + +### railway-app[bot] — 2/20/2026, 3:49:00 AM + +<!-- railway-bot-comment-version=2 --> + +<!-- railway-project-id="7388df7a-f562-4897-87ea-dc5891303a79" railway-project-name="Plate Editor" --> +🚅 Deployed to the [plate-pr-114](https://railway.com/project/7388df7a-f562-4897-87ea-dc5891303a79?environmentId=18bb6f14-e5ae-4789-b1d8-34504fc027ba) environment in **[Plate Editor](https://railway.com/project/7388df7a-f562-4897-87ea-dc5891303a79)** +| **Service** | **Status** | **Web** | **Updated** (UTC) | +| :--- | :--- | :--- | :--- | +| <img src="https://devicons.railway.app/Kk" width="16px" height="16px" /> www | ✅ Success ([View Logs](https://railway.com/project/7388df7a-f562-4897-87ea-dc5891303a79/service/4a297609-b2d0-40be-a2bf-b9db035e23c1?id=98717cba-4772-4409-ae3c-bef47fd1ad95&environmentId=18bb6f14-e5ae-4789-b1d8-34504fc027ba)) | [Web](https://www-plate-pr-114.up.railway.app) | Feb 22, 2026 at 2:45 am | + +--- + +### chatgpt-codex-connector[bot] — 2/20/2026, 3:48:59 AM + +You have reached your Codex usage limits for code reviews. You can see your limits in the [Codex usage dashboard](https://chatgpt.com/codex/settings/usage). + +--- + +### vercel[bot] — 2/20/2026, 3:48:59 AM + +Deployment failed with the following error: +~~~ +Resource is limited - try again in 12 hours (more than 100, code: "api-deployments-free-per-day"). +~~~ +Learn More: https://vercel.com/arthrods-projects?upgradeToPro=build-rate-limit + +--- + +### codesandbox[bot] — 2/20/2026, 3:48:57 AM + +#### Review or Edit in CodeSandbox + +Open the branch in <a href="https://codesandbox.io/p/github/arthrod/plate/feat/docx-import-export-suggestions-comments?mode=review&utm_source=gh_app">Web Editor</a> • <a href="https://codesandbox.io/p/vscode?owner=arthrod&repo=plate&branch=feat/docx-import-export-suggestions-comments&utm_source=gh_app">VS Code</a> • <a href="https://codesandbox.io/p/vscode?owner=arthrod&repo=plate&branch=feat/docx-import-export-suggestions-comments&insiders=true&utm_source=gh_app">Insiders</a><br> +Open <a href="https://codesandbox.io/p/devtool/preview/arthrod/plate/feat/docx-import-export-suggestions-comments?task=preview&port=51423&redirect=true&utm_source=gh_app">Preview</a> + +<!-- open-in-codesandbox:complete --> + + +--- + diff --git a/apps/www/next.config.ts b/apps/www/next.config.ts index ef7f0e42e2..d496a2b299 100644 --- a/apps/www/next.config.ts +++ b/apps/www/next.config.ts @@ -4,6 +4,7 @@ import { globSync } from 'glob'; const nextConfig = async (phase: string) => { const config: NextConfig = { + output: 'standalone', typescript: { ignoreBuildErrors: true, }, diff --git a/apps/www/public/r/block-discussion.json b/apps/www/public/r/block-discussion.json index 0cb57d7149..cecf2b37e5 100644 --- a/apps/www/public/r/block-discussion.json +++ b/apps/www/public/r/block-discussion.json @@ -28,7 +28,7 @@ "files": [ { "path": "src/registry/ui/block-discussion.tsx", - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { PlateElementProps, RenderNodeWrapper } from 'platejs/react';\n\nimport { getDraftCommentKey } from '@platejs/comment';\nimport { CommentPlugin } from '@platejs/comment/react';\nimport { getTransientSuggestionKey } from '@platejs/suggestion';\nimport { SuggestionPlugin } from '@platejs/suggestion/react';\nimport {\n MessageSquareTextIcon,\n MessagesSquareIcon,\n PencilLineIcon,\n} from 'lucide-react';\nimport {\n type AnyPluginConfig,\n type NodeEntry,\n type Path,\n type TCommentText,\n type TElement,\n type TSuggestionText,\n PathApi,\n TextApi,\n} from 'platejs';\nimport { useEditorPlugin, useEditorRef, usePluginOption } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from '@/components/ui/popover';\nimport { commentPlugin } from '@/registry/components/editor/plugins/comment-kit';\nimport {\n type TDiscussion,\n discussionPlugin,\n} from '@/registry/components/editor/plugins/discussion-kit';\nimport { suggestionPlugin } from '@/registry/components/editor/plugins/suggestion-kit';\n\nimport {\n BlockSuggestionCard,\n isResolvedSuggestion,\n useResolveSuggestion,\n} from './block-suggestion';\nimport { Comment, CommentCreateForm } from './comment';\n\nexport const BlockDiscussion: RenderNodeWrapper<AnyPluginConfig> = (props) => {\n const { editor, element } = props;\n\n const commentsApi = editor.getApi(CommentPlugin).comment;\n const blockPath = editor.api.findPath(element);\n\n // avoid duplicate in table or column\n if (!blockPath || blockPath.length > 1) return;\n\n const draftCommentNode = commentsApi.node({ at: blockPath, isDraft: true });\n\n const commentNodes = [...commentsApi.nodes({ at: blockPath })];\n\n const suggestionNodes = [\n ...editor.getApi(SuggestionPlugin).suggestion.nodes({ at: blockPath }),\n ].filter(([node]) => !node[getTransientSuggestionKey()]);\n\n if (\n commentNodes.length === 0 &&\n suggestionNodes.length === 0 &&\n !draftCommentNode\n ) {\n return;\n }\n\n return (props) => (\n <BlockCommentContent\n blockPath={blockPath}\n commentNodes={commentNodes}\n draftCommentNode={draftCommentNode}\n suggestionNodes={suggestionNodes}\n {...props}\n />\n );\n};\n\nconst BlockCommentContent = ({\n blockPath,\n children,\n commentNodes,\n draftCommentNode,\n suggestionNodes,\n}: PlateElementProps & {\n blockPath: Path;\n commentNodes: NodeEntry<TCommentText>[];\n draftCommentNode: NodeEntry<TCommentText> | undefined;\n suggestionNodes: NodeEntry<TElement | TSuggestionText>[];\n}) => {\n const editor = useEditorRef();\n const resolvedSuggestions = useResolveSuggestion(suggestionNodes, blockPath);\n const resolvedDiscussions = useResolvedDiscussion(commentNodes, blockPath);\n\n const suggestionsCount = resolvedSuggestions.length;\n const discussionsCount = resolvedDiscussions.length;\n const totalCount = suggestionsCount + discussionsCount;\n\n const activeSuggestionId = usePluginOption(suggestionPlugin, 'activeId');\n const activeSuggestion =\n activeSuggestionId &&\n resolvedSuggestions.find((s) => s.suggestionId === activeSuggestionId);\n\n const commentingBlock = usePluginOption(commentPlugin, 'commentingBlock');\n const activeCommentId = usePluginOption(commentPlugin, 'activeId');\n const isCommenting = activeCommentId === getDraftCommentKey();\n const activeDiscussion =\n activeCommentId &&\n resolvedDiscussions.find((d) => d.id === activeCommentId);\n\n const noneActive = !activeSuggestion && !activeDiscussion;\n\n const sortedMergedData = [\n ...resolvedDiscussions,\n ...resolvedSuggestions,\n ].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const selected =\n resolvedDiscussions.some((d) => d.id === activeCommentId) ||\n resolvedSuggestions.some((s) => s.suggestionId === activeSuggestionId);\n\n const [_open, setOpen] = React.useState(selected);\n\n // in some cases, we may comment the multiple blocks\n const commentingCurrent =\n !!commentingBlock && PathApi.equals(blockPath, commentingBlock);\n\n const open =\n _open ||\n selected ||\n (isCommenting && !!draftCommentNode && commentingCurrent);\n\n const anchorElement = React.useMemo(() => {\n let activeNode: NodeEntry | undefined;\n\n if (activeSuggestion) {\n activeNode = suggestionNodes.find(\n ([node]) =>\n TextApi.isText(node) &&\n editor.getApi(SuggestionPlugin).suggestion.nodeId(node) ===\n activeSuggestion.suggestionId\n );\n }\n\n if (activeCommentId) {\n if (activeCommentId === getDraftCommentKey()) {\n activeNode = draftCommentNode;\n } else {\n activeNode = commentNodes.find(\n ([node]) =>\n editor.getApi(commentPlugin).comment.nodeId(node) ===\n activeCommentId\n );\n }\n }\n\n if (!activeNode) return null;\n\n return editor.api.toDOMNode(activeNode[0])!;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n open,\n activeSuggestion,\n activeCommentId,\n editor.api,\n suggestionNodes,\n draftCommentNode,\n commentNodes,\n ]);\n\n if (suggestionsCount + resolvedDiscussions.length === 0 && !draftCommentNode)\n return <div className=\"w-full\">{children}</div>;\n\n return (\n <div className=\"flex w-full justify-between\">\n <Popover\n open={open}\n onOpenChange={(_open_) => {\n if (!_open_ && isCommenting && draftCommentNode) {\n editor.tf.unsetNodes(getDraftCommentKey(), {\n at: [],\n mode: 'lowest',\n match: (n) => n[getDraftCommentKey()],\n });\n }\n setOpen(_open_);\n }}\n >\n <div className=\"w-full\">{children}</div>\n {anchorElement && (\n <PopoverAnchor\n asChild\n className=\"w-full\"\n virtualRef={{ current: anchorElement }}\n />\n )}\n\n <PopoverContent\n className=\"max-h-[min(50dvh,calc(-24px+var(--radix-popper-available-height)))] w-[380px] min-w-[130px] max-w-[calc(100vw-24px)] overflow-y-auto p-0 data-[state=closed]:opacity-0\"\n onCloseAutoFocus={(e) => e.preventDefault()}\n onOpenAutoFocus={(e) => e.preventDefault()}\n align=\"center\"\n side=\"bottom\"\n >\n {isCommenting ? (\n <CommentCreateForm className=\"p-4\" focusOnMount />\n ) : noneActive ? (\n sortedMergedData.map((item, index) =>\n isResolvedSuggestion(item) ? (\n <BlockSuggestionCard\n key={item.suggestionId}\n idx={index}\n isLast={index === sortedMergedData.length - 1}\n suggestion={item}\n />\n ) : (\n <BlockComment\n key={item.id}\n discussion={item}\n isLast={index === sortedMergedData.length - 1}\n />\n )\n )\n ) : (\n <>\n {activeSuggestion && (\n <BlockSuggestionCard\n key={activeSuggestion.suggestionId}\n idx={0}\n isLast={true}\n suggestion={activeSuggestion}\n />\n )}\n\n {activeDiscussion && (\n <BlockComment discussion={activeDiscussion} isLast={true} />\n )}\n </>\n )}\n </PopoverContent>\n\n {totalCount > 0 && (\n <div className=\"relative left-0 size-0 select-none\">\n <PopoverTrigger asChild>\n <Button\n variant=\"ghost\"\n className=\"!px-1.5 mt-1 ml-1 flex h-6 gap-1 py-0 text-muted-foreground/80 hover:text-muted-foreground/80 data-[active=true]:bg-muted\"\n data-active={open}\n contentEditable={false}\n >\n {suggestionsCount > 0 && discussionsCount === 0 && (\n <PencilLineIcon className=\"size-4 shrink-0\" />\n )}\n\n {suggestionsCount === 0 && discussionsCount > 0 && (\n <MessageSquareTextIcon className=\"size-4 shrink-0\" />\n )}\n\n {suggestionsCount > 0 && discussionsCount > 0 && (\n <MessagesSquareIcon className=\"size-4 shrink-0\" />\n )}\n\n <span className=\"font-semibold text-xs\">{totalCount}</span>\n </Button>\n </PopoverTrigger>\n </div>\n )}\n </Popover>\n </div>\n );\n};\n\nfunction BlockComment({\n discussion,\n isLast,\n}: {\n discussion: TDiscussion;\n isLast: boolean;\n}) {\n const [editingId, setEditingId] = React.useState<string | null>(null);\n\n return (\n <React.Fragment key={discussion.id}>\n <div className=\"p-4\">\n {discussion.comments.map((comment, index) => (\n <Comment\n key={comment.id ?? index}\n comment={comment}\n discussionLength={discussion.comments.length}\n documentContent={discussion?.documentContent}\n editingId={editingId}\n index={index}\n setEditingId={setEditingId}\n showDocumentContent\n />\n ))}\n <CommentCreateForm discussionId={discussion.id} />\n </div>\n\n {!isLast && <div className=\"h-px w-full bg-muted\" />}\n </React.Fragment>\n );\n}\n\nconst useResolvedDiscussion = (\n commentNodes: NodeEntry<TCommentText>[],\n blockPath: Path\n) => {\n const { api, getOption, setOption } = useEditorPlugin(commentPlugin);\n\n const discussions = usePluginOption(discussionPlugin, 'discussions');\n\n commentNodes.forEach(([node]) => {\n const id = api.comment.nodeId(node);\n const map = getOption('uniquePathMap');\n\n if (!id) return;\n\n const previousPath = map.get(id);\n\n // If there are no comment nodes in the corresponding path in the map, then update it.\n if (PathApi.isPath(previousPath)) {\n const nodes = api.comment.node({ id, at: previousPath });\n\n if (!nodes) {\n setOption('uniquePathMap', new Map(map).set(id, blockPath));\n return;\n }\n\n return;\n }\n // TODO: fix throw error\n setOption('uniquePathMap', new Map(map).set(id, blockPath));\n });\n\n const commentsIds = new Set(\n commentNodes.map(([node]) => api.comment.nodeId(node)).filter(Boolean)\n );\n\n const resolvedDiscussions = discussions\n .map((d: TDiscussion) => ({\n ...d,\n createdAt: new Date(d.createdAt),\n }))\n .filter((item: TDiscussion) => {\n /** If comment cross blocks just show it in the first block */\n const commentsPathMap = getOption('uniquePathMap');\n const firstBlockPath = commentsPathMap.get(item.id);\n\n if (!firstBlockPath) return false;\n if (!PathApi.equals(firstBlockPath, blockPath)) return false;\n\n return (\n api.comment.has({ id: item.id }) &&\n commentsIds.has(item.id) &&\n !item.isResolved\n );\n });\n\n return resolvedDiscussions;\n};\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { PlateElementProps, RenderNodeWrapper } from 'platejs/react';\n\nimport {\n getCommentKeyId,\n getCommentKeys,\n getDraftCommentKey,\n} from '@platejs/comment';\nimport { CommentPlugin } from '@platejs/comment/react';\nimport { getTransientSuggestionKey } from '@platejs/suggestion';\nimport { SuggestionPlugin } from '@platejs/suggestion/react';\nimport {\n MessageSquareTextIcon,\n MessagesSquareIcon,\n PencilLineIcon,\n} from 'lucide-react';\nimport {\n type AnyPluginConfig,\n type NodeEntry,\n type Path,\n type TCommentText,\n type TElement,\n type TSuggestionText,\n PathApi,\n TextApi,\n} from 'platejs';\nimport { useEditorPlugin, useEditorRef, usePluginOption } from 'platejs/react';\n\nimport { Button } from '@/components/ui/button';\nimport {\n Popover,\n PopoverAnchor,\n PopoverContent,\n PopoverTrigger,\n} from '@/components/ui/popover';\nimport { commentPlugin } from '@/registry/components/editor/plugins/comment-kit';\nimport {\n type TDiscussion,\n discussionPlugin,\n} from '@/registry/components/editor/plugins/discussion-kit';\nimport { suggestionPlugin } from '@/registry/components/editor/plugins/suggestion-kit';\n\nimport {\n BlockSuggestionCard,\n isResolvedSuggestion,\n useResolveSuggestion,\n} from './block-suggestion';\nimport { Comment, CommentCreateForm } from './comment';\n\nexport const BlockDiscussion: RenderNodeWrapper<AnyPluginConfig> = (props) => {\n const { editor, element } = props;\n\n const commentsApi = editor.getApi(CommentPlugin).comment;\n const blockPath = editor.api.findPath(element);\n\n // avoid duplicate in table or column\n if (!blockPath || blockPath.length > 1) return;\n\n const draftCommentNode = commentsApi.node({ at: blockPath, isDraft: true });\n\n const commentNodes = [...commentsApi.nodes({ at: blockPath })];\n\n const suggestionNodes = [\n ...editor.getApi(SuggestionPlugin).suggestion.nodes({ at: blockPath }),\n ].filter(([node]) => !node[getTransientSuggestionKey()]);\n\n if (\n commentNodes.length === 0 &&\n suggestionNodes.length === 0 &&\n !draftCommentNode\n ) {\n return;\n }\n\n return (props) => (\n <BlockCommentContent\n blockPath={blockPath}\n commentNodes={commentNodes}\n draftCommentNode={draftCommentNode}\n suggestionNodes={suggestionNodes}\n {...props}\n />\n );\n};\n\nconst BlockCommentContent = ({\n blockPath,\n children,\n commentNodes,\n draftCommentNode,\n suggestionNodes,\n}: PlateElementProps & {\n blockPath: Path;\n commentNodes: NodeEntry<TCommentText>[];\n draftCommentNode: NodeEntry<TCommentText> | undefined;\n suggestionNodes: NodeEntry<TElement | TSuggestionText>[];\n}) => {\n const editor = useEditorRef();\n const resolvedSuggestions = useResolveSuggestion(suggestionNodes, blockPath);\n const resolvedDiscussions = useResolvedDiscussion(commentNodes, blockPath);\n\n const suggestionsCount = resolvedSuggestions.length;\n const discussionsCount = resolvedDiscussions.length;\n const totalCount = suggestionsCount + discussionsCount;\n\n const activeSuggestionId = usePluginOption(suggestionPlugin, 'activeId');\n const activeSuggestion =\n activeSuggestionId &&\n resolvedSuggestions.find((s) => s.suggestionId === activeSuggestionId);\n\n const commentingBlock = usePluginOption(commentPlugin, 'commentingBlock');\n const activeCommentId = usePluginOption(commentPlugin, 'activeId');\n const isCommenting = activeCommentId === getDraftCommentKey();\n const activeDiscussion =\n activeCommentId &&\n resolvedDiscussions.find((d) => d.id === activeCommentId);\n\n const noneActive = !activeSuggestion && !activeDiscussion;\n\n const sortedMergedData = [\n ...resolvedDiscussions,\n ...resolvedSuggestions,\n ].sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());\n\n const selected =\n resolvedDiscussions.some((d) => d.id === activeCommentId) ||\n resolvedSuggestions.some((s) => s.suggestionId === activeSuggestionId);\n\n const [_open, setOpen] = React.useState(selected);\n\n // in some cases, we may comment the multiple blocks\n const commentingCurrent =\n !!commentingBlock && PathApi.equals(blockPath, commentingBlock);\n\n const open =\n _open ||\n selected ||\n (isCommenting && !!draftCommentNode && commentingCurrent);\n\n const anchorElement = React.useMemo(() => {\n let activeNode: NodeEntry | undefined;\n\n if (activeSuggestion) {\n activeNode = suggestionNodes.find(\n ([node]) =>\n TextApi.isText(node) &&\n editor.getApi(SuggestionPlugin).suggestion.nodeId(node) ===\n activeSuggestion.suggestionId\n );\n }\n\n if (activeCommentId) {\n if (activeCommentId === getDraftCommentKey()) {\n activeNode = draftCommentNode;\n } else {\n activeNode = commentNodes.find(\n ([node]) =>\n editor.getApi(commentPlugin).comment.nodeId(node) ===\n activeCommentId\n );\n }\n }\n\n if (!activeNode) return null;\n\n return editor.api.toDOMNode(activeNode[0])!;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n open,\n activeSuggestion,\n activeCommentId,\n editor.api,\n suggestionNodes,\n draftCommentNode,\n commentNodes,\n ]);\n\n if (suggestionsCount + resolvedDiscussions.length === 0 && !draftCommentNode)\n return <div className=\"w-full\">{children}</div>;\n\n return (\n <div className=\"flex w-full justify-between\">\n <Popover\n open={open}\n onOpenChange={(_open_) => {\n if (!_open_ && isCommenting && draftCommentNode) {\n editor.tf.unsetNodes(getDraftCommentKey(), {\n at: [],\n mode: 'lowest',\n match: (n) => n[getDraftCommentKey()],\n });\n }\n setOpen(_open_);\n }}\n >\n <div className=\"w-full\">{children}</div>\n {anchorElement && (\n <PopoverAnchor\n asChild\n className=\"w-full\"\n virtualRef={{ current: anchorElement }}\n />\n )}\n\n <PopoverContent\n className=\"max-h-[min(50dvh,calc(-24px+var(--radix-popper-available-height)))] w-[380px] min-w-[130px] max-w-[calc(100vw-24px)] overflow-y-auto p-0 data-[state=closed]:opacity-0\"\n onCloseAutoFocus={(e) => e.preventDefault()}\n onOpenAutoFocus={(e) => e.preventDefault()}\n align=\"center\"\n side=\"bottom\"\n >\n {isCommenting ? (\n <CommentCreateForm className=\"p-4\" focusOnMount />\n ) : noneActive ? (\n sortedMergedData.map((item, index) =>\n isResolvedSuggestion(item) ? (\n <BlockSuggestionCard\n key={item.suggestionId}\n idx={index}\n isLast={index === sortedMergedData.length - 1}\n suggestion={item}\n />\n ) : (\n <BlockComment\n key={item.id}\n discussion={item}\n isLast={index === sortedMergedData.length - 1}\n />\n )\n )\n ) : (\n <>\n {activeSuggestion && (\n <BlockSuggestionCard\n key={activeSuggestion.suggestionId}\n idx={0}\n isLast={true}\n suggestion={activeSuggestion}\n />\n )}\n\n {activeDiscussion && (\n <BlockComment discussion={activeDiscussion} isLast={true} />\n )}\n </>\n )}\n </PopoverContent>\n\n {totalCount > 0 && (\n <div className=\"relative left-0 size-0 select-none\">\n <PopoverTrigger asChild>\n <Button\n variant=\"ghost\"\n className=\"!px-1.5 mt-1 ml-1 flex h-6 gap-1 py-0 text-muted-foreground/80 hover:text-muted-foreground/80 data-[active=true]:bg-muted\"\n data-active={open}\n contentEditable={false}\n >\n {suggestionsCount > 0 && discussionsCount === 0 && (\n <PencilLineIcon className=\"size-4 shrink-0\" />\n )}\n\n {suggestionsCount === 0 && discussionsCount > 0 && (\n <MessageSquareTextIcon className=\"size-4 shrink-0\" />\n )}\n\n {suggestionsCount > 0 && discussionsCount > 0 && (\n <MessagesSquareIcon className=\"size-4 shrink-0\" />\n )}\n\n <span className=\"font-semibold text-xs\">{totalCount}</span>\n </Button>\n </PopoverTrigger>\n </div>\n )}\n </Popover>\n </div>\n );\n};\n\nfunction BlockComment({\n discussion,\n isLast,\n}: {\n discussion: TDiscussion;\n isLast: boolean;\n}) {\n const [editingId, setEditingId] = React.useState<string | null>(null);\n\n return (\n <React.Fragment key={discussion.id}>\n <div className=\"p-4\">\n {discussion.comments.map((comment, index) => (\n <Comment\n key={comment.id ?? index}\n comment={comment}\n discussionLength={discussion.comments.length}\n documentContent={discussion?.documentContent}\n editingId={editingId}\n index={index}\n setEditingId={setEditingId}\n showDocumentContent\n />\n ))}\n <CommentCreateForm discussionId={discussion.id} />\n </div>\n\n {!isLast && <div className=\"h-px w-full bg-muted\" />}\n </React.Fragment>\n );\n}\n\nconst useResolvedDiscussion = (\n commentNodes: NodeEntry<TCommentText>[],\n blockPath: Path\n) => {\n const { api, getOption, setOption } = useEditorPlugin(commentPlugin);\n\n const discussions = usePluginOption(discussionPlugin, 'discussions');\n\n const getLeafCommentIds = (leaf: TCommentText) =>\n getCommentKeys(leaf)\n .map(getCommentKeyId)\n .filter((id): id is string => Boolean(id) && id !== 'draft');\n\n const map = getOption('uniquePathMap');\n const nextMap = new Map(map);\n let mapChanged = false;\n\n commentNodes.forEach(([node]) => {\n const ids = getLeafCommentIds(node);\n if (ids.length === 0) return;\n\n ids.forEach((id) => {\n const previousPath = nextMap.get(id);\n\n // If there are no comment nodes in the corresponding path in the map, then update it.\n if (PathApi.isPath(previousPath)) {\n const nodes = api.comment.node({ id, at: previousPath });\n\n if (!nodes) {\n nextMap.set(id, blockPath);\n mapChanged = true;\n }\n\n return;\n }\n // TODO: fix throw error\n nextMap.set(id, blockPath);\n mapChanged = true;\n });\n });\n\n if (mapChanged) {\n setOption('uniquePathMap', nextMap);\n }\n\n const commentsIds = new Set(\n commentNodes.flatMap(([node]) => getLeafCommentIds(node))\n );\n\n const resolvedDiscussions = discussions\n .map((d: TDiscussion) => ({\n ...d,\n createdAt: new Date(d.createdAt),\n }))\n .filter((item: TDiscussion) => {\n /** If comment cross blocks just show it in the first block */\n const commentsPathMap = getOption('uniquePathMap');\n const firstBlockPath = commentsPathMap.get(item.id);\n\n if (!firstBlockPath) return false;\n if (!PathApi.equals(firstBlockPath, blockPath)) return false;\n\n return (\n api.comment.has({ id: item.id }) &&\n commentsIds.has(item.id) &&\n !item.isResolved\n );\n });\n\n return resolvedDiscussions;\n};\n", "type": "registry:ui" }, { @@ -38,7 +38,7 @@ }, { "path": "src/registry/ui/comment.tsx", - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { CreatePlateEditorOptions } from 'platejs/react';\n\nimport { getCommentKey, getDraftCommentKey } from '@platejs/comment';\nimport { CommentPlugin, useCommentId } from '@platejs/comment/react';\nimport {\n differenceInDays,\n differenceInHours,\n differenceInMinutes,\n format,\n} from 'date-fns';\nimport {\n ArrowUpIcon,\n CheckIcon,\n MoreHorizontalIcon,\n PencilIcon,\n TrashIcon,\n XIcon,\n} from 'lucide-react';\nimport { type Value, KEYS, nanoid, NodeApi } from 'platejs';\nimport {\n Plate,\n useEditorPlugin,\n useEditorRef,\n usePlateEditor,\n usePluginOption,\n} from 'platejs/react';\n\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';\nimport { Button } from '@/components/ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { cn } from '@/lib/utils';\nimport { BasicMarksKit } from '@/registry/components/editor/plugins/basic-marks-kit';\nimport {\n type TDiscussion,\n discussionPlugin,\n} from '@/registry/components/editor/plugins/discussion-kit';\n\nimport { Editor, EditorContainer } from './editor';\n\nexport type TComment = {\n id: string;\n contentRich: Value;\n createdAt: Date;\n discussionId: string;\n isEdited: boolean;\n userId: string;\n};\n\nexport function Comment(props: {\n comment: TComment;\n discussionLength: number;\n editingId: string | null;\n index: number;\n setEditingId: React.Dispatch<React.SetStateAction<string | null>>;\n documentContent?: string;\n showDocumentContent?: boolean;\n onEditorClick?: () => void;\n}) {\n const {\n comment,\n discussionLength,\n documentContent,\n editingId,\n index,\n setEditingId,\n showDocumentContent = false,\n onEditorClick,\n } = props;\n\n const editor = useEditorRef();\n const userInfo = usePluginOption(discussionPlugin, 'user', comment.userId);\n const currentUserId = usePluginOption(discussionPlugin, 'currentUserId');\n\n const resolveDiscussion = async (id: string) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id === id) {\n return { ...discussion, isResolved: true };\n }\n return discussion;\n });\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const removeDiscussion = async (id: string) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .filter((discussion) => discussion.id !== id);\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const updateComment = async (input: {\n id: string;\n contentRich: Value;\n discussionId: string;\n isEdited: boolean;\n }) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id === input.discussionId) {\n const updatedComments = discussion.comments.map((comment) => {\n if (comment.id === input.id) {\n return {\n ...comment,\n contentRich: input.contentRich,\n isEdited: true,\n updatedAt: new Date(),\n };\n }\n return comment;\n });\n return { ...discussion, comments: updatedComments };\n }\n return discussion;\n });\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const { tf } = useEditorPlugin(CommentPlugin);\n\n // Replace to your own backend or refer to potion\n const isMyComment = currentUserId === comment.userId;\n\n const initialValue = comment.contentRich;\n\n const commentEditor = useCommentEditor(\n {\n id: comment.id,\n value: initialValue,\n },\n [initialValue]\n );\n\n const onCancel = () => {\n setEditingId(null);\n commentEditor.tf.replaceNodes(initialValue, {\n at: [],\n children: true,\n });\n };\n\n const onSave = () => {\n void updateComment({\n id: comment.id,\n contentRich: commentEditor.children,\n discussionId: comment.discussionId,\n isEdited: true,\n });\n setEditingId(null);\n };\n\n const onResolveComment = () => {\n void resolveDiscussion(comment.discussionId);\n tf.comment.unsetMark({ id: comment.discussionId });\n };\n\n const isFirst = index === 0;\n const isLast = index === discussionLength - 1;\n const isEditing = editingId && editingId === comment.id;\n\n const [hovering, setHovering] = React.useState(false);\n const [dropdownOpen, setDropdownOpen] = React.useState(false);\n\n return (\n <div\n onMouseEnter={() => setHovering(true)}\n onMouseLeave={() => setHovering(false)}\n >\n <div className=\"relative flex items-center\">\n <Avatar className=\"size-5\">\n <AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} />\n <AvatarFallback>{userInfo?.name?.[0]}</AvatarFallback>\n </Avatar>\n <h4 className=\"mx-2 font-semibold text-sm leading-none\">\n {/* Replace to your own backend or refer to potion */}\n {userInfo?.name}\n </h4>\n\n <div className=\"text-muted-foreground/80 text-xs leading-none\">\n <span className=\"mr-1\">\n {formatCommentDate(new Date(comment.createdAt))}\n </span>\n {comment.isEdited && <span>(edited)</span>}\n </div>\n\n {isMyComment && (hovering || dropdownOpen) && (\n <div className=\"absolute top-0 right-0 flex space-x-1\">\n {index === 0 && (\n <Button\n variant=\"ghost\"\n className=\"h-6 p-1 text-muted-foreground\"\n onClick={onResolveComment}\n type=\"button\"\n >\n <CheckIcon className=\"size-4\" />\n </Button>\n )}\n\n <CommentMoreDropdown\n onCloseAutoFocus={() => {\n setTimeout(() => {\n commentEditor.tf.focus({ edge: 'endEditor' });\n }, 0);\n }}\n onRemoveComment={() => {\n if (discussionLength === 1) {\n tf.comment.unsetMark({ id: comment.discussionId });\n void removeDiscussion(comment.discussionId);\n }\n }}\n comment={comment}\n dropdownOpen={dropdownOpen}\n setDropdownOpen={setDropdownOpen}\n setEditingId={setEditingId}\n />\n </div>\n )}\n </div>\n\n {isFirst && showDocumentContent && (\n <div className=\"relative mt-1 flex pl-[32px] text-sm text-subtle-foreground\">\n {discussionLength > 1 && (\n <div className=\"absolute top-[5px] left-3 h-full w-0.5 shrink-0 bg-muted\" />\n )}\n <div className=\"my-px w-0.5 shrink-0 bg-highlight\" />\n {documentContent && <div className=\"ml-2\">{documentContent}</div>}\n </div>\n )}\n\n <div className=\"relative my-1 pl-[26px]\">\n {!isLast && (\n <div className=\"absolute top-0 left-3 h-full w-0.5 shrink-0 bg-muted\" />\n )}\n <Plate readOnly={!isEditing} editor={commentEditor}>\n <EditorContainer variant=\"comment\">\n <Editor\n variant=\"comment\"\n className=\"w-auto grow\"\n onClick={() => onEditorClick?.()}\n />\n\n {isEditing && (\n <div className=\"ml-auto flex shrink-0 gap-1\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-[28px]\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n void onCancel();\n }}\n >\n <div className=\"flex size-5 shrink-0 items-center justify-center rounded-[50%] bg-primary/40\">\n <XIcon className=\"size-3 stroke-[3px] text-background\" />\n </div>\n </Button>\n\n <Button\n size=\"icon\"\n variant=\"ghost\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n void onSave();\n }}\n >\n <div className=\"flex size-5 shrink-0 items-center justify-center rounded-[50%] bg-brand\">\n <CheckIcon className=\"size-3 stroke-[3px] text-background\" />\n </div>\n </Button>\n </div>\n )}\n </EditorContainer>\n </Plate>\n </div>\n </div>\n );\n}\n\nfunction CommentMoreDropdown(props: {\n comment: TComment;\n dropdownOpen: boolean;\n setDropdownOpen: React.Dispatch<React.SetStateAction<boolean>>;\n setEditingId: React.Dispatch<React.SetStateAction<string | null>>;\n onCloseAutoFocus?: () => void;\n onRemoveComment?: () => void;\n}) {\n const {\n comment,\n dropdownOpen,\n setDropdownOpen,\n setEditingId,\n onCloseAutoFocus,\n onRemoveComment,\n } = props;\n\n const editor = useEditorRef();\n\n const selectedEditCommentRef = React.useRef<boolean>(false);\n\n const onDeleteComment = React.useCallback(() => {\n if (!comment.id)\n return alert('You are operating too quickly, please try again later.');\n\n // Find and update the discussion\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id !== comment.discussionId) {\n return discussion;\n }\n\n const commentIndex = discussion.comments.findIndex(\n (c) => c.id === comment.id\n );\n if (commentIndex === -1) {\n return discussion;\n }\n\n return {\n ...discussion,\n comments: [\n ...discussion.comments.slice(0, commentIndex),\n ...discussion.comments.slice(commentIndex + 1),\n ],\n };\n });\n\n // Save back to session storage\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n onRemoveComment?.();\n }, [comment.discussionId, comment.id, editor, onRemoveComment]);\n\n const onEditComment = React.useCallback(() => {\n selectedEditCommentRef.current = true;\n\n if (!comment.id)\n return alert('You are operating too quickly, please try again later.');\n\n setEditingId(comment.id);\n }, [comment.id, setEditingId]);\n\n return (\n <DropdownMenu\n open={dropdownOpen}\n onOpenChange={setDropdownOpen}\n modal={false}\n >\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" className={cn('h-6 p-1 text-muted-foreground')}>\n <MoreHorizontalIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-48\"\n onCloseAutoFocus={(e) => {\n if (selectedEditCommentRef.current) {\n onCloseAutoFocus?.();\n selectedEditCommentRef.current = false;\n }\n\n return e.preventDefault();\n }}\n >\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={onEditComment}>\n <PencilIcon className=\"size-4\" />\n Edit comment\n </DropdownMenuItem>\n <DropdownMenuItem onClick={onDeleteComment}>\n <TrashIcon className=\"size-4\" />\n Delete comment\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nconst useCommentEditor = (\n options: Omit<CreatePlateEditorOptions, 'plugins'> = {},\n deps: any[] = []\n) => {\n const commentEditor = usePlateEditor(\n {\n id: 'comment',\n plugins: BasicMarksKit,\n value: [],\n ...options,\n },\n deps\n );\n\n return commentEditor;\n};\n\nexport function CommentCreateForm({\n autoFocus = false,\n className,\n discussionId: discussionIdProp,\n focusOnMount = false,\n}: {\n autoFocus?: boolean;\n className?: string;\n discussionId?: string;\n focusOnMount?: boolean;\n}) {\n const discussions = usePluginOption(discussionPlugin, 'discussions');\n\n const editor = useEditorRef();\n const commentId = useCommentId();\n const discussionId = discussionIdProp ?? commentId;\n\n const userInfo = usePluginOption(discussionPlugin, 'currentUser');\n const [commentValue, setCommentValue] = React.useState<Value | undefined>();\n const commentContent = React.useMemo(\n () =>\n commentValue\n ? NodeApi.string({ children: commentValue, type: KEYS.p })\n : '',\n [commentValue]\n );\n const commentEditor = useCommentEditor();\n\n React.useEffect(() => {\n if (commentEditor && focusOnMount) {\n commentEditor.tf.focus();\n }\n }, [commentEditor, focusOnMount]);\n\n const onAddComment = React.useCallback(async () => {\n if (!commentValue) return;\n\n commentEditor.tf.reset();\n\n if (discussionId) {\n // Get existing discussion\n const discussion = discussions.find((d) => d.id === discussionId);\n if (!discussion) {\n // Mock creating suggestion\n const newDiscussion: TDiscussion = {\n id: discussionId,\n comments: [\n {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n },\n ],\n createdAt: new Date(),\n isResolved: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n editor.setOption(discussionPlugin, 'discussions', [\n ...discussions,\n newDiscussion,\n ]);\n return;\n }\n\n // Create reply comment\n const comment: TComment = {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n // Add reply to discussion comments\n const updatedDiscussion = {\n ...discussion,\n comments: [...discussion.comments, comment],\n };\n\n // Filter out old discussion and add updated one\n const updatedDiscussions = discussions\n .filter((d) => d.id !== discussionId)\n .concat(updatedDiscussion);\n\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n\n return;\n }\n\n const commentsNodeEntry = editor\n .getApi(CommentPlugin)\n .comment.nodes({ at: [], isDraft: true });\n\n if (commentsNodeEntry.length === 0) return;\n\n const documentContent = commentsNodeEntry\n .map(([node]) => node.text)\n .join('');\n\n const _discussionId = nanoid();\n // Mock creating new discussion\n const newDiscussion: TDiscussion = {\n id: _discussionId,\n comments: [\n {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId: _discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n },\n ],\n createdAt: new Date(),\n documentContent,\n isResolved: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n editor.setOption(discussionPlugin, 'discussions', [\n ...discussions,\n newDiscussion,\n ]);\n\n const id = newDiscussion.id;\n\n commentsNodeEntry.forEach(([, path]) => {\n editor.tf.setNodes(\n {\n [getCommentKey(id)]: true,\n },\n { at: path, split: true }\n );\n editor.tf.unsetNodes([getDraftCommentKey()], { at: path });\n });\n }, [commentValue, commentEditor.tf, discussionId, editor, discussions]);\n\n return (\n <div className={cn('flex w-full', className)}>\n <div className=\"mt-2 mr-1 shrink-0\">\n {/* Replace to your own backend or refer to potion */}\n <Avatar className=\"size-5\">\n <AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} />\n <AvatarFallback>{userInfo?.name?.[0]}</AvatarFallback>\n </Avatar>\n </div>\n\n <div className=\"relative flex grow gap-2\">\n <Plate\n onChange={({ value }) => {\n setCommentValue(value);\n }}\n editor={commentEditor}\n >\n <EditorContainer variant=\"comment\">\n <Editor\n variant=\"comment\"\n className=\"min-h-[25px] grow pt-0.5 pr-8\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n onAddComment();\n }\n }}\n placeholder=\"Reply...\"\n autoComplete=\"off\"\n autoFocus={autoFocus}\n />\n\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"absolute right-0.5 bottom-0.5 ml-auto size-6 shrink-0\"\n disabled={commentContent.trim().length === 0}\n onClick={(e) => {\n e.stopPropagation();\n onAddComment();\n }}\n >\n <div className=\"flex size-6 items-center justify-center rounded-full\">\n <ArrowUpIcon />\n </div>\n </Button>\n </EditorContainer>\n </Plate>\n </div>\n </div>\n );\n}\n\nexport const formatCommentDate = (date: Date) => {\n const now = new Date();\n const diffMinutes = differenceInMinutes(now, date);\n const diffHours = differenceInHours(now, date);\n const diffDays = differenceInDays(now, date);\n\n if (diffMinutes < 60) {\n return `${diffMinutes}m`;\n }\n if (diffHours < 24) {\n return `${diffHours}h`;\n }\n if (diffDays < 2) {\n return `${diffDays}d`;\n }\n\n return format(date, 'MM/dd/yyyy');\n};\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { CreatePlateEditorOptions } from 'platejs/react';\n\nimport { getCommentKey, getDraftCommentKey } from '@platejs/comment';\nimport { CommentPlugin, useCommentId } from '@platejs/comment/react';\nimport {\n differenceInDays,\n differenceInHours,\n differenceInMinutes,\n format,\n} from 'date-fns';\nimport {\n ArrowUpIcon,\n CheckIcon,\n MoreHorizontalIcon,\n PencilIcon,\n TrashIcon,\n XIcon,\n} from 'lucide-react';\nimport { type Value, KEYS, nanoid, NodeApi } from 'platejs';\nimport {\n Plate,\n useEditorPlugin,\n useEditorRef,\n usePlateEditor,\n usePluginOption,\n} from 'platejs/react';\n\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';\nimport { Button } from '@/components/ui/button';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { cn } from '@/lib/utils';\nimport { BasicMarksKit } from '@/registry/components/editor/plugins/basic-marks-kit';\nimport {\n type TDiscussion,\n discussionPlugin,\n} from '@/registry/components/editor/plugins/discussion-kit';\n\nimport { Editor, EditorContainer } from './editor';\n\nexport type TComment = {\n id: string;\n contentRich: Value;\n createdAt: Date;\n discussionId: string;\n isEdited: boolean;\n userId: string;\n /** Direct author name from DOCX import (bypasses user lookup) */\n authorName?: string;\n /** Author initials from DOCX import */\n authorInitials?: string;\n /** OOXML paraId for round-trip DOCX threading fidelity */\n paraId?: string;\n /** OOXML parentParaId for round-trip DOCX reply threading */\n parentParaId?: string;\n};\n\nexport function Comment(props: {\n comment: TComment;\n discussionLength: number;\n editingId: string | null;\n index: number;\n setEditingId: React.Dispatch<React.SetStateAction<string | null>>;\n documentContent?: string;\n showDocumentContent?: boolean;\n onEditorClick?: () => void;\n}) {\n const {\n comment,\n discussionLength,\n documentContent,\n editingId,\n index,\n setEditingId,\n showDocumentContent = false,\n onEditorClick,\n } = props;\n\n const editor = useEditorRef();\n const userInfo = usePluginOption(discussionPlugin, 'user', comment.userId);\n const currentUserId = usePluginOption(discussionPlugin, 'currentUserId');\n\n const resolveDiscussion = async (id: string) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id === id) {\n return { ...discussion, isResolved: true };\n }\n return discussion;\n });\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const removeDiscussion = async (id: string) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .filter((discussion) => discussion.id !== id);\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const updateComment = async (input: {\n id: string;\n contentRich: Value;\n discussionId: string;\n isEdited: boolean;\n }) => {\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id === input.discussionId) {\n const updatedComments = discussion.comments.map((comment) => {\n if (comment.id === input.id) {\n return {\n ...comment,\n contentRich: input.contentRich,\n isEdited: true,\n updatedAt: new Date(),\n };\n }\n return comment;\n });\n return { ...discussion, comments: updatedComments };\n }\n return discussion;\n });\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n };\n\n const { tf } = useEditorPlugin(CommentPlugin);\n\n // Replace to your own backend or refer to potion\n const isMyComment = currentUserId === comment.userId;\n\n const initialValue = comment.contentRich;\n\n const commentEditor = useCommentEditor(\n {\n id: comment.id,\n value: initialValue,\n },\n [initialValue]\n );\n\n const onCancel = () => {\n setEditingId(null);\n commentEditor.tf.replaceNodes(initialValue, {\n at: [],\n children: true,\n });\n };\n\n const onSave = () => {\n void updateComment({\n id: comment.id,\n contentRich: commentEditor.children,\n discussionId: comment.discussionId,\n isEdited: true,\n });\n setEditingId(null);\n };\n\n const onResolveComment = () => {\n void resolveDiscussion(comment.discussionId);\n tf.comment.unsetMark({ id: comment.discussionId });\n };\n\n const isFirst = index === 0;\n const isLast = index === discussionLength - 1;\n const isEditing = editingId && editingId === comment.id;\n\n const [hovering, setHovering] = React.useState(false);\n const [dropdownOpen, setDropdownOpen] = React.useState(false);\n\n return (\n <div\n onMouseEnter={() => setHovering(true)}\n onMouseLeave={() => setHovering(false)}\n >\n <div className=\"relative flex items-center\">\n <Avatar className=\"size-5\">\n <AvatarImage\n alt={comment.authorName ?? userInfo?.name}\n src={userInfo?.avatarUrl}\n />\n <AvatarFallback>\n {comment.authorInitials ??\n comment.authorName?.[0] ??\n userInfo?.name?.[0]}\n </AvatarFallback>\n </Avatar>\n <h4 className=\"mx-2 font-semibold text-sm leading-none\">\n {/* Use direct author name from DOCX or fall back to user lookup */}\n {comment.authorName ?? userInfo?.name}\n </h4>\n\n <div className=\"text-muted-foreground/80 text-xs leading-none\">\n <span className=\"mr-1\">\n {formatCommentDate(new Date(comment.createdAt))}\n </span>\n {comment.isEdited && <span>(edited)</span>}\n </div>\n\n {isMyComment && (hovering || dropdownOpen) && (\n <div className=\"absolute top-0 right-0 flex space-x-1\">\n {index === 0 && (\n <Button\n variant=\"ghost\"\n className=\"h-6 p-1 text-muted-foreground\"\n onClick={onResolveComment}\n type=\"button\"\n >\n <CheckIcon className=\"size-4\" />\n </Button>\n )}\n\n <CommentMoreDropdown\n onCloseAutoFocus={() => {\n setTimeout(() => {\n commentEditor.tf.focus({ edge: 'endEditor' });\n }, 0);\n }}\n onRemoveComment={() => {\n if (discussionLength === 1) {\n tf.comment.unsetMark({ id: comment.discussionId });\n void removeDiscussion(comment.discussionId);\n }\n }}\n comment={comment}\n dropdownOpen={dropdownOpen}\n setDropdownOpen={setDropdownOpen}\n setEditingId={setEditingId}\n />\n </div>\n )}\n </div>\n\n {isFirst && showDocumentContent && (\n <div className=\"relative mt-1 flex pl-[32px] text-sm text-subtle-foreground\">\n {discussionLength > 1 && (\n <div className=\"absolute top-[5px] left-3 h-full w-0.5 shrink-0 bg-muted\" />\n )}\n <div className=\"my-px w-0.5 shrink-0 bg-highlight\" />\n {documentContent && <div className=\"ml-2\">{documentContent}</div>}\n </div>\n )}\n\n <div className=\"relative my-1 pl-[26px]\">\n {!isLast && (\n <div className=\"absolute top-0 left-3 h-full w-0.5 shrink-0 bg-muted\" />\n )}\n <Plate readOnly={!isEditing} editor={commentEditor}>\n <EditorContainer variant=\"comment\">\n <Editor\n variant=\"comment\"\n className=\"w-auto grow\"\n onClick={() => onEditorClick?.()}\n />\n\n {isEditing && (\n <div className=\"ml-auto flex shrink-0 gap-1\">\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-[28px]\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n void onCancel();\n }}\n >\n <div className=\"flex size-5 shrink-0 items-center justify-center rounded-[50%] bg-primary/40\">\n <XIcon className=\"size-3 stroke-[3px] text-background\" />\n </div>\n </Button>\n\n <Button\n size=\"icon\"\n variant=\"ghost\"\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n e.stopPropagation();\n void onSave();\n }}\n >\n <div className=\"flex size-5 shrink-0 items-center justify-center rounded-[50%] bg-brand\">\n <CheckIcon className=\"size-3 stroke-[3px] text-background\" />\n </div>\n </Button>\n </div>\n )}\n </EditorContainer>\n </Plate>\n </div>\n </div>\n );\n}\n\nfunction CommentMoreDropdown(props: {\n comment: TComment;\n dropdownOpen: boolean;\n setDropdownOpen: React.Dispatch<React.SetStateAction<boolean>>;\n setEditingId: React.Dispatch<React.SetStateAction<string | null>>;\n onCloseAutoFocus?: () => void;\n onRemoveComment?: () => void;\n}) {\n const {\n comment,\n dropdownOpen,\n setDropdownOpen,\n setEditingId,\n onCloseAutoFocus,\n onRemoveComment,\n } = props;\n\n const editor = useEditorRef();\n\n const selectedEditCommentRef = React.useRef<boolean>(false);\n\n const onDeleteComment = React.useCallback(() => {\n if (!comment.id)\n return alert('You are operating too quickly, please try again later.');\n\n // Find and update the discussion\n const updatedDiscussions = editor\n .getOption(discussionPlugin, 'discussions')\n .map((discussion) => {\n if (discussion.id !== comment.discussionId) {\n return discussion;\n }\n\n const commentIndex = discussion.comments.findIndex(\n (c) => c.id === comment.id\n );\n if (commentIndex === -1) {\n return discussion;\n }\n\n return {\n ...discussion,\n comments: [\n ...discussion.comments.slice(0, commentIndex),\n ...discussion.comments.slice(commentIndex + 1),\n ],\n };\n });\n\n // Save back to session storage\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n onRemoveComment?.();\n }, [comment.discussionId, comment.id, editor, onRemoveComment]);\n\n const onEditComment = React.useCallback(() => {\n selectedEditCommentRef.current = true;\n\n if (!comment.id)\n return alert('You are operating too quickly, please try again later.');\n\n setEditingId(comment.id);\n }, [comment.id, setEditingId]);\n\n return (\n <DropdownMenu\n open={dropdownOpen}\n onOpenChange={setDropdownOpen}\n modal={false}\n >\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" className={cn('h-6 p-1 text-muted-foreground')}>\n <MoreHorizontalIcon className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-48\"\n onCloseAutoFocus={(e) => {\n if (selectedEditCommentRef.current) {\n onCloseAutoFocus?.();\n selectedEditCommentRef.current = false;\n }\n\n return e.preventDefault();\n }}\n >\n <DropdownMenuGroup>\n <DropdownMenuItem onClick={onEditComment}>\n <PencilIcon className=\"size-4\" />\n Edit comment\n </DropdownMenuItem>\n <DropdownMenuItem onClick={onDeleteComment}>\n <TrashIcon className=\"size-4\" />\n Delete comment\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nconst useCommentEditor = (\n options: Omit<CreatePlateEditorOptions, 'plugins'> = {},\n deps: any[] = []\n) => {\n const commentEditor = usePlateEditor(\n {\n id: 'comment',\n plugins: BasicMarksKit,\n value: [],\n ...options,\n },\n deps\n );\n\n return commentEditor;\n};\n\nexport function CommentCreateForm({\n autoFocus = false,\n className,\n discussionId: discussionIdProp,\n focusOnMount = false,\n}: {\n autoFocus?: boolean;\n className?: string;\n discussionId?: string;\n focusOnMount?: boolean;\n}) {\n const discussions = usePluginOption(discussionPlugin, 'discussions');\n\n const editor = useEditorRef();\n const commentId = useCommentId();\n const discussionId = discussionIdProp ?? commentId;\n\n const userInfo = usePluginOption(discussionPlugin, 'currentUser');\n const [commentValue, setCommentValue] = React.useState<Value | undefined>();\n const commentContent = React.useMemo(\n () =>\n commentValue\n ? NodeApi.string({ children: commentValue, type: KEYS.p })\n : '',\n [commentValue]\n );\n const commentEditor = useCommentEditor();\n\n React.useEffect(() => {\n if (commentEditor && focusOnMount) {\n commentEditor.tf.focus();\n }\n }, [commentEditor, focusOnMount]);\n\n const onAddComment = React.useCallback(async () => {\n if (!commentValue) return;\n\n commentEditor.tf.reset();\n\n if (discussionId) {\n // Get existing discussion\n const discussion = discussions.find((d) => d.id === discussionId);\n if (!discussion) {\n // Mock creating suggestion\n const newDiscussion: TDiscussion = {\n id: discussionId,\n comments: [\n {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n },\n ],\n createdAt: new Date(),\n isResolved: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n editor.setOption(discussionPlugin, 'discussions', [\n ...discussions,\n newDiscussion,\n ]);\n return;\n }\n\n // Create reply comment\n const comment: TComment = {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n // Add reply to discussion comments\n const updatedDiscussion = {\n ...discussion,\n comments: [...discussion.comments, comment],\n };\n\n // Filter out old discussion and add updated one\n const updatedDiscussions = discussions\n .filter((d) => d.id !== discussionId)\n .concat(updatedDiscussion);\n\n editor.setOption(discussionPlugin, 'discussions', updatedDiscussions);\n\n return;\n }\n\n const commentsNodeEntry = editor\n .getApi(CommentPlugin)\n .comment.nodes({ at: [], isDraft: true });\n\n if (commentsNodeEntry.length === 0) return;\n\n const documentContent = commentsNodeEntry\n .map(([node]) => node.text)\n .join('');\n\n const _discussionId = nanoid();\n // Mock creating new discussion\n const newDiscussion: TDiscussion = {\n id: _discussionId,\n comments: [\n {\n id: nanoid(),\n contentRich: commentValue,\n createdAt: new Date(),\n discussionId: _discussionId,\n isEdited: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n },\n ],\n createdAt: new Date(),\n documentContent,\n isResolved: false,\n userId: editor.getOption(discussionPlugin, 'currentUserId'),\n };\n\n editor.setOption(discussionPlugin, 'discussions', [\n ...discussions,\n newDiscussion,\n ]);\n\n const id = newDiscussion.id;\n\n commentsNodeEntry.forEach(([, path]) => {\n editor.tf.setNodes(\n {\n [getCommentKey(id)]: true,\n },\n { at: path, split: true }\n );\n editor.tf.unsetNodes([getDraftCommentKey()], { at: path });\n });\n }, [commentValue, commentEditor.tf, discussionId, editor, discussions]);\n\n return (\n <div className={cn('flex w-full', className)}>\n <div className=\"mt-2 mr-1 shrink-0\">\n {/* Replace to your own backend or refer to potion */}\n <Avatar className=\"size-5\">\n <AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} />\n <AvatarFallback>{userInfo?.name?.[0]}</AvatarFallback>\n </Avatar>\n </div>\n\n <div className=\"relative flex grow gap-2\">\n <Plate\n onChange={({ value }) => {\n setCommentValue(value);\n }}\n editor={commentEditor}\n >\n <EditorContainer variant=\"comment\">\n <Editor\n variant=\"comment\"\n className=\"min-h-[25px] grow pt-0.5 pr-8\"\n onKeyDown={(e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n onAddComment();\n }\n }}\n placeholder=\"Reply...\"\n autoComplete=\"off\"\n autoFocus={autoFocus}\n />\n\n <Button\n size=\"icon\"\n variant=\"ghost\"\n className=\"absolute right-0.5 bottom-0.5 ml-auto size-6 shrink-0\"\n disabled={commentContent.trim().length === 0}\n onClick={(e) => {\n e.stopPropagation();\n onAddComment();\n }}\n >\n <div className=\"flex size-6 items-center justify-center rounded-full\">\n <ArrowUpIcon />\n </div>\n </Button>\n </EditorContainer>\n </Plate>\n </div>\n </div>\n );\n}\n\nexport const formatCommentDate = (date: Date) => {\n const now = new Date();\n const diffMinutes = differenceInMinutes(now, date);\n const diffHours = differenceInHours(now, date);\n const diffDays = differenceInDays(now, date);\n\n if (diffMinutes < 60) {\n return `${diffMinutes}m`;\n }\n if (diffHours < 24) {\n return `${diffHours}h`;\n }\n if (diffDays < 2) {\n return `${diffDays}d`;\n }\n\n return format(date, 'MM/dd/yyyy');\n};\n", "type": "registry:ui" } ], diff --git a/apps/www/public/r/discussion-kit.json b/apps/www/public/r/discussion-kit.json index d4a3329098..8e52381a87 100644 --- a/apps/www/public/r/discussion-kit.json +++ b/apps/www/public/r/discussion-kit.json @@ -9,7 +9,7 @@ "files": [ { "path": "src/registry/components/editor/plugins/discussion-kit.tsx", - "content": "'use client';\n\nimport type { TComment } from '@/registry/ui/comment';\n\nimport { createPlatePlugin } from 'platejs/react';\n\nimport { BlockDiscussion } from '@/registry/ui/block-discussion';\n\nexport type TDiscussion = {\n id: string;\n comments: TComment[];\n createdAt: Date;\n isResolved: boolean;\n userId: string;\n documentContent?: string;\n};\n\nconst discussionsData: TDiscussion[] = [\n {\n id: 'discussion1',\n comments: [\n {\n id: 'comment1',\n contentRich: [\n {\n children: [\n {\n text: 'Comments are a great way to provide feedback and discuss changes.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 600_000),\n discussionId: 'discussion1',\n isEdited: false,\n userId: 'charlie',\n },\n {\n id: 'comment2',\n contentRich: [\n {\n children: [\n {\n text: 'Agreed! The link to the docs makes it easy to learn more.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 500_000),\n discussionId: 'discussion1',\n isEdited: false,\n userId: 'bob',\n },\n ],\n createdAt: new Date(),\n documentContent: 'comments',\n isResolved: false,\n userId: 'charlie',\n },\n {\n id: 'discussion2',\n comments: [\n {\n id: 'comment1',\n contentRich: [\n {\n children: [\n {\n text: 'Nice demonstration of overlapping annotations with both comments and suggestions!',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 300_000),\n discussionId: 'discussion2',\n isEdited: false,\n userId: 'bob',\n },\n {\n id: 'comment2',\n contentRich: [\n {\n children: [\n {\n text: 'This helps users understand how powerful the editor can be.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 200_000),\n discussionId: 'discussion2',\n isEdited: false,\n userId: 'charlie',\n },\n ],\n createdAt: new Date(),\n documentContent: 'overlapping',\n isResolved: false,\n userId: 'bob',\n },\n];\n\nconst avatarUrl = (seed: string) =>\n `https://api.dicebear.com/9.x/glass/svg?seed=${seed}`;\n\nconst usersData: Record<\n string,\n { id: string; avatarUrl: string; name: string; hue?: number }\n> = {\n alice: {\n id: 'alice',\n avatarUrl: avatarUrl('alice6'),\n name: 'Alice',\n },\n bob: {\n id: 'bob',\n avatarUrl: avatarUrl('bob4'),\n name: 'Bob',\n },\n charlie: {\n id: 'charlie',\n avatarUrl: avatarUrl('charlie2'),\n name: 'Charlie',\n },\n};\n\n// This plugin is purely UI. It's only used to store the discussions and users data\nexport const discussionPlugin = createPlatePlugin({\n key: 'discussion',\n options: {\n currentUserId: 'alice',\n discussions: discussionsData,\n users: usersData,\n },\n})\n .configure({\n render: { aboveNodes: BlockDiscussion },\n })\n .extendSelectors(({ getOption }) => ({\n currentUser: () => getOption('users')[getOption('currentUserId')],\n user: (id: string) => getOption('users')[id],\n }));\n\nexport const DiscussionKit = [discussionPlugin];\n", + "content": "'use client';\n\nimport type { TComment } from '@/registry/ui/comment';\n\nimport { createPlatePlugin } from 'platejs/react';\n\nimport { BlockDiscussion } from '@/registry/ui/block-discussion';\n\nexport type TDiscussion = {\n id: string;\n comments: TComment[];\n createdAt: Date;\n isResolved: boolean;\n userId: string;\n documentContent?: string;\n /** Direct author name from DOCX import (bypasses user lookup) */\n authorName?: string;\n /** Author initials from DOCX import */\n authorInitials?: string;\n /** OOXML paraId for round-trip DOCX threading fidelity */\n paraId?: string;\n};\n\nconst discussionsData: TDiscussion[] = [\n {\n id: 'discussion1',\n comments: [\n {\n id: 'comment1',\n contentRich: [\n {\n children: [\n {\n text: 'Comments are a great way to provide feedback and discuss changes.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 600_000),\n discussionId: 'discussion1',\n isEdited: false,\n userId: 'charlie',\n },\n {\n id: 'comment2',\n contentRich: [\n {\n children: [\n {\n text: 'Agreed! The link to the docs makes it easy to learn more.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 500_000),\n discussionId: 'discussion1',\n isEdited: false,\n userId: 'bob',\n },\n ],\n createdAt: new Date(),\n documentContent: 'comments',\n isResolved: false,\n userId: 'charlie',\n },\n {\n id: 'discussion2',\n comments: [\n {\n id: 'comment1',\n contentRich: [\n {\n children: [\n {\n text: 'Nice demonstration of overlapping annotations with both comments and suggestions!',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 300_000),\n discussionId: 'discussion2',\n isEdited: false,\n userId: 'bob',\n },\n {\n id: 'comment2',\n contentRich: [\n {\n children: [\n {\n text: 'This helps users understand how powerful the editor can be.',\n },\n ],\n type: 'p',\n },\n ],\n createdAt: new Date(Date.now() - 200_000),\n discussionId: 'discussion2',\n isEdited: false,\n userId: 'charlie',\n },\n ],\n createdAt: new Date(),\n documentContent: 'overlapping',\n isResolved: false,\n userId: 'bob',\n },\n];\n\nconst avatarUrl = (seed: string) =>\n `https://api.dicebear.com/9.x/glass/svg?seed=${seed}`;\n\nconst usersData: Record<\n string,\n { id: string; avatarUrl: string; name: string; hue?: number }\n> = {\n alice: {\n id: 'alice',\n avatarUrl: avatarUrl('alice6'),\n name: 'Alice',\n },\n bob: {\n id: 'bob',\n avatarUrl: avatarUrl('bob4'),\n name: 'Bob',\n },\n charlie: {\n id: 'charlie',\n avatarUrl: avatarUrl('charlie2'),\n name: 'Charlie',\n },\n};\n\n// This plugin is purely UI. It's only used to store the discussions and users data\nexport const discussionPlugin = createPlatePlugin({\n key: 'discussion',\n options: {\n currentUserId: 'alice',\n discussions: discussionsData,\n users: usersData,\n },\n})\n .configure({\n render: { aboveNodes: BlockDiscussion },\n })\n .extendSelectors(({ getOption }) => ({\n currentUser: () => getOption('users')[getOption('currentUserId')],\n user: (id: string) => getOption('users')[id],\n }));\n\nexport const DiscussionKit = [discussionPlugin];\n", "type": "registry:component" } ] diff --git a/apps/www/public/r/docx-export-kit.json b/apps/www/public/r/docx-export-kit.json index 7193ddeee5..6012b6e127 100644 --- a/apps/www/public/r/docx-export-kit.json +++ b/apps/www/public/r/docx-export-kit.json @@ -15,7 +15,7 @@ "files": [ { "path": "src/registry/components/editor/plugins/docx-export-kit.tsx", - "content": "/**\n * Editor kit optimized for DOCX export.\n *\n * Uses docx-specific static components for elements that require\n * inline styles instead of Tailwind classes (which don't work in DOCX):\n * - Code blocks: Need inline syntax highlighting colors and line breaks\n * - Columns: Need table layout instead of flexbox\n * - Equations: Need inline font styling (KaTeX doesn't work in DOCX)\n * - Callouts: Need table layout for icon + content\n * - TOC: Need anchor links with proper paragraph breaks\n *\n * Tables use base version with juice CSS inlining.\n */\n\nimport { CalloutElementDocx } from '@/registry/ui/callout-node-static';\nimport {\n CodeBlockElementDocx,\n CodeLineElementDocx,\n CodeSyntaxLeafDocx,\n} from '@/registry/ui/code-block-node-static';\nimport {\n ColumnElementDocx,\n ColumnGroupElementDocx,\n} from '@/registry/ui/column-node-static';\nimport {\n EquationElementDocx,\n InlineEquationElementDocx,\n} from '@/registry/ui/equation-node-static';\nimport { TocElementDocx } from '@/registry/ui/toc-node-static';\nimport { DocxExportPlugin } from '@platejs/docx-io';\nimport { KEYS } from 'platejs';\n\n/**\n * Editor kit for DOCX export.\n *\n * Uses standard static components for most elements (with juice CSS inlining),\n * but uses docx-specific components for elements that need special handling:\n * - Code blocks (syntax highlighting, line breaks)\n * - Columns (table layout instead of flexbox)\n * - Equations (inline font instead of KaTeX)\n * - Callouts (table layout for icon placement)\n * - TOC (anchor links with paragraph breaks)\n *\n * Tables use base version with juice CSS inlining.\n */\nexport const DocxExportKit = [\n DocxExportPlugin.configure({\n override: {\n components: {\n [KEYS.codeBlock]: CodeBlockElementDocx,\n [KEYS.codeLine]: CodeLineElementDocx,\n [KEYS.codeSyntax]: CodeSyntaxLeafDocx,\n [KEYS.column]: ColumnElementDocx,\n [KEYS.columnGroup]: ColumnGroupElementDocx,\n [KEYS.equation]: EquationElementDocx,\n [KEYS.inlineEquation]: InlineEquationElementDocx,\n [KEYS.callout]: CalloutElementDocx,\n [KEYS.toc]: TocElementDocx,\n },\n },\n }),\n];\n", + "content": "/**\n * Editor kit optimized for DOCX export.\n *\n * Uses docx-specific static components for elements that require\n * inline styles instead of Tailwind classes (which don't work in DOCX):\n * - Code blocks: Need inline syntax highlighting colors and line breaks\n * - Columns: Need table layout instead of flexbox\n * - Equations: Need inline font styling (KaTeX doesn't work in DOCX)\n * - Callouts: Need table layout for icon + content\n * - TOC: Need anchor links with proper paragraph breaks\n *\n * Tables use base version with juice CSS inlining.\n */\n\nimport { CalloutElementDocx } from '@/registry/ui/callout-node-static';\nimport {\n CodeBlockElementDocx,\n CodeLineElementDocx,\n CodeSyntaxLeafDocx,\n} from '@/registry/ui/code-block-node-static';\nimport {\n ColumnElementDocx,\n ColumnGroupElementDocx,\n} from '@/registry/ui/column-node-static';\nimport {\n EquationElementDocx,\n InlineEquationElementDocx,\n} from '@/registry/ui/equation-node-static';\nimport { SuggestionLeafDocx } from '@/registry/ui/suggestion-node-docx';\nimport { TocElementDocx } from '@/registry/ui/toc-node-static';\nimport { DocxExportPlugin } from '@platejs/docx-io';\nimport { KEYS } from 'platejs';\n\n/**\n * Editor kit for DOCX export.\n *\n * Uses standard static components for most elements (with juice CSS inlining),\n * but uses docx-specific components for elements that need special handling:\n * - Code blocks (syntax highlighting, line breaks)\n * - Columns (table layout instead of flexbox)\n * - Equations (inline font instead of KaTeX)\n * - Callouts (table layout for icon placement)\n * - TOC (anchor links with paragraph breaks)\n * - Suggestions (<span> instead of <ins>/<del> to avoid unwanted formatting)\n *\n * Tables use base version with juice CSS inlining.\n */\nexport const DocxExportKit = [\n DocxExportPlugin.configure({\n override: {\n components: {\n [KEYS.codeBlock]: CodeBlockElementDocx,\n [KEYS.codeLine]: CodeLineElementDocx,\n [KEYS.codeSyntax]: CodeSyntaxLeafDocx,\n [KEYS.column]: ColumnElementDocx,\n [KEYS.columnGroup]: ColumnGroupElementDocx,\n [KEYS.equation]: EquationElementDocx,\n [KEYS.inlineEquation]: InlineEquationElementDocx,\n [KEYS.callout]: CalloutElementDocx,\n [KEYS.toc]: TocElementDocx,\n [KEYS.suggestion]: SuggestionLeafDocx,\n },\n },\n }),\n];\n", "type": "registry:component" } ] diff --git a/apps/www/public/r/export-toolbar-button.json b/apps/www/public/r/export-toolbar-button.json index 54654c38da..88c889f7fa 100644 --- a/apps/www/public/r/export-toolbar-button.json +++ b/apps/www/public/r/export-toolbar-button.json @@ -18,7 +18,7 @@ "files": [ { "path": "src/registry/ui/export-toolbar-button.tsx", - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { exportToDocx } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { ArrowDownToLineIcon } from 'lucide-react';\nimport type { SlatePlugin } from 'platejs';\nimport { createSlateEditor } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\nimport { serializeHtml } from 'platejs/static';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { BaseEditorKit } from '@/registry/components/editor/editor-base-kit';\n\nimport { EditorStatic } from './editor-static';\nimport { ToolbarButton } from './toolbar';\nimport { DocxExportKit } from '@/registry/components/editor/plugins/docx-export-kit';\n\nconst siteUrl = 'https://platejs.org';\n\nexport function ExportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getCanvas = async () => {\n const { default: html2canvas } = await import('html2canvas-pro');\n\n const style = document.createElement('style');\n document.head.append(style);\n\n const canvas = await html2canvas(editor.api.toDOMNode(editor)!, {\n onclone: (document: Document) => {\n const editorElement = document.querySelector(\n '[contenteditable=\"true\"]'\n );\n if (editorElement) {\n Array.from(editorElement.querySelectorAll('*')).forEach((element) => {\n const existingStyle = element.getAttribute('style') || '';\n element.setAttribute(\n 'style',\n `${existingStyle}; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif !important`\n );\n });\n }\n },\n });\n style.remove();\n\n return canvas;\n };\n\n const downloadFile = async (url: string, filename: string) => {\n const response = await fetch(url);\n\n const blob = await response.blob();\n const blobUrl = window.URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = filename;\n document.body.append(link);\n link.click();\n link.remove();\n\n // Clean up the blob URL\n window.URL.revokeObjectURL(blobUrl);\n };\n\n const exportToPdf = async () => {\n const canvas = await getCanvas();\n\n const PDFLib = await import('pdf-lib');\n const pdfDoc = await PDFLib.PDFDocument.create();\n const page = pdfDoc.addPage([canvas.width, canvas.height]);\n const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));\n const { height, width } = imageEmbed.scale(1);\n page.drawImage(imageEmbed, {\n height,\n width,\n x: 0,\n y: 0,\n });\n const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });\n\n await downloadFile(pdfBase64, 'plate.pdf');\n };\n\n const exportToImage = async () => {\n const canvas = await getCanvas();\n await downloadFile(canvas.toDataURL('image/png'), 'plate.png');\n };\n\n const exportToHtml = async () => {\n const editorStatic = createSlateEditor({\n plugins: BaseEditorKit,\n value: editor.children,\n });\n\n const editorHtml = await serializeHtml(editorStatic, {\n editorComponent: EditorStatic,\n props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },\n });\n\n const tailwindCss = `<link rel=\"stylesheet\" href=\"${siteUrl}/tailwind.css\">`;\n const katexCss = `<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css\" integrity=\"sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV\" crossorigin=\"anonymous\">`;\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link\n href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap\"\n rel=\"stylesheet\"\n />\n ${tailwindCss}\n ${katexCss}\n <style>\n :root {\n --font-sans: 'Inter', 'Inter Fallback';\n --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';\n }\n </style>\n </head>\n <body>\n ${editorHtml}\n </body>\n </html>`;\n\n const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;\n\n await downloadFile(url, 'plate.html');\n };\n\n const exportToMarkdown = async () => {\n const md = editor.getApi(MarkdownPlugin).markdown.serialize();\n const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;\n await downloadFile(url, 'plate.md');\n };\n\n const exportToWord = async () => {\n const blob = await exportToDocx(editor.children, {\n editorPlugins: [...BaseEditorKit, ...DocxExportKit] as SlatePlugin[],\n });\n\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = 'plate.docx';\n document.body.append(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n };\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Export\" isDropdown>\n <ArrowDownToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem onSelect={exportToHtml}>\n Export as HTML\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToPdf}>\n Export as PDF\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToImage}>\n Export as Image\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToMarkdown}>\n Export as Markdown\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToWord}>\n Export as Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { exportToDocx, type DocxExportDiscussion } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { ArrowDownToLineIcon } from 'lucide-react';\nimport type { SlatePlugin } from 'platejs';\nimport { createSlateEditor } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\nimport { serializeHtml } from 'platejs/static';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { BaseEditorKit } from '@/registry/components/editor/editor-base-kit';\nimport { discussionPlugin } from '@/registry/components/editor/plugins/discussion-kit';\n\nimport { EditorStatic } from './editor-static';\nimport { ToolbarButton } from './toolbar';\nimport { DocxExportKit } from '@/registry/components/editor/plugins/docx-export-kit';\n\nconst siteUrl = 'https://platejs.org';\n\nexport function ExportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getCanvas = async () => {\n const { default: html2canvas } = await import('html2canvas-pro');\n\n const style = document.createElement('style');\n document.head.append(style);\n\n const canvas = await html2canvas(editor.api.toDOMNode(editor)!, {\n onclone: (document: Document) => {\n const editorElement = document.querySelector(\n '[contenteditable=\"true\"]'\n );\n if (editorElement) {\n Array.from(editorElement.querySelectorAll('*')).forEach((element) => {\n const existingStyle = element.getAttribute('style') || '';\n element.setAttribute(\n 'style',\n `${existingStyle}; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif !important`\n );\n });\n }\n },\n });\n style.remove();\n\n return canvas;\n };\n\n const downloadFile = async (url: string, filename: string) => {\n const response = await fetch(url);\n\n const blob = await response.blob();\n const blobUrl = window.URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = blobUrl;\n link.download = filename;\n document.body.append(link);\n link.click();\n link.remove();\n\n // Clean up the blob URL\n window.URL.revokeObjectURL(blobUrl);\n };\n\n const exportToPdf = async () => {\n const canvas = await getCanvas();\n\n const PDFLib = await import('pdf-lib');\n const pdfDoc = await PDFLib.PDFDocument.create();\n const page = pdfDoc.addPage([canvas.width, canvas.height]);\n const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG'));\n const { height, width } = imageEmbed.scale(1);\n page.drawImage(imageEmbed, {\n height,\n width,\n x: 0,\n y: 0,\n });\n const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });\n\n await downloadFile(pdfBase64, 'plate.pdf');\n };\n\n const exportToImage = async () => {\n const canvas = await getCanvas();\n await downloadFile(canvas.toDataURL('image/png'), 'plate.png');\n };\n\n const exportToHtml = async () => {\n const editorStatic = createSlateEditor({\n plugins: BaseEditorKit,\n value: editor.children,\n });\n\n const editorHtml = await serializeHtml(editorStatic, {\n editorComponent: EditorStatic,\n props: { style: { padding: '0 calc(50% - 350px)', paddingBottom: '' } },\n });\n\n const tailwindCss = `<link rel=\"stylesheet\" href=\"${siteUrl}/tailwind.css\">`;\n const katexCss = `<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/katex@0.16.18/dist/katex.css\" integrity=\"sha384-9PvLvaiSKCPkFKB1ZsEoTjgnJn+O3KvEwtsz37/XrkYft3DTk2gHdYvd9oWgW3tV\" crossorigin=\"anonymous\">`;\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link\n href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400..700&family=JetBrains+Mono:wght@400..700&display=swap\"\n rel=\"stylesheet\"\n />\n ${tailwindCss}\n ${katexCss}\n <style>\n :root {\n --font-sans: 'Inter', 'Inter Fallback';\n --font-mono: 'JetBrains Mono', 'JetBrains Mono Fallback';\n }\n </style>\n </head>\n <body>\n ${editorHtml}\n </body>\n </html>`;\n\n const url = `data:text/html;charset=utf-8,${encodeURIComponent(html)}`;\n\n await downloadFile(url, 'plate.html');\n };\n\n const exportToMarkdown = async () => {\n const md = editor.getApi(MarkdownPlugin).markdown.serialize();\n const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;\n await downloadFile(url, 'plate.md');\n };\n\n const exportToWord = async () => {\n // Get discussions and users from the discussion plugin for comment export\n const discussions = editor.getOption(discussionPlugin, 'discussions') ?? [];\n const users = editor.getOption(discussionPlugin, 'users') ?? {};\n\n // Resolve display name: prefer authorName (from DOCX import), fall back to users lookup\n const resolveUser = (\n userId: string,\n authorName?: string\n ): { id: string; name: string } | undefined => {\n const name = authorName ?? users[userId]?.name;\n return name ? { id: userId, name } : undefined;\n };\n\n // Convert discussions to export format\n const exportDiscussions: DocxExportDiscussion[] = discussions.map((d) => ({\n id: d.id,\n comments: d.comments?.map((c) => ({\n contentRich: c.contentRich,\n createdAt: c.createdAt,\n id: c.id,\n paraId: c.paraId,\n parentParaId: c.parentParaId,\n userId: c.userId,\n user: resolveUser(c.userId, c.authorName),\n })),\n createdAt: d.createdAt,\n documentContent: d.documentContent,\n paraId: d.paraId,\n userId: d.userId,\n user: resolveUser(d.userId, d.authorName),\n }));\n\n const blob = await exportToDocx(editor.children, {\n editorPlugins: [...BaseEditorKit, ...DocxExportKit] as SlatePlugin[],\n tracking: {\n discussions: exportDiscussions,\n },\n });\n\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = 'plate.docx';\n document.body.append(link);\n link.click();\n link.remove();\n URL.revokeObjectURL(url);\n };\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Export\" isDropdown>\n <ArrowDownToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem onSelect={exportToHtml}>\n Export as HTML\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToPdf}>\n Export as PDF\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToImage}>\n Export as Image\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToMarkdown}>\n Export as Markdown\n </DropdownMenuItem>\n <DropdownMenuItem onSelect={exportToWord}>\n Export as Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", "type": "registry:ui" } ], diff --git a/apps/www/public/r/import-toolbar-button.json b/apps/www/public/r/import-toolbar-button.json index 1e1c1bb8f9..d93884a095 100644 --- a/apps/www/public/r/import-toolbar-button.json +++ b/apps/www/public/r/import-toolbar-button.json @@ -14,7 +14,7 @@ "files": [ { "path": "src/registry/ui/import-toolbar-button.tsx", - "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { importDocx } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { ArrowUpToLineIcon } from 'lucide-react';\nimport { getEditorDOMFromHtmlString } from 'platejs/static';\nimport { useEditorRef } from 'platejs/react';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\n\nimport { ToolbarButton } from './toolbar';\n\ntype ImportType = 'html' | 'markdown';\n\nexport function ImportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getFileNodes = (text: string, type: ImportType) => {\n if (type === 'html') {\n const editorNode = getEditorDOMFromHtmlString(text);\n const nodes = editor.api.html.deserialize({\n element: editorNode,\n });\n\n return nodes;\n }\n\n if (type === 'markdown') {\n return editor.getApi(MarkdownPlugin).markdown.deserialize(text);\n }\n\n return [];\n };\n\n const { openFilePicker: openMdFilePicker } = useFilePicker({\n accept: ['.md', '.mdx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'markdown');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openHtmlFilePicker } = useFilePicker({\n accept: ['text/html'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'html');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openDocxFilePicker } = useFilePicker({\n accept: ['.docx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const arrayBuffer = await plainFiles[0].arrayBuffer();\n const result = await importDocx(editor, arrayBuffer);\n\n editor.tf.insertNodes(result.nodes as typeof editor.children);\n },\n });\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Import\" isDropdown>\n <ArrowUpToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem\n onSelect={() => {\n openHtmlFilePicker();\n }}\n >\n Import from HTML\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openMdFilePicker();\n }}\n >\n Import from Markdown\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openDocxFilePicker();\n }}\n >\n Import from Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", + "content": "'use client';\n\nimport * as React from 'react';\n\nimport type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu';\n\nimport { getCommentKey } from '@platejs/comment';\nimport { importDocxWithTracking } from '@platejs/docx-io';\nimport { MarkdownPlugin } from '@platejs/markdown';\nimport { getSuggestionKey } from '@platejs/suggestion';\nimport { ArrowUpToLineIcon } from 'lucide-react';\nimport { KEYS, TextApi } from 'platejs';\nimport { useEditorRef } from 'platejs/react';\nimport { getEditorDOMFromHtmlString } from 'platejs/static';\nimport { useFilePicker } from 'use-file-picker';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\n\nimport { commentPlugin } from '@/registry/components/editor/plugins/comment-kit';\nimport {\n discussionPlugin,\n type TDiscussion,\n} from '@/registry/components/editor/plugins/discussion-kit';\nimport { getDiscussionCounterSeed } from '../lib/discussion-ids';\nimport { ToolbarButton } from './toolbar';\n\ntype ImportType = 'html' | 'markdown';\n\nconst WHITESPACE_REGEX = /\\s+/;\n\nexport function ImportToolbarButton(props: DropdownMenuProps) {\n const editor = useEditorRef();\n const [open, setOpen] = React.useState(false);\n\n const getFileNodes = (text: string, type: ImportType) => {\n if (type === 'html') {\n const editorNode = getEditorDOMFromHtmlString(text);\n const nodes = editor.api.html.deserialize({\n element: editorNode,\n });\n\n return nodes;\n }\n\n if (type === 'markdown') {\n return editor.getApi(MarkdownPlugin).markdown.deserialize(text);\n }\n\n return [];\n };\n\n const { openFilePicker: openMdFilePicker } = useFilePicker({\n accept: ['.md', '.mdx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'markdown');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openHtmlFilePicker } = useFilePicker({\n accept: ['text/html'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const text = await plainFiles[0].text();\n\n const nodes = getFileNodes(text, 'html');\n\n editor.tf.insertNodes(nodes);\n },\n });\n\n const { openFilePicker: openDocxFilePicker } = useFilePicker({\n accept: ['.docx'],\n multiple: false,\n onFilesSelected: async ({ plainFiles }) => {\n const arrayBuffer = await plainFiles[0].arrayBuffer();\n\n // Compute next discussion number to avoid ID collisions\n const existingDiscussions =\n editor.getOption(discussionPlugin, 'discussions') ?? [];\n let discussionCounter = getDiscussionCounterSeed(existingDiscussions);\n\n // Import with full tracking support (suggestions + comments)\n const result = await importDocxWithTracking(editor as any, arrayBuffer, {\n suggestionKey: KEYS.suggestion,\n getSuggestionKey,\n commentKey: KEYS.comment,\n getCommentKey,\n isText: TextApi.isText,\n generateId: () => `discussion${++discussionCounter}`,\n });\n\n // Register imported users so suggestion/comment UI can resolve them\n if (result.users.length > 0) {\n const existingUsers = editor.getOption(discussionPlugin, 'users') ?? {};\n const updatedUsers = { ...existingUsers };\n\n for (const user of result.users) {\n if (!updatedUsers[user.id]) {\n updatedUsers[user.id] = {\n id: user.id,\n name: user.name,\n avatarUrl: `https://api.dicebear.com/9.x/glass/svg?seed=${encodeURIComponent(user.name)}`,\n };\n }\n }\n\n editor.setOption(discussionPlugin, 'users', updatedUsers);\n }\n\n // Convert imported discussions to TDiscussion format (empty array if none)\n const newDiscussions: TDiscussion[] = (result.discussions ?? []).map(\n (d) => ({\n id: d.id,\n comments: (d.comments ?? []).map((c, index) => ({\n id: c.id || `comment${index + 1}`,\n contentRich:\n c.contentRich as TDiscussion['comments'][number]['contentRich'],\n createdAt: c.createdAt ?? new Date(),\n discussionId: d.id,\n isEdited: false,\n userId: c.userId ?? c.user?.id ?? 'imported-unknown',\n authorName: c.user?.name,\n authorInitials: c.user?.name\n ? c.user.name\n .split(WHITESPACE_REGEX)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('')\n : undefined,\n paraId: c.paraId,\n parentParaId: c.parentParaId,\n })),\n createdAt: d.createdAt ?? new Date(),\n documentContent: d.documentContent,\n isResolved: false,\n userId: d.userId ?? d.user?.id ?? 'imported-unknown',\n authorName: d.user?.name,\n authorInitials: d.user?.name\n ? d.user.name\n .split(WHITESPACE_REGEX)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('')\n : undefined,\n paraId: d.paraId,\n })\n );\n\n // Replace all discussions (not append) because importDocxWithTracking\n // replaces the entire editor content, making old discussions stale\n editor.setOption(discussionPlugin, 'discussions', newDiscussions);\n editor.setOption(commentPlugin, 'uniquePathMap', new Map());\n\n // Log import results in dev only\n if (\n result.hasTracking &&\n result.errors.length > 0 &&\n process.env.NODE_ENV !== 'production'\n ) {\n console.warn('[DOCX Import] Errors:', result.errors);\n }\n },\n });\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen} modal={false} {...props}>\n <DropdownMenuTrigger asChild>\n <ToolbarButton pressed={open} tooltip=\"Import\" isDropdown>\n <ArrowUpToLineIcon className=\"size-4\" />\n </ToolbarButton>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent align=\"start\">\n <DropdownMenuGroup>\n <DropdownMenuItem\n onSelect={() => {\n openHtmlFilePicker();\n }}\n >\n Import from HTML\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openMdFilePicker();\n }}\n >\n Import from Markdown\n </DropdownMenuItem>\n\n <DropdownMenuItem\n onSelect={() => {\n openDocxFilePicker();\n }}\n >\n Import from Word\n </DropdownMenuItem>\n </DropdownMenuGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n", "type": "registry:ui" } ], diff --git a/apps/www/public/r/table-node.json b/apps/www/public/r/table-node.json index bb8d629595..5280f2381c 100644 --- a/apps/www/public/r/table-node.json +++ b/apps/www/public/r/table-node.json @@ -30,7 +30,7 @@ }, { "path": "src/registry/ui/table-node-static.tsx", - "content": "import * as React from 'react';\n\nimport type { TTableCellElement, TTableElement } from 'platejs';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { BaseTablePlugin } from '@platejs/table';\nimport { SlateElement } from 'platejs/static';\n\nimport { cn } from '@/lib/utils';\n\nexport function TableElementStatic({\n children,\n ...props\n}: SlateElementProps<TTableElement>) {\n const { disableMarginLeft } = props.editor.getOptions(BaseTablePlugin);\n const marginLeft = disableMarginLeft ? 0 : props.element.marginLeft;\n\n return (\n <SlateElement\n {...props}\n className=\"overflow-x-auto py-5\"\n style={{ paddingLeft: marginLeft }}\n >\n <div className=\"group/table relative w-fit\">\n <table\n className=\"mr-0 ml-px table h-px table-fixed border-collapse\"\n style={{ borderCollapse: 'collapse', width: '100%' }}\n >\n <tbody className=\"min-w-full\">{children}</tbody>\n </table>\n </div>\n </SlateElement>\n );\n}\n\nexport function TableRowElementStatic(props: SlateElementProps) {\n return (\n <SlateElement {...props} as=\"tr\" className=\"h-full\">\n {props.children}\n </SlateElement>\n );\n}\n\nexport function TableCellElementStatic({\n isHeader,\n ...props\n}: SlateElementProps<TTableCellElement> & {\n isHeader?: boolean;\n}) {\n const { editor, element } = props;\n const { api } = editor.getPlugin(BaseTablePlugin);\n\n const { minHeight, width } = api.table.getCellSize({ element });\n const borders = api.table.getCellBorders({ element });\n\n return (\n <SlateElement\n {...props}\n as={isHeader ? 'th' : 'td'}\n className={cn(\n 'h-full overflow-visible border-none bg-background p-0',\n element.background ? 'bg-(--cellBackground)' : 'bg-background',\n isHeader && 'text-left font-normal *:m-0',\n 'before:size-full',\n \"before:absolute before:box-border before:select-none before:content-['']\",\n borders &&\n cn(\n borders.bottom?.size && 'before:border-b before:border-b-border',\n borders.right?.size && 'before:border-r before:border-r-border',\n borders.left?.size && 'before:border-l before:border-l-border',\n borders.top?.size && 'before:border-t before:border-t-border'\n )\n )}\n style={\n {\n '--cellBackground': element.background,\n maxWidth: width || 240,\n minWidth: width || 120,\n } as React.CSSProperties\n }\n attributes={{\n ...props.attributes,\n colSpan: api.table.getColSpan(element),\n rowSpan: api.table.getRowSpan(element),\n }}\n >\n <div\n className=\"relative z-20 box-border h-full px-4 py-2\"\n style={{ minHeight }}\n >\n {props.children}\n </div>\n </SlateElement>\n );\n}\n\nexport function TableCellHeaderElementStatic(\n props: SlateElementProps<TTableCellElement>\n) {\n return <TableCellElementStatic {...props} isHeader />;\n}\n", + "content": "import * as React from 'react';\n\nimport type { TTableCellElement, TTableElement } from 'platejs';\nimport type { SlateElementProps } from 'platejs/static';\n\nimport { BaseTablePlugin } from '@platejs/table';\nimport { SlateElement } from 'platejs/static';\n\nimport { cn } from '@/lib/utils';\n\nexport function TableElementStatic({\n children,\n ...props\n}: SlateElementProps<TTableElement>) {\n const { disableMarginLeft } = props.editor.getOptions(BaseTablePlugin);\n const marginLeft = disableMarginLeft ? 0 : props.element.marginLeft;\n\n return (\n <SlateElement\n {...props}\n className=\"overflow-x-auto py-5\"\n style={{ paddingLeft: marginLeft }}\n >\n <div className=\"group/table relative w-fit\">\n <table\n className=\"mr-0 ml-px table h-px table-fixed border-collapse\"\n style={{ borderCollapse: 'collapse', width: '100%' }}\n >\n <tbody className=\"min-w-full\">{children}</tbody>\n </table>\n </div>\n </SlateElement>\n );\n}\n\nexport function TableRowElementStatic(props: SlateElementProps) {\n return (\n <SlateElement {...props} as=\"tr\" className=\"h-full\">\n {props.children}\n </SlateElement>\n );\n}\n\n/** Build inline border styles for DOCX export (all 4 sides per cell). */\nconst cellBorderStyles = (\n element: TTableCellElement\n): Record<string, string> => {\n const b = element.borders;\n if (!b) return {};\n\n const fmt = (dir: 'bottom' | 'left' | 'right' | 'top') => {\n const border = b[dir];\n if (!border || !border.size) return;\n return `${border.size}px ${border.style || 'solid'} ${border.color || '#000'}`;\n };\n\n const styles: Record<string, string> = {};\n const top = fmt('top');\n const right = fmt('right');\n const bottom = fmt('bottom');\n const left = fmt('left');\n\n if (top) styles.borderTop = top;\n if (right) styles.borderRight = right;\n if (bottom) styles.borderBottom = bottom;\n if (left) styles.borderLeft = left;\n\n return styles;\n};\n\nexport function TableCellElementStatic({\n isHeader,\n ...props\n}: SlateElementProps<TTableCellElement> & {\n isHeader?: boolean;\n}) {\n const { editor, element } = props;\n const { api } = editor.getPlugin(BaseTablePlugin);\n\n const { minHeight, width } = api.table.getCellSize({ element });\n const borders = api.table.getCellBorders({ element });\n\n return (\n <SlateElement\n {...props}\n as={isHeader ? 'th' : 'td'}\n className={cn(\n 'h-full overflow-visible border-none bg-background p-0',\n element.background ? 'bg-(--cellBackground)' : 'bg-background',\n isHeader && 'text-left font-normal *:m-0',\n 'before:size-full',\n \"before:absolute before:box-border before:select-none before:content-['']\",\n borders &&\n cn(\n borders.bottom?.size && 'before:border-b before:border-b-border',\n borders.right?.size && 'before:border-r before:border-r-border',\n borders.left?.size && 'before:border-l before:border-l-border',\n borders.top?.size && 'before:border-t before:border-t-border'\n )\n )}\n style={\n {\n '--cellBackground': element.background,\n ...(element.background\n ? { backgroundColor: element.background }\n : {}),\n maxWidth: width || 240,\n minWidth: width || 120,\n ...cellBorderStyles(element),\n } as React.CSSProperties\n }\n attributes={{\n ...props.attributes,\n colSpan: api.table.getColSpan(element),\n rowSpan: api.table.getRowSpan(element),\n }}\n >\n <div\n className=\"relative z-20 box-border h-full px-4 py-2\"\n style={{ minHeight }}\n >\n {props.children}\n </div>\n </SlateElement>\n );\n}\n\nexport function TableCellHeaderElementStatic(\n props: SlateElementProps<TTableCellElement>\n) {\n return <TableCellElementStatic {...props} isHeader />;\n}\n", "type": "registry:ui" } ], diff --git a/apps/www/public/tailwind.css b/apps/www/public/tailwind.css index bdfd60ad33..7d797ef872 100644 --- a/apps/www/public/tailwind.css +++ b/apps/www/public/tailwind.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-font-weight:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:"var(--font-sans)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-900:oklch(40.5% .101 131.063);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-950:oklch(30.2% .056 229.695);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-sm:24rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-tight:1.25;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:calc(var(--radius) - 4px);--radius-lg:var(--radius);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-xs:4px;--blur-sm:8px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:"var(--font-sans)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--default-mono-font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-code:var(--surface);--color-code-foreground:var(--surface-foreground);--color-code-highlight:var(--code-highlight);--color-code-number:var(--code-number);--font-heading:"var(--font-heading)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}}@layer base{@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab,var(--ring)50%,transparent)}}body{overscroll-behavior:none;background-color:var(--background);color:var(--foreground);font-synthesis-weight:none;text-rendering:optimizeLegibility}@supports (font:-apple-system-body) and (appearance:none){@media (min-width:1800px){[data-wrapper]{border-top-style:var(--tw-border-style);border-top-width:1px}}}a:active,button:active{opacity:.6}@media (min-width:48rem){a:active,button:active{opacity:1}}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:hsl(var(--border));border-radius:5px}*{scrollbar-width:thin;scrollbar-color:hsl(var(--border))transparent}.prose{--tw-prose-body:var(--foreground);--tw-prose-bold:inherit;--tw-prose-links:inherit;--tw-prose-bullets:var(--foreground)}[data-theme=light]{display:block}[data-theme=dark],.dark [data-theme=light]{display:none}.dark [data-theme=dark]{display:block}[data-rehype-pretty-code-fragment]{color:var(--color-white);position:relative}[data-rehype-pretty-code-fragment] code{border-style:var(--tw-border-style);min-width:100%;padding:calc(var(--spacing)*0);overflow-wrap:break-word;counter-reset:line;-webkit-box-decoration-break:clone;box-decoration-break:clone;background-color:#0000;border-width:0;border-radius:0;display:grid}[data-rehype-pretty-code-fragment] .line{width:100%;min-height:1rem;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*.5);display:inline-block}[data-rehype-pretty-code-fragment] [data-line-numbers] .line{padding-inline:calc(var(--spacing)*2)}[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:#fafafa66}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{color:color-mix(in oklab,var(--color-zinc-50)40%,transparent)}}[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{counter-increment:line;content:counter(line);text-align:right;width:1.8rem;margin-right:1.4rem;display:inline-block}[data-rehype-pretty-code-fragment] .line--highlighted{background-color:#3f3f4680}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .line--highlighted{background-color:color-mix(in oklab,var(--color-zinc-700)50%,transparent)}}[data-rehype-pretty-code-fragment] .line-highlighted span{position:relative}[data-rehype-pretty-code-fragment] .word--highlighted{border-radius:calc(var(--radius) - 2px);border-color:#3f3f46b3}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .word--highlighted{border-color:color-mix(in oklab,var(--color-zinc-700)70%,transparent)}}[data-rehype-pretty-code-fragment] .word--highlighted{background-color:#3f3f4680}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .word--highlighted{background-color:color-mix(in oklab,var(--color-zinc-700)50%,transparent)}}[data-rehype-pretty-code-fragment] .word--highlighted{padding:calc(var(--spacing)*1)}.dark [data-rehype-pretty-code-fragment] .word--highlighted{background-color:var(--color-zinc-900)}[data-rehype-pretty-code-title]{margin-top:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);padding-top:calc(var(--spacing)*6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground)}[data-rehype-pretty-code-title]+pre{margin-top:calc(var(--spacing)*2)!important}}@layer components{.steps:first-child,.steps:first-child>h3:first-child{margin-top:calc(var(--spacing)*0)!important}.steps>h3{margin-top:calc(var(--spacing)*16)!important}.steps>h3+p{margin-top:calc(var(--spacing)*2)!important}[data-rehype-pretty-code-figure]{background-color:var(--color-code);color:var(--color-code-foreground);border-radius:var(--radius-lg);border-width:0;border-color:var(--border);margin-top:calc(var(--spacing)*6);font-size:var(--text-sm);outline:none;position:relative;overflow:hidden}@media (min-width:48rem){[data-rehype-pretty-code-figure]{margin-inline:calc(var(--spacing)*-4)}}[data-rehype-pretty-code-figure]:has([data-rehype-pretty-code-title]) [data-slot=copy-button]{top:calc(var(--spacing)*1.5)!important}[data-rehype-pretty-code-title]{border-bottom:var(--border)}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-title]{border-bottom:color-mix(in oklab,var(--border)30%,transparent)}}[data-rehype-pretty-code-title]{padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*4);font-size:var(--text-sm);font-family:var(--font-mono);color:var(--color-code-foreground);border-bottom-style:solid;border-bottom-width:1px}[data-line-numbers]{white-space:pre;counter-reset:line;-webkit-box-decoration-break:clone;box-decoration-break:clone;background:0 0;border:0;min-width:100%;padding:0;display:grid}[data-line-numbers] [data-line]:before{font-size:var(--text-sm);counter-increment:line;content:counter(line);width:calc(var(--spacing)*16);padding-right:calc(var(--spacing)*6);text-align:right;color:var(--color-code-number);background-color:var(--color-code);display:inline-block;position:sticky;left:0}[data-line-numbers] [data-highlighted-line][data-line]:before{background-color:var(--color-code-highlight)}[data-line]{padding-top:calc(var(--spacing)*.5);padding-bottom:calc(var(--spacing)*.5);min-height:calc(var(--spacing)*1);width:100%;display:inline-block}[data-line] span{color:var(--shiki-light)}[data-line] span:is(.dark *){color:var(--shiki-dark)!important}[data-highlighted-line],[data-highlighted-chars]{background-color:var(--color-code-highlight);position:relative}[data-highlighted-line]:after{content:"";background-color:var(--muted-foreground);width:2px;height:100%;position:absolute;top:0;left:0}@supports (color:color-mix(in lab, red, red)){[data-highlighted-line]:after{background-color:color-mix(in oklab,var(--muted-foreground)50%,transparent)}}[data-highlighted-chars]{border-radius:var(--radius-sm);font-family:var(--font-mono);padding-block:.1rem;padding-inline:.3rem;font-size:.8rem}}@layer utilities{.\@container\/card-header{container:card-header/inline-size}.\@container{container-type:inline-size}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}@media (pointer:coarse){.extend-touch-target{touch-action:manipulation;position:relative}.extend-touch-target:after{content:var(--tw-content);content:var(--tw-content);inset:calc(var(--spacing)*-2);position:absolute}}.step{counter-increment:step;position:relative}.step:before{right:calc(var(--spacing)*0);margin-right:calc(var(--spacing)*2);width:calc(var(--spacing)*7);height:calc(var(--spacing)*7);text-align:center;text-indent:-1px;font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);border-radius:3.40282e38px;justify-content:center;align-items:center;display:none}@media (min-width:48rem){.step:before{position:absolute}}.step:before{content:counter(step)}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-32{inset:calc(var(--spacing)*32)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.start-1{inset-inline-start:calc(var(--spacing)*1)}.-top-2{top:calc(var(--spacing)*-2)}.-top-4{top:calc(var(--spacing)*-4)}.-top-px{top:-1px}.top-0{top:calc(var(--spacing)*0)}.top-1{top:calc(var(--spacing)*1)}.top-1\.5{top:calc(var(--spacing)*1.5)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing)*2)}.top-2\.5{top:calc(var(--spacing)*2.5)}.top-3\.5{top:calc(var(--spacing)*3.5)}.top-4{top:calc(var(--spacing)*4)}.top-10{top:calc(var(--spacing)*10)}.top-14{top:calc(var(--spacing)*14)}.top-16{top:calc(var(--spacing)*16)}.top-20{top:calc(var(--spacing)*20)}.top-\[0\.3rem\]{top:.3rem}.top-\[0\.4rem\]{top:.4rem}.top-\[5px\]{top:5px}.top-\[50\%\]{top:50%}.top-\[calc\(var\(--header-height\)\+1px\)\]{top:calc(var(--header-height) + 1px)}.-right-1{right:calc(var(--spacing)*-1)}.-right-3{right:calc(var(--spacing)*-3)}.right-0{right:calc(var(--spacing)*0)}.right-0\.5{right:calc(var(--spacing)*.5)}.right-1{right:calc(var(--spacing)*1)}.right-2{right:calc(var(--spacing)*2)}.right-2\.5{right:calc(var(--spacing)*2.5)}.right-3{right:calc(var(--spacing)*3)}.right-4{right:calc(var(--spacing)*4)}.right-6{right:calc(var(--spacing)*6)}.right-16{right:calc(var(--spacing)*16)}.right-24{right:calc(var(--spacing)*24)}.right-\[-1\.5px\]{right:-1.5px}.right-\[-11px\]{right:-11px}.right-\[0\.3rem\]{right:.3rem}.right-\[28px\]{right:28px}.-bottom-1{bottom:calc(var(--spacing)*-1)}.-bottom-px{bottom:-1px}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-0\.5{bottom:calc(var(--spacing)*.5)}.bottom-1{bottom:calc(var(--spacing)*1)}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-16{bottom:calc(var(--spacing)*16)}.bottom-24{bottom:calc(var(--spacing)*24)}.-left-0{left:calc(var(--spacing)*0)}.-left-0\.5{left:calc(var(--spacing)*-.5)}.-left-1{left:calc(var(--spacing)*-1)}.-left-3{left:calc(var(--spacing)*-3)}.-left-5{left:calc(var(--spacing)*-5)}.-left-6{left:calc(var(--spacing)*-6)}.left-0{left:calc(var(--spacing)*0)}.left-1{left:calc(var(--spacing)*1)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.left-2\.5{left:calc(var(--spacing)*2.5)}.left-3{left:calc(var(--spacing)*3)}.left-16{left:calc(var(--spacing)*16)}.left-\[-1\.5px\]{left:-1.5px}.left-\[-10\.5px\]{left:-10.5px}.left-\[50\%\]{left:50%}.isolate{isolation:isolate}.z-1{z-index:1}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-51{z-index:51}.z-60{z-index:60}.z-100{z-index:100}.z-500{z-index:500}.col-span-1{grid-column:span 1/span 1}.col-start-2{grid-column-start:2}.row-span-2{grid-row:span 2/span 2}.row-start-1{grid-row-start:1}.container{width:100%}@media (min-width:1600px){.container{max-width:1600px}}@media (min-width:2000px){.container{max-width:2000px}}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.\!m-0{margin:calc(var(--spacing)*0)!important}.m-0{margin:calc(var(--spacing)*0)}.m-0\.5{margin:calc(var(--spacing)*.5)}.m-4{margin:calc(var(--spacing)*4)}.container-wrapper{width:100%;max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*2);margin-inline:auto}.container{max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media (min-width:80rem){.container{padding-inline:calc(var(--spacing)*6)}}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.mx-0{margin-inline:calc(var(--spacing)*0)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-1\.5{margin-inline:calc(var(--spacing)*1.5)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-3\.5{margin-inline:calc(var(--spacing)*3.5)}.mx-auto{margin-inline:auto}.mx-px{margin-inline:1px}.\!my-0{margin-block:calc(var(--spacing)*0)!important}.my-1{margin-block:calc(var(--spacing)*1)}.my-1\.5{margin-block:calc(var(--spacing)*1.5)}.my-2{margin-block:calc(var(--spacing)*2)}.my-4{margin-block:calc(var(--spacing)*4)}.my-6{margin-block:calc(var(--spacing)*6)}.my-8{margin-block:calc(var(--spacing)*8)}.my-auto{margin-block:auto}.my-px{margin-block:1px}.-ms-5{margin-inline-start:calc(var(--spacing)*-5)}.me-1\.5{margin-inline-end:calc(var(--spacing)*1.5)}.\!mt-0{margin-top:calc(var(--spacing)*0)!important}.-mt-2\.5{margin-top:calc(var(--spacing)*-2.5)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-12{margin-top:calc(var(--spacing)*-12)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-\[0\.75em\]{margin-top:.75em}.mt-\[1\.4em\]{margin-top:1.4em}.mt-\[1\.6em\]{margin-top:1.6em}.mt-\[1em\]{margin-top:1em}.mt-auto{margin-top:auto}.-mr-3{margin-right:calc(var(--spacing)*-3)}.mr-0{margin-right:calc(var(--spacing)*0)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-1\.5{margin-right:calc(var(--spacing)*1.5)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-\[14px\]{margin-right:14px}.mr-auto{margin-right:auto}.\!mb-0{margin-bottom:calc(var(--spacing)*0)!important}.mb-0{margin-bottom:calc(var(--spacing)*0)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-2\.5{margin-bottom:calc(var(--spacing)*2.5)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.mb-12{margin-bottom:calc(var(--spacing)*12)}.mb-16{margin-bottom:calc(var(--spacing)*16)}.\!ml-6{margin-left:calc(var(--spacing)*6)!important}.-ml-2{margin-left:calc(var(--spacing)*-2)}.-ml-3{margin-left:calc(var(--spacing)*-3)}.ml-0{margin-left:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-6{margin-left:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.ml-px{margin-left:1px}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.no-scrollbar::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}.block{display:block}.contents{display:contents}.flex{display:flex}.flex\!{display:flex!important}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-row{display:table-row}.aspect-1\/2{aspect-ratio:1/2}.aspect-\[4\/2\.5\]{aspect-ratio:4/2.5}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!size-3{width:calc(var(--spacing)*3)!important;height:calc(var(--spacing)*3)!important}.\!size-3\.5{width:calc(var(--spacing)*3.5)!important;height:calc(var(--spacing)*3.5)!important}.size-0{width:calc(var(--spacing)*0);height:calc(var(--spacing)*0)}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-2\.5{width:calc(var(--spacing)*2.5);height:calc(var(--spacing)*2.5)}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-3\.5\!{width:calc(var(--spacing)*3.5)!important;height:calc(var(--spacing)*3.5)!important}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.size-10{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10)}.size-12{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12)}.size-\[14px\]{width:14px;height:14px}.size-\[22px\]{width:22px;height:22px}.size-\[28px\]{width:28px;height:28px}.size-\[130px\]{width:130px;height:130px}.size-full{width:100%;height:100%}.h-\(--container-height\){height:var(--container-height)}.h-\(--header-height\){height:var(--header-height)}.h-\(--height\){height:var(--height)}.h-\(--radix-popper-available-height\){height:var(--radix-popper-available-height)}.h-\(--top-spacing\){height:var(--top-spacing)}.h-0\.5{height:calc(var(--spacing)*.5)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-11{height:calc(var(--spacing)*11)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-32{height:calc(var(--spacing)*32)}.h-\[0\.1px\]{height:.1px}.h-\[1\.5em\]{height:1.5em}.h-\[1\.45rem\]{height:1.45rem}.h-\[1px\]{height:1px}.h-\[19px\]{height:19px}.h-\[23rem\]{height:23rem}.h-\[24px\]{height:24px}.h-\[26px\]{height:26px}.h-\[28px\]{height:28px}.h-\[344px\]{height:344px}.h-\[350px\]{height:350px}.h-\[500px\]{height:500px}.h-\[520px\]{height:520px}.h-\[600px\]{height:600px}.h-\[650px\]{height:650px}.h-\[800px\]{height:800px}.h-\[7500px\]{height:7500px}.h-\[calc\(100\%-1px\)\]{height:calc(100% - 1px)}.h-\[calc\(100\%_\+_8px\)\]{height:calc(100% + 8px)}.h-\[calc\(100svh-var\(--header-height\)-var\(--footer-height\)\)\]{height:calc(100svh - var(--header-height) - var(--footer-height))}.h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.h-\[calc\(100vh-100px\)\]{height:calc(100vh - 100px)}.h-\[calc\(theme\(spacing\.7\)_-_1px\)\]{height:calc(1.75rem - 1px)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-dvh{height:100dvh}.h-fit{height:fit-content}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.h-svh{height:100svh}.max-h-\(--radix-context-menu-content-available-height\){max-height:var(--radix-context-menu-content-available-height)}.max-h-\(--radix-dropdown-menu-content-available-height\){max-height:var(--radix-dropdown-menu-content-available-height)}.max-h-\(--radix-select-content-available-height\){max-height:var(--radix-select-content-available-height)}.max-h-14{max-height:calc(var(--spacing)*14)}.max-h-72{max-height:calc(var(--spacing)*72)}.max-h-\[50vh\]{max-height:50vh}.max-h-\[70svh\]{max-height:70svh}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[288px\]{max-height:288px}.max-h-\[300px\]{max-height:300px}.max-h-\[500px\]{max-height:500px}.max-h-\[650px\]{max-height:650px}.max-h-\[calc\(100vh-4rem\)\]{max-height:calc(100vh - 4rem)}.max-h-\[calc\(100vh-var\(--header-height\)-44px\)\]{max-height:calc(100vh - var(--header-height) - 44px)}.max-h-\[min\(50dvh\,calc\(-24px\+var\(--radix-popper-available-height\)\)\)\]{max-height:min(50dvh,calc(-24px + var(--radix-popper-available-height)))}.max-h-\[min\(70vh\,320px\)\]{max-height:min(70vh,320px)}.max-h-screen{max-height:100vh}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-4{min-height:calc(var(--spacing)*4)}.min-h-14{min-height:calc(var(--spacing)*14)}.min-h-\[1lh\]{min-height:1lh}.min-h-\[25px\]{min-height:25px}.min-h-\[50\%\]{min-height:50%}.min-h-\[350px\]{min-height:350px}.min-h-full\!{min-height:100%!important}.min-h-min{min-height:min-content}.min-h-svh{min-height:100svh}.w-\(--radix-popper-available-width\){width:var(--radix-popper-available-width)}.w-\(--sidebar-width\){width:var(--sidebar-width)}.w-0\.5{width:calc(var(--spacing)*.5)}.w-1{width:calc(var(--spacing)*1)}.w-1\/2{width:50%}.w-2{width:calc(var(--spacing)*2)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-3\/4{width:75%}.w-4{width:calc(var(--spacing)*4)}.w-4\.5{width:calc(var(--spacing)*4.5)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-48{width:calc(var(--spacing)*48)}.w-64{width:calc(var(--spacing)*64)}.w-72{width:calc(var(--spacing)*72)}.w-80{width:calc(var(--spacing)*80)}.w-\[1px\]{width:1px}.w-\[100px\]{width:100px}.w-\[180px\]{width:180px}.w-\[200px\]{width:200px}.w-\[230px\]{width:230px}.w-\[240px\]{width:240px}.w-\[280px\]{width:280px}.w-\[300px\]{width:300px}.w-\[330px\]{width:330px}.w-\[380px\]{width:380px}.w-\[700px\]{width:700px}.w-\[896px\]{width:896px}.w-\[970px\]{width:970px}.w-\[calc\(100\%-1rem\)\]{width:calc(100% - 1rem)}.w-\[min\(100\%\,600px\)\]{width:min(100%,600px)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.w-screen{width:100vw}.max-w-\(--skeleton-width\){max-width:var(--skeleton-width)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-\[80vw\]{max-width:80vw}.max-w-\[calc\(100\%-2rem\)\]{max-width:calc(100% - 2rem)}.max-w-\[calc\(100vw-24px\)\]{max-width:calc(100vw - 24px)}.max-w-full{max-width:100%}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-5{min-width:calc(var(--spacing)*5)}.min-w-8{min-width:calc(var(--spacing)*8)}.min-w-9{min-width:calc(var(--spacing)*9)}.min-w-10{min-width:calc(var(--spacing)*10)}.min-w-32{min-width:calc(var(--spacing)*32)}.min-w-\[8px\]{min-width:8px}.min-w-\[8rem\]{min-width:8rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[92px\]{min-width:92px}.min-w-\[125px\]{min-width:125px}.min-w-\[130px\]{min-width:130px}.min-w-\[180px\]{min-width:180px}.min-w-\[220px\]{min-width:220px}.min-w-\[225px\]{min-width:225px}.min-w-\[450px\]{min-width:450px}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow-1,.grow{flex-grow:1}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.origin-\(--radix-context-menu-content-transform-origin\){transform-origin:var(--radix-context-menu-content-transform-origin)}.origin-\(--radix-dropdown-menu-content-transform-origin\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.origin-\(--radix-hover-card-content-transform-origin\){transform-origin:var(--radix-hover-card-content-transform-origin)}.origin-\(--radix-menubar-content-transform-origin\){transform-origin:var(--radix-menubar-content-transform-origin)}.origin-\(--radix-popover-content-transform-origin\){transform-origin:var(--radix-popover-content-transform-origin)}.origin-\(--radix-select-content-transform-origin\){transform-origin:var(--radix-select-content-transform-origin)}.origin-\(--radix-tooltip-content-transform-origin\){transform-origin:var(--radix-tooltip-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-px{--tw-translate-x:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing)*12);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\[-50\%\]{--tw-translate-x:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-px{--tw-translate-x:1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/4{--tw-translate-y:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-0\.5{--tw-translate-y:calc(var(--spacing)*.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[-50\%\]{--tw-translate-y:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[2px\]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[calc\(-50\%_-_2px\)\]{--tw-translate-y:calc(-50% - 2px);translate:var(--tw-translate-x)var(--tw-translate-y)}.-rotate-45{rotate:-45deg}.rotate-0{rotate:none}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-in{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.cursor-text{cursor:text}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.scroll-m-16{scroll-margin:calc(var(--spacing)*16)}.scroll-m-20{scroll-margin:calc(var(--spacing)*20)}.scroll-m-28{scroll-margin:calc(var(--spacing)*28)}.scroll-my-1{scroll-margin-block:calc(var(--spacing)*1)}.scroll-mt-20{scroll-margin-top:calc(var(--spacing)*20)}.scroll-mt-24{scroll-margin-top:calc(var(--spacing)*24)}.scroll-py-1{scroll-padding-block:calc(var(--spacing)*1)}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.list-none\!{list-style-type:none!important}.appearance-none{appearance:none}.grid-flow-row{grid-auto-flow:row}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-\[0_1fr\]{grid-template-columns:0 1fr}.grid-cols-\[repeat\(10\,1fr\)\]{grid-template-columns:repeat(10,1fr)}.grid-rows-\[auto_auto\]{grid-template-rows:auto auto}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.justify-items-start{justify-items:start}.gap-0{gap:calc(var(--spacing)*0)}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}.gap-10{gap:calc(var(--spacing)*10)}.gap-12{gap:calc(var(--spacing)*12)}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*0)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*0)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*10)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*10)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-16>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*16)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*16)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-1{column-gap:calc(var(--spacing)*1)}:where(.space-x-0\.5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*.5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*.5)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.overscroll-none{overscroll-behavior:none}.rounded{border-radius:.25rem}.rounded-\[0\.5rem\]{border-radius:.5rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[50\%\]{border-radius:50%}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.rounded-xs{border-radius:var(--radius-xs)}.rounded-t-lg{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.rounded-r-md{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#ddd\]{border-color:#ddd}.border-blue-500{border-color:var(--color-blue-500)}.border-border,.border-border\/40{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/40{border-color:color-mix(in oklab,var(--border)40%,transparent)}}.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/50{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.border-border\/60{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/60{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.border-current{border-color:currentColor}.border-gray-200{border-color:var(--color-gray-200)}.border-input{border-color:var(--input)}.border-muted{border-color:var(--muted)}.border-muted-foreground{border-color:var(--muted-foreground)}.border-primary{border-color:var(--primary)}.border-sidebar-border{border-color:var(--sidebar-border)}.border-stone-700{border-color:var(--color-stone-700)}.border-transparent{border-color:#0000}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-s-blue-500\/50{border-inline-start-color:#3080ff80}@supports (color:color-mix(in lab, red, red)){.border-s-blue-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-blue-500)50%,transparent)}}.border-s-green-500\/50{border-inline-start-color:#00c75880}@supports (color:color-mix(in lab, red, red)){.border-s-green-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-green-500)50%,transparent)}}.border-s-orange-500\/50{border-inline-start-color:#fe6e0080}@supports (color:color-mix(in lab, red, red)){.border-s-orange-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-orange-500)50%,transparent)}}.border-s-red-500\/50{border-inline-start-color:#fb2c3680}@supports (color:color-mix(in lab, red, red)){.border-s-red-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-red-500)50%,transparent)}}.border-t-border{border-top-color:var(--border)}.border-t-transparent{border-top-color:#0000}.border-b-border{border-bottom-color:var(--border)}.border-b-brand\/\[\.24\]{border-bottom-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.border-b-brand\/\[\.24\]{border-bottom-color:color-mix(in oklab,var(--brand)24%,transparent)}}.border-b-gray-300{border-bottom-color:var(--color-gray-300)}.border-b-highlight,.border-b-highlight\/35{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/35{border-bottom-color:color-mix(in oklab,var(--highlight)35%,transparent)}}.border-b-highlight\/\[\.7\]{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/\[\.7\]{border-bottom-color:color-mix(in oklab,var(--highlight)70%,transparent)}}.border-b-highlight\/\[\.36\]{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/\[\.36\]{border-bottom-color:color-mix(in oklab,var(--highlight)36%,transparent)}}.border-b-purple-100{border-bottom-color:var(--color-purple-100)}.border-b-transparent{border-bottom-color:#0000}.border-l-transparent{border-left-color:#0000}.bg-\(--cellBackground\){background-color:var(--cellBackground)}.bg-\(--color-1\){background-color:var(--color-1)}.bg-\(--color-2\){background-color:var(--color-2)}.bg-\(--color-3\){background-color:var(--color-3)}.bg-\(--color-4\){background-color:var(--color-4)}.bg-\[\#adfa1d\]{background-color:#adfa1d}.bg-\[\#eee\]{background-color:#eee}.bg-\[rgba\(0\,0\,0\,0\.5\)\]{background-color:#00000080}.bg-accent{background-color:var(--accent)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background,.bg-background\/90{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/90{background-color:color-mix(in oklab,var(--background)90%,transparent)}}.bg-background\/95{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/95{background-color:color-mix(in oklab,var(--background)95%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-border{background-color:var(--border)}.bg-brand,.bg-brand\/25{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/25{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.bg-brand\/50{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/50{background-color:color-mix(in oklab,var(--brand)50%,transparent)}}.bg-brand\/\[\.08\]{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/\[\.08\]{background-color:color-mix(in oklab,var(--brand)8%,transparent)}}.bg-brand\/\[\.13\]{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/\[\.13\]{background-color:color-mix(in oklab,var(--brand)13%,transparent)}}.bg-card{background-color:var(--card)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-destructive{background-color:var(--destructive)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200\/80{background-color:#a4f4cfcc}@supports (color:color-mix(in lab, red, red)){.bg-emerald-200\/80{background-color:color-mix(in oklab,var(--color-emerald-200)80%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300\/25{background-color:#d1d5dc40}@supports (color:color-mix(in lab, red, red)){.bg-gray-300\/25{background-color:color-mix(in oklab,var(--color-gray-300)25%,transparent)}}.bg-gray-800{background-color:var(--color-gray-800)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-highlight,.bg-highlight\/15{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/15{background-color:color-mix(in oklab,var(--highlight)15%,transparent)}}.bg-highlight\/25{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/25{background-color:color-mix(in oklab,var(--highlight)25%,transparent)}}.bg-highlight\/30{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/30{background-color:color-mix(in oklab,var(--highlight)30%,transparent)}}.bg-highlight\/45{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/45{background-color:color-mix(in oklab,var(--highlight)45%,transparent)}}.bg-highlight\/\[\.13\]{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/\[\.13\]{background-color:color-mix(in oklab,var(--highlight)13%,transparent)}}.bg-inherit{background-color:inherit}.bg-muted,.bg-muted\/30{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab,var(--muted)30%,transparent)}}.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/50{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.bg-muted\/60{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/60{background-color:color-mix(in oklab,var(--muted)60%,transparent)}}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-popover,.bg-popover\/90{background-color:var(--popover)}@supports (color:color-mix(in lab, red, red)){.bg-popover\/90{background-color:color-mix(in oklab,var(--popover)90%,transparent)}}.bg-primary,.bg-primary\/10{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.bg-primary\/40{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/40{background-color:color-mix(in oklab,var(--primary)40%,transparent)}}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-200\/80{background-color:#ffcacacc}@supports (color:color-mix(in lab, red, red)){.bg-red-200\/80{background-color:color-mix(in oklab,var(--color-red-200)80%,transparent)}}.bg-ring{background-color:var(--ring)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-sidebar-border{background-color:var(--sidebar-border)}.bg-slate-200\/50{background-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.bg-slate-200\/50{background-color:color-mix(in oklab,var(--color-slate-200)50%,transparent)}}.bg-surface{background-color:var(--surface)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-zinc-950\!{background-color:var(--color-zinc-950)!important}.bg-linear-to-b{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-b{--tw-gradient-position:to bottom in oklab}}.bg-linear-to-b{background-image:linear-gradient(var(--tw-gradient-stops))}.bg-linear-to-r{--tw-gradient-position:to right}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-r{--tw-gradient-position:to right in oklab}}.bg-linear-to-r{background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#6EB6F2\]{--tw-gradient-from:#6eb6f2;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-background{--tw-gradient-from:var(--background);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-background\/10{--tw-gradient-from:var(--background)}@supports (color:color-mix(in lab, red, red)){.from-background\/10{--tw-gradient-from:color-mix(in oklab,var(--background)10%,transparent)}}.from-background\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-gray-900\/30{--tw-gradient-from:#1018284d}@supports (color:color-mix(in lab, red, red)){.from-gray-900\/30{--tw-gradient-from:color-mix(in oklab,var(--color-gray-900)30%,transparent)}}.from-gray-900\/30{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-\[\#a855f7\]{--tw-gradient-via:#a855f7;--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-background\/60{--tw-gradient-via:var(--background)}@supports (color:color-mix(in lab, red, red)){.via-background\/60{--tw-gradient-via:color-mix(in oklab,var(--background)60%,transparent)}}.via-background\/60{--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-\[\#eab308\]{--tw-gradient-to:#eab308;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-background{--tw-gradient-to:var(--background);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-90\%{--tw-gradient-to-position:90%}.bg-cover{background-size:cover}.bg-clip-content{background-clip:content-box}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.bg-center{background-position:50%}.fill-blue-500{fill:var(--color-blue-500)}.fill-current{fill:currentColor}.fill-green-500{fill:var(--color-green-500)}.fill-none{fill:none}.fill-orange-500{fill:var(--color-orange-500)}.fill-primary{fill:var(--primary)}.fill-red-500{fill:var(--color-red-500)}.stroke-slate-300{stroke:var(--color-slate-300)}.stroke-\[3px\]{stroke-width:3px}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.\!p-0{padding:calc(var(--spacing)*0)!important}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.p-11{padding:calc(var(--spacing)*11)}.p-20{padding:calc(var(--spacing)*20)}.p-\[2px\]{padding:2px}.p-\[3px\]{padding:3px}.p-px{padding:1px}.\!px-0\.5{padding-inline:calc(var(--spacing)*.5)!important}.\!px-1{padding-inline:calc(var(--spacing)*1)!important}.\!px-1\.5{padding-inline:calc(var(--spacing)*1.5)!important}.\!px-2{padding-inline:calc(var(--spacing)*2)!important}.px-0{padding-inline:calc(var(--spacing)*0)}.px-0\.5{padding-inline:calc(var(--spacing)*.5)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.px-10{padding-inline:calc(var(--spacing)*10)}.px-16{padding-inline:calc(var(--spacing)*16)}.px-\[0\.3em\]{padding-inline:.3em}.px-\[0\.3rem\]{padding-inline:.3rem}.px-\[calc\(--spacing\(1\)-2px\)\]{padding-inline:calc(calc(var(--spacing)*1) - 2px)}.px-px{padding-inline:1px}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-4\!{padding-block:calc(var(--spacing)*4)!important}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-\[--spacing\(1\)\]{padding-block:calc(var(--spacing)*1)}.py-\[0\.2em\]{padding-block:.2em}.py-\[0\.2rem\]{padding-block:.2rem}.py-\[1\.5px\]{padding-block:1.5px}.py-\[3px\]{padding-block:3px}.ps-6{padding-inline-start:calc(var(--spacing)*6)}.pt-0{padding-top:calc(var(--spacing)*0)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-2\.5{padding-top:calc(var(--spacing)*2.5)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-24{padding-top:calc(var(--spacing)*24)}.pt-\[0\.275em\]{padding-top:.275em}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pr-\[14px\]{padding-right:14px}.pr-px{padding-right:1px}.pb-0{padding-bottom:calc(var(--spacing)*0)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-48{padding-bottom:calc(var(--spacing)*48)}.pb-72{padding-bottom:calc(var(--spacing)*72)}.pb-\[20vh\]{padding-bottom:20vh}.pb-\[51\.25\%\]{padding-bottom:51.25%}.pb-\[56\.25\%\]{padding-bottom:56.25%}.pb-\[56\.0417\%\]{padding-bottom:56.0417%}.pb-\[75\%\]{padding-bottom:75%}.pb-px{padding-bottom:1px}.pl-\(--index\){padding-left:var(--index)}.pl-0{padding-left:calc(var(--spacing)*0)}.pl-0\.5{padding-left:calc(var(--spacing)*.5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-6{padding-left:calc(var(--spacing)*6)}.pl-8{padding-left:calc(var(--spacing)*8)}.pl-\[26px\]{padding-left:26px}.pl-\[32px\]{padding-left:32px}.pl-\[50px\]{padding-left:50px}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-start{text-align:start}.align-baseline{vertical-align:baseline}.align-middle{vertical-align:middle}.align-text-bottom{vertical-align:text-bottom}.font-\[inherit\]{font-family:inherit}.font-heading{font-family:"var(--font-heading)","ui-sans-serif",-apple-system,BlinkMacSystemFont,Segoe UI Variable Display,Segoe UI,Helvetica,Apple Color Emoji,Arial,"sans-serif",Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-mono{font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:"var(--font-sans)","ui-sans-serif",-apple-system,BlinkMacSystemFont,Segoe UI Variable Display,Segoe UI,Helvetica,Apple Color Emoji,Arial,"sans-serif",Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.text-\[1\.05rem\]{font-size:1.05rem}.text-\[10px\]{font-size:10px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[40px\]{font-size:40px}.text-\[max\(87\.5\%\,\.875rem\)\]{font-size:max(87.5%,.875rem)}.leading-7{--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[10px\]{--tw-leading:10px;line-height:10px}.leading-\[normal\]{--tw-leading:normal;line-height:normal}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-balance{text-wrap:balance}.text-nowrap{text-wrap:nowrap}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#000000\]{color:#000}.text-\[\#aaa\]{color:#aaa}.text-accent-foreground{color:var(--accent-foreground)}.text-background{color:var(--background)}.text-black{color:var(--color-black)}.text-blue-600{color:var(--color-blue-600)}.text-brand\/80{color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.text-brand\/80{color:color-mix(in oklab,var(--brand)80%,transparent)}}.text-card-foreground{color:var(--card-foreground)}.text-current{color:currentColor}.text-cyan-700{color:var(--color-cyan-700)}.text-destructive{color:var(--destructive)}.text-emerald-700{color:var(--color-emerald-700)}.text-foreground,.text-foreground\/80{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.text-foreground\/80{color:color-mix(in oklab,var(--foreground)80%,transparent)}}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-inherit{color:inherit}.text-muted-foreground,.text-muted-foreground\/70{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/70{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}.text-muted-foreground\/80{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/80{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-800{color:var(--color-neutral-800)}.text-orange-500{color:var(--color-orange-500)}.text-orange-700{color:var(--color-orange-700)}.text-pink-700{color:var(--color-pink-700)}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground,.text-sidebar-foreground\/70{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab, red, red)){.text-sidebar-foreground\/70{color:color-mix(in oklab,var(--sidebar-foreground)70%,transparent)}}.text-slate-50{color:var(--color-slate-50)}.text-slate-500{color:var(--color-slate-500)}.text-stone-400{color:var(--color-stone-400)}.text-surface-foreground{color:var(--surface-foreground)}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-700{color:var(--color-zinc-700)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-primary{-webkit-text-decoration-color:var(--primary);-webkit-text-decoration-color:var(--primary);text-decoration-color:var(--primary)}.decoration-\[0\.5px\]{text-decoration-thickness:.5px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.caret-primary{caret-color:var(--primary)}.accent-foreground{accent-color:var(--foreground)}.opacity-0{opacity:0}.opacity-10{opacity:.1}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_0_1px_hsl\(var\(--sidebar-border\)\)\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,hsl(var(--sidebar-border)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(248\,_249\,_250\)_0px_1px_5px_0px_inset\,_rgb\(193\,_200\,_205\)_0px_0px_0px_0\.5px\,_rgb\(193\,_200\,_205\)_0px_2px_1px_-1px\,_rgb\(193\,_200\,_205\)_0px_1px_0px_0px\]{--tw-shadow-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(248\,_249\,_250\)_0px_1px_5px_0px_inset\,_rgb\(193\,_200\,_205\)_0px_0px_0px_0\.5px\,_rgb\(193\,_200\,_205\)_0px_2px_1px_-1px\,_rgb\(193\,_200\,_205\)_0px_1px_0px_0px\]{--tw-shadow-color:color-mix(in oklab,#ffffff1a 0px .5px 0px 0px inset,#f8f9fa 0px 1px 5px 0px inset,#c1c8cd 0px 0px 0px .5px,#c1c8cd 0px 2px 1px -1px,#c1c8cd 0px 1px 0px 0px var(--tw-shadow-alpha),transparent)}}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.ring-ring{--tw-ring-color:var(--ring)}.ring-sidebar-ring{--tw-ring-color:var(--sidebar-ring)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.invert-1{--tw-invert:invert(1%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.\!filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[left\,right\,width\]{transition-property:left,right,width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[margin\,opacity\]{transition-property:margin,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\,height\,padding\]{transition-property:width,height,padding;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.\[contain\:content\]{contain:content}.fade-in-0{--tw-enter-opacity:0}.outline-none{--tw-outline-style:none;outline-style:none}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}.zoom-in-95{--tw-enter-scale:.95}.\[--footer-height\:calc\(var\(--spacing\)\*14\)\]{--footer-height:calc(var(--spacing)*14)}.\[--header-height\:calc\(var\(--spacing\)\*14\)\]{--header-height:calc(var(--spacing)*14)}.\[--sidebar-width\:220px\]{--sidebar-width:220px}.\[--top-spacing\:0\]{--top-spacing:0}.\[counter-increment\:step\]{counter-increment:step}.\[counter-reset\:step\]{counter-reset:step}.\[tab-size\:2\]{tab-size:2}.fade-in{--tw-enter-opacity:0}.ring-inset{--tw-ring-inset:inset}.running{animation-play-state:running}:is(.\*\:m-0>*){margin:calc(var(--spacing)*0)}:is(.\*\:shrink-0>*){flex-shrink:0}:is(.\*\*\:my-0 *){margin-block:calc(var(--spacing)*0)}:is(.\*\*\:leading-\[calc\(1\.25\/\.875\)\] *){--tw-leading:calc(1.25/.875);line-height:1.42857}:is(.\*\*\:leading-normal *){--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.not-first\:mt-4:not(:first-child){margin-top:calc(var(--spacing)*4)}.not-first\:mt-6:not(:first-child){margin-top:calc(var(--spacing)*6)}.not-first\:mt-12:not(:first-child){margin-top:calc(var(--spacing)*12)}.not-last\:border-b:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.group-first\/column\:-left-1:is(:where(.group\/column):first-child *){left:calc(var(--spacing)*-1)}.group-first\/column\:pl-0:is(:where(.group\/column):first-child *){padding-left:calc(var(--spacing)*0)}.group-last\/column\:-right-1:is(:where(.group\/column):last-child *){right:calc(var(--spacing)*-1)}.group-last\/column\:pr-0:is(:where(.group\/column):last-child *){padding-right:calc(var(--spacing)*0)}.group-last\/toolbar-group\:hidden\!:is(:where(.group\/toolbar-group):last-child *){display:none!important}.group-focus-within\:pointer-events-none:is(:where(.group):focus-within *){pointer-events:none}.group-focus-within\:top-0:is(:where(.group):focus-within *){top:calc(var(--spacing)*0)}.group-focus-within\:cursor-default:is(:where(.group):focus-within *){cursor:default}.group-focus-within\:text-xs:is(:where(.group):focus-within *){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.group-focus-within\:font-medium:is(:where(.group):focus-within *){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.group-focus-within\:text-foreground:is(:where(.group):focus-within *){color:var(--foreground)}.group-focus-within\/menu-item\:opacity-100:is(:where(.group\/menu-item):focus-within *){opacity:1}@media (hover:hover){.group-hover\:translate-x-\[-135px\]:is(:where(.group):hover *){--tw-translate-x:-135px;translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:translate-x-\[-181px\]:is(:where(.group):hover *){--tw-translate-x:-181px;translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:rotate-0:is(:where(.group):hover *){rotate:none}.group-hover\:rotate-45:is(:where(.group):hover *){rotate:45deg}.group-hover\:text-\[\#e3b341\]:is(:where(.group):hover *){color:#e3b341}.group-hover\:no-underline:is(:where(.group):hover *){text-decoration-line:none}.group-hover\:underline:is(:where(.group):hover *){text-decoration-line:underline}.group-hover\:opacity-100:is(:where(.group):hover *),.group-hover\/column\:opacity-100:is(:where(.group\/column):hover *),.group-hover\/container\:opacity-100:is(:where(.group\/container):hover *),.group-hover\/menu-item\:opacity-100:is(:where(.group\/menu-item):hover *),.group-hover\/row\:opacity-100:is(:where(.group\/row):hover *){opacity:1}}.group-has-disabled\:opacity-50:is(:where(.group):has(:disabled) *){opacity:.5}.group-has-data-\[resizing\=\"true\"\]\/row\:opacity-0:is(:where(.group\/row):has([data-resizing=true]) *),.group-has-data-\[resizing\=\\\"true\\\"\]\/row\:opacity-0:is(:where(.group\/row):has([data-resizing=\"true\"]) *){opacity:0}.group-has-data-\[sidebar\=menu-action\]\/menu-item\:pr-8:is(:where(.group\/menu-item):has([data-sidebar=menu-action]) *){padding-right:calc(var(--spacing)*8)}.group-has-\[\[data-col\=\"0\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="0"]:hover) *),.group-has-\[\[data-col\=\"0\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="0"][data-resizing=true]) *),.group-has-\[\[data-col\=\"1\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="1"]:hover) *),.group-has-\[\[data-col\=\"1\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="1"][data-resizing=true]) *),.group-has-\[\[data-col\=\"10\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="10"]:hover) *),.group-has-\[\[data-col\=\"10\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="10"][data-resizing=true]) *),.group-has-\[\[data-col\=\"2\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="2"]:hover) *),.group-has-\[\[data-col\=\"2\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="2"][data-resizing=true]) *),.group-has-\[\[data-col\=\"3\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="3"]:hover) *),.group-has-\[\[data-col\=\"3\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="3"][data-resizing=true]) *),.group-has-\[\[data-col\=\"4\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="4"]:hover) *),.group-has-\[\[data-col\=\"4\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="4"][data-resizing=true]) *),.group-has-\[\[data-col\=\"5\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="5"]:hover) *),.group-has-\[\[data-col\=\"5\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="5"][data-resizing=true]) *),.group-has-\[\[data-col\=\"6\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="6"]:hover) *),.group-has-\[\[data-col\=\"6\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="6"][data-resizing=true]) *),.group-has-\[\[data-col\=\"7\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="7"]:hover) *),.group-has-\[\[data-col\=\"7\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="7"][data-resizing=true]) *),.group-has-\[\[data-col\=\"8\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="8"]:hover) *),.group-has-\[\[data-col\=\"8\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="8"][data-resizing=true]) *),.group-has-\[\[data-col\=\"9\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="9"]:hover) *),.group-has-\[\[data-col\=\"9\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="9"][data-resizing=true]) *),.group-has-\[\[data-col\=\\\"0\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"0\"]:hover) *),.group-has-\[\[data-col\=\\\"0\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"0\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"10\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"10\"]:hover) *),.group-has-\[\[data-col\=\\\"10\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"10\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"1\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"1\"]:hover) *),.group-has-\[\[data-col\=\\\"1\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"1\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"2\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"2\"]:hover) *),.group-has-\[\[data-col\=\\\"2\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"2\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"3\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"3\"]:hover) *),.group-has-\[\[data-col\=\\\"3\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"3\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"4\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"4\"]:hover) *),.group-has-\[\[data-col\=\\\"4\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"4\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"5\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"5\"]:hover) *),.group-has-\[\[data-col\=\\\"5\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"5\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"6\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"6\"]:hover) *),.group-has-\[\[data-col\=\\\"6\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"6\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"7\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"7\"]:hover) *),.group-has-\[\[data-col\=\\\"7\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"7\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"8\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"8\"]:hover) *),.group-has-\[\[data-col\=\\\"8\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"8\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"9\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"9\"]:hover) *),.group-has-\[\[data-col\=\\\"9\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"9\"][data-resizing=\"true\"]) *),.group-has-\[\[data-resizer-left\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-resizer-left]:hover) *),.group-has-\[\[data-resizer-left\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-resizer-left][data-resizing=true]) *),.group-has-\[\[data-resizer-left\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-resizer-left][data-resizing=\"true\"]) *){display:block}.group-data-list\:my-2:is(:where(.group)[data-list] *){margin-block:calc(var(--spacing)*2)}.group-data-\[collapsible\=icon\]\:-mt-8:is(:where(.group)[data-collapsible=icon] *){margin-top:calc(var(--spacing)*-8)}.group-data-\[collapsible\=icon\]\:hidden:is(:where(.group)[data-collapsible=icon] *){display:none}.group-data-\[collapsible\=icon\]\:size-8\!:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--spacing)*8)!important;height:calc(var(--spacing)*8)!important}.group-data-\[collapsible\=icon\]\:w-\(--sidebar-width-icon\):is(:where(.group)[data-collapsible=icon] *){width:var(--sidebar-width-icon)}.group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\)\]:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(var(--spacing)*4)))}.group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\+2px\)\]:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(var(--spacing)*4)) + 2px)}.group-data-\[collapsible\=icon\]\:overflow-hidden:is(:where(.group)[data-collapsible=icon] *){overflow:hidden}.group-data-\[collapsible\=icon\]\:p-0\!:is(:where(.group)[data-collapsible=icon] *){padding:calc(var(--spacing)*0)!important}.group-data-\[collapsible\=icon\]\:p-2\!:is(:where(.group)[data-collapsible=icon] *){padding:calc(var(--spacing)*2)!important}.group-data-\[collapsible\=icon\]\:opacity-0:is(:where(.group)[data-collapsible=icon] *){opacity:0}.group-data-\[collapsible\=offcanvas\]\:right-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *){right:calc(var(--sidebar-width)*-1)}.group-data-\[collapsible\=offcanvas\]\:left-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *){left:calc(var(--sidebar-width)*-1)}.group-data-\[collapsible\=offcanvas\]\:w-0:is(:where(.group)[data-collapsible=offcanvas] *){width:calc(var(--spacing)*0)}.group-data-\[collapsible\=offcanvas\]\:translate-x-0:is(:where(.group)[data-collapsible=offcanvas] *){--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.group-data-\[empty\=true\]\/subheading\:hidden:is(:where(.group\/subheading)[data-empty=true] *){display:none}.group-data-\[pressed\=true\]\:bg-accent:is(:where(.group)[data-pressed=true] *){background-color:var(--accent)}.group-data-\[pressed\=true\]\:text-accent-foreground:is(:where(.group)[data-pressed=true] *){color:var(--accent-foreground)}.group-data-\[side\=left\]\:-right-4:is(:where(.group)[data-side=left] *){right:calc(var(--spacing)*-4)}.group-data-\[side\=left\]\:border-r:is(:where(.group)[data-side=left] *){border-right-style:var(--tw-border-style);border-right-width:1px}.group-data-\[side\=right\]\:left-0:is(:where(.group)[data-side=right] *){left:calc(var(--spacing)*0)}.group-data-\[side\=right\]\:rotate-180:is(:where(.group)[data-side=right] *){rotate:180deg}.group-data-\[side\=right\]\:border-l:is(:where(.group)[data-side=right] *){border-left-style:var(--tw-border-style);border-left-width:1px}.group-data-\[variant\=floating\]\:rounded-lg:is(:where(.group)[data-variant=floating] *){border-radius:var(--radius)}.group-data-\[variant\=floating\]\:border:is(:where(.group)[data-variant=floating] *){border-style:var(--tw-border-style);border-width:1px}.group-data-\[variant\=floating\]\:border-sidebar-border:is(:where(.group)[data-variant=floating] *){border-color:var(--sidebar-border)}.group-data-\[variant\=floating\]\:shadow-sm:is(:where(.group)[data-variant=floating] *){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-data-\[vaul-drawer-direction\=bottom\]\/drawer-content\:block:is(:where(.group\/drawer-content)[data-vaul-drawer-direction=bottom] *){display:block}.group-data-\[view\=code\]\/block-view-wrapper\:hidden:is(:where(.group\/block-view-wrapper)[data-view=code] *),.group-data-\[view\=preview\]\/block-view-wrapper\:hidden:is(:where(.group\/block-view-wrapper)[data-view=preview] *){display:none}@media (hover:hover){.peer-hover\/menu-button\:text-sidebar-accent-foreground:is(:where(.peer\/menu-button):hover~*){color:var(--sidebar-accent-foreground)}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.peer-has-\[\[role\=menuitem\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=menuitem])~*),.peer-has-\[\[role\=menuitemradio\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=menuitemradio])~*),.peer-has-\[\[role\=option\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=option])~*){display:block}.peer-data-\[active\=true\]\/menu-button\:text-sidebar-accent-foreground:is(:where(.peer\/menu-button)[data-active=true]~*){color:var(--sidebar-accent-foreground)}.peer-data-\[size\=default\]\/menu-button\:top-1\.5:is(:where(.peer\/menu-button)[data-size=default]~*){top:calc(var(--spacing)*1.5)}.peer-data-\[size\=lg\]\/menu-button\:top-2\.5:is(:where(.peer\/menu-button)[data-size=lg]~*){top:calc(var(--spacing)*2.5)}.peer-data-\[size\=sm\]\/menu-button\:top-1:is(:where(.peer\/menu-button)[data-size=sm]~*){top:calc(var(--spacing)*1)}.selection\:bg-brand\/25 ::selection{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-brand\/25 ::selection{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.selection\:bg-brand\/25::selection{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-brand\/25::selection{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.selection\:bg-transparent ::selection{background-color:#0000}.selection\:bg-transparent::selection{background-color:#0000}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-7::file-selector-button{height:calc(var(--spacing)*7)}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::placeholder,.placeholder\:text-muted-foreground\/80::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.placeholder\:text-muted-foreground\/80::placeholder{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:z-10:before{content:var(--tw-content);z-index:10}.before\:mt-\[-4px\]:before{content:var(--tw-content);margin-top:-4px}.before\:ml-\[-50px\]:before{content:var(--tw-content);margin-left:-50px}.before\:box-border:before{content:var(--tw-content);box-sizing:border-box}.before\:inline-flex:before{content:var(--tw-content);display:inline-flex}.before\:size-full:before{content:var(--tw-content);width:100%;height:100%}.before\:h-9:before{content:var(--tw-content);height:calc(var(--spacing)*9)}.before\:w-9:before{content:var(--tw-content);width:calc(var(--spacing)*9)}.before\:cursor-text:before{content:var(--tw-content);cursor:text}.before\:items-center:before{content:var(--tw-content);align-items:center}.before\:justify-center:before{content:var(--tw-content);justify-content:center}.before\:rounded-full:before{content:var(--tw-content);border-radius:3.40282e38px}.before\:border-4:before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:4px}.before\:border-t:before{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.before\:border-r:before{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:1px}.before\:border-b:before{content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.before\:border-l:before{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:1px}.before\:border-background:before{content:var(--tw-content);border-color:var(--background)}.before\:border-t-border:before{content:var(--tw-content);border-top-color:var(--border)}.before\:border-r-border:before{content:var(--tw-content);border-right-color:var(--border)}.before\:border-b-border:before{content:var(--tw-content);border-bottom-color:var(--border)}.before\:border-l-border:before{content:var(--tw-content);border-left-color:var(--border)}.before\:bg-brand\/5:before{content:var(--tw-content);background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.before\:bg-brand\/5:before{background-color:color-mix(in oklab,var(--brand)5%,transparent)}}.before\:bg-muted:before{content:var(--tw-content);background-color:var(--muted)}.before\:text-center:before{content:var(--tw-content);text-align:center}.before\:-indent-px:before{content:var(--tw-content);text-indent:-1px}.before\:font-mono:before{content:var(--tw-content);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.before\:text-base:before{content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.before\:font-medium:before{content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.before\:text-muted-foreground\/80:before{content:var(--tw-content);color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.before\:text-muted-foreground\/80:before{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.before\:opacity-30:before{content:var(--tw-content);opacity:.3}.before\:content-\[\'\'\]:before{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.before\:content-\[attr\(placeholder\)\]:before{content:var(--tw-content);--tw-content:attr(placeholder);content:var(--tw-content)}.before\:\[content\:counter\(step\)\]:before{content:var(--tw-content);content:counter(step)}.before\:select-none:before{content:var(--tw-content);-webkit-user-select:none;user-select:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-2:after{content:var(--tw-content);inset:calc(var(--spacing)*-2)}.after\:inset-0:after{content:var(--tw-content);inset:calc(var(--spacing)*0)}.after\:inset-x-0:after{content:var(--tw-content);inset-inline:calc(var(--spacing)*0)}.after\:inset-y-0:after{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.after\:inset-y-\[-2px\]:after{content:var(--tw-content);inset-block:-2px}.after\:-top-0\.5:after{content:var(--tw-content);top:calc(var(--spacing)*-.5)}.after\:top-1\/2:after{content:var(--tw-content);top:50%}.after\:right-0:after{content:var(--tw-content);right:calc(var(--spacing)*0)}.after\:-left-1:after{content:var(--tw-content);left:calc(var(--spacing)*-1)}.after\:left-0:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:z-1:after{content:var(--tw-content);z-index:1}.after\:ml-1\.5:after{content:var(--tw-content);margin-left:calc(var(--spacing)*1.5)}.after\:block:after{content:var(--tw-content);display:block}.after\:flex:after{content:var(--tw-content);display:flex}.after\:inline-block:after{content:var(--tw-content);display:inline-block}.after\:h-3:after{content:var(--tw-content);height:calc(var(--spacing)*3)}.after\:h-8:after{content:var(--tw-content);height:calc(var(--spacing)*8)}.after\:h-16:after{content:var(--tw-content);height:calc(var(--spacing)*16)}.after\:h-\[calc\(100\%\)\+4px\]:after{content:var(--tw-content);height:calc(100%)4px}.after\:w-1:after{content:var(--tw-content);width:calc(var(--spacing)*1)}.after\:w-3:after{content:var(--tw-content);width:calc(var(--spacing)*3)}.after\:w-10:after{content:var(--tw-content);width:calc(var(--spacing)*10)}.after\:w-\[2px\]:after{content:var(--tw-content);width:2px}.after\:w-\[3px\]:after{content:var(--tw-content);width:3px}.after\:w-\[6px\]:after{content:var(--tw-content);width:6px}.after\:w-\[calc\(100\%\+8px\)\]:after{content:var(--tw-content);width:calc(100% + 8px)}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:-translate-x-px:after{content:var(--tw-content);--tw-translate-x:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:-translate-y-1\/2:after{content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:rounded-\[6px\]:after{content:var(--tw-content);border-radius:6px}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:rounded-lg:after{content:var(--tw-content);border-radius:var(--radius)}.after\:rounded-sm:after{content:var(--tw-content);border-radius:calc(var(--radius) - 4px)}.after\:bg-border:after{content:var(--tw-content);background-color:var(--border)}.after\:bg-brand\/15:after{content:var(--tw-content);background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.after\:bg-brand\/15:after{background-color:color-mix(in oklab,var(--brand)15%,transparent)}}.after\:bg-neutral-500\/10:after{content:var(--tw-content);background-color:#7373731a}@supports (color:color-mix(in lab, red, red)){.after\:bg-neutral-500\/10:after{background-color:color-mix(in oklab,var(--color-neutral-500)10%,transparent)}}.after\:bg-primary:after{content:var(--tw-content);background-color:var(--primary)}.after\:bg-ring:after{content:var(--tw-content);background-color:var(--ring)}.after\:bg-zinc-950:after{content:var(--tw-content);background-color:var(--color-zinc-950)}.after\:pb-\[var\(--aspect-ratio\)\]:after{content:var(--tw-content);padding-bottom:var(--aspect-ratio)}.after\:align-middle:after{content:var(--tw-content);vertical-align:middle}.after\:opacity-0:after{content:var(--tw-content);opacity:0}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\"\"\]:after{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.after\:content-\[\'_\'\]:after{content:var(--tw-content);--tw-content:" ";content:var(--tw-content)}.after\:content-\[\\\"\\\"\]:after{content:var(--tw-content);--tw-content:\"\";content:var(--tw-content)}@media (hover:hover){.group-hover\:after\:opacity-100:is(:where(.group):hover *):after{content:var(--tw-content);opacity:1}}.group-data-\[collapsible\=offcanvas\]\:after\:left-full:is(:where(.group)[data-collapsible=offcanvas] *):after{content:var(--tw-content);left:100%}.first\:\!mt-0:first-child{margin-top:calc(var(--spacing)*0)!important}.first\:mt-0:first-child{margin-top:calc(var(--spacing)*0)}.first\:rounded-l-md:first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:rounded-r-md:last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.empty\:hidden:empty{display:none}.focus-within\:relative:focus-within{position:relative}.focus-within\:z-20:focus-within{z-index:20}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-ring:focus-within{--tw-ring-color:var(--ring)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}@media (hover:hover){.hover\:scale-125:hover{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-accent:hover,.hover\:bg-accent\/80:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/80:hover{background-color:color-mix(in oklab,var(--accent)80%,transparent)}}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-muted-foreground\/15:hover{background-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted-foreground\/15:hover{background-color:color-mix(in oklab,var(--muted-foreground)15%,transparent)}}.hover\:bg-muted\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.hover\:bg-primary:hover,.hover\:bg-primary\/10:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}.hover\:bg-secondary\/60:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/60:hover{background-color:color-mix(in oklab,var(--secondary)60%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-brand\/80:hover{color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.hover\:text-brand\/80:hover{color:color-mix(in oklab,var(--brand)80%,transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-muted-foreground:hover,.hover\:text-muted-foreground\/80:hover{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.hover\:text-muted-foreground\/80:hover{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.hover\:text-primary:hover{color:var(--primary)}.hover\:text-primary-foreground:hover{color:var(--primary-foreground)}.hover\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-white:hover{color:var(--color-white)}.hover\:text-zinc-50:hover{color:var(--color-zinc-50)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-\[0_0_0_1px_hsl\(var\(--sidebar-accent\)\)\]:hover{--tw-shadow:0 0 0 1px var(--tw-shadow-color,hsl(var(--sidebar-accent)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-primary:hover{--tw-ring-color:var(--primary)}.hover\:ring-offset-2:hover{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.hover\:group-data-\[collapsible\=offcanvas\]\:bg-sidebar:hover:is(:where(.group)[data-collapsible=offcanvas] *){background-color:var(--sidebar)}.hover\:after\:absolute:hover:after{content:var(--tw-content);position:absolute}.hover\:after\:-bottom-1:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*-1)}.hover\:after\:bottom-0:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}.hover\:after\:left-0:hover:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.hover\:after\:h-10:hover:after{content:var(--tw-content);height:calc(var(--spacing)*10)}.hover\:after\:h-\[1\.5px\]:hover:after{content:var(--tw-content);height:1.5px}.hover\:after\:w-\[calc\(100\%-2px\)\]:hover:after{content:var(--tw-content);width:calc(100% - 2px)}.hover\:after\:bg-brand:hover:after{content:var(--tw-content);background-color:var(--brand)}.hover\:after\:bg-primary:hover:after{content:var(--tw-content);background-color:var(--primary)}.hover\:after\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:var(--sidebar-border)}}.focus\:z-10:focus{z-index:10}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:bg-primary:focus{background-color:var(--primary)}.focus\:bg-zinc-700:focus{background-color:var(--color-zinc-700)}.focus\:text-accent-foreground:focus{color:var(--accent-foreground)}.focus\:text-primary-foreground:focus{color:var(--primary-foreground)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--ring)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:bg-transparent:focus-visible{background-color:#0000}.focus-visible\:bg-zinc-700:focus-visible{background-color:var(--color-zinc-700)}.focus-visible\:text-white:focus-visible{color:var(--color-white)}.focus-visible\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab,var(--ring)50%,transparent)}}.focus-visible\:ring-slate-700:focus-visible{--tw-ring-color:var(--color-slate-700)}.focus-visible\:ring-transparent:focus-visible{--tw-ring-color:transparent}.focus-visible\:ring-offset-0:focus-visible{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus-visible\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.focus-visible\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-ring:focus-visible{outline-color:var(--ring)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-sidebar-accent:active{background-color:var(--sidebar-accent)}.active\:bg-transparent:active{background-color:#0000}.active\:bg-zinc-700:active{background-color:var(--color-zinc-700)}.active\:text-sidebar-accent-foreground:active{color:var(--sidebar-accent-foreground)}.active\:text-white:active{color:var(--color-white)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}:where([data-side=left]) .in-data-\[side\=left\]\:cursor-w-resize{cursor:w-resize}:where([data-side=right]) .in-data-\[side\=right\]\:cursor-e-resize{cursor:e-resize}.has-aria-disabled\:border-input:has([aria-disabled=true]){border-color:var(--input)}.has-aria-disabled\:bg-muted:has([aria-disabled=true]){background-color:var(--muted)}.has-data-readonly\:w-fit:has([data-readonly]){width:fit-content}.has-data-readonly\:cursor-default:has([data-readonly]){cursor:default}.has-data-readonly\:border-transparent:has([data-readonly]){border-color:#0000}.has-data-readonly\:focus-within\:\[box-shadow\:none\]:has([data-readonly]):focus-within{box-shadow:none}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-data-\[variant\=inset\]\:bg-sidebar:has([data-variant=inset]){background-color:var(--sidebar)}.has-\[\[data-slate-editor\]\:focus\]\:border-brand\/50:has([data-slate-editor]:focus){border-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.has-\[\[data-slate-editor\]\:focus\]\:border-brand\/50:has([data-slate-editor]:focus){border-color:color-mix(in oklab,var(--brand)50%,transparent)}}.has-\[\[data-slate-editor\]\:focus\]\:ring-2:has([data-slate-editor]:focus){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.has-\[\[data-slate-editor\]\:focus\]\:ring-brand\/30:has([data-slate-editor]:focus){--tw-ring-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.has-\[\[data-slate-editor\]\:focus\]\:ring-brand\/30:has([data-slate-editor]:focus){--tw-ring-color:color-mix(in oklab,var(--brand)30%,transparent)}}.has-\[\[role\=menuitem\]\]\:block:has([role=menuitem]),.has-\[\[role\=menuitemradio\]\]\:block:has([role=menuitemradio]),.has-\[\[role\=option\]\]\:block:has([role=option]){display:block}.has-\[button\]\:flex:has(:is(button)){display:flex}.has-\[\+input\:not\(\:placeholder-shown\)\]\:pointer-events-none:has(+input:not(:placeholder-shown)){pointer-events:none}.has-\[\+input\:not\(\:placeholder-shown\)\]\:top-0:has(+input:not(:placeholder-shown)){top:calc(var(--spacing)*0)}.has-\[\+input\:not\(\:placeholder-shown\)\]\:cursor-default:has(+input:not(:placeholder-shown)){cursor:default}.has-\[\+input\:not\(\:placeholder-shown\)\]\:text-xs:has(+input:not(:placeholder-shown)){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.has-\[\+input\:not\(\:placeholder-shown\)\]\:font-medium:has(+input:not(:placeholder-shown)){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.has-\[\+input\:not\(\:placeholder-shown\)\]\:text-foreground:has(+input:not(:placeholder-shown)){color:var(--foreground)}.has-\[\>svg\]\:grid-cols-\[calc\(var\(--spacing\)\*4\)_1fr\]:has(>svg){grid-template-columns:calc(var(--spacing)*4)1fr}.has-\[\>svg\]\:gap-x-3:has(>svg){column-gap:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.aria-checked\:border-\(--color-1\)[aria-checked=true]{border-color:var(--color-1)}.aria-checked\:bg-accent[aria-checked=true]{background-color:var(--accent)}.aria-checked\:text-accent-foreground[aria-checked=true]{color:var(--accent-foreground)}.aria-disabled\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.aria-selected\:bg-accent[aria-selected=true]{background-color:var(--accent)}.aria-selected\:bg-primary[aria-selected=true]{background-color:var(--primary)}.aria-selected\:text-accent-foreground[aria-selected=true]{color:var(--accent-foreground)}.aria-selected\:text-muted-foreground[aria-selected=true]{color:var(--muted-foreground)}.aria-selected\:text-primary-foreground[aria-selected=true]{color:var(--primary-foreground)}.aria-selected\:opacity-100[aria-selected=true]{opacity:1}:is(.\*\*\:data-block-hide\:hidden *)[data-block-hide]{display:none}.data-readonly\:w-fit[data-readonly]{width:fit-content}:is(.\*\*\:data-slate-editor\:max-h-\[calc\(100dvh-44px\)\] *)[data-slate-editor]{max-height:calc(100dvh - 44px)}:is(.\*\*\:data-slate-placeholder\:\!top-1\/2 *)[data-slate-placeholder]{top:50%!important}:is(.\*\*\:data-slate-placeholder\:top-\[auto_\!important\] *)[data-slate-placeholder]{top:auto!important}:is(.\*\*\:data-slate-placeholder\:-translate-y-1\/2 *)[data-slate-placeholder]{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}:is(.\*\*\:data-slate-placeholder\:text-muted-foreground\/80 *)[data-slate-placeholder]{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){:is(.\*\*\:data-slate-placeholder\:text-muted-foreground\/80 *)[data-slate-placeholder]{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}:is(.\*\*\:data-slate-placeholder\:opacity-100\! *)[data-slate-placeholder]{opacity:1!important}.data-\[active-item\=true\]\:bg-accent[data-active-item=true]{background-color:var(--accent)}.data-\[active-item\=true\]\:text-accent-foreground[data-active-item=true]{color:var(--accent-foreground)}.data-\[active\=true\]\:bg-muted[data-active=true]{background-color:var(--muted)}.data-\[active\=true\]\:bg-sidebar-accent[data-active=true]{background-color:var(--sidebar-accent)}.data-\[active\=true\]\:bg-zinc-700[data-active=true]{background-color:var(--color-zinc-700)}.data-\[active\=true\]\:font-medium[data-active=true]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.data-\[active\=true\]\:text-foreground[data-active=true]{color:var(--foreground)}.data-\[active\=true\]\:text-sidebar-accent-foreground[data-active=true]{color:var(--sidebar-accent-foreground)}.data-\[active\=true\]\:text-white[data-active=true]{color:var(--color-white)}.data-\[block\=login-01\]\:max-w-full[data-block=login-01]{max-width:100%}.data-\[block\=sidebar-10\]\:right-0[data-block=sidebar-10]{right:calc(var(--spacing)*0)}.data-\[block\=sidebar-10\]\:left-auto[data-block=sidebar-10]{left:auto}.data-\[block\=sidebar-11\]\:-top-1\/3[data-block=sidebar-11]{top:-33.3333%}.data-\[block\=sidebar-13\]\:max-w-full[data-block=sidebar-13]{max-width:100%}.data-\[block\=sidebar-14\]\:right-0[data-block=sidebar-14]{right:calc(var(--spacing)*0)}.data-\[block\=sidebar-14\]\:left-auto[data-block=sidebar-14]{left:auto}.data-\[block\=sidebar-15\]\:max-w-full[data-block=sidebar-15]{max-width:100%}.data-\[depth\=3\]\:pl-4[data-depth="3"]{padding-left:calc(var(--spacing)*4)}.data-\[depth\=3\]\:pl-6[data-depth="3"],.data-\[depth\=4\]\:pl-6[data-depth="4"]{padding-left:calc(var(--spacing)*6)}.data-\[depth\=4\]\:pl-8[data-depth="4"]{padding-left:calc(var(--spacing)*8)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[disabled\=true\]\:pointer-events-none[data-disabled=true]{pointer-events:none}.data-\[disabled\=true\]\:opacity-50[data-disabled=true]{opacity:.5}.data-\[error\=true\]\:text-destructive[data-error=true]{color:var(--destructive)}.data-\[highlighted\=true\]\:bg-accent[data-highlighted=true]{background-color:var(--accent)}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[orientation\=horizontal\]\:h-px[data-orientation=horizontal]{height:1px}.data-\[orientation\=horizontal\]\:w-full[data-orientation=horizontal]{width:100%}.data-\[orientation\=vertical\]\:h-full[data-orientation=vertical]{height:100%}.data-\[orientation\=vertical\]\:w-px[data-orientation=vertical]{width:1px}.data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction=vertical]{height:1px}.data-\[panel-group-direction\=vertical\]\:w-full[data-panel-group-direction=vertical]{width:100%}.data-\[panel-group-direction\=vertical\]\:flex-col[data-panel-group-direction=vertical]{flex-direction:column}.data-\[panel-group-direction\=vertical\]\:after\:left-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.data-\[panel-group-direction\=vertical\]\:after\:h-1[data-panel-group-direction=vertical]:after{content:var(--tw-content);height:calc(var(--spacing)*1)}.data-\[panel-group-direction\=vertical\]\:after\:w-full[data-panel-group-direction=vertical]:after{content:var(--tw-content);width:100%}.data-\[panel-group-direction\=vertical\]\:after\:translate-x-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[panel-group-direction\=vertical\]\:after\:-translate-y-1\/2[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[selected\=true\]\:bg-accent[data-selected=true]{background-color:var(--accent)}.data-\[selected\=true\]\:bg-primary\/10[data-selected=true]{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\=true\]\:bg-primary\/10[data-selected=true]{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.data-\[selected\=true\]\:text-accent-foreground[data-selected=true]{color:var(--accent-foreground)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=alert\]\:first\:mt-0>*)[data-slot=alert]:first-child{margin-top:calc(var(--spacing)*0)}:is(.\*\:data-\[slot\=alert-description\]\:text-destructive\/90>*)[data-slot=alert-description]{color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.\*\:data-\[slot\=alert-description\]\:text-destructive\/90>*)[data-slot=alert-description]{color:color-mix(in oklab,var(--destructive)90%,transparent)}}:is(.\*\:data-\[slot\=block-selection\]\:left-2>*)[data-slot=block-selection]{left:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=command-input-wrapper\]\:h-12 *)[data-slot=command-input-wrapper]{height:calc(var(--spacing)*12)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=separator\]\:\!h-4 *)[data-slot=separator]{height:calc(var(--spacing)*4)!important}.data-\[state\=active\]\:flex[data-state=active]{display:flex}.data-\[state\=active\]\:border-b-primary[data-state=active]{border-bottom-color:var(--primary)}.data-\[state\=active\]\:border-b-zinc-50[data-state=active]{border-bottom-color:var(--color-zinc-50)}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:var(--background)}.data-\[state\=active\]\:bg-transparent[data-state=active]{background-color:#0000}.data-\[state\=active\]\:text-foreground[data-state=active]{color:var(--foreground)}.data-\[state\=active\]\:text-zinc-50[data-state=active]{color:var(--color-zinc-50)}.data-\[state\=active\]\:shadow-none[data-state=active]{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:var(--primary)}.data-\[state\=checked\]\:bg-accent[data-state=checked]{background-color:var(--accent)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--primary)}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:var(--primary-foreground)}.data-\[state\=closed\]\:shrink-0[data-state=closed]{flex-shrink:0}.data-\[state\=closed\]\:animate-accordion-up[data-state=closed]{animation:accordion-up var(--tw-animation-duration,var(--tw-duration,.2s))ease-out}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:opacity-0[data-state=closed]{opacity:0}.data-\[state\=closed\]\:duration-300[data-state=closed]{--tw-duration:.3s;transition-duration:.3s}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed]{--tw-exit-translate-y:100%}.data-\[state\=closed\]\:slide-out-to-left[data-state=closed]{--tw-exit-translate-x:-100%}.data-\[state\=closed\]\:slide-out-to-right[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-top[data-state=closed]{--tw-exit-translate-y:-100%}.data-\[state\=on\]\:bg-accent[data-state=on]{background-color:var(--accent)}.data-\[state\=on\]\:text-accent-foreground[data-state=on]{color:var(--accent-foreground)}.data-\[state\=open\]\:grow[data-state=open]{flex-grow:1}.data-\[state\=open\]\:animate-accordion-down[data-state=open]{animation:accordion-down var(--tw-animation-duration,var(--tw-duration,.2s))ease-out}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:animate-none[data-state=open]{animation:none}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:bg-secondary[data-state=open]{background-color:var(--secondary)}.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:var(--accent-foreground)}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:var(--muted-foreground)}.data-\[state\=open\]\:opacity-100[data-state=open]{opacity:1}.data-\[state\=open\]\:duration-500[data-state=open]{--tw-duration:.5s;transition-duration:.5s}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=open\]\:slide-in-from-bottom[data-state=open]{--tw-enter-translate-y:100%}.data-\[state\=open\]\:slide-in-from-left[data-state=open]{--tw-enter-translate-x:-100%}.data-\[state\=open\]\:slide-in-from-right[data-state=open]{--tw-enter-translate-x:100%}.data-\[state\=open\]\:slide-in-from-top[data-state=open]{--tw-enter-translate-y:-100%}@media (hover:hover){.data-\[state\=open\]\:hover\:bg-sidebar-accent[data-state=open]:hover{background-color:var(--sidebar-accent)}.data-\[state\=open\]\:hover\:bg-zinc-700[data-state=open]:hover{background-color:var(--color-zinc-700)}.data-\[state\=open\]\:hover\:text-sidebar-accent-foreground[data-state=open]:hover{color:var(--sidebar-accent-foreground)}.data-\[state\=open\]\:hover\:text-white[data-state=open]:hover{color:var(--color-white)}}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:var(--muted)}.data-\[variant\=destructive\]\:text-destructive[data-variant=destructive]{color:var(--destructive)}.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.data-\[variant\=destructive\]\:focus\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.data-\[variant\=outline\]\:shadow-xs[data-variant=outline]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[variant\=outline\]\:first\:border-l[data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}.data-\[vaul-drawer-direction\=bottom\]\:inset-x-0[data-vaul-drawer-direction=bottom]{inset-inline:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=bottom\]\:bottom-0[data-vaul-drawer-direction=bottom]{bottom:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=bottom\]\:mt-24[data-vaul-drawer-direction=bottom]{margin-top:calc(var(--spacing)*24)}.data-\[vaul-drawer-direction\=bottom\]\:max-h-\[80vh\][data-vaul-drawer-direction=bottom]{max-height:80vh}.data-\[vaul-drawer-direction\=bottom\]\:rounded-t-lg[data-vaul-drawer-direction=bottom]{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.data-\[vaul-drawer-direction\=bottom\]\:border-t[data-vaul-drawer-direction=bottom]{border-top-style:var(--tw-border-style);border-top-width:1px}.data-\[vaul-drawer-direction\=left\]\:inset-y-0[data-vaul-drawer-direction=left]{inset-block:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=left\]\:left-0[data-vaul-drawer-direction=left]{left:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=left\]\:w-3\/4[data-vaul-drawer-direction=left]{width:75%}.data-\[vaul-drawer-direction\=left\]\:border-r[data-vaul-drawer-direction=left]{border-right-style:var(--tw-border-style);border-right-width:1px}.data-\[vaul-drawer-direction\=right\]\:inset-y-0[data-vaul-drawer-direction=right]{inset-block:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=right\]\:right-0[data-vaul-drawer-direction=right]{right:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=right\]\:w-3\/4[data-vaul-drawer-direction=right]{width:75%}.data-\[vaul-drawer-direction\=right\]\:border-l[data-vaul-drawer-direction=right]{border-left-style:var(--tw-border-style);border-left-width:1px}.data-\[vaul-drawer-direction\=top\]\:inset-x-0[data-vaul-drawer-direction=top]{inset-inline:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=top\]\:top-0[data-vaul-drawer-direction=top]{top:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=top\]\:mb-24[data-vaul-drawer-direction=top]{margin-bottom:calc(var(--spacing)*24)}.data-\[vaul-drawer-direction\=top\]\:max-h-\[80vh\][data-vaul-drawer-direction=top]{max-height:80vh}.data-\[vaul-drawer-direction\=top\]\:rounded-b-lg[data-vaul-drawer-direction=top]{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.data-\[vaul-drawer-direction\=top\]\:border-b[data-vaul-drawer-direction=top]{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}:is(.\*\:nth-\[2\]\:flex-1>*):nth-child(2){flex:1}:is(.\*\:nth-\[2\]\:text-muted-foreground>*):nth-child(2){color:var(--muted-foreground)}:is(.\*\:nth-\[2\]\:line-through>*):nth-child(2){text-decoration-line:line-through}:is(.\*\:nth-\[2\]\:focus\:outline-none>*):nth-child(2):focus{--tw-outline-style:none;outline-style:none}@supports (backdrop-blur:var(--tw)){.supports-backdrop-blur\:bg-background\/60{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.supports-backdrop-blur\:bg-background\/60{background-color:color-mix(in oklab,var(--background)60%,transparent)}}}@supports ((-webkit-backdrop-filter:var(--tw)) or (backdrop-filter:var(--tw))){.supports-backdrop-filter\:bg-background\/60{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.supports-backdrop-filter\:bg-background\/60{background-color:color-mix(in oklab,var(--background)60%,transparent)}}}@media not all and (min-width:48rem){.max-md\:hidden{display:none}}@media not all and (min-width:40rem){.max-sm\:hidden{display:none}.max-sm\:w-full{width:100%}.max-sm\:flex-auto\!{flex:auto!important}}@media (min-width:40rem){.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:w-\[1280px\]{width:1280px}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-lg{max-width:var(--container-lg)}.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}.sm\:p-10{padding:calc(var(--spacing)*10)}.sm\:px-24{padding-inline:calc(var(--spacing)*24)}.sm\:px-\[max\(64px\,calc\(50\%-350px\)\)\]{padding-inline:max(64px,50% - 350px)}.sm\:pr-12{padding-right:calc(var(--spacing)*12)}.sm\:text-left{text-align:left}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:opacity-0{opacity:0}.data-\[vaul-drawer-direction\=left\]\:sm\:max-w-sm[data-vaul-drawer-direction=left],.data-\[vaul-drawer-direction\=right\]\:sm\:max-w-sm[data-vaul-drawer-direction=right]{max-width:var(--container-sm)}}@media (min-width:48rem){.md\:sticky{position:sticky}.md\:my-8{margin-block:calc(var(--spacing)*8)}.md\:mt-24{margin-top:calc(var(--spacing)*24)}.md\:block{display:block}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:aspect-auto{aspect-ratio:auto}.md\:size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.md\:h-7{height:calc(var(--spacing)*7)}.md\:h-24{height:calc(var(--spacing)*24)}.md\:w-40{width:calc(var(--spacing)*40)}.md\:w-auto{width:auto}.md\:flex-1{flex:1}.md\:flex-none{flex:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[var\(--sidebar-width\)_minmax\(0\,1fr\)\]{grid-template-columns:var(--sidebar-width)minmax(0,1fr)}.md\:flex-row{flex-direction:row}.md\:flex-row-reverse{flex-direction:row-reverse}.md\:items-start{align-items:flex-start}.md\:justify-end{justify-content:flex-end}.md\:gap-4{gap:calc(var(--spacing)*4)}.md\:gap-16{gap:calc(var(--spacing)*16)}.md\:gap-24{gap:calc(var(--spacing)*24)}:where(.md\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.md\:px-2{padding-inline:calc(var(--spacing)*2)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:py-0{padding-block:calc(var(--spacing)*0)}.md\:py-10{padding-block:calc(var(--spacing)*10)}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pr-\[14px\]{padding-right:14px}.md\:text-left{text-align:left}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:opacity-0{opacity:0}.md\:\[--sidebar-width\:240px\]{--sidebar-width:240px}.md\:\[--top-spacing\:calc\(var\(--spacing\)\*4\)\]{--top-spacing:calc(var(--spacing)*4)}.md\:peer-data-\[variant\=inset\]\:m-2:is(:where(.peer)[data-variant=inset]~*){margin:calc(var(--spacing)*2)}.md\:peer-data-\[variant\=inset\]\:ml-0:is(:where(.peer)[data-variant=inset]~*){margin-left:calc(var(--spacing)*0)}.md\:peer-data-\[variant\=inset\]\:rounded-xl:is(:where(.peer)[data-variant=inset]~*){border-radius:calc(var(--radius) + 4px)}.md\:peer-data-\[variant\=inset\]\:shadow-sm:is(:where(.peer)[data-variant=inset]~*){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:peer-data-\[variant\=inset\]\:peer-data-\[state\=collapsed\]\:ml-2:is(:where(.peer)[data-variant=inset]~*):is(:where(.peer)[data-state=collapsed]~*){margin-left:calc(var(--spacing)*2)}.md\:after\:hidden:after{content:var(--tw-content);display:none}}@media (min-width:64rem){.lg\:sticky{position:sticky}.lg\:top-20{top:calc(var(--spacing)*20)}.lg\:bottom-auto{bottom:auto}.lg\:mt-36{margin-top:calc(var(--spacing)*36)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-flex{display:inline-flex}.lg\:w-56{width:calc(var(--spacing)*56)}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:flex-col{flex-direction:column}.lg\:justify-start{justify-content:flex-start}.lg\:gap-1{gap:calc(var(--spacing)*1)}.lg\:gap-6{gap:calc(var(--spacing)*6)}.lg\:gap-48{gap:calc(var(--spacing)*48)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:py-8{padding-block:calc(var(--spacing)*8)}.lg\:py-12{padding-block:calc(var(--spacing)*12)}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}}@media (min-width:80rem){.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:w-64{width:calc(var(--spacing)*64)}.xl\:w-auto{width:auto}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:gap-10{gap:calc(var(--spacing)*10)}.xl\:\[--footer-height\:calc\(var\(--spacing\)\*24\)\]{--footer-height:calc(var(--spacing)*24)}}@media (min-width:96rem){.\32 xl\:block{display:block}.\32 xl\:hidden{display:none}}@container not (min-width:32rem){.\@max-lg\:col-span-full{grid-column:1/-1}}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:border-border:is(.dark *){border-color:var(--border)}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-cyan-950:is(.dark *){background-color:var(--color-cyan-950)}.dark\:bg-destructive\/60:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-destructive\/60:is(.dark *){background-color:color-mix(in oklab,var(--destructive)60%,transparent)}}.dark\:bg-gray-800:is(.dark *){background-color:var(--color-gray-800)}.dark\:bg-green-950:is(.dark *){background-color:var(--color-green-950)}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:bg-muted:is(.dark *){background-color:var(--muted)}.dark\:bg-neutral-900:is(.dark *){background-color:var(--color-neutral-900)}.dark\:bg-orange-950:is(.dark *){background-color:var(--color-orange-950)}.dark\:bg-pink-950:is(.dark *){background-color:var(--color-pink-950)}.dark\:bg-purple-900:is(.dark *){background-color:var(--color-purple-900)}.dark\:bg-purple-950:is(.dark *){background-color:var(--color-purple-950)}.dark\:bg-red-900:is(.dark *){background-color:var(--color-red-900)}.dark\:bg-red-950:is(.dark *){background-color:var(--color-red-950)}.dark\:bg-white:is(.dark *){background-color:var(--color-white)}.dark\:bg-zinc-800:is(.dark *){background-color:var(--color-zinc-800)}.dark\:bg-zinc-900:is(.dark *){background-color:var(--color-zinc-900)}.dark\:bg-zinc-900\!:is(.dark *){background-color:var(--color-zinc-900)!important}.dark\:stroke-slate-600:is(.dark *){stroke:var(--color-slate-600)}.dark\:text-background:is(.dark *){color:var(--background)}.dark\:text-black:is(.dark *){color:var(--color-black)}.dark\:text-cyan-300:is(.dark *){color:var(--color-cyan-300)}.dark\:text-foreground:is(.dark *){color:var(--foreground)}.dark\:text-gray-300:is(.dark *){color:var(--color-gray-300)}.dark\:text-green-300:is(.dark *){color:var(--color-green-300)}.dark\:text-muted-foreground:is(.dark *){color:var(--muted-foreground)}.dark\:text-neutral-300:is(.dark *){color:var(--color-neutral-300)}.dark\:text-neutral-400:is(.dark *){color:var(--color-neutral-400)}.dark\:text-orange-300:is(.dark *){color:var(--color-orange-300)}.dark\:text-pink-300:is(.dark *){color:var(--color-pink-300)}.dark\:text-purple-300:is(.dark *){color:var(--color-purple-300)}.dark\:text-purple-400:is(.dark *){color:var(--color-purple-400)}.dark\:text-red-300:is(.dark *){color:var(--color-red-300)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:text-white:is(.dark *){color:var(--color-white)}.dark\:shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(26\,_29\,_30\)_0px_1px_5px_0px_inset\,_rgb\(76\,_81\,_85\)_0px_0px_0px_0\.5px\,_rgb\(76\,_81\,_85\)_0px_2px_1px_-1px\,_rgb\(76\,_81\,_85\)_0px_1px_0px_0px\]:is(.dark *){--tw-shadow-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.dark\:shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(26\,_29\,_30\)_0px_1px_5px_0px_inset\,_rgb\(76\,_81\,_85\)_0px_0px_0px_0\.5px\,_rgb\(76\,_81\,_85\)_0px_2px_1px_-1px\,_rgb\(76\,_81\,_85\)_0px_1px_0px_0px\]:is(.dark *){--tw-shadow-color:color-mix(in oklab,#ffffff1a 0px .5px 0px 0px inset,#1a1d1e 0px 1px 5px 0px inset,#4c5155 0px 0px 0px .5px,#4c5155 0px 2px 1px -1px,#4c5155 0px 1px 0px 0px var(--tw-shadow-alpha),transparent)}}.dark\:ring-white\/5:is(.dark *){--tw-ring-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.dark\:ring-white\/5:is(.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)5%,transparent)}}@media (hover:hover){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input)50%,transparent)}}.dark\:hover\:bg-transparent:is(.dark *):hover{background-color:#0000}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:data-\[state\=active\]\:border-input:is(.dark *)[data-state=active]{border-color:var(--input)}.dark\:data-\[state\=active\]\:bg-input\/30:is(.dark *)[data-state=active]{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:data-\[state\=active\]\:bg-input\/30:is(.dark *)[data-state=active]{background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:data-\[state\=active\]\:text-foreground:is(.dark *)[data-state=active]{color:var(--foreground)}.dark\:data-\[state\=checked\]\:bg-primary:is(.dark *)[data-state=checked]{background-color:var(--primary)}.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}@media (min-width:48rem){.md\:dark\:hidden:is(.dark *){display:none}}@media print{.print\:hidden{display:none}.print\:break-inside-avoid{break-inside:avoid}.print\:placeholder\:text-transparent::placeholder{color:#0000}}@media (min-width:1600px){.\33 xl\:fixed\:container:is(.layout-fixed *){width:100%}@media (min-width:1600px){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:1600px}}@media (min-width:2000px){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:2000px}}@media (min-width:40rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:40rem}}@media (min-width:48rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:48rem}}@media (min-width:64rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:64rem}}@media (min-width:80rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:80rem}}@media (min-width:96rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:96rem}}.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media (min-width:80rem){.\33 xl\:fixed\:container:is(.layout-fixed *){padding-inline:calc(var(--spacing)*6)}}.\33 xl\:fixed\:px-0:is(.layout-fixed *){padding-inline:calc(var(--spacing)*0)}.\33 xl\:fixed\:px-3:is(.layout-fixed *){padding-inline:calc(var(--spacing)*3)}}.\[\&_\*\:\:selection\]\:\!bg-transparent ::selection{background-color:#0000!important}.\[\&_\*\:\:selection\]\:bg-none ::selection{background-image:none}.\[\&_\.katex-display\]\:my-0 .katex-display{margin-block:calc(var(--spacing)*0)}.\[\&_\.katex-display\]\:my-0\! .katex-display{margin-block:calc(var(--spacing)*0)!important}.\[\&_\.line\:before\]\:sticky .line:before{position:sticky}.\[\&_\.line\:before\]\:left-2 .line:before{left:calc(var(--spacing)*2)}.\[\&_\.line\:before\]\:z-10 .line:before{z-index:10}.\[\&_\.line\:before\]\:-translate-y-px .line:before{--tw-translate-y:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&_\.line\:before\]\:pr-1 .line:before{padding-right:calc(var(--spacing)*1)}.\[\&_\.react-tweet-theme\]\:my-0 .react-tweet-theme{margin-block:calc(var(--spacing)*0)}.\[\&_\.react-tweet-theme\]\:ring-2 .react-tweet-theme{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.\[\&_\.react-tweet-theme\]\:ring-ring .react-tweet-theme{--tw-ring-color:var(--ring)}.\[\&_\.react-tweet-theme\]\:ring-offset-2 .react-tweet-theme{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.\[\&_\.slate-selected\]\:\!bg-primary\/20 .slate-selected{background-color:var(--primary)!important}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selected\]\:\!bg-primary\/20 .slate-selected{background-color:color-mix(in oklab,var(--primary)20%,transparent)!important}}.\[\&_\.slate-selection-area\]\:z-50 .slate-selection-area{z-index:50}.\[\&_\.slate-selection-area\]\:border .slate-selection-area{border-style:var(--tw-border-style);border-width:1px}.\[\&_\.slate-selection-area\]\:border-brand\/25 .slate-selection-area{border-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:border-brand\/25 .slate-selection-area{border-color:color-mix(in oklab,var(--brand)25%,transparent)}}.\[\&_\.slate-selection-area\]\:border-primary .slate-selection-area{border-color:var(--primary)}.\[\&_\.slate-selection-area\]\:bg-brand\/15 .slate-selection-area{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:bg-brand\/15 .slate-selection-area{background-color:color-mix(in oklab,var(--brand)15%,transparent)}}.\[\&_\.slate-selection-area\]\:bg-primary\/10 .slate-selection-area{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:bg-primary\/10 .slate-selection-area{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.\[\&_\>_\.lty-playbtn\]\:absolute>.lty-playbtn{position:absolute}.\[\&_\>_\.lty-playbtn\]\:top-1\/2>.lty-playbtn{top:50%}.\[\&_\>_\.lty-playbtn\]\:left-1\/2>.lty-playbtn{left:50%}.\[\&_\>_\.lty-playbtn\]\:z-1>.lty-playbtn{z-index:1}.\[\&_\>_\.lty-playbtn\]\:h-\[46px\]>.lty-playbtn{height:46px}.\[\&_\>_\.lty-playbtn\]\:w-\[70px\]>.lty-playbtn{width:70px}.\[\&_\>_\.lty-playbtn\]\:\[transform\:translate3d\(-50\%\,-50\%\,0\)\]>.lty-playbtn{transform:translate(-50%,-50%)}.\[\&_\>_\.lty-playbtn\]\:rounded-\[14\%\]>.lty-playbtn{border-radius:14%}.\[\&_\>_\.lty-playbtn\]\:bg-\[\#212121\]>.lty-playbtn{background-color:#212121}.\[\&_\>_\.lty-playbtn\]\:opacity-80>.lty-playbtn{opacity:.8}.\[\&_\>_\.lty-playbtn\]\:\[transition\:all_0\.2s_cubic-bezier\(0\,_0\,_0\.2\,_1\)\]>.lty-playbtn{transition:all .2s cubic-bezier(0,0,.2,1)}.\[\&_\>_\.lty-playbtn\]\:before\:absolute>.lty-playbtn:before{content:var(--tw-content);position:absolute}.\[\&_\>_\.lty-playbtn\]\:before\:top-1\/2>.lty-playbtn:before{content:var(--tw-content);top:50%}.\[\&_\>_\.lty-playbtn\]\:before\:left-1\/2>.lty-playbtn:before{content:var(--tw-content);left:50%}.\[\&_\>_\.lty-playbtn\]\:before\:\[transform\:translate3d\(-50\%\,-50\%\,0\)\]>.lty-playbtn:before{content:var(--tw-content);transform:translate(-50%,-50%)}.\[\&_\>_\.lty-playbtn\]\:before\:border-y-\[11px\]>.lty-playbtn:before{content:var(--tw-content);border-block-style:var(--tw-border-style);border-block-width:11px}.\[\&_\>_\.lty-playbtn\]\:before\:border-r-0>.lty-playbtn:before{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:0}.\[\&_\>_\.lty-playbtn\]\:before\:border-l-\[19px\]>.lty-playbtn:before{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:19px}.\[\&_\>_\.lty-playbtn\]\:before\:border-\[transparent_transparent_transparent_\#fff\]>.lty-playbtn:before{content:var(--tw-content);border-color:#0000 #0000 #0000 #fff}.\[\&_\>_\.lty-playbtn\]\:before\:content-\[\"\"\]>.lty-playbtn:before{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.\[\&_\>_\.lty-playbtn\]\:before\:content-\[\\\"\\\"\]>.lty-playbtn:before{content:var(--tw-content);--tw-content:\"\";content:var(--tw-content)}.\[\&_\>_iframe\]\:absolute>iframe{position:absolute}.\[\&_\>_iframe\]\:top-0>iframe{top:calc(var(--spacing)*0)}.\[\&_\>_iframe\]\:left-0>iframe{left:calc(var(--spacing)*0)}.\[\&_\>_iframe\]\:size-full>iframe{width:100%;height:100%}.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading]{padding-block:calc(var(--spacing)*1.5)}.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading]{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading]{color:var(--muted-foreground)}.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden])~[cmdk-group]{padding-top:calc(var(--spacing)*0)}.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg{height:calc(var(--spacing)*5)}.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg{width:calc(var(--spacing)*5)}.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input]{height:calc(var(--spacing)*12)}.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item]{padding-block:calc(var(--spacing)*3)}.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg{height:calc(var(--spacing)*5)}.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg{width:calc(var(--spacing)*5)}.\[\&_button\]\:hidden button{display:none}.\[\&_code\]\:text-sm code{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\[\&_h3\.font-heading\]\:text-base h3.font-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\[\&_h3\.font-heading\]\:font-semibold h3.font-heading{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_p\]\:leading-relaxed p{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_pre\]\:my-0 pre{margin-block:calc(var(--spacing)*0)}.\[\&_pre\]\:h-\(--height\) pre{height:var(--height)}.\[\&_pre\]\:max-h-\[350px\] pre{max-height:350px}.\[\&_pre\]\:max-h-\[650px\] pre{max-height:650px}.\[\&_pre\]\:overflow-auto pre{overflow:auto}.\[\&_pre\]\:overflow-hidden pre{overflow:hidden}.\[\&_pre\]\:bg-transparent\! pre{background-color:#0000!important}.\[\&_pre\]\:pt-4 pre{padding-top:calc(var(--spacing)*4)}.\[\&_pre\]\:pb-20 pre{padding-bottom:calc(var(--spacing)*20)}.\[\&_pre\]\:pb-\[100px\] pre{padding-bottom:100px}.\[\&_pre\]\:font-mono pre{font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.\[\&_pre\]\:text-sm pre{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\[\&_pre\]\:leading-relaxed pre{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_strong\]\:font-bold strong{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:\!size-3 svg{width:calc(var(--spacing)*3)!important;height:calc(var(--spacing)*3)!important}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\]\:size-6 svg{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\]\:text-muted-foreground svg{color:var(--muted-foreground)}@media (hover:hover){.\[\&_svg\]\:hover\:text-muted-foreground svg:hover{color:var(--muted-foreground)}}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:var(--muted-foreground)}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&_ul\]\:list-\[circle\] ul{list-style-type:circle}.\[\&_ul_ul\]\:list-\[square\] ul ul{list-style-type:square}.\[\&\.lyt-activated\]\:cursor-\[unset\].lyt-activated{cursor:unset}.\[\&\.lyt-activated\]\:before\:pointer-events-none.lyt-activated:before{content:var(--tw-content);pointer-events:none}.\[\&\.lyt-activated\]\:before\:absolute.lyt-activated:before{content:var(--tw-content);position:absolute}.\[\&\.lyt-activated\]\:before\:top-0.lyt-activated:before{content:var(--tw-content);top:calc(var(--spacing)*0)}.\[\&\.lyt-activated\]\:before\:h-\[60px\].lyt-activated:before{content:var(--tw-content);height:60px}.\[\&\.lyt-activated\]\:before\:w-full.lyt-activated:before{content:var(--tw-content);width:100%}.\[\&\.lyt-activated\]\:before\:bg-\[url\(data\:image\/png\;base64\,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT\+OqFAAAAdklEQVQoz42QQQ7AIAgEF\/T\/D\+kbq\/RWAlnQyyazA4aoAB4FsBSA\/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg\=\=\)\].lyt-activated:before{content:var(--tw-content);background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)}.\[\&\.lyt-activated\]\:before\:bg-top.lyt-activated:before{content:var(--tw-content);background-position:top}.\[\&\.lyt-activated\]\:before\:bg-repeat-x.lyt-activated:before{content:var(--tw-content);background-repeat:repeat-x}.\[\&\.lyt-activated\]\:before\:pb-\[50px\].lyt-activated:before{content:var(--tw-content);padding-bottom:50px}.\[\&\.lyt-activated\]\:before\:opacity-0.lyt-activated:before{content:var(--tw-content);opacity:0}.\[\&\.lyt-activated\]\:before\:\[transition\:all_0\.2s_cubic-bezier\(0\,_0\,_0\.2\,_1\)\].lyt-activated:before{content:var(--tw-content);transition:all .2s cubic-bezier(0,0,.2,1)}.\[\&\.lyt-activated_\>_\.lty-playbtn\]\:pointer-events-none.lyt-activated>.lty-playbtn{pointer-events:none}.\[\&\.lyt-activated_\>_\.lty-playbtn\]\:opacity-0\!.lyt-activated>.lty-playbtn{opacity:0!important}.\[\&\:\:-webkit-scrollbar\]\:w-4::-webkit-scrollbar{width:calc(var(--spacing)*4)}.\[\&\:\:-webkit-scrollbar-button\]\:hidden::-webkit-scrollbar-button{display:none}.\[\&\:\:-webkit-scrollbar-button\]\:size-0::-webkit-scrollbar-button{width:calc(var(--spacing)*0);height:calc(var(--spacing)*0)}.\[\&\:\:-webkit-scrollbar-thumb\]\:min-h-11::-webkit-scrollbar-thumb{min-height:calc(var(--spacing)*11)}.\[\&\:\:-webkit-scrollbar-thumb\]\:rounded-full::-webkit-scrollbar-thumb{border-radius:3.40282e38px}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-4::-webkit-scrollbar-thumb{border-style:var(--tw-border-style);border-width:4px}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-solid::-webkit-scrollbar-thumb{--tw-border-style:solid;border-style:solid}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-popover::-webkit-scrollbar-thumb{border-color:var(--popover)}.\[\&\:\:-webkit-scrollbar-thumb\]\:bg-muted::-webkit-scrollbar-thumb{background-color:var(--muted)}.\[\&\:\:-webkit-scrollbar-thumb\]\:bg-clip-padding::-webkit-scrollbar-thumb{background-clip:padding-box}@media (hover:hover){.\[\&\:\:-webkit-scrollbar-thumb\]\:hover\:bg-muted-foreground\/25::-webkit-scrollbar-thumb:hover{background-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.\[\&\:\:-webkit-scrollbar-thumb\]\:hover\:bg-muted-foreground\/25::-webkit-scrollbar-thumb:hover{background-color:color-mix(in oklab,var(--muted-foreground)25%,transparent)}}}.focus\:\[\&\:\:placeholder\]\:opacity-0:focus::placeholder{opacity:0}.\[\&\:has\(\>\.day-range-end\)\]\:rounded-r-md:has(>.day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\>\.day-range-start\)\]\:rounded-l-md:has(>.day-range-start){border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:rounded-md:has([aria-selected]){border-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]){background-color:var(--accent)}.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:first-child:has([aria-selected]){border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:last-child:has([aria-selected]),.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:calc(var(--spacing)*0)}.\[\&\:has\(\[role\=option\]\)\]\:block:has([role=option]){display:block}.\[\&\:hover_\>_\.lty-playbtn\]\:bg-\[red\]:hover>.lty-playbtn{background-color:red}.\[\&\:hover_\>_\.lty-playbtn\]\:opacity-100:hover>.lty-playbtn{opacity:1}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\*\:\[\.hljs-addition\]\:bg-\[\#f0fff4\] *).hljs-addition{background-color:#f0fff4}:is(.\*\*\:\[\.hljs-addition\]\:text-\[\#22863a\] *).hljs-addition{color:#22863a}:is(.dark\:\*\*\:\[\.hljs-addition\]\:bg-\[\#3c5743\]:is(.dark *) *).hljs-addition{background-color:#3c5743}:is(.dark\:\*\*\:\[\.hljs-addition\]\:text-\[\#ceead5\]:is(.dark *) *).hljs-addition{color:#ceead5}:is(.\*\*\:\[\.hljs-attr\,\.hljs-attribute\,\.hljs-literal\,\.hljs-meta\,\.hljs-number\,\.hljs-operator\,\.hljs-selector-attr\,\.hljs-selector-class\,\.hljs-selector-id\,\.hljs-variable\]\:text-\[\#005cc5\] *):is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable){color:#005cc5}:is(.dark\:\*\*\:\[\.hljs-attr\,\.hljs-attribute\,\.hljs-literal\,\.hljs-meta\,\.hljs-number\,\.hljs-operator\,\.hljs-selector-attr\,\.hljs-selector-class\,\.hljs-selector-id\,\.hljs-variable\]\:text-\[\#6596cf\]:is(.dark *) *):is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable){color:#6596cf}:is(.\*\*\:\[\.hljs-built\\\\\\\\\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#e36209\] *):is(.hljs-built\\\\\\_in,.hljs-symbol){color:#e36209}:is(.dark\:\*\*\:\[\.hljs-built\\\\\\\\\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#c3854e\]:is(.dark *) *):is(.hljs-built\\\\\\_in,.hljs-symbol){color:#c3854e}:is(.\*\*\:\[\.hljs-built\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#e36209\] *):is(.hljs-built\\_in,.hljs-symbol){color:#e36209}:is(.dark\:\*\*\:\[\.hljs-built\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#c3854e\]:is(.dark *) *):is(.hljs-built\\_in,.hljs-symbol){color:#c3854e}:is(.\*\*\:\[\.hljs-bullet\]\:text-\[\#735c0f\] *).hljs-bullet{color:#735c0f}:is(.\*\*\:\[\.hljs-comment\,\.hljs-code\,\.hljs-formula\]\:text-\[\#6a737d\] *):is(.hljs-comment,.hljs-code,.hljs-formula),:is(.dark\:\*\*\:\[\.hljs-comment\,\.hljs-code\,\.hljs-formula\]\:text-\[\#6a737d\]:is(.dark *) *):is(.hljs-comment,.hljs-code,.hljs-formula){color:#6a737d}:is(.\*\*\:\[\.hljs-deletion\]\:bg-\[\#ffeef0\] *).hljs-deletion{background-color:#ffeef0}:is(.\*\*\:\[\.hljs-deletion\]\:text-\[\#b31d28\] *).hljs-deletion{color:#b31d28}:is(.dark\:\*\*\:\[\.hljs-deletion\]\:bg-\[\#473235\]:is(.dark *) *).hljs-deletion{background-color:#473235}:is(.dark\:\*\*\:\[\.hljs-deletion\]\:text-\[\#e7c7cb\]:is(.dark *) *).hljs-deletion{color:#e7c7cb}:is(.\*\*\:\[\.hljs-emphasis\]\:italic *).hljs-emphasis{font-style:italic}:is(.\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\\\\\\\\\_\]\:text-\[\#d73a49\] *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\\\_){color:#d73a49}:is(.dark\:\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\\\\\\\\\_\]\:text-\[\#ee6960\]:is(.dark *) *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\\\_){color:#ee6960}:is(.\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\_\]\:text-\[\#d73a49\] *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\_){color:#d73a49}:is(.dark\:\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\_\]\:text-\[\#ee6960\]:is(.dark *) *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\_){color:#ee6960}:is(.\*\*\:\[\.hljs-name\,\.hljs-quote\,\.hljs-selector-tag\,\.hljs-selector-pseudo\]\:text-\[\#22863a\] *):is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:#22863a}:is(.dark\:\*\*\:\[\.hljs-name\,\.hljs-quote\,\.hljs-selector-tag\,\.hljs-selector-pseudo\]\:text-\[\#36a84f\]:is(.dark *) *):is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:#36a84f}:is(.\*\*\:\[\.hljs-regexp\,\.hljs-string\,\.hljs-meta_\.hljs-string\]\:text-\[\#032f62\] *):is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:#032f62}:is(.dark\:\*\*\:\[\.hljs-regexp\,\.hljs-string\,\.hljs-meta_\.hljs-string\]\:text-\[\#3593ff\]:is(.dark *) *):is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:#3593ff}:is(.\*\*\:\[\.hljs-section\]\:font-bold *).hljs-section{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(.\*\*\:\[\.hljs-section\]\:text-\[\#005cc5\] *).hljs-section{color:#005cc5}:is(.dark\:\*\*\:\[\.hljs-section\]\:text-\[\#61a5f2\]:is(.dark *) *).hljs-section{color:#61a5f2}:is(.\*\*\:\[\.hljs-strong\]\:font-bold *).hljs-strong{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(.\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\.inherited\\\\\\\\\\\\\\\\_\\\\\\\\\\\\\\\\_\,\.hljs-title\.function\\\\\\\\\\\\\\\\_\]\:text-\[\#6f42c1\] *):is(.hljs-title,.hljs-title.class\\\\\\_,.hljs-title.class\\\\\\_.inherited\\\\\\_\\\\\\_,.hljs-title.function\\\\\\_){color:#6f42c1}:is(.dark\:\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\.inherited\\\\\\\\\\\\\\\\_\\\\\\\\\\\\\\\\_\,\.hljs-title\.function\\\\\\\\\\\\\\\\_\]\:text-\[\#a77bfa\]:is(.dark *) *):is(.hljs-title,.hljs-title.class\\\\\\_,.hljs-title.class\\\\\\_.inherited\\\\\\_\\\\\\_,.hljs-title.function\\\\\\_){color:#a77bfa}:is(.\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\_\,\.hljs-title\.class\\\\\\\\_\.inherited\\\\\\\\_\\\\\\\\_\,\.hljs-title\.function\\\\\\\\_\]\:text-\[\#6f42c1\] *):is(.hljs-title,.hljs-title.class\\_,.hljs-title.class\\_.inherited\\_\\_,.hljs-title.function\\_){color:#6f42c1}:is(.dark\:\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\_\,\.hljs-title\.class\\\\\\\\_\.inherited\\\\\\\\_\\\\\\\\_\,\.hljs-title\.function\\\\\\\\_\]\:text-\[\#a77bfa\]:is(.dark *) *):is(.hljs-title,.hljs-title.class\\_,.hljs-title.class\\_.inherited\\_\\_,.hljs-title.function\\_){color:#a77bfa}@media (hover:hover){:is(.\*\*\:\[\[data-slot\=\"mdx-link\"\]\]\:hover\:after\:bottom-0 *)[data-slot=mdx-link]:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}}:is(.\*\:\[code\]\:bg-inherit>*):is(code){background-color:inherit}:is(.\*\:\[h3\,h4\]\:mt-8>*):is(h3,h4){margin-top:calc(var(--spacing)*8)}:is(.\*\:\[h3\,h4\]\:mb-4>*):is(h3,h4){margin-bottom:calc(var(--spacing)*4)}:is(.\*\:\[h3\,h4\]\:text-base>*):is(h3,h4){font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}:is(.\*\:\[h3\,h4\]\:font-semibold>*):is(h3,h4){--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}:is(.\*\:\[h3\,h4\]\:\[counter-increment\:step\]>*):is(h3,h4){counter-increment:step}:is(.\*\:\[h3\,h4\]\:before\:absolute>*):is(h3,h4):before{content:var(--tw-content);position:absolute}:is(.\*\:\[h3\,h4\]\:before\:mt-\[-4px\]>*):is(h3,h4):before{content:var(--tw-content);margin-top:-4px}:is(.\*\:\[h3\,h4\]\:before\:ml-\[-50px\]>*):is(h3,h4):before{content:var(--tw-content);margin-left:-50px}:is(.\*\:\[h3\,h4\]\:before\:inline-flex>*):is(h3,h4):before{content:var(--tw-content);display:inline-flex}:is(.\*\:\[h3\,h4\]\:before\:h-9>*):is(h3,h4):before{content:var(--tw-content);height:calc(var(--spacing)*9)}:is(.\*\:\[h3\,h4\]\:before\:w-9>*):is(h3,h4):before{content:var(--tw-content);width:calc(var(--spacing)*9)}:is(.\*\:\[h3\,h4\]\:before\:items-center>*):is(h3,h4):before{content:var(--tw-content);align-items:center}:is(.\*\:\[h3\,h4\]\:before\:justify-center>*):is(h3,h4):before{content:var(--tw-content);justify-content:center}:is(.\*\:\[h3\,h4\]\:before\:rounded-full>*):is(h3,h4):before{content:var(--tw-content);border-radius:3.40282e38px}:is(.\*\:\[h3\,h4\]\:before\:border-4>*):is(h3,h4):before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:4px}:is(.\*\:\[h3\,h4\]\:before\:border-background>*):is(h3,h4):before{content:var(--tw-content);border-color:var(--background)}:is(.\*\:\[h3\,h4\]\:before\:bg-muted>*):is(h3,h4):before{content:var(--tw-content);background-color:var(--muted)}:is(.\*\:\[h3\,h4\]\:before\:text-center>*):is(h3,h4):before{content:var(--tw-content);text-align:center}:is(.\*\:\[h3\,h4\]\:before\:-indent-px>*):is(h3,h4):before{content:var(--tw-content);text-indent:-1px}:is(.\*\:\[h3\,h4\]\:before\:font-mono>*):is(h3,h4):before{content:var(--tw-content);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}:is(.\*\:\[h3\,h4\]\:before\:text-base>*):is(h3,h4):before{content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}:is(.\*\:\[h3\,h4\]\:before\:font-medium>*):is(h3,h4):before{content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}:is(.\*\:\[h3\,h4\]\:before\:\[content\:counter\(step\)\]>*):is(h3,h4):before{content:var(--tw-content);content:counter(step)}:is(.\*\*\:\[p\]\:m-0 *):is(p){margin:calc(var(--spacing)*0)}:is(.\*\*\:\[p\,ul\,ol\,li\]\:first\:my-0 *):is(p,ul,ol,li):first-child{margin-block:calc(var(--spacing)*0)}:is(.\*\:first\:\[span\]\:hidden>*):first-child:is(span){display:none}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}:is(.\*\:\[svg\]\:text-muted-foreground>*):is(svg){color:var(--muted-foreground)}:is(.\*\:\[svg\]\:text-neutral-50>*):is(svg){color:var(--color-neutral-50)}:is(.data-\[variant\=destructive\]\:\*\:\[svg\]\:\!text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)!important}:is(.dark\:\*\:\[svg\]\:text-neutral-900:is(.dark *)>*):is(svg){color:var(--color-neutral-900)}:is(.\*\:\[ul\]\:my-0>*):is(ul){margin-block:calc(var(--spacing)*0)}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>button\]\:hidden>button{display:none}.\[\&\>span\:last-child\]\:truncate>span:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.\[\&\>svg\]\:pointer-events-none>svg{pointer-events:none}.\[\&\>svg\]\:size-3>svg{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.\[\&\>svg\]\:size-4>svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}.\[\&\>svg\]\:translate-y-0\.5>svg{--tw-translate-y:calc(var(--spacing)*.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\]\:text-current>svg{color:currentColor}.\[\&\>svg\]\:text-sidebar-accent-foreground>svg{color:var(--sidebar-accent-foreground)}.\[\&\>tr\]\:last\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.\[\&\[align\=center\]\]\:text-center[align=center]{text-align:center}.\[\&\[align\=right\]\]\:text-right[align=right]{text-align:right}.\[\&\[data-panel-group-direction\=vertical\]\>div\]\:rotate-90[data-panel-group-direction=vertical]>div,.\[\&\[data-state\=open\]\>button\>svg\:first-child\]\:rotate-90[data-state=open]>button>svg:first-child{rotate:90deg}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{rotate:180deg}@media (min-width:1800px){.\[\@media\(width\>\=1800px\)\]\:max-w-\(--breakpoint-2xl\){max-width:var(--breakpoint-2xl)}.\[\@media\(width\>\=1800px\)\]\:border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}}[data-side=left][data-collapsible=offcanvas] .\[\[data-side\=left\]\[data-collapsible\=offcanvas\]_\&\]\:-right-2{right:calc(var(--spacing)*-2)}[data-side=left][data-state=collapsed] .\[\[data-side\=left\]\[data-state\=collapsed\]_\&\]\:cursor-e-resize{cursor:e-resize}[data-side=right][data-collapsible=offcanvas] .\[\[data-side\=right\]\[data-collapsible\=offcanvas\]_\&\]\:-left-2{left:calc(var(--spacing)*-2)}[data-side=right][data-state=collapsed] .\[\[data-side\=right\]\[data-state\=collapsed\]_\&\]\:cursor-w-resize{cursor:w-resize}@media (hover:hover){a.\[a\&\]\:hover\:bg-accent:hover{background-color:var(--accent)}a.\[a\&\]\:hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}a.\[a\&\]\:hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}a.\[a\&\]\:hover\:bg-secondary\/90:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-secondary\/90:hover{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}}a.\[a\&\]\:hover\:text-accent-foreground:hover{color:var(--accent-foreground)}}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}.prose{max-width:65ch;color:var(--foreground)}.prose h1:not(.not-prose h1){scroll-margin:calc(var(--spacing)*20);font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height));--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}@media (min-width:64rem){.prose h1:not(.not-prose h1){font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}}.prose h2:not(.not-prose h2){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);border-bottom-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*2);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);border-bottom-width:1px}.prose h2:not(.not-prose h2):first-child{margin-top:calc(var(--spacing)*0)}.prose h3:not(.not-prose h3){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h3:not(.not-prose h3):first-child{margin-top:calc(var(--spacing)*0)}.prose h4:not(.not-prose h4){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h4:not(.not-prose h4):first-child{margin-top:calc(var(--spacing)*0)}.prose h5:not(.not-prose h5){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h5:not(.not-prose h5):first-child{margin-top:calc(var(--spacing)*0)}.prose h6:not(.not-prose h6){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h6:not(.not-prose h6):first-child,:is(.prose h1:not(.not-prose h1),.prose h2:not(.not-prose h2),.prose h3:not(.not-prose h3),.prose h4:not(.not-prose h4),.prose h5:not(.not-prose h5),.prose h6:not(.not-prose h6))+p{margin-top:calc(var(--spacing)*0)}.prose p:not(.not-prose p){--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.prose p:not(.not-prose p):not(:first-child){margin-top:calc(var(--spacing)*6)}.prose a:not(.not-prose a){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--primary);text-underline-offset:4px;text-decoration-line:underline}.prose blockquote:not(.not-prose blockquote){margin-top:calc(var(--spacing)*6);border-left-style:var(--tw-border-style);padding-left:calc(var(--spacing)*6);border-left-width:2px;font-style:italic}.prose table:not(.not-prose table){margin-block:calc(var(--spacing)*6);width:100%;overflow-y:auto}.prose table:not(.not-prose table) thead tr{margin:calc(var(--spacing)*0);border-top-style:var(--tw-border-style);padding:calc(var(--spacing)*0);border-top-width:1px}.prose table:not(.not-prose table) thead tr:nth-child(2n){background-color:var(--muted)}.prose table:not(.not-prose table) th{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:left;--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);border-width:1px}.prose table:not(.not-prose table) th[align=center]{text-align:center}.prose table:not(.not-prose table) th[align=right]{text-align:right}.prose table:not(.not-prose table) tbody tr{margin:calc(var(--spacing)*0);border-top-style:var(--tw-border-style);padding:calc(var(--spacing)*0);border-top-width:1px}.prose table:not(.not-prose table) tbody tr:nth-child(2n){background-color:var(--muted)}.prose table:not(.not-prose table) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:left;border-width:1px}.prose table:not(.not-prose table) td[align=center]{text-align:center}.prose table:not(.not-prose table) td[align=right]{text-align:right}.prose ul:not(.not-prose ul){margin-block:calc(var(--spacing)*6);margin-left:calc(var(--spacing)*6);list-style-type:disc}.prose ul:not(.not-prose ul)>li{margin-top:calc(var(--spacing)*2)}.prose ul:not(.not-prose ul) p{margin-block:calc(var(--spacing)*0);display:inline}.prose ol:not(.not-prose ol){margin-block:calc(var(--spacing)*6);margin-left:calc(var(--spacing)*6);list-style-type:decimal}.prose ol:not(.not-prose ol)>li{margin-top:calc(var(--spacing)*2)}.prose ol:not(.not-prose ol) p{margin-block:calc(var(--spacing)*0);display:inline}.prose pre:not(.not-prose pre){margin-block:calc(var(--spacing)*6);border-radius:calc(var(--radius) - 2px);background-color:var(--muted);padding:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-y:auto}.prose code:not(pre code):not(.not-prose code){background-color:var(--muted);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);border-radius:.25rem;padding-block:.2rem;padding-inline:.3rem;position:relative}.prose .lead:not(.not-prose .lead){font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));color:var(--muted-foreground)}.prose .large:not(.not-prose .large){font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.prose .small:not(.not-prose .small){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium)}.prose .muted:not(.not-prose .muted){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--muted-foreground)}.prose img:not(.not-prose img),.prose picture:not(.not-prose picture),.prose video:not(.not-prose video){margin-block:calc(var(--spacing)*6)}.prose picture>img:not(.not-prose picture>img){margin-block:calc(var(--spacing)*0)}.prose kbd:not(.not-prose kbd){border-radius:calc(var(--radius) - 2px);background-color:var(--muted);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.prose hr{margin-block:calc(var(--spacing)*10)}.prose dl:not(.not-prose dl){margin-block:calc(var(--spacing)*6)}.prose dl:not(.not-prose dl) dt{margin-top:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose dl:not(.not-prose dl) dt:first-child{margin-top:calc(var(--spacing)*0)}.prose details:not(.not-prose details){margin-top:calc(var(--spacing)*6)}.prose details:not(.not-prose details) summary{margin-top:calc(var(--spacing)*6);cursor:pointer;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose details:not(.not-prose details) p:first-of-type{margin-top:calc(var(--spacing)*2)}.prose mark:not(.not-prose mark){background-color:var(--color-yellow-300)}.prose small:not(.not-prose small){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;line-height:1}.theme-default .theme-container{--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}.theme-blue .theme-container{--primary:var(--color-blue-600);--primary-foreground:var(--color-blue-50);--ring:var(--color-blue-400);--sidebar-primary:var(--color-blue-600);--sidebar-primary-foreground:var(--color-blue-50);--sidebar-ring:var(--color-blue-400);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}.theme-blue .theme-container:is(.dark *){--primary:var(--color-blue-500);--primary-foreground:var(--color-blue-50);--ring:var(--color-blue-900);--sidebar-primary:var(--color-blue-500);--sidebar-primary-foreground:var(--color-blue-50);--sidebar-ring:var(--color-blue-900)}.theme-green .theme-container{--primary:var(--color-lime-600);--primary-foreground:var(--color-lime-50);--ring:var(--color-lime-400);--chart-1:var(--color-green-300);--chart-2:var(--color-green-500);--chart-3:var(--color-green-600);--chart-4:var(--color-green-700);--chart-5:var(--color-green-800);--sidebar-primary:var(--color-lime-600);--sidebar-primary-foreground:var(--color-lime-50);--sidebar-ring:var(--color-lime-400)}.theme-green .theme-container:is(.dark *){--primary:var(--color-lime-600);--primary-foreground:var(--color-lime-50);--ring:var(--color-lime-900);--sidebar-primary:var(--color-lime-500);--sidebar-primary-foreground:var(--color-lime-50);--sidebar-ring:var(--color-lime-900)}.theme-amber .theme-container{--primary:var(--color-amber-600);--primary-foreground:var(--color-amber-50);--ring:var(--color-amber-400);--chart-1:var(--color-amber-300);--chart-2:var(--color-amber-500);--chart-3:var(--color-amber-600);--chart-4:var(--color-amber-700);--chart-5:var(--color-amber-800);--sidebar-primary:var(--color-amber-600);--sidebar-primary-foreground:var(--color-amber-50);--sidebar-ring:var(--color-amber-400)}.theme-amber .theme-container:is(.dark *){--primary:var(--color-amber-500);--primary-foreground:var(--color-amber-50);--ring:var(--color-amber-900);--sidebar-primary:var(--color-amber-500);--sidebar-primary-foreground:var(--color-amber-50);--sidebar-ring:var(--color-amber-900)}.theme-rose .theme-container{--primary:var(--color-rose-600);--primary-foreground:var(--color-rose-50);--ring:var(--color-rose-400);--chart-1:var(--color-rose-300);--chart-2:var(--color-rose-500);--chart-3:var(--color-rose-600);--chart-4:var(--color-rose-700);--chart-5:var(--color-rose-800);--sidebar-primary:var(--color-rose-600);--sidebar-primary-foreground:var(--color-rose-50);--sidebar-ring:var(--color-rose-400)}.theme-rose .theme-container:is(.dark *){--primary:var(--color-rose-500);--primary-foreground:var(--color-rose-50);--ring:var(--color-rose-900);--sidebar-primary:var(--color-rose-500);--sidebar-primary-foreground:var(--color-rose-50);--sidebar-ring:var(--color-rose-900)}.theme-purple .theme-container{--primary:var(--color-purple-600);--primary-foreground:var(--color-purple-50);--ring:var(--color-purple-400);--chart-1:var(--color-purple-300);--chart-2:var(--color-purple-500);--chart-3:var(--color-purple-600);--chart-4:var(--color-purple-700);--chart-5:var(--color-purple-800);--sidebar-primary:var(--color-purple-600);--sidebar-primary-foreground:var(--color-purple-50);--sidebar-ring:var(--color-purple-400)}.theme-purple .theme-container:is(.dark *){--primary:var(--color-purple-500);--primary-foreground:var(--color-purple-50);--ring:var(--color-purple-900);--sidebar-primary:var(--color-purple-500);--sidebar-primary-foreground:var(--color-purple-50);--sidebar-ring:var(--color-purple-900)}.theme-orange .theme-container{--primary:var(--color-orange-600);--primary-foreground:var(--color-orange-50);--ring:var(--color-orange-400);--chart-1:var(--color-orange-300);--chart-2:var(--color-orange-500);--chart-3:var(--color-orange-600);--chart-4:var(--color-orange-700);--chart-5:var(--color-orange-800);--sidebar-primary:var(--color-orange-600);--sidebar-primary-foreground:var(--color-orange-50);--sidebar-ring:var(--color-orange-400)}.theme-orange .theme-container:is(.dark *){--primary:var(--color-orange-500);--primary-foreground:var(--color-orange-50);--ring:var(--color-orange-900);--sidebar-primary:var(--color-orange-500);--sidebar-primary-foreground:var(--color-orange-50);--sidebar-ring:var(--color-orange-900)}.theme-teal .theme-container{--primary:var(--color-teal-600);--primary-foreground:var(--color-teal-50);--chart-1:var(--color-teal-300);--chart-2:var(--color-teal-500);--chart-3:var(--color-teal-600);--chart-4:var(--color-teal-700);--chart-5:var(--color-teal-800);--sidebar-primary:var(--color-teal-600);--sidebar-primary-foreground:var(--color-teal-50);--sidebar-ring:var(--color-teal-400)}.theme-teal .theme-container:is(.dark *){--primary:var(--color-teal-500);--primary-foreground:var(--color-teal-50);--sidebar-primary:var(--color-teal-500);--sidebar-primary-foreground:var(--color-teal-50);--sidebar-ring:var(--color-teal-900)}.theme-mono .theme-container{--font-sans:var(--font-mono);--primary:var(--color-stone-600);--primary-foreground:var(--color-stone-50);--chart-1:var(--color-stone-300);--chart-2:var(--color-stone-500);--chart-3:var(--color-stone-600);--chart-4:var(--color-stone-700);--chart-5:var(--color-stone-800);--sidebar-primary:var(--color-stone-600);--sidebar-primary-foreground:var(--color-stone-50);--sidebar-ring:var(--color-stone-400)}.theme-mono .theme-container:is(.dark *){--primary:var(--color-stone-500);--primary-foreground:var(--color-stone-50);--sidebar-primary:var(--color-stone-500);--sidebar-primary-foreground:var(--color-stone-50);--sidebar-ring:var(--color-stone-900)}@media (min-width:1024px){.theme-mono .theme-container{--font-sans:var(--font-mono);--radius:.45em;--text-lg:1rem;--text-xl:1.1rem;--text-2xl:1.2rem;--text-3xl:1.3rem;--text-4xl:1.4rem;--text-5xl:1.5rem;--text-6xl:1.6rem;--text-7xl:1.7rem;--text-8xl:1.8rem;--text-base:.85rem;--text-sm:.8rem;--spacing:.222222rem}}.theme-mono .theme-container .rounded-xs,.theme-mono .theme-container .rounded-sm,.theme-mono .theme-container .rounded-md,.theme-mono .theme-container .rounded-lg,.theme-mono .theme-container .rounded-xl{border-radius:0}.theme-mono .theme-container .shadow-xs,.theme-mono .theme-container .shadow-sm,.theme-mono .theme-container .shadow-md,.theme-mono .theme-container .shadow-lg,.theme-mono .theme-container .shadow-xl{box-shadow:none}.theme-mono .theme-container [data-slot=toggle-group],.theme-mono .theme-container [data-slot=toggle-group-item]{--tw-shadow:0 0 #0000!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important;border-radius:0!important}.theme-scaled .theme-container{--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}@media (min-width:1024px){.theme-scaled .theme-container{--radius:.45em;--text-lg:1rem;--text-xl:1.1rem;--text-2xl:1.2rem;--text-3xl:1.3rem;--text-4xl:1.4rem;--text-5xl:1.5rem;--text-6xl:1.6rem;--text-7xl:1.7rem;--text-8xl:1.8rem;--text-base:.85rem;--text-sm:.8rem;--spacing:.2rem}}.theme-scaled .theme-container [data-slot=select-trigger],.theme-scaled .theme-container [data-slot=toggle-group-item]{--spacing:.2rem}.theme-scaled .theme-container [data-slot=card]{border-radius:var(--radius);padding-block:calc(var(--spacing)*4);gap:calc(var(--spacing)*2)}.theme-scaled .theme-container [data-slot=card].pb-0{padding-bottom:0}.theme-red .theme-container{--primary:var(--color-red-600);--primary-foreground:var(--color-red-50);--ring:var(--color-red-400);--chart-1:var(--color-red-300);--chart-2:var(--color-red-500);--chart-3:var(--color-red-600);--chart-4:var(--color-red-700);--chart-5:var(--color-red-800);--sidebar-primary:var(--color-red-600);--sidebar-primary-foreground:var(--color-red-50);--sidebar-ring:var(--color-red-400)}.theme-red .theme-container:is(.dark *){--primary:var(--color-red-500);--primary-foreground:var(--color-red-50);--ring:var(--color-red-900);--sidebar-primary:var(--color-red-500);--sidebar-primary-foreground:var(--color-red-50);--sidebar-ring:var(--color-red-900)}.theme-yellow .theme-container{--primary:var(--color-yellow-400);--primary-foreground:var(--color-yellow-900);--ring:var(--color-yellow-400);--chart-1:var(--color-yellow-300);--chart-2:var(--color-yellow-500);--chart-3:var(--color-yellow-600);--chart-4:var(--color-yellow-700);--chart-5:var(--color-yellow-800);--sidebar-primary:var(--color-yellow-600);--sidebar-primary-foreground:var(--color-yellow-50);--sidebar-ring:var(--color-yellow-400)}.theme-yellow .theme-container:is(.dark *){--primary:var(--color-yellow-500);--primary-foreground:var(--color-yellow-900);--ring:var(--color-yellow-900);--sidebar-primary:var(--color-yellow-500);--sidebar-primary-foreground:var(--color-yellow-50);--sidebar-ring:var(--color-yellow-900)}.theme-violet .theme-container{--primary:var(--color-violet-600);--primary-foreground:var(--color-violet-50);--ring:var(--color-violet-400);--chart-1:var(--color-violet-300);--chart-2:var(--color-violet-500);--chart-3:var(--color-violet-600);--chart-4:var(--color-violet-700);--chart-5:var(--color-violet-800);--sidebar-primary:var(--color-violet-600);--sidebar-primary-foreground:var(--color-violet-50);--sidebar-ring:var(--color-violet-400)}.theme-violet .theme-container:is(.dark *){--primary:var(--color-violet-500);--primary-foreground:var(--color-violet-50);--ring:var(--color-violet-900);--sidebar-primary:var(--color-violet-500);--sidebar-primary-foreground:var(--color-violet-50);--sidebar-ring:var(--color-violet-900)}:root{--radius:.625rem;--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0);--surface:oklch(98% 0 0);--surface-foreground:var(--foreground);--code:var(--surface);--code-foreground:var(--surface-foreground);--code-highlight:oklch(96% 0 0);--code-number:oklch(56% 0 0);--selection:oklch(14.5% 0 0);--selection-foreground:oklch(100% 0 0);--color-blue-300:oklch(77.8% .108 231.731);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.9% .234 260.011);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(41.9% .243 267.218);--brand:oklch(62.3% .214 259.815);--highlight:oklch(85.2% .199 91.936)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(26.9% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(37.1% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(43.9% 0 0);--surface:oklch(20% 0 0);--surface-foreground:oklch(70.8% 0 0);--code:var(--surface);--code-foreground:var(--surface-foreground);--code-highlight:oklch(27% 0 0);--code-number:oklch(72% 0 0);--selection:oklch(92.2% 0 0);--selection-foreground:oklch(20.5% 0 0);--brand:oklch(70.7% .165 254.624);--highlight:oklch(85.2% .199 91.936)}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0))}}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height,var(--bits-accordion-content-height,var(--reka-accordion-content-height,var(--kb-accordion-content-height,auto))))}}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height,var(--bits-accordion-content-height,var(--reka-accordion-content-height,var(--kb-accordion-content-height,auto))))}to{height:0}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-font-weight:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:"var(--font-sans)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-300:oklch(83.7% .128 66.29);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--color-orange-800:oklch(47% .157 37.304);--color-orange-900:oklch(40.8% .123 38.172);--color-orange-950:oklch(26.6% .079 36.259);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-300:oklch(90.5% .182 98.111);--color-yellow-400:oklch(85.2% .199 91.936);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-yellow-900:oklch(42.1% .095 57.708);--color-lime-50:oklch(98.6% .031 120.757);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-lime-900:oklch(40.5% .101 131.063);--color-green-50:oklch(98.2% .018 155.826);--color-green-100:oklch(96.2% .044 156.743);--color-green-200:oklch(92.5% .084 155.995);--color-green-300:oklch(87.1% .15 154.449);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-green-950:oklch(26.6% .065 152.934);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-50:oklch(98.4% .014 180.72);--color-teal-300:oklch(85.5% .138 181.071);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-teal-700:oklch(51.1% .096 186.391);--color-teal-800:oklch(43.7% .078 188.216);--color-teal-900:oklch(38.6% .063 188.416);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-300:oklch(86.5% .127 207.078);--color-cyan-700:oklch(52% .105 223.128);--color-cyan-950:oklch(30.2% .056 229.695);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-violet-800:oklch(43.2% .232 292.759);--color-violet-900:oklch(38% .189 293.745);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-300:oklch(82.7% .119 306.383);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-purple-600:oklch(55.8% .288 302.321);--color-purple-700:oklch(49.6% .265 301.924);--color-purple-800:oklch(43.8% .218 303.724);--color-purple-900:oklch(38.1% .176 304.987);--color-purple-950:oklch(29.1% .149 302.717);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-300:oklch(82.3% .12 346.018);--color-pink-700:oklch(52.5% .223 3.958);--color-pink-950:oklch(28.4% .109 3.907);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-xl:80rem;--breakpoint-2xl:96rem;--container-sm:24rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-light:300;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--tracking-tighter:-.05em;--tracking-tight:-.025em;--tracking-widest:.1em;--leading-tight:1.25;--leading-normal:1.5;--leading-relaxed:1.625;--leading-loose:2;--radius-xs:.125rem;--radius-sm:calc(var(--radius) - 4px);--radius-lg:var(--radius);--ease-out:cubic-bezier(0,0,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-xs:4px;--blur-sm:8px;--aspect-video:16/9;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:"var(--font-sans)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--default-mono-font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-code:var(--surface);--color-code-foreground:var(--surface-foreground);--color-code-highlight:var(--code-highlight);--color-code-number:var(--code-number);--font-heading:"var(--font-heading)","ui-sans-serif","-apple-system","BlinkMacSystemFont","Segoe UI Variable Display","Segoe UI","Helvetica","Apple Color Emoji","Arial","sans-serif","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}}@layer base{@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab,var(--ring)50%,transparent)}}body{overscroll-behavior:none;background-color:var(--background);color:var(--foreground);font-synthesis-weight:none;text-rendering:optimizeLegibility}@supports (font:-apple-system-body) and (appearance:none){@media (min-width:1800px){[data-wrapper]{border-top-style:var(--tw-border-style);border-top-width:1px}}}a:active,button:active{opacity:.6}@media (min-width:48rem){a:active,button:active{opacity:1}}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:0 0}::-webkit-scrollbar-thumb{background:hsl(var(--border));border-radius:5px}*{scrollbar-width:thin;scrollbar-color:hsl(var(--border))transparent}.prose{--tw-prose-body:var(--foreground);--tw-prose-bold:inherit;--tw-prose-links:inherit;--tw-prose-bullets:var(--foreground)}[data-theme=light]{display:block}[data-theme=dark],.dark [data-theme=light]{display:none}.dark [data-theme=dark]{display:block}[data-rehype-pretty-code-fragment]{color:var(--color-white);position:relative}[data-rehype-pretty-code-fragment] code{border-style:var(--tw-border-style);min-width:100%;padding:calc(var(--spacing)*0);overflow-wrap:break-word;counter-reset:line;-webkit-box-decoration-break:clone;box-decoration-break:clone;background-color:#0000;border-width:0;border-radius:0;display:grid}[data-rehype-pretty-code-fragment] .line{width:100%;min-height:1rem;padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*.5);display:inline-block}[data-rehype-pretty-code-fragment] [data-line-numbers] .line{padding-inline:calc(var(--spacing)*2)}[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));color:#fafafa66}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{color:color-mix(in oklab,var(--color-zinc-50)40%,transparent)}}[data-rehype-pretty-code-fragment] [data-line-numbers]>.line:before{counter-increment:line;content:counter(line);text-align:right;width:1.8rem;margin-right:1.4rem;display:inline-block}[data-rehype-pretty-code-fragment] .line--highlighted{background-color:#3f3f4680}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .line--highlighted{background-color:color-mix(in oklab,var(--color-zinc-700)50%,transparent)}}[data-rehype-pretty-code-fragment] .line-highlighted span{position:relative}[data-rehype-pretty-code-fragment] .word--highlighted{border-radius:calc(var(--radius) - 2px);border-color:#3f3f46b3}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .word--highlighted{border-color:color-mix(in oklab,var(--color-zinc-700)70%,transparent)}}[data-rehype-pretty-code-fragment] .word--highlighted{background-color:#3f3f4680}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-fragment] .word--highlighted{background-color:color-mix(in oklab,var(--color-zinc-700)50%,transparent)}}[data-rehype-pretty-code-fragment] .word--highlighted{padding:calc(var(--spacing)*1)}.dark [data-rehype-pretty-code-fragment] .word--highlighted{background-color:var(--color-zinc-900)}[data-rehype-pretty-code-title]{margin-top:calc(var(--spacing)*2);padding-inline:calc(var(--spacing)*4);padding-top:calc(var(--spacing)*6);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--foreground)}[data-rehype-pretty-code-title]+pre{margin-top:calc(var(--spacing)*2)!important}}@layer components{.steps:first-child,.steps:first-child>h3:first-child{margin-top:calc(var(--spacing)*0)!important}.steps>h3{margin-top:calc(var(--spacing)*16)!important}.steps>h3+p{margin-top:calc(var(--spacing)*2)!important}[data-rehype-pretty-code-figure]{background-color:var(--color-code);color:var(--color-code-foreground);border-radius:var(--radius-lg);border-width:0;border-color:var(--border);margin-top:calc(var(--spacing)*6);font-size:var(--text-sm);outline:none;position:relative;overflow:hidden}@media (min-width:48rem){[data-rehype-pretty-code-figure]{margin-inline:calc(var(--spacing)*-4)}}[data-rehype-pretty-code-figure]:has([data-rehype-pretty-code-title]) [data-slot=copy-button]{top:calc(var(--spacing)*1.5)!important}[data-rehype-pretty-code-title]{border-bottom:var(--border)}@supports (color:color-mix(in lab, red, red)){[data-rehype-pretty-code-title]{border-bottom:color-mix(in oklab,var(--border)30%,transparent)}}[data-rehype-pretty-code-title]{padding-block:calc(var(--spacing)*2.5);padding-inline:calc(var(--spacing)*4);font-size:var(--text-sm);font-family:var(--font-mono);color:var(--color-code-foreground);border-bottom-style:solid;border-bottom-width:1px}[data-line-numbers]{white-space:pre;counter-reset:line;-webkit-box-decoration-break:clone;box-decoration-break:clone;background:0 0;border:0;min-width:100%;padding:0;display:grid}[data-line-numbers] [data-line]:before{font-size:var(--text-sm);counter-increment:line;content:counter(line);width:calc(var(--spacing)*16);padding-right:calc(var(--spacing)*6);text-align:right;color:var(--color-code-number);background-color:var(--color-code);display:inline-block;position:sticky;left:0}[data-line-numbers] [data-highlighted-line][data-line]:before{background-color:var(--color-code-highlight)}[data-line]{padding-top:calc(var(--spacing)*.5);padding-bottom:calc(var(--spacing)*.5);min-height:calc(var(--spacing)*1);width:100%;display:inline-block}[data-line] span{color:var(--shiki-light)}[data-line] span:is(.dark *){color:var(--shiki-dark)!important}[data-highlighted-line],[data-highlighted-chars]{background-color:var(--color-code-highlight);position:relative}[data-highlighted-line]:after{content:"";background-color:var(--muted-foreground);width:2px;height:100%;position:absolute;top:0;left:0}@supports (color:color-mix(in lab, red, red)){[data-highlighted-line]:after{background-color:color-mix(in oklab,var(--muted-foreground)50%,transparent)}}[data-highlighted-chars]{border-radius:var(--radius-sm);font-family:var(--font-mono);padding-block:.1rem;padding-inline:.3rem;font-size:.8rem}}@layer utilities{.\@container\/card-header{container:card-header/inline-size}.\@container{container-type:inline-size}.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}@media (pointer:coarse){.extend-touch-target{touch-action:manipulation;position:relative}.extend-touch-target:after{content:var(--tw-content);content:var(--tw-content);inset:calc(var(--spacing)*-2);position:absolute}}.step{counter-increment:step;position:relative}.step:before{right:calc(var(--spacing)*0);margin-right:calc(var(--spacing)*2);width:calc(var(--spacing)*7);height:calc(var(--spacing)*7);text-align:center;text-indent:-1px;font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--muted-foreground);border-radius:3.40282e38px;justify-content:center;align-items:center;display:none}@media (min-width:48rem){.step:before{position:absolute}}.step:before{content:counter(step)}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:calc(var(--spacing)*0)}.inset-32{inset:calc(var(--spacing)*32)}.inset-x-0{inset-inline:calc(var(--spacing)*0)}.inset-y-0{inset-block:calc(var(--spacing)*0)}.start-1{inset-inline-start:calc(var(--spacing)*1)}.-top-2{top:calc(var(--spacing)*-2)}.-top-4{top:calc(var(--spacing)*-4)}.-top-px{top:-1px}.top-0{top:calc(var(--spacing)*0)}.top-1{top:calc(var(--spacing)*1)}.top-1\.5{top:calc(var(--spacing)*1.5)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing)*2)}.top-2\.5{top:calc(var(--spacing)*2.5)}.top-3\.5{top:calc(var(--spacing)*3.5)}.top-4{top:calc(var(--spacing)*4)}.top-10{top:calc(var(--spacing)*10)}.top-14{top:calc(var(--spacing)*14)}.top-16{top:calc(var(--spacing)*16)}.top-20{top:calc(var(--spacing)*20)}.top-\[0\.3rem\]{top:.3rem}.top-\[0\.4rem\]{top:.4rem}.top-\[5px\]{top:5px}.top-\[50\%\]{top:50%}.top-\[calc\(var\(--header-height\)\+1px\)\]{top:calc(var(--header-height) + 1px)}.-right-1{right:calc(var(--spacing)*-1)}.-right-3{right:calc(var(--spacing)*-3)}.right-0{right:calc(var(--spacing)*0)}.right-0\.5{right:calc(var(--spacing)*.5)}.right-1{right:calc(var(--spacing)*1)}.right-2{right:calc(var(--spacing)*2)}.right-2\.5{right:calc(var(--spacing)*2.5)}.right-3{right:calc(var(--spacing)*3)}.right-4{right:calc(var(--spacing)*4)}.right-6{right:calc(var(--spacing)*6)}.right-16{right:calc(var(--spacing)*16)}.right-24{right:calc(var(--spacing)*24)}.right-\[-1\.5px\]{right:-1.5px}.right-\[-11px\]{right:-11px}.right-\[0\.3rem\]{right:.3rem}.right-\[28px\]{right:28px}.-bottom-1{bottom:calc(var(--spacing)*-1)}.-bottom-px{bottom:-1px}.bottom-0{bottom:calc(var(--spacing)*0)}.bottom-0\.5{bottom:calc(var(--spacing)*.5)}.bottom-1{bottom:calc(var(--spacing)*1)}.bottom-2{bottom:calc(var(--spacing)*2)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-16{bottom:calc(var(--spacing)*16)}.bottom-24{bottom:calc(var(--spacing)*24)}.-left-0{left:calc(var(--spacing)*0)}.-left-0\.5{left:calc(var(--spacing)*-.5)}.-left-1{left:calc(var(--spacing)*-1)}.-left-3{left:calc(var(--spacing)*-3)}.-left-5{left:calc(var(--spacing)*-5)}.-left-6{left:calc(var(--spacing)*-6)}.left-0{left:calc(var(--spacing)*0)}.left-1{left:calc(var(--spacing)*1)}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing)*2)}.left-2\.5{left:calc(var(--spacing)*2.5)}.left-3{left:calc(var(--spacing)*3)}.left-16{left:calc(var(--spacing)*16)}.left-\[-1\.5px\]{left:-1.5px}.left-\[-10\.5px\]{left:-10.5px}.left-\[50\%\]{left:50%}.isolate{isolation:isolate}.z-1{z-index:1}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-51{z-index:51}.z-60{z-index:60}.z-100{z-index:100}.z-500{z-index:500}.z-\[100\]{z-index:100}.col-span-1{grid-column:span 1/span 1}.col-start-2{grid-column-start:2}.row-span-2{grid-row:span 2/span 2}.row-start-1{grid-row-start:1}.container{width:100%}@media (min-width:1600px){.container{max-width:1600px}}@media (min-width:2000px){.container{max-width:2000px}}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.\!m-0{margin:calc(var(--spacing)*0)!important}.m-0{margin:calc(var(--spacing)*0)}.m-0\.5{margin:calc(var(--spacing)*.5)}.m-4{margin:calc(var(--spacing)*4)}.container-wrapper{width:100%;max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*2);margin-inline:auto}.container{max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media (min-width:80rem){.container{padding-inline:calc(var(--spacing)*6)}}.-mx-1{margin-inline:calc(var(--spacing)*-1)}.mx-0{margin-inline:calc(var(--spacing)*0)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-1\.5{margin-inline:calc(var(--spacing)*1.5)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-3\.5{margin-inline:calc(var(--spacing)*3.5)}.mx-auto{margin-inline:auto}.mx-px{margin-inline:1px}.\!my-0{margin-block:calc(var(--spacing)*0)!important}.my-1{margin-block:calc(var(--spacing)*1)}.my-1\.5{margin-block:calc(var(--spacing)*1.5)}.my-2{margin-block:calc(var(--spacing)*2)}.my-4{margin-block:calc(var(--spacing)*4)}.my-6{margin-block:calc(var(--spacing)*6)}.my-8{margin-block:calc(var(--spacing)*8)}.my-auto{margin-block:auto}.my-px{margin-block:1px}.-ms-5{margin-inline-start:calc(var(--spacing)*-5)}.me-1\.5{margin-inline-end:calc(var(--spacing)*1.5)}.\!mt-0{margin-top:calc(var(--spacing)*0)!important}.-mt-2\.5{margin-top:calc(var(--spacing)*-2.5)}.-mt-6{margin-top:calc(var(--spacing)*-6)}.-mt-12{margin-top:calc(var(--spacing)*-12)}.mt-0{margin-top:calc(var(--spacing)*0)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-\[0\.75em\]{margin-top:.75em}.mt-\[1\.4em\]{margin-top:1.4em}.mt-\[1\.6em\]{margin-top:1.6em}.mt-\[1em\]{margin-top:1em}.mt-auto{margin-top:auto}.-mr-3{margin-right:calc(var(--spacing)*-3)}.mr-0{margin-right:calc(var(--spacing)*0)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-1\.5{margin-right:calc(var(--spacing)*1.5)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-\[14px\]{margin-right:14px}.mr-auto{margin-right:auto}.\!mb-0{margin-bottom:calc(var(--spacing)*0)!important}.mb-0{margin-bottom:calc(var(--spacing)*0)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-2\.5{margin-bottom:calc(var(--spacing)*2.5)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.mb-12{margin-bottom:calc(var(--spacing)*12)}.mb-16{margin-bottom:calc(var(--spacing)*16)}.\!ml-6{margin-left:calc(var(--spacing)*6)!important}.-ml-2{margin-left:calc(var(--spacing)*-2)}.-ml-3{margin-left:calc(var(--spacing)*-3)}.ml-0{margin-left:calc(var(--spacing)*0)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-6{margin-left:calc(var(--spacing)*6)}.ml-auto{margin-left:auto}.ml-px{margin-left:1px}.box-border{box-sizing:border-box}.box-content{box-sizing:content-box}.line-clamp-1{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}.no-scrollbar::-webkit-scrollbar{display:none}.scrollbar-hide{-ms-overflow-style:none;scrollbar-width:none}.scrollbar-hide::-webkit-scrollbar{display:none}.block{display:block}.contents{display:contents}.flex{display:flex}.flex\!{display:flex!important}.flow-root{display:flow-root}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.table-caption{display:table-caption}.table-cell{display:table-cell}.table-row{display:table-row}.aspect-1\/2{aspect-ratio:1/2}.aspect-\[4\/2\.5\]{aspect-ratio:4/2.5}.aspect-square{aspect-ratio:1}.aspect-video{aspect-ratio:var(--aspect-video)}.\!size-3{width:calc(var(--spacing)*3)!important;height:calc(var(--spacing)*3)!important}.\!size-3\.5{width:calc(var(--spacing)*3.5)!important;height:calc(var(--spacing)*3.5)!important}.size-0{width:calc(var(--spacing)*0);height:calc(var(--spacing)*0)}.size-2{width:calc(var(--spacing)*2);height:calc(var(--spacing)*2)}.size-2\.5{width:calc(var(--spacing)*2.5);height:calc(var(--spacing)*2.5)}.size-3{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.size-3\.5{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.size-3\.5\!{width:calc(var(--spacing)*3.5)!important;height:calc(var(--spacing)*3.5)!important}.size-4{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.size-4\.5{width:calc(var(--spacing)*4.5);height:calc(var(--spacing)*4.5)}.size-5{width:calc(var(--spacing)*5);height:calc(var(--spacing)*5)}.size-6{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.size-8{width:calc(var(--spacing)*8);height:calc(var(--spacing)*8)}.size-9{width:calc(var(--spacing)*9);height:calc(var(--spacing)*9)}.size-10{width:calc(var(--spacing)*10);height:calc(var(--spacing)*10)}.size-12{width:calc(var(--spacing)*12);height:calc(var(--spacing)*12)}.size-\[14px\]{width:14px;height:14px}.size-\[22px\]{width:22px;height:22px}.size-\[28px\]{width:28px;height:28px}.size-\[130px\]{width:130px;height:130px}.size-full{width:100%;height:100%}.h-\(--container-height\){height:var(--container-height)}.h-\(--header-height\){height:var(--header-height)}.h-\(--height\){height:var(--height)}.h-\(--radix-popper-available-height\){height:var(--radix-popper-available-height)}.h-\(--top-spacing\){height:var(--top-spacing)}.h-0\.5{height:calc(var(--spacing)*.5)}.h-2{height:calc(var(--spacing)*2)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-11{height:calc(var(--spacing)*11)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-16{height:calc(var(--spacing)*16)}.h-20{height:calc(var(--spacing)*20)}.h-32{height:calc(var(--spacing)*32)}.h-\[0\.1px\]{height:.1px}.h-\[1\.5em\]{height:1.5em}.h-\[1\.45rem\]{height:1.45rem}.h-\[1px\]{height:1px}.h-\[19px\]{height:19px}.h-\[23rem\]{height:23rem}.h-\[24px\]{height:24px}.h-\[26px\]{height:26px}.h-\[28px\]{height:28px}.h-\[344px\]{height:344px}.h-\[350px\]{height:350px}.h-\[500px\]{height:500px}.h-\[520px\]{height:520px}.h-\[600px\]{height:600px}.h-\[650px\]{height:650px}.h-\[800px\]{height:800px}.h-\[7500px\]{height:7500px}.h-\[calc\(100\%-1px\)\]{height:calc(100% - 1px)}.h-\[calc\(100\%_\+_8px\)\]{height:calc(100% + 8px)}.h-\[calc\(100svh-var\(--header-height\)-var\(--footer-height\)\)\]{height:calc(100svh - var(--header-height) - var(--footer-height))}.h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.h-\[calc\(100vh-100px\)\]{height:calc(100vh - 100px)}.h-\[calc\(theme\(spacing\.7\)_-_1px\)\]{height:calc(1.75rem - 1px)}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-dvh{height:100dvh}.h-fit{height:fit-content}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.h-svh{height:100svh}.max-h-\(--radix-context-menu-content-available-height\){max-height:var(--radix-context-menu-content-available-height)}.max-h-\(--radix-dropdown-menu-content-available-height\){max-height:var(--radix-dropdown-menu-content-available-height)}.max-h-\(--radix-select-content-available-height\){max-height:var(--radix-select-content-available-height)}.max-h-14{max-height:calc(var(--spacing)*14)}.max-h-72{max-height:calc(var(--spacing)*72)}.max-h-\[50vh\]{max-height:50vh}.max-h-\[70svh\]{max-height:70svh}.max-h-\[70vh\]{max-height:70vh}.max-h-\[80vh\]{max-height:80vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[288px\]{max-height:288px}.max-h-\[300px\]{max-height:300px}.max-h-\[500px\]{max-height:500px}.max-h-\[650px\]{max-height:650px}.max-h-\[calc\(100vh-4rem\)\]{max-height:calc(100vh - 4rem)}.max-h-\[calc\(100vh-var\(--header-height\)-44px\)\]{max-height:calc(100vh - var(--header-height) - 44px)}.max-h-\[min\(50dvh\,calc\(-24px\+var\(--radix-popper-available-height\)\)\)\]{max-height:min(50dvh,calc(-24px + var(--radix-popper-available-height)))}.max-h-\[min\(70vh\,320px\)\]{max-height:min(70vh,320px)}.max-h-full{max-height:100%}.max-h-screen{max-height:100vh}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-4{min-height:calc(var(--spacing)*4)}.min-h-14{min-height:calc(var(--spacing)*14)}.min-h-\[1lh\]{min-height:1lh}.min-h-\[25px\]{min-height:25px}.min-h-\[50\%\]{min-height:50%}.min-h-\[350px\]{min-height:350px}.min-h-full\!{min-height:100%!important}.min-h-min{min-height:min-content}.min-h-svh{min-height:100svh}.w-\(--radix-popper-available-width\){width:var(--radix-popper-available-width)}.w-\(--sidebar-width\){width:var(--sidebar-width)}.w-0\.5{width:calc(var(--spacing)*.5)}.w-1{width:calc(var(--spacing)*1)}.w-1\/2{width:50%}.w-2{width:calc(var(--spacing)*2)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-3\/4{width:75%}.w-4{width:calc(var(--spacing)*4)}.w-4\.5{width:calc(var(--spacing)*4.5)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-48{width:calc(var(--spacing)*48)}.w-64{width:calc(var(--spacing)*64)}.w-72{width:calc(var(--spacing)*72)}.w-80{width:calc(var(--spacing)*80)}.w-\[1px\]{width:1px}.w-\[80px\]{width:80px}.w-\[100px\]{width:100px}.w-\[120px\]{width:120px}.w-\[180px\]{width:180px}.w-\[200px\]{width:200px}.w-\[230px\]{width:230px}.w-\[240px\]{width:240px}.w-\[280px\]{width:280px}.w-\[300px\]{width:300px}.w-\[330px\]{width:330px}.w-\[380px\]{width:380px}.w-\[700px\]{width:700px}.w-\[896px\]{width:896px}.w-\[970px\]{width:970px}.w-\[calc\(100\%-1rem\)\]{width:calc(100% - 1rem)}.w-\[min\(100\%\,600px\)\]{width:min(100%,600px)}.w-auto{width:auto}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.w-screen{width:100vw}.max-w-\(--skeleton-width\){max-width:var(--skeleton-width)}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-\[80vw\]{max-width:80vw}.max-w-\[calc\(100\%-2rem\)\]{max-width:calc(100% - 2rem)}.max-w-\[calc\(100vw-24px\)\]{max-width:calc(100vw - 24px)}.max-w-full{max-width:100%}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-5{min-width:calc(var(--spacing)*5)}.min-w-8{min-width:calc(var(--spacing)*8)}.min-w-9{min-width:calc(var(--spacing)*9)}.min-w-10{min-width:calc(var(--spacing)*10)}.min-w-32{min-width:calc(var(--spacing)*32)}.min-w-\[8px\]{min-width:8px}.min-w-\[8rem\]{min-width:8rem}.min-w-\[12rem\]{min-width:12rem}.min-w-\[92px\]{min-width:92px}.min-w-\[125px\]{min-width:125px}.min-w-\[130px\]{min-width:130px}.min-w-\[180px\]{min-width:180px}.min-w-\[220px\]{min-width:220px}.min-w-\[225px\]{min-width:225px}.min-w-\[450px\]{min-width:450px}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.min-w-full{min-width:100%}.flex-1{flex:1}.flex-none{flex:none}.shrink-0{flex-shrink:0}.flex-grow-1,.grow{flex-grow:1}.table-fixed{table-layout:fixed}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.origin-\(--radix-context-menu-content-transform-origin\){transform-origin:var(--radix-context-menu-content-transform-origin)}.origin-\(--radix-dropdown-menu-content-transform-origin\){transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.origin-\(--radix-hover-card-content-transform-origin\){transform-origin:var(--radix-hover-card-content-transform-origin)}.origin-\(--radix-menubar-content-transform-origin\){transform-origin:var(--radix-menubar-content-transform-origin)}.origin-\(--radix-popover-content-transform-origin\){transform-origin:var(--radix-popover-content-transform-origin)}.origin-\(--radix-select-content-transform-origin\){transform-origin:var(--radix-select-content-transform-origin)}.origin-\(--radix-tooltip-content-transform-origin\){transform-origin:var(--radix-tooltip-content-transform-origin)}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-1\/4{--tw-translate-x:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-x-px{--tw-translate-x:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-12{--tw-translate-x:calc(var(--spacing)*12);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-\[-50\%\]{--tw-translate-x:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-px{--tw-translate-x:1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/4{--tw-translate-y:calc(calc(1/4*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-full{--tw-translate-y:-100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-0\.5{--tw-translate-y:calc(var(--spacing)*.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[-50\%\]{--tw-translate-y:-50%;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[2px\]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-y-\[calc\(-50\%_-_2px\)\]{--tw-translate-y:calc(-50% - 2px);translate:var(--tw-translate-x)var(--tw-translate-y)}.-rotate-45{rotate:-45deg}.rotate-0{rotate:none}.rotate-12{rotate:12deg}.rotate-45{rotate:45deg}.rotate-90{rotate:90deg}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-in{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.cursor-text{cursor:text}.touch-manipulation{touch-action:manipulation}.touch-none{touch-action:none}.resize{resize:both}.resize-none{resize:none}.scroll-m-16{scroll-margin:calc(var(--spacing)*16)}.scroll-m-20{scroll-margin:calc(var(--spacing)*20)}.scroll-m-28{scroll-margin:calc(var(--spacing)*28)}.scroll-my-1{scroll-margin-block:calc(var(--spacing)*1)}.scroll-mt-20{scroll-margin-top:calc(var(--spacing)*20)}.scroll-mt-24{scroll-margin-top:calc(var(--spacing)*24)}.scroll-py-1{scroll-padding-block:calc(var(--spacing)*1)}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.list-none{list-style-type:none}.list-none\!{list-style-type:none!important}.appearance-none{appearance:none}.grid-flow-row{grid-auto-flow:row}.auto-rows-max{grid-auto-rows:max-content}.auto-rows-min{grid-auto-rows:min-content}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-\[0_1fr\]{grid-template-columns:0 1fr}.grid-cols-\[repeat\(10\,1fr\)\]{grid-template-columns:repeat(10,1fr)}.grid-rows-\[auto_auto\]{grid-template-rows:auto auto}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-nowrap{flex-wrap:nowrap}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-evenly{justify-content:space-evenly}.justify-start{justify-content:flex-start}.justify-items-start{justify-items:start}.gap-0{gap:calc(var(--spacing)*0)}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-2\.5{gap:calc(var(--spacing)*2.5)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}.gap-10{gap:calc(var(--spacing)*10)}.gap-12{gap:calc(var(--spacing)*12)}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*0)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*0)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-10>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*10)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*10)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-16>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*16)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*16)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-1{column-gap:calc(var(--spacing)*1)}:where(.space-x-0\.5>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*.5)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*.5)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-1>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*1)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.gap-y-0\.5{row-gap:calc(var(--spacing)*.5)}.self-start{align-self:flex-start}.justify-self-end{justify-self:flex-end}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.overscroll-none{overscroll-behavior:none}.rounded{border-radius:.25rem}.rounded-\[0\.5rem\]{border-radius:.5rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[4px\]{border-radius:4px}.rounded-\[6px\]{border-radius:6px}.rounded-\[50\%\]{border-radius:50%}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.rounded-xs{border-radius:var(--radius-xs)}.rounded-t-lg{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.rounded-r-md{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.rounded-r-none{border-top-right-radius:0;border-bottom-right-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-\[1\.5px\]{border-style:var(--tw-border-style);border-width:1.5px}.border-s-2{border-inline-start-style:var(--tw-border-style);border-inline-start-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-0{border-left-style:var(--tw-border-style);border-left-width:0}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-solid{--tw-border-style:solid;border-style:solid}.border-\[\#ddd\]{border-color:#ddd}.border-blue-500{border-color:var(--color-blue-500)}.border-border,.border-border\/40{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/40{border-color:color-mix(in oklab,var(--border)40%,transparent)}}.border-border\/50{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/50{border-color:color-mix(in oklab,var(--border)50%,transparent)}}.border-border\/60{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/60{border-color:color-mix(in oklab,var(--border)60%,transparent)}}.border-current{border-color:currentColor}.border-gray-200{border-color:var(--color-gray-200)}.border-input{border-color:var(--input)}.border-muted{border-color:var(--muted)}.border-muted-foreground{border-color:var(--muted-foreground)}.border-primary{border-color:var(--primary)}.border-sidebar-border{border-color:var(--sidebar-border)}.border-stone-700{border-color:var(--color-stone-700)}.border-transparent{border-color:#0000}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.border-s-blue-500\/50{border-inline-start-color:#3080ff80}@supports (color:color-mix(in lab, red, red)){.border-s-blue-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-blue-500)50%,transparent)}}.border-s-green-500\/50{border-inline-start-color:#00c75880}@supports (color:color-mix(in lab, red, red)){.border-s-green-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-green-500)50%,transparent)}}.border-s-orange-500\/50{border-inline-start-color:#fe6e0080}@supports (color:color-mix(in lab, red, red)){.border-s-orange-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-orange-500)50%,transparent)}}.border-s-red-500\/50{border-inline-start-color:#fb2c3680}@supports (color:color-mix(in lab, red, red)){.border-s-red-500\/50{border-inline-start-color:color-mix(in oklab,var(--color-red-500)50%,transparent)}}.border-t-border{border-top-color:var(--border)}.border-t-transparent{border-top-color:#0000}.border-b-border{border-bottom-color:var(--border)}.border-b-brand\/\[\.24\]{border-bottom-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.border-b-brand\/\[\.24\]{border-bottom-color:color-mix(in oklab,var(--brand)24%,transparent)}}.border-b-gray-300{border-bottom-color:var(--color-gray-300)}.border-b-highlight,.border-b-highlight\/35{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/35{border-bottom-color:color-mix(in oklab,var(--highlight)35%,transparent)}}.border-b-highlight\/\[\.7\]{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/\[\.7\]{border-bottom-color:color-mix(in oklab,var(--highlight)70%,transparent)}}.border-b-highlight\/\[\.36\]{border-bottom-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.border-b-highlight\/\[\.36\]{border-bottom-color:color-mix(in oklab,var(--highlight)36%,transparent)}}.border-b-purple-100{border-bottom-color:var(--color-purple-100)}.border-b-transparent{border-bottom-color:#0000}.border-l-transparent{border-left-color:#0000}.bg-\(--cellBackground\){background-color:var(--cellBackground)}.bg-\(--color-1\){background-color:var(--color-1)}.bg-\(--color-2\){background-color:var(--color-2)}.bg-\(--color-3\){background-color:var(--color-3)}.bg-\(--color-4\){background-color:var(--color-4)}.bg-\[\#adfa1d\]{background-color:#adfa1d}.bg-\[\#eee\]{background-color:#eee}.bg-\[rgba\(0\,0\,0\,0\.5\)\]{background-color:#00000080}.bg-accent{background-color:var(--accent)}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background,.bg-background\/90{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/90{background-color:color-mix(in oklab,var(--background)90%,transparent)}}.bg-background\/95{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.bg-background\/95{background-color:color-mix(in oklab,var(--background)95%,transparent)}}.bg-black{background-color:var(--color-black)}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-200{background-color:var(--color-blue-200)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-border{background-color:var(--border)}.bg-brand,.bg-brand\/25{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/25{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.bg-brand\/50{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/50{background-color:color-mix(in oklab,var(--brand)50%,transparent)}}.bg-brand\/\[\.08\]{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/\[\.08\]{background-color:color-mix(in oklab,var(--brand)8%,transparent)}}.bg-brand\/\[\.13\]{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.bg-brand\/\[\.13\]{background-color:color-mix(in oklab,var(--brand)13%,transparent)}}.bg-card{background-color:var(--card)}.bg-current{background-color:currentColor}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-destructive{background-color:var(--destructive)}.bg-emerald-100{background-color:var(--color-emerald-100)}.bg-emerald-200\/80{background-color:#a4f4cfcc}@supports (color:color-mix(in lab, red, red)){.bg-emerald-200\/80{background-color:color-mix(in oklab,var(--color-emerald-200)80%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300\/25{background-color:#d1d5dc40}@supports (color:color-mix(in lab, red, red)){.bg-gray-300\/25{background-color:color-mix(in oklab,var(--color-gray-300)25%,transparent)}}.bg-gray-800{background-color:var(--color-gray-800)}.bg-green-50{background-color:var(--color-green-50)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-200{background-color:var(--color-green-200)}.bg-highlight,.bg-highlight\/15{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/15{background-color:color-mix(in oklab,var(--highlight)15%,transparent)}}.bg-highlight\/25{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/25{background-color:color-mix(in oklab,var(--highlight)25%,transparent)}}.bg-highlight\/30{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/30{background-color:color-mix(in oklab,var(--highlight)30%,transparent)}}.bg-highlight\/45{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/45{background-color:color-mix(in oklab,var(--highlight)45%,transparent)}}.bg-highlight\/\[\.13\]{background-color:var(--highlight)}@supports (color:color-mix(in lab, red, red)){.bg-highlight\/\[\.13\]{background-color:color-mix(in oklab,var(--highlight)13%,transparent)}}.bg-inherit{background-color:inherit}.bg-muted,.bg-muted\/30{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/30{background-color:color-mix(in oklab,var(--muted)30%,transparent)}}.bg-muted\/50{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/50{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.bg-muted\/60{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/60{background-color:color-mix(in oklab,var(--muted)60%,transparent)}}.bg-neutral-50{background-color:var(--color-neutral-50)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-popover,.bg-popover\/90{background-color:var(--popover)}@supports (color:color-mix(in lab, red, red)){.bg-popover\/90{background-color:color-mix(in oklab,var(--popover)90%,transparent)}}.bg-primary,.bg-primary\/10{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/10{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.bg-primary\/40{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/40{background-color:color-mix(in oklab,var(--primary)40%,transparent)}}.bg-purple-50{background-color:var(--color-purple-50)}.bg-purple-100{background-color:var(--color-purple-100)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-red-200{background-color:var(--color-red-200)}.bg-red-200\/80{background-color:#ffcacacc}@supports (color:color-mix(in lab, red, red)){.bg-red-200\/80{background-color:color-mix(in oklab,var(--color-red-200)80%,transparent)}}.bg-ring{background-color:var(--ring)}.bg-secondary{background-color:var(--secondary)}.bg-sidebar{background-color:var(--sidebar)}.bg-sidebar-border{background-color:var(--sidebar-border)}.bg-slate-200\/50{background-color:#e2e8f080}@supports (color:color-mix(in lab, red, red)){.bg-slate-200\/50{background-color:color-mix(in oklab,var(--color-slate-200)50%,transparent)}}.bg-surface{background-color:var(--surface)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-yellow-100{background-color:var(--color-yellow-100)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.bg-zinc-950\!{background-color:var(--color-zinc-950)!important}.bg-linear-to-b{--tw-gradient-position:to bottom}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-b{--tw-gradient-position:to bottom in oklab}}.bg-linear-to-b{background-image:linear-gradient(var(--tw-gradient-stops))}.bg-linear-to-r{--tw-gradient-position:to right}@supports (background-image:linear-gradient(in lab, red, red)){.bg-linear-to-r{--tw-gradient-position:to right in oklab}}.bg-linear-to-r{background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-b{--tw-gradient-position:to bottom in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-t{--tw-gradient-position:to top in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-\[\#6EB6F2\]{--tw-gradient-from:#6eb6f2;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-background{--tw-gradient-from:var(--background);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-background\/10{--tw-gradient-from:var(--background)}@supports (color:color-mix(in lab, red, red)){.from-background\/10{--tw-gradient-from:color-mix(in oklab,var(--background)10%,transparent)}}.from-background\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-gray-900\/30{--tw-gradient-from:#1018284d}@supports (color:color-mix(in lab, red, red)){.from-gray-900\/30{--tw-gradient-from:color-mix(in oklab,var(--color-gray-900)30%,transparent)}}.from-gray-900\/30{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-\[\#a855f7\]{--tw-gradient-via:#a855f7;--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.via-background\/60{--tw-gradient-via:var(--background)}@supports (color:color-mix(in lab, red, red)){.via-background\/60{--tw-gradient-via:color-mix(in oklab,var(--background)60%,transparent)}}.via-background\/60{--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-\[\#eab308\]{--tw-gradient-to:#eab308;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-background{--tw-gradient-to:var(--background);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-transparent{--tw-gradient-to:transparent;--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-90\%{--tw-gradient-to-position:90%}.bg-cover{background-size:cover}.bg-clip-content{background-clip:content-box}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.bg-center{background-position:50%}.fill-blue-500{fill:var(--color-blue-500)}.fill-current{fill:currentColor}.fill-green-500{fill:var(--color-green-500)}.fill-none{fill:none}.fill-orange-500{fill:var(--color-orange-500)}.fill-primary{fill:var(--primary)}.fill-red-500{fill:var(--color-red-500)}.stroke-slate-300{stroke:var(--color-slate-300)}.stroke-\[3px\]{stroke-width:3px}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.\!p-0{padding:calc(var(--spacing)*0)!important}.p-0{padding:calc(var(--spacing)*0)}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.p-11{padding:calc(var(--spacing)*11)}.p-20{padding:calc(var(--spacing)*20)}.p-\[2px\]{padding:2px}.p-\[3px\]{padding:3px}.p-px{padding:1px}.\!px-0\.5{padding-inline:calc(var(--spacing)*.5)!important}.\!px-1{padding-inline:calc(var(--spacing)*1)!important}.\!px-1\.5{padding-inline:calc(var(--spacing)*1.5)!important}.\!px-2{padding-inline:calc(var(--spacing)*2)!important}.px-0{padding-inline:calc(var(--spacing)*0)}.px-0\.5{padding-inline:calc(var(--spacing)*.5)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.px-10{padding-inline:calc(var(--spacing)*10)}.px-16{padding-inline:calc(var(--spacing)*16)}.px-\[0\.3em\]{padding-inline:.3em}.px-\[0\.3rem\]{padding-inline:.3rem}.px-\[calc\(--spacing\(1\)-2px\)\]{padding-inline:calc(calc(var(--spacing)*1) - 2px)}.px-px{padding-inline:1px}.py-0{padding-block:calc(var(--spacing)*0)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-4\!{padding-block:calc(var(--spacing)*4)!important}.py-5{padding-block:calc(var(--spacing)*5)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-\[--spacing\(1\)\]{padding-block:calc(var(--spacing)*1)}.py-\[0\.2em\]{padding-block:.2em}.py-\[0\.2rem\]{padding-block:.2rem}.py-\[1\.5px\]{padding-block:1.5px}.py-\[3px\]{padding-block:3px}.ps-6{padding-inline-start:calc(var(--spacing)*6)}.pt-0{padding-top:calc(var(--spacing)*0)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-1\.5{padding-top:calc(var(--spacing)*1.5)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-2\.5{padding-top:calc(var(--spacing)*2.5)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pt-24{padding-top:calc(var(--spacing)*24)}.pt-\[0\.275em\]{padding-top:.275em}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-8{padding-right:calc(var(--spacing)*8)}.pr-9{padding-right:calc(var(--spacing)*9)}.pr-10{padding-right:calc(var(--spacing)*10)}.pr-\[14px\]{padding-right:14px}.pr-px{padding-right:1px}.pb-0{padding-bottom:calc(var(--spacing)*0)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-1\.5{padding-bottom:calc(var(--spacing)*1.5)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-6{padding-bottom:calc(var(--spacing)*6)}.pb-8{padding-bottom:calc(var(--spacing)*8)}.pb-48{padding-bottom:calc(var(--spacing)*48)}.pb-72{padding-bottom:calc(var(--spacing)*72)}.pb-\[20vh\]{padding-bottom:20vh}.pb-\[51\.25\%\]{padding-bottom:51.25%}.pb-\[56\.25\%\]{padding-bottom:56.25%}.pb-\[56\.0417\%\]{padding-bottom:56.0417%}.pb-\[75\%\]{padding-bottom:75%}.pb-px{padding-bottom:1px}.pl-\(--index\){padding-left:var(--index)}.pl-0{padding-left:calc(var(--spacing)*0)}.pl-0\.5{padding-left:calc(var(--spacing)*.5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-2\.5{padding-left:calc(var(--spacing)*2.5)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-6{padding-left:calc(var(--spacing)*6)}.pl-8{padding-left:calc(var(--spacing)*8)}.pl-\[26px\]{padding-left:26px}.pl-\[32px\]{padding-left:32px}.pl-\[50px\]{padding-left:50px}.text-center{text-align:center}.text-end{text-align:end}.text-justify{text-align:justify}.text-left{text-align:left}.text-start{text-align:start}.align-baseline{vertical-align:baseline}.align-middle{vertical-align:middle}.align-text-bottom{vertical-align:text-bottom}.font-\[inherit\]{font-family:inherit}.font-heading{font-family:"var(--font-heading)","ui-sans-serif",-apple-system,BlinkMacSystemFont,Segoe UI Variable Display,Segoe UI,Helvetica,Apple Color Emoji,Arial,"sans-serif",Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.font-mono{font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-sans{font-family:"var(--font-sans)","ui-sans-serif",-apple-system,BlinkMacSystemFont,Segoe UI Variable Display,Segoe UI,Helvetica,Apple Color Emoji,Arial,"sans-serif",Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.text-\[1\.05rem\]{font-size:1.05rem}.text-\[10px\]{font-size:10px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.text-\[40px\]{font-size:40px}.text-\[max\(87\.5\%\,\.875rem\)\]{font-size:max(87.5%,.875rem)}.leading-7{--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}.leading-\[1\.5\]{--tw-leading:1.5;line-height:1.5}.leading-\[10px\]{--tw-leading:10px;line-height:10px}.leading-\[normal\]{--tw-leading:normal;line-height:normal}.leading-loose{--tw-leading:var(--leading-loose);line-height:var(--leading-loose)}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-light{--tw-font-weight:var(--font-weight-light);font-weight:var(--font-weight-light)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-tighter{--tw-tracking:var(--tracking-tighter);letter-spacing:var(--tracking-tighter)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.text-balance{text-wrap:balance}.text-nowrap{text-wrap:nowrap}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.whitespace-break-spaces{white-space:break-spaces}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#000000\]{color:#000}.text-\[\#aaa\]{color:#aaa}.text-accent-foreground{color:var(--accent-foreground)}.text-background{color:var(--background)}.text-black{color:var(--color-black)}.text-blue-600{color:var(--color-blue-600)}.text-brand\/80{color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.text-brand\/80{color:color-mix(in oklab,var(--brand)80%,transparent)}}.text-card-foreground{color:var(--card-foreground)}.text-current{color:currentColor}.text-cyan-700{color:var(--color-cyan-700)}.text-destructive{color:var(--destructive)}.text-emerald-700{color:var(--color-emerald-700)}.text-foreground,.text-foreground\/80{color:var(--foreground)}@supports (color:color-mix(in lab, red, red)){.text-foreground\/80{color:color-mix(in oklab,var(--foreground)80%,transparent)}}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-green-700{color:var(--color-green-700)}.text-green-800{color:var(--color-green-800)}.text-inherit{color:inherit}.text-muted-foreground,.text-muted-foreground\/70{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/70{color:color-mix(in oklab,var(--muted-foreground)70%,transparent)}}.text-muted-foreground\/80{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.text-muted-foreground\/80{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.text-neutral-600{color:var(--color-neutral-600)}.text-neutral-800{color:var(--color-neutral-800)}.text-orange-500{color:var(--color-orange-500)}.text-orange-700{color:var(--color-orange-700)}.text-pink-700{color:var(--color-pink-700)}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-purple-600{color:var(--color-purple-600)}.text-purple-700{color:var(--color-purple-700)}.text-purple-800{color:var(--color-purple-800)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-secondary-foreground{color:var(--secondary-foreground)}.text-sidebar-foreground,.text-sidebar-foreground\/70{color:var(--sidebar-foreground)}@supports (color:color-mix(in lab, red, red)){.text-sidebar-foreground\/70{color:color-mix(in oklab,var(--sidebar-foreground)70%,transparent)}}.text-slate-50{color:var(--color-slate-50)}.text-slate-500{color:var(--color-slate-500)}.text-stone-400{color:var(--color-stone-400)}.text-surface-foreground{color:var(--surface-foreground)}.text-transparent{color:#0000}.text-white{color:var(--color-white)}.text-zinc-50{color:var(--color-zinc-50)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-700{color:var(--color-zinc-700)}.capitalize{text-transform:capitalize}.lowercase{text-transform:lowercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,)var(--tw-slashed-zero,)var(--tw-numeric-figure,)var(--tw-numeric-spacing,)var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.no-underline{text-decoration-line:none}.underline{text-decoration-line:underline}.decoration-primary{-webkit-text-decoration-color:var(--primary);-webkit-text-decoration-color:var(--primary);text-decoration-color:var(--primary)}.decoration-\[0\.5px\]{text-decoration-thickness:.5px}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.caret-primary{caret-color:var(--primary)}.accent-foreground{accent-color:var(--foreground)}.opacity-0{opacity:0}.opacity-10{opacity:.1}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-100{opacity:1}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[0_0_0_1px_hsl\(var\(--sidebar-border\)\)\]{--tw-shadow:0 0 0 1px var(--tw-shadow-color,hsl(var(--sidebar-border)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-none{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring,.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(248\,_249\,_250\)_0px_1px_5px_0px_inset\,_rgb\(193\,_200\,_205\)_0px_0px_0px_0\.5px\,_rgb\(193\,_200\,_205\)_0px_2px_1px_-1px\,_rgb\(193\,_200\,_205\)_0px_1px_0px_0px\]{--tw-shadow-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(248\,_249\,_250\)_0px_1px_5px_0px_inset\,_rgb\(193\,_200\,_205\)_0px_0px_0px_0\.5px\,_rgb\(193\,_200\,_205\)_0px_2px_1px_-1px\,_rgb\(193\,_200\,_205\)_0px_1px_0px_0px\]{--tw-shadow-color:color-mix(in oklab,#ffffff1a 0px .5px 0px 0px inset,#f8f9fa 0px 1px 5px 0px inset,#c1c8cd 0px 0px 0px .5px,#c1c8cd 0px 2px 1px -1px,#c1c8cd 0px 1px 0px 0px var(--tw-shadow-alpha),transparent)}}.ring-black\/5{--tw-ring-color:#0000000d}@supports (color:color-mix(in lab, red, red)){.ring-black\/5{--tw-ring-color:color-mix(in oklab,var(--color-black)5%,transparent)}}.ring-ring{--tw-ring-color:var(--ring)}.ring-sidebar-ring{--tw-ring-color:var(--sidebar-ring)}.ring-offset-0{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-2{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.ring-offset-background{--tw-ring-offset-color:var(--background)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.invert-1{--tw-invert:invert(1%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.\!filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)!important}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-xs{--tw-backdrop-blur:blur(var(--blur-xs));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,visibility,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[left\,right\,width\]{transition-property:left,right,width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[margin\,opacity\]{transition-property:margin,opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\,height\,padding\]{transition-property:width,height,padding;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[width\]{transition-property:width;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-none{transition-property:none}.duration-75{--tw-duration:75ms;transition-duration:75ms}.duration-100{--tw-duration:.1s;transition-duration:.1s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.duration-1000{--tw-duration:1s;transition-duration:1s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-linear{--tw-ease:linear;transition-timing-function:linear}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.\[contain\:content\]{contain:content}.fade-in-0{--tw-enter-opacity:0}.outline-none{--tw-outline-style:none;outline-style:none}.select-auto{-webkit-user-select:auto;user-select:auto}.select-none{-webkit-user-select:none;user-select:none}.select-text{-webkit-user-select:text;user-select:text}.zoom-in-95{--tw-enter-scale:.95}.\[--footer-height\:calc\(var\(--spacing\)\*14\)\]{--footer-height:calc(var(--spacing)*14)}.\[--header-height\:calc\(var\(--spacing\)\*14\)\]{--header-height:calc(var(--spacing)*14)}.\[--sidebar-width\:220px\]{--sidebar-width:220px}.\[--top-spacing\:0\]{--top-spacing:0}.\[counter-increment\:step\]{counter-increment:step}.\[counter-reset\:step\]{counter-reset:step}.\[tab-size\:2\]{tab-size:2}.fade-in{--tw-enter-opacity:0}.ring-inset{--tw-ring-inset:inset}.running{animation-play-state:running}:is(.\*\:m-0>*){margin:calc(var(--spacing)*0)}:is(.\*\:shrink-0>*){flex-shrink:0}:is(.\*\*\:my-0 *){margin-block:calc(var(--spacing)*0)}:is(.\*\*\:leading-\[calc\(1\.25\/\.875\)\] *){--tw-leading:calc(1.25/.875);line-height:1.42857}:is(.\*\*\:leading-normal *){--tw-leading:var(--leading-normal);line-height:var(--leading-normal)}.not-first\:mt-4:not(:first-child){margin-top:calc(var(--spacing)*4)}.not-first\:mt-6:not(:first-child){margin-top:calc(var(--spacing)*6)}.not-first\:mt-12:not(:first-child){margin-top:calc(var(--spacing)*12)}.not-last\:border-b:not(:last-child){border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.group-first\/column\:-left-1:is(:where(.group\/column):first-child *){left:calc(var(--spacing)*-1)}.group-first\/column\:pl-0:is(:where(.group\/column):first-child *){padding-left:calc(var(--spacing)*0)}.group-last\/column\:-right-1:is(:where(.group\/column):last-child *){right:calc(var(--spacing)*-1)}.group-last\/column\:pr-0:is(:where(.group\/column):last-child *){padding-right:calc(var(--spacing)*0)}.group-last\/toolbar-group\:hidden\!:is(:where(.group\/toolbar-group):last-child *){display:none!important}.group-focus-within\:pointer-events-none:is(:where(.group):focus-within *){pointer-events:none}.group-focus-within\:top-0:is(:where(.group):focus-within *){top:calc(var(--spacing)*0)}.group-focus-within\:cursor-default:is(:where(.group):focus-within *){cursor:default}.group-focus-within\:text-xs:is(:where(.group):focus-within *){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.group-focus-within\:font-medium:is(:where(.group):focus-within *){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.group-focus-within\:text-foreground:is(:where(.group):focus-within *){color:var(--foreground)}.group-focus-within\/menu-item\:opacity-100:is(:where(.group\/menu-item):focus-within *){opacity:1}@media (hover:hover){.group-hover\:translate-x-\[-135px\]:is(:where(.group):hover *){--tw-translate-x:-135px;translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:translate-x-\[-181px\]:is(:where(.group):hover *){--tw-translate-x:-181px;translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:rotate-0:is(:where(.group):hover *){rotate:none}.group-hover\:rotate-45:is(:where(.group):hover *){rotate:45deg}.group-hover\:text-\[\#e3b341\]:is(:where(.group):hover *){color:#e3b341}.group-hover\:no-underline:is(:where(.group):hover *){text-decoration-line:none}.group-hover\:underline:is(:where(.group):hover *){text-decoration-line:underline}.group-hover\:opacity-100:is(:where(.group):hover *),.group-hover\/column\:opacity-100:is(:where(.group\/column):hover *),.group-hover\/container\:opacity-100:is(:where(.group\/container):hover *),.group-hover\/menu-item\:opacity-100:is(:where(.group\/menu-item):hover *),.group-hover\/row\:opacity-100:is(:where(.group\/row):hover *){opacity:1}}.group-has-disabled\:opacity-50:is(:where(.group):has(:disabled) *){opacity:.5}.group-has-data-\[resizing\=\"true\"\]\/row\:opacity-0:is(:where(.group\/row):has([data-resizing=true]) *),.group-has-data-\[resizing\=\\\"true\\\"\]\/row\:opacity-0:is(:where(.group\/row):has([data-resizing=\"true\"]) *){opacity:0}.group-has-data-\[sidebar\=menu-action\]\/menu-item\:pr-8:is(:where(.group\/menu-item):has([data-sidebar=menu-action]) *){padding-right:calc(var(--spacing)*8)}.group-has-\[\[data-col\=\"0\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="0"]:hover) *),.group-has-\[\[data-col\=\"0\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="0"][data-resizing=true]) *),.group-has-\[\[data-col\=\"1\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="1"]:hover) *),.group-has-\[\[data-col\=\"1\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="1"][data-resizing=true]) *),.group-has-\[\[data-col\=\"10\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="10"]:hover) *),.group-has-\[\[data-col\=\"10\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="10"][data-resizing=true]) *),.group-has-\[\[data-col\=\"2\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="2"]:hover) *),.group-has-\[\[data-col\=\"2\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="2"][data-resizing=true]) *),.group-has-\[\[data-col\=\"3\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="3"]:hover) *),.group-has-\[\[data-col\=\"3\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="3"][data-resizing=true]) *),.group-has-\[\[data-col\=\"4\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="4"]:hover) *),.group-has-\[\[data-col\=\"4\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="4"][data-resizing=true]) *),.group-has-\[\[data-col\=\"5\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="5"]:hover) *),.group-has-\[\[data-col\=\"5\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="5"][data-resizing=true]) *),.group-has-\[\[data-col\=\"6\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="6"]:hover) *),.group-has-\[\[data-col\=\"6\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="6"][data-resizing=true]) *),.group-has-\[\[data-col\=\"7\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="7"]:hover) *),.group-has-\[\[data-col\=\"7\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="7"][data-resizing=true]) *),.group-has-\[\[data-col\=\"8\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="8"]:hover) *),.group-has-\[\[data-col\=\"8\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="8"][data-resizing=true]) *),.group-has-\[\[data-col\=\"9\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col="9"]:hover) *),.group-has-\[\[data-col\=\"9\"\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-col="9"][data-resizing=true]) *),.group-has-\[\[data-col\=\\\"0\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"0\"]:hover) *),.group-has-\[\[data-col\=\\\"0\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"0\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"10\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"10\"]:hover) *),.group-has-\[\[data-col\=\\\"10\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"10\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"1\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"1\"]:hover) *),.group-has-\[\[data-col\=\\\"1\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"1\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"2\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"2\"]:hover) *),.group-has-\[\[data-col\=\\\"2\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"2\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"3\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"3\"]:hover) *),.group-has-\[\[data-col\=\\\"3\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"3\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"4\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"4\"]:hover) *),.group-has-\[\[data-col\=\\\"4\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"4\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"5\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"5\"]:hover) *),.group-has-\[\[data-col\=\\\"5\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"5\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"6\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"6\"]:hover) *),.group-has-\[\[data-col\=\\\"6\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"6\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"7\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"7\"]:hover) *),.group-has-\[\[data-col\=\\\"7\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"7\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"8\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"8\"]:hover) *),.group-has-\[\[data-col\=\\\"8\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"8\"][data-resizing=\"true\"]) *),.group-has-\[\[data-col\=\\\"9\\\"\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-col=\"9\"]:hover) *),.group-has-\[\[data-col\=\\\"9\\\"\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-col=\"9\"][data-resizing=\"true\"]) *),.group-has-\[\[data-resizer-left\]\:hover\]\/table\:block:is(:where(.group\/table):has([data-resizer-left]:hover) *),.group-has-\[\[data-resizer-left\]\[data-resizing\=\"true\"\]\]\/table\:block:is(:where(.group\/table):has([data-resizer-left][data-resizing=true]) *),.group-has-\[\[data-resizer-left\]\[data-resizing\=\\\"true\\\"\]\]\/table\:block:is(:where(.group\/table):has([data-resizer-left][data-resizing=\"true\"]) *){display:block}.group-data-list\:my-2:is(:where(.group)[data-list] *){margin-block:calc(var(--spacing)*2)}.group-data-\[collapsible\=icon\]\:-mt-8:is(:where(.group)[data-collapsible=icon] *){margin-top:calc(var(--spacing)*-8)}.group-data-\[collapsible\=icon\]\:hidden:is(:where(.group)[data-collapsible=icon] *){display:none}.group-data-\[collapsible\=icon\]\:size-8\!:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--spacing)*8)!important;height:calc(var(--spacing)*8)!important}.group-data-\[collapsible\=icon\]\:w-\(--sidebar-width-icon\):is(:where(.group)[data-collapsible=icon] *){width:var(--sidebar-width-icon)}.group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\)\]:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(var(--spacing)*4)))}.group-data-\[collapsible\=icon\]\:w-\[calc\(var\(--sidebar-width-icon\)\+\(--spacing\(4\)\)\+2px\)\]:is(:where(.group)[data-collapsible=icon] *){width:calc(var(--sidebar-width-icon) + (calc(var(--spacing)*4)) + 2px)}.group-data-\[collapsible\=icon\]\:overflow-hidden:is(:where(.group)[data-collapsible=icon] *){overflow:hidden}.group-data-\[collapsible\=icon\]\:p-0\!:is(:where(.group)[data-collapsible=icon] *){padding:calc(var(--spacing)*0)!important}.group-data-\[collapsible\=icon\]\:p-2\!:is(:where(.group)[data-collapsible=icon] *){padding:calc(var(--spacing)*2)!important}.group-data-\[collapsible\=icon\]\:opacity-0:is(:where(.group)[data-collapsible=icon] *){opacity:0}.group-data-\[collapsible\=offcanvas\]\:right-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *){right:calc(var(--sidebar-width)*-1)}.group-data-\[collapsible\=offcanvas\]\:left-\[calc\(var\(--sidebar-width\)\*-1\)\]:is(:where(.group)[data-collapsible=offcanvas] *){left:calc(var(--sidebar-width)*-1)}.group-data-\[collapsible\=offcanvas\]\:w-0:is(:where(.group)[data-collapsible=offcanvas] *){width:calc(var(--spacing)*0)}.group-data-\[collapsible\=offcanvas\]\:translate-x-0:is(:where(.group)[data-collapsible=offcanvas] *){--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.group-data-\[disabled\=true\]\:pointer-events-none:is(:where(.group)[data-disabled=true] *){pointer-events:none}.group-data-\[disabled\=true\]\:opacity-50:is(:where(.group)[data-disabled=true] *){opacity:.5}.group-data-\[empty\=true\]\/subheading\:hidden:is(:where(.group\/subheading)[data-empty=true] *){display:none}.group-data-\[pressed\=true\]\:bg-accent:is(:where(.group)[data-pressed=true] *){background-color:var(--accent)}.group-data-\[pressed\=true\]\:text-accent-foreground:is(:where(.group)[data-pressed=true] *){color:var(--accent-foreground)}.group-data-\[side\=left\]\:-right-4:is(:where(.group)[data-side=left] *){right:calc(var(--spacing)*-4)}.group-data-\[side\=left\]\:border-r:is(:where(.group)[data-side=left] *){border-right-style:var(--tw-border-style);border-right-width:1px}.group-data-\[side\=right\]\:left-0:is(:where(.group)[data-side=right] *){left:calc(var(--spacing)*0)}.group-data-\[side\=right\]\:rotate-180:is(:where(.group)[data-side=right] *){rotate:180deg}.group-data-\[side\=right\]\:border-l:is(:where(.group)[data-side=right] *){border-left-style:var(--tw-border-style);border-left-width:1px}.group-data-\[variant\=floating\]\:rounded-lg:is(:where(.group)[data-variant=floating] *){border-radius:var(--radius)}.group-data-\[variant\=floating\]\:border:is(:where(.group)[data-variant=floating] *){border-style:var(--tw-border-style);border-width:1px}.group-data-\[variant\=floating\]\:border-sidebar-border:is(:where(.group)[data-variant=floating] *){border-color:var(--sidebar-border)}.group-data-\[variant\=floating\]\:shadow-sm:is(:where(.group)[data-variant=floating] *){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.group-data-\[vaul-drawer-direction\=bottom\]\/drawer-content\:block:is(:where(.group\/drawer-content)[data-vaul-drawer-direction=bottom] *){display:block}.group-data-\[view\=code\]\/block-view-wrapper\:hidden:is(:where(.group\/block-view-wrapper)[data-view=code] *),.group-data-\[view\=preview\]\/block-view-wrapper\:hidden:is(:where(.group\/block-view-wrapper)[data-view=preview] *){display:none}@media (hover:hover){.peer-hover\/menu-button\:text-sidebar-accent-foreground:is(:where(.peer\/menu-button):hover~*){color:var(--sidebar-accent-foreground)}}.peer-disabled\:cursor-not-allowed:is(:where(.peer):disabled~*){cursor:not-allowed}.peer-disabled\:opacity-50:is(:where(.peer):disabled~*){opacity:.5}.peer-has-\[\[role\=menuitem\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=menuitem])~*),.peer-has-\[\[role\=menuitemradio\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=menuitemradio])~*),.peer-has-\[\[role\=option\]\]\/menu-group\:block:is(:where(.peer\/menu-group):has([role=option])~*){display:block}.peer-data-\[active\=true\]\/menu-button\:text-sidebar-accent-foreground:is(:where(.peer\/menu-button)[data-active=true]~*){color:var(--sidebar-accent-foreground)}.peer-data-\[size\=default\]\/menu-button\:top-1\.5:is(:where(.peer\/menu-button)[data-size=default]~*){top:calc(var(--spacing)*1.5)}.peer-data-\[size\=lg\]\/menu-button\:top-2\.5:is(:where(.peer\/menu-button)[data-size=lg]~*){top:calc(var(--spacing)*2.5)}.peer-data-\[size\=sm\]\/menu-button\:top-1:is(:where(.peer\/menu-button)[data-size=sm]~*){top:calc(var(--spacing)*1)}.selection\:bg-brand\/25 ::selection{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-brand\/25 ::selection{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.selection\:bg-brand\/25::selection{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.selection\:bg-brand\/25::selection{background-color:color-mix(in oklab,var(--brand)25%,transparent)}}.selection\:bg-transparent ::selection{background-color:#0000}.selection\:bg-transparent::selection{background-color:#0000}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-7::file-selector-button{height:calc(var(--spacing)*7)}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::placeholder,.placeholder\:text-muted-foreground\/80::placeholder{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.placeholder\:text-muted-foreground\/80::placeholder{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.before\:absolute:before{content:var(--tw-content);position:absolute}.before\:z-10:before{content:var(--tw-content);z-index:10}.before\:mt-\[-4px\]:before{content:var(--tw-content);margin-top:-4px}.before\:ml-\[-50px\]:before{content:var(--tw-content);margin-left:-50px}.before\:box-border:before{content:var(--tw-content);box-sizing:border-box}.before\:inline-flex:before{content:var(--tw-content);display:inline-flex}.before\:size-full:before{content:var(--tw-content);width:100%;height:100%}.before\:h-9:before{content:var(--tw-content);height:calc(var(--spacing)*9)}.before\:w-9:before{content:var(--tw-content);width:calc(var(--spacing)*9)}.before\:cursor-text:before{content:var(--tw-content);cursor:text}.before\:items-center:before{content:var(--tw-content);align-items:center}.before\:justify-center:before{content:var(--tw-content);justify-content:center}.before\:rounded-full:before{content:var(--tw-content);border-radius:3.40282e38px}.before\:border-4:before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:4px}.before\:border-t:before{content:var(--tw-content);border-top-style:var(--tw-border-style);border-top-width:1px}.before\:border-r:before{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:1px}.before\:border-b:before{content:var(--tw-content);border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.before\:border-l:before{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:1px}.before\:border-background:before{content:var(--tw-content);border-color:var(--background)}.before\:border-t-border:before{content:var(--tw-content);border-top-color:var(--border)}.before\:border-r-border:before{content:var(--tw-content);border-right-color:var(--border)}.before\:border-b-border:before{content:var(--tw-content);border-bottom-color:var(--border)}.before\:border-l-border:before{content:var(--tw-content);border-left-color:var(--border)}.before\:bg-brand\/5:before{content:var(--tw-content);background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.before\:bg-brand\/5:before{background-color:color-mix(in oklab,var(--brand)5%,transparent)}}.before\:bg-muted:before{content:var(--tw-content);background-color:var(--muted)}.before\:text-center:before{content:var(--tw-content);text-align:center}.before\:-indent-px:before{content:var(--tw-content);text-indent:-1px}.before\:font-mono:before{content:var(--tw-content);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.before\:text-base:before{content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.before\:font-medium:before{content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.before\:text-muted-foreground\/80:before{content:var(--tw-content);color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.before\:text-muted-foreground\/80:before{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.before\:opacity-30:before{content:var(--tw-content);opacity:.3}.before\:content-\[\'\'\]:before{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.before\:content-\[attr\(placeholder\)\]:before{content:var(--tw-content);--tw-content:attr(placeholder);content:var(--tw-content)}.before\:\[content\:counter\(step\)\]:before{content:var(--tw-content);content:counter(step)}.before\:select-none:before{content:var(--tw-content);-webkit-user-select:none;user-select:none}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-2:after{content:var(--tw-content);inset:calc(var(--spacing)*-2)}.after\:inset-0:after{content:var(--tw-content);inset:calc(var(--spacing)*0)}.after\:inset-x-0:after{content:var(--tw-content);inset-inline:calc(var(--spacing)*0)}.after\:inset-y-0:after{content:var(--tw-content);inset-block:calc(var(--spacing)*0)}.after\:inset-y-\[-2px\]:after{content:var(--tw-content);inset-block:-2px}.after\:-top-0\.5:after{content:var(--tw-content);top:calc(var(--spacing)*-.5)}.after\:top-1\/2:after{content:var(--tw-content);top:50%}.after\:right-0:after{content:var(--tw-content);right:calc(var(--spacing)*0)}.after\:-left-1:after{content:var(--tw-content);left:calc(var(--spacing)*-1)}.after\:left-0:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.after\:left-1\/2:after{content:var(--tw-content);left:50%}.after\:z-1:after{content:var(--tw-content);z-index:1}.after\:ml-1\.5:after{content:var(--tw-content);margin-left:calc(var(--spacing)*1.5)}.after\:block:after{content:var(--tw-content);display:block}.after\:flex:after{content:var(--tw-content);display:flex}.after\:inline-block:after{content:var(--tw-content);display:inline-block}.after\:h-3:after{content:var(--tw-content);height:calc(var(--spacing)*3)}.after\:h-8:after{content:var(--tw-content);height:calc(var(--spacing)*8)}.after\:h-16:after{content:var(--tw-content);height:calc(var(--spacing)*16)}.after\:h-\[calc\(100\%\)\+4px\]:after{content:var(--tw-content);height:calc(100%)4px}.after\:w-1:after{content:var(--tw-content);width:calc(var(--spacing)*1)}.after\:w-3:after{content:var(--tw-content);width:calc(var(--spacing)*3)}.after\:w-10:after{content:var(--tw-content);width:calc(var(--spacing)*10)}.after\:w-\[2px\]:after{content:var(--tw-content);width:2px}.after\:w-\[3px\]:after{content:var(--tw-content);width:3px}.after\:w-\[6px\]:after{content:var(--tw-content);width:6px}.after\:w-\[calc\(100\%\+8px\)\]:after{content:var(--tw-content);width:calc(100% + 8px)}.after\:-translate-x-1\/2:after{content:var(--tw-content);--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:-translate-x-px:after{content:var(--tw-content);--tw-translate-x:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:-translate-y-1\/2:after{content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.after\:rounded-\[6px\]:after{content:var(--tw-content);border-radius:6px}.after\:rounded-full:after{content:var(--tw-content);border-radius:3.40282e38px}.after\:rounded-lg:after{content:var(--tw-content);border-radius:var(--radius)}.after\:rounded-sm:after{content:var(--tw-content);border-radius:calc(var(--radius) - 4px)}.after\:bg-border:after{content:var(--tw-content);background-color:var(--border)}.after\:bg-brand\/15:after{content:var(--tw-content);background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.after\:bg-brand\/15:after{background-color:color-mix(in oklab,var(--brand)15%,transparent)}}.after\:bg-neutral-500\/10:after{content:var(--tw-content);background-color:#7373731a}@supports (color:color-mix(in lab, red, red)){.after\:bg-neutral-500\/10:after{background-color:color-mix(in oklab,var(--color-neutral-500)10%,transparent)}}.after\:bg-primary:after{content:var(--tw-content);background-color:var(--primary)}.after\:bg-ring:after{content:var(--tw-content);background-color:var(--ring)}.after\:bg-zinc-950:after{content:var(--tw-content);background-color:var(--color-zinc-950)}.after\:pb-\[var\(--aspect-ratio\)\]:after{content:var(--tw-content);padding-bottom:var(--aspect-ratio)}.after\:align-middle:after{content:var(--tw-content);vertical-align:middle}.after\:opacity-0:after{content:var(--tw-content);opacity:0}.after\:transition-all:after{content:var(--tw-content);transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.after\:content-\[\"\"\]:after{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.after\:content-\[\'_\'\]:after{content:var(--tw-content);--tw-content:" ";content:var(--tw-content)}.after\:content-\[\\\"\\\"\]:after{content:var(--tw-content);--tw-content:\"\";content:var(--tw-content)}@media (hover:hover){.group-hover\:after\:opacity-100:is(:where(.group):hover *):after{content:var(--tw-content);opacity:1}}.group-data-\[collapsible\=offcanvas\]\:after\:left-full:is(:where(.group)[data-collapsible=offcanvas] *):after{content:var(--tw-content);left:100%}.first\:\!mt-0:first-child{margin-top:calc(var(--spacing)*0)!important}.first\:mt-0:first-child{margin-top:calc(var(--spacing)*0)}.first\:rounded-l-md:first-child{border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:rounded-r-md:last-child{border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.empty\:hidden:empty{display:none}.focus-within\:relative:focus-within{position:relative}.focus-within\:z-20:focus-within{z-index:20}.focus-within\:ring-2:focus-within{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-within\:ring-ring:focus-within{--tw-ring-color:var(--ring)}.focus-within\:ring-offset-2:focus-within{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}@media (hover:hover){.hover\:scale-125:hover{--tw-scale-x:125%;--tw-scale-y:125%;--tw-scale-z:125%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:bg-accent:hover,.hover\:bg-accent\/80:hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-accent\/80:hover{background-color:color-mix(in oklab,var(--accent)80%,transparent)}}.hover\:bg-black:hover{background-color:var(--color-black)}.hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-muted-foreground\/15:hover{background-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted-foreground\/15:hover{background-color:color-mix(in oklab,var(--muted-foreground)15%,transparent)}}.hover\:bg-muted\/50:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,var(--muted)50%,transparent)}}.hover\:bg-primary:hover,.hover\:bg-primary\/10:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/10:hover{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}.hover\:bg-secondary\/60:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/60:hover{background-color:color-mix(in oklab,var(--secondary)60%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,var(--secondary)80%,transparent)}}.hover\:bg-sidebar-accent:hover{background-color:var(--sidebar-accent)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-transparent:hover{background-color:#0000}.hover\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:text-accent-foreground:hover{color:var(--accent-foreground)}.hover\:text-brand\/80:hover{color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.hover\:text-brand\/80:hover{color:color-mix(in oklab,var(--brand)80%,transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:text-muted-foreground:hover,.hover\:text-muted-foreground\/80:hover{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.hover\:text-muted-foreground\/80:hover{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}.hover\:text-primary:hover{color:var(--primary)}.hover\:text-primary-foreground:hover{color:var(--primary-foreground)}.hover\:text-sidebar-accent-foreground:hover{color:var(--sidebar-accent-foreground)}.hover\:text-slate-50:hover{color:var(--color-slate-50)}.hover\:text-white:hover{color:var(--color-white)}.hover\:text-zinc-50:hover{color:var(--color-zinc-50)}.hover\:no-underline:hover{text-decoration-line:none}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-90:hover{opacity:.9}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-\[0_0_0_1px_hsl\(var\(--sidebar-accent\)\)\]:hover{--tw-shadow:0 0 0 1px var(--tw-shadow-color,hsl(var(--sidebar-accent)));box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-2:hover{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.hover\:ring-primary:hover{--tw-ring-color:var(--primary)}.hover\:ring-offset-2:hover{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.hover\:group-data-\[collapsible\=offcanvas\]\:bg-sidebar:hover:is(:where(.group)[data-collapsible=offcanvas] *){background-color:var(--sidebar)}.hover\:after\:absolute:hover:after{content:var(--tw-content);position:absolute}.hover\:after\:-bottom-1:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*-1)}.hover\:after\:bottom-0:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}.hover\:after\:left-0:hover:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.hover\:after\:h-10:hover:after{content:var(--tw-content);height:calc(var(--spacing)*10)}.hover\:after\:h-\[1\.5px\]:hover:after{content:var(--tw-content);height:1.5px}.hover\:after\:w-\[calc\(100\%-2px\)\]:hover:after{content:var(--tw-content);width:calc(100% - 2px)}.hover\:after\:bg-brand:hover:after{content:var(--tw-content);background-color:var(--brand)}.hover\:after\:bg-primary:hover:after{content:var(--tw-content);background-color:var(--primary)}.hover\:after\:bg-sidebar-border:hover:after{content:var(--tw-content);background-color:var(--sidebar-border)}}.focus\:z-10:focus{z-index:10}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:bg-primary:focus{background-color:var(--primary)}.focus\:bg-zinc-700:focus{background-color:var(--color-zinc-700)}.focus\:text-accent-foreground:focus{color:var(--accent-foreground)}.focus\:text-primary-foreground:focus{color:var(--primary-foreground)}.focus\:text-white:focus{color:var(--color-white)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:var(--ring)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-hidden:focus{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus\:outline-hidden:focus{outline-offset:2px;outline:2px solid #0000}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:bg-transparent:focus-visible{background-color:#0000}.focus-visible\:bg-zinc-700:focus-visible{background-color:var(--color-zinc-700)}.focus-visible\:text-white:focus-visible{color:var(--color-white)}.focus-visible\:ring-0:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(0px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(3px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.focus-visible\:ring-ring:focus-visible,.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab,var(--ring)50%,transparent)}}.focus-visible\:ring-slate-700:focus-visible{--tw-ring-color:var(--color-slate-700)}.focus-visible\:ring-transparent:focus-visible{--tw-ring-color:transparent}.focus-visible\:ring-offset-0:focus-visible{--tw-ring-offset-width:0px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus-visible\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus-visible\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.focus-visible\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}.focus-visible\:outline-ring:focus-visible{outline-color:var(--ring)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}.active\:bg-sidebar-accent:active{background-color:var(--sidebar-accent)}.active\:bg-transparent:active{background-color:#0000}.active\:bg-zinc-700:active{background-color:var(--color-zinc-700)}.active\:text-sidebar-accent-foreground:active{color:var(--sidebar-accent-foreground)}.active\:text-white:active{color:var(--color-white)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}:where([data-side=left]) .in-data-\[side\=left\]\:cursor-w-resize{cursor:w-resize}:where([data-side=right]) .in-data-\[side\=right\]\:cursor-e-resize{cursor:e-resize}.has-aria-disabled\:border-input:has([aria-disabled=true]){border-color:var(--input)}.has-aria-disabled\:bg-muted:has([aria-disabled=true]){background-color:var(--muted)}.has-data-readonly\:w-fit:has([data-readonly]){width:fit-content}.has-data-readonly\:cursor-default:has([data-readonly]){cursor:default}.has-data-readonly\:border-transparent:has([data-readonly]){border-color:#0000}.has-data-readonly\:focus-within\:\[box-shadow\:none\]:has([data-readonly]):focus-within{box-shadow:none}.has-data-\[slot\=card-action\]\:grid-cols-\[1fr_auto\]:has([data-slot=card-action]){grid-template-columns:1fr auto}.has-data-\[variant\=inset\]\:bg-sidebar:has([data-variant=inset]){background-color:var(--sidebar)}.has-\[\[data-slate-editor\]\:focus\]\:border-brand\/50:has([data-slate-editor]:focus){border-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.has-\[\[data-slate-editor\]\:focus\]\:border-brand\/50:has([data-slate-editor]:focus){border-color:color-mix(in oklab,var(--brand)50%,transparent)}}.has-\[\[data-slate-editor\]\:focus\]\:ring-2:has([data-slate-editor]:focus){--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.has-\[\[data-slate-editor\]\:focus\]\:ring-brand\/30:has([data-slate-editor]:focus){--tw-ring-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.has-\[\[data-slate-editor\]\:focus\]\:ring-brand\/30:has([data-slate-editor]:focus){--tw-ring-color:color-mix(in oklab,var(--brand)30%,transparent)}}.has-\[\[role\=menuitem\]\]\:block:has([role=menuitem]),.has-\[\[role\=menuitemradio\]\]\:block:has([role=menuitemradio]),.has-\[\[role\=option\]\]\:block:has([role=option]){display:block}.has-\[button\]\:flex:has(:is(button)){display:flex}.has-\[\+input\:not\(\:placeholder-shown\)\]\:pointer-events-none:has(+input:not(:placeholder-shown)){pointer-events:none}.has-\[\+input\:not\(\:placeholder-shown\)\]\:top-0:has(+input:not(:placeholder-shown)){top:calc(var(--spacing)*0)}.has-\[\+input\:not\(\:placeholder-shown\)\]\:cursor-default:has(+input:not(:placeholder-shown)){cursor:default}.has-\[\+input\:not\(\:placeholder-shown\)\]\:text-xs:has(+input:not(:placeholder-shown)){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.has-\[\+input\:not\(\:placeholder-shown\)\]\:font-medium:has(+input:not(:placeholder-shown)){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.has-\[\+input\:not\(\:placeholder-shown\)\]\:text-foreground:has(+input:not(:placeholder-shown)){color:var(--foreground)}.has-\[\>svg\]\:grid-cols-\[calc\(var\(--spacing\)\*4\)_1fr\]:has(>svg){grid-template-columns:calc(var(--spacing)*4)1fr}.has-\[\>svg\]\:gap-x-3:has(>svg){column-gap:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-inline:calc(var(--spacing)*2.5)}.has-\[\>svg\]\:px-3:has(>svg){padding-inline:calc(var(--spacing)*3)}.has-\[\>svg\]\:px-4:has(>svg){padding-inline:calc(var(--spacing)*4)}.aria-checked\:border-\(--color-1\)[aria-checked=true]{border-color:var(--color-1)}.aria-checked\:bg-accent[aria-checked=true]{background-color:var(--accent)}.aria-checked\:text-accent-foreground[aria-checked=true]{color:var(--accent-foreground)}.aria-disabled\:pointer-events-none[aria-disabled=true]{pointer-events:none}.aria-disabled\:opacity-50[aria-disabled=true]{opacity:.5}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)20%,transparent)}}.aria-selected\:bg-accent[aria-selected=true]{background-color:var(--accent)}.aria-selected\:bg-primary[aria-selected=true]{background-color:var(--primary)}.aria-selected\:text-accent-foreground[aria-selected=true]{color:var(--accent-foreground)}.aria-selected\:text-muted-foreground[aria-selected=true]{color:var(--muted-foreground)}.aria-selected\:text-primary-foreground[aria-selected=true]{color:var(--primary-foreground)}.aria-selected\:opacity-100[aria-selected=true]{opacity:1}:is(.\*\*\:data-block-hide\:hidden *)[data-block-hide]{display:none}.data-readonly\:w-fit[data-readonly]{width:fit-content}:is(.\*\*\:data-slate-editor\:max-h-\[calc\(100dvh-44px\)\] *)[data-slate-editor]{max-height:calc(100dvh - 44px)}:is(.\*\*\:data-slate-placeholder\:\!top-1\/2 *)[data-slate-placeholder]{top:50%!important}:is(.\*\*\:data-slate-placeholder\:top-\[auto_\!important\] *)[data-slate-placeholder]{top:auto!important}:is(.\*\*\:data-slate-placeholder\:-translate-y-1\/2 *)[data-slate-placeholder]{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}:is(.\*\*\:data-slate-placeholder\:text-muted-foreground\/80 *)[data-slate-placeholder]{color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){:is(.\*\*\:data-slate-placeholder\:text-muted-foreground\/80 *)[data-slate-placeholder]{color:color-mix(in oklab,var(--muted-foreground)80%,transparent)}}:is(.\*\*\:data-slate-placeholder\:opacity-100\! *)[data-slate-placeholder]{opacity:1!important}.data-\[active-item\=true\]\:bg-accent[data-active-item=true]{background-color:var(--accent)}.data-\[active-item\=true\]\:text-accent-foreground[data-active-item=true]{color:var(--accent-foreground)}.data-\[active\=true\]\:bg-muted[data-active=true]{background-color:var(--muted)}.data-\[active\=true\]\:bg-sidebar-accent[data-active=true]{background-color:var(--sidebar-accent)}.data-\[active\=true\]\:bg-zinc-700[data-active=true]{background-color:var(--color-zinc-700)}.data-\[active\=true\]\:font-medium[data-active=true]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.data-\[active\=true\]\:text-foreground[data-active=true]{color:var(--foreground)}.data-\[active\=true\]\:text-sidebar-accent-foreground[data-active=true]{color:var(--sidebar-accent-foreground)}.data-\[active\=true\]\:text-white[data-active=true]{color:var(--color-white)}.data-\[block\=login-01\]\:max-w-full[data-block=login-01]{max-width:100%}.data-\[block\=sidebar-10\]\:right-0[data-block=sidebar-10]{right:calc(var(--spacing)*0)}.data-\[block\=sidebar-10\]\:left-auto[data-block=sidebar-10]{left:auto}.data-\[block\=sidebar-11\]\:-top-1\/3[data-block=sidebar-11]{top:-33.3333%}.data-\[block\=sidebar-13\]\:max-w-full[data-block=sidebar-13]{max-width:100%}.data-\[block\=sidebar-14\]\:right-0[data-block=sidebar-14]{right:calc(var(--spacing)*0)}.data-\[block\=sidebar-14\]\:left-auto[data-block=sidebar-14]{left:auto}.data-\[block\=sidebar-15\]\:max-w-full[data-block=sidebar-15]{max-width:100%}.data-\[depth\=3\]\:pl-4[data-depth="3"]{padding-left:calc(var(--spacing)*4)}.data-\[depth\=3\]\:pl-6[data-depth="3"],.data-\[depth\=4\]\:pl-6[data-depth="4"]{padding-left:calc(var(--spacing)*6)}.data-\[depth\=4\]\:pl-8[data-depth="4"]{padding-left:calc(var(--spacing)*8)}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[disabled\=true\]\:pointer-events-none[data-disabled=true]{pointer-events:none}.data-\[disabled\=true\]\:opacity-50[data-disabled=true]{opacity:.5}.data-\[error\=true\]\:text-destructive[data-error=true]{color:var(--destructive)}.data-\[highlighted\=true\]\:bg-accent[data-highlighted=true]{background-color:var(--accent)}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing)*8)}.data-\[orientation\=horizontal\]\:h-px[data-orientation=horizontal]{height:1px}.data-\[orientation\=horizontal\]\:w-full[data-orientation=horizontal]{width:100%}.data-\[orientation\=vertical\]\:h-full[data-orientation=vertical]{height:100%}.data-\[orientation\=vertical\]\:w-px[data-orientation=vertical]{width:1px}.data-\[panel-group-direction\=vertical\]\:h-px[data-panel-group-direction=vertical]{height:1px}.data-\[panel-group-direction\=vertical\]\:w-full[data-panel-group-direction=vertical]{width:100%}.data-\[panel-group-direction\=vertical\]\:flex-col[data-panel-group-direction=vertical]{flex-direction:column}.data-\[panel-group-direction\=vertical\]\:after\:left-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);left:calc(var(--spacing)*0)}.data-\[panel-group-direction\=vertical\]\:after\:h-1[data-panel-group-direction=vertical]:after{content:var(--tw-content);height:calc(var(--spacing)*1)}.data-\[panel-group-direction\=vertical\]\:after\:w-full[data-panel-group-direction=vertical]:after{content:var(--tw-content);width:100%}.data-\[panel-group-direction\=vertical\]\:after\:translate-x-0[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-x:calc(var(--spacing)*0);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[panel-group-direction\=vertical\]\:after\:-translate-y-1\/2[data-panel-group-direction=vertical]:after{content:var(--tw-content);--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[placeholder\]\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[selected\=true\]\:bg-accent[data-selected=true]{background-color:var(--accent)}.data-\[selected\=true\]\:bg-primary\/10[data-selected=true]{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.data-\[selected\=true\]\:bg-primary\/10[data-selected=true]{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.data-\[selected\=true\]\:text-accent-foreground[data-selected=true]{color:var(--accent-foreground)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing)*9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing)*8)}:is(.\*\:data-\[slot\=alert\]\:first\:mt-0>*)[data-slot=alert]:first-child{margin-top:calc(var(--spacing)*0)}:is(.\*\:data-\[slot\=alert-description\]\:text-destructive\/90>*)[data-slot=alert-description]{color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){:is(.\*\:data-\[slot\=alert-description\]\:text-destructive\/90>*)[data-slot=alert-description]{color:color-mix(in oklab,var(--destructive)90%,transparent)}}:is(.\*\:data-\[slot\=block-selection\]\:left-2>*)[data-slot=block-selection]{left:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=command-input-wrapper\]\:h-12 *)[data-slot=command-input-wrapper]{height:calc(var(--spacing)*12)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing)*2)}:is(.\*\*\:data-\[slot\=separator\]\:\!h-4 *)[data-slot=separator]{height:calc(var(--spacing)*4)!important}.data-\[state\=active\]\:flex[data-state=active]{display:flex}.data-\[state\=active\]\:border-b-primary[data-state=active]{border-bottom-color:var(--primary)}.data-\[state\=active\]\:border-b-zinc-50[data-state=active]{border-bottom-color:var(--color-zinc-50)}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:var(--background)}.data-\[state\=active\]\:bg-transparent[data-state=active]{background-color:#0000}.data-\[state\=active\]\:text-foreground[data-state=active]{color:var(--foreground)}.data-\[state\=active\]\:text-zinc-50[data-state=active]{color:var(--color-zinc-50)}.data-\[state\=active\]\:shadow-none[data-state=active]{--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[state\=checked\]\:border-primary[data-state=checked]{border-color:var(--primary)}.data-\[state\=checked\]\:bg-accent[data-state=checked]{background-color:var(--accent)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:var(--primary)}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:var(--primary-foreground)}.data-\[state\=closed\]\:shrink-0[data-state=closed]{flex-shrink:0}.data-\[state\=closed\]\:animate-accordion-up[data-state=closed]{animation:accordion-up var(--tw-animation-duration,var(--tw-duration,.2s))ease-out}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:opacity-0[data-state=closed]{opacity:0}.data-\[state\=closed\]\:duration-300[data-state=closed]{--tw-duration:.3s;transition-duration:.3s}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed]{--tw-exit-translate-y:100%}.data-\[state\=closed\]\:slide-out-to-left[data-state=closed]{--tw-exit-translate-x:-100%}.data-\[state\=closed\]\:slide-out-to-right[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-top[data-state=closed]{--tw-exit-translate-y:-100%}.data-\[state\=on\]\:bg-accent[data-state=on]{background-color:var(--accent)}.data-\[state\=on\]\:text-accent-foreground[data-state=on]{color:var(--accent-foreground)}.data-\[state\=open\]\:grow[data-state=open]{flex-grow:1}.data-\[state\=open\]\:animate-accordion-down[data-state=open]{animation:accordion-down var(--tw-animation-duration,var(--tw-duration,.2s))ease-out}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:animate-none[data-state=open]{animation:none}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:var(--accent)}.data-\[state\=open\]\:bg-secondary[data-state=open]{background-color:var(--secondary)}.data-\[state\=open\]\:text-accent-foreground[data-state=open]{color:var(--accent-foreground)}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:var(--muted-foreground)}.data-\[state\=open\]\:opacity-100[data-state=open]{opacity:1}.data-\[state\=open\]\:duration-500[data-state=open]{--tw-duration:.5s;transition-duration:.5s}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=open\]\:slide-in-from-bottom[data-state=open]{--tw-enter-translate-y:100%}.data-\[state\=open\]\:slide-in-from-left[data-state=open]{--tw-enter-translate-x:-100%}.data-\[state\=open\]\:slide-in-from-right[data-state=open]{--tw-enter-translate-x:100%}.data-\[state\=open\]\:slide-in-from-top[data-state=open]{--tw-enter-translate-y:-100%}@media (hover:hover){.data-\[state\=open\]\:hover\:bg-sidebar-accent[data-state=open]:hover{background-color:var(--sidebar-accent)}.data-\[state\=open\]\:hover\:bg-zinc-700[data-state=open]:hover{background-color:var(--color-zinc-700)}.data-\[state\=open\]\:hover\:text-sidebar-accent-foreground[data-state=open]:hover{color:var(--sidebar-accent-foreground)}.data-\[state\=open\]\:hover\:text-white[data-state=open]:hover{color:var(--color-white)}}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:var(--muted)}.data-\[variant\=destructive\]\:text-destructive[data-variant=destructive]{color:var(--destructive)}.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.data-\[variant\=destructive\]\:focus\:bg-destructive\/10[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)10%,transparent)}}.data-\[variant\=destructive\]\:focus\:text-destructive[data-variant=destructive]:focus{color:var(--destructive)}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.data-\[variant\=outline\]\:shadow-xs[data-variant=outline]{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.data-\[variant\=outline\]\:first\:border-l[data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}.data-\[vaul-drawer-direction\=bottom\]\:inset-x-0[data-vaul-drawer-direction=bottom]{inset-inline:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=bottom\]\:bottom-0[data-vaul-drawer-direction=bottom]{bottom:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=bottom\]\:mt-24[data-vaul-drawer-direction=bottom]{margin-top:calc(var(--spacing)*24)}.data-\[vaul-drawer-direction\=bottom\]\:max-h-\[80vh\][data-vaul-drawer-direction=bottom]{max-height:80vh}.data-\[vaul-drawer-direction\=bottom\]\:rounded-t-lg[data-vaul-drawer-direction=bottom]{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.data-\[vaul-drawer-direction\=bottom\]\:border-t[data-vaul-drawer-direction=bottom]{border-top-style:var(--tw-border-style);border-top-width:1px}.data-\[vaul-drawer-direction\=left\]\:inset-y-0[data-vaul-drawer-direction=left]{inset-block:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=left\]\:left-0[data-vaul-drawer-direction=left]{left:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=left\]\:w-3\/4[data-vaul-drawer-direction=left]{width:75%}.data-\[vaul-drawer-direction\=left\]\:border-r[data-vaul-drawer-direction=left]{border-right-style:var(--tw-border-style);border-right-width:1px}.data-\[vaul-drawer-direction\=right\]\:inset-y-0[data-vaul-drawer-direction=right]{inset-block:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=right\]\:right-0[data-vaul-drawer-direction=right]{right:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=right\]\:w-3\/4[data-vaul-drawer-direction=right]{width:75%}.data-\[vaul-drawer-direction\=right\]\:border-l[data-vaul-drawer-direction=right]{border-left-style:var(--tw-border-style);border-left-width:1px}.data-\[vaul-drawer-direction\=top\]\:inset-x-0[data-vaul-drawer-direction=top]{inset-inline:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=top\]\:top-0[data-vaul-drawer-direction=top]{top:calc(var(--spacing)*0)}.data-\[vaul-drawer-direction\=top\]\:mb-24[data-vaul-drawer-direction=top]{margin-bottom:calc(var(--spacing)*24)}.data-\[vaul-drawer-direction\=top\]\:max-h-\[80vh\][data-vaul-drawer-direction=top]{max-height:80vh}.data-\[vaul-drawer-direction\=top\]\:rounded-b-lg[data-vaul-drawer-direction=top]{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.data-\[vaul-drawer-direction\=top\]\:border-b[data-vaul-drawer-direction=top]{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}:is(.\*\:nth-\[2\]\:flex-1>*):nth-child(2){flex:1}:is(.\*\:nth-\[2\]\:text-muted-foreground>*):nth-child(2){color:var(--muted-foreground)}:is(.\*\:nth-\[2\]\:line-through>*):nth-child(2){text-decoration-line:line-through}:is(.\*\:nth-\[2\]\:focus\:outline-none>*):nth-child(2):focus{--tw-outline-style:none;outline-style:none}@supports (backdrop-blur:var(--tw)){.supports-backdrop-blur\:bg-background\/60{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.supports-backdrop-blur\:bg-background\/60{background-color:color-mix(in oklab,var(--background)60%,transparent)}}}@supports ((-webkit-backdrop-filter:var(--tw)) or (backdrop-filter:var(--tw))){.supports-backdrop-filter\:bg-background\/60{background-color:var(--background)}@supports (color:color-mix(in lab, red, red)){.supports-backdrop-filter\:bg-background\/60{background-color:color-mix(in oklab,var(--background)60%,transparent)}}}@media not all and (min-width:48rem){.max-md\:hidden{display:none}}@media not all and (min-width:40rem){.max-sm\:hidden{display:none}.max-sm\:w-full{width:100%}.max-sm\:flex-auto\!{flex:auto!important}}@media (min-width:40rem){.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:w-\[1280px\]{width:1280px}.sm\:max-w-3xl{max-width:var(--container-3xl)}.sm\:max-w-lg{max-width:var(--container-lg)}.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}.sm\:p-10{padding:calc(var(--spacing)*10)}.sm\:px-24{padding-inline:calc(var(--spacing)*24)}.sm\:px-\[max\(64px\,calc\(50\%-350px\)\)\]{padding-inline:max(64px,50% - 350px)}.sm\:pr-12{padding-right:calc(var(--spacing)*12)}.sm\:text-left{text-align:left}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.sm\:opacity-0{opacity:0}.data-\[vaul-drawer-direction\=left\]\:sm\:max-w-sm[data-vaul-drawer-direction=left],.data-\[vaul-drawer-direction\=right\]\:sm\:max-w-sm[data-vaul-drawer-direction=right]{max-width:var(--container-sm)}}@media (min-width:48rem){.md\:sticky{position:sticky}.md\:my-8{margin-block:calc(var(--spacing)*8)}.md\:mt-24{margin-top:calc(var(--spacing)*24)}.md\:block{display:block}.md\:flex{display:flex}.md\:grid{display:grid}.md\:hidden{display:none}.md\:aspect-auto{aspect-ratio:auto}.md\:size-7{width:calc(var(--spacing)*7);height:calc(var(--spacing)*7)}.md\:h-7{height:calc(var(--spacing)*7)}.md\:h-24{height:calc(var(--spacing)*24)}.md\:w-40{width:calc(var(--spacing)*40)}.md\:w-auto{width:auto}.md\:flex-1{flex:1}.md\:flex-none{flex:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-\[var\(--sidebar-width\)_minmax\(0\,1fr\)\]{grid-template-columns:var(--sidebar-width)minmax(0,1fr)}.md\:flex-row{flex-direction:row}.md\:flex-row-reverse{flex-direction:row-reverse}.md\:items-start{align-items:flex-start}.md\:justify-end{justify-content:flex-end}.md\:gap-4{gap:calc(var(--spacing)*4)}.md\:gap-16{gap:calc(var(--spacing)*16)}.md\:gap-24{gap:calc(var(--spacing)*24)}:where(.md\:space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.md\:px-2{padding-inline:calc(var(--spacing)*2)}.md\:px-8{padding-inline:calc(var(--spacing)*8)}.md\:py-0{padding-block:calc(var(--spacing)*0)}.md\:py-10{padding-block:calc(var(--spacing)*10)}.md\:pt-0{padding-top:calc(var(--spacing)*0)}.md\:pr-\[14px\]{padding-right:14px}.md\:text-left{text-align:left}.md\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.md\:opacity-0{opacity:0}.md\:\[--sidebar-width\:240px\]{--sidebar-width:240px}.md\:\[--top-spacing\:calc\(var\(--spacing\)\*4\)\]{--top-spacing:calc(var(--spacing)*4)}.md\:peer-data-\[variant\=inset\]\:m-2:is(:where(.peer)[data-variant=inset]~*){margin:calc(var(--spacing)*2)}.md\:peer-data-\[variant\=inset\]\:ml-0:is(:where(.peer)[data-variant=inset]~*){margin-left:calc(var(--spacing)*0)}.md\:peer-data-\[variant\=inset\]\:rounded-xl:is(:where(.peer)[data-variant=inset]~*){border-radius:calc(var(--radius) + 4px)}.md\:peer-data-\[variant\=inset\]\:shadow-sm:is(:where(.peer)[data-variant=inset]~*){--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.md\:peer-data-\[variant\=inset\]\:peer-data-\[state\=collapsed\]\:ml-2:is(:where(.peer)[data-variant=inset]~*):is(:where(.peer)[data-state=collapsed]~*){margin-left:calc(var(--spacing)*2)}.md\:after\:hidden:after{content:var(--tw-content);display:none}}@media (min-width:64rem){.lg\:sticky{position:sticky}.lg\:top-20{top:calc(var(--spacing)*20)}.lg\:bottom-auto{bottom:auto}.lg\:mt-36{margin-top:calc(var(--spacing)*36)}.lg\:block{display:block}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:inline{display:inline}.lg\:inline-flex{display:inline-flex}.lg\:w-56{width:calc(var(--spacing)*56)}.lg\:w-auto{width:auto}.lg\:w-full{width:100%}.lg\:flex-col{flex-direction:column}.lg\:justify-start{justify-content:flex-start}.lg\:gap-1{gap:calc(var(--spacing)*1)}.lg\:gap-6{gap:calc(var(--spacing)*6)}.lg\:gap-48{gap:calc(var(--spacing)*48)}.lg\:px-0{padding-inline:calc(var(--spacing)*0)}.lg\:py-8{padding-block:calc(var(--spacing)*8)}.lg\:py-12{padding-block:calc(var(--spacing)*12)}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:leading-\[1\.1\]{--tw-leading:1.1;line-height:1.1}}@media (min-width:80rem){.xl\:block{display:block}.xl\:flex{display:flex}.xl\:hidden{display:none}.xl\:inline{display:inline}.xl\:w-64{width:calc(var(--spacing)*64)}.xl\:w-auto{width:auto}.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:gap-10{gap:calc(var(--spacing)*10)}.xl\:\[--footer-height\:calc\(var\(--spacing\)\*24\)\]{--footer-height:calc(var(--spacing)*24)}}@media (min-width:96rem){.\32 xl\:block{display:block}.\32 xl\:hidden{display:none}}@container not (min-width:32rem){.\@max-lg\:col-span-full{grid-column:1/-1}}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:border-border:is(.dark *){border-color:var(--border)}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-cyan-950:is(.dark *){background-color:var(--color-cyan-950)}.dark\:bg-destructive\/60:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-destructive\/60:is(.dark *){background-color:color-mix(in oklab,var(--destructive)60%,transparent)}}.dark\:bg-gray-800:is(.dark *){background-color:var(--color-gray-800)}.dark\:bg-green-950:is(.dark *){background-color:var(--color-green-950)}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:bg-muted:is(.dark *){background-color:var(--muted)}.dark\:bg-neutral-900:is(.dark *){background-color:var(--color-neutral-900)}.dark\:bg-orange-950:is(.dark *){background-color:var(--color-orange-950)}.dark\:bg-pink-950:is(.dark *){background-color:var(--color-pink-950)}.dark\:bg-purple-900:is(.dark *){background-color:var(--color-purple-900)}.dark\:bg-purple-950:is(.dark *){background-color:var(--color-purple-950)}.dark\:bg-red-900:is(.dark *){background-color:var(--color-red-900)}.dark\:bg-red-950:is(.dark *){background-color:var(--color-red-950)}.dark\:bg-white:is(.dark *){background-color:var(--color-white)}.dark\:bg-zinc-800:is(.dark *){background-color:var(--color-zinc-800)}.dark\:bg-zinc-900:is(.dark *){background-color:var(--color-zinc-900)}.dark\:bg-zinc-900\!:is(.dark *){background-color:var(--color-zinc-900)!important}.dark\:stroke-slate-600:is(.dark *){stroke:var(--color-slate-600)}.dark\:text-background:is(.dark *){color:var(--background)}.dark\:text-black:is(.dark *){color:var(--color-black)}.dark\:text-cyan-300:is(.dark *){color:var(--color-cyan-300)}.dark\:text-foreground:is(.dark *){color:var(--foreground)}.dark\:text-gray-300:is(.dark *){color:var(--color-gray-300)}.dark\:text-green-300:is(.dark *){color:var(--color-green-300)}.dark\:text-muted-foreground:is(.dark *){color:var(--muted-foreground)}.dark\:text-neutral-300:is(.dark *){color:var(--color-neutral-300)}.dark\:text-neutral-400:is(.dark *){color:var(--color-neutral-400)}.dark\:text-orange-300:is(.dark *){color:var(--color-orange-300)}.dark\:text-pink-300:is(.dark *){color:var(--color-pink-300)}.dark\:text-purple-300:is(.dark *){color:var(--color-purple-300)}.dark\:text-purple-400:is(.dark *){color:var(--color-purple-400)}.dark\:text-red-300:is(.dark *){color:var(--color-red-300)}.dark\:text-red-400:is(.dark *){color:var(--color-red-400)}.dark\:text-white:is(.dark *){color:var(--color-white)}.dark\:shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(26\,_29\,_30\)_0px_1px_5px_0px_inset\,_rgb\(76\,_81\,_85\)_0px_0px_0px_0\.5px\,_rgb\(76\,_81\,_85\)_0px_2px_1px_-1px\,_rgb\(76\,_81\,_85\)_0px_1px_0px_0px\]:is(.dark *){--tw-shadow-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.dark\:shadow-\[rgba\(255\,_255\,_255\,_0\.1\)_0px_0\.5px_0px_0px_inset\,_rgb\(26\,_29\,_30\)_0px_1px_5px_0px_inset\,_rgb\(76\,_81\,_85\)_0px_0px_0px_0\.5px\,_rgb\(76\,_81\,_85\)_0px_2px_1px_-1px\,_rgb\(76\,_81\,_85\)_0px_1px_0px_0px\]:is(.dark *){--tw-shadow-color:color-mix(in oklab,#ffffff1a 0px .5px 0px 0px inset,#1a1d1e 0px 1px 5px 0px inset,#4c5155 0px 0px 0px .5px,#4c5155 0px 2px 1px -1px,#4c5155 0px 1px 0px 0px var(--tw-shadow-alpha),transparent)}}.dark\:ring-white\/5:is(.dark *){--tw-ring-color:#ffffff0d}@supports (color:color-mix(in lab, red, red)){.dark\:ring-white\/5:is(.dark *){--tw-ring-color:color-mix(in oklab,var(--color-white)5%,transparent)}}@media (hover:hover){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:var(--accent)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-accent\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--accent)50%,transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input)50%,transparent)}}.dark\:hover\:bg-transparent:is(.dark *):hover{background-color:#0000}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab,var(--destructive)40%,transparent)}}.dark\:data-\[state\=active\]\:border-input:is(.dark *)[data-state=active]{border-color:var(--input)}.dark\:data-\[state\=active\]\:bg-input\/30:is(.dark *)[data-state=active]{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:data-\[state\=active\]\:bg-input\/30:is(.dark *)[data-state=active]{background-color:color-mix(in oklab,var(--input)30%,transparent)}}.dark\:data-\[state\=active\]\:text-foreground:is(.dark *)[data-state=active]{color:var(--foreground)}.dark\:data-\[state\=checked\]\:bg-primary:is(.dark *)[data-state=checked]{background-color:var(--primary)}.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:data-\[variant\=destructive\]\:focus\:bg-destructive\/20:is(.dark *)[data-variant=destructive]:focus{background-color:color-mix(in oklab,var(--destructive)20%,transparent)}}@media (min-width:48rem){.md\:dark\:hidden:is(.dark *){display:none}}@media print{.print\:hidden{display:none}.print\:break-inside-avoid{break-inside:avoid}.print\:placeholder\:text-transparent::placeholder{color:#0000}}@media (min-width:1600px){.\33 xl\:fixed\:container:is(.layout-fixed *){width:100%}@media (min-width:1600px){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:1600px}}@media (min-width:2000px){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:2000px}}@media (min-width:40rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:40rem}}@media (min-width:48rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:48rem}}@media (min-width:64rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:64rem}}@media (min-width:80rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:80rem}}@media (min-width:96rem){.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:96rem}}.\33 xl\:fixed\:container:is(.layout-fixed *){max-width:calc(var(--breakpoint-xl) + 2rem);padding-inline:calc(var(--spacing)*4);margin-inline:auto}@media (min-width:80rem){.\33 xl\:fixed\:container:is(.layout-fixed *){padding-inline:calc(var(--spacing)*6)}}.\33 xl\:fixed\:px-0:is(.layout-fixed *){padding-inline:calc(var(--spacing)*0)}.\33 xl\:fixed\:px-3:is(.layout-fixed *){padding-inline:calc(var(--spacing)*3)}}.\[\&_\*\:\:selection\]\:\!bg-transparent ::selection{background-color:#0000!important}.\[\&_\*\:\:selection\]\:bg-none ::selection{background-image:none}.\[\&_\.katex-display\]\:my-0 .katex-display{margin-block:calc(var(--spacing)*0)}.\[\&_\.katex-display\]\:my-0\! .katex-display{margin-block:calc(var(--spacing)*0)!important}.\[\&_\.line\:before\]\:sticky .line:before{position:sticky}.\[\&_\.line\:before\]\:left-2 .line:before{left:calc(var(--spacing)*2)}.\[\&_\.line\:before\]\:z-10 .line:before{z-index:10}.\[\&_\.line\:before\]\:-translate-y-px .line:before{--tw-translate-y:-1px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&_\.line\:before\]\:pr-1 .line:before{padding-right:calc(var(--spacing)*1)}.\[\&_\.react-tweet-theme\]\:my-0 .react-tweet-theme{margin-block:calc(var(--spacing)*0)}.\[\&_\.react-tweet-theme\]\:ring-2 .react-tweet-theme{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.\[\&_\.react-tweet-theme\]\:ring-ring .react-tweet-theme{--tw-ring-color:var(--ring)}.\[\&_\.react-tweet-theme\]\:ring-offset-2 .react-tweet-theme{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.\[\&_\.slate-selected\]\:\!bg-primary\/20 .slate-selected{background-color:var(--primary)!important}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selected\]\:\!bg-primary\/20 .slate-selected{background-color:color-mix(in oklab,var(--primary)20%,transparent)!important}}.\[\&_\.slate-selection-area\]\:z-50 .slate-selection-area{z-index:50}.\[\&_\.slate-selection-area\]\:border .slate-selection-area{border-style:var(--tw-border-style);border-width:1px}.\[\&_\.slate-selection-area\]\:border-brand\/25 .slate-selection-area{border-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:border-brand\/25 .slate-selection-area{border-color:color-mix(in oklab,var(--brand)25%,transparent)}}.\[\&_\.slate-selection-area\]\:border-primary .slate-selection-area{border-color:var(--primary)}.\[\&_\.slate-selection-area\]\:bg-brand\/15 .slate-selection-area{background-color:var(--brand)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:bg-brand\/15 .slate-selection-area{background-color:color-mix(in oklab,var(--brand)15%,transparent)}}.\[\&_\.slate-selection-area\]\:bg-primary\/10 .slate-selection-area{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.\[\&_\.slate-selection-area\]\:bg-primary\/10 .slate-selection-area{background-color:color-mix(in oklab,var(--primary)10%,transparent)}}.\[\&_\>_\.lty-playbtn\]\:absolute>.lty-playbtn{position:absolute}.\[\&_\>_\.lty-playbtn\]\:top-1\/2>.lty-playbtn{top:50%}.\[\&_\>_\.lty-playbtn\]\:left-1\/2>.lty-playbtn{left:50%}.\[\&_\>_\.lty-playbtn\]\:z-1>.lty-playbtn{z-index:1}.\[\&_\>_\.lty-playbtn\]\:h-\[46px\]>.lty-playbtn{height:46px}.\[\&_\>_\.lty-playbtn\]\:w-\[70px\]>.lty-playbtn{width:70px}.\[\&_\>_\.lty-playbtn\]\:\[transform\:translate3d\(-50\%\,-50\%\,0\)\]>.lty-playbtn{transform:translate(-50%,-50%)}.\[\&_\>_\.lty-playbtn\]\:rounded-\[14\%\]>.lty-playbtn{border-radius:14%}.\[\&_\>_\.lty-playbtn\]\:bg-\[\#212121\]>.lty-playbtn{background-color:#212121}.\[\&_\>_\.lty-playbtn\]\:opacity-80>.lty-playbtn{opacity:.8}.\[\&_\>_\.lty-playbtn\]\:\[transition\:all_0\.2s_cubic-bezier\(0\,_0\,_0\.2\,_1\)\]>.lty-playbtn{transition:all .2s cubic-bezier(0,0,.2,1)}.\[\&_\>_\.lty-playbtn\]\:before\:absolute>.lty-playbtn:before{content:var(--tw-content);position:absolute}.\[\&_\>_\.lty-playbtn\]\:before\:top-1\/2>.lty-playbtn:before{content:var(--tw-content);top:50%}.\[\&_\>_\.lty-playbtn\]\:before\:left-1\/2>.lty-playbtn:before{content:var(--tw-content);left:50%}.\[\&_\>_\.lty-playbtn\]\:before\:\[transform\:translate3d\(-50\%\,-50\%\,0\)\]>.lty-playbtn:before{content:var(--tw-content);transform:translate(-50%,-50%)}.\[\&_\>_\.lty-playbtn\]\:before\:border-y-\[11px\]>.lty-playbtn:before{content:var(--tw-content);border-block-style:var(--tw-border-style);border-block-width:11px}.\[\&_\>_\.lty-playbtn\]\:before\:border-r-0>.lty-playbtn:before{content:var(--tw-content);border-right-style:var(--tw-border-style);border-right-width:0}.\[\&_\>_\.lty-playbtn\]\:before\:border-l-\[19px\]>.lty-playbtn:before{content:var(--tw-content);border-left-style:var(--tw-border-style);border-left-width:19px}.\[\&_\>_\.lty-playbtn\]\:before\:border-\[transparent_transparent_transparent_\#fff\]>.lty-playbtn:before{content:var(--tw-content);border-color:#0000 #0000 #0000 #fff}.\[\&_\>_\.lty-playbtn\]\:before\:content-\[\"\"\]>.lty-playbtn:before{content:var(--tw-content);--tw-content:"";content:var(--tw-content)}.\[\&_\>_\.lty-playbtn\]\:before\:content-\[\\\"\\\"\]>.lty-playbtn:before{content:var(--tw-content);--tw-content:\"\";content:var(--tw-content)}.\[\&_\>_iframe\]\:absolute>iframe{position:absolute}.\[\&_\>_iframe\]\:top-0>iframe{top:calc(var(--spacing)*0)}.\[\&_\>_iframe\]\:left-0>iframe{left:calc(var(--spacing)*0)}.\[\&_\>_iframe\]\:size-full>iframe{width:100%;height:100%}.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading]{padding-block:calc(var(--spacing)*1.5)}.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading]{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.\[\&_\[cmdk-group-heading\]\]\:text-muted-foreground [cmdk-group-heading]{color:var(--muted-foreground)}.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden])~[cmdk-group]{padding-top:calc(var(--spacing)*0)}.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg{height:calc(var(--spacing)*5)}.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg{width:calc(var(--spacing)*5)}.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input]{height:calc(var(--spacing)*12)}.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item]{padding-inline:calc(var(--spacing)*2)}.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item]{padding-block:calc(var(--spacing)*3)}.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg{height:calc(var(--spacing)*5)}.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg{width:calc(var(--spacing)*5)}.\[\&_button\]\:hidden button{display:none}.\[\&_code\]\:text-sm code{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\[\&_h3\.font-heading\]\:text-base h3.font-heading{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.\[\&_h3\.font-heading\]\:font-semibold h3.font-heading{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.\[\&_p\]\:leading-relaxed p{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_pre\]\:my-0 pre{margin-block:calc(var(--spacing)*0)}.\[\&_pre\]\:h-\(--height\) pre{height:var(--height)}.\[\&_pre\]\:max-h-\[350px\] pre{max-height:350px}.\[\&_pre\]\:max-h-\[650px\] pre{max-height:650px}.\[\&_pre\]\:overflow-auto pre{overflow:auto}.\[\&_pre\]\:overflow-hidden pre{overflow:hidden}.\[\&_pre\]\:bg-transparent\! pre{background-color:#0000!important}.\[\&_pre\]\:pt-4 pre{padding-top:calc(var(--spacing)*4)}.\[\&_pre\]\:pb-20 pre{padding-bottom:calc(var(--spacing)*20)}.\[\&_pre\]\:pb-\[100px\] pre{padding-bottom:100px}.\[\&_pre\]\:font-mono pre{font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.\[\&_pre\]\:text-sm pre{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.\[\&_pre\]\:leading-relaxed pre{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.\[\&_strong\]\:font-bold strong{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:\!size-3 svg{width:calc(var(--spacing)*3)!important;height:calc(var(--spacing)*3)!important}.\[\&_svg\]\:size-3\.5 svg{width:calc(var(--spacing)*3.5);height:calc(var(--spacing)*3.5)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\]\:size-6 svg{width:calc(var(--spacing)*6);height:calc(var(--spacing)*6)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\]\:text-muted-foreground svg{color:var(--muted-foreground)}@media (hover:hover){.\[\&_svg\]\:hover\:text-muted-foreground svg:hover{color:var(--muted-foreground)}}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:var(--muted-foreground)}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&_ul\]\:list-\[circle\] ul{list-style-type:circle}.\[\&_ul_ul\]\:list-\[square\] ul ul{list-style-type:square}.\[\&\.lyt-activated\]\:cursor-\[unset\].lyt-activated{cursor:unset}.\[\&\.lyt-activated\]\:before\:pointer-events-none.lyt-activated:before{content:var(--tw-content);pointer-events:none}.\[\&\.lyt-activated\]\:before\:absolute.lyt-activated:before{content:var(--tw-content);position:absolute}.\[\&\.lyt-activated\]\:before\:top-0.lyt-activated:before{content:var(--tw-content);top:calc(var(--spacing)*0)}.\[\&\.lyt-activated\]\:before\:h-\[60px\].lyt-activated:before{content:var(--tw-content);height:60px}.\[\&\.lyt-activated\]\:before\:w-full.lyt-activated:before{content:var(--tw-content);width:100%}.\[\&\.lyt-activated\]\:before\:bg-\[url\(data\:image\/png\;base64\,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT\+OqFAAAAdklEQVQoz42QQQ7AIAgEF\/T\/D\+kbq\/RWAlnQyyazA4aoAB4FsBSA\/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg\=\=\)\].lyt-activated:before{content:var(--tw-content);background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==)}.\[\&\.lyt-activated\]\:before\:bg-top.lyt-activated:before{content:var(--tw-content);background-position:top}.\[\&\.lyt-activated\]\:before\:bg-repeat-x.lyt-activated:before{content:var(--tw-content);background-repeat:repeat-x}.\[\&\.lyt-activated\]\:before\:pb-\[50px\].lyt-activated:before{content:var(--tw-content);padding-bottom:50px}.\[\&\.lyt-activated\]\:before\:opacity-0.lyt-activated:before{content:var(--tw-content);opacity:0}.\[\&\.lyt-activated\]\:before\:\[transition\:all_0\.2s_cubic-bezier\(0\,_0\,_0\.2\,_1\)\].lyt-activated:before{content:var(--tw-content);transition:all .2s cubic-bezier(0,0,.2,1)}.\[\&\.lyt-activated_\>_\.lty-playbtn\]\:pointer-events-none.lyt-activated>.lty-playbtn{pointer-events:none}.\[\&\.lyt-activated_\>_\.lty-playbtn\]\:opacity-0\!.lyt-activated>.lty-playbtn{opacity:0!important}.\[\&\:\:-webkit-scrollbar\]\:w-4::-webkit-scrollbar{width:calc(var(--spacing)*4)}.\[\&\:\:-webkit-scrollbar-button\]\:hidden::-webkit-scrollbar-button{display:none}.\[\&\:\:-webkit-scrollbar-button\]\:size-0::-webkit-scrollbar-button{width:calc(var(--spacing)*0);height:calc(var(--spacing)*0)}.\[\&\:\:-webkit-scrollbar-thumb\]\:min-h-11::-webkit-scrollbar-thumb{min-height:calc(var(--spacing)*11)}.\[\&\:\:-webkit-scrollbar-thumb\]\:rounded-full::-webkit-scrollbar-thumb{border-radius:3.40282e38px}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-4::-webkit-scrollbar-thumb{border-style:var(--tw-border-style);border-width:4px}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-solid::-webkit-scrollbar-thumb{--tw-border-style:solid;border-style:solid}.\[\&\:\:-webkit-scrollbar-thumb\]\:border-popover::-webkit-scrollbar-thumb{border-color:var(--popover)}.\[\&\:\:-webkit-scrollbar-thumb\]\:bg-muted::-webkit-scrollbar-thumb{background-color:var(--muted)}.\[\&\:\:-webkit-scrollbar-thumb\]\:bg-clip-padding::-webkit-scrollbar-thumb{background-clip:padding-box}@media (hover:hover){.\[\&\:\:-webkit-scrollbar-thumb\]\:hover\:bg-muted-foreground\/25::-webkit-scrollbar-thumb:hover{background-color:var(--muted-foreground)}@supports (color:color-mix(in lab, red, red)){.\[\&\:\:-webkit-scrollbar-thumb\]\:hover\:bg-muted-foreground\/25::-webkit-scrollbar-thumb:hover{background-color:color-mix(in oklab,var(--muted-foreground)25%,transparent)}}}.focus\:\[\&\:\:placeholder\]\:opacity-0:focus::placeholder{opacity:0}.\[\&\:has\(\>\.day-range-end\)\]\:rounded-r-md:has(>.day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\>\.day-range-start\)\]\:rounded-l-md:has(>.day-range-start){border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:rounded-md:has([aria-selected]){border-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[aria-selected\]\)\]\:bg-accent:has([aria-selected]){background-color:var(--accent)}.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:first-child:has([aria-selected]){border-top-left-radius:calc(var(--radius) - 2px);border-bottom-left-radius:calc(var(--radius) - 2px)}.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:last-child:has([aria-selected]),.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end){border-top-right-radius:calc(var(--radius) - 2px);border-bottom-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:calc(var(--spacing)*0)}.\[\&\:has\(\[role\=option\]\)\]\:block:has([role=option]){display:block}.\[\&\:hover_\>_\.lty-playbtn\]\:bg-\[red\]:hover>.lty-playbtn{background-color:red}.\[\&\:hover_\>_\.lty-playbtn\]\:opacity-100:hover>.lty-playbtn{opacity:1}.\[\.border-b\]\:pb-6.border-b{padding-bottom:calc(var(--spacing)*6)}.\[\.border-t\]\:pt-6.border-t{padding-top:calc(var(--spacing)*6)}:is(.\*\*\:\[\.hljs-addition\]\:bg-\[\#f0fff4\] *).hljs-addition{background-color:#f0fff4}:is(.\*\*\:\[\.hljs-addition\]\:text-\[\#22863a\] *).hljs-addition{color:#22863a}:is(.dark\:\*\*\:\[\.hljs-addition\]\:bg-\[\#3c5743\]:is(.dark *) *).hljs-addition{background-color:#3c5743}:is(.dark\:\*\*\:\[\.hljs-addition\]\:text-\[\#ceead5\]:is(.dark *) *).hljs-addition{color:#ceead5}:is(.\*\*\:\[\.hljs-attr\,\.hljs-attribute\,\.hljs-literal\,\.hljs-meta\,\.hljs-number\,\.hljs-operator\,\.hljs-selector-attr\,\.hljs-selector-class\,\.hljs-selector-id\,\.hljs-variable\]\:text-\[\#005cc5\] *):is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable){color:#005cc5}:is(.dark\:\*\*\:\[\.hljs-attr\,\.hljs-attribute\,\.hljs-literal\,\.hljs-meta\,\.hljs-number\,\.hljs-operator\,\.hljs-selector-attr\,\.hljs-selector-class\,\.hljs-selector-id\,\.hljs-variable\]\:text-\[\#6596cf\]:is(.dark *) *):is(.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable){color:#6596cf}:is(.\*\*\:\[\.hljs-built\\\\\\\\\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#e36209\] *):is(.hljs-built\\\\\\_in,.hljs-symbol){color:#e36209}:is(.dark\:\*\*\:\[\.hljs-built\\\\\\\\\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#c3854e\]:is(.dark *) *):is(.hljs-built\\\\\\_in,.hljs-symbol){color:#c3854e}:is(.\*\*\:\[\.hljs-built\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#e36209\] *):is(.hljs-built\\_in,.hljs-symbol){color:#e36209}:is(.dark\:\*\*\:\[\.hljs-built\\\\\\\\_in\,\.hljs-symbol\]\:text-\[\#c3854e\]:is(.dark *) *):is(.hljs-built\\_in,.hljs-symbol){color:#c3854e}:is(.\*\*\:\[\.hljs-bullet\]\:text-\[\#735c0f\] *).hljs-bullet{color:#735c0f}:is(.\*\*\:\[\.hljs-comment\,\.hljs-code\,\.hljs-formula\]\:text-\[\#6a737d\] *):is(.hljs-comment,.hljs-code,.hljs-formula),:is(.dark\:\*\*\:\[\.hljs-comment\,\.hljs-code\,\.hljs-formula\]\:text-\[\#6a737d\]:is(.dark *) *):is(.hljs-comment,.hljs-code,.hljs-formula){color:#6a737d}:is(.\*\*\:\[\.hljs-deletion\]\:bg-\[\#ffeef0\] *).hljs-deletion{background-color:#ffeef0}:is(.\*\*\:\[\.hljs-deletion\]\:text-\[\#b31d28\] *).hljs-deletion{color:#b31d28}:is(.dark\:\*\*\:\[\.hljs-deletion\]\:bg-\[\#473235\]:is(.dark *) *).hljs-deletion{background-color:#473235}:is(.dark\:\*\*\:\[\.hljs-deletion\]\:text-\[\#e7c7cb\]:is(.dark *) *).hljs-deletion{color:#e7c7cb}:is(.\*\*\:\[\.hljs-emphasis\]\:italic *).hljs-emphasis{font-style:italic}:is(.\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\\\\\\\\\_\]\:text-\[\#d73a49\] *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\\\_){color:#d73a49}:is(.dark\:\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\\\\\\\\\_\]\:text-\[\#ee6960\]:is(.dark *) *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\\\_){color:#ee6960}:is(.\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\_\]\:text-\[\#d73a49\] *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\_){color:#d73a49}:is(.dark\:\*\*\:\[\.hljs-keyword\,\.hljs-doctag\,\.hljs-template-tag\,\.hljs-template-variable\,\.hljs-type\,\.hljs-variable\.language\\\\\\\\_\]\:text-\[\#ee6960\]:is(.dark *) *):is(.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\_){color:#ee6960}:is(.\*\*\:\[\.hljs-name\,\.hljs-quote\,\.hljs-selector-tag\,\.hljs-selector-pseudo\]\:text-\[\#22863a\] *):is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:#22863a}:is(.dark\:\*\*\:\[\.hljs-name\,\.hljs-quote\,\.hljs-selector-tag\,\.hljs-selector-pseudo\]\:text-\[\#36a84f\]:is(.dark *) *):is(.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo){color:#36a84f}:is(.\*\*\:\[\.hljs-regexp\,\.hljs-string\,\.hljs-meta_\.hljs-string\]\:text-\[\#032f62\] *):is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:#032f62}:is(.dark\:\*\*\:\[\.hljs-regexp\,\.hljs-string\,\.hljs-meta_\.hljs-string\]\:text-\[\#3593ff\]:is(.dark *) *):is(.hljs-regexp,.hljs-string,.hljs-meta .hljs-string){color:#3593ff}:is(.\*\*\:\[\.hljs-section\]\:font-bold *).hljs-section{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(.\*\*\:\[\.hljs-section\]\:text-\[\#005cc5\] *).hljs-section{color:#005cc5}:is(.dark\:\*\*\:\[\.hljs-section\]\:text-\[\#61a5f2\]:is(.dark *) *).hljs-section{color:#61a5f2}:is(.\*\*\:\[\.hljs-strong\]\:font-bold *).hljs-strong{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}:is(.\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\.inherited\\\\\\\\\\\\\\\\_\\\\\\\\\\\\\\\\_\,\.hljs-title\.function\\\\\\\\\\\\\\\\_\]\:text-\[\#6f42c1\] *):is(.hljs-title,.hljs-title.class\\\\\\_,.hljs-title.class\\\\\\_.inherited\\\\\\_\\\\\\_,.hljs-title.function\\\\\\_){color:#6f42c1}:is(.dark\:\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\,\.hljs-title\.class\\\\\\\\\\\\\\\\_\.inherited\\\\\\\\\\\\\\\\_\\\\\\\\\\\\\\\\_\,\.hljs-title\.function\\\\\\\\\\\\\\\\_\]\:text-\[\#a77bfa\]:is(.dark *) *):is(.hljs-title,.hljs-title.class\\\\\\_,.hljs-title.class\\\\\\_.inherited\\\\\\_\\\\\\_,.hljs-title.function\\\\\\_){color:#a77bfa}:is(.\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\_\,\.hljs-title\.class\\\\\\\\_\.inherited\\\\\\\\_\\\\\\\\_\,\.hljs-title\.function\\\\\\\\_\]\:text-\[\#6f42c1\] *):is(.hljs-title,.hljs-title.class\\_,.hljs-title.class\\_.inherited\\_\\_,.hljs-title.function\\_){color:#6f42c1}:is(.dark\:\*\*\:\[\.hljs-title\,\.hljs-title\.class\\\\\\\\_\,\.hljs-title\.class\\\\\\\\_\.inherited\\\\\\\\_\\\\\\\\_\,\.hljs-title\.function\\\\\\\\_\]\:text-\[\#a77bfa\]:is(.dark *) *):is(.hljs-title,.hljs-title.class\\_,.hljs-title.class\\_.inherited\\_\\_,.hljs-title.function\\_){color:#a77bfa}@media (hover:hover){:is(.\*\*\:\[\[data-slot\=\"mdx-link\"\]\]\:hover\:after\:bottom-0 *)[data-slot=mdx-link]:hover:after{content:var(--tw-content);bottom:calc(var(--spacing)*0)}}:is(.\*\:\[code\]\:bg-inherit>*):is(code){background-color:inherit}:is(.\*\:\[h3\,h4\]\:mt-8>*):is(h3,h4){margin-top:calc(var(--spacing)*8)}:is(.\*\:\[h3\,h4\]\:mb-4>*):is(h3,h4){margin-bottom:calc(var(--spacing)*4)}:is(.\*\:\[h3\,h4\]\:text-base>*):is(h3,h4){font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}:is(.\*\:\[h3\,h4\]\:font-semibold>*):is(h3,h4){--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}:is(.\*\:\[h3\,h4\]\:\[counter-increment\:step\]>*):is(h3,h4){counter-increment:step}:is(.\*\:\[h3\,h4\]\:before\:absolute>*):is(h3,h4):before{content:var(--tw-content);position:absolute}:is(.\*\:\[h3\,h4\]\:before\:mt-\[-4px\]>*):is(h3,h4):before{content:var(--tw-content);margin-top:-4px}:is(.\*\:\[h3\,h4\]\:before\:ml-\[-50px\]>*):is(h3,h4):before{content:var(--tw-content);margin-left:-50px}:is(.\*\:\[h3\,h4\]\:before\:inline-flex>*):is(h3,h4):before{content:var(--tw-content);display:inline-flex}:is(.\*\:\[h3\,h4\]\:before\:h-9>*):is(h3,h4):before{content:var(--tw-content);height:calc(var(--spacing)*9)}:is(.\*\:\[h3\,h4\]\:before\:w-9>*):is(h3,h4):before{content:var(--tw-content);width:calc(var(--spacing)*9)}:is(.\*\:\[h3\,h4\]\:before\:items-center>*):is(h3,h4):before{content:var(--tw-content);align-items:center}:is(.\*\:\[h3\,h4\]\:before\:justify-center>*):is(h3,h4):before{content:var(--tw-content);justify-content:center}:is(.\*\:\[h3\,h4\]\:before\:rounded-full>*):is(h3,h4):before{content:var(--tw-content);border-radius:3.40282e38px}:is(.\*\:\[h3\,h4\]\:before\:border-4>*):is(h3,h4):before{content:var(--tw-content);border-style:var(--tw-border-style);border-width:4px}:is(.\*\:\[h3\,h4\]\:before\:border-background>*):is(h3,h4):before{content:var(--tw-content);border-color:var(--background)}:is(.\*\:\[h3\,h4\]\:before\:bg-muted>*):is(h3,h4):before{content:var(--tw-content);background-color:var(--muted)}:is(.\*\:\[h3\,h4\]\:before\:text-center>*):is(h3,h4):before{content:var(--tw-content);text-align:center}:is(.\*\:\[h3\,h4\]\:before\:-indent-px>*):is(h3,h4):before{content:var(--tw-content);text-indent:-1px}:is(.\*\:\[h3\,h4\]\:before\:font-mono>*):is(h3,h4):before{content:var(--tw-content);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}:is(.\*\:\[h3\,h4\]\:before\:text-base>*):is(h3,h4):before{content:var(--tw-content);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}:is(.\*\:\[h3\,h4\]\:before\:font-medium>*):is(h3,h4):before{content:var(--tw-content);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}:is(.\*\:\[h3\,h4\]\:before\:\[content\:counter\(step\)\]>*):is(h3,h4):before{content:var(--tw-content);content:counter(step)}:is(.\*\*\:\[p\]\:m-0 *):is(p){margin:calc(var(--spacing)*0)}:is(.\*\*\:\[p\,ul\,ol\,li\]\:first\:my-0 *):is(p,ul,ol,li):first-child{margin-block:calc(var(--spacing)*0)}:is(.\*\:first\:\[span\]\:hidden>*):first-child:is(span){display:none}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing)*2)}:is(.\*\:\[svg\]\:text-muted-foreground>*):is(svg){color:var(--muted-foreground)}:is(.\*\:\[svg\]\:text-neutral-50>*):is(svg){color:var(--color-neutral-50)}:is(.data-\[variant\=destructive\]\:\*\:\[svg\]\:\!text-destructive[data-variant=destructive]>*):is(svg){color:var(--destructive)!important}:is(.dark\:\*\:\[svg\]\:text-neutral-900:is(.dark *)>*):is(svg){color:var(--color-neutral-900)}:is(.\*\:\[ul\]\:my-0>*):is(ul){margin-block:calc(var(--spacing)*0)}.\[\&\>\[role\=checkbox\]\]\:translate-y-\[2px\]>[role=checkbox]{--tw-translate-y:2px;translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>button\]\:hidden>button{display:none}.\[\&\>span\:last-child\]\:truncate>span:last-child{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.\[\&\>svg\]\:pointer-events-none>svg{pointer-events:none}.\[\&\>svg\]\:size-3>svg{width:calc(var(--spacing)*3);height:calc(var(--spacing)*3)}.\[\&\>svg\]\:size-4>svg{width:calc(var(--spacing)*4);height:calc(var(--spacing)*4)}.\[\&\>svg\]\:shrink-0>svg{flex-shrink:0}.\[\&\>svg\]\:translate-y-0\.5>svg{--tw-translate-y:calc(var(--spacing)*.5);translate:var(--tw-translate-x)var(--tw-translate-y)}.\[\&\>svg\]\:text-current>svg{color:currentColor}.\[\&\>svg\]\:text-sidebar-accent-foreground>svg{color:var(--sidebar-accent-foreground)}.\[\&\>tr\]\:last\:border-b-0>tr:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.\[\&\[align\=center\]\]\:text-center[align=center]{text-align:center}.\[\&\[align\=right\]\]\:text-right[align=right]{text-align:right}.\[\&\[data-panel-group-direction\=vertical\]\>div\]\:rotate-90[data-panel-group-direction=vertical]>div,.\[\&\[data-state\=open\]\>button\>svg\:first-child\]\:rotate-90[data-state=open]>button>svg:first-child{rotate:90deg}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{rotate:180deg}@media (min-width:1800px){.\[\@media\(width\>\=1800px\)\]\:max-w-\(--breakpoint-2xl\){max-width:var(--breakpoint-2xl)}.\[\@media\(width\>\=1800px\)\]\:border-x{border-inline-style:var(--tw-border-style);border-inline-width:1px}}[data-side=left][data-collapsible=offcanvas] .\[\[data-side\=left\]\[data-collapsible\=offcanvas\]_\&\]\:-right-2{right:calc(var(--spacing)*-2)}[data-side=left][data-state=collapsed] .\[\[data-side\=left\]\[data-state\=collapsed\]_\&\]\:cursor-e-resize{cursor:e-resize}[data-side=right][data-collapsible=offcanvas] .\[\[data-side\=right\]\[data-collapsible\=offcanvas\]_\&\]\:-left-2{left:calc(var(--spacing)*-2)}[data-side=right][data-state=collapsed] .\[\[data-side\=right\]\[data-state\=collapsed\]_\&\]\:cursor-w-resize{cursor:w-resize}@media (hover:hover){a.\[a\&\]\:hover\:bg-accent:hover{background-color:var(--accent)}a.\[a\&\]\:hover\:bg-destructive\/90:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,var(--destructive)90%,transparent)}}a.\[a\&\]\:hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,var(--primary)90%,transparent)}}a.\[a\&\]\:hover\:bg-secondary\/90:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){a.\[a\&\]\:hover\:bg-secondary\/90:hover{background-color:color-mix(in oklab,var(--secondary)90%,transparent)}}a.\[a\&\]\:hover\:text-accent-foreground:hover{color:var(--accent-foreground)}}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}.prose{max-width:65ch;color:var(--foreground)}.prose h1:not(.not-prose h1){scroll-margin:calc(var(--spacing)*20);font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height));--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}@media (min-width:64rem){.prose h1:not(.not-prose h1){font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}}.prose h2:not(.not-prose h2){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);border-bottom-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*2);font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight);border-bottom-width:1px}.prose h2:not(.not-prose h2):first-child{margin-top:calc(var(--spacing)*0)}.prose h3:not(.not-prose h3){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h3:not(.not-prose h3):first-child{margin-top:calc(var(--spacing)*0)}.prose h4:not(.not-prose h4){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h4:not(.not-prose h4):first-child{margin-top:calc(var(--spacing)*0)}.prose h5:not(.not-prose h5){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h5:not(.not-prose h5):first-child{margin-top:calc(var(--spacing)*0)}.prose h6:not(.not-prose h6){margin-top:calc(var(--spacing)*12);scroll-margin:calc(var(--spacing)*20);font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose h6:not(.not-prose h6):first-child,:is(.prose h1:not(.not-prose h1),.prose h2:not(.not-prose h2),.prose h3:not(.not-prose h3),.prose h4:not(.not-prose h4),.prose h5:not(.not-prose h5),.prose h6:not(.not-prose h6))+p{margin-top:calc(var(--spacing)*0)}.prose p:not(.not-prose p){--tw-leading:calc(var(--spacing)*7);line-height:calc(var(--spacing)*7)}.prose p:not(.not-prose p):not(:first-child){margin-top:calc(var(--spacing)*6)}.prose a:not(.not-prose a){--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--primary);text-underline-offset:4px;text-decoration-line:underline}.prose blockquote:not(.not-prose blockquote){margin-top:calc(var(--spacing)*6);border-left-style:var(--tw-border-style);padding-left:calc(var(--spacing)*6);border-left-width:2px;font-style:italic}.prose table:not(.not-prose table){margin-block:calc(var(--spacing)*6);width:100%;overflow-y:auto}.prose table:not(.not-prose table) thead tr{margin:calc(var(--spacing)*0);border-top-style:var(--tw-border-style);padding:calc(var(--spacing)*0);border-top-width:1px}.prose table:not(.not-prose table) thead tr:nth-child(2n){background-color:var(--muted)}.prose table:not(.not-prose table) th{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:left;--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold);border-width:1px}.prose table:not(.not-prose table) th[align=center]{text-align:center}.prose table:not(.not-prose table) th[align=right]{text-align:right}.prose table:not(.not-prose table) tbody tr{margin:calc(var(--spacing)*0);border-top-style:var(--tw-border-style);padding:calc(var(--spacing)*0);border-top-width:1px}.prose table:not(.not-prose table) tbody tr:nth-child(2n){background-color:var(--muted)}.prose table:not(.not-prose table) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*4);padding-block:calc(var(--spacing)*2);text-align:left;border-width:1px}.prose table:not(.not-prose table) td[align=center]{text-align:center}.prose table:not(.not-prose table) td[align=right]{text-align:right}.prose ul:not(.not-prose ul){margin-block:calc(var(--spacing)*6);margin-left:calc(var(--spacing)*6);list-style-type:disc}.prose ul:not(.not-prose ul)>li{margin-top:calc(var(--spacing)*2)}.prose ul:not(.not-prose ul) p{margin-block:calc(var(--spacing)*0);display:inline}.prose ol:not(.not-prose ol){margin-block:calc(var(--spacing)*6);margin-left:calc(var(--spacing)*6);list-style-type:decimal}.prose ol:not(.not-prose ol)>li{margin-top:calc(var(--spacing)*2)}.prose ol:not(.not-prose ol) p{margin-block:calc(var(--spacing)*0);display:inline}.prose pre:not(.not-prose pre){margin-block:calc(var(--spacing)*6);border-radius:calc(var(--radius) - 2px);background-color:var(--muted);padding:calc(var(--spacing)*4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));overflow-y:auto}.prose code:not(pre code):not(.not-prose code){background-color:var(--muted);font-family:"var(--font-mono)",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);border-radius:.25rem;padding-block:.2rem;padding-inline:.3rem;position:relative}.prose .lead:not(.not-prose .lead){font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height));color:var(--muted-foreground)}.prose .large:not(.not-prose .large){font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.prose .small:not(.not-prose .small){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;--tw-font-weight:var(--font-weight-medium);line-height:1;font-weight:var(--font-weight-medium)}.prose .muted:not(.not-prose .muted){font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));color:var(--muted-foreground)}.prose img:not(.not-prose img),.prose picture:not(.not-prose picture),.prose video:not(.not-prose video){margin-block:calc(var(--spacing)*6)}.prose picture>img:not(.not-prose picture>img){margin-block:calc(var(--spacing)*0)}.prose kbd:not(.not-prose kbd){border-radius:calc(var(--radius) - 2px);background-color:var(--muted);padding-inline:calc(var(--spacing)*1.5);padding-block:calc(var(--spacing)*.5);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.prose hr{margin-block:calc(var(--spacing)*10)}.prose dl:not(.not-prose dl){margin-block:calc(var(--spacing)*6)}.prose dl:not(.not-prose dl) dt{margin-top:calc(var(--spacing)*6);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose dl:not(.not-prose dl) dt:first-child{margin-top:calc(var(--spacing)*0)}.prose details:not(.not-prose details){margin-top:calc(var(--spacing)*6)}.prose details:not(.not-prose details) summary{margin-top:calc(var(--spacing)*6);cursor:pointer;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.prose details:not(.not-prose details) p:first-of-type{margin-top:calc(var(--spacing)*2)}.prose mark:not(.not-prose mark){background-color:var(--color-yellow-300)}.prose small:not(.not-prose small){font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:1;line-height:1}.theme-default .theme-container{--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}.theme-blue .theme-container{--primary:var(--color-blue-600);--primary-foreground:var(--color-blue-50);--ring:var(--color-blue-400);--sidebar-primary:var(--color-blue-600);--sidebar-primary-foreground:var(--color-blue-50);--sidebar-ring:var(--color-blue-400);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}.theme-blue .theme-container:is(.dark *){--primary:var(--color-blue-500);--primary-foreground:var(--color-blue-50);--ring:var(--color-blue-900);--sidebar-primary:var(--color-blue-500);--sidebar-primary-foreground:var(--color-blue-50);--sidebar-ring:var(--color-blue-900)}.theme-green .theme-container{--primary:var(--color-lime-600);--primary-foreground:var(--color-lime-50);--ring:var(--color-lime-400);--chart-1:var(--color-green-300);--chart-2:var(--color-green-500);--chart-3:var(--color-green-600);--chart-4:var(--color-green-700);--chart-5:var(--color-green-800);--sidebar-primary:var(--color-lime-600);--sidebar-primary-foreground:var(--color-lime-50);--sidebar-ring:var(--color-lime-400)}.theme-green .theme-container:is(.dark *){--primary:var(--color-lime-600);--primary-foreground:var(--color-lime-50);--ring:var(--color-lime-900);--sidebar-primary:var(--color-lime-500);--sidebar-primary-foreground:var(--color-lime-50);--sidebar-ring:var(--color-lime-900)}.theme-amber .theme-container{--primary:var(--color-amber-600);--primary-foreground:var(--color-amber-50);--ring:var(--color-amber-400);--chart-1:var(--color-amber-300);--chart-2:var(--color-amber-500);--chart-3:var(--color-amber-600);--chart-4:var(--color-amber-700);--chart-5:var(--color-amber-800);--sidebar-primary:var(--color-amber-600);--sidebar-primary-foreground:var(--color-amber-50);--sidebar-ring:var(--color-amber-400)}.theme-amber .theme-container:is(.dark *){--primary:var(--color-amber-500);--primary-foreground:var(--color-amber-50);--ring:var(--color-amber-900);--sidebar-primary:var(--color-amber-500);--sidebar-primary-foreground:var(--color-amber-50);--sidebar-ring:var(--color-amber-900)}.theme-rose .theme-container{--primary:var(--color-rose-600);--primary-foreground:var(--color-rose-50);--ring:var(--color-rose-400);--chart-1:var(--color-rose-300);--chart-2:var(--color-rose-500);--chart-3:var(--color-rose-600);--chart-4:var(--color-rose-700);--chart-5:var(--color-rose-800);--sidebar-primary:var(--color-rose-600);--sidebar-primary-foreground:var(--color-rose-50);--sidebar-ring:var(--color-rose-400)}.theme-rose .theme-container:is(.dark *){--primary:var(--color-rose-500);--primary-foreground:var(--color-rose-50);--ring:var(--color-rose-900);--sidebar-primary:var(--color-rose-500);--sidebar-primary-foreground:var(--color-rose-50);--sidebar-ring:var(--color-rose-900)}.theme-purple .theme-container{--primary:var(--color-purple-600);--primary-foreground:var(--color-purple-50);--ring:var(--color-purple-400);--chart-1:var(--color-purple-300);--chart-2:var(--color-purple-500);--chart-3:var(--color-purple-600);--chart-4:var(--color-purple-700);--chart-5:var(--color-purple-800);--sidebar-primary:var(--color-purple-600);--sidebar-primary-foreground:var(--color-purple-50);--sidebar-ring:var(--color-purple-400)}.theme-purple .theme-container:is(.dark *){--primary:var(--color-purple-500);--primary-foreground:var(--color-purple-50);--ring:var(--color-purple-900);--sidebar-primary:var(--color-purple-500);--sidebar-primary-foreground:var(--color-purple-50);--sidebar-ring:var(--color-purple-900)}.theme-orange .theme-container{--primary:var(--color-orange-600);--primary-foreground:var(--color-orange-50);--ring:var(--color-orange-400);--chart-1:var(--color-orange-300);--chart-2:var(--color-orange-500);--chart-3:var(--color-orange-600);--chart-4:var(--color-orange-700);--chart-5:var(--color-orange-800);--sidebar-primary:var(--color-orange-600);--sidebar-primary-foreground:var(--color-orange-50);--sidebar-ring:var(--color-orange-400)}.theme-orange .theme-container:is(.dark *){--primary:var(--color-orange-500);--primary-foreground:var(--color-orange-50);--ring:var(--color-orange-900);--sidebar-primary:var(--color-orange-500);--sidebar-primary-foreground:var(--color-orange-50);--sidebar-ring:var(--color-orange-900)}.theme-teal .theme-container{--primary:var(--color-teal-600);--primary-foreground:var(--color-teal-50);--chart-1:var(--color-teal-300);--chart-2:var(--color-teal-500);--chart-3:var(--color-teal-600);--chart-4:var(--color-teal-700);--chart-5:var(--color-teal-800);--sidebar-primary:var(--color-teal-600);--sidebar-primary-foreground:var(--color-teal-50);--sidebar-ring:var(--color-teal-400)}.theme-teal .theme-container:is(.dark *){--primary:var(--color-teal-500);--primary-foreground:var(--color-teal-50);--sidebar-primary:var(--color-teal-500);--sidebar-primary-foreground:var(--color-teal-50);--sidebar-ring:var(--color-teal-900)}.theme-mono .theme-container{--font-sans:var(--font-mono);--primary:var(--color-stone-600);--primary-foreground:var(--color-stone-50);--chart-1:var(--color-stone-300);--chart-2:var(--color-stone-500);--chart-3:var(--color-stone-600);--chart-4:var(--color-stone-700);--chart-5:var(--color-stone-800);--sidebar-primary:var(--color-stone-600);--sidebar-primary-foreground:var(--color-stone-50);--sidebar-ring:var(--color-stone-400)}.theme-mono .theme-container:is(.dark *){--primary:var(--color-stone-500);--primary-foreground:var(--color-stone-50);--sidebar-primary:var(--color-stone-500);--sidebar-primary-foreground:var(--color-stone-50);--sidebar-ring:var(--color-stone-900)}@media (min-width:1024px){.theme-mono .theme-container{--font-sans:var(--font-mono);--radius:.45em;--text-lg:1rem;--text-xl:1.1rem;--text-2xl:1.2rem;--text-3xl:1.3rem;--text-4xl:1.4rem;--text-5xl:1.5rem;--text-6xl:1.6rem;--text-7xl:1.7rem;--text-8xl:1.8rem;--text-base:.85rem;--text-sm:.8rem;--spacing:.222222rem}}.theme-mono .theme-container .rounded-xs,.theme-mono .theme-container .rounded-sm,.theme-mono .theme-container .rounded-md,.theme-mono .theme-container .rounded-lg,.theme-mono .theme-container .rounded-xl{border-radius:0}.theme-mono .theme-container .shadow-xs,.theme-mono .theme-container .shadow-sm,.theme-mono .theme-container .shadow-md,.theme-mono .theme-container .shadow-lg,.theme-mono .theme-container .shadow-xl{box-shadow:none}.theme-mono .theme-container [data-slot=toggle-group],.theme-mono .theme-container [data-slot=toggle-group-item]{--tw-shadow:0 0 #0000!important;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)!important;border-radius:0!important}.theme-scaled .theme-container{--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800)}@media (min-width:1024px){.theme-scaled .theme-container{--radius:.45em;--text-lg:1rem;--text-xl:1.1rem;--text-2xl:1.2rem;--text-3xl:1.3rem;--text-4xl:1.4rem;--text-5xl:1.5rem;--text-6xl:1.6rem;--text-7xl:1.7rem;--text-8xl:1.8rem;--text-base:.85rem;--text-sm:.8rem;--spacing:.2rem}}.theme-scaled .theme-container [data-slot=select-trigger],.theme-scaled .theme-container [data-slot=toggle-group-item]{--spacing:.2rem}.theme-scaled .theme-container [data-slot=card]{border-radius:var(--radius);padding-block:calc(var(--spacing)*4);gap:calc(var(--spacing)*2)}.theme-scaled .theme-container [data-slot=card].pb-0{padding-bottom:0}.theme-red .theme-container{--primary:var(--color-red-600);--primary-foreground:var(--color-red-50);--ring:var(--color-red-400);--chart-1:var(--color-red-300);--chart-2:var(--color-red-500);--chart-3:var(--color-red-600);--chart-4:var(--color-red-700);--chart-5:var(--color-red-800);--sidebar-primary:var(--color-red-600);--sidebar-primary-foreground:var(--color-red-50);--sidebar-ring:var(--color-red-400)}.theme-red .theme-container:is(.dark *){--primary:var(--color-red-500);--primary-foreground:var(--color-red-50);--ring:var(--color-red-900);--sidebar-primary:var(--color-red-500);--sidebar-primary-foreground:var(--color-red-50);--sidebar-ring:var(--color-red-900)}.theme-yellow .theme-container{--primary:var(--color-yellow-400);--primary-foreground:var(--color-yellow-900);--ring:var(--color-yellow-400);--chart-1:var(--color-yellow-300);--chart-2:var(--color-yellow-500);--chart-3:var(--color-yellow-600);--chart-4:var(--color-yellow-700);--chart-5:var(--color-yellow-800);--sidebar-primary:var(--color-yellow-600);--sidebar-primary-foreground:var(--color-yellow-50);--sidebar-ring:var(--color-yellow-400)}.theme-yellow .theme-container:is(.dark *){--primary:var(--color-yellow-500);--primary-foreground:var(--color-yellow-900);--ring:var(--color-yellow-900);--sidebar-primary:var(--color-yellow-500);--sidebar-primary-foreground:var(--color-yellow-50);--sidebar-ring:var(--color-yellow-900)}.theme-violet .theme-container{--primary:var(--color-violet-600);--primary-foreground:var(--color-violet-50);--ring:var(--color-violet-400);--chart-1:var(--color-violet-300);--chart-2:var(--color-violet-500);--chart-3:var(--color-violet-600);--chart-4:var(--color-violet-700);--chart-5:var(--color-violet-800);--sidebar-primary:var(--color-violet-600);--sidebar-primary-foreground:var(--color-violet-50);--sidebar-ring:var(--color-violet-400)}.theme-violet .theme-container:is(.dark *){--primary:var(--color-violet-500);--primary-foreground:var(--color-violet-50);--ring:var(--color-violet-900);--sidebar-primary:var(--color-violet-500);--sidebar-primary-foreground:var(--color-violet-50);--sidebar-ring:var(--color-violet-900)}:root{--radius:.625rem;--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0);--sidebar-ring:oklch(70.8% 0 0);--surface:oklch(98% 0 0);--surface-foreground:var(--foreground);--code:var(--surface);--code-foreground:var(--surface-foreground);--code-highlight:oklch(96% 0 0);--code-number:oklch(56% 0 0);--selection:oklch(14.5% 0 0);--selection-foreground:oklch(100% 0 0);--color-blue-300:oklch(77.8% .108 231.731);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.9% .234 260.011);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(41.9% .243 267.218);--brand:oklch(62.3% .214 259.815);--highlight:oklch(85.2% .199 91.936)}.dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(26.9% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(37.1% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:var(--color-blue-300);--chart-2:var(--color-blue-500);--chart-3:var(--color-blue-600);--chart-4:var(--color-blue-700);--chart-5:var(--color-blue-800);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1);--sidebar-ring:oklch(43.9% 0 0);--surface:oklch(20% 0 0);--surface-foreground:oklch(70.8% 0 0);--code:var(--surface);--code-foreground:var(--surface-foreground);--code-highlight:oklch(27% 0 0);--code-number:oklch(72% 0 0);--selection:oklch(92.2% 0 0);--selection-foreground:oklch(20.5% 0 0);--brand:oklch(70.7% .165 254.624);--highlight:oklch(85.2% .199 91.936)}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0))}}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height,var(--bits-accordion-content-height,var(--reka-accordion-content-height,var(--kb-accordion-content-height,auto))))}}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height,var(--bits-accordion-content-height,var(--reka-accordion-content-height,var(--kb-accordion-content-height,auto))))}to{height:0}} \ No newline at end of file diff --git a/apps/www/src/registry/components/editor/plugins/discussion-kit.spec.tsx b/apps/www/src/registry/components/editor/plugins/discussion-kit.spec.tsx new file mode 100644 index 0000000000..bdd446bd3a --- /dev/null +++ b/apps/www/src/registry/components/editor/plugins/discussion-kit.spec.tsx @@ -0,0 +1,258 @@ +import { describe, expect, it } from 'bun:test'; +import { createPlateEditor } from 'platejs/react'; + +import { + discussionPlugin, + type TDiscussion, + DiscussionKit, +} from './discussion-kit'; + +describe('discussionPlugin', () => { + it('should create plugin with default options', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + + expect(options.currentUserId).toBe('alice'); + expect(options.discussions).toBeInstanceOf(Array); + expect(options.discussions.length).toBeGreaterThan(0); + expect(options.users).toHaveProperty('alice'); + expect(options.users).toHaveProperty('bob'); + expect(options.users).toHaveProperty('charlie'); + }); + + it('should have correct user data structure', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const alice = options.users.alice; + + expect(alice.id).toBe('alice'); + expect(alice.name).toBe('Alice'); + expect(alice.avatarUrl).toContain('dicebear.com'); + }); + + it('should have discussion data with correct structure', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const discussion = options.discussions[0] as TDiscussion; + + expect(discussion).toHaveProperty('id'); + expect(discussion).toHaveProperty('comments'); + expect(discussion).toHaveProperty('createdAt'); + expect(discussion).toHaveProperty('isResolved'); + expect(discussion).toHaveProperty('userId'); + expect(discussion.comments).toBeInstanceOf(Array); + }); + + it('should have comments with required fields', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const discussion = options.discussions[0] as TDiscussion; + const comment = discussion.comments[0]; + + expect(comment).toHaveProperty('id'); + expect(comment).toHaveProperty('contentRich'); + expect(comment).toHaveProperty('createdAt'); + expect(comment).toHaveProperty('discussionId'); + expect(comment).toHaveProperty('isEdited'); + expect(comment).toHaveProperty('userId'); + }); + + it('should configure BlockDiscussion render component', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const plugin = editor.getPlugin(discussionPlugin); + + expect(plugin.render?.aboveNodes).toBeDefined(); + }); +}); + +describe('discussionPlugin selectors', () => { + it('should return current user with currentUser selector', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const currentUser = editor.getOption(discussionPlugin, 'currentUser'); + + expect(currentUser?.id).toBe('alice'); + expect(currentUser?.name).toBe('Alice'); + expect(currentUser?.avatarUrl).toContain('dicebear.com'); + }); + + it('should return specific user with user selector', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const bob = editor.getOption(discussionPlugin, 'user', 'bob'); + + expect(bob?.id).toBe('bob'); + expect(bob?.name).toBe('Bob'); + expect(bob?.avatarUrl).toContain('dicebear.com'); + }); + + it('should return charlie with user selector', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const charlie = editor.getOption(discussionPlugin, 'user', 'charlie'); + + expect(charlie?.id).toBe('charlie'); + expect(charlie?.name).toBe('Charlie'); + expect(charlie?.avatarUrl).toContain('dicebear.com'); + }); + + it('should handle undefined user gracefully', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const unknownUser = editor.getOption(discussionPlugin, 'user', 'unknown'); + + expect(unknownUser).toBeUndefined(); + }); +}); + +describe('DiscussionKit', () => { + it('should be an array containing discussionPlugin', () => { + expect(DiscussionKit).toBeInstanceOf(Array); + expect(DiscussionKit.length).toBe(1); + expect(DiscussionKit[0]).toBe(discussionPlugin); + }); + + it('should work when spread into plugins array', () => { + const editor = createPlateEditor({ + plugins: [...DiscussionKit], + }); + + const options = editor.getOptions(discussionPlugin); + + expect(options.currentUserId).toBe('alice'); + }); +}); + +describe('TDiscussion type support', () => { + it('should support optional authorName field', () => { + const discussion: TDiscussion = { + id: 'test-1', + comments: [], + createdAt: new Date(), + isResolved: false, + userId: 'user-1', + authorName: 'Test Author', + }; + + expect(discussion.authorName).toBe('Test Author'); + }); + + it('should support optional authorInitials field', () => { + const discussion: TDiscussion = { + id: 'test-2', + comments: [], + createdAt: new Date(), + isResolved: false, + userId: 'user-2', + authorInitials: 'TA', + }; + + expect(discussion.authorInitials).toBe('TA'); + }); + + it('should support optional paraId field for DOCX roundtrip', () => { + const discussion: TDiscussion = { + id: 'test-3', + comments: [], + createdAt: new Date(), + isResolved: false, + userId: 'user-3', + paraId: '12345678', + }; + + expect(discussion.paraId).toBe('12345678'); + }); + + it('should support optional documentContent field', () => { + const discussion: TDiscussion = { + id: 'test-4', + comments: [], + createdAt: new Date(), + isResolved: false, + userId: 'user-4', + documentContent: 'commented text', + }; + + expect(discussion.documentContent).toBe('commented text'); + }); +}); + +describe('discussionsData fixtures', () => { + it('should have discussion1 with 2 comments', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const discussion1 = options.discussions.find( + (d: TDiscussion) => d.id === 'discussion1' + ); + + expect(discussion1).toBeDefined(); + expect(discussion1?.comments.length).toBe(2); + }); + + it('should have discussion2 with overlapping content', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const discussion2 = options.discussions.find( + (d: TDiscussion) => d.id === 'discussion2' + ); + + expect(discussion2).toBeDefined(); + expect(discussion2?.documentContent).toBe('overlapping'); + expect(discussion2?.comments.length).toBe(2); + }); + + it('should have unresolved discussions by default', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + + options.discussions.forEach((d: TDiscussion) => { + expect(d.isResolved).toBe(false); + }); + }); + + it('should have valid timestamps for comments', () => { + const editor = createPlateEditor({ + plugins: [discussionPlugin], + }); + + const options = editor.getOptions(discussionPlugin); + const discussion = options.discussions[0] as TDiscussion; + + discussion.comments.forEach((comment) => { + expect(comment.createdAt).toBeInstanceOf(Date); + expect(comment.createdAt.getTime()).toBeLessThanOrEqual(Date.now()); + }); + }); +}); diff --git a/apps/www/src/registry/components/editor/plugins/discussion-kit.tsx b/apps/www/src/registry/components/editor/plugins/discussion-kit.tsx index ed94a3445b..beab11ea34 100644 --- a/apps/www/src/registry/components/editor/plugins/discussion-kit.tsx +++ b/apps/www/src/registry/components/editor/plugins/discussion-kit.tsx @@ -13,6 +13,12 @@ export type TDiscussion = { isResolved: boolean; userId: string; documentContent?: string; + /** Direct author name from DOCX import (bypasses user lookup) */ + authorName?: string; + /** Author initials from DOCX import */ + authorInitials?: string; + /** OOXML paraId for round-trip DOCX threading fidelity */ + paraId?: string; }; const discussionsData: TDiscussion[] = [ diff --git a/apps/www/src/registry/components/editor/plugins/docx-export-kit.spec.tsx b/apps/www/src/registry/components/editor/plugins/docx-export-kit.spec.tsx new file mode 100644 index 0000000000..a9c77b1c05 --- /dev/null +++ b/apps/www/src/registry/components/editor/plugins/docx-export-kit.spec.tsx @@ -0,0 +1,176 @@ +import { describe, expect, it } from 'bun:test'; +import { KEYS } from 'platejs'; + +import { DocxExportKit } from './docx-export-kit'; + +const getConfiguredComponents = (plugin: any) => + plugin.__configuration?.({})?.override?.components; + +describe('DocxExportKit', () => { + it('should be an array with one plugin', () => { + expect(DocxExportKit).toBeInstanceOf(Array); + expect(DocxExportKit.length).toBe(1); + }); + + it('should configure DocxExportPlugin with custom components', () => { + const plugin = DocxExportKit[0] as any; + + expect(plugin).toBeDefined(); + expect(plugin.__configuration).toBeDefined(); + expect(getConfiguredComponents(plugin)).toBeDefined(); + }); + + it('should override code block components', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.codeBlock); + expect(components).toHaveProperty(KEYS.codeLine); + expect(components).toHaveProperty(KEYS.codeSyntax); + }); + + it('should override column components', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.column); + expect(components).toHaveProperty(KEYS.columnGroup); + }); + + it('should override equation components', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.equation); + expect(components).toHaveProperty(KEYS.inlineEquation); + }); + + it('should override callout component', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.callout); + }); + + it('should override toc component', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.toc); + }); + + it('should override suggestion component', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components).toHaveProperty(KEYS.suggestion); + }); + + it('should have all documented component overrides', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + const expectedKeys = [ + KEYS.codeBlock, + KEYS.codeLine, + KEYS.codeSyntax, + KEYS.column, + KEYS.columnGroup, + KEYS.equation, + KEYS.inlineEquation, + KEYS.callout, + KEYS.toc, + KEYS.suggestion, + ]; + + expectedKeys.forEach((key) => { + expect(components).toHaveProperty(key); + expect(components?.[key]).toBeDefined(); + }); + }); + + it('should use function components for overrides', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(typeof components?.[KEYS.codeBlock]).toBe('function'); + expect(typeof components?.[KEYS.column]).toBe('function'); + expect(typeof components?.[KEYS.equation]).toBe('function'); + expect(typeof components?.[KEYS.callout]).toBe('function'); + expect(typeof components?.[KEYS.toc]).toBe('function'); + expect(typeof components?.[KEYS.suggestion]).toBe('function'); + }); +}); + +describe('DocxExportKit purpose', () => { + it('should be optimized for DOCX export with inline styles', () => { + const plugin = DocxExportKit[0] as any; + + expect(plugin).toBeDefined(); + expect(plugin.__configuration).toBeDefined(); + }); + + it('should handle elements requiring special DOCX handling', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.codeBlock]).toBeDefined(); + expect(components?.[KEYS.column]).toBeDefined(); + expect(components?.[KEYS.equation]).toBeDefined(); + expect(components?.[KEYS.callout]).toBeDefined(); + expect(components?.[KEYS.toc]).toBeDefined(); + expect(components?.[KEYS.suggestion]).toBeDefined(); + }); +}); + +describe('DocxExportKit component references', () => { + it('should reference SuggestionLeafDocx for suggestions', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + const suggestionComponent = components?.[KEYS.suggestion]; + + expect(suggestionComponent).toBeDefined(); + expect(suggestionComponent?.name).toBe('SuggestionLeafDocx'); + }); + + it('should reference static DOCX components for code blocks', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.codeBlock]?.name).toBe('CodeBlockElementDocx'); + expect(components?.[KEYS.codeLine]?.name).toBe('CodeLineElementDocx'); + expect(components?.[KEYS.codeSyntax]?.name).toBe('CodeSyntaxLeafDocx'); + }); + + it('should reference static DOCX components for columns', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.column]?.name).toBe('ColumnElementDocx'); + expect(components?.[KEYS.columnGroup]?.name).toBe('ColumnGroupElementDocx'); + }); + + it('should reference static DOCX components for equations', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.equation]?.name).toBe('EquationElementDocx'); + expect(components?.[KEYS.inlineEquation]?.name).toBe( + 'InlineEquationElementDocx' + ); + }); + + it('should reference CalloutElementDocx for callouts', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.callout]?.name).toBe('CalloutElementDocx'); + }); + + it('should reference TocElementDocx for TOC', () => { + const plugin = DocxExportKit[0] as any; + const components = getConfiguredComponents(plugin); + + expect(components?.[KEYS.toc]?.name).toBe('TocElementDocx'); + }); +}); diff --git a/apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx b/apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx index 81c07282ba..0f5382c486 100644 --- a/apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx +++ b/apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx @@ -26,6 +26,7 @@ import { EquationElementDocx, InlineEquationElementDocx, } from '@/registry/ui/equation-node-static'; +import { SuggestionLeafDocx } from '@/registry/ui/suggestion-node-docx'; import { TocElementDocx } from '@/registry/ui/toc-node-static'; import { DocxExportPlugin } from '@platejs/docx-io'; import { KEYS } from 'platejs'; @@ -40,6 +41,7 @@ import { KEYS } from 'platejs'; * - Equations (inline font instead of KaTeX) * - Callouts (table layout for icon placement) * - TOC (anchor links with paragraph breaks) + * - Suggestions (<span> instead of <ins>/<del> to avoid unwanted formatting) * * Tables use base version with juice CSS inlining. */ @@ -56,6 +58,7 @@ export const DocxExportKit = [ [KEYS.inlineEquation]: InlineEquationElementDocx, [KEYS.callout]: CalloutElementDocx, [KEYS.toc]: TocElementDocx, + [KEYS.suggestion]: SuggestionLeafDocx, }, }, }), diff --git a/apps/www/src/registry/examples/playground-demo.tsx b/apps/www/src/registry/examples/playground-demo.tsx index 5150bd4037..fe6ac8c813 100644 --- a/apps/www/src/registry/examples/playground-demo.tsx +++ b/apps/www/src/registry/examples/playground-demo.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; -import { PlaywrightPlugin } from '@platejs/playwright'; import { KEYS, NormalizeTypesPlugin } from 'platejs'; import { Plate, usePlateEditor } from 'platejs/react'; @@ -31,7 +30,6 @@ export default function PlaygroundDemo({ [KEYS.indent]: id !== 'listClassic', [KEYS.list]: id !== 'listClassic', [KEYS.listClassic]: id === 'listClassic', - [KEYS.playwright]: process.env.NODE_ENV !== 'production', }, }, plugins: [ @@ -45,9 +43,6 @@ export default function PlaygroundDemo({ rules: [{ path: [0], strictType: 'h1' }], }, }), - - // Testing - PlaywrightPlugin, ], value, }, diff --git a/apps/www/src/registry/lib/discussion-ids.spec.ts b/apps/www/src/registry/lib/discussion-ids.spec.ts new file mode 100644 index 0000000000..fd2ff3a6e3 --- /dev/null +++ b/apps/www/src/registry/lib/discussion-ids.spec.ts @@ -0,0 +1,144 @@ +import { describe, expect, it } from 'bun:test'; + +import { getDiscussionCounterSeed } from './discussion-ids'; + +describe('getDiscussionCounterSeed', () => { + it('returns 0 for empty list', () => { + expect(getDiscussionCounterSeed([])).toBe(0); + }); + + it('returns max suffix for contiguous ids', () => { + const discussions = [{ id: 'discussion1' }, { id: 'discussion2' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(2); + }); + + it('returns max suffix for non-contiguous ids', () => { + const discussions = [{ id: 'discussion1' }, { id: 'discussion3' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(3); + }); + + it('ignores non-matching ids', () => { + const discussions = [ + { id: 'alpha1' }, + { id: 'discussion' }, + { id: 'discussion10x' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('handles mixed ids and returns highest match', () => { + const discussions = [ + { id: 'discussion2' }, + { id: 'alpha1' }, + { id: 'discussion12' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(12); + }); + + it('handles discussion0 as valid', () => { + const discussions = [{ id: 'discussion0' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('handles very large numbers', () => { + const discussions = [ + { id: 'discussion1' }, + { id: 'discussion999999' }, + { id: 'discussion100' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(999_999); + }); + + it('handles discussion IDs with leading zeros', () => { + const discussions = [ + { id: 'discussion01' }, + { id: 'discussion02' }, + { id: 'discussion10' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(10); + }); + + it('ignores negative numbers', () => { + const discussions = [{ id: 'discussion-5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('ignores decimal numbers', () => { + const discussions = [{ id: 'discussion1.5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('handles IDs with spaces', () => { + const discussions = [{ id: 'discussion 5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('ignores IDs with extra text after number', () => { + const discussions = [{ id: 'discussion5extra' }, { id: 'discussion3' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(3); + }); + + it('handles single discussion ID', () => { + const discussions = [{ id: 'discussion42' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(42); + }); + + it('handles many discussions in random order', () => { + const discussions = [ + { id: 'discussion7' }, + { id: 'discussion1' }, + { id: 'discussion15' }, + { id: 'discussion3' }, + { id: 'discussion99' }, + { id: 'discussion50' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(99); + }); + + it('handles duplicate IDs', () => { + const discussions = [ + { id: 'discussion5' }, + { id: 'discussion5' }, + { id: 'discussion3' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(5); + }); + + it('ignores IDs with wrong prefix case', () => { + const discussions = [ + { id: 'Discussion5' }, + { id: 'DISCUSSION10' }, + { id: 'discussion3' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(3); + }); + + it('handles IDs that are just the prefix', () => { + const discussions = [{ id: 'discussion' }, { id: 'discussion5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(5); + }); + + it('handles empty string IDs', () => { + const discussions = [{ id: '' }, { id: 'discussion5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(5); + }); + + it('handles all non-matching IDs', () => { + const discussions = [ + { id: 'comment1' }, + { id: 'thread2' }, + { id: 'chat3' }, + ]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); + + it('handles NaN result from invalid number', () => { + const discussions = [{ id: 'discussionABC' }, { id: 'discussion5' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(5); + }); + + it('returns 0 when all IDs have NaN suffixes', () => { + const discussions = [{ id: 'discussionABC' }, { id: 'discussionXYZ' }]; + expect(getDiscussionCounterSeed(discussions)).toBe(0); + }); +}); diff --git a/apps/www/src/registry/lib/discussion-ids.ts b/apps/www/src/registry/lib/discussion-ids.ts new file mode 100644 index 0000000000..1c49cd9453 --- /dev/null +++ b/apps/www/src/registry/lib/discussion-ids.ts @@ -0,0 +1,18 @@ +export type DiscussionIdLike = { + id: string; +}; + +const discussionIdPattern = /^discussion(\d+)$/; + +export const getDiscussionCounterSeed = ( + discussions: DiscussionIdLike[] +): number => + discussions.reduce((max, discussion) => { + const match = discussionIdPattern.exec(discussion.id); + if (!match) return max; + + const value = Number(match[1]); + if (Number.isNaN(value)) return max; + + return Math.max(max, value); + }, 0); diff --git a/apps/www/src/registry/ui/block-discussion.tsx b/apps/www/src/registry/ui/block-discussion.tsx index a66cc8f8ef..04eefad0ae 100644 --- a/apps/www/src/registry/ui/block-discussion.tsx +++ b/apps/www/src/registry/ui/block-discussion.tsx @@ -4,7 +4,11 @@ import * as React from 'react'; import type { PlateElementProps, RenderNodeWrapper } from 'platejs/react'; -import { getDraftCommentKey } from '@platejs/comment'; +import { + getCommentKeyId, + getCommentKeys, + getDraftCommentKey, +} from '@platejs/comment'; import { CommentPlugin } from '@platejs/comment/react'; import { getTransientSuggestionKey } from '@platejs/suggestion'; import { SuggestionPlugin } from '@platejs/suggestion/react'; @@ -316,31 +320,45 @@ const useResolvedDiscussion = ( const discussions = usePluginOption(discussionPlugin, 'discussions'); + const getLeafCommentIds = (leaf: TCommentText) => + getCommentKeys(leaf) + .map(getCommentKeyId) + .filter((id): id is string => Boolean(id) && id !== 'draft'); + + const map = getOption('uniquePathMap'); + const nextMap = new Map(map); + let mapChanged = false; + commentNodes.forEach(([node]) => { - const id = api.comment.nodeId(node); - const map = getOption('uniquePathMap'); + const ids = getLeafCommentIds(node); + if (ids.length === 0) return; - if (!id) return; + ids.forEach((id) => { + const previousPath = nextMap.get(id); - const previousPath = map.get(id); + // If there are no comment nodes in the corresponding path in the map, then update it. + if (PathApi.isPath(previousPath)) { + const nodes = api.comment.node({ id, at: previousPath }); - // If there are no comment nodes in the corresponding path in the map, then update it. - if (PathApi.isPath(previousPath)) { - const nodes = api.comment.node({ id, at: previousPath }); + if (!nodes) { + nextMap.set(id, blockPath); + mapChanged = true; + } - if (!nodes) { - setOption('uniquePathMap', new Map(map).set(id, blockPath)); return; } - - return; - } - // TODO: fix throw error - setOption('uniquePathMap', new Map(map).set(id, blockPath)); + // TODO: fix throw error + nextMap.set(id, blockPath); + mapChanged = true; + }); }); + if (mapChanged) { + setOption('uniquePathMap', nextMap); + } + const commentsIds = new Set( - commentNodes.map(([node]) => api.comment.nodeId(node)).filter(Boolean) + commentNodes.flatMap(([node]) => getLeafCommentIds(node)) ); const resolvedDiscussions = discussions diff --git a/apps/www/src/registry/ui/comment.spec.ts b/apps/www/src/registry/ui/comment.spec.ts new file mode 100644 index 0000000000..bfef6881ac --- /dev/null +++ b/apps/www/src/registry/ui/comment.spec.ts @@ -0,0 +1,170 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + setSystemTime, +} from 'bun:test'; + +import { formatCommentDate } from './comment'; + +describe('formatCommentDate', () => { + const now = new Date('2026-01-15T12:00:00Z'); + + beforeEach(() => { + setSystemTime(now); + }); + + afterEach(() => { + setSystemTime(); + }); + + it('should format recent minutes as "Nm"', () => { + const date = new Date(now.getTime() - 5 * 60 * 1000); // 5 minutes ago + const result = formatCommentDate(date); + + expect(result).toBe('5m'); + }); + + it('should format 0 minutes as "0m"', () => { + const date = new Date(now.getTime() - 0); // just now + const result = formatCommentDate(date); + + expect(result).toBe('0m'); + }); + + it('should format 59 minutes as "59m"', () => { + const date = new Date(now.getTime() - 59 * 60 * 1000); // 59 minutes ago + const result = formatCommentDate(date); + + expect(result).toBe('59m'); + }); + + it('should format hours as "Nh" when less than 24 hours', () => { + const date = new Date(now.getTime() - 3 * 60 * 60 * 1000); // 3 hours ago + const result = formatCommentDate(date); + + expect(result).toBe('3h'); + }); + + it('should format 23 hours as "23h"', () => { + const date = new Date(now.getTime() - 23 * 60 * 60 * 1000); // 23 hours ago + const result = formatCommentDate(date); + + expect(result).toBe('23h'); + }); + + it('should format 1 day as "1d"', () => { + const date = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 1 day ago + const result = formatCommentDate(date); + + expect(result).toBe('1d'); + }); + + it('should format dates 2+ days ago as full date', () => { + const date = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000); // 3 days ago + const result = formatCommentDate(date); + + // Format should be MM/dd/yyyy + expect(result).toMatch(/^\d{2}\/\d{2}\/\d{4}$/); + }); + + it('should format old dates as MM/dd/yyyy', () => { + const date = new Date('2023-06-10T08:30:00Z'); + const result = formatCommentDate(date); + + expect(result).toBe('06/10/2023'); + }); + + it('should format dates from years ago correctly', () => { + const date = new Date('2020-12-25T00:00:00Z'); + const result = formatCommentDate(date); + + expect(result).toBe('12/25/2020'); + }); + + it('should handle edge case at exactly 60 minutes', () => { + const date = new Date(now.getTime() - 60 * 60 * 1000); // exactly 60 minutes ago + const result = formatCommentDate(date); + + // Should show as "1h" not "60m" + expect(result).toBe('1h'); + }); + + it('should handle edge case at exactly 24 hours', () => { + const date = new Date(now.getTime() - 24 * 60 * 60 * 1000); // exactly 24 hours ago + const result = formatCommentDate(date); + + // Should show as "1d" not "24h" + expect(result).toBe('1d'); + }); + + it('should handle edge case at exactly 48 hours (2 days)', () => { + const date = new Date(now.getTime() - 48 * 60 * 60 * 1000); // exactly 48 hours ago + const result = formatCommentDate(date); + + // Should show full date format, not "2d" + expect(result).toMatch(/^\d{2}\/\d{2}\/\d{4}$/); + }); + + it('should handle dates in the future (edge case)', () => { + const futureDate = new Date(now.getTime() + 60 * 60 * 1000); // 1 hour in future + const result = formatCommentDate(futureDate); + + // Should handle negative difference (though this shouldn't happen in practice) + // Will likely show as negative or 0 + expect(typeof result).toBe('string'); + }); + + it('should handle very recent dates (same second)', () => { + const date = new Date(now.getTime() - 100); // 100ms ago + const result = formatCommentDate(date); + + expect(result).toBe('0m'); + }); + + it('should format January dates correctly', () => { + const date = new Date('2024-01-05T10:00:00Z'); + const result = formatCommentDate(date); + + expect(result).toBe('01/05/2024'); + }); + + it('should format December dates correctly', () => { + const date = new Date('2023-12-31T23:59:59Z'); + const result = formatCommentDate(date); + + expect(result).toBe('12/31/2023'); + }); + + it('should handle leap year dates', () => { + const date = new Date('2024-02-29T12:00:00Z'); // Leap year + const result = formatCommentDate(date); + + expect(result).toBe('02/29/2024'); + }); + + it('should format single-digit days with leading zero', () => { + const date = new Date('2024-03-05T10:00:00Z'); + const result = formatCommentDate(date); + + expect(result).toBe('03/05/2024'); + }); + + it('should format single-digit months with leading zero', () => { + const date = new Date('2024-09-15T10:00:00Z'); + const result = formatCommentDate(date); + + expect(result).toBe('09/15/2024'); + }); + + it('should consistently format the same date', () => { + const date = new Date('2023-07-20T15:30:00Z'); + const result1 = formatCommentDate(date); + const result2 = formatCommentDate(date); + + expect(result1).toBe(result2); + expect(result1).toBe('07/20/2023'); + }); +}); diff --git a/apps/www/src/registry/ui/comment.tsx b/apps/www/src/registry/ui/comment.tsx index 512374cecc..0a90cfb675 100644 --- a/apps/www/src/registry/ui/comment.tsx +++ b/apps/www/src/registry/ui/comment.tsx @@ -61,6 +61,14 @@ export type TComment = { discussionId: string; isEdited: boolean; userId: string; + /** Direct author name from DOCX import (bypasses user lookup) */ + authorName?: string; + /** Author initials from DOCX import */ + authorInitials?: string; + /** OOXML paraId for round-trip DOCX threading fidelity */ + paraId?: string; + /** OOXML parentParaId for round-trip DOCX reply threading */ + parentParaId?: string; }; export function Comment(props: { @@ -187,12 +195,19 @@ export function Comment(props: { > <div className="relative flex items-center"> <Avatar className="size-5"> - <AvatarImage alt={userInfo?.name} src={userInfo?.avatarUrl} /> - <AvatarFallback>{userInfo?.name?.[0]}</AvatarFallback> + <AvatarImage + alt={comment.authorName ?? userInfo?.name} + src={userInfo?.avatarUrl} + /> + <AvatarFallback> + {comment.authorInitials ?? + comment.authorName?.[0] ?? + userInfo?.name?.[0]} + </AvatarFallback> </Avatar> <h4 className="mx-2 font-semibold text-sm leading-none"> - {/* Replace to your own backend or refer to potion */} - {userInfo?.name} + {/* Use direct author name from DOCX or fall back to user lookup */} + {comment.authorName ?? userInfo?.name} </h4> <div className="text-muted-foreground/80 text-xs leading-none"> diff --git a/apps/www/src/registry/ui/export-toolbar-button.tsx b/apps/www/src/registry/ui/export-toolbar-button.tsx index 436bd190af..d5246d6562 100644 --- a/apps/www/src/registry/ui/export-toolbar-button.tsx +++ b/apps/www/src/registry/ui/export-toolbar-button.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; -import { exportToDocx } from '@platejs/docx-io'; +import { exportToDocx, type DocxExportDiscussion } from '@platejs/docx-io'; import { MarkdownPlugin } from '@platejs/markdown'; import { ArrowDownToLineIcon } from 'lucide-react'; import type { SlatePlugin } from 'platejs'; @@ -20,6 +20,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { BaseEditorKit } from '@/registry/components/editor/editor-base-kit'; +import { discussionPlugin } from '@/registry/components/editor/plugins/discussion-kit'; import { EditorStatic } from './editor-static'; import { ToolbarButton } from './toolbar'; @@ -151,8 +152,43 @@ export function ExportToolbarButton(props: DropdownMenuProps) { }; const exportToWord = async () => { + // Get discussions and users from the discussion plugin for comment export + const discussions = editor.getOption(discussionPlugin, 'discussions') ?? []; + const users = editor.getOption(discussionPlugin, 'users') ?? {}; + + // Resolve display name: prefer authorName (from DOCX import), fall back to users lookup + const resolveUser = ( + userId: string, + authorName?: string + ): { id: string; name: string } | undefined => { + const name = authorName ?? users[userId]?.name; + return name ? { id: userId, name } : undefined; + }; + + // Convert discussions to export format + const exportDiscussions: DocxExportDiscussion[] = discussions.map((d) => ({ + id: d.id, + comments: d.comments?.map((c) => ({ + contentRich: c.contentRich, + createdAt: c.createdAt, + id: c.id, + paraId: c.paraId, + parentParaId: c.parentParaId, + userId: c.userId, + user: resolveUser(c.userId, c.authorName), + })), + createdAt: d.createdAt, + documentContent: d.documentContent, + paraId: d.paraId, + userId: d.userId, + user: resolveUser(d.userId, d.authorName), + })); + const blob = await exportToDocx(editor.children, { editorPlugins: [...BaseEditorKit, ...DocxExportKit] as SlatePlugin[], + tracking: { + discussions: exportDiscussions, + }, }); const url = URL.createObjectURL(blob); diff --git a/apps/www/src/registry/ui/import-toolbar-button.tsx b/apps/www/src/registry/ui/import-toolbar-button.tsx index 1f03f23238..2b2f0db622 100644 --- a/apps/www/src/registry/ui/import-toolbar-button.tsx +++ b/apps/www/src/registry/ui/import-toolbar-button.tsx @@ -4,11 +4,14 @@ import * as React from 'react'; import type { DropdownMenuProps } from '@radix-ui/react-dropdown-menu'; -import { importDocx } from '@platejs/docx-io'; +import { getCommentKey } from '@platejs/comment'; +import { importDocxWithTracking } from '@platejs/docx-io'; import { MarkdownPlugin } from '@platejs/markdown'; +import { getSuggestionKey } from '@platejs/suggestion'; import { ArrowUpToLineIcon } from 'lucide-react'; -import { getEditorDOMFromHtmlString } from 'platejs/static'; +import { KEYS, TextApi } from 'platejs'; import { useEditorRef } from 'platejs/react'; +import { getEditorDOMFromHtmlString } from 'platejs/static'; import { useFilePicker } from 'use-file-picker'; import { @@ -19,10 +22,18 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { commentPlugin } from '@/registry/components/editor/plugins/comment-kit'; +import { + discussionPlugin, + type TDiscussion, +} from '@/registry/components/editor/plugins/discussion-kit'; +import { getDiscussionCounterSeed } from '../lib/discussion-ids'; import { ToolbarButton } from './toolbar'; type ImportType = 'html' | 'markdown'; +const WHITESPACE_REGEX = /\s+/; + export function ImportToolbarButton(props: DropdownMenuProps) { const editor = useEditorRef(); const [open, setOpen] = React.useState(false); @@ -73,9 +84,92 @@ export function ImportToolbarButton(props: DropdownMenuProps) { multiple: false, onFilesSelected: async ({ plainFiles }) => { const arrayBuffer = await plainFiles[0].arrayBuffer(); - const result = await importDocx(editor, arrayBuffer); - editor.tf.insertNodes(result.nodes as typeof editor.children); + // Compute next discussion number to avoid ID collisions + const existingDiscussions = + editor.getOption(discussionPlugin, 'discussions') ?? []; + let discussionCounter = getDiscussionCounterSeed(existingDiscussions); + + // Import with full tracking support (suggestions + comments) + const result = await importDocxWithTracking(editor as any, arrayBuffer, { + suggestionKey: KEYS.suggestion, + getSuggestionKey, + commentKey: KEYS.comment, + getCommentKey, + isText: TextApi.isText, + generateId: () => `discussion${++discussionCounter}`, + }); + + // Register imported users so suggestion/comment UI can resolve them + if (result.users.length > 0) { + const existingUsers = editor.getOption(discussionPlugin, 'users') ?? {}; + const updatedUsers = { ...existingUsers }; + + for (const user of result.users) { + if (!updatedUsers[user.id]) { + updatedUsers[user.id] = { + id: user.id, + name: user.name, + avatarUrl: `https://api.dicebear.com/9.x/glass/svg?seed=${encodeURIComponent(user.name)}`, + }; + } + } + + editor.setOption(discussionPlugin, 'users', updatedUsers); + } + + // Convert imported discussions to TDiscussion format (empty array if none) + const newDiscussions: TDiscussion[] = (result.discussions ?? []).map( + (d) => ({ + id: d.id, + comments: (d.comments ?? []).map((c, index) => ({ + id: c.id || `comment${index + 1}`, + contentRich: + c.contentRich as TDiscussion['comments'][number]['contentRich'], + createdAt: c.createdAt ?? new Date(), + discussionId: d.id, + isEdited: false, + userId: c.userId ?? c.user?.id ?? 'imported-unknown', + authorName: c.user?.name, + authorInitials: c.user?.name + ? c.user.name + .split(WHITESPACE_REGEX) + .slice(0, 2) + .map((w) => w[0]?.toUpperCase() ?? '') + .join('') + : undefined, + paraId: c.paraId, + parentParaId: c.parentParaId, + })), + createdAt: d.createdAt ?? new Date(), + documentContent: d.documentContent, + isResolved: false, + userId: d.userId ?? d.user?.id ?? 'imported-unknown', + authorName: d.user?.name, + authorInitials: d.user?.name + ? d.user.name + .split(WHITESPACE_REGEX) + .slice(0, 2) + .map((w) => w[0]?.toUpperCase() ?? '') + .join('') + : undefined, + paraId: d.paraId, + }) + ); + + // Replace all discussions (not append) because importDocxWithTracking + // replaces the entire editor content, making old discussions stale + editor.setOption(discussionPlugin, 'discussions', newDiscussions); + editor.setOption(commentPlugin, 'uniquePathMap', new Map()); + + // Log import results in dev only + if ( + result.hasTracking && + result.errors.length > 0 && + process.env.NODE_ENV !== 'production' + ) { + console.warn('[DOCX Import] Errors:', result.errors); + } }, }); diff --git a/apps/www/src/registry/ui/suggestion-node-docx.spec.tsx b/apps/www/src/registry/ui/suggestion-node-docx.spec.tsx new file mode 100644 index 0000000000..4309b64727 --- /dev/null +++ b/apps/www/src/registry/ui/suggestion-node-docx.spec.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; + +import { describe, expect, it } from 'bun:test'; +import { render } from '@testing-library/react'; +import type { TSuggestionText } from 'platejs'; + +import { SuggestionLeafDocx } from './suggestion-node-docx'; + +describe('SuggestionLeafDocx', () => { + it('should render as span element', () => { + const mockProps = { + attributes: { 'data-slate-leaf': true }, + children: 'test content', + editor: {} as any, + leaf: { + text: 'test content', + suggestion: true, + } as TSuggestionText, + text: { + text: 'test content', + suggestion: true, + } as TSuggestionText, + }; + + const { container } = render(<SuggestionLeafDocx {...mockProps} />); + const span = container.querySelector('span'); + + expect(span).not.toBeNull(); + expect(span?.textContent).toBe('test content'); + }); + + it('should not render as ins or del tags', () => { + const mockProps = { + attributes: { 'data-slate-leaf': true }, + children: 'suggestion text', + editor: {} as any, + leaf: { + text: 'suggestion text', + suggestion: true, + suggestionDeletion: true, + } as TSuggestionText, + text: { + text: 'suggestion text', + suggestion: true, + suggestionDeletion: true, + } as TSuggestionText, + }; + + const { container } = render(<SuggestionLeafDocx {...mockProps} />); + + // Should NOT use <ins> or <del> tags + expect(container.querySelector('ins')).toBeNull(); + expect(container.querySelector('del')).toBeNull(); + + // Should use <span> instead + expect(container.querySelector('span')).not.toBeNull(); + }); + + it('should preserve children content', () => { + const mockProps = { + attributes: { 'data-slate-leaf': true }, + children: 'preserved content', + editor: {} as any, + leaf: { + text: 'preserved content', + } as TSuggestionText, + text: { + text: 'preserved content', + } as TSuggestionText, + }; + + const { container } = render(<SuggestionLeafDocx {...mockProps} />); + + expect(container.textContent).toBe('preserved content'); + }); + + it('should handle empty content', () => { + const mockProps = { + attributes: { 'data-slate-leaf': true }, + children: '', + editor: {} as any, + leaf: { + text: '', + } as TSuggestionText, + text: { + text: '', + } as TSuggestionText, + }; + + const { container } = render(<SuggestionLeafDocx {...mockProps} />); + const span = container.querySelector('span'); + + expect(span).not.toBeNull(); + expect(span?.textContent).toBe(''); + }); + + it('should pass through attributes', () => { + const mockProps = { + attributes: { + 'data-slate-leaf': true, + 'data-testid': 'custom-attribute', + }, + children: 'test', + editor: {} as any, + leaf: { + text: 'test', + } as TSuggestionText, + text: { + text: 'test', + } as TSuggestionText, + }; + + const { container } = render(<SuggestionLeafDocx {...mockProps} />); + const span = container.querySelector('span'); + + expect(span?.getAttribute('data-slate-leaf')).toBe('true'); + expect(span?.getAttribute('data-testid')).toBe('custom-attribute'); + }); +}); diff --git a/apps/www/src/registry/ui/suggestion-node-docx.tsx b/apps/www/src/registry/ui/suggestion-node-docx.tsx new file mode 100644 index 0000000000..5364ca55a9 --- /dev/null +++ b/apps/www/src/registry/ui/suggestion-node-docx.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; + +import type { TSuggestionText } from 'platejs'; +import type { SlateLeafProps } from 'platejs/static'; + +import { SlateLeaf } from 'platejs/static'; + +/** + * DOCX export suggestion leaf: renders as <span> instead of <ins>/<del>. + * + * The default SuggestionLeafStatic uses <ins>/<del> HTML tags which + * html-to-docx interprets as underline/strikethrough formatting. + * For DOCX export, the tracked changes are handled by the injected + * DOCX tracking tokens, so we only need a plain wrapper. + */ +export function SuggestionLeafDocx(props: SlateLeafProps<TSuggestionText>) { + return ( + <SlateLeaf {...props} as="span"> + {props.children} + </SlateLeaf> + ); +} diff --git a/apps/www/src/registry/ui/table-node-static.spec.tsx b/apps/www/src/registry/ui/table-node-static.spec.tsx new file mode 100644 index 0000000000..d9f708ab7d --- /dev/null +++ b/apps/www/src/registry/ui/table-node-static.spec.tsx @@ -0,0 +1,399 @@ +import React from 'react'; +import { describe, expect, it } from 'bun:test'; +import { render } from '@testing-library/react'; +import type { TTableCellElement, TTableElement } from 'platejs'; + +import { + TableCellElementStatic, + TableCellHeaderElementStatic, + TableElementStatic, + TableRowElementStatic, +} from './table-node-static'; + +const createMockEditor = (options = {}) => ({ + getOptions: () => ({ disableMarginLeft: false, ...options }), + getPlugin: () => ({ + api: { + table: { + getCellSize: () => ({ width: 120, minHeight: 40 }), + getCellBorders: () => null, + getColSpan: () => 1, + getRowSpan: () => 1, + }, + }, + }), +}); + +describe('TableElementStatic', () => { + it('should render table with correct structure', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: ( + <tr> + <td>Cell</td> + </tr> + ), + editor: createMockEditor() as any, + element: { type: 'table', children: [], marginLeft: 0 } as TTableElement, + }; + + const { container } = render(<TableElementStatic {...mockProps} />); + const table = container.querySelector('table'); + + expect(table).not.toBeNull(); + expect(table?.style.borderCollapse).toBe('collapse'); + }); + + it('should apply marginLeft when not disabled', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: ( + <tr> + <td>Cell</td> + </tr> + ), + editor: createMockEditor({ disableMarginLeft: false }) as any, + element: { type: 'table', children: [], marginLeft: 20 } as TTableElement, + }; + + const { container } = render(<TableElementStatic {...mockProps} />); + const wrapper = container.querySelector('[data-slate-node="element"]'); + + expect(wrapper?.getAttribute('style')).toContain('padding-left: 20'); + }); + + it('should not apply marginLeft when disabled', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: ( + <tr> + <td>Cell</td> + </tr> + ), + editor: createMockEditor({ disableMarginLeft: true }) as any, + element: { type: 'table', children: [], marginLeft: 20 } as TTableElement, + }; + + const { container } = render(<TableElementStatic {...mockProps} />); + const wrapper = container.querySelector('[data-slate-node="element"]'); + + expect(wrapper?.getAttribute('style')).toContain('padding-left: 0'); + }); + + it('should render with overflow-x-auto class', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: ( + <tr> + <td>Cell</td> + </tr> + ), + editor: createMockEditor() as any, + element: { type: 'table', children: [], marginLeft: 0 } as TTableElement, + }; + + const { container } = render(<TableElementStatic {...mockProps} />); + const wrapper = container.querySelector('[data-slate-node="element"]'); + + expect(wrapper?.className).toContain('overflow-x-auto'); + }); +}); + +describe('TableRowElementStatic', () => { + it('should render as tr element', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: <td>Cell</td>, + editor: {} as any, + element: { type: 'tr', children: [] }, + }; + + const { container } = render(<TableRowElementStatic {...mockProps} />); + const tr = container.querySelector('tr'); + + expect(tr).not.toBeNull(); + }); + + it('should have h-full class', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: <td>Cell</td>, + editor: {} as any, + element: { type: 'tr', children: [] }, + }; + + const { container } = render(<TableRowElementStatic {...mockProps} />); + const tr = container.querySelector('tr'); + + expect(tr?.className).toContain('h-full'); + }); +}); + +describe('TableCellElementStatic', () => { + it('should render as td by default', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell content', + editor: createMockEditor() as any, + element: { + type: 'td', + children: [], + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td).not.toBeNull(); + expect(container.textContent).toBe('Cell content'); + }); + + it('should apply cell width from getCellSize', () => { + const mockEditor = { + getOptions: () => ({ disableMarginLeft: false }), + getPlugin: () => ({ + api: { + table: { + getCellSize: () => ({ width: 200, minHeight: 50 }), + getCellBorders: () => null, + getColSpan: () => 1, + getRowSpan: () => 1, + }, + }, + }), + }; + + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell', + editor: mockEditor as any, + element: { + type: 'td', + children: [], + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td?.style.maxWidth).toBe('200px'); + expect(td?.style.minWidth).toBe('200px'); + }); + + it('should apply background color when provided', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell', + editor: createMockEditor() as any, + element: { + type: 'td', + children: [], + background: '#ff0000', + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td?.style.backgroundColor).toMatch( + /^(#ff0000|rgb\(255,\s*0,\s*0\))$/ + ); + }); + + it('should apply border styles when borders are provided', () => { + const mockEditor = { + getOptions: () => ({ disableMarginLeft: false }), + getPlugin: () => ({ + api: { + table: { + getCellSize: () => ({ width: 120, minHeight: 40 }), + getCellBorders: () => ({ + top: { size: 1, style: 'solid', color: '#000' }, + right: { size: 2, style: 'dashed', color: '#f00' }, + bottom: { size: 1, style: 'solid', color: '#000' }, + left: { size: 1, style: 'solid', color: '#000' }, + }), + getColSpan: () => 1, + getRowSpan: () => 1, + }, + }, + }), + }; + + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell', + editor: mockEditor as any, + element: { + type: 'td', + children: [], + borders: { + top: { size: 1, style: 'solid', color: '#000' }, + right: { size: 2, style: 'dashed', color: '#f00' }, + bottom: { size: 1, style: 'solid', color: '#000' }, + left: { size: 1, style: 'solid', color: '#000' }, + }, + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td?.style.borderTop).toContain('1px solid'); + expect(td?.style.borderRight).toContain('2px dashed'); + expect(td?.style.borderBottom).toContain('1px solid'); + expect(td?.style.borderLeft).toContain('1px solid'); + }); + + it('should handle colSpan and rowSpan attributes', () => { + const mockEditor = { + getOptions: () => ({ disableMarginLeft: false }), + getPlugin: () => ({ + api: { + table: { + getCellSize: () => ({ width: 120, minHeight: 40 }), + getCellBorders: () => null, + getColSpan: () => 2, + getRowSpan: () => 3, + }, + }, + }), + }; + + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Merged cell', + editor: mockEditor as any, + element: { + type: 'td', + children: [], + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td?.getAttribute('colSpan')).toBe('2'); + expect(td?.getAttribute('rowSpan')).toBe('3'); + }); + + it('should render inner div with padding', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell content', + editor: createMockEditor() as any, + element: { + type: 'td', + children: [], + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const innerDiv = container.querySelector('td > div'); + + expect(innerDiv).not.toBeNull(); + expect(innerDiv?.className).toContain('px-4'); + expect(innerDiv?.className).toContain('py-2'); + }); +}); + +describe('TableCellHeaderElementStatic', () => { + it('should render as th element', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Header', + editor: createMockEditor() as any, + element: { + type: 'th', + children: [], + } as TTableCellElement, + }; + + const { container } = render( + <TableCellHeaderElementStatic {...mockProps} /> + ); + const th = container.querySelector('th'); + + expect(th).not.toBeNull(); + expect(container.textContent).toBe('Header'); + }); + + it('should apply header-specific styles', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Header', + editor: createMockEditor() as any, + element: { + type: 'th', + children: [], + } as TTableCellElement, + }; + + const { container } = render( + <TableCellHeaderElementStatic {...mockProps} /> + ); + const th = container.querySelector('th'); + + expect(th?.className).toContain('text-left'); + expect(th?.className).toContain('font-normal'); + }); +}); + +describe('cellBorderStyles edge cases', () => { + it('should handle missing borders gracefully', () => { + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell', + editor: createMockEditor() as any, + element: { + type: 'td', + children: [], + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + // Should render without errors even with no borders + expect(td).not.toBeNull(); + }); + + it('should handle partial border definitions', () => { + const mockEditor = { + getOptions: () => ({ disableMarginLeft: false }), + getPlugin: () => ({ + api: { + table: { + getCellSize: () => ({ width: 120, minHeight: 40 }), + getCellBorders: () => ({ + top: { size: 1, style: 'solid', color: '#000' }, + // Only top border defined + }), + getColSpan: () => 1, + getRowSpan: () => 1, + }, + }, + }), + }; + + const mockProps = { + attributes: { 'data-slate-node': 'element' }, + children: 'Cell', + editor: mockEditor as any, + element: { + type: 'td', + children: [], + borders: { + top: { size: 1, style: 'solid', color: '#000' }, + }, + } as TTableCellElement, + }; + + const { container } = render(<TableCellElementStatic {...mockProps} />); + const td = container.querySelector('td'); + + expect(td?.style.borderTop).toContain('1px solid'); + // Other borders should not be set + expect(td?.style.borderRight).toBe(''); + }); +}); diff --git a/apps/www/src/registry/ui/table-node-static.tsx b/apps/www/src/registry/ui/table-node-static.tsx index c794b07b22..ff45194654 100644 --- a/apps/www/src/registry/ui/table-node-static.tsx +++ b/apps/www/src/registry/ui/table-node-static.tsx @@ -41,6 +41,33 @@ export function TableRowElementStatic(props: SlateElementProps) { ); } +/** Build inline border styles for DOCX export (all 4 sides per cell). */ +const cellBorderStyles = ( + element: TTableCellElement +): Record<string, string> => { + const b = element.borders; + if (!b) return {}; + + const fmt = (dir: 'bottom' | 'left' | 'right' | 'top') => { + const border = b[dir]; + if (!border || !border.size) return; + return `${border.size}px ${border.style || 'solid'} ${border.color || '#000'}`; + }; + + const styles: Record<string, string> = {}; + const top = fmt('top'); + const right = fmt('right'); + const bottom = fmt('bottom'); + const left = fmt('left'); + + if (top) styles.borderTop = top; + if (right) styles.borderRight = right; + if (bottom) styles.borderBottom = bottom; + if (left) styles.borderLeft = left; + + return styles; +}; + export function TableCellElementStatic({ isHeader, ...props @@ -74,8 +101,12 @@ export function TableCellElementStatic({ style={ { '--cellBackground': element.background, + ...(element.background + ? { backgroundColor: element.background } + : {}), maxWidth: width || 240, minWidth: width || 120, + ...cellBorderStyles(element), } as React.CSSProperties } attributes={{ diff --git a/biome.jsonc b/biome.jsonc index ca35d7fec5..fc337db8a8 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -27,7 +27,9 @@ "!!tooling/config/global.d.ts", "!!templates", "!**/__registry__", - "!**/next-env.d.ts" + "!**/next-env.d.ts", + "!**/mammoth.js", + "!.claude" ] }, "linter": { diff --git a/nixpacks.toml b/nixpacks.toml new file mode 100644 index 0000000000..f90f768a5e --- /dev/null +++ b/nixpacks.toml @@ -0,0 +1,13 @@ +[phases.install] +cmds = ['yarn install --mode=skip-build'] + +[phases.build] +cmds = [ + 'NODE_OPTIONS="--max-old-space-size=8192" yarn build', + 'mkdir -p apps/www/.next/standalone/apps/www/.next', + 'cp -r apps/www/public apps/www/.next/standalone/apps/www/public', + 'cp -r apps/www/.next/static apps/www/.next/standalone/apps/www/.next/static' +] + +[start] +cmd = 'node apps/www/.next/standalone/apps/www/server.js' diff --git a/packages/autoformat/package.json b/packages/autoformat/package.json index 467ed6a4e8..eba181aa8a 100644 --- a/packages/autoformat/package.json +++ b/packages/autoformat/package.json @@ -39,7 +39,7 @@ "typecheck": "plate-pkg p:typecheck" }, "dependencies": { - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "devDependencies": { diff --git a/packages/comment/package.json b/packages/comment/package.json index 31a6f156cc..dc5985d67d 100644 --- a/packages/comment/package.json +++ b/packages/comment/package.json @@ -40,7 +40,7 @@ "typecheck": "plate-pkg p:typecheck" }, "dependencies": { - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "devDependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index 807faa42df..d8089d759a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -57,8 +57,8 @@ "jotai": "~2.8.4", "jotai-optics": "0.4.0", "jotai-x": "2.3.3", - "lodash": "^4.17.21", - "nanoid": "^5.1.5", + "lodash": "^4.17.23", + "nanoid": "^5.1.6", "optics-ts": "2.4.1", "react-compiler-runtime": "^1.0.0", "slate": "0.120.0", diff --git a/packages/core/src/internal/plugin/resolvePlugins.ts b/packages/core/src/internal/plugin/resolvePlugins.ts index e81fe10885..b571e10c53 100644 --- a/packages/core/src/internal/plugin/resolvePlugins.ts +++ b/packages/core/src/internal/plugin/resolvePlugins.ts @@ -105,7 +105,9 @@ export const resolvePlugins = ( if ( plugin.node?.isLeaf && - (plugin.node?.isDecoration === true || plugin.render.leaf) + (plugin.node?.isDecoration === true || + plugin.render.leaf || + (plugin.render.node && plugin.node?.isDecoration !== false)) ) { editor.meta.pluginCache.node.isLeaf.push(plugin.key); } diff --git a/packages/diff/package.json b/packages/diff/package.json index d74da40ea6..a4c21ddbce 100644 --- a/packages/diff/package.json +++ b/packages/diff/package.json @@ -39,7 +39,7 @@ }, "dependencies": { "diff-match-patch-ts": "^0.6.0", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "devDependencies": { diff --git a/packages/dnd/package.json b/packages/dnd/package.json index cb2c443b53..4b9a7fd49f 100644 --- a/packages/dnd/package.json +++ b/packages/dnd/package.json @@ -39,7 +39,7 @@ "typecheck": "plate-pkg p:typecheck" }, "dependencies": { - "lodash": "^4.17.21", + "lodash": "^4.17.23", "raf": "^3.4.1", "react-compiler-runtime": "^1.0.0" }, diff --git a/packages/docx-io/package.json b/packages/docx-io/package.json index 070706eced..e39ccfc7b3 100644 --- a/packages/docx-io/package.json +++ b/packages/docx-io/package.json @@ -33,45 +33,44 @@ "dist/**/*" ], "scripts": { - "brl": "plate-pkg p:brl", - "build": "plate-pkg p:build", - "build:watch": "plate-pkg p:build:watch", - "clean": "plate-pkg p:clean", - "lint": "plate-pkg p:lint", - "lint:fix": "plate-pkg p:lint:fix", - "test": "plate-pkg p:test", - "test:watch": "plate-pkg p:test:watch", - "typecheck": "plate-pkg p:typecheck" + "brl": "yarn p:brl", + "build": "yarn p:build", + "build:watch": "yarn p:build:watch", + "clean": "yarn p:clean", + "lint": "yarn p:lint", + "lint:fix": "yarn p:lint:fix", + "test": "yarn p:test", + "test:watch": "yarn p:test:watch", + "typecheck": "yarn p:typecheck" }, "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", "color-name": "^2.0.0", + "dingbat-to-unicode": "^1.0.1", "html-entities": "^2.5.2", "html-to-vdom": "^0.7.0", "jszip": "^3.10.1", - "juice": "^11.0.1", "lodash": "^4.17.21", - "mammoth": "^1.9.0", + "lop": "^0.4.2", "mime-types": "^2.1.35", "nanoid": "^5.0.9", + "path-is-absolute": "^1.0.0", "react-compiler-runtime": "^1.0.0", + "turbo": "^2.8.2", + "underscore": "^1.13.1", "validator": "^13.15.15", "virtual-dom": "^2.1.1", - "xmlbuilder2": "^3.1.1" + "xmlbuilder": "^10.0.0", + "xmlbuilder2": "^3.1.1", + "yarn": "^1.22.22" }, "devDependencies": { - "@plate/scripts": "workspace:*", - "@platejs/basic-nodes": "^52.0.11", - "@platejs/basic-styles": "^52.0.11", - "@platejs/code-block": "^52.0.11", - "@platejs/docx": "^52.0.11", - "@platejs/indent": "^52.0.11", - "@platejs/link": "^52.0.11", - "@platejs/list": "^52.0.11", - "@platejs/markdown": "^52.1.0", - "@platejs/media": "^52.0.11", - "@platejs/table": "^52.0.11", - "@platejs/test-utils": "^52.0.10", - "platejs": "^52.0.17" + "@platejs/docx": "workspace:^", + "@platejs/markdown": "workspace:^", + "platejs": "workspace:^" }, "peerDependencies": { "@platejs/docx": ">=52.0.0", diff --git a/packages/docx-io/src/lib/DocxIOPlugin.spec.tsx b/packages/docx-io/src/lib/DocxIOPlugin.spec.tsx new file mode 100644 index 0000000000..3efda01178 --- /dev/null +++ b/packages/docx-io/src/lib/DocxIOPlugin.spec.tsx @@ -0,0 +1,230 @@ +import { describe, expect, it } from 'bun:test'; + +import { + DocxIOKit, + DocxIOPlugin, + downloadDocx, + exportEditorToDocx, + exportToDocx, + htmlToDocxBlob, + importDocx, +} from './DocxIOPlugin'; +import { DocxExportPlugin } from './docx-export-plugin'; + +describe('DocxIOPlugin', () => { + it('should export DocxIOPlugin', () => { + expect(DocxIOPlugin).toBeDefined(); + }); + + it('should be the same as DocxExportPlugin', () => { + expect(DocxIOPlugin).toBe(DocxExportPlugin); + }); + + it('should have a key property', () => { + expect(DocxIOPlugin.key).toBeDefined(); + }); +}); + +describe('DocxIOKit', () => { + it('should be an array', () => { + expect(Array.isArray(DocxIOKit)).toBe(true); + }); + + it('should contain DocxExportPlugin', () => { + expect(DocxIOKit).toContain(DocxExportPlugin); + }); + + it('should have length of 1', () => { + expect(DocxIOKit.length).toBe(1); + }); + + it('should be spreadable into plugins array', () => { + const plugins = [...DocxIOKit]; + + expect(plugins.length).toBe(1); + expect(plugins[0]).toBe(DocxExportPlugin); + }); +}); + +describe('importDocx function', () => { + it('should be exported as a function', () => { + expect(typeof importDocx).toBe('function'); + }); + + it('should be defined', () => { + expect(importDocx).toBeDefined(); + }); +}); + +describe('exportToDocx function', () => { + it('should be exported as a function', () => { + expect(typeof exportToDocx).toBe('function'); + }); + + it('should be defined', () => { + expect(exportToDocx).toBeDefined(); + }); +}); + +describe('downloadDocx function', () => { + it('should be exported as a function', () => { + expect(typeof downloadDocx).toBe('function'); + }); + + it('should be defined', () => { + expect(downloadDocx).toBeDefined(); + }); +}); + +describe('exportEditorToDocx function', () => { + it('should be exported as a function', () => { + expect(typeof exportEditorToDocx).toBe('function'); + }); + + it('should be defined', () => { + expect(exportEditorToDocx).toBeDefined(); + }); +}); + +describe('htmlToDocxBlob function', () => { + it('should be exported as a function', () => { + expect(typeof htmlToDocxBlob).toBe('function'); + }); + + it('should be defined', () => { + expect(htmlToDocxBlob).toBeDefined(); + }); + + it('should create a DOCX blob from basic HTML', async () => { + const html = '<p>Hello world</p>'; + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + expect(blob.type).toBe( + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ); + expect(blob.size).toBeGreaterThan(0); + }); + + it('should handle empty HTML', async () => { + const html = ''; + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + expect(blob.size).toBeGreaterThan(0); // Still creates a valid DOCX structure + }); + + it('should handle HTML with multiple paragraphs', async () => { + const html = '<p>First paragraph</p><p>Second paragraph</p>'; + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + expect(blob.size).toBeGreaterThan(0); + }); + + it('should handle HTML with formatting', async () => { + const html = '<p><strong>Bold</strong> and <em>italic</em> text</p>'; + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + expect(blob.size).toBeGreaterThan(0); + }); + + it('should handle HTML with headings', async () => { + const html = '<h1>Heading 1</h1><h2>Heading 2</h2><p>Body text</p>'; + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + expect(blob.size).toBeGreaterThan(0); + }); + + it('should create different blob sizes for different content', async () => { + const shortHtml = '<p>Short</p>'; + const longHtml = `<p>${'Long text content '.repeat(100)}</p>`; + + const shortBlob = await htmlToDocxBlob(shortHtml); + const longBlob = await htmlToDocxBlob(longHtml); + + expect(longBlob.size).toBeGreaterThan(shortBlob.size); + }); +}); + +describe('Module exports', () => { + it('should export all expected functions', () => { + expect(importDocx).toBeDefined(); + expect(exportToDocx).toBeDefined(); + expect(downloadDocx).toBeDefined(); + expect(exportEditorToDocx).toBeDefined(); + expect(htmlToDocxBlob).toBeDefined(); + }); + + it('should export all expected plugins', () => { + expect(DocxIOPlugin).toBeDefined(); + expect(DocxExportPlugin).toBeDefined(); + expect(DocxIOKit).toBeDefined(); + }); +}); + +describe('DocxIOPlugin re-export', () => { + it('should maintain plugin identity through re-export', () => { + // DocxIOPlugin is a re-export of DocxExportPlugin + // They should be the exact same object + expect(DocxIOPlugin).toBe(DocxExportPlugin); + + // Any properties should be identical + expect(DocxIOPlugin.key).toBe(DocxExportPlugin.key); + }); + + it('should support being used in plugin arrays', () => { + const plugins1 = [DocxIOPlugin]; + const plugins2 = [DocxExportPlugin]; + + // Both should work identically since they're the same plugin + expect(plugins1[0]).toBe(plugins2[0]); + }); +}); + +describe('DocxIOKit usage patterns', () => { + it('should work with spread operator', () => { + const basePlugins: any[] = []; + const allPlugins = [...basePlugins, ...DocxIOKit]; + + expect(allPlugins.length).toBe(1); + expect(allPlugins[0]).toBe(DocxExportPlugin); + }); + + it('should work with concat', () => { + const basePlugins: any[] = []; + const allPlugins = basePlugins.concat(DocxIOKit); + + expect(allPlugins.length).toBe(1); + expect(allPlugins[0]).toBe(DocxExportPlugin); + }); + + it('should work when destructured', () => { + const [plugin] = DocxIOKit; + + expect(plugin).toBe(DocxExportPlugin); + }); +}); + +describe('Package documentation compliance', () => { + it('should provide both import and export functionality', () => { + // As documented, this module provides both import and export + expect(importDocx).toBeDefined(); // Import function + expect(exportToDocx).toBeDefined(); // Export function + }); + + it('should provide plugin for easy integration', () => { + // Plugin should be available for editor configuration + expect(DocxIOPlugin).toBeDefined(); + expect(DocxIOKit).toBeDefined(); + }); + + it('should provide helper functions', () => { + // Additional helper functions for flexibility + expect(downloadDocx).toBeDefined(); + expect(exportEditorToDocx).toBeDefined(); + expect(htmlToDocxBlob).toBeDefined(); + }); +}); diff --git a/packages/docx-io/src/lib/DocxIOPlugin.tsx b/packages/docx-io/src/lib/DocxIOPlugin.tsx new file mode 100644 index 0000000000..8007c8a993 --- /dev/null +++ b/packages/docx-io/src/lib/DocxIOPlugin.tsx @@ -0,0 +1,76 @@ +'use client'; + +/** + * DOCX Import/Export Plugin Kit for Plate.js + * + * This module provides an easy-to-use plugin kit that combines both + * import and export functionality for DOCX files. + * + * @example + * ```tsx + * import { DocxIOPlugin, importDocx, exportToDocx } from '@platejs/docx-io'; + * + * // Use the plugin in your editor + * const editor = createPlateEditor({ + * plugins: [ + * ...BaseEditorKit, + * DocxIOPlugin, + * ], + * }); + * + * // Import a DOCX file + * const { nodes } = await importDocx(editor, arrayBuffer); + * editor.tf.insertNodes(nodes); + * + * // Export to DOCX + * const blob = await exportToDocx(editor.children, { + * editorPlugins: editor.plugins, + * }); + * ``` + * + * @packageDocumentation + */ + +import { DocxExportPlugin } from './docx-export-plugin'; + +export { importDocx } from './importDocx'; +export { + downloadDocx, + exportEditorToDocx, + exportToDocx, +} from './docx-export-plugin'; +export { htmlToDocxBlob } from './exportDocx'; + +/** + * DOCX Import/Export Plugin for Plate.js + * + * This plugin provides both import and export functionality for DOCX files. + * It's a simple re-export of DocxExportPlugin with better discoverability. + * + * For import, use the `importDocx` function directly. + * For export, use the plugin's `exportToDocx` or `downloadDocx` functions. + * + * @example + * ```tsx + * import { DocxIOPlugin } from '@platejs/docx-io'; + * + * const editor = createPlateEditor({ + * plugins: [DocxIOPlugin], + * }); + * ``` + */ +export const DocxIOPlugin = DocxExportPlugin; + +/** + * DOCX Import/Export Kit - array form for use with spread operator + * + * @example + * ```tsx + * import { DocxIOKit } from '@platejs/docx-io'; + * + * const editor = createPlateEditor({ + * plugins: [...BaseEditorKit, ...DocxIOKit], + * }); + * ``` + */ +export const DocxIOKit = [DocxExportPlugin]; diff --git a/packages/docx-io/src/lib/__tests__/complex-tracking-export.spec.ts b/packages/docx-io/src/lib/__tests__/complex-tracking-export.spec.ts new file mode 100644 index 0000000000..9693fa79a6 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/complex-tracking-export.spec.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from 'bun:test'; +import { htmlToDocxBlob } from '../exportDocx'; +import { + buildCommentStartToken, + buildSuggestionStartToken, +} from '../html-to-docx/tracking'; + +describe('Crash Reproduction: Overlapping and Duplicate IDs', () => { + it('should not crash when exporting overlapping comments/suggestions and duplicate reply IDs', async () => { + // 1. Construct Payloads + const discussion1Payload = { + id: 'discussion1', + authorName: 'Charlie', + authorInitials: 'C', + date: '2026-02-06T11:55:06.789Z', + text: 'Comments are a great way to provide feedback and discuss changes.', + replies: [ + { + id: 'comment2', // Duplicate ID + authorName: 'Bob', + authorInitials: 'B', + date: '2026-02-06T11:56:46.789Z', + text: 'Agreed! The link to the docs makes it easy to learn more.', + }, + ], + }; + + const discussion2Payload = { + id: 'discussion2', + authorName: 'Bob', + authorInitials: 'B', + date: '2026-02-06T12:00:06.789Z', + text: 'Nice demonstration of overlapping annotations with both comments and suggestions!', + replies: [ + { + id: 'comment2', // Duplicate ID reused here + authorName: 'Charlie', + authorInitials: 'C', + date: '2026-02-06T12:01:46.789Z', + text: 'This helps users understand how powerful the editor can be.', + }, + ], + }; + + const suggestionPayload = { + id: 'playground3', + type: 'insert', + author: 'charlie', + date: '1770379506741', + }; + + // 2. Construct Tokens + const d1Start = buildCommentStartToken(discussion1Payload); + const d2Start = buildCommentStartToken(discussion2Payload); + const sStart = buildSuggestionStartToken(suggestionPayload, 'insert'); + + // 3. Construct HTML + // Simulating: + // <p> + // <a href="...">[d1]comments[/d1]</a> + // [d1] on many text segments[/d1]. You can even have + // [s][d2]overlapping[/d2][/s] annotations! + // </p> + const html = ` + <p> + Review and refine content seamlessly. Use + <a href="/docs/comment"> + ${d1Start}comments[[DOCX_CMT_END:discussion1]] + </a> + ${d1Start} on many text segments[[DOCX_CMT_END:discussion1]]. + You can even have + ${sStart}${d2Start}overlapping[[DOCX_CMT_END:discussion2]][[DOCX_INS_END:playground3]] + annotations! + </p> + `; + + // 4. Run Export + try { + const blob = await htmlToDocxBlob(html); + expect(blob).toBeDefined(); + expect(blob.size).toBeGreaterThan(0); + } catch (error) { + console.error('Export failed:', error); + throw error; + } + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/docx-io-demo.spec.ts b/packages/docx-io/src/lib/__tests__/docx-io-demo.spec.ts new file mode 100644 index 0000000000..81ff9be0b9 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/docx-io-demo.spec.ts @@ -0,0 +1,129 @@ +/** + * DOCX Import/Export Demo Tests + * + * These tests demonstrate the htmlToDocxBlob utility for + * converting HTML to DOCX format. + */ + +import { describe, expect, it } from 'bun:test'; +import JSZip from 'jszip'; + +// Import directly from exportDocx to avoid platejs dependencies +import { htmlToDocxBlob } from '../exportDocx'; + +const loadZipFromBlob = async (blob: Blob): Promise<JSZip> => { + const arrayBuffer = await blob.arrayBuffer(); + return JSZip.loadAsync(arrayBuffer); +}; + +describe('DOCX Import/Export Demo', () => { + describe('htmlToDocxBlob utility', () => { + it('should convert HTML string to DOCX blob', async () => { + const html = ` + <h1>Document Title</h1> + <p>This is a <strong>formatted</strong> paragraph.</p> + <ul> + <li>Item 1</li> + <li>Item 2</li> + </ul> + `; + + const blob = await htmlToDocxBlob(html); + + expect(blob).toBeInstanceOf(Blob); + + const zip = await loadZipFromBlob(blob); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('Document Title'); + expect(docXml).toContain('formatted'); + expect(docXml).toContain('Item 1'); + expect(docXml).toContain('Item 2'); + }); + + it('should handle empty HTML', async () => { + const blob = await htmlToDocxBlob(''); + + expect(blob).toBeInstanceOf(Blob); + }); + + it('should apply document options', async () => { + const html = '<p>Test content</p>'; + const blob = await htmlToDocxBlob(html, { + orientation: 'landscape', + margins: { + top: 720, // 0.5 inch in TWIP + bottom: 720, + left: 720, + right: 720, + }, + }); + + const zip = await loadZipFromBlob(blob); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('landscape'); + }); + }); + + describe('Complete HTML to DOCX workflow', () => { + it('should produce valid DOCX with all required files', async () => { + const html = ` + <h1>Test Document</h1> + <p>This is a test paragraph with <strong>bold</strong> text.</p> + <p>Second paragraph.</p> + `; + + const blob = await htmlToDocxBlob(html); + + // Verify DOCX structure + const zip = await loadZipFromBlob(blob); + + // Check required DOCX files exist + expect(zip.file('word/document.xml')).not.toBeNull(); + expect(zip.file('[Content_Types].xml')).not.toBeNull(); + expect(zip.file('_rels/.rels')).not.toBeNull(); + + // Verify content + const docXml = await zip.file('word/document.xml')!.async('string'); + expect(docXml).toContain('Test Document'); + expect(docXml).toContain('bold'); + expect(docXml).toContain('Second paragraph'); + }); + + it('should handle tables', async () => { + const html = ` + <table> + <tr> + <td>Cell 1</td> + <td>Cell 2</td> + </tr> + <tr> + <td>Cell 3</td> + <td>Cell 4</td> + </tr> + </table> + `; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('Cell 1'); + expect(docXml).toContain('Cell 4'); + expect(docXml).toContain('<w:tbl'); + }); + + it('should handle links', async () => { + const html = + '<p>Visit <a href="https://example.com">our website</a>.</p>'; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('our website'); + expect(docXml).toContain('<w:hyperlink'); + }); + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/export-comment-ids.spec.ts b/packages/docx-io/src/lib/__tests__/export-comment-ids.spec.ts new file mode 100644 index 0000000000..75621d5a93 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/export-comment-ids.spec.ts @@ -0,0 +1,336 @@ +/** + * Tests for comment ID uniqueness and author resolution during export. + * + * Verifies that: + * - Discussion IDs (not comment IDs) are used as export payload IDs + * - Cross-discussion duplicate comment IDs don't collide + * - Author names flow through correctly to the OOXML output + * - Reply composite IDs are unique per discussion + */ + +import { describe, expect, it } from 'bun:test'; + +import type { TNode } from 'platejs'; + +import type { DocxExportDiscussion } from '../exportTrackChanges'; +import { injectDocxTrackingTokens } from '../exportTrackChanges'; +import type { CommentPayload } from '../html-to-docx/tracking'; +import { splitDocxTrackingTokens } from '../html-to-docx/tracking'; + +/** Extract all CommentPayloads from injected token text for given discussion IDs */ +function extractAllCommentPayloads( + discussionIds: string[], + discussions: DocxExportDiscussion[] +): Map<string, CommentPayload> { + const marks: Record<string, true> = {}; + + for (const id of discussionIds) { + marks[`comment_${id}`] = true; + } + + const value: TNode[] = [ + { + type: 'p', + children: [{ text: 'Annotated text', ...marks }], + }, + ]; + + const result = injectDocxTrackingTokens(value, { discussions }); + const injectedText = ( + (result[0] as Record<string, unknown>).children as Record<string, unknown>[] + )[0].text as string; + + const tokens = splitDocxTrackingTokens(injectedText); + const payloads = new Map<string, CommentPayload>(); + + for (const token of tokens) { + if (token.type === 'commentStart') { + payloads.set(token.data.id, token.data); + } + } + + return payloads; +} + +describe('export comment ID uniqueness', () => { + it('should use discussion ID as payload ID, not comment ID', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'discussion1', + comments: [ + { + id: 'comment1', + contentRich: [{ type: 'p', children: [{ text: 'Root comment' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Root comment', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + ]; + + const payloads = extractAllCommentPayloads(['discussion1'], discussions); + + expect(payloads.size).toBe(1); + // Payload ID must be the discussion ID, NOT the comment ID + expect(payloads.has('discussion1')).toBe(true); + expect(payloads.has('comment1')).toBe(false); + }); + + it('should not collide when two discussions use the same comment IDs', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'discussion1', + comments: [ + { + id: 'comment1', // Duplicate across discussions + contentRich: [{ type: 'p', children: [{ text: 'From Alice' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'From Alice', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + { + id: 'discussion2', + comments: [ + { + id: 'comment1', // Same ID as above — must not collide + contentRich: [{ type: 'p', children: [{ text: 'From Bob' }] }], + createdAt: '2025-01-15T11:00:00.000Z', + user: { id: 'bob', name: 'Bob' }, + userId: 'bob', + }, + ], + createdAt: '2025-01-15T11:00:00.000Z', + documentContent: 'From Bob', + user: { id: 'bob', name: 'Bob' }, + userId: 'bob', + }, + ]; + + const payloads = extractAllCommentPayloads( + ['discussion1', 'discussion2'], + discussions + ); + + // Must produce 2 distinct payloads keyed by discussion ID + expect(payloads.size).toBe(2); + + const d1 = payloads.get('discussion1'); + const d2 = payloads.get('discussion2'); + expect(d1).toBeDefined(); + expect(d2).toBeDefined(); + + // Author names must be distinct (no collision/overwrite) + expect(d1!.authorName).toBe('Alice'); + expect(d2!.authorName).toBe('Bob'); + + // Payload IDs must differ + expect(d1!.id).not.toBe(d2!.id); + }); + + it('should produce unique reply composite IDs across discussions', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'discussion1', + comments: [ + { + id: 'comment1', + contentRich: [{ type: 'p', children: [{ text: 'Root 1' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + { + id: 'reply1', // Same reply ID in both discussions + contentRich: [{ type: 'p', children: [{ text: 'Reply in d1' }] }], + createdAt: '2025-01-15T11:00:00.000Z', + user: { id: 'bob', name: 'Bob' }, + userId: 'bob', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Root 1', + user: { id: 'alice', name: 'Alice' }, + userId: 'alice', + }, + { + id: 'discussion2', + comments: [ + { + id: 'comment1', + contentRich: [{ type: 'p', children: [{ text: 'Root 2' }] }], + createdAt: '2025-01-15T12:00:00.000Z', + user: { id: 'charlie', name: 'Charlie' }, + userId: 'charlie', + }, + { + id: 'reply1', // Same reply ID as discussion1 + contentRich: [{ type: 'p', children: [{ text: 'Reply in d2' }] }], + createdAt: '2025-01-15T13:00:00.000Z', + user: { id: 'dave', name: 'Dave' }, + userId: 'dave', + }, + ], + createdAt: '2025-01-15T12:00:00.000Z', + documentContent: 'Root 2', + user: { id: 'charlie', name: 'Charlie' }, + userId: 'charlie', + }, + ]; + + const payloads = extractAllCommentPayloads( + ['discussion1', 'discussion2'], + discussions + ); + + const d1 = payloads.get('discussion1'); + const d2 = payloads.get('discussion2'); + + expect(d1!.replies).toHaveLength(1); + expect(d2!.replies).toHaveLength(1); + + // Reply IDs in the payload are preserved from the comment data + expect(d1!.replies![0].id).toBe('reply1'); + expect(d2!.replies![0].id).toBe('reply1'); + + // But the discussion IDs (used as lookup keys) are different, + // so xml-builder will create composite IDs: + // "discussion1-reply-reply1" vs "discussion2-reply-reply1" + // — these are unique because the parent ID differs + expect(d1!.id).toBe('discussion1'); + expect(d2!.id).toBe('discussion2'); + + // Author names must be preserved per discussion + expect(d1!.replies![0].authorName).toBe('Bob'); + expect(d2!.replies![0].authorName).toBe('Dave'); + }); +}); + +describe('export comment author resolution', () => { + it('should resolve author from user.name', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + comments: [ + { + contentRich: [{ type: 'p', children: [{ text: 'Comment' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'user-1', name: 'Jane Doe' }, + userId: 'user-1', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Comment', + user: { id: 'user-1', name: 'Jane Doe' }, + userId: 'user-1', + }, + ]; + + const payloads = extractAllCommentPayloads(['disc-1'], discussions); + const payload = payloads.get('disc-1'); + + expect(payload!.authorName).toBe('Jane Doe'); + expect(payload!.authorInitials).toBe('JD'); + }); + + it('should fallback to userId when user.name is missing', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + comments: [ + { + contentRich: [{ type: 'p', children: [{ text: 'Comment' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + userId: 'charlie', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Comment', + userId: 'charlie', + }, + ]; + + const payloads = extractAllCommentPayloads(['disc-1'], discussions); + const payload = payloads.get('disc-1'); + + // Falls back to userId when no user.name + expect(payload!.authorName).toBe('charlie'); + }); + + it('should use "unknown" when no author info available', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + comments: [ + { + contentRich: [{ type: 'p', children: [{ text: 'Orphan' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Orphan', + }, + ]; + + const payloads = extractAllCommentPayloads(['disc-1'], discussions); + const payload = payloads.get('disc-1'); + + expect(payload!.authorName).toBe('unknown'); + }); + + it('should use userNameMap for author resolution', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + comments: [ + { + contentRich: [{ type: 'p', children: [{ text: 'Comment' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + userId: 'user-42', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Comment', + userId: 'user-42', + }, + ]; + + const userNameMap = new Map([['user-42', 'Mapped Name']]); + + const value = [ + { + type: 'p', + children: [{ text: 'text', 'comment_disc-1': true }], + }, + ] as TNode[]; + + const result = injectDocxTrackingTokens(value, { + discussions, + userNameMap, + }); + const injectedText = ( + (result[0] as Record<string, unknown>).children as Record< + string, + unknown + >[] + )[0].text as string; + + const tokens = splitDocxTrackingTokens(injectedText); + const commentStart = tokens.find((t) => t.type === 'commentStart'); + + expect(commentStart).toBeDefined(); + if (commentStart?.type === 'commentStart') { + expect(commentStart.data.authorName).toBe('Mapped Name'); + } + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/export-ooxml-comments.spec.ts b/packages/docx-io/src/lib/__tests__/export-ooxml-comments.spec.ts new file mode 100644 index 0000000000..02ad6e68cb --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/export-ooxml-comments.spec.ts @@ -0,0 +1,184 @@ +/** + * Tests for OOXML comment XML generation via htmlToDocxBlob. + * + * Verifies that: + * - Comments produce valid comments.xml with correct authors + * - Reply threading produces commentsExtended.xml with parentParaId + * - Multiple discussions don't collide in the OOXML output + * - paraId values are unique across all comments + */ + +import { describe, expect, it } from 'bun:test'; +import JSZip from 'jszip'; + +import { htmlToDocxBlob } from '../exportDocx'; +import { + buildCommentStartToken, + buildCommentEndToken, +} from '../html-to-docx/tracking'; + +async function loadZipFromBlob(blob: Blob): Promise<JSZip> { + const arrayBuffer = await blob.arrayBuffer(); + return JSZip.loadAsync(arrayBuffer); +} + +async function getXmlFromZip(zip: JSZip, path: string): Promise<string | null> { + const file = zip.file(path); + if (!file) return null; + return file.async('text'); +} + +describe('OOXML comment generation', () => { + it('should produce comments.xml with correct author names', async () => { + const payload = { + id: 'discussion1', + authorName: 'Alice', + authorInitials: 'A', + date: '2025-01-15T10:00:00.000Z', + text: 'Great work!', + }; + + const token = buildCommentStartToken(payload); + const html = `<p>${token}annotated text${buildCommentEndToken('discussion1')}</p>`; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const commentsXml = await getXmlFromZip(zip, 'word/comments.xml'); + + expect(commentsXml).not.toBeNull(); + // Author should be "Alice", not "unknown" + expect(commentsXml).toContain('author="Alice"'); + expect(commentsXml).toContain('initials="A"'); + }); + + it('should preserve distinct authors for two discussions', async () => { + const d1 = { + id: 'discussion1', + authorName: 'Alice', + authorInitials: 'A', + date: '2025-01-15T10:00:00.000Z', + text: 'Comment from Alice', + }; + + const d2 = { + id: 'discussion2', + authorName: 'Bob', + authorInitials: 'B', + date: '2025-01-15T11:00:00.000Z', + text: 'Comment from Bob', + }; + + const html = ` + <p> + ${buildCommentStartToken(d1)}text one${buildCommentEndToken('discussion1')} + ${buildCommentStartToken(d2)}text two${buildCommentEndToken('discussion2')} + </p> + `; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const commentsXml = await getXmlFromZip(zip, 'word/comments.xml'); + + expect(commentsXml).not.toBeNull(); + expect(commentsXml).toContain('author="Alice"'); + expect(commentsXml).toContain('author="Bob"'); + }); + + it('should thread replies via commentsExtended.xml', async () => { + const payload = { + id: 'discussion1', + authorName: 'Alice', + authorInitials: 'A', + date: '2025-01-15T10:00:00.000Z', + text: 'Root comment', + replies: [ + { + id: 'reply1', + authorName: 'Bob', + authorInitials: 'B', + date: '2025-01-15T11:00:00.000Z', + text: 'Reply from Bob', + }, + ], + }; + + const html = `<p>${buildCommentStartToken(payload)}annotated${buildCommentEndToken('discussion1')}</p>`; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + + const commentsXml = await getXmlFromZip(zip, 'word/comments.xml'); + const extendedXml = await getXmlFromZip(zip, 'word/commentsExtended.xml'); + + expect(commentsXml).not.toBeNull(); + // Both authors present + expect(commentsXml).toContain('author="Alice"'); + expect(commentsXml).toContain('author="Bob"'); + + // commentsExtended should exist with threading + expect(extendedXml).not.toBeNull(); + // Should contain paraIdParent for the reply + expect(extendedXml).toContain('paraIdParent'); + }); + + it('should produce unique paraIds for all comments', async () => { + const d1 = { + id: 'disc1', + authorName: 'Alice', + authorInitials: 'A', + text: 'Comment 1', + replies: [{ id: 'r1', authorName: 'Bob', text: 'Reply 1' }], + }; + + const d2 = { + id: 'disc2', + authorName: 'Charlie', + authorInitials: 'C', + text: 'Comment 2', + }; + + const html = ` + <p> + ${buildCommentStartToken(d1)}first${buildCommentEndToken('disc1')} + ${buildCommentStartToken(d2)}second${buildCommentEndToken('disc2')} + </p> + `; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const commentsXml = await getXmlFromZip(zip, 'word/comments.xml'); + + expect(commentsXml).not.toBeNull(); + + // Extract all paraId values from comments.xml + const paraIdMatches = commentsXml!.match(/paraId="([^"]+)"/g); + expect(paraIdMatches).not.toBeNull(); + expect(paraIdMatches!.length).toBeGreaterThanOrEqual(3); // 2 roots + 1 reply + + // All paraIds must be unique + const paraIds = paraIdMatches!.map((m) => m.replace(/paraId="|"/g, '')); + const uniqueParaIds = new Set(paraIds); + expect(uniqueParaIds.size).toBe(paraIds.length); + }); + + it('should not produce "unknown" author when author is provided', async () => { + const payload = { + id: 'disc1', + authorName: 'Charlie', + authorInitials: 'C', + date: '2025-01-15T10:00:00.000Z', + text: 'Named comment', + }; + + const html = `<p>${buildCommentStartToken(payload)}text${buildCommentEndToken('disc1')}</p>`; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + const commentsXml = await getXmlFromZip(zip, 'word/comments.xml'); + + expect(commentsXml).not.toBeNull(); + expect(commentsXml).toContain('author="Charlie"'); + // "unknown" should not appear when author is explicitly provided + expect(commentsXml).not.toContain('author="unknown"'); + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/export-replies-id.spec.ts b/packages/docx-io/src/lib/__tests__/export-replies-id.spec.ts new file mode 100644 index 0000000000..ce3c109057 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/export-replies-id.spec.ts @@ -0,0 +1,133 @@ +/** + * Tests for resolveCommentMeta() reply ID handling. + */ + +import { describe, expect, it } from 'bun:test'; + +import type { TNode } from 'platejs'; + +import type { DocxExportDiscussion } from '../exportTrackChanges'; +import { injectDocxTrackingTokens } from '../exportTrackChanges'; +import type { CommentPayload } from '../html-to-docx/tracking'; +import { splitDocxTrackingTokens } from '../html-to-docx/tracking'; + +/** Extract the first CommentPayload from injected token text */ +function extractCommentPayload( + discussionId: string, + discussions: DocxExportDiscussion[] +): CommentPayload | undefined { + const value: TNode[] = [ + { + type: 'p', + children: [ + { + text: 'Hello world', + [`comment_${discussionId}`]: true, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { discussions }); + const injectedText = ( + (result[0] as Record<string, unknown>).children as Record<string, unknown>[] + )[0].text as string; + + const tokens = splitDocxTrackingTokens(injectedText); + const commentStart = tokens.find((t) => t.type === 'commentStart'); + + if (commentStart && commentStart.type === 'commentStart') { + return commentStart.data; + } + + return; +} + +describe('resolveCommentMeta with reply IDs', () => { + it('should include IDs in replies', () => { + const discussionId = 'disc-001'; + + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment text' }] }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + // Parent doesn't need explicit ID in this structure as discussion.id is used + }, + { + id: 'reply-1', // ID we want to preserve + contentRich: [ + { type: 'p', children: [{ text: 'First reply text' }] }, + ], + createdAt: '2025-01-15T11:00:00.000Z', + user: { id: 'user-2', name: 'Bob Reviewer' }, + userId: 'user-2', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Parent comment text', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + ]; + + const payload = extractCommentPayload(discussionId, discussions); + + expect(payload).toBeDefined(); + expect(payload!.replies).toBeDefined(); + expect(payload!.replies).toHaveLength(1); + + // This check is expected to fail initially because 'id' is not passed through + expect(payload!.replies![0]!.id).toBe('reply-1'); + }); + + it('should generate IDs for replies when missing', () => { + const discussionId = 'disc-002'; + + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment text' }] }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + { + // Missing ID, should be generated + contentRich: [ + { type: 'p', children: [{ text: 'Reply without ID' }] }, + ], + createdAt: '2025-01-15T11:00:00.000Z', + user: { id: 'user-2', name: 'Bob Reviewer' }, + userId: 'user-2', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Parent comment text', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + ]; + + const payload = extractCommentPayload(discussionId, discussions); + + expect(payload).toBeDefined(); + expect(payload!.replies).toBeDefined(); + expect(payload!.replies).toHaveLength(1); + + const replyId = payload!.replies![0]!.id; + expect(replyId).toBeDefined(); + expect(typeof replyId).toBe('string'); + expect(replyId.length).toBeGreaterThan(0); + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/export-replies.spec.ts b/packages/docx-io/src/lib/__tests__/export-replies.spec.ts new file mode 100644 index 0000000000..f2508cce48 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/export-replies.spec.ts @@ -0,0 +1,146 @@ +/** + * Tests for resolveCommentMeta() reply handling. + * + * resolveCommentMeta is private, so we test through the public API: + * injectDocxTrackingTokens -> token text -> splitDocxTrackingTokens + * + * resolveCommentMeta now populates replies from discussion.comments[1..n]. + */ + +import { describe, expect, it } from 'bun:test'; + +import type { TNode } from 'platejs'; + +import type { DocxExportDiscussion } from '../exportTrackChanges'; +import { injectDocxTrackingTokens } from '../exportTrackChanges'; +import type { CommentPayload } from '../html-to-docx/tracking'; +import { splitDocxTrackingTokens } from '../html-to-docx/tracking'; + +/** Extract the first CommentPayload from injected token text */ +function extractCommentPayload( + discussionId: string, + discussions: DocxExportDiscussion[] +): CommentPayload | undefined { + const value: TNode[] = [ + { + type: 'p', + children: [ + { + text: 'Hello world', + [`comment_${discussionId}`]: true, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { discussions }); + const injectedText = ( + (result[0] as Record<string, unknown>).children as Record<string, unknown>[] + )[0].text as string; + + const tokens = splitDocxTrackingTokens(injectedText); + const commentStart = tokens.find((t) => t.type === 'commentStart'); + + if (commentStart && commentStart.type === 'commentStart') { + return commentStart.data; + } + + return; +} + +describe('resolveCommentMeta with replies', () => { + it('should include replies array from multi-comment discussion', () => { + const discussionId = 'disc-001'; + + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment text' }] }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + { + contentRich: [ + { type: 'p', children: [{ text: 'First reply text' }] }, + ], + createdAt: '2025-01-15T11:00:00.000Z', + user: { id: 'user-2', name: 'Bob Reviewer' }, + userId: 'user-2', + }, + { + contentRich: [ + { type: 'p', children: [{ text: 'Second reply text' }] }, + ], + createdAt: '2025-01-15T12:00:00.000Z', + user: { id: 'user-3', name: 'Carol Editor' }, + userId: 'user-3', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Parent comment text', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + ]; + + const payload = extractCommentPayload(discussionId, discussions); + + expect(payload).toBeDefined(); + expect(payload!.id).toBe(discussionId); + + // Parent metadata + expect(payload!.authorName).toBe('Alice Author'); + expect(payload!.date).toBe('2025-01-15T10:00:00Z'); + + // Replies + expect(payload!.replies).toBeDefined(); + expect(payload!.replies).toHaveLength(2); + + // First reply + expect(payload!.replies![0].authorName).toBe('Bob Reviewer'); + expect(payload!.replies![0].text).toBe('First reply text'); + expect(payload!.replies![0].date).toBe('2025-01-15T11:00:00Z'); + + // Second reply + expect(payload!.replies![1].authorName).toBe('Carol Editor'); + expect(payload!.replies![1].text).toBe('Second reply text'); + expect(payload!.replies![1].date).toBe('2025-01-15T12:00:00Z'); + }); + + it('should return empty replies for single-comment discussion', () => { + const discussionId = 'disc-002'; + + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [{ type: 'p', children: [{ text: 'Only comment' }] }], + createdAt: '2025-01-15T10:00:00.000Z', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + ], + createdAt: '2025-01-15T10:00:00.000Z', + documentContent: 'Only comment', + user: { id: 'user-1', name: 'Alice Author' }, + userId: 'user-1', + }, + ]; + + const payload = extractCommentPayload(discussionId, discussions); + + expect(payload).toBeDefined(); + expect(payload!.id).toBe(discussionId); + expect(payload!.authorName).toBe('Alice Author'); + + // Single comment -> no replies (empty array or undefined) + const replies = payload!.replies ?? []; + expect(replies).toHaveLength(0); + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/lists.spec.tsx b/packages/docx-io/src/lib/__tests__/lists.spec.tsx index b3f157b7c9..ef9c31a060 100644 --- a/packages/docx-io/src/lib/__tests__/lists.spec.tsx +++ b/packages/docx-io/src/lib/__tests__/lists.spec.tsx @@ -8,56 +8,24 @@ jsx; const name = 'lists'; -// TODO: ListPlugin uses indent-based lists, not nested structure -// mammoth output: ol/ul preserved with nesting -describe.skip(getDocxTestName(name), () => { +// BaseListPlugin uses indent-based lists, so nested ol/ul/li from mammoth +// are deserialized into flat paragraphs (list structure is flattened). +describe(getDocxTestName(name), () => { testDocxImporter({ expected: ( <editor> <hh2>Some nested lists</hh2> - <hol> - <hli> - <hlic>one</hlic> - </hli> - <hli> - <hlic>two</hlic> - <hol> - <hli> - <hlic>a</hlic> - </hli> - <hli> - <hlic>b</hlic> - </hli> - </hol> - </hli> - </hol> - <hul> - <hli> - <hlic>one</hlic> - </hli> - <hli> - <hlic>two</hlic> - <hul> - <hli> - <hlic>three</hlic> - <hul> - <hli> - <hlic>four</hlic> - </hli> - </hul> - </hli> - </hul> - </hli> - </hul> + <hp>one</hp> + <hp>two</hp> + <hp>a</hp> + <hp>b</hp> + <hp>one</hp> + <hp>two</hp> + <hp>three</hp> + <hp>four</hp> <hp>Sub paragraph</hp> - <hul> - <hli> - <hlic>Same list</hlic> - </hli> - <hli> - <hlic>Different list adjacent to the one above.</hlic> - </hli> - </hul> + <hp>Same list</hp> + <hp>Different list adjacent to the one above.</hp> </editor> ), filename: name, diff --git a/packages/docx-io/src/lib/__tests__/preprocessMammothHtml.spec.ts b/packages/docx-io/src/lib/__tests__/preprocessMammothHtml.spec.ts new file mode 100644 index 0000000000..371a478bc6 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/preprocessMammothHtml.spec.ts @@ -0,0 +1,20 @@ +import { preprocessMammothHtml } from '../importDocx'; + +describe('preprocessMammothHtml', () => { + it('throws when DOMParser is unavailable', () => { + const globalWithDom = globalThis as Omit<typeof globalThis, 'DOMParser'> & { + DOMParser?: typeof DOMParser; + }; + const originalDOMParser = globalWithDom.DOMParser; + + try { + globalWithDom.DOMParser = undefined; + + expect(() => preprocessMammothHtml('<p>Test</p>')).toThrow( + 'preprocessMammothHtml requires DOMParser (browser-like environment).' + ); + } finally { + globalWithDom.DOMParser = originalDOMParser; + } + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/roundtrip-audit.spec.ts b/packages/docx-io/src/lib/__tests__/roundtrip-audit.spec.ts new file mode 100644 index 0000000000..2a7c130b48 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/roundtrip-audit.spec.ts @@ -0,0 +1,198 @@ +/** + * Round-trip audit: Export → Unzip → Inspect XML → Verify + * + * Checks that everything we put into the export comes out correctly + * in the OOXML ZIP, and that mammoth can parse it back. + */ +import { describe, expect, it } from 'bun:test'; +import JSZip from 'jszip'; + +import { htmlToDocxBlob } from '../exportDocx'; +import { + buildCommentStartToken, + buildCommentEndToken, + type CommentPayload, +} from '../html-to-docx/tracking'; + +async function loadZip(blob: Blob): Promise<JSZip> { + return JSZip.loadAsync(await blob.arrayBuffer()); +} + +async function getXml(zip: JSZip, path: string): Promise<string | null> { + const file = zip.file(path); + return file ? file.async('text') : null; +} + +// Build a full HTML with comment tokens +function buildCommentHtml(payload: CommentPayload): string { + const start = buildCommentStartToken(payload); + const end = buildCommentEndToken(payload.id); + return `<p>${start}annotated text${end}</p>`; +} + +describe('Round-trip audit: export OOXML correctness', () => { + it('comments.xml should have correct author, not "unknown"', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + authorInitials: 'A', + date: '2025-01-15T10:00:00.000Z', + text: 'Hello', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/comments.xml'); + + expect(xml).toContain('author="Alice"'); + expect(xml).not.toContain('author="unknown"'); + }); + + it('comments.xml should have paraId on <w:p> element', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + paraId: 'AABBCCDD', + text: 'Test', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/comments.xml'); + + expect(xml).toContain('AABBCCDD'); + }); + + it('commentsExtended.xml should exist and contain paraId', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + paraId: '11223344', + text: 'Test', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/commentsExtended.xml'); + + expect(xml).not.toBeNull(); + expect(xml).toContain('11223344'); + }); + + it('reply should have parentParaId in commentsExtended.xml', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + paraId: 'PARENT01', + text: 'Root comment', + replies: [ + { + id: 'reply1', + authorName: 'Bob', + paraId: 'REPLY001', + text: 'Reply text', + }, + ], + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const extXml = await getXml(zip, 'word/commentsExtended.xml'); + + expect(extXml).not.toBeNull(); + // Root comment paraId + expect(extXml).toContain('PARENT01'); + // Reply paraId + expect(extXml).toContain('REPLY001'); + // Reply should reference parent + expect(extXml).toContain('paraIdParent'); + }); + + it('document.xml should have commentRangeStart and commentRangeEnd', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + text: 'Test', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/document.xml'); + + expect(xml).toContain('commentRangeStart'); + expect(xml).toContain('commentRangeEnd'); + expect(xml).toContain('commentReference'); + }); + + it('styles.xml should define CommentText and CommentReference styles', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + text: 'Test', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/styles.xml'); + + // These styles are referenced by comments.xml — if missing, mammoth warns + expect(xml).toContain('CommentText'); + expect(xml).toContain('CommentReference'); + }); + + it('commentsExtended.xml namespace should use w15 prefix readable by mammoth', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + paraId: 'AABB1122', + text: 'Test', + replies: [ + { + id: 'r1', + authorName: 'Bob', + paraId: 'CCDD3344', + text: 'Reply', + }, + ], + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/commentsExtended.xml'); + + // mammoth expects element names like <w15:commentEx w15:paraId="..."> + // Check the actual namespace URI is declared + expect(xml).toContain( + 'http://schemas.microsoft.com/office/word/2012/wordml' + ); + // Check element name format - should be w15:commentEx or namespace-qualified + expect(xml).toContain('commentEx'); + expect(xml).toContain('paraId'); + }); + + it('two discussions should produce two separate comment entries', async () => { + const p1 = buildCommentStartToken({ + id: 'disc1', + authorName: 'Alice', + text: 'First', + }); + const p2 = buildCommentStartToken({ + id: 'disc2', + authorName: 'Bob', + text: 'Second', + }); + const html = + `<p>${p1}text1${buildCommentEndToken('disc1')}</p>` + + `<p>${p2}text2${buildCommentEndToken('disc2')}</p>`; + + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/comments.xml'); + + expect(xml).toContain('author="Alice"'); + expect(xml).toContain('author="Bob"'); + // Should have w:id="1" and w:id="2" + const idMatches = xml!.match(/w:id="(\d+)"/g); + expect(idMatches!.length).toBeGreaterThanOrEqual(2); + }); + + it('no tokens should remain in document.xml text', async () => { + const html = buildCommentHtml({ + id: 'disc1', + authorName: 'Alice', + text: 'Test', + }); + const zip = await loadZip(await htmlToDocxBlob(html)); + const xml = await getXml(zip, 'word/document.xml'); + + expect(xml).not.toContain('[[DOCX_CMT_START'); + expect(xml).not.toContain('[[DOCX_CMT_END'); + expect(xml).not.toContain('DOCX_INS_START'); + expect(xml).not.toContain('DOCX_DEL_START'); + }); +}); diff --git a/packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx b/packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx index 7032fc9be2..6924ebcf88 100644 --- a/packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx +++ b/packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx @@ -1,23 +1,90 @@ -/** @jsx jsx */ +/** biome-ignore-all lint/suspicious/noEvolvingTypes: test file */ +/** biome-ignore-all lint/suspicious/noImplicitAnyLet: test file */ +/** biome-ignore-all lint/suspicious/noAssignInExpressions: test file */ +/** biome-ignore-all lint/suspicious/useIterableCallbackReturn: test file */ import fs from 'node:fs'; import path from 'node:path'; +import { + BaseBlockquotePlugin, + BaseBoldPlugin, + BaseCodePlugin, + BaseH1Plugin, + BaseH2Plugin, + BaseH3Plugin, + BaseH4Plugin, + BaseH5Plugin, + BaseH6Plugin, + BaseHorizontalRulePlugin, + BaseItalicPlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseUnderlinePlugin, +} from '@platejs/basic-nodes'; +import { BaseTextAlignPlugin } from '@platejs/basic-styles'; +import { BaseCommentPlugin, getCommentKey } from '@platejs/comment'; import { cleanDocx } from '@platejs/docx'; -import type { SlatePlugin, TNode, Value } from 'platejs'; -import { createSlateEditor } from 'platejs'; +import { BaseLinkPlugin } from '@platejs/link'; +import { BaseListPlugin } from '@platejs/list'; +import { BaseSuggestionPlugin } from '@platejs/suggestion'; +import { + BaseTableCellHeaderPlugin, + BaseTableCellPlugin, + BaseTablePlugin, + BaseTableRowPlugin, +} from '@platejs/table'; +import JSZip from 'jszip'; +import type { TNode, Value } from 'platejs'; +import { + BaseParagraphPlugin, + createSlateEditor, + KEYS, + NodeApi, + TextApi, +} from 'platejs'; import { serializeHtml } from 'platejs/static'; -import { jsx } from '@platejs/test-utils'; -import mammoth from 'mammoth'; -import { BaseEditorKit } from 'www/src/registry/components/editor/editor-base-kit'; -import { DocxExportKit } from 'www/src/registry/components/editor/plugins/docx-export-kit'; - -import { htmlToDocxBlob } from '../html-to-docx'; -import { preprocessMammothHtml } from '../preprocessMammothHtml'; - -jsx; - -const editorPlugins = [...BaseEditorKit, ...DocxExportKit] as SlatePlugin[]; +import { DocxExportPlugin } from '../docx-export-plugin'; +import { exportToDocx, htmlToDocxBlob } from '../docx-export-plugin'; +import type { DocxExportDiscussion } from '../exportTrackChanges'; +import { + applyTrackedCommentsLocal, + parseDocxTracking, +} from '../importComments'; +import { mammoth, preprocessMammothHtml } from '../importDocx'; +import { searchRange } from '../searchRange'; + +/** Minimal editor kit for roundtrip tests (no registry dependency). */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const editorPlugins: any[] = [ + BaseParagraphPlugin, + BaseH1Plugin, + BaseH2Plugin, + BaseH3Plugin, + BaseH4Plugin, + BaseH5Plugin, + BaseH6Plugin, + BaseBlockquotePlugin, + BaseHorizontalRulePlugin, + BaseBoldPlugin, + BaseItalicPlugin, + BaseUnderlinePlugin, + BaseCodePlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseTablePlugin.configure({ render: { as: 'table' } }), + BaseTableRowPlugin.configure({ render: { as: 'tr' } }), + BaseTableCellPlugin.configure({ render: { as: 'td' } }), + BaseTableCellHeaderPlugin.configure({ render: { as: 'th' } }), + BaseLinkPlugin.configure({ render: { as: 'a' } }), + BaseListPlugin, + BaseTextAlignPlugin, + BaseCommentPlugin, + BaseSuggestionPlugin, + DocxExportPlugin, +]; const createTestEditor = (value?: Value) => createSlateEditor({ @@ -35,12 +102,28 @@ const readDocxFixture = (filename: string): Buffer => { return fs.readFileSync(filepath); }; +const readMammothFixture = (filename: string): Buffer => { + // Mammoth test fixtures live in the mammoth npm package + const docxTestDir = path.resolve( + require.resolve('mammoth/package.json'), + '../test/test-data' + ); + const filepath = path.join(docxTestDir, `${filename}.docx`); + + return fs.readFileSync(filepath); +}; + const importDocxBuffer = async ( editor: ReturnType<typeof createTestEditor>, buffer: Buffer ): Promise<TNode[]> => { + // Convert Node Buffer to ArrayBuffer for mammoth + const arrayBuffer = buffer.buffer.slice( + buffer.byteOffset, + buffer.byteOffset + buffer.byteLength + ) as ArrayBuffer; const mammothResult = await mammoth.convertToHtml( - { buffer }, + { arrayBuffer }, { styleMap: ['comment-reference => sup'] } ); @@ -68,6 +151,33 @@ const exportNodesToDocx = async (nodes: TNode[]): Promise<Buffer> => { return Buffer.from(arrayBuffer); }; +const loadZipFromBlob = async (blob: Blob): Promise<JSZip> => { + const arrayBuffer = await blob.arrayBuffer(); + return JSZip.loadAsync(arrayBuffer); +}; + +const hasCommentMark = (nodes: TNode[]): boolean => { + let found = false; + + const visit = (node: TNode) => { + if (found) return; + if (typeof node.text === 'string') { + if (Object.keys(node).some((key) => key.startsWith('comment_'))) { + found = true; + } + return; + } + + if ('children' in node && Array.isArray(node.children)) { + node.children.forEach((child) => visit(child as TNode)); + } + }; + + nodes.forEach((node) => visit(node)); + + return found; +}; + /** * Roundtrip test: import → export → reimport * @@ -143,4 +253,434 @@ describe('docx roundtrip', () => { expect(nodesD.length).toBeGreaterThan(0); // Note: nodesD won't equal nodesB due to mark/linebreak loss }); + + // NOTE: Skipped - requires mammoth npm package test fixtures not in vendored fork + it.skip('should roundtrip comments without ref tokens and with non-empty ranges', async () => { + const buffer = readMammothFixture('comments'); + const editor = createTestEditor(); + + // Convert Node Buffer to ArrayBuffer for mammoth + const arrayBuffer = buffer.buffer.slice( + buffer.byteOffset, + buffer.byteOffset + buffer.byteLength + ) as ArrayBuffer; + const mammothResult = await mammoth.convertToHtml( + { arrayBuffer }, + { styleMap: ['comment-reference => sup'] } + ); + + const { html: preprocessedHtml } = preprocessMammothHtml( + mammothResult.value + ); + + expect(preprocessedHtml).not.toContain('DOCX_COMMENT_REF'); + expect(preprocessedHtml).not.toContain('comment-ref-'); + + const cleanedHtml = cleanDocx(preprocessedHtml, ''); + const doc = new DOMParser().parseFromString(cleanedHtml, 'text/html'); + const nodes = editor.api.html.deserialize({ element: doc.body }) as TNode[]; + + expect(JSON.stringify(nodes)).not.toContain('DOCX_COMMENT_REF'); + + const tracking = parseDocxTracking(mammothResult.value); + const commentEditor = createTestEditor(nodes as Value); + let discussionIndex = 0; + + const commentsResult = applyTrackedCommentsLocal({ + editor: commentEditor as Parameters< + typeof applyTrackedCommentsLocal + >[0]['editor'], + comments: tracking.comments.comments, + searchRange: (_editor, search) => + searchRange(commentEditor as Parameters<typeof searchRange>[0], search), + commentKey: KEYS.comment, + getCommentKey, + isText: TextApi.isText, + generateId: () => `discussion-${(discussionIndex += 1)}`, + documentDate: new Date(), + }); + + expect(commentsResult.discussions.length).toBeGreaterThan(0); + expect(hasCommentMark(commentEditor.children as TNode[])).toBe(true); + + const docxDiscussions: DocxExportDiscussion[] = + commentsResult.discussions.map((discussion) => ({ + id: discussion.id, + comments: discussion.comments?.map((comment) => ({ + contentRich: comment.contentRich, + createdAt: comment.createdAt, + userId: comment.userId, + user: comment.user, + })), + createdAt: discussion.createdAt, + documentContent: discussion.documentContent, + userId: discussion.userId, + user: discussion.user, + })); + + const exportBlob = await exportToDocx(commentEditor.children as Value, { + editorPlugins, + tracking: { + discussions: docxDiscussions, + nodeToString: (node: unknown) => { + try { + return NodeApi.string(node as Parameters<typeof NodeApi.string>[0]); + } catch { + return ''; + } + }, + }, + }); + + const zip = await loadZipFromBlob(exportBlob); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('<w:commentRangeStart'); + expect(docXml).toContain('<w:commentRangeEnd'); + + const startRegex = /<w:commentRangeStart[^>]*w:id="(\d+)"/g; + let match; + + while ((match = startRegex.exec(docXml)) !== null) { + const id = match[1]; + const remaining = docXml.slice(match.index); + const endMatch = new RegExp(`<w:commentRangeEnd[^>]*w:id="${id}"`).exec( + remaining + ); + + expect(endMatch).not.toBeNull(); + if (!endMatch) continue; + + const endIndex = match.index + endMatch.index; + const between = docXml.slice(match.index, endIndex); + expect(between).toMatch(/<w:t\b|<w:delText\b|<w:ins\b|<w:del\b/); + } + }); + + it('should contain all 5 comment XML files in exported DOCX ZIP', async () => { + // Create editor value with a comment mark + const discussionId = 'disc-1'; + const value: Value = [ + { + type: 'p', + children: [ + { + text: 'Hello commented world', + [`comment_${discussionId}`]: true, + }, + ], + }, + ]; + + // Discussion with 1 parent comment + 1 reply + const docxDiscussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment' }] }, + ], + createdAt: new Date('2025-01-01T00:00:00Z'), + userId: 'user-1', + user: { id: 'user-1', name: 'Alice' }, + }, + { + contentRich: [{ type: 'p', children: [{ text: 'Reply comment' }] }], + createdAt: new Date('2025-01-01T01:00:00Z'), + userId: 'user-2', + user: { id: 'user-2', name: 'Bob' }, + }, + ], + createdAt: new Date('2025-01-01T00:00:00Z'), + documentContent: 'Hello commented world', + userId: 'user-1', + user: { id: 'user-1', name: 'Alice' }, + }, + ]; + + const exportBlob = await exportToDocx(value, { + editorPlugins, + tracking: { + discussions: docxDiscussions, + nodeToString: (node: unknown) => { + try { + return NodeApi.string(node as Parameters<typeof NodeApi.string>[0]); + } catch { + return ''; + } + }, + }, + }); + + const zip = await loadZipFromBlob(exportBlob); + + const expectedFiles = [ + 'word/comments.xml', + 'word/commentsExtended.xml', + 'word/commentsIds.xml', + 'word/commentsExtensible.xml', + 'word/people.xml', + ]; + + for (const filePath of expectedFiles) { + expect(zip.file(filePath)).not.toBeNull(); + } + }); + + it('should produce people.xml with unique authors only', async () => { + // Build a minimal editor value with a comment mark on "Hello" + const value: Value = [ + { + type: 'p', + children: [{ text: 'Hello', comment_disc1: true }, { text: ' world' }], + }, + ]; + + // Discussion: parent by Alice, reply by Bob + const docxDiscussions: DocxExportDiscussion[] = [ + { + id: 'disc1', + userId: 'alice-id', + user: { id: 'alice-id', name: 'Alice' }, + createdAt: new Date('2026-01-01T00:00:00Z'), + documentContent: 'Hello', + comments: [ + { + userId: 'alice-id', + user: { id: 'alice-id', name: 'Alice' }, + createdAt: new Date('2026-01-01T00:00:00Z'), + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment' }] }, + ], + }, + { + userId: 'bob-id', + user: { id: 'bob-id', name: 'Bob' }, + createdAt: new Date('2026-01-01T00:01:00Z'), + contentRich: [{ type: 'p', children: [{ text: 'Reply comment' }] }], + }, + ], + }, + ]; + + const exportBlob = await exportToDocx(value, { + editorPlugins, + tracking: { + discussions: docxDiscussions, + nodeToString: (node: unknown) => { + try { + return NodeApi.string(node as Parameters<typeof NodeApi.string>[0]); + } catch { + return ''; + } + }, + }, + }); + + const zip = await loadZipFromBlob(exportBlob); + const peopleFile = zip.file('word/people.xml'); + + expect(peopleFile).not.toBeNull(); + + const peopleXml = await peopleFile!.async('string'); + + // Extract all <w15:person> elements with their w15:author attributes + const personRegex = /<w15:person\b[^>]*w15:author="([^"]*)"/g; + const authors: string[] = []; + let personMatch; + + while ((personMatch = personRegex.exec(peopleXml)) !== null) { + authors.push(personMatch[1]); + } + + // Exactly 2 unique authors: Alice and Bob + expect(authors).toHaveLength(2); + expect(authors).toContain('Alice'); + expect(authors).toContain('Bob'); + + // Verify uniqueness -- no duplicates + const uniqueAuthors = [...new Set(authors)]; + expect(uniqueAuthors).toHaveLength(authors.length); + }); + + it('should export commentsExtended.xml with correct paraIdParent linking for replies', async () => { + // Create editor value with a comment mark on text + const discussionId = 'disc-parent-reply'; + const value: Value = [ + { + type: 'p', + children: [ + { + text: 'Hello world with comment', + [`comment_${discussionId}`]: true, + }, + ], + }, + ]; + + // Discussion with parent comment (comments[0]) and a reply (comments[1]) + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Parent comment' }] }, + ], + createdAt: new Date('2024-01-01T00:00:00Z'), + user: { id: 'user1', name: 'Alice' }, + userId: 'user1', + }, + { + contentRich: [ + { type: 'p', children: [{ text: 'Reply to parent' }] }, + ], + createdAt: new Date('2024-01-01T01:00:00Z'), + user: { id: 'user2', name: 'Bob' }, + userId: 'user2', + }, + ], + createdAt: new Date('2024-01-01T00:00:00Z'), + documentContent: 'Hello world with comment', + user: { id: 'user1', name: 'Alice' }, + userId: 'user1', + }, + ]; + + const exportBlob = await exportToDocx(value, { + editorPlugins, + tracking: { + discussions, + nodeToString: (node: unknown) => { + try { + return NodeApi.string(node as Parameters<typeof NodeApi.string>[0]); + } catch { + return ''; + } + }, + }, + }); + + const zip = await loadZipFromBlob(exportBlob); + + // commentsExtended.xml must exist + const commentsExtFile = zip.file('word/commentsExtended.xml'); + expect(commentsExtFile).not.toBeNull(); + + const commentsExtXml = await commentsExtFile!.async('string'); + + // Parse all w15:commentEx elements (self-closing or with body) + const commentExRegex = /<w15:commentEx[^/>]*\/?>/g; + const commentExElements: string[] = []; + let ceMatch; + + while ((ceMatch = commentExRegex.exec(commentsExtXml)) !== null) { + commentExElements.push(ceMatch[0]); + } + + // Should have at least 2 commentEx elements (parent + reply) + expect(commentExElements.length).toBeGreaterThanOrEqual(2); + + // Helper: extract an attribute value from an XML element string + const parseAttr = (el: string, attr: string): string | null => { + const attrRegex = new RegExp(`${attr}="([^"]+)"`); + const m = attrRegex.exec(el); + return m ? m[1] : null; + }; + + // Find the parent commentEx (has w15:paraId but NO w15:paraIdParent) + const parentElements = commentExElements.filter( + (el) => parseAttr(el, 'w15:paraId') && !parseAttr(el, 'w15:paraIdParent') + ); + expect(parentElements.length).toBeGreaterThanOrEqual(1); + + const parentParaId = parseAttr(parentElements[0], 'w15:paraId'); + expect(parentParaId).toBeTruthy(); + + // Find the reply commentEx (has w15:paraIdParent matching parent's w15:paraId) + const replyElements = commentExElements.filter( + (el) => parseAttr(el, 'w15:paraIdParent') === parentParaId + ); + expect(replyElements.length).toBeGreaterThanOrEqual(1); + + // Reply must also have its own paraId + const replyParaId = parseAttr(replyElements[0], 'w15:paraId'); + expect(replyParaId).toBeTruthy(); + + // Parent and reply paraIds must be different + expect(replyParaId).not.toBe(parentParaId); + }); + + it('should round-trip comment date through export', async () => { + const inputDate = '2025-01-15T10:30:00Z'; + const discussionId = 'date-roundtrip-disc'; + + const value: Value = [ + { + type: 'p', + children: [ + { text: 'Before ' }, + { text: 'dated comment', [`comment_${discussionId}`]: true }, + { text: ' after.' }, + ], + }, + ]; + + const discussions: DocxExportDiscussion[] = [ + { + id: discussionId, + comments: [ + { + contentRich: [ + { type: 'p', children: [{ text: 'Comment with date' }] }, + ], + createdAt: inputDate, + user: { id: 'user-1', name: 'Test User' }, + userId: 'user-1', + }, + ], + createdAt: inputDate, + documentContent: 'dated comment', + user: { id: 'user-1', name: 'Test User' }, + userId: 'user-1', + }, + ]; + + const exportBlob = await exportToDocx(value, { + editorPlugins, + tracking: { + discussions, + nodeToString: (node: unknown) => { + try { + return NodeApi.string(node as Parameters<typeof NodeApi.string>[0]); + } catch { + return ''; + } + }, + }, + }); + + const zip = await loadZipFromBlob(exportBlob); + const commentsFile = zip.file('word/comments.xml'); + expect(commentsFile).not.toBeNull(); + + const commentsXml = await commentsFile!.async('string'); + + // Extract all w:date attributes from w:comment elements + const commentDateRegex = /<w:comment[^>]*w:date="([^"]*)"/g; + const dates: string[] = []; + let dateMatch; + + while ((dateMatch = commentDateRegex.exec(commentsXml)) !== null) { + dates.push(dateMatch[1]); + } + + expect(dates.length).toBeGreaterThan(0); + + // Verify the exported date represents the same instant as input (may differ in tz offset) + const inputMs = new Date(inputDate).getTime(); + const matchesInstant = dates.some((d) => new Date(d).getTime() === inputMs); + expect(matchesInstant).toBe(true); + }); }); diff --git a/packages/docx-io/src/lib/__tests__/testDocxImporter.tsx b/packages/docx-io/src/lib/__tests__/testDocxImporter.tsx index 74fb7163e8..3880f27f4b 100644 --- a/packages/docx-io/src/lib/__tests__/testDocxImporter.tsx +++ b/packages/docx-io/src/lib/__tests__/testDocxImporter.tsx @@ -3,36 +3,42 @@ import fs from 'node:fs'; import path from 'node:path'; +import { + BaseBlockquotePlugin, + BaseBoldPlugin, + BaseCodePlugin, + BaseH1Plugin, + BaseH2Plugin, + BaseH3Plugin, + BaseH4Plugin, + BaseH5Plugin, + BaseH6Plugin, + BaseHorizontalRulePlugin, + BaseItalicPlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseUnderlinePlugin, +} from '@platejs/basic-nodes'; +import { BaseTextAlignPlugin } from '@platejs/basic-styles'; import { cleanDocx } from '@platejs/docx'; -import type { SlatePlugin, TNode } from 'platejs'; -import { createSlateEditor } from 'platejs'; -import { TextAlignPlugin } from '@platejs/basic-styles/react'; +import { BaseLinkPlugin } from '@platejs/link'; +import { BaseListPlugin } from '@platejs/list'; import { - BasicBlocksPlugin, - BasicMarksPlugin, -} from '@platejs/basic-nodes/react'; -import { HorizontalRulePlugin } from '@platejs/basic-nodes/react'; -import { CodeBlockPlugin } from '@platejs/code-block/react'; -import { IndentPlugin } from '@platejs/indent/react'; -import { LineHeightPlugin } from '@platejs/basic-styles/react'; -import { LinkPlugin } from '@platejs/link/react'; -import { ListPlugin } from '@platejs/list/react'; -import { ImagePlugin } from '@platejs/media/react'; -import { TablePlugin } from '@platejs/table/react'; + BaseTableCellHeaderPlugin, + BaseTableCellPlugin, + BaseTablePlugin, + BaseTableRowPlugin, +} from '@platejs/table'; import { jsx } from '@platejs/test-utils'; -import mammoth from 'mammoth'; +import type { SlatePlugin, TNode } from 'platejs'; +import { BaseParagraphPlugin, createSlateEditor } from 'platejs'; -import { preprocessMammothHtml } from '../preprocessMammothHtml'; +import { mammoth, preprocessMammothHtml } from '../importDocx'; // biome-ignore lint/nursery/noUnusedExpressions: test jsx; -const injectConfig = { - inject: { - targetPlugins: ['p', 'h1', 'h2', 'h3'], - }, -}; - /** Read .docx file from docx package's __tests__ directory */ export const readDocxFixture = (filename: string): Buffer => { const docxTestDir = path.resolve( @@ -64,26 +70,44 @@ export const testDocxImporter = ({ }, plugins: [ ...plugins, - ImagePlugin, - HorizontalRulePlugin, - CodeBlockPlugin, - LinkPlugin, - BasicBlocksPlugin, - BasicMarksPlugin, - ListPlugin, - TablePlugin, - LineHeightPlugin.extend(() => injectConfig), - TextAlignPlugin.extend(() => injectConfig), - IndentPlugin.extend(() => injectConfig), + BaseParagraphPlugin, + BaseH1Plugin, + BaseH2Plugin, + BaseH3Plugin, + BaseH4Plugin, + BaseH5Plugin, + BaseH6Plugin, + BaseBlockquotePlugin, + BaseHorizontalRulePlugin, + BaseBoldPlugin, + BaseItalicPlugin, + BaseUnderlinePlugin, + BaseCodePlugin, + BaseStrikethroughPlugin, + BaseSubscriptPlugin, + BaseSuperscriptPlugin, + BaseLinkPlugin, + BaseListPlugin, + BaseTablePlugin, + BaseTableRowPlugin, + BaseTableCellPlugin, + BaseTableCellHeaderPlugin, + BaseTextAlignPlugin, ], }); // Read docx file as Node Buffer const buffer = readDocxFixture(filename); - // Use mammoth with buffer option (Node.js compatible) + // Convert Node Buffer to ArrayBuffer for mammoth + const arrayBuffer = buffer.buffer.slice( + buffer.byteOffset, + buffer.byteOffset + buffer.byteLength + ) as ArrayBuffer; + + // Use mammoth with arrayBuffer option const mammothResult = await mammoth.convertToHtml( - { buffer }, + { arrayBuffer }, { styleMap: ['comment-reference => sup'] } ); diff --git a/packages/docx-io/src/lib/__tests__/xml-builder-falsy-ids.spec.ts b/packages/docx-io/src/lib/__tests__/xml-builder-falsy-ids.spec.ts new file mode 100644 index 0000000000..b7ff789949 --- /dev/null +++ b/packages/docx-io/src/lib/__tests__/xml-builder-falsy-ids.spec.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from 'bun:test'; +import JSZip from 'jszip'; +import { htmlToDocxBlob } from '../exportDocx'; +import { buildCommentStartToken } from '../html-to-docx/tracking'; + +async function loadZipFromBlob(blob: Blob): Promise<JSZip> { + const arrayBuffer = await blob.arrayBuffer(); + return JSZip.loadAsync(arrayBuffer); +} + +describe('XML Builder Regression Tests', () => { + it('should handle falsy but valid IDs (empty string) for replies without overriding them', async () => { + // Construct a comment payload where a reply has an empty string ID + const payload = { + id: 'cmt-1', + authorName: 'Test User', + text: 'Parent', + replies: [ + { + id: '', // Empty string ID (falsy) + authorName: 'Reply User', + text: 'Reply with empty ID', + }, + ], + }; + + // We expect the XML builder to use "" as the ID, not "cmt-1-reply-0" + // And subsequently track it and close it properly. + + const token = buildCommentStartToken(payload); + const html = `<p>Text ${token}Commented${'[[DOCX_CMT_END:cmt-1]]'}</p>`; + + const blob = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(blob); + + // Check comments.xml to see if a comment was created for the reply + const commentsXml = await zip.file('word/comments.xml')!.async('string'); + + // There should be 2 comments: parent and reply. + // The reply should have the text "Reply with empty ID" + expect(commentsXml).toContain('Reply with empty ID'); + + // Check document.xml to ensure ranges are closed + const docXml = await zip.file('word/document.xml')!.async('string'); + + // We expect 2 pairs of commentRangeStart/End + // Counting them is a rough check + const startCount = (docXml.match(/<w:commentRangeStart/g) || []).length; + const endCount = (docXml.match(/<w:commentRangeEnd/g) || []).length; + + expect(startCount).toBe(2); + expect(endCount).toBe(2); + }); +}); diff --git a/packages/docx-io/src/lib/applyDocxTracking.spec.ts b/packages/docx-io/src/lib/applyDocxTracking.spec.ts new file mode 100644 index 0000000000..3fd58dea19 --- /dev/null +++ b/packages/docx-io/src/lib/applyDocxTracking.spec.ts @@ -0,0 +1,1254 @@ +import { describe, expect, it, mock } from 'bun:test'; + +const mockFn = <T extends (...args: any[]) => any>(fn: T): T => + mock(fn) as unknown as T; + +import { + applyAllTracking, + applyTrackedComments, + applyTrackedCommentsLocal, + type DocxImportComment, +} from './importComments'; +import { + applyTrackedChangeSuggestions, + type TrackingEditor, + type TRange, +} from './importTrackChanges'; +import type { DocxTrackedChange } from './types'; + +// Mock editor factory +function createMockEditor(): TrackingEditor { + return { + api: { + string: mockFn(() => 'sample text'), + rangeRef: (range: TRange) => ({ + current: range, + unref: mockFn(() => range), + }), + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + setOption: mockFn(() => {}), + }; +} + +// Mock search function that returns predictable ranges +function createMockSearchRange( + tokenMap?: Map<string, TRange | null> +): (editor: TrackingEditor, search: string) => TRange | null { + return (_editor, search) => { + if (tokenMap) { + return tokenMap.get(search) ?? null; + } + // Default behavior: return a range for tokens + if (search.includes('START') || search.includes('END')) { + return { + anchor: { path: [0, 0], offset: 0 }, + focus: { path: [0, 0], offset: search.length }, + }; + } + return null; + }; +} + +describe('applyDocxTracking', () => { + describe('applyTrackedChangeSuggestions', () => { + it('returns zero counts for empty changes', () => { + const editor = createMockEditor(); + const result = applyTrackedChangeSuggestions({ + editor, + changes: [], + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.insertions).toBe(0); + expect(result.deletions).toBe(0); + expect(result.total).toBe(0); + expect(result.errors).toEqual([]); + }); + + it('applies insertion suggestion', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'ins-1', + type: 'insert', + author: 'John Doe', + date: '2024-01-15T12:00:00Z', + startToken: '[[START:ins-1]]', + endToken: '[[END:ins-1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.insertions).toBe(1); + expect(result.deletions).toBe(0); + expect(result.total).toBe(1); + expect(editor.tf.setNodes).toHaveBeenCalled(); + }); + + it('applies deletion suggestion', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'del-1', + type: 'remove', + author: 'Jane Doe', + startToken: '[[START:del-1]]', + endToken: '[[END:del-1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.insertions).toBe(0); + expect(result.deletions).toBe(1); + expect(result.total).toBe(1); + }); + + it('handles missing start token', () => { + const editor = createMockEditor(); + const tokenMap = new Map<string, TRange | null>(); + tokenMap.set('[[START:missing]]', null); + tokenMap.set('[[END:missing]]', { + anchor: { path: [0, 0], offset: 0 }, + focus: { path: [0, 0], offset: 10 }, + }); + + const changes: DocxTrackedChange[] = [ + { + id: 'missing', + type: 'insert', + startToken: '[[START:missing]]', + endToken: '[[END:missing]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(tokenMap), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.total).toBe(0); + expect(result.errors.length).toBe(1); + expect(result.errors[0]).toContain('Missing token'); + }); + + it('handles missing end token', () => { + const editor = createMockEditor(); + const tokenMap = new Map<string, TRange | null>(); + tokenMap.set('[[START:missing]]', { + anchor: { path: [0, 0], offset: 0 }, + focus: { path: [0, 0], offset: 10 }, + }); + tokenMap.set('[[END:missing]]', null); + + const changes: DocxTrackedChange[] = [ + { + id: 'missing', + type: 'insert', + startToken: '[[START:missing]]', + endToken: '[[END:missing]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(tokenMap), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.total).toBe(0); + expect(result.errors.length).toBe(1); + }); + + it('applies multiple changes', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + { + id: 'change-2', + type: 'remove', + startToken: '[[START:2]]', + endToken: '[[END:2]]', + }, + { + id: 'change-3', + type: 'insert', + startToken: '[[START:3]]', + endToken: '[[END:3]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: (node) => + typeof (node as Record<string, unknown>).text === 'string', + }); + + expect(result.insertions).toBe(2); + expect(result.deletions).toBe(1); + expect(result.total).toBe(3); + }); + + it('deletes tokens after applying marks', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'ins-1', + type: 'insert', + startToken: '[[START:ins-1]]', + endToken: '[[END:ins-1]]', + }, + ]; + + applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + // Delete should be called twice (start and end tokens) + expect(editor.tf.delete).toHaveBeenCalledTimes(2); + }); + + it('handles invalid rangeRef (null current)', () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => ({ + current: null, + unref: mockFn(() => null), + }), + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.total).toBe(0); + expect(result.errors.length).toBe(1); + expect(result.errors[0]).toContain('Invalid range'); + }); + + it('handles exceptions during processing', () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => { + throw new Error('Test error'); + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.total).toBe(0); + expect(result.errors.length).toBe(1); + expect(result.errors[0]).toContain('Failed to apply change'); + expect(result.errors[0]).toContain('Test error'); + }); + + it('handles non-Error exceptions', () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => { + throw new Error('string error'); + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.errors[0]).toContain('string error'); + }); + + it('handles missing author (uses default)', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + author: undefined, + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.total).toBe(1); + }); + + it('handles invalid date (uses current time)', () => { + const editor = createMockEditor(); + const changes: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + date: 'invalid-date', + startToken: '[[START:1]]', + endToken: '[[END:1]]', + }, + ]; + + const result = applyTrackedChangeSuggestions({ + editor, + changes, + searchRange: createMockSearchRange(), + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }); + + expect(result.total).toBe(1); + }); + }); + + describe('applyTrackedComments', () => { + it('returns zero counts for empty comments', async () => { + const editor = createMockEditor(); + const result = await applyTrackedComments({ + editor, + comments: [], + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(0); + expect(result.skipped).toBe(0); + expect(result.errors).toEqual([]); + }); + + it('creates comment for valid tokens', async () => { + const editor = createMockEditor(); + const createDiscussion = mockFn(async () => ({ id: 'disc-new' })); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { mutateAsync: createDiscussion }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(1); + expect(result.skipped).toBe(0); + expect(createDiscussion).toHaveBeenCalledWith( + expect.objectContaining({ + documentId: 'doc-1', + }) + ); + }); + + it('handles null ranges during cleanup', async () => { + const refs: Array<{ + current: TRange | null; + unref: () => TRange | null; + }> = []; + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: (range: TRange) => { + const ref = { current: range, unref: mockFn(() => range) }; + refs.push(ref); + return ref; + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + refs.forEach((ref) => { + ref.current = null; + }); + }), + }, + setOption: mockFn(() => {}), + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(1); + expect(result.errors).toEqual([]); + expect(editor.tf.setNodes).toHaveBeenCalled(); + expect(editor.tf.delete).not.toHaveBeenCalled(); + }); + + it('skips comment with no tokens found', async () => { + const editor = createMockEditor(); + const tokenMap = new Map<string, TRange | null>(); + tokenMap.set('[[CMT_START:1]]', null); + tokenMap.set('[[CMT_END:1]]', null); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(tokenMap), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(0); + expect(result.skipped).toBe(1); + }); + + it('creates point comment when only start token found', async () => { + const editor = createMockEditor(); + const tokenMap = new Map<string, TRange | null>(); + tokenMap.set('[[CMT_START:1]]', { + anchor: { path: [0, 0], offset: 0 }, + focus: { path: [0, 0], offset: 10 }, + }); + tokenMap.set('[[CMT_END:1]]', null); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Point comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: false, // Indicates end token not in original HTML + }, + ]; + + const createDiscussion = mockFn(async () => ({ id: 'disc-point' })); + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(tokenMap), + documentId: 'doc-1', + createDiscussionWithComment: { mutateAsync: createDiscussion }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(1); + }); + + it('applies transient comment key when provided', async () => { + const editor = createMockEditor(); + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + getTransientCommentKey: () => 'transient_comment', + isText: () => true, + }); + + expect(editor.tf.setNodes).toHaveBeenCalledWith( + expect.objectContaining({ + transient_comment: true, + }), + expect.any(Object) + ); + }); + + it('calls onCommentsCreated callback when comments are created', async () => { + const editor = createMockEditor(); + const onCommentsCreated = mockFn(() => {}); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + onCommentsCreated, + }); + + expect(onCommentsCreated).toHaveBeenCalledTimes(1); + }); + + it('does not call onCommentsCreated when no comments created', async () => { + const editor = createMockEditor(); + const onCommentsCreated = mockFn(() => {}); + + const tokenMap = new Map<string, TRange | null>(); + tokenMap.set('[[CMT_START:1]]', null); + tokenMap.set('[[CMT_END:1]]', null); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(tokenMap), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + onCommentsCreated, + }); + + expect(onCommentsCreated).not.toHaveBeenCalled(); + }); + + it('handles invalid rangeRef (null current) for comments', async () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => ({ + current: null, + unref: mockFn(() => null), + }), + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.created).toBe(0); + expect(result.skipped).toBe(1); + }); + + it('handles empty document content (uses default)', async () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => ''), // Returns empty string + rangeRef: (range: TRange) => ({ + current: range, + unref: mockFn(() => range), + }), + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const createDiscussion = mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { mutateAsync: createDiscussion }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(createDiscussion).toHaveBeenCalledWith( + expect.objectContaining({ + documentContent: 'Imported comment', + }) + ); + }); + + it('handles comment with commentPlugin and setOption', async () => { + const editor = createMockEditor(); + const commentPlugin = { key: 'comment' }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + commentPlugin, + }); + + expect(editor.setOption).toHaveBeenCalledWith( + commentPlugin, + 'updateTimestamp', + expect.any(Number) + ); + }); + + it('handles exceptions during comment processing', async () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => { + throw new Error('Comment error'); + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.errors.length).toBe(1); + expect(result.errors[0]).toContain('Failed to apply comment'); + expect(result.errors[0]).toContain('Comment error'); + }); + + it('unrefs rangeRefs when create discussion fails', async () => { + const startUnref = mockFn(() => null); + const endUnref = mockFn(() => null); + let rangeRefCalls = 0; + + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: (range: TRange) => { + rangeRefCalls += 1; + return { + current: range, + unref: rangeRefCalls === 1 ? startUnref : endUnref, + }; + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn(async () => { + throw new Error('API fail'); + }), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.errors.length).toBe(1); + expect(startUnref).toHaveBeenCalledTimes(1); + expect(endUnref).toHaveBeenCalledTimes(1); + }); + + it('handles non-Error exceptions in comments', async () => { + const editor: TrackingEditor = { + api: { + string: mockFn(() => 'sample text'), + rangeRef: () => { + throw new Error('string comment error'); + }, + }, + tf: { + setNodes: mockFn(() => {}), + delete: mockFn(() => {}), + withMerging: mockFn((fn: () => void) => { + fn(); + }), + }, + }; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Test', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(result.errors[0]).toContain('string comment error'); + }); + + it('handles comment without text (undefined)', async () => { + const editor = createMockEditor(); + const createDiscussion = mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: undefined, + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + await applyTrackedComments({ + editor, + comments, + searchRange: createMockSearchRange(), + documentId: 'doc-1', + createDiscussionWithComment: { mutateAsync: createDiscussion }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }); + + expect(createDiscussion).toHaveBeenCalledWith( + expect.objectContaining({ + contentRich: undefined, + }) + ); + }); + }); + + describe('applyTrackedCommentsLocal', () => { + it('removes reply tokens without creating discussions', () => { + const editor = createMockEditor(); + + const comments: DocxImportComment[] = [ + { + id: 'cmt-reply', + text: 'Reply text', + startToken: '[[CMT_START:reply]]', + endToken: '[[CMT_END:reply]]', + hasStartToken: true, + hasEndToken: true, + parentParaId: 'parent-para-1', + }, + ]; + + const result = applyTrackedCommentsLocal({ + editor, + comments, + searchRange: createMockSearchRange(), + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + generateId: () => 'discussion-1', + documentDate: new Date(), + }); + + expect(result.applied).toBe(0); + expect(result.discussions).toEqual([]); + expect(result.errors).toEqual([]); + expect(editor.tf.setNodes).not.toHaveBeenCalled(); + expect(editor.tf.delete).toHaveBeenCalledTimes(2); + }); + }); + + describe('applyAllTracking', () => { + it('applies both suggestions and comments', async () => { + const editor = createMockEditor(); + + const trackedChanges: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[INS_START:1]]', + endToken: '[[INS_END:1]]', + }, + ]; + + const comments: DocxImportComment[] = [ + { + id: 'cmt-1', + text: 'Comment', + startToken: '[[CMT_START:1]]', + endToken: '[[CMT_END:1]]', + hasStartToken: true, + hasEndToken: true, + }, + ]; + + const result = await applyAllTracking({ + editor, + trackedChanges, + comments, + searchRange: createMockSearchRange(), + suggestionConfig: { + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }, + commentConfig: { + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }, + }); + + expect(result.suggestions.total).toBe(1); + expect(result.comments?.created).toBe(1); + expect(result.totalApplied).toBe(2); + }); + + it('applies only suggestions when no comment config', async () => { + const editor = createMockEditor(); + + const trackedChanges: DocxTrackedChange[] = [ + { + id: 'change-1', + type: 'insert', + startToken: '[[INS_START:1]]', + endToken: '[[INS_END:1]]', + }, + ]; + + const result = await applyAllTracking({ + editor, + trackedChanges, + searchRange: createMockSearchRange(), + suggestionConfig: { + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }, + }); + + expect(result.suggestions.total).toBe(1); + expect(result.comments).toBeNull(); + expect(result.totalApplied).toBe(1); + }); + + it('skips comments when empty array', async () => { + const editor = createMockEditor(); + + const result = await applyAllTracking({ + editor, + trackedChanges: [], + comments: [], + searchRange: createMockSearchRange(), + suggestionConfig: { + suggestionKey: 'suggestion', + getSuggestionKey: (id) => `suggestion_${id}`, + isText: () => true, + }, + commentConfig: { + documentId: 'doc-1', + createDiscussionWithComment: { + mutateAsync: mockFn( + async (_input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => ({ id: 'disc-1' }) + ), + }, + commentKey: 'comment', + getCommentKey: (id) => `comment_${id}`, + isText: () => true, + }, + }); + + expect(result.suggestions.total).toBe(0); + expect(result.comments).toBeNull(); // Skipped due to empty array + expect(result.totalApplied).toBe(0); + }); + }); +}); diff --git a/packages/docx-io/src/lib/docx-export-plugin.tsx b/packages/docx-io/src/lib/docx-export-plugin.tsx index 57e4aeb5f7..485b32ae76 100644 --- a/packages/docx-io/src/lib/docx-export-plugin.tsx +++ b/packages/docx-io/src/lib/docx-export-plugin.tsx @@ -33,15 +33,20 @@ 'use client'; import type { SlatePlugin, Value } from 'platejs'; +import juice from 'juice'; import { createSlateEditor, createSlatePlugin } from 'platejs'; import type { PlateStaticProps, SerializeHtmlOptions } from 'platejs/static'; import { serializeHtml } from 'platejs/static'; -import juice from 'juice'; - import type { DocumentMargins } from './html-to-docx'; -import { htmlToDocxBlob } from './html-to-docx'; +import { htmlToDocxBlob } from './exportDocx'; +import { + buildUserNameMap, + injectDocxTrackingTokens, + type DocxExportDiscussion, + type InjectDocxTrackingTokensOptions, +} from './exportTrackChanges'; // ============================================================================= // CSS Styles for DOCX Export @@ -155,6 +160,35 @@ export type DocxExportMargins = DocumentMargins; */ export type DocxExportOrientation = 'landscape' | 'portrait'; +/** + * Options for tracked changes/comments export. + */ +export type DocxTrackingExportOptions = { + /** + * Discussion threads for comment metadata. + * Each discussion represents a comment thread with its comments. + */ + discussions?: DocxExportDiscussion[] | null; + + /** + * Custom function to get comment IDs from a text node. + * If not provided, uses default implementation that looks for 'comment_' prefixed keys. + */ + getCommentIds?: InjectDocxTrackingTokensOptions['getCommentIds']; + + /** + * Custom function to get suggestion metadata from a text node. + * If not provided, uses default implementation that looks for 'suggestion_' prefixed keys. + */ + getSuggestions?: InjectDocxTrackingTokensOptions['getSuggestions']; + + /** + * Function to convert rich content to plain text. + * Used for extracting comment text from rich content. + */ + nodeToString?: InjectDocxTrackingTokensOptions['nodeToString']; +}; + /** * Options for DOCX export operations. */ @@ -202,6 +236,13 @@ export type DocxExportOperationOptions = { * Document title (for metadata purposes). */ title?: string; + + /** + * Options for exporting tracked changes and comments. + * When provided, tracking tokens will be injected into the document + * and converted to Word tracked changes format. + */ + tracking?: DocxTrackingExportOptions; }; /** @@ -409,22 +450,39 @@ async function exportToDocxInternal( fontFamily, margins = DEFAULT_DOCX_MARGINS, orientation = 'portrait', + tracking, value, } = options; + // Process tracking tokens if enabled + let processedValue: Value = value; + + if (tracking) { + const userNameMap = buildUserNameMap(tracking.discussions); + processedValue = injectDocxTrackingTokens(value, { + discussions: tracking.discussions, + getCommentIds: tracking.getCommentIds, + getSuggestions: tracking.getSuggestions, + nodeToString: tracking.nodeToString, + userNameMap, + }) as Value; + } + // Serialize editor content to HTML const bodyHtml = await serializeToHtml({ EditorStaticComponent: editorStaticComponent, components, fontFamily, plugins: editorPlugins, - value, + value: processedValue, }); // Wrap in complete HTML document const fullHtml = wrapHtmlForDocx(bodyHtml, customStyles); // Inline CSS styles using juice for DOCX compatibility + // html-to-docx only reads inline style="" attributes, so CSS <style> blocks + // (e.g. table borders, backgrounds) must be inlined first. const inlinedHtml = juice(fullHtml, { removeStyleTags: false, preserveMediaQueries: false, @@ -670,4 +728,11 @@ export const DocxExportPlugin = createSlatePlugin({ // Re-exports // ============================================================================= -export { htmlToDocxBlob } from './html-to-docx'; +export { htmlToDocxBlob } from './exportDocx'; + +// Re-export tracking types and utilities for convenience +export type { DocxExportDiscussion } from './exportTrackChanges'; +export { + injectDocxTrackingTokens, + buildUserNameMap, +} from './exportTrackChanges'; diff --git a/packages/docx-io/src/lib/exportComments.ts b/packages/docx-io/src/lib/exportComments.ts new file mode 100644 index 0000000000..c4c0147c69 --- /dev/null +++ b/packages/docx-io/src/lib/exportComments.ts @@ -0,0 +1,29 @@ +/** + * DOCX Comments Export + * + * This module provides types and utilities specific to comment export. + * The actual token injection is handled by exportTrackChanges.ts since + * track changes and comments are processed together during export. + * + * For token injection, use injectDocxTrackingTokens from exportTrackChanges.ts + */ + +// Re-export comment-related types from exportTrackChanges +export type { + DocxExportComment, + DocxExportDiscussion, + DocxExportUser, + InjectDocxTrackingTokensOptions, +} from './exportTrackChanges'; + +// Re-export utility functions useful for comments +export { + buildUserNameMap, + createDiscussionsForTransientComments, + normalizeDate, + normalizeDateUtc, + toInitials, +} from './exportTrackChanges'; + +// Re-export the main injection function +export { injectDocxTrackingTokens } from './exportTrackChanges'; diff --git a/packages/docx-io/src/lib/exportDocx.ts b/packages/docx-io/src/lib/exportDocx.ts new file mode 100644 index 0000000000..fad21fa184 --- /dev/null +++ b/packages/docx-io/src/lib/exportDocx.ts @@ -0,0 +1,100 @@ +/** + * HTML to DOCX converter using html-to-docx + * + * This module wraps the html-to-docx library to provide + * a simple API for converting HTML content to DOCX format. + * + * IMPORTANT: This uses native DOCX element generation (not altChunk). + * altChunk embeds raw HTML and only works in Microsoft Word - it breaks + * in LibreOffice and Google Docs. This library converts HTML to native + * DOCX elements (<w:p>, <w:r>, <w:t>, tables, images, etc.) which works + * in all word processors. + * + * @packageDocumentation + */ + +import JSZip from 'jszip'; + +import addFilesToContainer from './html-to-docx/index'; + +// Re-export types from the library +export type { + DocumentOptions, + HeadingOptions, + HeadingSpacing, + HeadingStyleOptions, + LineNumberOptions, + Margins, + NumberingOptions, + PageSize, + TableBorderOptions, + TableOptions, +} from './html-to-docx/index'; + +import type { DocumentOptions, Margins } from './html-to-docx/index'; + +// Backwards compatibility aliases +export type DocumentMargins = Margins; +export type HtmlToDocxOptions = DocumentOptions; + +const DOCX_MIME_TYPE = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; + +async function createDocxZip(html: string, options: DocumentOptions) { + // Handle empty HTML - the underlying library crashes on empty string + const safeHtml = html.trim() === '' ? '<p></p>' : html; + + // Create a new JSZip instance + const zip = new JSZip(); + + // Add files to the zip container + // Parameters: (zip, htmlString, options, headerHTML, footerHTML) + return addFilesToContainer(zip, safeHtml, options); +} + +/** + * Convert HTML content to a DOCX blob. + * + * This function uses html-to-docx to create a valid DOCX file + * from HTML content with proper support for images, tables, and styling. + * + * @param html - The HTML content to convert + * @param options - Optional document configuration (orientation, margins, etc.) + * @returns A Promise that resolves to a Blob containing the DOCX file + * + * @example + * ```typescript + * const html = '<h1>Hello World</h1><p>This is a paragraph.</p>'; + * const blob = await htmlToDocxBlob(html, { orientation: 'landscape' }); + * + * // Download the file + * const url = URL.createObjectURL(blob); + * const a = document.createElement('a'); + * a.href = url; + * a.download = 'document.docx'; + * a.click(); + * ``` + */ +export async function htmlToDocxBlob( + html: string, + options: DocumentOptions = {} +): Promise<Blob> { + // Handle empty HTML - the underlying library crashes on empty string + const safeHtml = html.trim() === '' ? '<p></p>' : html; + + // Create a new JSZip instance + const zip = new JSZip(); + + // Add files to the zip container + // Parameters: (zip, htmlString, options, headerHTML, footerHTML) + const populatedZip = await addFilesToContainer(zip, safeHtml, options); + + // Generate the DOCX blob from the populated zip + const result = await populatedZip.generateAsync({ + type: 'blob', + mimeType: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }); + + return result; +} diff --git a/packages/docx-io/src/lib/exportTrackChanges.ts b/packages/docx-io/src/lib/exportTrackChanges.ts new file mode 100644 index 0000000000..a6e4e7d37e --- /dev/null +++ b/packages/docx-io/src/lib/exportTrackChanges.ts @@ -0,0 +1,979 @@ +/** + * DOCX Export Token Injection + * + * This module injects tracking tokens into Plate editor values for export. + * Tokens are embedded in text nodes and later processed by html-to-docx + * to generate Word tracked changes and comments XML. + * + * Token Format: + * - Insertions: [[DOCX_INS_START:{payload}]] ... [[DOCX_INS_END:id]] + * - Deletions: [[DOCX_DEL_START:{payload}]] ... [[DOCX_DEL_END:id]] + * - Comments: [[DOCX_CMT_START:{payload}]] ... [[DOCX_CMT_END:id]] + */ + +import type { TNode, TText } from 'platejs'; + +import { nanoid } from 'nanoid'; + +import { + buildCommentEndToken, + buildCommentStartToken, + buildSuggestionEndToken, + buildSuggestionStartToken, + type CommentPayload, + type SuggestionPayload, +} from './html-to-docx/tracking'; + +// ============================================================================ +// Top-level Regex Patterns (for performance) +// ============================================================================ + +const WHITESPACE_REGEX = /\s+/; +const ZERO_WIDTH_SPACE = '\u200B'; +const ZERO_WIDTH_SPACE_REGEX = /\u200B/g; + +// ============================================================================ +// Types +// ============================================================================ + +/** User information for resolving author names */ +export type DocxExportUser = { + id?: string | null; + name?: string | null; +}; + +/** Comment data from discussion thread */ +export type DocxExportComment = { + contentRich?: unknown | null; + createdAt?: Date | number | string | null; + id?: string | null; + /** OOXML paraId for round-trip threading fidelity */ + paraId?: string | null; + /** OOXML parentParaId for round-trip reply threading */ + parentParaId?: string | null; + user?: DocxExportUser | null; + userId?: string | null; +}; + +/** Discussion thread containing comments */ +export type DocxExportDiscussion = { + comments?: DocxExportComment[] | null; + createdAt?: Date | number | string | null; + documentContent?: string | null; + id: string; + /** OOXML paraId of the first (root) comment for round-trip threading fidelity */ + paraId?: string | null; + user?: DocxExportUser | null; + userId?: string | null; +}; + +/** Suggestion metadata stored on nodes */ +export type DocxExportSuggestionMeta = { + createdAt?: Date | number | string | null; + id: string; + type?: 'insert' | 'remove' | string | null; + userId?: string | null; +}; + +/** Options for token injection */ +export type InjectDocxTrackingTokensOptions = { + /** Default author name for transient comments (default: 'Draft') */ + defaultTransientAuthor?: string; + /** Discussion threads for comment metadata */ + discussions?: DocxExportDiscussion[] | null; + /** Function to get comment IDs from a text node */ + getCommentIds?: (node: TText) => string[]; + /** Function to get suggestion metadata from a text node */ + getSuggestions?: (node: TText) => DocxExportSuggestionMeta[]; + /** Include transient (uncommitted) comment marks in export */ + includeTransientComments?: boolean; + /** Function to convert rich content to plain text */ + nodeToString?: (node: unknown) => string; + /** Key used for transient comment marks (default: 'commentTransient') */ + transientCommentKey?: string; + /** Pre-built user name map (userId -> name) */ + userNameMap?: Map<string, string>; +}; + +/** Internal leaf entry for tracking */ +type LeafEntry = { + commentIds: string[]; + node: TText; + suggestions: Map<string, DocxExportSuggestionMeta>; +}; + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * Deep clone a value using structuredClone or JSON fallback. + */ +function cloneValue<T>(value: T): T { + if (typeof structuredClone === 'function') { + return structuredClone(value); + } + + return JSON.parse(JSON.stringify(value)) as T; +} + +function hasLeafText(text: string): boolean { + return text.replace(ZERO_WIDTH_SPACE_REGEX, '').length > 0; +} + +function ensureNonEmptyCommentRanges(leaves: LeafEntry[]): void { + const commentInfo = new Map< + string, + { firstIndex: number; hasText: boolean; lastIndex: number } + >(); + + leaves.forEach((leaf, index) => { + if (leaf.commentIds.length === 0) return; + + const hasText = hasLeafText(leaf.node.text); + + leaf.commentIds.forEach((id) => { + const entry = commentInfo.get(id); + if (entry) { + entry.lastIndex = index; + if (hasText) entry.hasText = true; + return; + } + + commentInfo.set(id, { + firstIndex: index, + hasText, + lastIndex: index, + }); + }); + }); + + const addCommentId = (index: number, id: string) => { + const leaf = leaves[index]; + if (!leaf) return; + if (!leaf.commentIds.includes(id)) { + leaf.commentIds.push(id); + } + }; + + const extendRange = (from: number, to: number, id: string) => { + if (from <= to) { + for (let i = from; i <= to; i += 1) { + addCommentId(i, id); + } + return; + } + + for (let i = from; i >= to; i -= 1) { + addCommentId(i, id); + } + }; + + const findNextTextLeaf = (startIndex: number): number | null => { + for (let i = startIndex; i < leaves.length; i += 1) { + if (hasLeafText(leaves[i].node.text)) return i; + } + return null; + }; + + const findPrevTextLeaf = (startIndex: number): number | null => { + for (let i = startIndex; i >= 0; i -= 1) { + if (hasLeafText(leaves[i].node.text)) return i; + } + return null; + }; + + commentInfo.forEach((info, id) => { + if (info.hasText) return; + + const nextTextIndex = findNextTextLeaf(info.lastIndex + 1); + if (nextTextIndex !== null) { + extendRange(info.lastIndex + 1, nextTextIndex, id); + return; + } + + const prevTextIndex = findPrevTextLeaf(info.firstIndex - 1); + if (prevTextIndex !== null) { + extendRange(info.firstIndex - 1, prevTextIndex, id); + return; + } + + const leaf = leaves[info.firstIndex]; + if (!leaf || leaf.commentIds.length === 0) return; + if (!hasLeafText(leaf.node.text) && leaf.node.text.length === 0) { + leaf.node.text = ZERO_WIDTH_SPACE; + } + }); +} + +/** + * Build a user name map from discussion threads. + */ +export function buildUserNameMap( + discussions?: DocxExportDiscussion[] | null +): Map<string, string> { + const map = new Map<string, string>(); + + discussions?.forEach((discussion) => { + if (discussion?.user?.id && discussion.user?.name) { + map.set(discussion.user.id, discussion.user.name); + } + if (discussion?.userId && discussion?.user?.name) { + map.set(discussion.userId, discussion.user.name); + } + + discussion?.comments?.forEach((comment) => { + if (comment?.user?.id && comment.user?.name) { + map.set(comment.user.id, comment.user.name); + } + if (comment?.userId && comment?.user?.name) { + map.set(comment.userId, comment.user.name); + } + }); + }); + + return map; +} + +/** + * Convert a name to initials (max 2 characters). + */ +export function toInitials(name?: null | string): string { + if (!name) return ''; + const parts = name.trim().split(WHITESPACE_REGEX).filter(Boolean); + + return parts + .slice(0, 2) + .map((part) => part[0]?.toUpperCase() ?? '') + .join(''); +} + +/** + * Format a Date as the browser's local time with a "Z" suffix. + * This matches Word's convention: w:date stores the author's local time + * but always appends "Z" regardless of timezone. + * e.g. user at 11:51 AM EST → "2024-01-15T11:51:00Z" (NOT real UTC) + * + * The real UTC is preserved separately in dateUtc (commentsExtensible.xml). + */ +function toLocalWithFakeZ(date: Date): string { + const Y = date.getFullYear(); + const M = String(date.getMonth() + 1).padStart(2, '0'); + const D = String(date.getDate()).padStart(2, '0'); + const h = String(date.getHours()).padStart(2, '0'); + const m = String(date.getMinutes()).padStart(2, '0'); + const s = String(date.getSeconds()).padStart(2, '0'); + + return `${Y}-${M}-${D}T${h}:${m}:${s}Z`; +} + +/** + * Normalize a date to local time with "Z" suffix (Word convention). + * Used for w:date attributes on comments and track changes. + */ +export function normalizeDate( + date?: Date | null | number | string +): string | undefined { + if (!date) return; + const parsed = new Date(date); + if (Number.isNaN(parsed.getTime())) return; + + return toLocalWithFakeZ(parsed); +} + +/** + * Normalize a date to ISO string in UTC (suffix "Z"). + * Used for w16cex:dateUtc in commentsExtensible.xml. + */ +export function normalizeDateUtc( + date?: Date | null | number | string +): string | undefined { + if (!date) return; + const parsed = new Date(date); + if (Number.isNaN(parsed.getTime())) return; + + return parsed.toISOString(); +} + +/** + * Resolve suggestion type to 'insert' or 'remove'. + */ +function resolveSuggestionType(type?: null | string): 'insert' | 'remove' { + if (type === 'remove') return 'remove'; + + return 'insert'; +} + +/** + * Resolve suggestion author name from userId. + */ +function resolveSuggestionAuthor( + userId?: null | string, + userNameMap?: Map<string, string> +): string { + if (!userId) return 'unknown'; + + return userNameMap?.get(userId) ?? userId; +} + +/** + * Resolve comment metadata from discussions. + */ +/** Extract plain text from Plate node tree without nodeToString. */ +function extractPlainText(nodes: unknown[]): string { + const parts: string[] = []; + + for (const node of nodes) { + if (!node || typeof node !== 'object') continue; + const n = node as Record<string, unknown>; + + if (typeof n.text === 'string') { + parts.push(n.text); + } + if (Array.isArray(n.children)) { + parts.push(extractPlainText(n.children)); + } + } + + return parts.join(''); +} + +function resolveCommentText( + comment: DocxExportComment | null | undefined, + fallback: string | null | undefined, + nodeToString?: (node: unknown) => string +): string { + const contentRich = comment?.contentRich; + let text = ''; + + if (Array.isArray(contentRich)) { + if (nodeToString) { + try { + text = nodeToString({ + children: contentRich, + type: 'root', + }); + } catch { + text = ''; + } + } + + // Fallback: extract plain text from known Plate node structure + if (!text) { + text = extractPlainText(contentRich); + } + } + + return text || fallback || 'Imported comment'; +} + +function resolveCommentAuthorName( + comment: DocxExportComment | null | undefined, + discussion: DocxExportDiscussion | null | undefined, + userNameMap?: Map<string, string> +): string { + return ( + comment?.user?.name ?? + discussion?.user?.name ?? + userNameMap?.get(comment?.userId ?? '') ?? + userNameMap?.get(discussion?.userId ?? '') ?? + comment?.userId ?? + discussion?.userId ?? + 'unknown' + ); +} + +function resolveCommentMeta( + id: string, + discussions?: DocxExportDiscussion[] | null, + userNameMap?: Map<string, string>, + nodeToString?: (node: unknown) => string +): CommentPayload { + const discussion = discussions?.find((item) => item?.id === id); + const comment = discussion?.comments?.[0]; + + const text = resolveCommentText( + comment, + discussion?.documentContent, + nodeToString + ); + const authorName = resolveCommentAuthorName(comment, discussion, userNameMap); + const date = normalizeDate(comment?.createdAt ?? discussion?.createdAt); + + // Build replies from discussion.comments[1..n] + const replyComments = discussion?.comments?.slice(1); + const replies: CommentPayload['replies'] = + replyComments && replyComments.length > 0 + ? replyComments.map((reply) => { + const replyAuthorName = resolveCommentAuthorName( + reply, + discussion, + userNameMap + ); + const replyText = resolveCommentText(reply, undefined, nodeToString); + const replyDate = normalizeDate(reply?.createdAt); + + return { + authorInitials: toInitials(replyAuthorName), + authorName: replyAuthorName, + date: replyDate, + id: reply?.id ?? nanoid(), + paraId: reply?.paraId ?? undefined, + text: replyText, + }; + }) + : undefined; + + return { + authorInitials: toInitials(authorName), + authorName, + date, + // Must use discussion ID (= `id`) because END tokens are keyed by discussion ID. + // Using comment?.id would mismatch START vs END and create duplicate entries. + id, + paraId: comment?.paraId ?? discussion?.paraId ?? undefined, + replies, + text, + }; +} + +// ============================================================================ +// Default Mark Extractors +// ============================================================================ + +/** Suggestion key prefix used by @platejs/suggestion */ +const SUGGESTION_KEY_PREFIX = 'suggestion_'; + +/** Comment key prefix used by @platejs/comment */ +const COMMENT_KEY_PREFIX = 'comment_'; + +/** + * Default function to extract suggestion metadata from a text node. + * Looks for keys starting with 'suggestion_' prefix. + */ +function defaultGetSuggestions(node: TText): DocxExportSuggestionMeta[] { + const suggestions: DocxExportSuggestionMeta[] = []; + + for (const key of Object.keys(node)) { + if (key.startsWith(SUGGESTION_KEY_PREFIX)) { + const suggestionId = key.slice(SUGGESTION_KEY_PREFIX.length); + + if (suggestionId) { + const value = node[key] as + | DocxExportSuggestionMeta + | boolean + | undefined; + const meta: DocxExportSuggestionMeta = + typeof value === 'object' && value !== null + ? { ...value, id: suggestionId } + : { id: suggestionId }; + suggestions.push(meta); + } + } + } + + // Also check for block-level suggestion inherited via 'suggestion' property + const blockSuggestion = (node as Record<string, unknown>) + .suggestion as DocxExportSuggestionMeta; + + if (blockSuggestion?.id) { + suggestions.push(blockSuggestion); + } + + return suggestions; +} + +/** + * Default function to extract comment IDs from a text node. + * Looks for keys starting with 'comment_' prefix. + */ +function defaultGetCommentIds(node: TText): string[] { + const commentIds: string[] = []; + + for (const key of Object.keys(node)) { + if (key.startsWith(COMMENT_KEY_PREFIX)) { + const commentId = key.slice(COMMENT_KEY_PREFIX.length); + + if (commentId) { + commentIds.push(commentId); + } + } + } + + return commentIds; +} + +// ============================================================================ +// Leaf Collection +// ============================================================================ + +/** + * Recursively collect all text leaves with their suggestion and comment marks. + */ +function collectLeaves( + node: Record<string, unknown>, + leaves: LeafEntry[], + options: InjectDocxTrackingTokensOptions, + inheritedSuggestions: DocxExportSuggestionMeta[] = [] +): void { + const { + getCommentIds = defaultGetCommentIds, + getSuggestions = defaultGetSuggestions, + } = options; + + // Check if this is a text node + if (typeof node.text === 'string') { + const textNode = node as unknown as TText; + const suggestionMap = new Map<string, DocxExportSuggestionMeta>(); + + // Add inherited suggestions + inheritedSuggestions.forEach((suggestion) => { + suggestionMap.set(suggestion.id, suggestion); + }); + + // Add suggestions from this node + const nodeSuggestions = getSuggestions(textNode); + nodeSuggestions.forEach((suggestion) => { + suggestionMap.set(suggestion.id, suggestion); + }); + + // Get comment IDs + const commentIds = getCommentIds(textNode); + + leaves.push({ + commentIds, + node: textNode, + suggestions: suggestionMap, + }); + + return; + } + + // Check if this is an element with children + if (!Array.isArray(node.children)) return; + + // Check for block-level suggestion + let nextInherited = inheritedSuggestions; + const blockSuggestion = node.suggestion as + | DocxExportSuggestionMeta + | undefined; + + if (blockSuggestion?.id) { + nextInherited = [ + ...inheritedSuggestions, + { + createdAt: blockSuggestion.createdAt, + id: blockSuggestion.id, + type: blockSuggestion.type, + userId: blockSuggestion.userId, + }, + ]; + } + + // Recurse into children + (node.children as Record<string, unknown>[]).forEach((child) => { + collectLeaves(child, leaves, options, nextInherited); + }); +} + +// ============================================================================ +// Token Building Helpers +// ============================================================================ + +/** + * Build a suggestion start token with resolved metadata. + */ +function buildResolvedSuggestionStartToken( + suggestion: DocxExportSuggestionMeta, + userNameMap?: Map<string, string> +): string { + const type = resolveSuggestionType(suggestion.type); + const payload: SuggestionPayload = { + author: resolveSuggestionAuthor(suggestion.userId, userNameMap), + date: normalizeDate(suggestion.createdAt), + id: suggestion.id, + }; + + return buildSuggestionStartToken(payload, type); +} + +/** + * Build a suggestion end token. + */ +function buildResolvedSuggestionEndToken( + suggestion: DocxExportSuggestionMeta +): string { + const type = resolveSuggestionType(suggestion.type); + + return buildSuggestionEndToken(suggestion.id, type); +} + +/** + * Build a comment start token with resolved metadata. + */ +function buildResolvedCommentStartToken( + id: string, + options: InjectDocxTrackingTokensOptions +): string { + const payload = resolveCommentMeta( + id, + options.discussions, + options.userNameMap, + options.nodeToString + ); + + return buildCommentStartToken(payload); +} + +// ============================================================================ +// Transient Comment Preprocessing +// ============================================================================ + +/** Default key for transient comment marks */ +const DEFAULT_TRANSIENT_KEY = 'commentTransient'; + +/** Default author for transient comments */ +const DEFAULT_TRANSIENT_AUTHOR = 'Draft'; + +/** + * Internal type for tracking transient text nodes during preprocessing. + */ +type TransientTextEntry = { + node: TText; + path: number[]; +}; + +/** + * Recursively collect all text nodes with transient comment marks. + */ +function collectTransientTextNodes( + node: Record<string, unknown>, + transientKey: string, + entries: TransientTextEntry[], + currentPath: number[] = [] +): void { + if (typeof node.text === 'string') { + if (node[transientKey]) { + entries.push({ + node: node as unknown as TText, + path: [...currentPath], + }); + } + return; + } + + if (!Array.isArray(node.children)) return; + + (node.children as Record<string, unknown>[]).forEach((child, index) => { + collectTransientTextNodes(child, transientKey, entries, [ + ...currentPath, + index, + ]); + }); +} + +/** + * Group consecutive transient text entries into ranges. + * Two entries are consecutive if they are adjacent siblings or in sequence. + */ +function groupTransientRanges( + entries: TransientTextEntry[] +): TransientTextEntry[][] { + if (entries.length === 0) return []; + + const ranges: TransientTextEntry[][] = []; + let currentRange: TransientTextEntry[] = [entries[0]!]; + + for (let i = 1; i < entries.length; i++) { + const prev = entries[i - 1]!; + const curr = entries[i]!; + + // Check if consecutive (same parent, adjacent indices) + const prevParent = prev.path.slice(0, -1); + const currParent = curr.path.slice(0, -1); + const prevIndex = prev.path.at(-1) ?? -1; + const currIndex = curr.path.at(-1) ?? -1; + + const sameParent = + prevParent.length === currParent.length && + prevParent.every((v, i) => v === currParent[i]); + const adjacent = currIndex === prevIndex + 1; + + if (sameParent && adjacent) { + currentRange.push(curr); + } else { + ranges.push(currentRange); + currentRange = [curr]; + } + } + + ranges.push(currentRange); + return ranges; +} + +/** + * Extract text content from a range of transient text entries. + */ +function extractRangeText(entries: TransientTextEntry[]): string { + return entries.map((e) => e.node.text).join(''); +} + +/** + * Preprocess transient comment marks in cloned editor value. + * + * This function: + * 1. Finds all text nodes with transient comment marks + * 2. Groups consecutive nodes into ranges + * 3. Generates a unique ID for each range + * 4. Mutates nodes to add `comment_<id>` marks + * 5. Returns DocxExportDiscussion entries for metadata resolution + * + * @param clonedValue - The cloned editor value (will be mutated) + * @param options - Options for transient comment processing + * @returns Array of generated discussions for transient comments + */ +export function createDiscussionsForTransientComments( + clonedValue: TNode[], + options: { + /** Default author name (default: 'Draft') */ + defaultAuthor?: string; + /** Key used for transient marks (default: 'commentTransient') */ + transientKey?: string; + } = {} +): DocxExportDiscussion[] { + const { + defaultAuthor = DEFAULT_TRANSIENT_AUTHOR, + transientKey = DEFAULT_TRANSIENT_KEY, + } = options; + + const discussions: DocxExportDiscussion[] = []; + const allEntries: TransientTextEntry[] = []; + + // Collect all transient text nodes + clonedValue.forEach((node, index) => { + collectTransientTextNodes( + node as Record<string, unknown>, + transientKey, + allEntries, + [index] + ); + }); + + if (allEntries.length === 0) return discussions; + + // Group into contiguous ranges + const ranges = groupTransientRanges(allEntries); + + // Process each range + for (const range of ranges) { + const id = nanoid(); + const text = extractRangeText(range); + const now = new Date(); + + // Mutate nodes to add comment_<id> mark + for (const entry of range) { + (entry.node as Record<string, unknown>)[`${COMMENT_KEY_PREFIX}${id}`] = + true; + } + + // Create discussion entry + discussions.push({ + id, + comments: [ + { + contentRich: [{ type: 'p', children: [{ text }] }], + createdAt: now, + user: { id: 'draft', name: defaultAuthor }, + userId: 'draft', + }, + ], + createdAt: now, + documentContent: text, + user: { id: 'draft', name: defaultAuthor }, + userId: 'draft', + }); + } + + return discussions; +} + +// ============================================================================ +// Main Export Function +// ============================================================================ + +/** + * Inject DOCX tracking tokens into editor value for export. + * + * This function: + * 1. Clones the editor value to avoid mutation + * 2. Collects all text leaves with their suggestion/comment marks + * 3. Determines where each suggestion/comment starts and ends + * 4. Injects start/end tokens into text nodes + * + * The resulting value can be serialized to HTML and then converted to DOCX + * with tracked changes and comments preserved. + * + * @param value - The editor value (array of nodes) + * @param options - Options for token injection + * @returns Cloned value with tracking tokens injected into text nodes + */ +export function injectDocxTrackingTokens( + value: TNode[], + options: InjectDocxTrackingTokensOptions = {} +): TNode[] { + const cloned = cloneValue(value); + + // Preprocess transient comments if enabled + let mergedDiscussions = options.discussions ?? []; + if (options.includeTransientComments) { + const transientDiscussions = createDiscussionsForTransientComments(cloned, { + defaultAuthor: options.defaultTransientAuthor, + transientKey: options.transientCommentKey, + }); + mergedDiscussions = [...mergedDiscussions, ...transientDiscussions]; + } + + // Create options with merged discussions + const resolvedOptions: InjectDocxTrackingTokensOptions = { + ...options, + discussions: mergedDiscussions, + }; + + const leaves: LeafEntry[] = []; + + // Collect all leaves from all top-level nodes + cloned.forEach((node) => { + collectLeaves(node as Record<string, unknown>, leaves, resolvedOptions); + }); + + ensureNonEmptyCommentRanges(leaves); + + // Warn about discussions without text marks (can't be exported to DOCX) + if (mergedDiscussions.length > 0) { + const anchoredIds = new Set<string>(); + + for (const leaf of leaves) { + for (const id of leaf.commentIds) { + anchoredIds.add(id); + } + } + + for (const disc of mergedDiscussions) { + if (!anchoredIds.has(disc.id) && process.env.NODE_ENV !== 'production') { + console.warn( + `[DOCX Export] Discussion "${disc.id}" has no comment marks on text - skipping export (comments need text selection to anchor in DOCX)` + ); + } + } + } + + // Track when each suggestion/comment first appears + const suggestionStartOrder = new Map<string, number>(); + const commentStartOrder = new Map<string, number>(); + + // Process each leaf to inject tokens + leaves.forEach((leaf, index) => { + const prev = leaves[index - 1]; + const next = leaves[index + 1]; + + // Get current suggestion/comment IDs + const currentSuggestionIds = [...leaf.suggestions.keys()]; + const prevSuggestionIds = new Set(prev ? [...prev.suggestions.keys()] : []); + const nextSuggestionIds = new Set(next ? [...next.suggestions.keys()] : []); + + const currentCommentIds = leaf.commentIds; + const prevCommentIds = new Set(prev?.commentIds ?? []); + const nextCommentIds = new Set(next?.commentIds ?? []); + + // Find which suggestions/comments start and end at this leaf + const startSuggestions = currentSuggestionIds.filter( + (id) => !prevSuggestionIds.has(id) + ); + const endSuggestions = currentSuggestionIds.filter( + (id) => !nextSuggestionIds.has(id) + ); + + const startComments = currentCommentIds.filter( + (id) => !prevCommentIds.has(id) + ); + const endComments = currentCommentIds.filter( + (id) => !nextCommentIds.has(id) + ); + + // Record start order for proper nesting + startSuggestions.forEach((id) => { + if (!suggestionStartOrder.has(id)) { + suggestionStartOrder.set(id, index); + } + }); + + startComments.forEach((id) => { + if (!commentStartOrder.has(id)) { + commentStartOrder.set(id, index); + } + }); + + // Build sorted start tokens (suggestions before comments, earlier starts first) + const startTokens = [ + ...startSuggestions.map((id) => ({ + id, + kind: 'suggestion' as const, + order: suggestionStartOrder.get(id) ?? index, + })), + ...startComments.map((id) => ({ + id, + kind: 'comment' as const, + order: commentStartOrder.get(id) ?? index, + })), + ].sort((a, b) => + a.order === b.order + ? a.kind === 'suggestion' + ? -1 + : 1 + : a.order - b.order + ); + + // Build sorted end tokens (comments before suggestions, later ends first) + const endTokens = [ + ...endSuggestions.map((id) => ({ + id, + kind: 'suggestion' as const, + order: suggestionStartOrder.get(id) ?? index, + })), + ...endComments.map((id) => ({ + id, + kind: 'comment' as const, + order: commentStartOrder.get(id) ?? index, + })), + ].sort((a, b) => + a.order === b.order ? (a.kind === 'comment' ? -1 : 1) : b.order - a.order + ); + + // Generate token strings + const prefixTokens = startTokens + .map((token) => + token.kind === 'suggestion' + ? buildResolvedSuggestionStartToken( + leaf.suggestions.get(token.id) ?? { id: token.id }, + resolvedOptions.userNameMap + ) + : buildResolvedCommentStartToken(token.id, resolvedOptions) + ) + .join(''); + + const suffixTokens = endTokens + .map((token) => + token.kind === 'comment' + ? buildCommentEndToken(token.id) + : buildResolvedSuggestionEndToken( + leaf.suggestions.get(token.id) ?? { id: token.id } + ) + ) + .join(''); + + // Inject tokens into text + leaf.node.text = `${prefixTokens}${leaf.node.text}${suffixTokens}`; + }); + + return cloned; +} diff --git a/packages/docx-io/src/lib/html-to-docx.ts b/packages/docx-io/src/lib/html-to-docx.ts deleted file mode 100644 index 31f47d1177..0000000000 --- a/packages/docx-io/src/lib/html-to-docx.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * HTML to DOCX converter using @turbodocx/html-to-docx - * - * This module wraps the @turbodocx/html-to-docx library to provide - * a simple API for converting HTML content to DOCX format. - * - * @packageDocumentation - */ - -import HTMLtoDOCX from './html-to-docx/index'; -import type { DocumentOptions, Margins } from './html-to-docx/index'; - -// Re-export types from the library -export type { - DocumentOptions, - LineNumberOptions, - Margins, - NumberingOptions, - PageSize, - TableOptions, -} from './html-to-docx/index'; - -// Backwards compatibility aliases -export type DocumentMargins = Margins; -export type HtmlToDocxOptions = DocumentOptions; - -/** - * Convert HTML content to a DOCX blob. - * - * This function uses @turbodocx/html-to-docx to create a valid DOCX file - * from HTML content with proper support for images, tables, and styling. - * - * @param html - The HTML content to convert - * @param options - Optional document configuration (orientation, margins, etc.) - * @returns A Promise that resolves to a Blob containing the DOCX file - * - * @example - * ```typescript - * const html = '<h1>Hello World</h1><p>This is a paragraph.</p>'; - * const blob = await htmlToDocxBlob(html, { orientation: 'landscape' }); - * - * // Download the file - * const url = URL.createObjectURL(blob); - * const a = document.createElement('a'); - * a.href = url; - * a.download = 'document.docx'; - * a.click(); - * ``` - */ -export async function htmlToDocxBlob( - html: string, - options: DocumentOptions = {} -): Promise<Blob> { - // Handle empty HTML - the underlying library crashes on empty string - const safeHtml = html.trim() === '' ? '<p></p>' : html; - - const result = await HTMLtoDOCX(safeHtml, null, options, null); - - // In browser environment, result is a Blob - if (result instanceof Blob) { - return result; - } - - // In Node.js/Bun environment, result is a Buffer - convert to Blob - // Buffer.isBuffer is the type-safe way to check for Buffer - return new Blob([new Uint8Array(result)], { - type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - }); -} diff --git a/packages/docx-io/src/lib/html-to-docx/comment-templates.ts b/packages/docx-io/src/lib/html-to-docx/comment-templates.ts new file mode 100644 index 0000000000..863b7127c9 --- /dev/null +++ b/packages/docx-io/src/lib/html-to-docx/comment-templates.ts @@ -0,0 +1,35 @@ +/** + * XML template string constants for DOCX comment-related files. + * + * Each template is the exact root element shell (with 30+ namespace + * declarations) extracted from Word-generated DOCX files. Child elements + * are appended at runtime via xmlbuilder2. + * + * Do NOT simplify these -- the full namespace set and mc:Ignorable list + * are required for maximum Word compatibility. + */ + +/** Root element shell for word/comments.xml */ +export const COMMENTS_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14"> +</w:comments>`; + +/** Root element shell for word/commentsExtended.xml */ +export const COMMENTS_EXTENDED_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w15:commentsEx xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14"> +</w15:commentsEx>`; + +/** Root element shell for word/commentsIds.xml */ +export const COMMENTS_IDS_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w16cid:commentsIds xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14"> +</w16cid:commentsIds>`; + +/** Root element shell for word/commentsExtensible.xml (includes extra xmlns:cr namespace) */ +export const COMMENTS_EXTENSIBLE_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w16cex:commentsExtensible xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl cr w16du wp14"> +</w16cex:commentsExtensible>`; + +/** Root element shell for word/people.xml (minimal namespaces, matches Word output) */ +export const PEOPLE_TEMPLATE = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"> +</w15:people>`; diff --git a/packages/docx-io/src/lib/html-to-docx/constants.ts b/packages/docx-io/src/lib/html-to-docx/constants.ts index cdd81dcb55..b3c966d695 100644 --- a/packages/docx-io/src/lib/html-to-docx/constants.ts +++ b/packages/docx-io/src/lib/html-to-docx/constants.ts @@ -168,6 +168,32 @@ const documentFileName = 'document'; const headerType = 'header'; const footerType = 'footer'; const themeType = 'theme'; +const commentsType = 'comments'; +const commentsExtendedType = 'commentsExtended'; +const commentsIdsType = 'commentsIds'; +const commentsExtensibleType = 'commentsExtensible'; +const peopleType = 'people'; + +// Content types for comment-related XML parts +const commentsExtendedContentType = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml'; +const commentsIdsContentType = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml'; +const commentsExtensibleContentType = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml'; +const peopleContentType = + 'application/vnd.openxmlformats-officedocument.wordprocessingml.people+xml'; + +// Relationship types for comment-related XML parts +const commentsExtendedRelationshipType = + 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended'; +const commentsIdsRelationshipType = + 'http://schemas.microsoft.com/office/2016/09/relationships/commentsIds'; +const commentsExtensibleRelationshipType = + 'http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible'; +const peopleRelationshipType = + 'http://schemas.microsoft.com/office/2011/relationships/people'; + const hyperlinkType = 'hyperlink'; const imageType = 'image'; const internalRelationship = 'Internal'; @@ -201,6 +227,16 @@ const verticalAlignValues: VerticalAlign[] = ['top', 'middle', 'bottom']; export { applicationName, colorlessColors, + commentsExtendedContentType, + commentsExtendedRelationshipType, + commentsExtendedType, + commentsExtensibleContentType, + commentsExtensibleRelationshipType, + commentsExtensibleType, + commentsIdsContentType, + commentsIdsRelationshipType, + commentsIdsType, + commentsType, defaultDocumentOptions, defaultFont, defaultFontSize, @@ -219,6 +255,9 @@ export { landscapeMargins, landscapeWidth, paragraphBordersObject, + peopleContentType, + peopleRelationshipType, + peopleType, portraitMargins, relsFolderName, themeFileName, diff --git a/packages/docx-io/src/lib/html-to-docx/docx-document.ts b/packages/docx-io/src/lib/html-to-docx/docx-document.ts index 802e816b26..c63988fc2a 100644 --- a/packages/docx-io/src/lib/html-to-docx/docx-document.ts +++ b/packages/docx-io/src/lib/html-to-docx/docx-document.ts @@ -5,8 +5,33 @@ import { nanoid } from 'nanoid'; import { create, fragment } from 'xmlbuilder2'; import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; +import type { CommentPayload, StoredComment, TrackingState } from './tracking'; +import { + COMMENTS_EXTENDED_TEMPLATE, + COMMENTS_EXTENSIBLE_TEMPLATE, + COMMENTS_IDS_TEMPLATE, + COMMENTS_TEMPLATE, + PEOPLE_TEMPLATE, +} from './comment-templates'; +import type { CommentPayload, StoredComment, TrackingState } from './tracking'; +import { + allocatedIds, + findDocxTrackingTokens, + generateHexId, +} from './tracking'; + import { applicationName, + commentsExtendedContentType, + commentsExtendedRelationshipType, + commentsExtendedType, + commentsExtensibleContentType, + commentsExtensibleRelationshipType, + commentsExtensibleType, + commentsIdsContentType, + commentsIdsRelationshipType, + commentsIdsType, + commentsType, defaultDocumentOptions, defaultFont, defaultFontSize, @@ -20,6 +45,9 @@ import { landscapeHeight, landscapeMargins, landscapeWidth, + peopleContentType, + peopleRelationshipType, + peopleType, portraitMargins, themeType as themeFileType, } from './constants'; @@ -378,6 +406,14 @@ class DocxDocument { width: number; zip: JSZip; + // Tracking support for comments and suggestions + _trackingState?: TrackingState; + comments: StoredComment[]; + commentIdMap: Map<string, number>; + lastCommentId: number; + revisionIdMap: Map<string, number>; + lastRevisionId: number; + constructor(properties: DocxDocumentProperties) { this.zip = properties.zip; this.htmlString = properties.htmlString; @@ -448,6 +484,13 @@ class DocxDocument { this.footerObjects = []; this.documentXML = null; + // Initialize tracking support + this.comments = []; + this.commentIdMap = new Map(); + this.lastCommentId = 0; + this.revisionIdMap = new Map(); + this.lastRevisionId = 0; + this.generateContentTypesXML = this.generateContentTypesXML.bind(this); this.generateDocumentXML = this.generateDocumentXML.bind(this); this.generateCoreXML = this.generateCoreXML.bind(this); @@ -464,6 +507,10 @@ class DocxDocument { this.generateHeaderXML = this.generateHeaderXML.bind(this); this.generateFooterXML = this.generateFooterXML.bind(this); this.generateSectionXML = generateSectionXML.bind(this); + this.generateCommentsXML = this.generateCommentsXML.bind(this); + this.ensureComment = this.ensureComment.bind(this); + this.getCommentId = this.getCommentId.bind(this); + this.getRevisionId = this.getRevisionId.bind(this); this.ListStyleBuilder = new ListStyleBuilder(properties.numbering); } @@ -490,6 +537,44 @@ class DocxDocument { this.footerObjects ); + // Add comment-related content types if there are comments + if (this.comments.length > 0) { + const overrides: Array<{ contentType: string; partName: string }> = [ + { + contentType: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml', + partName: '/word/comments.xml', + }, + { + contentType: commentsExtendedContentType, + partName: '/word/commentsExtended.xml', + }, + { + contentType: commentsIdsContentType, + partName: '/word/commentsIds.xml', + }, + { + contentType: commentsExtensibleContentType, + partName: '/word/commentsExtensible.xml', + }, + { + contentType: peopleContentType, + partName: '/word/people.xml', + }, + ]; + + overrides.forEach(({ contentType, partName }) => { + const frag = fragment({ + defaultNamespace: { ele: namespaces.contentTypes }, + }) + .ele('Override') + .att('PartName', partName) + .att('ContentType', contentType) + .up(); + contentTypesXML.root().import(frag); + }); + } + return contentTypesXML.toString({ prettyPrint: true }); } @@ -619,6 +704,17 @@ class DocxDocument { ); }); + const deadTokens = findDocxTrackingTokens(xmlString); + if (deadTokens.length > 0) { + const uniqueTokens = Array.from(new Set(deadTokens)); + const sample = uniqueTokens.slice(0, 3).join(', '); + const suffix = + uniqueTokens.length > 3 ? ` (+${uniqueTokens.length - 3} more)` : ''; + console.warn( + `[docx] dead tracking tokens in document.xml: ${sample}${suffix}` + ); + } + return xmlString; } @@ -730,7 +826,7 @@ class DocxDocument { .ele('@w', 'abstractNum') .att('@w', 'abstractNumId', String(numberingId)); - [...new Array(8).keys()].forEach((level) => { + Array.from({ length: 8 }, (_, level) => level).forEach((level) => { const levelFragment = fragment({ namespaceAlias: { w: namespaces.w } }) .ele('@w', 'lvl') .att('@w', 'ilvl', String(level)) @@ -929,6 +1025,21 @@ class DocxDocument { case imageType: relationshipType = namespaces.images; break; + case commentsType: + relationshipType = namespaces.comments; + break; + case commentsExtendedType: + relationshipType = commentsExtendedRelationshipType; + break; + case commentsIdsType: + relationshipType = commentsIdsRelationshipType; + break; + case commentsExtensibleType: + relationshipType = commentsExtensibleRelationshipType; + break; + case peopleType: + relationshipType = peopleRelationshipType; + break; case headerFileType: relationshipType = namespaces.headers; break; @@ -957,6 +1068,287 @@ class DocxDocument { generateFooterXML(vTree: VTree): Promise<FooterResult> { return this.generateSectionXML(vTree, 'footer') as Promise<FooterResult>; } + + // ============================================================================ + // Tracking Support Methods (Comments and Suggestions) + // ============================================================================ + + /** + * Get a revision ID for a suggestion. Creates a new one if needed. + * Ensures consistent IDs for the same suggestion across multiple occurrences. + */ + getRevisionId(id?: string): number { + if (!id) { + this.lastRevisionId += 1; + return this.lastRevisionId; + } + + const existing = this.revisionIdMap.get(id); + if (existing !== undefined) { + return existing; + } + + this.lastRevisionId += 1; + this.revisionIdMap.set(id, this.lastRevisionId); + return this.lastRevisionId; + } + + /** + * Ensure a comment exists in the document and return its numeric ID. + * Updates metadata if the comment already exists but had missing fields. + */ + ensureComment(data: Partial<CommentPayload>, parentParaId?: string): number { + const { id, authorName, authorInitials, date, text } = data; + const commentId = + id !== undefined ? id : `comment-${this.lastCommentId + 1}`; + let numericId = this.commentIdMap.get(commentId); + + if (numericId === undefined) { + this.lastCommentId += 1; + numericId = this.lastCommentId; + this.commentIdMap.set(commentId, numericId); + } + + const existing = this.comments.find((item) => item.id === numericId); + if (existing) { + // Update missing fields + if (!existing.authorName && authorName) { + existing.authorName = authorName; + } + if (!existing.authorInitials && authorInitials) { + existing.authorInitials = authorInitials; + } + if (!existing.date && date) { + existing.date = date; + } + if (!existing.text && text) { + existing.text = text; + } + if (!existing.parentParaId && parentParaId) { + existing.parentParaId = parentParaId; + } + return numericId; + } + + // Preserve imported paraId when provided; otherwise generate fresh. + // Register in allocatedIds to prevent collisions with generated IDs. + let paraId: string; + if (data.paraId) { + paraId = data.paraId; + allocatedIds.add(paraId); + } else { + paraId = generateHexId(); + } + + const entry = { + id: numericId, + authorName: authorName || 'unknown', + authorInitials: authorInitials || '', + date, + durableId: generateHexId(), + paraId, + parentParaId, + text: text || 'Imported comment', + }; + this.comments.push(entry); + + return numericId; + } + + /** + * Get the numeric ID for a comment, creating it if necessary. + */ + getCommentId(id: string): number { + if (id === undefined || id === null) { + return this.ensureComment({ id: undefined }); + } + return this.ensureComment({ id }); + } + + /** + * Generate the comments.xml file content. + * Matches reference library structure: w14:paraId on paragraphs, + * CommentReference style on first run, text runs with formatting. + */ + generateCommentsXML(): string { + const w = namespaces.w; + const commentsXML = create(COMMENTS_TEMPLATE); + const root = commentsXML.root(); + + this.comments.forEach((comment) => { + const commentElement = root + .ele(w, 'comment') + .att(w, 'id', String(comment.id)) + .att(w, 'author', comment.authorName || 'unknown'); + + if (comment.authorInitials) { + commentElement.att(w, 'initials', comment.authorInitials); + } + if (comment.date) { + commentElement.att(w, 'date', comment.date); + } + + // Split multi-line comment text into paragraphs, preserving empty lines + const paragraphs = String(comment.text || '').split(/\r?\n/); + + paragraphs.forEach((line, pIdx) => { + const pElement = commentElement.ele(w, 'p'); + + // Add w14:paraId and w14:textId per OOXML spec + pElement.att(namespaces.w14, 'paraId', comment.paraId); + pElement.att(namespaces.w14, 'textId', '77777777'); + + // Paragraph properties + pElement + .ele(w, 'pPr') + .ele(w, 'pStyle') + .att(w, 'val', 'CommentText') + .up() + .up(); + + // First paragraph gets CommentReference run + if (pIdx === 0) { + const refRun = pElement.ele(w, 'r'); + refRun + .ele(w, 'rPr') + .ele(w, 'rStyle') + .att(w, 'val', 'CommentReference') + .up() + .up(); + refRun.ele(w, 'annotationRef').up(); + refRun.up(); + } + + // Text run + const textRun = pElement.ele(w, 'r'); + textRun + .ele(w, 'rPr') + .ele(w, 'color') + .att(w, 'val', '000000') + .up() + .ele(w, 'sz') + .att(w, 'val', '20') + .up() + .ele(w, 'szCs') + .att(w, 'val', '20') + .up() + .up(); + textRun + .ele(w, 't') + .att('http://www.w3.org/XML/1998/namespace', 'space', 'preserve') + .txt(line) + .up(); + textRun.up(); + + pElement.up(); + }); + + commentElement.up(); + }); + + return commentsXML.end({ prettyPrint: true }); + } + + /** + * Generate word/commentsExtended.xml. + * Links comments via paraId and establishes parent-child threading via paraIdParent. + */ + generateCommentsExtendedXML(): string { + const doc = create(COMMENTS_EXTENDED_TEMPLATE); + const root = doc.root(); + + this.comments.forEach((comment) => { + const el = root.ele(namespaces.w15, 'commentEx'); + el.att(namespaces.w15, 'paraId', comment.paraId); + el.att(namespaces.w15, 'done', '0'); + if (comment.parentParaId) { + el.att(namespaces.w15, 'paraIdParent', comment.parentParaId); + } + el.up(); + }); + + return doc.end({ prettyPrint: true }); + } + + /** + * Generate word/commentsIds.xml. + * Maps paraId to durableId for each comment. + */ + generateCommentsIdsXML(): string { + const doc = create(COMMENTS_IDS_TEMPLATE); + const root = doc.root(); + + this.comments.forEach((comment) => { + const el = root.ele(namespaces.w16cid, 'commentId'); + el.att(namespaces.w16cid, 'paraId', comment.paraId); + el.att(namespaces.w16cid, 'durableId', comment.durableId); + el.up(); + }); + + return doc.end({ prettyPrint: true }); + } + + /** + * Generate word/commentsExtensible.xml. + * Links durableId to dateUtc for each comment. + */ + generateCommentsExtensibleXML(): string { + const doc = create(COMMENTS_EXTENSIBLE_TEMPLATE); + const root = doc.root(); + + this.comments.forEach((comment) => { + const el = root.ele(namespaces.w16cex, 'commentExtensible'); + el.att(namespaces.w16cex, 'durableId', comment.durableId); + if (comment.date) { + // comment.date is local time with fake Z (Word convention). + // Reverse the fake Z to recover real UTC: + // fakeMs = epoch interpreting local time as UTC + // tzMs = comment date's offset (DST-safe) + // real = fakeMs + tzMs + const fakeMs = new Date(comment.date).getTime(); + const tzMs = new Date(comment.date).getTimezoneOffset() * 60_000; + const realUtc = new Date(fakeMs + tzMs); + el.att( + namespaces.w16cex, + 'dateUtc', + Number.isNaN(realUtc.getTime()) ? comment.date : realUtc.toISOString() + ); + } + el.up(); + }); + + return doc.end({ prettyPrint: true }); + } + + /** + * Generate word/people.xml. + * Contains unique author entries with presence info. + */ + generatePeopleXML(): string { + const doc = create(PEOPLE_TEMPLATE); + const root = doc.root(); + + // Collect unique authors + const uniqueAuthors = new Set<string>(); + this.comments.forEach((comment) => { + if (comment.authorName) { + uniqueAuthors.add(comment.authorName); + } + }); + + uniqueAuthors.forEach((author) => { + const personEl = root.ele(namespaces.w15, 'person'); + personEl.att(namespaces.w15, 'author', author); + personEl + .ele(namespaces.w15, 'presenceInfo') + .att(namespaces.w15, 'providerId', 'None') + .att(namespaces.w15, 'userId', author) + .up(); + personEl.up(); + }); + + return doc.end({ prettyPrint: true }); + } } export default DocxDocument; diff --git a/packages/docx-io/src/lib/html-to-docx/helpers/index.ts b/packages/docx-io/src/lib/html-to-docx/helpers/index.ts index 6934490252..07aab090f6 100644 --- a/packages/docx-io/src/lib/html-to-docx/helpers/index.ts +++ b/packages/docx-io/src/lib/html-to-docx/helpers/index.ts @@ -1,5 +1,30 @@ -/* eslint-disable import/prefer-default-export */ +/** + * Barrel file for html-to-docx helpers + */ + export { + buildImage, + buildList, convertVTreeToXML, - default as renderDocumentFile, + getListTracking, + resetListTracking, + setListTracking, } from './render-document-file'; + +export { default as renderDocumentFile } from './render-document-file'; + +export { + buildBold, + buildDrawing, + buildIndentation, + buildItalics, + buildLineBreak, + buildNumberingInstances, + buildParagraph, + buildRunsFromTextWithTokens, + buildTable, + buildTextElement, + buildTextRunFragment, + buildUnderline, + fixupLineHeight, +} from './xml-builder'; diff --git a/packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts b/packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts index fc7ff0196f..83daf09574 100644 --- a/packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts +++ b/packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts @@ -360,7 +360,9 @@ export const buildList = async ( isVNode(accumulator.at(-1)!.node) && ( (accumulator.at(-1)!.node as VNodeType).tagName || '' - ).toLowerCase() === 'p' + ).toLowerCase() === 'p' && + // Don't append <li> elements to paragraphs - they need separate processing + (childNode.tagName || '').toLowerCase() !== 'li' ) { const lastNode = accumulator.at(-1)!.node as VNodeType; if (lastNode.children) { @@ -927,6 +929,10 @@ export async function convertVTreeToXML( xmlFragment ); } else if (isVText(vTree)) { + const text = (vTree as VTextType).text; + if (!text || !text.trim()) { + return xmlFragment; + } const paragraphFragment = await xmlBuilder.buildParagraph( vTree as VTextType, {}, diff --git a/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.spec.ts b/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.spec.ts new file mode 100644 index 0000000000..a85611ff81 --- /dev/null +++ b/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.spec.ts @@ -0,0 +1,146 @@ +import { describe, expect, it, mock } from 'bun:test'; +import { buildRunsFromTextWithTokens } from './xml-builder'; + +// Mock types +type MockDocxDocumentInstance = { + _trackingState?: any; + comments: any[]; + commentIdMap: Map<string, number>; + lastCommentId: number; + revisionIdMap: Map<string, number>; + lastRevisionId: number; + ensureComment: (data: any, parentParaId?: string) => number; + getCommentId: (id: string) => number; + getRevisionId: (id?: string) => number; +}; + +describe('buildRunsFromTextWithTokens', () => { + it('should emit commentRangeEnd for reply with custom ID', () => { + const parentId = 'parent-1'; + const replyId = 'custom-reply-id'; + // xml-builder constructs composite reply ID: `${parentId}-reply-${replyId}` + const compositeReplyId = `${parentId}-reply-${replyId}`; + const parentNumericId = 100; + const replyNumericId = 200; + + const mockInstance: MockDocxDocumentInstance = { + comments: [], + commentIdMap: new Map(), + lastCommentId: 0, + revisionIdMap: new Map(), + lastRevisionId: 0, + ensureComment: (data: any, _parentParaId?: string) => { + if (data.id === parentId) return parentNumericId; + if (data.id === compositeReplyId) return replyNumericId; + return 999; + }, + getCommentId: (id: string) => { + if (id === parentId) return parentNumericId; + return 0; + }, + getRevisionId: (_id?: string) => 0, + }; + + // Populate commentIdMap as it would be during execution + mockInstance.commentIdMap.set(parentId, parentNumericId); + mockInstance.commentIdMap.set(compositeReplyId, replyNumericId); + + const tokenText = `[[DOCX_CMT_START:${encodeURIComponent( + JSON.stringify({ + id: parentId, + replies: [{ id: replyId, text: 'Reply' }], + }) + )}]]Comment Text[[DOCX_CMT_END:${encodeURIComponent(parentId)}]]`; + + const fragments = buildRunsFromTextWithTokens( + tokenText, + {}, + mockInstance as any + ); + + expect(fragments).not.toBeNull(); + if (!fragments) return; + + // Convert fragments to XML strings for inspection + const xmlStrings = fragments.map((f) => f.toString()); + const combinedXml = xmlStrings.join(''); + + // Check for Parent Start (using regex to be namespace-agnostic) + expect(combinedXml).toMatch( + new RegExp(`commentRangeStart[^>]*id="${parentNumericId}"`) + ); + + // Check for Reply Start + expect(combinedXml).toMatch( + new RegExp(`commentRangeStart[^>]*id="${replyNumericId}"`) + ); + + // Check for Parent End + expect(combinedXml).toMatch( + new RegExp(`commentRangeEnd[^>]*id="${parentNumericId}"`) + ); + + // Check for Reply End - THIS IS THE FIX VERIFICATION + // This asserts that the fix logic correctly found the reply ID and emitted the end tag + expect(combinedXml).toMatch( + new RegExp(`commentRangeEnd[^>]*id="${replyNumericId}"`) + ); + }); + + it('should fallback to generated ID when reply.id is missing', () => { + const parentId = 'parent-1'; + const parentNumericId = 100; + const expectedGeneratedId = `${parentId}-reply-0`; + const replyNumericId = 200; + + const mockInstance: MockDocxDocumentInstance = { + comments: [], + commentIdMap: new Map(), + lastCommentId: 0, + revisionIdMap: new Map(), + lastRevisionId: 0, + ensureComment: (data: any, _parentParaId?: string) => { + if (data.id === parentId) return parentNumericId; + if (data.id === expectedGeneratedId) return replyNumericId; + return 999; + }, + getCommentId: (id: string) => { + if (id === parentId) return parentNumericId; + return 0; + }, + getRevisionId: (_id?: string) => 0, + }; + + mockInstance.commentIdMap.set(parentId, parentNumericId); + mockInstance.commentIdMap.set(expectedGeneratedId, replyNumericId); + + const tokenText = `[[DOCX_CMT_START:${encodeURIComponent( + JSON.stringify({ + id: parentId, + replies: [{ text: 'Reply without ID' }], // No ID provided + }) + )}]]Comment Text[[DOCX_CMT_END:${encodeURIComponent(parentId)}]]`; + + const fragments = buildRunsFromTextWithTokens( + tokenText, + {}, + mockInstance as any + ); + + expect(fragments).not.toBeNull(); + if (!fragments) return; + + const xmlStrings = fragments.map((f) => f.toString()); + const combinedXml = xmlStrings.join(''); + + // Check for Reply Start with expected generated ID + expect(combinedXml).toMatch( + new RegExp(`commentRangeStart[^>]*id="${replyNumericId}"`) + ); + + // Check for Reply End + expect(combinedXml).toMatch( + new RegExp(`commentRangeEnd[^>]*id="${replyNumericId}"`) + ); + }); +}); diff --git a/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts b/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts index e98f2e2f15..73866256a4 100644 --- a/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts +++ b/packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts @@ -1,4 +1,5 @@ /* biome-ignore-all lint/nursery/useMaxParams: legacy code */ +/** biome-ignore-all lint/style/useAtIndex: legacy code */ /* biome-ignore-all lint/performance/useTopLevelRegex: legacy code */ /* biome-ignore-all lint/style/noParameterAssign: legacy code */ /* biome-ignore-all lint/style/useForOf: legacy code */ @@ -26,6 +27,18 @@ import { paragraphBordersObject, verticalAlignValues, } from '../constants'; +import { + buildCommentRangeEnd, + buildCommentRangeStart, + buildCommentReferenceRun, + buildDeletedTextElement, + ensureTrackingState, + hasTrackingTokens, + splitDocxTrackingTokens, + wrapRunWithSuggestion, + type ActiveSuggestion, + type TrackingDocumentInstance, +} from '../tracking'; import namespaces from '../namespaces'; import { hex3Regex, @@ -93,7 +106,7 @@ type MediaFileResponse = { id: number; }; -type DocxDocumentInstance = { +type DocxDocumentInstance = Partial<TrackingDocumentInstance> & { availableDocumentSpace: number; createDocumentRelationships: ( filename: string, @@ -397,6 +410,225 @@ const buildTextElement = (text: string): XMLBuilderType => .txt(text) .up(); +/** + * Build a text run fragment with run properties. + * Used for building runs within tracked changes. + */ +const buildTextRunFragment = ( + text: string, + attributes: RunAttributes, + options?: { deleted?: boolean } +): XMLBuilderType => { + const runFragment = fragment({ namespaceAlias: { w: namespaces.w } }).ele( + '@w', + 'r' + ); + const runPropertiesFragment = buildRunProperties(cloneDeep(attributes)); + + runFragment.import(runPropertiesFragment); + runFragment.import( + options?.deleted ? buildDeletedTextElement(text) : buildTextElement(text) + ); + runFragment.up(); + + return runFragment; +}; + +/** + * Build runs from text that may contain DOCX tracking tokens. + * Handles insertions, deletions, and comments by parsing tokens + * and generating appropriate XML structures. + * + * Returns null if text has no tracking tokens (use normal processing). + */ +const buildRunsFromTextWithTokens = ( + text: string, + attributes: RunAttributes, + docxDocumentInstance: DocxDocumentInstance +): XMLBuilderType[] | null => { + // Check if document instance has tracking support + if ( + !docxDocumentInstance.ensureComment || + !docxDocumentInstance.getCommentId || + !docxDocumentInstance.getRevisionId + ) { + return null; + } + + const parts = splitDocxTrackingTokens(text); + + // If just a single text part, return null to use normal processing + if (parts.length === 1 && parts[0].type === 'text') { + return null; + } + + const fragments: XMLBuilderType[] = []; + const trackingState = ensureTrackingState( + docxDocumentInstance as Required< + Pick< + DocxDocumentInstance, + | '_trackingState' + | 'comments' + | 'commentIdMap' + | 'lastCommentId' + | 'revisionIdMap' + | 'lastRevisionId' + | 'ensureComment' + | 'getCommentId' + | 'getRevisionId' + > + > + ); + + for (const part of parts) { + if (part.type === 'text') { + if (!part.value) continue; + + const activeSuggestion: ActiveSuggestion | undefined = + trackingState.suggestionStack[trackingState.suggestionStack.length - 1]; + const runFragment = buildTextRunFragment(part.value, attributes, { + deleted: activeSuggestion?.type === 'remove', + }); + + fragments.push( + activeSuggestion + ? wrapRunWithSuggestion(runFragment, activeSuggestion) + : runFragment + ); + continue; + } + + if (part.type === 'commentStart') { + const data = part.data; + // Register parent comment + const parentCommentId = docxDocumentInstance.ensureComment({ + id: data.id, + authorName: data.authorName, + authorInitials: data.authorInitials, + date: data.date, + paraId: data.paraId, + text: data.text, + }); + fragments.push(buildCommentRangeStart(parentCommentId)); + + // Register and anchor reply comments + if ( + data.replies && + data.replies.length > 0 && + docxDocumentInstance.comments && + docxDocumentInstance.ensureComment + ) { + // Find parent's paraId for threading + const parentComment = docxDocumentInstance.comments.find( + (c) => c.id === parentCommentId + ); + const parentParaId = parentComment?.paraId; + + data.replies.forEach((reply, idx) => { + const replyId = reply.id + ? `${data.id}-reply-${reply.id}` + : `${data.id}-reply-${idx}`; + + // Track reply ID associated with this parent + const existingReplies = + trackingState.replyIdsByParent.get(data.id) ?? []; + if (!existingReplies.includes(replyId)) { + existingReplies.push(replyId); + trackingState.replyIdsByParent.set(data.id, existingReplies); + } + + const replyCommentId = docxDocumentInstance.ensureComment!( + { + id: replyId, + authorName: reply.authorName, + authorInitials: reply.authorInitials, + date: reply.date, + paraId: reply.paraId, + text: reply.text, + }, + parentParaId + ); + // Reply commentRangeStart anchored after parent's + fragments.push(buildCommentRangeStart(replyCommentId)); + }); + } + continue; + } + + if (part.type === 'commentEnd') { + const commentId = docxDocumentInstance.getCommentId(part.id); + fragments.push(buildCommentRangeEnd(commentId)); + fragments.push(buildCommentReferenceRun(commentId)); + + // Emit range end + reference for reply comments + const replyIds: number[] = []; + const trackedReplies = trackingState.replyIdsByParent.get(part.id) || []; + + if (docxDocumentInstance.commentIdMap) { + // First try to use explicitly tracked reply IDs + if (trackedReplies.length > 0) { + for (const replyKey of trackedReplies) { + const numId = docxDocumentInstance.commentIdMap.get(replyKey); + if (numId !== undefined) { + replyIds.push(numId); + } + } + } else { + // Fallback to legacy prefix scan if no tracked replies found (backward compatibility) + for (const [ + key, + numId, + ] of docxDocumentInstance.commentIdMap.entries()) { + if (key.startsWith(`${part.id}-reply-`)) { + replyIds.push(numId); + } + } + } + } + // Sort to preserve insertion order (though trackedReplies order should be preserved) + // If we used trackedReplies, they are already in insertion order, but sorting by numeric ID + // is usually safe if IDs are allocated sequentially. However, trackedReplies order is more reliable. + // If we used trackedReplies, let's trust that order. If we used fallback, we sort. + if (trackedReplies.length === 0) { + replyIds.sort((a, b) => a - b); + } + for (const replyNumId of replyIds) { + fragments.push(buildCommentRangeEnd(replyNumId)); + fragments.push(buildCommentReferenceRun(replyNumId)); + } + continue; + } + + if (part.type === 'insStart' || part.type === 'delStart') { + const data = part.data; + const revisionId = docxDocumentInstance.getRevisionId(data.id); + const suggestionId = data.id || `suggestion-${revisionId}`; + const suggestion: ActiveSuggestion = { + id: suggestionId, + type: part.type === 'delStart' ? 'remove' : 'insert', + author: data.author, + date: data.date, + revisionId, + }; + + // Remove any existing suggestion with same ID before pushing + trackingState.suggestionStack = trackingState.suggestionStack.filter( + (item) => item.id !== suggestionId + ); + trackingState.suggestionStack.push(suggestion); + continue; + } + + if (part.type === 'insEnd' || part.type === 'delEnd') { + trackingState.suggestionStack = trackingState.suggestionStack.filter( + (item) => item.id !== part.id + ); + } + } + + return fragments; +}; + const fixupLineHeight = ( lineHeight: number, fontSize: number | null @@ -559,13 +791,11 @@ const modifiedStyleAttributesBuilder = ( modifiedAttributes.color = fixupColorCode(style.color); } - if ( - style['background-color'] && - !colorlessColors.includes(style['background-color']) - ) { - modifiedAttributes.backgroundColor = fixupColorCode( - style['background-color'] - ); + const backgroundColor = + style['background-color'] ?? + (style as Record<string, string>).backgroundColor; + if (backgroundColor && !colorlessColors.includes(backgroundColor)) { + modifiedAttributes.backgroundColor = fixupColorCode(backgroundColor); } if ( @@ -781,11 +1011,30 @@ const buildRun = async ( while (vNodes.length) { const tempVNode = vNodes.shift()!; if (isVText(tempVNode)) { - const textFragment = buildTextElement((tempVNode as VTextType).text); - const tempRunPropertiesFragment = buildRunProperties({ - ...attributes, - ...tempAttributes, - }); + const textContent = (tempVNode as VTextType).text; + const mergedAttributes = { ...attributes, ...tempAttributes }; + + // Check for tracking tokens in text + if (docxDocumentInstance && hasTrackingTokens(textContent)) { + const trackingFragments = buildRunsFromTextWithTokens( + textContent, + mergedAttributes, + docxDocumentInstance + ); + if (trackingFragments) { + runFragmentsArray.push(...trackingFragments); + // re initialize temp run fragments with new fragment + tempAttributes = cloneDeep(attributes); + tempRunFragment = fragment({ + namespaceAlias: { w: namespaces.w }, + }).ele('@w', 'r'); + continue; + } + } + + // Normal text processing + const textFragment = buildTextElement(textContent); + const tempRunPropertiesFragment = buildRunProperties(mergedAttributes); tempRunFragment.import(tempRunPropertiesFragment); tempRunFragment.import(textFragment); runFragmentsArray.push(tempRunFragment); @@ -895,7 +1144,22 @@ const buildRun = async ( runFragment.import(runPropertiesFragment); if (isVText(vNode)) { - const textFragment = buildTextElement((vNode as VTextType).text); + const textContent = (vNode as VTextType).text; + + // Check for tracking tokens in text + if (docxDocumentInstance && hasTrackingTokens(textContent)) { + const trackingFragments = buildRunsFromTextWithTokens( + textContent, + attributes, + docxDocumentInstance + ); + if (trackingFragments) { + return trackingFragments; + } + } + + // Normal text processing + const textFragment = buildTextElement(textContent); runFragment.import(textFragment); } else if (attributes && attributes.type === 'picture') { let response: MediaFileResponse | null = null; @@ -3205,4 +3469,7 @@ export { buildUnderline, buildDrawing, fixupLineHeight, + // Tracking support exports + buildRunsFromTextWithTokens, + buildTextRunFragment, }; diff --git a/packages/docx-io/src/lib/html-to-docx/html-to-docx.spec.ts b/packages/docx-io/src/lib/html-to-docx/html-to-docx.spec.ts index 563176fc01..5758010e6a 100644 --- a/packages/docx-io/src/lib/html-to-docx/html-to-docx.spec.ts +++ b/packages/docx-io/src/lib/html-to-docx/html-to-docx.spec.ts @@ -8,9 +8,10 @@ * - List indentation */ +import { describe, expect, it } from 'bun:test'; import JSZip from 'jszip'; -import { htmlToDocxBlob } from '../html-to-docx'; +import { htmlToDocxBlob, htmlToDocxBuffer } from '../exportDocx'; // Helper to load zip from Blob async function loadZipFromBlob(blob: Blob): Promise<JSZip> { diff --git a/packages/docx-io/src/lib/html-to-docx/html-to-docx.ts b/packages/docx-io/src/lib/html-to-docx/html-to-docx.ts index 53d43ad83e..f50c583186 100644 --- a/packages/docx-io/src/lib/html-to-docx/html-to-docx.ts +++ b/packages/docx-io/src/lib/html-to-docx/html-to-docx.ts @@ -12,6 +12,13 @@ import VText from 'virtual-dom/vnode/vtext'; import { create } from 'xmlbuilder2'; import { + commentsExtendedRelationshipType, + commentsExtendedType, + commentsExtensibleRelationshipType, + commentsExtensibleType, + commentsIdsRelationshipType, + commentsIdsType, + commentsType, defaultDocumentOptions, defaultHTMLString, documentFileName, @@ -20,6 +27,8 @@ import { headerFileName, headerType, internalRelationship, + peopleRelationshipType, + peopleType, relsFolderName, themeFileName, themeFolder, @@ -63,7 +72,7 @@ interface NormalizedDocumentOptions { headerType?: 'default' | 'even' | 'first'; keywords?: string[]; lastModifiedBy?: string; - lineNumber?: boolean; + lineNumber?: boolean | LineNumberOptions; lineNumberOptions?: LineNumberOptions; margins?: NormalizedMargins | null; modifiedAt?: Date; @@ -325,6 +334,16 @@ async function addFilesToContainer( // @ts-expect-error - DocxDocument implements DocxDocumentInstance with slight variations docxDocument.documentXML = await renderDocumentFile(docxDocument); + // Create comments relationship if there are comments (populated by renderDocumentFile) + if (docxDocument.comments.length > 0) { + docxDocument.createDocumentRelationships( + documentFileName, + commentsType, + 'comments.xml', + internalRelationship + ); + } + zip.folder(relsFolderName)!.file( '.rels', create({ encoding: 'UTF-8', standalone: true }, relsXML).toString({ @@ -427,6 +446,61 @@ async function addFilesToContainer( createFolders: false, }); + // Add comment-related XML files if there are comments + if (docxDocument.comments.length > 0) { + zip + .folder(wordFolder)! + .file('comments.xml', docxDocument.generateCommentsXML(), { + createFolders: false, + }) + .file( + 'commentsExtended.xml', + docxDocument.generateCommentsExtendedXML(), + { + createFolders: false, + } + ) + .file('commentsIds.xml', docxDocument.generateCommentsIdsXML(), { + createFolders: false, + }) + .file( + 'commentsExtensible.xml', + docxDocument.generateCommentsExtensibleXML(), + { + createFolders: false, + } + ) + .file('people.xml', docxDocument.generatePeopleXML(), { + createFolders: false, + }); + + // Add relationships for the 4 new comment-related files + docxDocument.createDocumentRelationships( + documentFileName, + commentsExtendedType, + 'commentsExtended.xml', + internalRelationship + ); + docxDocument.createDocumentRelationships( + documentFileName, + commentsIdsType, + 'commentsIds.xml', + internalRelationship + ); + docxDocument.createDocumentRelationships( + documentFileName, + commentsExtensibleType, + 'commentsExtensible.xml', + internalRelationship + ); + docxDocument.createDocumentRelationships( + documentFileName, + peopleType, + 'people.xml', + internalRelationship + ); + } + const relationshipXMLs = docxDocument.generateRelsXML(); if (relationshipXMLs && Array.isArray(relationshipXMLs)) { diff --git a/packages/docx-io/src/lib/html-to-docx/index.ts b/packages/docx-io/src/lib/html-to-docx/index.ts index 25cfaa5bcd..1bff4fccff 100644 --- a/packages/docx-io/src/lib/html-to-docx/index.ts +++ b/packages/docx-io/src/lib/html-to-docx/index.ts @@ -1,9 +1,100 @@ -/* eslint-disable no-useless-escape */ -import JSZip from 'jszip'; +/** + * HTML to DOCX converter + * + * This module provides tools for converting HTML to DOCX format. + */ -import addFilesToContainer from './html-to-docx'; +// Main converter function (default export) +export { default as HTMLtoDOCX, default } from './html-to-docx'; + +// Re-export types for backwards compatibility +export type { DocumentMargins } from './schemas'; + +// Re-export tracking utilities +export * from './tracking'; + +// Re-export constants +export * from './constants'; + +// Re-export namespaces +export { default as namespaces } from './namespaces'; + +// ============================================================================ +// Type definitions (matching index.d.ts namespace types) +// ============================================================================ + +export type Margins = { + bottom?: number; + footer?: number; + gutter?: number; + header?: number; + left?: number; + right?: number; + top?: number; +}; + +export type PageSize = { + height?: number; + width?: number; +}; + +export type Row = { + cantSplit?: boolean; +}; + +export type BorderOptions = { + color?: string; + size?: number; +}; + +export type TableOptions = { + borderOptions?: BorderOptions; + row?: Row; +}; + +export type TableBorderOptions = BorderOptions; + +export type LineNumberOptions = { + countBy?: number; + distance?: number; + restart?: 'continuous' | 'newPage' | 'newSection'; + start?: number; +}; + +export type HeadingSpacing = { + after?: number; + before?: number; + line?: number; + lineRule?: 'atLeast' | 'auto' | 'exact'; +}; + +export type HeadingStyleOptions = { + bold?: boolean; + color?: string; + font?: string; + fontSize?: number; + italic?: boolean; + spacing?: HeadingSpacing; + underline?: boolean; +}; + +export type HeadingOptions = { + heading1?: HeadingStyleOptions; + heading2?: HeadingStyleOptions; + heading3?: HeadingStyleOptions; + heading4?: HeadingStyleOptions; + heading5?: HeadingStyleOptions; + heading6?: HeadingStyleOptions; +}; + +export type ImageProcessing = { + svgHandling?: 'convert' | 'native'; +}; + +export type NumberingOptions = { + defaultOrderedListStyleType?: string; +}; -/** Document options for DOCX generation */ export type DocumentOptions = { complexScriptFontSize?: number | string; createdAt?: Date; @@ -17,9 +108,11 @@ export type DocumentOptions = { footerType?: 'default' | 'even' | 'first'; header?: boolean; headerType?: 'default' | 'even' | 'first'; + heading?: HeadingOptions; + imageProcessing?: ImageProcessing; keywords?: string[]; lastModifiedBy?: string; - lineNumber?: boolean; + lineNumber?: boolean | LineNumberOptions; lineNumberOptions?: LineNumberOptions; margins?: Margins; modifiedAt?: Date; @@ -33,103 +126,3 @@ export type DocumentOptions = { table?: TableOptions; title?: string; }; - -export type LineNumberOptions = { - countBy?: number; - restart?: 'continuous' | 'newPage' | 'newSection'; - start?: number; -}; - -export type Margins = { - bottom?: number | string; - footer?: number | string; - gutter?: number | string; - header?: number | string; - left?: number | string; - right?: number | string; - top?: number | string; -}; - -export type NumberingOptions = { - defaultOrderedListStyleType?: string; -}; - -export type PageSize = { - height?: number | string; - width?: number | string; -}; - -export type TableOptions = { - row?: { - cantSplit?: boolean; - }; -}; - -const minifyHTMLString = (htmlString: string): string | null => { - try { - if (typeof htmlString === 'string') { - const minifiedHTMLString = htmlString - .replace(/\n/g, ' ') - .replace(/\r/g, ' ') - .replace(/\r\n/g, ' ') - .replace(/[\t]+</g, '<') - .replace(/>[\t ]+</g, '><') - .replace(/>[\t ]+$/g, '>'); - - return minifiedHTMLString; - } - - throw new Error('invalid html string'); - } catch (_error) { - return null; - } -}; - -async function generateContainer( - htmlString: string, - headerHTMLString: string | null | undefined, - documentOptions: DocumentOptions, - footerHTMLString?: string | null -): Promise<Blob | Buffer> { - const zip = new JSZip(); - - let contentHTML: string | null = htmlString; - let headerHTML: string | null | undefined = headerHTMLString; - let footerHTML: string | null | undefined = footerHTMLString; - - if (htmlString) { - contentHTML = minifyHTMLString(contentHTML as string); - } - if (headerHTMLString) { - headerHTML = minifyHTMLString(headerHTML as string); - } - if (footerHTMLString) { - footerHTML = minifyHTMLString(footerHTML as string); - } - - await addFilesToContainer( - zip, - contentHTML, - documentOptions, - headerHTML, - footerHTML - ); - - const buffer = await zip.generateAsync({ type: 'arraybuffer' }); - - if (Object.hasOwn(global, 'Buffer')) { - return Buffer.from(new Uint8Array(buffer)); - } - if (Object.hasOwn(global, 'Blob')) { - // eslint-disable-next-line no-undef - return new Blob([buffer], { - type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - }); - } - - throw new Error( - 'Add blob support using a polyfill eg https://github.com/bjornstar/blob-polyfill' - ); -} - -export default generateContainer; diff --git a/packages/docx-io/src/lib/html-to-docx/namespaces.ts b/packages/docx-io/src/lib/html-to-docx/namespaces.ts index 68b8f000b8..b44cd86fa7 100644 --- a/packages/docx-io/src/lib/html-to-docx/namespaces.ts +++ b/packages/docx-io/src/lib/html-to-docx/namespaces.ts @@ -18,6 +18,8 @@ export type OoxmlNamespaces = { dcmitype: string; /** Dublin Core terms namespace */ dcterms: string; + /** Comments relationship namespace */ + comments: string; /** Font table relationship namespace */ fontTable: string; /** Footer relationship namespace */ @@ -60,6 +62,14 @@ export type OoxmlNamespaces = { w: string; /** Word 2010 namespace */ w10: string; + /** Word 2010 WordML namespace */ + w14: string; + /** Word 2012 WordML namespace */ + w15: string; + /** Word 2016 WordML comment IDs namespace */ + w16cid: string; + /** Word 2018 WordML comment extensible namespace */ + w16cex: string; /** Web settings relationship namespace */ webSettingsRelation: string; /** Word 2006 WordML namespace */ @@ -84,6 +94,8 @@ const namespaces: OoxmlNamespaces = { dc: 'http://purl.org/dc/elements/1.1/', dcmitype: 'http://purl.org/dc/dcmitype/', dcterms: 'http://purl.org/dc/terms/', + comments: + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', fontTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable', footers: @@ -115,6 +127,10 @@ const namespaces: OoxmlNamespaces = { vt: 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes', w: 'http://schemas.openxmlformats.org/wordprocessingml/2006/main', w10: 'urn:schemas-microsoft-com:office:word', + w14: 'http://schemas.microsoft.com/office/word/2010/wordml', + w15: 'http://schemas.microsoft.com/office/word/2012/wordml', + w16cid: 'http://schemas.microsoft.com/office/word/2016/wordml/cid', + w16cex: 'http://schemas.microsoft.com/office/word/2018/wordml/cex', webSettingsRelation: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings', wne: 'http://schemas.microsoft.com/office/word/2006/wordml', diff --git a/packages/docx-io/src/lib/html-to-docx/schemas/core.ts b/packages/docx-io/src/lib/html-to-docx/schemas/core.ts index 3f044402e5..eb1cff6aff 100644 --- a/packages/docx-io/src/lib/html-to-docx/schemas/core.ts +++ b/packages/docx-io/src/lib/html-to-docx/schemas/core.ts @@ -2,6 +2,21 @@ import { applicationName } from '../constants'; import namespaces from '../namespaces'; +/** + * Format a Date as local time with Z suffix. + * Word uses local time with a trailing 'Z' in dcterms:created/modified + * (non-standard but expected by the OOXML ecosystem). + */ +function toLocalWithZ(d: Date): string { + const Y = d.getFullYear(); + const M = String(d.getMonth() + 1).padStart(2, '0'); + const D = String(d.getDate()).padStart(2, '0'); + const h = String(d.getHours()).padStart(2, '0'); + const m = String(d.getMinutes()).padStart(2, '0'); + const s = String(d.getSeconds()).padStart(2, '0'); + return `${Y}-${M}-${D}T${h}:${m}:${s}Z`; +} + const generateCoreXML = ( title: string = '', subject: string = '', @@ -35,13 +50,13 @@ const generateCoreXML = ( <cp:revision>${revision}</cp:revision> <dcterms:created xsi:type="dcterms:W3CDTF">${ createdAt instanceof Date - ? createdAt.toISOString() - : new Date().toISOString() + ? toLocalWithZ(createdAt) + : toLocalWithZ(new Date()) }</dcterms:created> <dcterms:modified xsi:type="dcterms:W3CDTF">${ modifiedAt instanceof Date - ? modifiedAt.toISOString() - : new Date().toISOString() + ? toLocalWithZ(modifiedAt) + : toLocalWithZ(new Date()) }</dcterms:modified> </cp:coreProperties> `; diff --git a/packages/docx-io/src/lib/html-to-docx/schemas/index.ts b/packages/docx-io/src/lib/html-to-docx/schemas/index.ts index 1e57f3bd30..4edf7286f6 100644 --- a/packages/docx-io/src/lib/html-to-docx/schemas/index.ts +++ b/packages/docx-io/src/lib/html-to-docx/schemas/index.ts @@ -1,8 +1,14 @@ +/** + * Barrel file for html-to-docx schemas + */ + export { default as contentTypesXML } from './content-types'; export { default as generateCoreXML } from './core'; -export { default as generateDocumentTemplate } from './document.template'; -export type { DocumentMargins } from './document.template'; export { default as documentRelsXML } from './document-rels'; +export { + default as generateDocumentTemplate, + type DocumentMargins, +} from './document.template'; export { default as fontTableXML } from './font-table'; export { default as genericRelsXML } from './generic-rels'; export { default as generateNumberingXMLTemplate } from './numbering'; diff --git a/packages/docx-io/src/lib/html-to-docx/schemas/styles.ts b/packages/docx-io/src/lib/html-to-docx/schemas/styles.ts index 06b9a20f1a..baa3581437 100644 --- a/packages/docx-io/src/lib/html-to-docx/schemas/styles.ts +++ b/packages/docx-io/src/lib/html-to-docx/schemas/styles.ts @@ -147,6 +147,69 @@ const generateStylesXML = ( <w:szCs w:val="20" /> </w:rPr> </w:style> + <w:style w:type="paragraph" w:styleId="CommentText"> + <w:name w:val="annotation text" /> + <w:basedOn w:val="Normal" /> + <w:link w:val="CommentTextChar" /> + <w:uiPriority w:val="99" /> + <w:semiHidden /> + <w:unhideWhenUsed /> + <w:pPr> + <w:spacing w:line="240" w:lineRule="auto" /> + </w:pPr> + <w:rPr> + <w:sz w:val="20" /> + <w:szCs w:val="20" /> + </w:rPr> + </w:style> + <w:style w:type="character" w:styleId="CommentTextChar" w:customStyle="1"> + <w:name w:val="Comment Text Char" /> + <w:basedOn w:val="DefaultParagraphFont" /> + <w:link w:val="CommentText" /> + <w:uiPriority w:val="99" /> + <w:semiHidden /> + <w:rPr> + <w:sz w:val="20" /> + <w:szCs w:val="20" /> + </w:rPr> + </w:style> + <w:style w:type="character" w:styleId="CommentReference"> + <w:name w:val="annotation reference" /> + <w:basedOn w:val="DefaultParagraphFont" /> + <w:uiPriority w:val="99" /> + <w:semiHidden /> + <w:unhideWhenUsed /> + <w:rPr> + <w:sz w:val="16" /> + <w:szCs w:val="16" /> + </w:rPr> + </w:style> + <w:style w:type="paragraph" w:styleId="CommentSubject"> + <w:name w:val="annotation subject" /> + <w:basedOn w:val="CommentText" /> + <w:next w:val="CommentText" /> + <w:link w:val="CommentSubjectChar" /> + <w:uiPriority w:val="99" /> + <w:semiHidden /> + <w:unhideWhenUsed /> + <w:rPr> + <w:b /> + <w:bCs /> + </w:rPr> + </w:style> + <w:style w:type="character" w:styleId="CommentSubjectChar" w:customStyle="1"> + <w:name w:val="Comment Subject Char" /> + <w:basedOn w:val="CommentTextChar" /> + <w:link w:val="CommentSubject" /> + <w:uiPriority w:val="99" /> + <w:semiHidden /> + <w:rPr> + <w:b /> + <w:bCs /> + <w:sz w:val="20" /> + <w:szCs w:val="20" /> + </w:rPr> + </w:style> </w:styles> `; diff --git a/packages/docx-io/src/lib/html-to-docx/tracking.spec.ts b/packages/docx-io/src/lib/html-to-docx/tracking.spec.ts new file mode 100644 index 0000000000..20b1977a8b --- /dev/null +++ b/packages/docx-io/src/lib/html-to-docx/tracking.spec.ts @@ -0,0 +1,427 @@ +/** + * Unit tests for DOCX Tracked Changes and Comments Export Support. + * + * Tests the token-based tracking system for exporting Plate editor + * suggestions and comments to Word's tracked changes and comments format. + */ + +import { describe, expect, it, mock } from 'bun:test'; +import JSZip from 'jszip'; + +import { htmlToDocxBlob } from '../exportDocx'; +import { + buildCommentEndToken, + buildCommentStartToken, + buildSuggestionEndToken, + buildSuggestionStartToken, + DOCX_COMMENT_END_TOKEN_PREFIX, + DOCX_COMMENT_START_TOKEN_PREFIX, + DOCX_COMMENT_TOKEN_SUFFIX, + DOCX_DELETION_END_TOKEN_PREFIX, + DOCX_DELETION_START_TOKEN_PREFIX, + DOCX_DELETION_TOKEN_SUFFIX, + DOCX_INSERTION_END_TOKEN_PREFIX, + DOCX_INSERTION_START_TOKEN_PREFIX, + DOCX_INSERTION_TOKEN_SUFFIX, + hasTrackingTokens, + splitDocxTrackingTokens, +} from './tracking'; + +// Helper to load zip from Blob +async function loadZipFromBlob(blob: Blob): Promise<JSZip> { + const arrayBuffer = await blob.arrayBuffer(); + return JSZip.loadAsync(arrayBuffer); +} + +describe('Tracking Token Constants', () => { + it('should have correct insertion token prefixes', () => { + expect(DOCX_INSERTION_START_TOKEN_PREFIX).toBe('[[DOCX_INS_START:'); + expect(DOCX_INSERTION_END_TOKEN_PREFIX).toBe('[[DOCX_INS_END:'); + expect(DOCX_INSERTION_TOKEN_SUFFIX).toBe(']]'); + }); + + it('should have correct deletion token prefixes', () => { + expect(DOCX_DELETION_START_TOKEN_PREFIX).toBe('[[DOCX_DEL_START:'); + expect(DOCX_DELETION_END_TOKEN_PREFIX).toBe('[[DOCX_DEL_END:'); + expect(DOCX_DELETION_TOKEN_SUFFIX).toBe(']]'); + }); + + it('should have correct comment token prefixes', () => { + expect(DOCX_COMMENT_START_TOKEN_PREFIX).toBe('[[DOCX_CMT_START:'); + expect(DOCX_COMMENT_END_TOKEN_PREFIX).toBe('[[DOCX_CMT_END:'); + expect(DOCX_COMMENT_TOKEN_SUFFIX).toBe(']]'); + }); +}); + +describe('hasTrackingTokens', () => { + it('should return true for text with insertion tokens', () => { + const text = '[[DOCX_INS_START:test]]inserted text[[DOCX_INS_END:test]]'; + expect(hasTrackingTokens(text)).toBe(true); + }); + + it('should return true for text with deletion tokens', () => { + const text = '[[DOCX_DEL_START:test]]deleted text[[DOCX_DEL_END:test]]'; + expect(hasTrackingTokens(text)).toBe(true); + }); + + it('should return true for text with comment tokens', () => { + const text = '[[DOCX_CMT_START:test]]commented text[[DOCX_CMT_END:test]]'; + expect(hasTrackingTokens(text)).toBe(true); + }); + + it('should return false for text without tokens', () => { + const text = 'This is plain text without any tracking tokens'; + expect(hasTrackingTokens(text)).toBe(false); + }); + + it('should return false for empty text', () => { + expect(hasTrackingTokens('')).toBe(false); + }); +}); + +describe('splitDocxTrackingTokens', () => { + it('should return single text part for text without tokens', () => { + const text = 'Plain text'; + const parts = splitDocxTrackingTokens(text); + + expect(parts).toHaveLength(1); + expect(parts[0]).toEqual({ type: 'text', value: 'Plain text' }); + }); + + it('should parse insertion start token', () => { + const payload = encodeURIComponent( + JSON.stringify({ id: 'ins-1', author: 'John', date: '2024-01-01' }) + ); + const text = `Before [[DOCX_INS_START:${payload}]]inserted[[DOCX_INS_END:ins-1]] after`; + const parts = splitDocxTrackingTokens(text); + + expect(parts).toHaveLength(5); + expect(parts[0]).toEqual({ type: 'text', value: 'Before ' }); + expect(parts[1]).toEqual({ + type: 'insStart', + data: { id: 'ins-1', author: 'John', date: '2024-01-01' }, + }); + expect(parts[2]).toEqual({ type: 'text', value: 'inserted' }); + expect(parts[3]).toEqual({ type: 'insEnd', id: 'ins-1' }); + expect(parts[4]).toEqual({ type: 'text', value: ' after' }); + }); + + it('should parse deletion tokens', () => { + const payload = encodeURIComponent( + JSON.stringify({ id: 'del-1', author: 'Jane' }) + ); + const text = `[[DOCX_DEL_START:${payload}]]deleted[[DOCX_DEL_END:del-1]]`; + const parts = splitDocxTrackingTokens(text); + + expect(parts).toHaveLength(3); + expect(parts[0]).toEqual({ + type: 'delStart', + data: { id: 'del-1', author: 'Jane' }, + }); + expect(parts[1]).toEqual({ type: 'text', value: 'deleted' }); + expect(parts[2]).toEqual({ type: 'delEnd', id: 'del-1' }); + }); + + it('should parse comment tokens', () => { + const payload = encodeURIComponent( + JSON.stringify({ + id: 'cmt-1', + authorName: 'Bob', + authorInitials: 'B', + text: 'This is a comment', + }) + ); + const text = `[[DOCX_CMT_START:${payload}]]commented[[DOCX_CMT_END:cmt-1]]`; + const parts = splitDocxTrackingTokens(text); + + expect(parts).toHaveLength(3); + expect(parts[0]).toEqual({ + type: 'commentStart', + data: { + id: 'cmt-1', + authorName: 'Bob', + authorInitials: 'B', + text: 'This is a comment', + }, + }); + expect(parts[1]).toEqual({ type: 'text', value: 'commented' }); + expect(parts[2]).toEqual({ type: 'commentEnd', id: 'cmt-1' }); + }); + + it('should handle nested tokens', () => { + const insPayload = encodeURIComponent(JSON.stringify({ id: 'ins-1' })); + const cmtPayload = encodeURIComponent( + JSON.stringify({ id: 'cmt-1', text: 'Comment' }) + ); + const text = `[[DOCX_INS_START:${insPayload}]][[DOCX_CMT_START:${cmtPayload}]]nested[[DOCX_CMT_END:cmt-1]][[DOCX_INS_END:ins-1]]`; + const parts = splitDocxTrackingTokens(text); + + expect(parts).toHaveLength(5); + expect(parts[0].type).toBe('insStart'); + expect(parts[1].type).toBe('commentStart'); + expect(parts[2]).toEqual({ type: 'text', value: 'nested' }); + expect(parts[3].type).toBe('commentEnd'); + expect(parts[4].type).toBe('insEnd'); + }); + + it('should handle malformed tokens as text', () => { + const text = '[[DOCX_INS_START:invalid-not-json]]text[[DOCX_INS_END:id]]'; + const parts = splitDocxTrackingTokens(text); + + // The malformed start token should be treated as text + expect(parts.length).toBeGreaterThan(0); + expect(parts.some((p) => p.type === 'text')).toBe(true); + }); +}); + +describe('Token Building Functions', () => { + describe('buildSuggestionStartToken', () => { + it('should build insertion start token', () => { + const payload = { id: 'ins-1', author: 'John', date: '2024-01-01' }; + const token = buildSuggestionStartToken(payload, 'insert'); + + expect(token).toContain('[[DOCX_INS_START:'); + expect(token).toContain(']]'); + + // Should be parseable + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0].type).toBe('insStart'); + }); + + it('should build deletion start token', () => { + const payload = { id: 'del-1', author: 'Jane' }; + const token = buildSuggestionStartToken(payload, 'remove'); + + expect(token).toContain('[[DOCX_DEL_START:'); + expect(token).toContain(']]'); + + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0].type).toBe('delStart'); + }); + }); + + describe('buildSuggestionEndToken', () => { + it('should build insertion end token', () => { + const token = buildSuggestionEndToken('ins-1', 'insert'); + + expect(token).toBe('[[DOCX_INS_END:ins-1]]'); + + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0]).toEqual({ type: 'insEnd', id: 'ins-1' }); + }); + + it('should build deletion end token', () => { + const token = buildSuggestionEndToken('del-1', 'remove'); + + expect(token).toBe('[[DOCX_DEL_END:del-1]]'); + + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0]).toEqual({ type: 'delEnd', id: 'del-1' }); + }); + }); + + describe('buildCommentStartToken', () => { + it('should build comment start token with full payload', () => { + const payload = { + id: 'cmt-1', + authorName: 'John Doe', + authorInitials: 'JD', + date: '2024-01-01T10:00:00Z', + text: 'This is a comment', + }; + const token = buildCommentStartToken(payload); + + expect(token).toContain('[[DOCX_CMT_START:'); + expect(token).toContain(']]'); + + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0].type).toBe('commentStart'); + if (parts[0].type === 'commentStart') { + expect(parts[0].data.id).toBe('cmt-1'); + expect(parts[0].data.authorName).toBe('John Doe'); + expect(parts[0].data.text).toBe('This is a comment'); + } + }); + }); + + describe('buildCommentEndToken', () => { + it('should build comment end token', () => { + const token = buildCommentEndToken('cmt-1'); + + expect(token).toBe('[[DOCX_CMT_END:cmt-1]]'); + + const parts = splitDocxTrackingTokens(token); + expect(parts).toHaveLength(1); + expect(parts[0]).toEqual({ type: 'commentEnd', id: 'cmt-1' }); + }); + }); +}); + +describe('DOCX Export with Tracked Changes', () => { + it('should export insertion tokens as w:ins elements', async () => { + const startPayload = encodeURIComponent( + JSON.stringify({ id: 'ins-1', author: 'John Doe', date: '2024-01-01' }) + ); + const html = `<p>Normal text [[DOCX_INS_START:${startPayload}]]inserted text[[DOCX_INS_END:ins-1]] more text</p>`; + + const result = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(result); + const docXml = await zip.file('word/document.xml')!.async('string'); + + // Should contain w:ins element + expect(docXml).toContain('<w:ins'); + expect(docXml).toContain('w:author="John Doe"'); + expect(docXml).toContain('inserted text'); + }); + + it('should export deletion tokens as w:del elements with w:delText', async () => { + const startPayload = encodeURIComponent( + JSON.stringify({ id: 'del-1', author: 'Jane Smith', date: '2024-01-02' }) + ); + const html = `<p>Normal text [[DOCX_DEL_START:${startPayload}]]deleted text[[DOCX_DEL_END:del-1]] more text</p>`; + + const result = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(result); + const docXml = await zip.file('word/document.xml')!.async('string'); + + // Should contain w:del element + expect(docXml).toContain('<w:del'); + expect(docXml).toContain('w:author="Jane Smith"'); + // Deleted text should use w:delText element + expect(docXml).toContain('<w:delText'); + expect(docXml).toContain('deleted text'); + }); + + it('should export comment tokens with comment markers and comments.xml', async () => { + const startPayload = encodeURIComponent( + JSON.stringify({ + id: 'cmt-1', + authorName: 'Bob Wilson', + authorInitials: 'BW', + date: '2024-01-03T10:00:00Z', + text: 'This needs review', + }) + ); + const html = `<p>Normal text [[DOCX_CMT_START:${startPayload}]]commented text[[DOCX_CMT_END:cmt-1]] more text</p>`; + + const result = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(result); + const docXml = await zip.file('word/document.xml')!.async('string'); + + // Document should contain comment markers + expect(docXml).toContain('<w:commentRangeStart'); + expect(docXml).toContain('<w:commentRangeEnd'); + expect(docXml).toContain('<w:commentReference'); + + // Should have comments.xml file + const commentsFile = zip.file('word/comments.xml'); + expect(commentsFile).not.toBeNull(); + + if (commentsFile) { + const commentsXml = await commentsFile.async('string'); + // Check for comment element (with or without namespace prefix) + expect( + commentsXml.includes('<w:comment') || commentsXml.includes('<comment') + ).toBe(true); + // Check for author attribute (with or without namespace prefix) + expect( + commentsXml.includes('w:author="Bob Wilson"') || + commentsXml.includes('author="Bob Wilson"') || + commentsXml.includes(':author="Bob Wilson"') + ).toBe(true); + // Check for initials attribute + expect( + commentsXml.includes('w:initials="BW"') || + commentsXml.includes('initials="BW"') || + commentsXml.includes(':initials="BW"') + ).toBe(true); + expect(commentsXml).toContain('This needs review'); + } + + // Content types should include comments + const contentTypes = await zip.file('[Content_Types].xml')!.async('string'); + expect(contentTypes).toContain('comments.xml'); + }); + + it('should handle multiple tracked changes in same paragraph', async () => { + const ins1 = encodeURIComponent(JSON.stringify({ id: 'ins-1' })); + const del1 = encodeURIComponent(JSON.stringify({ id: 'del-1' })); + const html = `<p>Start [[DOCX_INS_START:${ins1}]]inserted[[DOCX_INS_END:ins-1]] middle [[DOCX_DEL_START:${del1}]]deleted[[DOCX_DEL_END:del-1]] end</p>`; + + const result = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(result); + const docXml = await zip.file('word/document.xml')!.async('string'); + + expect(docXml).toContain('<w:ins'); + expect(docXml).toContain('<w:del'); + expect(docXml).toContain('inserted'); + expect(docXml).toContain('deleted'); + }); + + it('should not create comments.xml when no comments exist', async () => { + const html = '<p>Plain text without comments</p>'; + + const result = await htmlToDocxBlob(html); + const zip = await loadZipFromBlob(result); + + const commentsFile = zip.file('word/comments.xml'); + expect(commentsFile).toBeNull(); + }); + + it('warns when dead tracking tokens remain in document.xml', async () => { + const warn = mock((..._args: unknown[]) => {}); + const originalWarn = console.warn; + console.warn = (...args: any[]) => + (warn as unknown as (...callArgs: any[]) => void)(...args); + + try { + const html = '<p>[[DOCX_INS_START:invalid]]text</p>'; + await htmlToDocxBlob(html); + } finally { + console.warn = originalWarn; + } + + expect(warn).toHaveBeenCalled(); + const firstCall = warn.mock.calls[0]; + expect(firstCall?.[0]).toContain('dead tracking tokens in document.xml'); + }); +}); + +describe('Round-trip Token Encoding', () => { + it('should correctly encode and decode special characters in author names', () => { + const payload = { + id: 'test-1', + author: 'José García & Maria <test>', + }; + const token = buildSuggestionStartToken(payload, 'insert'); + const parts = splitDocxTrackingTokens(token); + + expect(parts).toHaveLength(1); + expect(parts[0].type).toBe('insStart'); + if (parts[0].type === 'insStart') { + expect(parts[0].data.author).toBe('José García & Maria <test>'); + } + }); + + it('should correctly encode and decode Unicode in comment text', () => { + const payload = { + id: 'cmt-1', + authorName: '田中太郎', + text: 'Comment with emoji 🎉 and CJK 日本語', + }; + const token = buildCommentStartToken(payload); + const parts = splitDocxTrackingTokens(token); + + expect(parts).toHaveLength(1); + expect(parts[0].type).toBe('commentStart'); + if (parts[0].type === 'commentStart') { + expect(parts[0].data.authorName).toBe('田中太郎'); + expect(parts[0].data.text).toBe('Comment with emoji 🎉 and CJK 日本語'); + } + }); +}); diff --git a/packages/docx-io/src/lib/html-to-docx/tracking.ts b/packages/docx-io/src/lib/html-to-docx/tracking.ts new file mode 100644 index 0000000000..d9d4684219 --- /dev/null +++ b/packages/docx-io/src/lib/html-to-docx/tracking.ts @@ -0,0 +1,423 @@ +/** + * DOCX Tracked Changes and Comments Export Support + * + * This module provides token-based tracking for exporting Plate editor + * suggestions and comments to Word's tracked changes and comments format. + * + * Token Format: + * - Insertions: [[DOCX_INS_START:{payload}]] ... [[DOCX_INS_END:id]] + * - Deletions: [[DOCX_DEL_START:{payload}]] ... [[DOCX_DEL_END:id]] + * - Comments: [[DOCX_CMT_START:{payload}]] ... [[DOCX_CMT_END:id]] + */ +/** biome-ignore-all lint/style/useConsistentTypeDefinitions: legacy code */ + +import { fragment } from 'xmlbuilder2'; +import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces'; + +import namespaces from './namespaces'; + +// ============================================================================ +// Types +// ============================================================================ + +/** Payload for insertion/deletion tokens */ +export interface SuggestionPayload { + id: string; + author?: string; + date?: string; +} + +/** Payload for a single comment reply */ +export interface CommentReply { + id: string; + authorName?: string; + authorInitials?: string; + date?: string; + /** OOXML paraId for round-trip threading fidelity */ + paraId?: string; + text?: string; +} + +/** Payload for comment tokens */ +export interface CommentPayload { + id: string; + authorName?: string; + authorInitials?: string; + date?: string; + /** OOXML paraId for round-trip threading fidelity */ + paraId?: string; + /** OOXML parentParaId for round-trip threading fidelity */ + parentParaId?: string; + text?: string; + replies?: CommentReply[]; +} + +/** Parsed token from text */ +export type ParsedToken = + | { type: 'text'; value: string } + | { type: 'insStart'; data: SuggestionPayload } + | { type: 'insEnd'; id: string } + | { type: 'delStart'; data: SuggestionPayload } + | { type: 'delEnd'; id: string } + | { type: 'commentStart'; data: CommentPayload } + | { type: 'commentEnd'; id: string }; + +/** Active suggestion state for nesting */ +export interface ActiveSuggestion { + id: string; + type: 'insert' | 'remove'; + author?: string; + date?: string; + revisionId: number; +} + +/** Comment stored in the document */ +export interface StoredComment { + id: number; + authorName: string; + authorInitials: string; + date?: string; + text: string; + /** 8-char uppercase hex ID < 0x7FFFFFFF, links comments.xml <-> commentsExtended.xml <-> commentsIds.xml */ + paraId: string; + /** 8-char uppercase hex ID < 0x7FFFFFFF, links commentsIds.xml <-> commentsExtensible.xml */ + durableId: string; + /** paraId of parent comment; present only on replies */ + parentParaId?: string; +} + +/** Tracking state maintained during document generation */ +export interface TrackingState { + suggestionStack: ActiveSuggestion[]; + replyIdsByParent: Map<string, string[]>; +} + +/** Interface for document instance with tracking support */ +export interface TrackingDocumentInstance { + _trackingState?: TrackingState; + comments: StoredComment[]; + commentIdMap: Map<string, number>; + lastCommentId: number; + revisionIdMap: Map<string, number>; + lastRevisionId: number; + ensureComment: ( + data: Partial<CommentPayload>, + parentParaId?: string + ) => number; + getCommentId: (id: string) => number; + getRevisionId: (id?: string) => number; +} + +// ============================================================================ +// Hex ID Generation (OOXML spec: 8-char uppercase hex < 0x7FFFFFFF) +// ============================================================================ + +/** Document-wide set of allocated hex IDs to ensure uniqueness (per R12). */ +export const allocatedIds = new Set<string>(); + +/** Reset allocated IDs between documents. */ +export function resetAllocatedIds(): void { + allocatedIds.clear(); +} + +/** Generate a unique 8-char uppercase hex ID < 0x7FFFFFFF per OOXML spec. */ +export function generateHexId(): string { + let id: string; + + do { + const val = Math.floor(Math.random() * 0x7f_ff_ff_fe) + 1; + id = val.toString(16).toUpperCase().padStart(8, '0'); + } while (allocatedIds.has(id)); + + allocatedIds.add(id); + + return id; +} + +// ============================================================================ +// Token Constants +// ============================================================================ + +export const DOCX_INSERTION_START_TOKEN_PREFIX = '[[DOCX_INS_START:'; +export const DOCX_INSERTION_END_TOKEN_PREFIX = '[[DOCX_INS_END:'; +export const DOCX_INSERTION_TOKEN_SUFFIX = ']]'; + +export const DOCX_DELETION_START_TOKEN_PREFIX = '[[DOCX_DEL_START:'; +export const DOCX_DELETION_END_TOKEN_PREFIX = '[[DOCX_DEL_END:'; +export const DOCX_DELETION_TOKEN_SUFFIX = ']]'; + +export const DOCX_COMMENT_START_TOKEN_PREFIX = '[[DOCX_CMT_START:'; +export const DOCX_COMMENT_END_TOKEN_PREFIX = '[[DOCX_CMT_END:'; +export const DOCX_COMMENT_TOKEN_SUFFIX = ']]'; + +/** Regex to match all DOCX tracking tokens */ +const DOCX_TOKEN_REGEX = /\[\[DOCX_(INS|DEL|CMT)_(START|END):(.+?)\]\]/g; + +// ============================================================================ +// Token Parsing +// ============================================================================ + +/** + * Parse a single DOCX token into a structured object. + */ +function parseDocxToken( + kind: string, + position: string, + rawPayload: string +): ParsedToken | null { + try { + const decoded = decodeURIComponent(rawPayload); + + if (position === 'END') { + if (!decoded) return null; + + if (kind === 'CMT') { + return { type: 'commentEnd', id: decoded }; + } + if (kind === 'DEL') { + return { type: 'delEnd', id: decoded }; + } + return { type: 'insEnd', id: decoded }; + } + + const data = JSON.parse(decoded); + + if (kind === 'CMT') { + return { type: 'commentStart', data: data as CommentPayload }; + } + if (kind === 'DEL') { + return { type: 'delStart', data: data as SuggestionPayload }; + } + return { type: 'insStart', data: data as SuggestionPayload }; + } catch { + return null; + } +} + +/** + * Split text into an array of text segments and parsed tokens. + */ +export function splitDocxTrackingTokens(text: string): ParsedToken[] { + const parts: ParsedToken[] = []; + let lastIndex = 0; + const tokenRegex = new RegExp(DOCX_TOKEN_REGEX); + // biome-ignore lint/suspicious/noEvolvingTypes: regex exec result type + // biome-ignore lint/suspicious/noImplicitAnyLet: regex exec result type + let match; + + // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex loop + while ((match = tokenRegex.exec(text)) !== null) { + // Add text before this token + if (match.index > lastIndex) { + parts.push({ type: 'text', value: text.slice(lastIndex, match.index) }); + } + + // Parse the token + const token = parseDocxToken(match[1], match[2], match[3]); + if (token) { + parts.push(token); + } else { + // If parsing fails, treat as text + parts.push({ type: 'text', value: match[0] }); + } + + lastIndex = match.index + match[0].length; + } + + // Add remaining text + if (lastIndex < text.length) { + parts.push({ type: 'text', value: text.slice(lastIndex) }); + } + + return parts; +} + +/** + * Check if text contains any DOCX tracking tokens. + */ +export function hasTrackingTokens(text: string): boolean { + // Create a new regex each time to avoid state issues with global flag + // biome-ignore lint/performance/useTopLevelRegex: avoid global flag state issues + const tokenRegex = /\[\[DOCX_(INS|DEL|CMT)_(START|END):(.+?)\]\]/; + return tokenRegex.test(text); +} + +/** + * Collect all tracking token strings from text. + */ +export function findDocxTrackingTokens(text: string): string[] { + const tokens: string[] = []; + const tokenRegex = new RegExp(DOCX_TOKEN_REGEX); + // biome-ignore lint/suspicious/noEvolvingTypes: regex exec result type + // biome-ignore lint/suspicious/noImplicitAnyLet: regex exec result type + let match; + + // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic regex loop + while ((match = tokenRegex.exec(text)) !== null) { + tokens.push(match[0]); + } + + return tokens; +} + +// ============================================================================ +// Tracking State Management +// ============================================================================ + +/** + * Ensure tracking state is initialized on the document instance. + */ +export function ensureTrackingState( + docxDocumentInstance: TrackingDocumentInstance +): TrackingState { + if (!docxDocumentInstance._trackingState) { + docxDocumentInstance._trackingState = { + suggestionStack: [], + replyIdsByParent: new Map(), + }; + } + return docxDocumentInstance._trackingState; +} + +// ============================================================================ +// XML Fragment Builders +// ============================================================================ + +/** + * Build a text element for normal text. + */ +export function buildTextElement(text: string): XMLBuilder { + return fragment({ namespaceAlias: { w: namespaces.w } }) + .ele('@w', 't') + .att('@xml', 'space', 'preserve') + .txt(text) + .up(); +} + +/** + * Build a deleted text element (w:delText) for deletions. + */ +export function buildDeletedTextElement(text: string): XMLBuilder { + return fragment({ namespaceAlias: { w: namespaces.w } }) + .ele('@w', 'delText') + .att('@xml', 'space', 'preserve') + .txt(text) + .up(); +} + +/** + * Build a comment range start marker. + */ +export function buildCommentRangeStart(id: number): XMLBuilder { + return fragment({ namespaceAlias: { w: namespaces.w } }) + .ele('@w', 'commentRangeStart') + .att('@w', 'id', String(id)) + .up(); +} + +/** + * Build a comment range end marker. + */ +export function buildCommentRangeEnd(id: number): XMLBuilder { + return fragment({ namespaceAlias: { w: namespaces.w } }) + .ele('@w', 'commentRangeEnd') + .att('@w', 'id', String(id)) + .up(); +} + +/** + * Build a comment reference run (appears after commentRangeEnd). + */ +export function buildCommentReferenceRun(id: number): XMLBuilder { + return fragment({ namespaceAlias: { w: namespaces.w } }) + .ele('@w', 'r') + .ele('@w', 'commentReference') + .att('@w', 'id', String(id)) + .up() + .up(); +} + +/** + * Wrap a run fragment with a suggestion (w:ins or w:del). + */ +export function wrapRunWithSuggestion( + runFragment: XMLBuilder, + suggestion: ActiveSuggestion +): XMLBuilder { + const tagName = suggestion.type === 'remove' ? 'del' : 'ins'; + const wrapper = fragment({ namespaceAlias: { w: namespaces.w } }).ele( + '@w', + tagName + ); + + wrapper.att('@w', 'id', String(suggestion.revisionId)); + if (suggestion.author) { + wrapper.att('@w', 'author', suggestion.author); + } + if (suggestion.date) { + wrapper.att('@w', 'date', suggestion.date); + } + + wrapper.import(runFragment); + wrapper.up(); + + return wrapper; +} + +// ============================================================================ +// Token Building (for export from Plate) +// ============================================================================ + +/** + * Build a suggestion start token string. + */ +export function buildSuggestionStartToken( + payload: SuggestionPayload, + type: 'insert' | 'remove' +): string { + const encoded = encodeURIComponent(JSON.stringify(payload)); + const prefix = + type === 'remove' + ? DOCX_DELETION_START_TOKEN_PREFIX + : DOCX_INSERTION_START_TOKEN_PREFIX; + const suffix = + type === 'remove' + ? DOCX_DELETION_TOKEN_SUFFIX + : DOCX_INSERTION_TOKEN_SUFFIX; + return `${prefix}${encoded}${suffix}`; +} + +/** + * Build a suggestion end token string. + */ +export function buildSuggestionEndToken( + id: string, + type: 'insert' | 'remove' +): string { + const encodedId = encodeURIComponent(id); + const prefix = + type === 'remove' + ? DOCX_DELETION_END_TOKEN_PREFIX + : DOCX_INSERTION_END_TOKEN_PREFIX; + const suffix = + type === 'remove' + ? DOCX_DELETION_TOKEN_SUFFIX + : DOCX_INSERTION_TOKEN_SUFFIX; + return `${prefix}${encodedId}${suffix}`; +} + +/** + * Build a comment start token string. + */ +export function buildCommentStartToken(payload: CommentPayload): string { + const encoded = encodeURIComponent(JSON.stringify(payload)); + return `${DOCX_COMMENT_START_TOKEN_PREFIX}${encoded}${DOCX_COMMENT_TOKEN_SUFFIX}`; +} + +/** + * Build a comment end token string. + */ +export function buildCommentEndToken(id: string): string { + const encodedId = encodeURIComponent(id); + return `${DOCX_COMMENT_END_TOKEN_PREFIX}${encodedId}${DOCX_COMMENT_TOKEN_SUFFIX}`; +} diff --git a/packages/docx-io/src/lib/html-to-docx/utils/index.ts b/packages/docx-io/src/lib/html-to-docx/utils/index.ts new file mode 100644 index 0000000000..80e0e65aa2 --- /dev/null +++ b/packages/docx-io/src/lib/html-to-docx/utils/index.ts @@ -0,0 +1,12 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './color-conversion'; +export * from './font-family-conversion'; +export * from './image-dimensions'; +export * from './image-to-base64'; +export * from './list'; +export * from './unit-conversion'; +export * from './url'; +export * from './vnode'; diff --git a/packages/docx-io/src/lib/importComments.ts b/packages/docx-io/src/lib/importComments.ts new file mode 100644 index 0000000000..6254cff514 --- /dev/null +++ b/packages/docx-io/src/lib/importComments.ts @@ -0,0 +1,1278 @@ +/** + * DOCX Comments Import + * + * This module provides utilities for parsing and applying comments from + * DOCX files to a Plate editor. + * + * Two modes are supported: + * 1. API mode: Uses external API to create discussions (applyTrackedComments) + * 2. Local mode: Returns discussion data for local storage (applyTrackedCommentsLocal) + * + * Usage flow: + * 1. Convert DOCX to HTML with mammoth (with tracking token support) + * 2. Parse tokens from HTML using parseDocxComments + * 3. Deserialize HTML to editor nodes + * 4. Apply comments using applyTrackedComments or applyTrackedCommentsLocal + */ + +import { + formatAuthorAsUserId, + isPointAfter, + parseDateToDate, + type SearchRangeFn, + type TPoint, + type TrackingEditor, +} from './importTrackChanges'; +import type { TRange } from './searchRange'; + +import { + DOCX_COMMENT_END_TOKEN_PREFIX, + DOCX_COMMENT_START_TOKEN_PREFIX, + DOCX_COMMENT_TOKEN_SUFFIX, +} from './html-to-docx/tracking'; + +// Re-export token constants for test usage +export { + DOCX_COMMENT_END_TOKEN_PREFIX, + DOCX_COMMENT_START_TOKEN_PREFIX, + DOCX_COMMENT_TOKEN_SUFFIX, +} from './html-to-docx/tracking'; + +// Re-export shared types +export type { + SearchRangeFn, + TPoint, + TrackingEditor, +} from './importTrackChanges'; +export type { TRange } from './searchRange'; + +// ============================================================================ +// Types +// ============================================================================ + +/** Comment data structure matching the JSON payload */ +export type DocxCommentData = { + /** Unique ID for this comment */ + id: string; + /** Author display name */ + authorName?: string; + /** Author initials (for Word compatibility) */ + authorInitials?: string; + /** Date when the comment was made (ISO string) */ + date?: string; + /** Comment text content */ + text?: string; + /** Comment rich text body */ + body?: unknown; + /** Threading ID */ + paraId?: string; + /** Parent Threading ID */ + parentParaId?: string; + /** Is this a point comment? */ + isPoint?: boolean; + /** Nested replies */ + replies?: DocxCommentData[]; +}; + +/** Comment parsed from HTML with token metadata */ +export type DocxImportCommentReply = Omit<DocxCommentData, 'replies'> & { + /** The full start token string (for searching in editor) */ + startToken: string; + /** The full end token string (for searching in editor) */ + endToken: string; + /** The full point token string (comment reference start+end) */ + pointToken?: string; + /** Whether the start token was found in HTML */ + hasStartToken: boolean; + /** Whether the end token was found in HTML */ + hasEndToken: boolean; + /** Whether a point token was found in HTML */ + hasPointToken?: boolean; + /** Nested replies */ + replies?: DocxImportCommentReply[]; +}; + +/** Comment parsed from HTML with token metadata */ +export type DocxImportComment = Omit<DocxCommentData, 'replies'> & { + /** The full start token string (for searching in editor) */ + startToken: string; + /** The full end token string (for searching in editor) */ + endToken: string; + /** The full point token string (comment reference start+end) */ + pointToken?: string; + /** Whether the start token was found in HTML */ + hasStartToken: boolean; + /** Whether the end token was found in HTML */ + hasEndToken: boolean; + /** Whether a point token was found in HTML */ + hasPointToken?: boolean; + /** Nested replies */ + replies?: DocxImportCommentReply[]; +}; + +/** Result of parsing comments from HTML */ +export type ParseCommentsResult = { + /** All comments found */ + comments: DocxImportComment[]; + /** Number of comments found */ + count: number; +}; + +/** Discussion data created from DOCX comment (for local storage/import) */ +export type DocxImportDiscussion = { + /** Unique discussion ID */ + id: string; + /** Comments in this discussion */ + comments?: Array<{ + /** Unique ID for the comment */ + id?: string; + /** Rich content of the comment */ + contentRich?: unknown; + /** When the comment was created */ + createdAt?: Date; + /** OOXML paraId for round-trip threading fidelity */ + paraId?: string; + /** OOXML parentParaId for round-trip reply threading */ + parentParaId?: string; + /** User ID of the commenter */ + userId?: string; + /** Optional user object for direct author info */ + user?: { id: string; name: string }; + }>; + /** When the discussion was created */ + createdAt?: Date; + /** The document text that was commented on */ + documentContent?: string; + /** OOXML paraId of the root comment for round-trip threading fidelity */ + paraId?: string; + /** User ID who created the discussion */ + userId?: string; + /** Optional user object for direct author info */ + user?: { id: string; name: string }; +}; + +// ============================================================================ +// Comment Content Normalization +// ============================================================================ + +const isPlateText = (node: any): node is { text: string } => + !!node && typeof node === 'object' && typeof node.text === 'string'; + +const isPlateElement = (node: any): node is { children: unknown[] } => + !!node && typeof node === 'object' && Array.isArray(node.children); + +const isPlateNode = (node: any): boolean => + isPlateText(node) || + (isPlateElement(node) && node.children.every((child) => isPlateNode(child))); + +const isPlateValue = (value: unknown): value is unknown[] => + Array.isArray(value) && value.every((node) => isPlateNode(node)); + +const extractCommentBodyText = (value: unknown): string => { + if (!value) return ''; + if (typeof value === 'string') return value; + if (Array.isArray(value)) { + return value.map(extractCommentBodyText).join(''); + } + if (typeof value === 'object') { + const record = value as Record<string, unknown>; + + if (typeof record.text === 'string') return record.text; + if (typeof record.value === 'string') return record.value; + if (Array.isArray(record.children)) { + return record.children.map(extractCommentBodyText).join(''); + } + } + return ''; +}; + +const normalizeCommentContent = ( + body: unknown, + text?: string +): unknown[] | undefined => { + if (isPlateValue(body)) return body as unknown[]; + + const bodyText = extractCommentBodyText(body); + const contentText = (bodyText || text || '').replace(/\s+$/g, ''); + + if (!contentText) return; + + return [{ type: 'p', children: [{ text: contentText }] }]; +}; + +// ============================================================================ +// Point/Range Comparison Utilities +// ============================================================================ + +/** + * Check if two points are equal. + */ +export function isPointEqual(a: TPoint, b: TPoint): boolean { + if (a.path.length !== b.path.length) return false; + for (let i = 0; i < a.path.length; i++) { + if (a.path[i] !== b.path[i]) return false; + } + return a.offset === b.offset; +} + +/** + * Check if two ranges are equal. + */ +export function isRangeEqual(a: TRange | null, b: TRange | null): boolean { + if (!a || !b) return a === b; + return isPointEqual(a.anchor, b.anchor) && isPointEqual(a.focus, b.focus); +} + +/** + * Check if two paths are equal. + */ +export function isPathEqual(a: number[], b: number[]): boolean { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; +} + +// ============================================================================ +// Parsing Functions +// ============================================================================ + +/** Escape special regex characters in a string */ +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * Parse comment tokens from HTML. + */ +export function parseDocxComments(html: string): ParseCommentsResult { + const commentsById = new Map<string, DocxImportComment>(); + + const upsertComment = ( + id: string, + patch: Partial<DocxImportComment> & { + hasStartToken?: boolean; + hasEndToken?: boolean; + hasPointToken?: boolean; + } + ) => { + const existing = commentsById.get(id); + if (!existing) { + commentsById.set(id, { + id, + authorName: patch.authorName, + authorInitials: patch.authorInitials, + date: patch.date, + text: patch.text, + body: patch.body, + paraId: patch.paraId, + parentParaId: patch.parentParaId, + isPoint: patch.isPoint, + replies: patch.replies as DocxImportCommentReply[], + startToken: patch.startToken ?? patch.endToken ?? '', + endToken: patch.endToken ?? '', + pointToken: patch.pointToken, + hasStartToken: Boolean(patch.hasStartToken), + hasEndToken: Boolean(patch.hasEndToken), + hasPointToken: Boolean(patch.hasPointToken), + }); + return; + } + + commentsById.set(id, { + ...existing, + authorName: patch.authorName ?? existing.authorName, + authorInitials: patch.authorInitials ?? existing.authorInitials, + date: patch.date ?? existing.date, + text: patch.text ?? existing.text, + body: patch.body ?? existing.body, + paraId: patch.paraId ?? existing.paraId, + parentParaId: patch.parentParaId ?? existing.parentParaId, + isPoint: patch.isPoint ?? existing.isPoint, + replies: (patch.replies as DocxImportCommentReply[]) ?? existing.replies, + startToken: existing.hasStartToken + ? existing.startToken + : (patch.startToken ?? existing.startToken), + endToken: patch.endToken ?? existing.endToken, + pointToken: patch.pointToken ?? existing.pointToken, + hasStartToken: existing.hasStartToken || Boolean(patch.hasStartToken), + hasEndToken: existing.hasEndToken || Boolean(patch.hasEndToken), + hasPointToken: existing.hasPointToken || Boolean(patch.hasPointToken), + }); + }; + + // Parse comment start tokens + const startPattern = new RegExp( + `${escapeRegExp(DOCX_COMMENT_START_TOKEN_PREFIX)}(.*?)${escapeRegExp(DOCX_COMMENT_TOKEN_SUFFIX)}`, + 'g' + ); + + for (const match of html.matchAll(startPattern)) { + const rawPayload = match[1]; + if (!rawPayload) continue; + + try { + const payload = JSON.parse( + decodeURIComponent(rawPayload) + ) as DocxCommentData; + if (!payload.id) continue; + + const startToken = `${DOCX_COMMENT_START_TOKEN_PREFIX}${rawPayload}${DOCX_COMMENT_TOKEN_SUFFIX}`; + const endToken = `${DOCX_COMMENT_END_TOKEN_PREFIX}${encodeURIComponent(payload.id)}${DOCX_COMMENT_TOKEN_SUFFIX}`; + const pointToken = payload.isPoint ? startToken + endToken : undefined; + + upsertComment(payload.id, { + ...(payload as Omit<DocxCommentData, 'replies'>), + replies: payload.replies as DocxImportCommentReply[] | undefined, + ...(payload.isPoint + ? { + pointToken, + hasPointToken: true, + } + : { + startToken, + endToken, + hasStartToken: true, + }), + }); + } catch { + // Skip malformed tokens + } + } + + // Parse comment end tokens + const endPattern = new RegExp( + `${escapeRegExp(DOCX_COMMENT_END_TOKEN_PREFIX)}(.*?)${escapeRegExp(DOCX_COMMENT_TOKEN_SUFFIX)}`, + 'g' + ); + + for (const match of html.matchAll(endPattern)) { + const rawPayload = match[1]; + if (!rawPayload) continue; + + try { + const id = decodeURIComponent(rawPayload); + if (!id) continue; + + const endToken = `${DOCX_COMMENT_END_TOKEN_PREFIX}${rawPayload}${DOCX_COMMENT_TOKEN_SUFFIX}`; + upsertComment(id, { + endToken, + startToken: endToken, + hasEndToken: true, + }); + } catch { + // Skip malformed tokens + } + } + + const comments = Array.from(commentsById.values()); + return { comments, count: comments.length }; +} + +// ============================================================================ +// Point Comment Range Expansion +// ============================================================================ + +type PointCommentMarkRangeOptions = { + preferBefore?: boolean; + skipCurrentNode?: boolean; + isText: (node: unknown) => boolean; +}; + +/** + * Get a non-empty range for a point comment. + * + * When a comment is at a single point (start === end), we need to expand + * it to cover at least one character so it can be marked. + */ +export function getPointCommentMarkRange( + editor: TrackingEditor, + point: TPoint, + options: PointCommentMarkRangeOptions | boolean, + ...legacyArgs: [boolean?, ((node: unknown) => boolean)?] +): TRange | null { + const resolvedOptions: PointCommentMarkRangeOptions = + typeof options === 'object' && options + ? options + : { + preferBefore: Boolean(options), + skipCurrentNode: legacyArgs[0] ?? false, + isText: legacyArgs[1] as (node: unknown) => boolean, + }; + const { + preferBefore = false, + skipCurrentNode = false, + isText, + } = resolvedOptions; + + if (!isText) return null; + + // Try current node first if not skipping + if (!skipCurrentNode && editor.api.nodes) { + const textEntries = Array.from( + editor.api.nodes<{ text?: string }>({ + at: [], + match: isText, + }) + ); + + const currentEntry = textEntries.find(([, path]) => + isPathEqual(path, point.path) + ); + + if (currentEntry) { + const [node] = currentEntry; + if (typeof node.text === 'string') { + const textLength = node.text.length; + const beforeRange: TRange | null = + point.offset > 0 + ? { + anchor: { offset: point.offset - 1, path: point.path }, + focus: { offset: point.offset, path: point.path }, + } + : null; + const afterRange: TRange | null = + point.offset < textLength + ? { + anchor: { offset: point.offset, path: point.path }, + focus: { offset: point.offset + 1, path: point.path }, + } + : null; + + if (preferBefore) return beforeRange ?? afterRange; + return afterRange ?? beforeRange; + } + } + } + + // Scan all text nodes if current node didn't work + if (!editor.api.nodes) return null; + + const textEntries = Array.from( + editor.api.nodes<{ text?: string }>({ + at: [], + match: isText, + }) + ); + + if (textEntries.length === 0) return null; + + const entryIndex = textEntries.findIndex(([, path]) => + isPathEqual(path, point.path) + ); + + const rangeFromEntry = ( + entry: (typeof textEntries)[number], + tail: boolean + ): TRange | null => { + const [entryNode, entryPath] = entry; + if (typeof entryNode.text !== 'string') return null; + if (!entryNode.text || entryNode.text.length === 0) return null; + + if (tail) { + const endOffset = entryNode.text.length; + return { + anchor: { offset: endOffset - 1, path: entryPath }, + focus: { offset: endOffset, path: entryPath }, + }; + } + + return { + anchor: { offset: 0, path: entryPath }, + focus: { offset: 1, path: entryPath }, + }; + }; + + const scanFromIndex = ( + start: number, + end: number, + step: number, + tail: boolean + ): TRange | null => { + for (let i = start; i !== end; i += step) { + const range = rangeFromEntry(textEntries[i]!, tail); + if (range) return range; + } + return null; + }; + + if (entryIndex !== -1) { + if (preferBefore) { + return ( + scanFromIndex(entryIndex - 1, -1, -1, true) ?? + scanFromIndex(entryIndex + 1, textEntries.length, 1, false) + ); + } + + return ( + scanFromIndex(entryIndex + 1, textEntries.length, 1, false) ?? + scanFromIndex(entryIndex - 1, -1, -1, true) + ); + } + + if (preferBefore) { + return scanFromIndex(textEntries.length - 1, -1, -1, true); + } + + return scanFromIndex(0, textEntries.length, 1, false); +} + +/** + * Resolve target ranges for a comment (mark range and content range). + */ +export function resolveCommentTargetRanges( + editor: TrackingEditor, + ranges: { + startRange: TRange; + endRange: TRange; + pointRange: TRange | null; + }, + options: { + hasStartMarker: boolean; + hasEndMarker: boolean; + isText: (node: unknown) => boolean; + } +): { markRange: TRange | null; contentRange: TRange } { + const { startRange, endRange, pointRange } = ranges; + const { hasStartMarker, hasEndMarker, isText } = options; + + const startTokenPoint = startRange.focus; + const endTokenPoint = endRange.anchor; + const shouldUsePointMarker = + !!pointRange && + (!hasStartMarker || + !hasEndMarker || + isPointEqual(startTokenPoint, endTokenPoint)); + + // Detect point comments + const isPointComment = + shouldUsePointMarker || + !hasStartMarker || + !hasEndMarker || + isPointEqual(startTokenPoint, endTokenPoint); + + // Check if both tokens exist but are collapsed (at same position) + const pointHasNoSpan = + hasStartMarker && + hasEndMarker && + isPointEqual(startTokenPoint, endTokenPoint); + + if (!isPointComment) { + // Normal range comment - just normalize direction + let anchor = startTokenPoint; + let focus = endTokenPoint; + + if (isPointAfter(anchor, focus)) { + [anchor, focus] = [focus, anchor]; + } + + const range = { anchor, focus }; + return { markRange: range, contentRange: range }; + } + + // Point comment - determine expansion direction based on which marker exists + // - Only end token → prefer expanding before (true) + // - Only start token → prefer expanding after (false) + // - Both collapsed → prefer before (true) + const preferBefore = !(hasStartMarker && !hasEndMarker); + + // Use the appropriate point for marking + const pointForMark = shouldUsePointMarker + ? pointRange!.focus + : hasStartMarker && !hasEndMarker + ? startTokenPoint + : endTokenPoint; + + // Expand point to a single-character range + const markRange = getPointCommentMarkRange(editor, pointForMark, { + preferBefore, + skipCurrentNode: pointHasNoSpan, + isText, + }); + + return { + markRange, + contentRange: markRange ?? { + anchor: pointForMark, + focus: pointForMark, + }, + }; +} + +// ============================================================================ +// Types for API-based Comment Application +// ============================================================================ + +/** Function to create a discussion with comment via API */ +export type CreateDiscussionFn = { + mutateAsync: (input: { + contentRich?: unknown; + documentContent: string; + documentId: string; + }) => Promise<{ id: string }>; +}; + +/** Options for applying tracked comments via API */ +export type ApplyCommentsOptions = { + editor: TrackingEditor; + comments: DocxImportComment[]; + searchRange: SearchRangeFn; + documentId: string; + createDiscussionWithComment: CreateDiscussionFn; + commentKey: string; + getCommentKey: (discussionId: string) => string; + getTransientCommentKey?: () => string; + isText: (node: unknown) => boolean; + commentPlugin?: unknown; + onCommentsCreated?: () => void; +}; + +/** Result of applying tracked comments via API */ +export type ApplyCommentsResult = { + created: number; + skipped: number; + errors: string[]; +}; + +/** Options for applying tracked comments locally (without API) */ +export type ApplyCommentsLocalOptions = { + editor: TrackingEditor; + comments: DocxImportComment[]; + searchRange: SearchRangeFn; + commentKey: string; + getCommentKey: (discussionId: string) => string; + isText: (node: unknown) => boolean; + generateId: () => string; + documentDate?: Date; +}; + +/** Result of applying tracked comments locally */ +export type ApplyCommentsLocalResult = { + applied: number; + errors: string[]; + discussions: DocxImportDiscussion[]; +}; + +// ============================================================================ +// Apply Tracked Comments (API Mode) +// ============================================================================ + +/** + * Apply tracked comments to the editor using an external API. + * + * This function handles point comments (where start and end are at the same + * location) by expanding them to cover at least one character using the + * getPointCommentMarkRange helper. + */ +export async function applyTrackedComments( + options: ApplyCommentsOptions +): Promise<ApplyCommentsResult> { + const { + editor, + comments, + searchRange, + documentId, + createDiscussionWithComment, + commentKey, + getCommentKey, + getTransientCommentKey, + isText, + commentPlugin, + onCommentsCreated, + } = options; + + let created = 0; + let skipped = 0; + const errors: string[] = []; + + for (const comment of comments) { + let startTokenRef: ReturnType<TrackingEditor['api']['rangeRef']> | null = + null; + let endTokenRef: ReturnType<TrackingEditor['api']['rangeRef']> | null = + null; + let pointTokenRef: ReturnType<TrackingEditor['api']['rangeRef']> | null = + null; + + try { + const startTokenRange = comment.hasStartToken + ? searchRange(editor, comment.startToken) + : null; + const endTokenRange = comment.hasEndToken + ? searchRange(editor, comment.endToken) + : null; + const pointTokenRange = + comment.hasPointToken && comment.pointToken + ? searchRange(editor, comment.pointToken) + : null; + + const hasStartMarker = Boolean(startTokenRange); + const hasEndMarker = Boolean(endTokenRange); + const hasPointMarker = Boolean(pointTokenRange); + + // Golden rule: if we have ANY location marker, preserve the comment + // - Has start, no end → end = start (point comment) + // - Has end, no start → start = end (point comment) + // - Has neither → skip (no location available) + if (!startTokenRange && !endTokenRange && !pointTokenRange) { + skipped++; + continue; + } + + // Use whichever marker we have, fallback to the other for point comments + const effectiveStartRange = + startTokenRange ?? endTokenRange ?? pointTokenRange; + const effectiveEndRange = + endTokenRange ?? startTokenRange ?? pointTokenRange; + + if (!effectiveStartRange || !effectiveEndRange) { + skipped++; + continue; + } + + startTokenRef = editor.api.rangeRef(effectiveStartRange); + endTokenRef = editor.api.rangeRef(effectiveEndRange); + pointTokenRef = + hasPointMarker && pointTokenRange + ? editor.api.rangeRef(pointTokenRange) + : null; + + if (!startTokenRef || !endTokenRef) { + skipped++; + continue; + } + + const currentStartRange = startTokenRef.current; + const currentEndRange = endTokenRef.current; + const currentPointRange = pointTokenRef?.current ?? null; + + if (!currentStartRange || !currentEndRange) { + skipped++; + continue; + } + + // Get content range for document text extraction + const { contentRange } = resolveCommentTargetRanges( + editor, + { + startRange: currentStartRange, + endRange: currentEndRange, + pointRange: currentPointRange, + }, + { + hasStartMarker, + hasEndMarker, + isText, + } + ); + + let documentContent = stripDocxTrackingTokens( + editor.api.string(contentRange) + ); + if (!documentContent || documentContent.trim().length === 0) { + documentContent = 'Imported comment'; + } + + const commentText = stripDocxTrackingTokens(comment.text ?? ''); + const contentRich = commentText + ? [{ children: [{ text: commentText }], type: 'p' }] + : undefined; + + const discussion = await createDiscussionWithComment.mutateAsync({ + contentRich, + documentContent, + documentId, + }); + + created++; + + editor.tf.withMerging(() => { + const currentStart = startTokenRef?.current; + const currentEnd = endTokenRef?.current; + const currentPoint = pointTokenRef?.current ?? null; + + if (currentStart && currentEnd) { + // Re-resolve ranges after potential mutations + const { markRange } = resolveCommentTargetRanges( + editor, + { + startRange: currentStart, + endRange: currentEnd, + pointRange: currentPoint, + }, + { + hasStartMarker, + hasEndMarker, + isText, + } + ); + + if (!markRange) { + // Could not resolve a valid mark range + return; + } + + const marks: Record<string, unknown> = { + [getCommentKey(discussion.id)]: true, + [commentKey]: true, + }; + + if (getTransientCommentKey) { + marks[getTransientCommentKey()] = true; + } + + editor.tf.setNodes(marks, { + at: markRange, + match: isText, + split: true, + }); + + if (commentPlugin && editor.setOption) { + editor.setOption(commentPlugin, 'updateTimestamp', Date.now()); + } + } + }); + + const pointRange = pointTokenRef?.current ?? null; + const startRange = startTokenRef.current; + const endRange = endTokenRef.current; + const skipEndDelete = Boolean(pointRange) && !hasStartMarker; + const skipStartDelete = Boolean(pointRange) && !hasEndMarker; + const sameStartEnd = + startRange && endRange ? isRangeEqual(startRange, endRange) : false; + + // Delete tokens (only delete tokens that actually existed) + // For point comments, both refs may point to the same range + if ( + hasPointMarker && + pointRange && + (!startRange || !isRangeEqual(pointRange, startRange)) && + (!endRange || !isRangeEqual(pointRange, endRange)) + ) { + editor.tf.delete({ at: pointTokenRef!.current! }); + } + if (!skipEndDelete && hasEndMarker && endRange) { + editor.tf.delete({ at: endTokenRef.current! }); + } + if (!skipStartDelete && hasStartMarker && startRange && !sameStartEnd) { + editor.tf.delete({ at: startTokenRef.current! }); + } + } catch (error) { + errors.push( + `Failed to apply comment ${comment.id}: ${error instanceof Error ? error.message : String(error)}` + ); + } finally { + pointTokenRef?.unref(); + if (endTokenRef && (!startTokenRef || endTokenRef !== startTokenRef)) { + endTokenRef.unref(); + } + startTokenRef?.unref(); + } + } + + if (created > 0 && onCommentsCreated) { + onCommentsCreated(); + } + + return { created, skipped, errors }; +} + +// ============================================================================ +// Apply Tracked Comments (Local Mode) +// ============================================================================ + +/** + * Apply tracked comments to the editor locally (without API calls). + */ +export function applyTrackedCommentsLocal( + options: ApplyCommentsLocalOptions +): ApplyCommentsLocalResult { + const { + editor, + comments, + searchRange, + commentKey, + getCommentKey, + isText, + generateId, + documentDate, + } = options; + + const errors: string[] = []; + let applied = 0; + const discussions: DocxImportDiscussion[] = []; + const processedCommentIds = new Set<string>(); + + // Process all comments; replies skip discussion creation but still remove tokens. + for (const comment of comments) { + const isReplyComment = Boolean(comment.parentParaId); + try { + const startTokenRange = comment.hasStartToken + ? searchRange(editor, comment.startToken) + : null; + const endTokenRange = comment.hasEndToken + ? searchRange(editor, comment.endToken) + : null; + const pointTokenRange = + comment.hasPointToken && comment.pointToken + ? searchRange(editor, comment.pointToken) + : null; + + const hasStartMarker = Boolean(startTokenRange); + const hasEndMarker = Boolean(endTokenRange); + const hasPointMarker = Boolean(pointTokenRange); + + if (!startTokenRange && !endTokenRange && !pointTokenRange) { + errors.push(`Comment ${comment.id}: no location markers found`); + continue; + } + + const isSameTokenString = comment.startToken === comment.endToken; + const isSameRange = isRangeEqual(startTokenRange, endTokenRange); + + const effectiveStartRange = + startTokenRange ?? endTokenRange ?? pointTokenRange; + const effectiveEndRange = + endTokenRange ?? startTokenRange ?? pointTokenRange; + + if (!effectiveStartRange || !effectiveEndRange) { + errors.push(`Comment ${comment.id}: invalid ranges`); + continue; + } + + const startTokenRef = editor.api.rangeRef(effectiveStartRange); + const endTokenRef = + isSameRange || isSameTokenString + ? startTokenRef + : editor.api.rangeRef(effectiveEndRange); + const pointTokenRef = + hasPointMarker && pointTokenRange + ? editor.api.rangeRef(pointTokenRange) + : null; + + const currentStartRange = startTokenRef.current; + const currentEndRange = endTokenRef.current; + + if (!currentStartRange || !currentEndRange) { + startTokenRef.unref(); + if (!isSameRange && !isSameTokenString) { + endTokenRef.unref(); + } + if (pointTokenRef) { + pointTokenRef.unref(); + } + errors.push(`Comment ${comment.id}: ranges became invalid`); + continue; + } + + let discussion: DocxImportDiscussion | null = null; + + if (!isReplyComment && !processedCommentIds.has(comment.id)) { + const discussionId = generateId(); + + const discussionComments: NonNullable< + DocxImportDiscussion['comments'] + > = []; + + const addCommentRecursive = ( + c: Pick< + DocxCommentData, + | 'authorName' + | 'body' + | 'date' + | 'text' + | 'id' + | 'paraId' + | 'parentParaId' + > & { + replies?: Pick< + DocxCommentData, + | 'authorName' + | 'body' + | 'date' + | 'text' + | 'id' + | 'paraId' + | 'parentParaId' + >[]; + } + ) => { + processedCommentIds.add(c.id); + + const userId = formatAuthorAsUserId(c.authorName); + const createdAt = parseDateToDate(c.date, documentDate); + + const contentRich = normalizeCommentContent(c.body, c.text); + + discussionComments.push({ + contentRich, + createdAt, + id: c.id, + paraId: c.paraId, + parentParaId: c.parentParaId ?? undefined, + userId, + user: c.authorName ? { id: userId, name: c.authorName } : undefined, + }); + + if (c.replies) { + c.replies.forEach(addCommentRecursive); + } + }; + + addCommentRecursive(comment); + + // Resolve range early to get document content + const { contentRange } = resolveCommentTargetRanges( + editor, + { + startRange: currentStartRange, + endRange: currentEndRange, + pointRange: pointTokenRef?.current ?? null, + }, + { + hasStartMarker, + hasEndMarker, + isText, + } + ); + + let documentContent = stripDocxTrackingTokens( + editor.api.string(contentRange) + ); + if (!documentContent || documentContent.trim().length === 0) { + documentContent = stripDocxTrackingTokens(comment.text ?? ''); + } + + discussion = { + id: discussionId, + comments: discussionComments, + createdAt: discussionComments[0]?.createdAt, + documentContent, + paraId: comment.paraId, + userId: discussionComments[0]?.userId, + user: discussionComments[0]?.user, + }; + + editor.tf.withMerging(() => { + const currentStart = startTokenRef.current; + const currentEnd = endTokenRef.current; + const currentPoint = pointTokenRef?.current ?? null; + + if (currentStart && currentEnd) { + // Re-resolve range inside transaction + const { markRange } = resolveCommentTargetRanges( + editor, + { + startRange: currentStart, + endRange: currentEnd, + pointRange: currentPoint, + }, + { + hasStartMarker, + hasEndMarker: hasEndMarker && !isSameTokenString, + isText, + } + ); + + if (markRange) { + editor.tf.setNodes( + { + [getCommentKey(discussionId)]: true, + [commentKey]: true, + }, + { at: markRange, match: isText, split: true } + ); + } else { + errors.push(`Comment ${comment.id}: could not key range`); + } + } + }); + } + + const pointRange = pointTokenRef?.current ?? null; + const startRange = startTokenRef.current; + const endRange = endTokenRef.current; + const skipEndDelete = Boolean(pointRange) && !hasStartMarker; + const skipStartDelete = Boolean(pointRange) && !hasEndMarker; + const sameStartEnd = isRangeEqual(startRange, endRange); + + if ( + hasPointMarker && + pointRange && + !isRangeEqual(pointRange, startRange) && + !isRangeEqual(pointRange, endRange) + ) { + editor.tf.delete({ at: pointTokenRef!.current! }); + } + if (!skipEndDelete && hasEndMarker && endRange) { + editor.tf.delete({ at: endTokenRef.current! }); + } + if ( + !skipStartDelete && + hasStartMarker && + startRange && + !isSameTokenString && + !sameStartEnd + ) { + editor.tf.delete({ at: startTokenRef.current! }); + } + + pointTokenRef?.unref(); + if (endTokenRef !== startTokenRef) { + endTokenRef.unref(); + } + startTokenRef.unref(); + + if (discussion) { + discussions.push(discussion); + applied++; + } + } catch (error) { + errors.push( + `Comment ${comment.id}: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + + return { applied, errors, discussions }; +} + +// ============================================================================ +// Combined Parsing & Utility Functions +// ============================================================================ + +import { + applyTrackedChangeSuggestions, + hasDocxTrackingTokens as hasTrackChangeTokens, + parseDocxTrackedChanges, + type ApplySuggestionsOptions, + type ApplySuggestionsResult, + type ParseTrackedChangesResult, +} from './importTrackChanges'; +import type { DocxTrackedChange } from './types'; + +/** Result of parsing all tracking information from HTML */ +export type ParseDocxTrackingResult = { + /** Tracked changes (insertions and deletions) */ + trackedChanges: ParseTrackedChangesResult; + /** Comments */ + comments: ParseCommentsResult; + /** Whether any tracking tokens were found */ + hasTracking: boolean; +}; + +/** + * Parse all DOCX tracking tokens from HTML. + * + * This is a convenience function that combines tracked changes and + * comment parsing in a single call. + * + * @param html - The HTML string containing tracking tokens + * @returns All parsed tracking information + */ +export function parseDocxTracking(html: string): ParseDocxTrackingResult { + const trackedChanges = parseDocxTrackedChanges(html); + const comments = parseDocxComments(html); + + const hasTracking = + trackedChanges.changes.length > 0 || comments.comments.length > 0; + + return { trackedChanges, comments, hasTracking }; +} + +/** + * Check if HTML contains any DOCX tracking tokens (changes or comments). + */ +export function hasDocxTrackingTokens(html: string): boolean { + return ( + hasTrackChangeTokens(html) || html.includes(DOCX_COMMENT_START_TOKEN_PREFIX) + ); +} + +/** + * Remove all DOCX tracking tokens from HTML (changes and comments). + */ +export function stripDocxTrackingTokens(html: string): string { + const tokenPattern = /\[\[DOCX_(INS|DEL|CMT)_(START|END):[^\]]+\]\]/g; + return html.replace(tokenPattern, ''); +} + +// ============================================================================ +// Combined Application +// ============================================================================ + +/** Result of importing DOCX with full tracking support */ +export type ImportWithTrackingResult = { + /** Suggestions result */ + suggestions: ApplySuggestionsResult; + /** Comments result (may be null if not applied) */ + comments: ApplyCommentsResult | null; + /** Total tracked items applied */ + totalApplied: number; +}; + +/** Options for applying all tracking */ +export type ApplyAllTrackingOptions = { + /** The editor instance */ + editor: TrackingEditor; + /** Tracked changes to apply */ + trackedChanges: DocxTrackedChange[]; + /** Comments to apply (optional) */ + comments?: DocxImportComment[]; + /** Function to search for ranges in editor */ + searchRange: SearchRangeFn; + /** Config for applying suggestions */ + suggestionConfig: Omit< + ApplySuggestionsOptions, + 'editor' | 'changes' | 'searchRange' + >; + /** Config for applying comments (optional - if not provided, comments won't be applied) */ + commentConfig?: Omit< + ApplyCommentsOptions, + 'editor' | 'comments' | 'searchRange' + >; +}; + +/** + * Apply all tracked changes and comments from parsed DOCX. + * + * This is a convenience function that combines suggestion and comment + * application. Comments are optional and require API integration. + */ +export async function applyAllTracking( + options: ApplyAllTrackingOptions +): Promise<ImportWithTrackingResult> { + const { + editor, + trackedChanges, + comments, + searchRange, + suggestionConfig, + commentConfig, + } = options; + + // Apply suggestions + const suggestionsResult = applyTrackedChangeSuggestions({ + editor, + changes: trackedChanges, + searchRange, + ...suggestionConfig, + }); + + // Apply comments if config provided and there are comments + let commentsResult: ApplyCommentsResult | null = null; + if (commentConfig && comments && comments.length > 0) { + commentsResult = await applyTrackedComments({ + editor, + comments, + searchRange, + ...commentConfig, + }); + } + + return { + suggestions: suggestionsResult, + comments: commentsResult, + totalApplied: suggestionsResult.total + (commentsResult?.created ?? 0), + }; +} diff --git a/packages/docx-io/src/lib/importDocx.ts b/packages/docx-io/src/lib/importDocx.ts index 61085c6a70..a8ecc49bb2 100644 --- a/packages/docx-io/src/lib/importDocx.ts +++ b/packages/docx-io/src/lib/importDocx.ts @@ -1,91 +1,628 @@ -import { cleanDocx } from '@platejs/docx'; -import mammoth from 'mammoth'; -import type { SlateEditor, TNode } from 'platejs'; +/** + * DOCX Import Main Module + * + * This module provides the main entry point for importing DOCX files + * with tracked changes and comments support. + * + * Features: + * - Converts DOCX to HTML using a mammoth.js fork with tracking support + * - Emits tracking tokens for insertions, deletions, and comments + * - Preprocesses HTML to extract comment metadata + * + * Usage flow: + * 1. Call convertToHtmlWithTracking(arrayBuffer) + * 2. Parse tokens using parseDocxTrackedChanges/parseDocxComments + * 3. Deserialize HTML to editor nodes + * 4. Apply tracked changes and comments to editor + */ +// Local mammoth.js fork browser build +import mammothModule from './mammoth.js/mammoth.browser.js'; -import { - extractComments, - preprocessMammothHtml, -} from './preprocessMammothHtml'; -import type { ImportDocxOptions, ImportDocxResult } from './types'; +// ============================================================================ +// Mammoth Types and Export +// ============================================================================ + +/** Mammoth message type */ +export type MammothMessage = { + type: 'warning' | 'error'; + message: string; +}; + +/** Mammoth module type */ +type MammothModule = { + convertToHtml: ( + input: { arrayBuffer: ArrayBuffer }, + options?: { styleMap?: string[] } + ) => Promise<{ + value: string; + messages: MammothMessage[]; + }>; + MammothMessage: unknown; +}; + +/** Export mammoth for direct access if needed */ +export const mammoth = mammothModule as unknown as MammothModule; + +// ============================================================================ +// Preprocess Types +// ============================================================================ + +const DOCX_COMMENT_REF_TOKEN_PREFIX = '[[DOCX_COMMENT_REF:'; +const DOCX_COMMENT_REF_TOKEN_SUFFIX = ']]'; + +// Top-level regex patterns for performance +const COMMENT_ID_REGEX = /^comment-/; +const COMMENT_REF_ID_REGEX = /^comment-ref-/; +const ARROW_SUFFIX_REGEX = /↑\s*$/; + +export type PreprocessMammothHtmlResult = { + /** Processed HTML with comment tokens */ + html: string; + /** Map of comment ID to comment text */ + commentById: Map<string, string>; + /** Ordered list of comment IDs as they appear in document */ + commentIds: string[]; +}; + +// ============================================================================ +// Preprocess Functions +// ============================================================================ /** - * Parse HTML string to DOM element for deserialization. + * Preprocess mammoth HTML output to extract and tokenize comments. + * + * Mammoth converts DOCX comments to: + * - `<dl>` elements containing comment definitions + * - `<a id="comment-ref-{id}">` anchors marking comment locations + * + * This function: + * 1. Extracts comment text from `<dl>` elements + * 2. Removes comment anchors while tracking their IDs + * 3. Returns the processed HTML and comment data */ -function parseHtmlElement(html: string): HTMLElement | undefined { +export function preprocessMammothHtml( + html: string +): PreprocessMammothHtmlResult { + if (typeof DOMParser === 'undefined') { + throw new Error( + 'preprocessMammothHtml requires DOMParser (browser-like environment).' + ); + } + const doc = new DOMParser().parseFromString(html, 'text/html'); + const commentById = new Map<string, string>(); + + // Extract comments from <dl> elements + for (const dl of Array.from(doc.querySelectorAll('dl'))) { + if (!dl.querySelector('dt[id^="comment-"]')) continue; + + const dtNodes = Array.from(dl.querySelectorAll('dt[id^="comment-"]')); + + for (const dt of dtNodes) { + const dtId = dt.getAttribute('id') ?? ''; + const commentId = dtId.replace(COMMENT_ID_REGEX, ''); + + if (!commentId) continue; + + const dd = dt.nextElementSibling; + + if (!dd || dd.tagName !== 'DD') continue; + + const ddClone = dd.cloneNode(true) as HTMLElement; + + // Remove back-reference links + for (const a of Array.from( + ddClone.querySelectorAll('a[href^="#comment-ref-"]') + )) { + a.remove(); + } + + let text = (ddClone.textContent ?? '').replaceAll(/\s+/g, ' ').trim(); + text = text.replace(ARROW_SUFFIX_REGEX, '').trim(); + + commentById.set(commentId, text); + } + + dl.remove(); + } + + // Remove comment anchors but keep their IDs + const seen = new Set<string>(); + const commentIds: string[] = []; + + for (const a of Array.from(doc.querySelectorAll('a[id^="comment-ref-"]'))) { + const aId = a.getAttribute('id') ?? ''; + const commentId = aId.replace(COMMENT_REF_ID_REGEX, ''); + + if (!commentId) continue; - return doc.body ?? undefined; + if (!seen.has(commentId)) { + seen.add(commentId); + commentIds.push(commentId); + } + + const parent = a.parentElement; + + if (parent?.tagName === 'SUP' && parent.childNodes.length === 1) { + parent.remove(); + } else { + a.remove(); + } + } + + return { commentById, commentIds, html: doc.body.innerHTML }; +} + +/** Get the comment token prefix for searching in editor */ +export function getCommentTokenPrefix(): string { + return DOCX_COMMENT_REF_TOKEN_PREFIX; +} + +/** Get the comment token suffix for searching in editor */ +export function getCommentTokenSuffix(): string { + return DOCX_COMMENT_REF_TOKEN_SUFFIX; } +/** Build a comment token from ID */ +export function buildCommentToken(commentId: string): string { + return `${DOCX_COMMENT_REF_TOKEN_PREFIX}${commentId}${DOCX_COMMENT_REF_TOKEN_SUFFIX}`; +} + +// ============================================================================ +// Convert Types +// ============================================================================ + +/** Options for convertToHtmlWithTracking */ +export type ConvertToHtmlWithTrackingOptions = { + /** Mammoth style map for custom styling */ + styleMap?: string[]; +}; + +/** Result from convertToHtmlWithTracking */ +export type ConvertToHtmlWithTrackingResult = { + /** The converted HTML with tracking tokens embedded */ + value: string; + /** Messages from mammoth (warnings, etc.) */ + messages: MammothMessage[]; +}; + +// ============================================================================ +// Main Export Functions +// ============================================================================ + /** - * Import a DOCX file and convert it to Plate editor nodes. + * Convert DOCX to HTML with tracked changes support. + * + * This is the main entry point for importing DOCX files with tracked changes + * (insertions, deletions) and comments. The mammoth fork handles token + * emission natively during conversion. * - * @param editor - The Plate editor instance * @param arrayBuffer - The DOCX file as ArrayBuffer - * @param options - Import options - * @returns Import result with nodes, comments, and warnings + * @param options - Conversion options + * @returns HTML with embedded tracking tokens * * @example * ```ts - * const file = await picker.getFile(); - * const arrayBuffer = await file.arrayBuffer(); - * const result = await importDocx(editor, arrayBuffer); + * const result = await convertToHtmlWithTracking(arrayBuffer); * - * // Insert nodes into editor - * editor.tf.insertNodes(result.nodes); + * // Parse tokens from HTML + * const { changes } = parseDocxTrackedChanges(result.value); + * const { comments } = parseDocxComments(result.value); * - * // Handle comments separately - * for (const comment of result.comments) { - * // Create discussions via your backend - * } + * // Apply to editor + * applyTrackedChangeSuggestions({ editor, changes, ... }); + * applyTrackedCommentsLocal({ editor, comments, ... }); + * ``` + */ +export async function convertToHtmlWithTracking( + arrayBuffer: ArrayBuffer, + options: ConvertToHtmlWithTrackingOptions = {} +): Promise<ConvertToHtmlWithTrackingResult> { + // The mammoth fork natively emits tracking tokens during conversion + const result = await mammoth.convertToHtml( + { arrayBuffer }, + { styleMap: options.styleMap ?? ['comment-reference => sup'] } + ); + + return { + value: result.value, + messages: result.messages, + }; +} + +// ============================================================================ +// Simple Import Function +// ============================================================================ + +/** Editor interface for importDocx */ +type ImportDocxEditor = { + api: { + html: { + deserialize: (options: { element: Element }) => unknown[]; + }; + }; +}; + +/** + * Import a DOCX file and convert it to editor nodes. + * + * This is a convenience function that handles the entire import flow: + * 1. Converts DOCX to HTML using mammoth + * 2. Strips tracking tokens (use advanced APIs for tracked changes support) + * 3. Deserializes HTML to editor nodes + * + * For tracked changes and comments support, use `importDocxWithTracking` instead + * or use the advanced APIs: + * - convertToHtmlWithTracking + * - parseDocxTrackedChanges / parseDocxComments + * - applyTrackedChangeSuggestions / applyTrackedComments + * + * @param editor - The Plate editor instance + * @param arrayBuffer - The DOCX file as ArrayBuffer + * @param options - Import options + * @returns The deserialized nodes and metadata + * + * @example + * ```ts + * const { nodes } = await importDocx(editor, arrayBuffer); + * editor.tf.insertNodes(nodes); * ``` */ export async function importDocx( - editor: SlateEditor, + editor: ImportDocxEditor, arrayBuffer: ArrayBuffer, - options: ImportDocxOptions = {} -): Promise<ImportDocxResult> { - const { rtf = '' } = options; + options: ConvertToHtmlWithTrackingOptions = {} +): Promise<{ + /** The deserialized nodes ready to insert into editor */ + nodes: unknown[]; + /** The raw HTML from conversion */ + html: string; + /** Messages from mammoth (warnings, etc.) */ + messages: MammothMessage[]; +}> { + // Convert DOCX to HTML + const result = await convertToHtmlWithTracking(arrayBuffer, options); - // Convert DOCX to HTML using mammoth - const mammothResult = await mammoth.convertToHtml( - { arrayBuffer }, - { styleMap: ['comment-reference => sup'] } + // Strip tracking tokens for simple import (advanced users can use the APIs directly) + const cleanHtml = result.value.replaceAll( + /\[\[DOCX_(INS|DEL|CMT)_(START|END):[^\]]+\]\]/g, + '' ); - const mammothHtml = mammothResult.value; - const warnings = mammothResult.messages.map((msg) => msg.message); + // Parse HTML and deserialize to nodes + const parser = new DOMParser(); + const doc = parser.parseFromString(cleanHtml, 'text/html'); + const nodes = editor.api.html.deserialize({ element: doc.body }); + + return { + nodes, + html: cleanHtml, + messages: result.messages, + }; +} + +// ============================================================================ +// Import With Tracking Support +// ============================================================================ + +import { + applyTrackedCommentsLocal, + parseDocxComments, + type DocxImportDiscussion, +} from './importComments'; +import { + applyTrackedChangeSuggestions, + parseDocxTrackedChanges, + type ImportedUser, +} from './importTrackChanges'; +import { createSearchRangeFn } from './searchRange'; + +/** Extended editor interface for tracking imports */ +type ImportDocxWithTrackingEditor = ImportDocxEditor & { + api: ImportDocxEditor['api'] & { + string: (range: { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + }) => string; + rangeRef: (range: { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + }) => { + current: { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + } | null; + unref: () => { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + } | null; + }; + nodes?: <T>(options: { + at: number[]; + match?: (node: unknown) => boolean; + }) => Iterable<[T, number[]]>; + }; + tf: { + setNodes: ( + props: Record<string, unknown>, + options: { + at: { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + }; + match: (node: unknown) => boolean; + split: boolean; + } + ) => void; + delete: (options: { + at: { + anchor: { path: number[]; offset: number }; + focus: { path: number[]; offset: number }; + }; + }) => void; + withMerging: (fn: () => void) => void; + }; + children: unknown[]; +}; + +/** Options for importing DOCX with tracking support */ +export type ImportDocxWithTrackingOptions = ConvertToHtmlWithTrackingOptions & { + /** Key for suggestion marks (default: 'suggestion') */ + suggestionKey?: string; + /** Function to generate suggestion property key from ID */ + getSuggestionKey?: (id: string) => string; + /** Key for comment marks (default: 'comment') */ + commentKey?: string; + /** Function to generate comment property key from discussion ID */ + getCommentKey?: (discussionId: string) => string; + /** Function to check if a node is a text node */ + isText?: (node: unknown) => boolean; + /** Function to generate unique IDs for discussions */ + generateId?: () => string; +}; + +/** Result from importing DOCX with tracking */ +export type ImportDocxWithTrackingResult = { + /** Number of insertions applied */ + insertions: number; + /** Number of deletions applied */ + deletions: number; + /** Number of comments applied */ + comments: number; + /** Discussion data for UI (to be stored in discussion plugin) */ + discussions: DocxImportDiscussion[]; + /** Unique users found in imported content (register in user store for display) */ + users: ImportedUser[]; + /** Any errors encountered during import */ + errors: string[]; + /** Messages from mammoth (warnings, etc.) */ + messages: MammothMessage[]; + /** Whether any tracking was found and applied */ + hasTracking: boolean; +}; + +/** Default function to check if a node is text */ +function defaultIsText(node: unknown): boolean { + return typeof (node as { text?: unknown }).text === 'string'; +} + +/** Default ID generator using crypto.randomUUID or fallback */ +function defaultGenerateId(): string { + if (typeof crypto !== 'undefined' && crypto.randomUUID) { + return crypto.randomUUID(); + } + return `id-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; +} - // Preprocess to extract comments +/** + * Import a DOCX file with full tracked changes and comments support. + * + * This function handles the complete import flow including: + * 1. Converts DOCX to HTML with tracking tokens + * 2. Parses tracked changes (insertions/deletions) and comments + * 3. Deserializes HTML to editor nodes + * 4. Applies suggestion marks for tracked changes + * 5. Applies comment marks and creates discussion data + * + * After calling this function, the editor will display suggestions and comments + * with proper marks. The returned discussions should be added to your discussion + * plugin's state. + * + * @param editor - The Plate editor instance (must have required APIs) + * @param arrayBuffer - The DOCX file as ArrayBuffer + * @param options - Import options including key configurations + * @returns Result with counts, discussions, and any errors + * + * @example + * ```ts + * import { importDocxWithTracking } from '@platejs/docx-io'; + * import { getSuggestionKey } from '@platejs/suggestion'; + * import { getCommentKey } from '@platejs/comment'; + * + * const result = await importDocxWithTracking(editor, arrayBuffer, { + * suggestionKey: 'suggestion', + * getSuggestionKey, + * commentKey: 'comment', + * getCommentKey, + * }); + * + * // Add imported discussions to your discussion plugin + * if (result.discussions.length > 0) { + * editor.setOption(discussionPlugin, 'discussions', [ + * ...existingDiscussions, + * ...result.discussions, + * ]); + * } + * + * console.log(`Imported ${result.insertions} insertions, ${result.deletions} deletions, ${result.comments} comments`); + * ``` + */ +export async function importDocxWithTracking( + editor: ImportDocxWithTrackingEditor, + arrayBuffer: ArrayBuffer, + options: ImportDocxWithTrackingOptions = {} +): Promise<ImportDocxWithTrackingResult> { const { - commentById, - commentIds, - html: preprocessedHtml, - } = preprocessMammothHtml(mammothHtml); - - // Clean DOCX-specific HTML - const cleanedHtml = cleanDocx(preprocessedHtml, rtf); - - // Parse HTML to DOM element - const element = parseHtmlElement(cleanedHtml); - - if (!element) { - return { - comments: [], - nodes: [], - warnings: [...warnings, 'Failed to parse HTML'], + suggestionKey = 'suggestion', + getSuggestionKey = (id: string) => `suggestion_${id}`, + commentKey = 'comment', + getCommentKey = (id: string) => `comment_${id}`, + isText = defaultIsText, + generateId = defaultGenerateId, + ...convertOptions + } = options; + + const errors: string[] = []; + let insertions = 0; + let deletions = 0; + let commentsApplied = 0; + const discussions: DocxImportDiscussion[] = []; + const importedUsersMap = new Map<string, string>(); + + // Step 1: Convert DOCX to HTML with tracking tokens + const result = await convertToHtmlWithTracking(arrayBuffer, convertOptions); + + // Step 2: Parse tracked changes and comments from HTML + const trackedChanges = parseDocxTrackedChanges(result.value); + const parsedComments = parseDocxComments(result.value); + + const hasTracking = + trackedChanges.changes.length > 0 || parsedComments.comments.length > 0; + + // Step 3: Deserialize HTML to nodes (keep tokens for now) + const parser = new DOMParser(); + const doc = parser.parseFromString(result.value, 'text/html'); + const nodes = editor.api.html.deserialize({ element: doc.body }); + + // Replace editor content with deserialized nodes using transforms when possible. + // This keeps Slate internals in sync (node maps, paths) to avoid render issues. + const originalChildren = [...editor.children]; + const setEditorValue = (nextValue: unknown[]) => { + const tfAny = editor.tf as unknown as { + setValue?: (value: unknown[]) => void; + replaceNodes?: ( + value: unknown[], + options: { at: number[]; children: true } + ) => void; }; + if (tfAny?.setValue) { + tfAny.setValue(nextValue); + return; + } + if (tfAny?.replaceNodes) { + tfAny.replaceNodes(nextValue, { at: [], children: true }); + return; + } + (editor.children as unknown[]).length = 0; + (editor.children as unknown[]).push(...nextValue); + }; + setEditorValue(nodes as unknown[]); + try { + // Create search function adapter + // createSearchRangeFn returns (search) => TRange, but SearchRangeFn expects (editor, search) => TRange + const boundSearchFn = createSearchRangeFn(editor as any); + const searchRangeFn = (_editor: unknown, search: string) => + boundSearchFn(search); + + // Step 4: Apply tracked changes as suggestions + if (trackedChanges.changes.length > 0) { + const suggestionsResult = applyTrackedChangeSuggestions({ + editor: editor as any, + changes: trackedChanges.changes, + searchRange: searchRangeFn as any, + suggestionKey, + getSuggestionKey, + isText, + }); + + insertions = suggestionsResult.insertions; + deletions = suggestionsResult.deletions; + errors.push(...suggestionsResult.errors); + + // Collect users from suggestions + for (const user of suggestionsResult.users) { + importedUsersMap.set(user.id, user.name); + } + } + + // Step 5: Apply comments + if (parsedComments.comments.length > 0) { + const commentsResult = applyTrackedCommentsLocal({ + editor: editor as any, + comments: parsedComments.comments, + searchRange: searchRangeFn as any, + commentKey, + getCommentKey, + isText, + generateId, + }); + + commentsApplied = commentsResult.applied; + discussions.push(...commentsResult.discussions); + errors.push(...commentsResult.errors); + + // Collect users from comments + for (const disc of commentsResult.discussions) { + if (disc.user) { + importedUsersMap.set(disc.user.id, disc.user.name); + } + for (const c of disc.comments ?? []) { + if (c.user) { + importedUsersMap.set(c.user.id, c.user.name); + } + } + } + } + } catch (error) { + // Restore original content on failure + setEditorValue(originalChildren as unknown[]); + throw error; } - // Deserialize HTML to Plate nodes - const nodes = editor.api.html.deserialize({ element }) as TNode[]; + // Post-processing: strip any remaining tracking tokens from text nodes. + // After Slate transforms, nodes may be frozen (Immer), so we rebuild + // the tree with new objects instead of mutating in-place. + if (hasTracking) { + // Use non-greedy .*? with s flag to handle any content between [[ and ]] + const tokenPattern = /\[\[DOCX_(?:INS|DEL|CMT)_(?:START|END):[\s\S]*?\]\]/g; + + const stripTokenText = (text: string): string => + text.replace(tokenPattern, ''); - // Extract comments - const comments = extractComments(commentById, commentIds); + const stripTokensFromNode = (node: any): any => { + if (typeof node.text === 'string') { + const stripped = stripTokenText(node.text); + if (stripped === node.text) return node; + return { ...node, text: stripped }; + } + if (Array.isArray(node.children)) { + const newChildren = node.children + .map(stripTokensFromNode) + .filter( + (child: any) => typeof child.text !== 'string' || child.text !== '' + ); + // Ensure at least one child in elements + const children = newChildren.length > 0 ? newChildren : [{ text: '' }]; + return { ...node, children }; + } + return node; + }; + + const stripped = (editor.children as any[]).map(stripTokensFromNode); + setEditorValue(stripped); + } return { - comments, - nodes, - warnings, + insertions, + deletions, + comments: commentsApplied, + discussions, + users: Array.from(importedUsersMap.entries()).map(([id, name]) => ({ + id, + name, + })), + errors, + messages: result.messages, + hasTracking, }; } diff --git a/packages/docx-io/src/lib/importTrackChanges.ts b/packages/docx-io/src/lib/importTrackChanges.ts new file mode 100644 index 0000000000..ad18b27fe2 --- /dev/null +++ b/packages/docx-io/src/lib/importTrackChanges.ts @@ -0,0 +1,468 @@ +/** + * DOCX Tracked Changes Import + * + * This module provides utilities for parsing and applying tracked changes + * (insertions and deletions) from DOCX files to a Plate editor. + * + * Usage flow: + * 1. Convert DOCX to HTML with mammoth (with tracking token support) + * 2. Parse tokens from HTML using parseDocxTrackedChanges + * 3. Deserialize HTML to editor nodes + * 4. Apply tracked changes using applyTrackedChangeSuggestions + */ + +import type { Point, TRange } from './searchRange'; + +import { + DOCX_DELETION_END_TOKEN_PREFIX, + DOCX_DELETION_START_TOKEN_PREFIX, + DOCX_DELETION_TOKEN_SUFFIX, + DOCX_INSERTION_END_TOKEN_PREFIX, + DOCX_INSERTION_START_TOKEN_PREFIX, + DOCX_INSERTION_TOKEN_SUFFIX, +} from './html-to-docx/tracking'; +import type { DocxTrackedChange } from './types'; + +// Re-export token constants for test usage +export { + DOCX_DELETION_END_TOKEN_PREFIX, + DOCX_DELETION_START_TOKEN_PREFIX, + DOCX_DELETION_TOKEN_SUFFIX, + DOCX_INSERTION_END_TOKEN_PREFIX, + DOCX_INSERTION_START_TOKEN_PREFIX, + DOCX_INSERTION_TOKEN_SUFFIX, +} from './html-to-docx/tracking'; + +export type { TRange } from './searchRange'; +export type { DocxTrackedChange } from './types'; + +// ============================================================================ +// Types +// ============================================================================ + +/** Alias for Point type */ +export type TPoint = Point; + +/** Editor interface for applying tracking changes */ +export type TrackingEditor = { + /** Get string content from a range */ + api: { + string: (range: TRange) => string; + rangeRef: (range: TRange) => { + current: TRange | null; + unref: () => TRange | null; + }; + /** Get nodes matching criteria (for scanning text entries) */ + nodes?: <T>(options: { + at: number[]; + match?: (node: unknown) => boolean; + }) => Iterable<[T, number[]]>; + }; + /** Transform functions */ + tf: { + setNodes: ( + props: Record<string, unknown>, + options: { at: TRange; match: (node: unknown) => boolean; split: boolean } + ) => void; + delete: (options: { at: TRange }) => void; + withMerging: (fn: () => void) => void; + }; + /** Set plugin option */ + setOption?: (plugin: unknown, key: string, value: unknown) => void; + /** Get plugin option */ + getOption?: (plugin: unknown, key: string) => unknown; +}; + +/** Function to search for a string in the editor and return its range */ +export type SearchRangeFn = ( + editor: TrackingEditor, + search: string +) => TRange | null; + +/** Result of parsing tracked changes from HTML */ +export type ParseTrackedChangesResult = { + /** All tracked changes found (insertions and deletions) */ + changes: DocxTrackedChange[]; + /** Number of insertions found */ + insertionCount: number; + /** Number of deletions found */ + deletionCount: number; +}; + +/** Options for applying tracked change suggestions */ +export type ApplySuggestionsOptions = { + /** The editor instance */ + editor: TrackingEditor; + /** Tracked changes to apply */ + changes: DocxTrackedChange[]; + /** Function to search for ranges in editor */ + searchRange: SearchRangeFn; + /** Key constant for suggestion marks (e.g., 'suggestion') */ + suggestionKey: string; + /** Function to generate suggestion key property (e.g., getSuggestionKey) */ + getSuggestionKey: (id: string) => string; + /** Function to check if node is text (e.g., TextApi.isText) */ + isText: (node: unknown) => boolean; +}; + +/** Imported user info from tracked changes */ +export type ImportedUser = { + /** User ID (derived from author name) */ + id: string; + /** Display name (from w:author in DOCX) */ + name: string; +}; + +/** Result of applying tracked change suggestions */ +export type ApplySuggestionsResult = { + /** Number of insertions applied */ + insertions: number; + /** Number of deletions applied */ + deletions: number; + /** Total changes applied */ + total: number; + /** Errors encountered */ + errors: string[]; + /** Unique users found in imported suggestions */ + users: ImportedUser[]; +}; + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** Escape special regex characters in a string */ +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * Compare two points to determine order. + * Returns true if a is after b. + */ +export function isPointAfter(a: TPoint, b: TPoint): boolean { + for (let i = 0; i < Math.min(a.path.length, b.path.length); i++) { + if (a.path[i] > b.path[i]) return true; + if (a.path[i] < b.path[i]) return false; + } + if (a.path.length > b.path.length) return true; + if (a.path.length < b.path.length) return false; + return a.offset > b.offset; +} + +/** + * Format author name as userId. + * Uses the author's name directly from the DOCX. + */ +export function formatAuthorAsUserId(authorName: string | undefined): string { + if (!authorName) return 'imported-unknown'; + return authorName; +} + +/** + * Parse date string to timestamp. + * Returns Date.now() if parsing fails. + */ +export function parseDate(dateString: string | undefined): number { + if (!dateString) return Date.now(); + const parsed = Date.parse(dateString); + return Number.isNaN(parsed) ? Date.now() : parsed; +} + +/** + * Parse date string to Date object. + * Returns provided fallback or new Date() if parsing fails. + */ +export function parseDateToDate( + dateString: string | undefined, + fallback?: Date +): Date { + if (!dateString) return fallback ?? new Date(); + const parsed = Date.parse(dateString); + return Number.isNaN(parsed) ? (fallback ?? new Date()) : new Date(parsed); +} + +// ============================================================================ +// Parsing Functions +// ============================================================================ + +/** + * Parse tracked change tokens (insertions and deletions) from HTML. + * + * This function extracts all tracked changes from HTML that contains + * DOCX tracking tokens. It returns both the changes and token strings + * needed to locate and apply them in an editor. + * + * @param html - The HTML string containing tracking tokens + * @returns Parsed tracked changes with token strings + * + * @example + * ```ts + * const html = mammothResult.value; + * const { changes, insertionCount, deletionCount } = parseDocxTrackedChanges(html); + * + * for (const change of changes) { + * // Find and apply each change in the editor + * const startRange = searchRange(editor, change.startToken); + * const endRange = searchRange(editor, change.endToken); + * // Apply suggestion marks... + * } + * ``` + */ +export function parseDocxTrackedChanges( + html: string +): ParseTrackedChangesResult { + const changes: DocxTrackedChange[] = []; + let insertionCount = 0; + let deletionCount = 0; + + // Parse insertions + const insertionPattern = new RegExp( + `${escapeRegExp(DOCX_INSERTION_START_TOKEN_PREFIX)}(.*?)${escapeRegExp(DOCX_INSERTION_TOKEN_SUFFIX)}`, + 'g' + ); + + for (const match of html.matchAll(insertionPattern)) { + const rawPayload = match[1]; + if (!rawPayload) continue; + + try { + const payload = JSON.parse(decodeURIComponent(rawPayload)) as { + id?: string; + author?: string; + date?: string; + }; + if (!payload.id) continue; + + changes.push({ + id: payload.id, + type: 'insert', + author: payload.author, + date: payload.date, + startToken: `${DOCX_INSERTION_START_TOKEN_PREFIX}${rawPayload}${DOCX_INSERTION_TOKEN_SUFFIX}`, + endToken: `${DOCX_INSERTION_END_TOKEN_PREFIX}${payload.id}${DOCX_INSERTION_TOKEN_SUFFIX}`, + }); + insertionCount++; + } catch { + // Skip malformed tokens + } + } + + // Parse deletions + const deletionPattern = new RegExp( + `${escapeRegExp(DOCX_DELETION_START_TOKEN_PREFIX)}(.*?)${escapeRegExp(DOCX_DELETION_TOKEN_SUFFIX)}`, + 'g' + ); + + for (const match of html.matchAll(deletionPattern)) { + const rawPayload = match[1]; + if (!rawPayload) continue; + + try { + const payload = JSON.parse(decodeURIComponent(rawPayload)) as { + id?: string; + author?: string; + date?: string; + }; + if (!payload.id) continue; + + changes.push({ + id: payload.id, + type: 'remove', + author: payload.author, + date: payload.date, + startToken: `${DOCX_DELETION_START_TOKEN_PREFIX}${rawPayload}${DOCX_DELETION_TOKEN_SUFFIX}`, + endToken: `${DOCX_DELETION_END_TOKEN_PREFIX}${payload.id}${DOCX_DELETION_TOKEN_SUFFIX}`, + }); + deletionCount++; + } catch { + // Skip malformed tokens + } + } + + return { changes, insertionCount, deletionCount }; +} + +// ============================================================================ +// Apply Tracked Change Suggestions +// ============================================================================ + +/** + * Apply tracked change suggestions (insertions and deletions) to the editor. + * + * This function: + * 1. Finds start/end tokens in the editor using searchRange + * 2. Applies suggestion marks to the text between tokens + * 3. Removes the tokens from the document + * + * @param options - Options for applying suggestions + * @returns Result with counts and errors + * + * @example + * ```ts + * import { parseDocxTrackedChanges } from './importTrackChanges'; + * import { applyTrackedChangeSuggestions } from './importTrackChanges'; + * import { getSuggestionKey } from '@platejs/suggestion'; + * + * const { changes } = parseDocxTrackedChanges(html); + * + * const result = applyTrackedChangeSuggestions({ + * editor, + * changes, + * searchRange: mySearchRangeFn, + * suggestionKey: 'suggestion', + * getSuggestionKey, + * isText: TextApi.isText, + * }); + * + * console.log(`Applied ${result.insertions} insertions, ${result.deletions} deletions`); + * ``` + */ +export function applyTrackedChangeSuggestions( + options: ApplySuggestionsOptions +): ApplySuggestionsResult { + const { + editor, + changes, + searchRange, + suggestionKey, + getSuggestionKey, + isText, + } = options; + + let insertions = 0; + let deletions = 0; + const errors: string[] = []; + const usersMap = new Map<string, string>(); + + for (const change of changes) { + try { + const startTokenRange = searchRange(editor, change.startToken); + const endTokenRange = searchRange(editor, change.endToken); + + if (!startTokenRange || !endTokenRange) { + // Clean up any orphan tokens + if (startTokenRange) { + editor.tf.delete({ at: startTokenRange }); + } + if (endTokenRange) { + editor.tf.delete({ at: endTokenRange }); + } + errors.push(`Missing token for change ${change.id}`); + continue; + } + + // Use rangeRef to track ranges through node-splitting operations + const startTokenRef = editor.api.rangeRef(startTokenRange); + const endTokenRef = editor.api.rangeRef(endTokenRange); + + const currentStartRange = startTokenRef.current; + const currentEndRange = endTokenRef.current; + + if (!currentStartRange || !currentEndRange) { + startTokenRef.unref(); + endTokenRef.unref(); + errors.push(`Invalid range for change ${change.id}`); + continue; + } + + // Normalize range to ensure anchor comes before focus + let startPoint = currentStartRange.focus; + let endPoint = currentEndRange.anchor; + + if (isPointAfter(startPoint, endPoint)) { + [startPoint, endPoint] = [endPoint, startPoint]; + } + + const changeRange = { anchor: startPoint, focus: endPoint }; + + const createdAt = parseDate(change.date); + const userId = formatAuthorAsUserId(change.author); + + // Track unique users for registration in app user store + if (change.author && !usersMap.has(userId)) { + usersMap.set(userId, change.author); + } + + // Apply suggestion marks + editor.tf.setNodes( + { + [suggestionKey]: true, + [getSuggestionKey(change.id)]: { + id: change.id, + type: change.type, + userId, + createdAt, + }, + }, + { + at: changeRange, + match: isText, + split: true, + } + ); + + if (change.type === 'insert') { + insertions++; + } else { + deletions++; + } + + // Delete tokens (end first to avoid shifting) + const endRange = endTokenRef.unref(); + if (endRange) { + editor.tf.delete({ at: endRange }); + } + + const startRange = startTokenRef.unref(); + if (startRange) { + editor.tf.delete({ at: startRange }); + } + } catch (error) { + errors.push( + `Failed to apply change ${change.id}: ${error instanceof Error ? error.message : String(error)}` + ); + } + } + + return { + insertions, + deletions, + total: insertions + deletions, + errors, + users: Array.from(usersMap.entries()).map(([id, name]) => ({ id, name })), + }; +} + +// ============================================================================ +// Utility Functions for Tracking Detection +// ============================================================================ + +/** + * Check if HTML contains any DOCX tracking tokens. + * + * This is a fast check that doesn't parse the tokens, just checks + * for their presence. + * + * @param html - The HTML string to check + * @returns Whether any tracking tokens are present + */ +export function hasDocxTrackingTokens(html: string): boolean { + return ( + html.includes(DOCX_INSERTION_START_TOKEN_PREFIX) || + html.includes(DOCX_DELETION_START_TOKEN_PREFIX) + ); +} + +/** + * Remove all DOCX tracked change tokens from HTML. + * + * This preserves the content but removes the token markers. + * + * @param html - The HTML string containing tracking tokens + * @returns HTML with tokens removed (content preserved) + */ +export function stripDocxTrackingTokens(html: string): string { + const tokenPattern = /\[\[DOCX_(INS|DEL|CMT)_(START|END):[^\]]+\]\]/g; + return html.replace(tokenPattern, ''); +} diff --git a/packages/docx-io/src/lib/index.ts b/packages/docx-io/src/lib/index.ts index 83ae56ff0e..c091608dcb 100644 --- a/packages/docx-io/src/lib/index.ts +++ b/packages/docx-io/src/lib/index.ts @@ -1,12 +1,112 @@ /** - * @file Automatically generated by barrelsby. + * DOCX Import/Export Library + * + * This package provides DOCX import and export functionality for Plate editors, + * including support for tracked changes (suggestions) and comments. */ +// ============================================================================ +// Easy Import/Export Plugin Kit +// ============================================================================ + +export { DocxIOKit, DocxIOPlugin } from './DocxIOPlugin'; + +// ============================================================================ // Import +// ============================================================================ + export * from './importDocx'; -export * from './preprocessMammothHtml'; -export * from './types'; +// Export from importTrackChanges, excluding duplicates that are re-exported from importComments +export { + applyTrackedChangeSuggestions, + formatAuthorAsUserId, + isPointAfter, + parseDate, + parseDateToDate, + parseDocxTrackedChanges, + DOCX_DELETION_END_TOKEN_PREFIX, + DOCX_DELETION_START_TOKEN_PREFIX, + DOCX_DELETION_TOKEN_SUFFIX, + DOCX_INSERTION_END_TOKEN_PREFIX, + DOCX_INSERTION_START_TOKEN_PREFIX, + DOCX_INSERTION_TOKEN_SUFFIX, + type ApplySuggestionsOptions, + type ApplySuggestionsResult, + type ImportedUser, + type ParseTrackedChangesResult, + type SearchRangeFn, + type TPoint, + type TrackingEditor, +} from './importTrackChanges'; + +// Export everything from importComments (includes combined hasDocxTrackingTokens & stripDocxTrackingTokens) +export * from './importComments'; + +// ============================================================================ // Export -export * from './docx-export-plugin'; -export * from './html-to-docx'; +// ============================================================================ + +// Export from docx-export-plugin, excluding htmlToDocxBlob which comes from exportDocx +export { + DEFAULT_DOCX_MARGINS, + DOCX_EXPORT_STYLES, + DocxExportPlugin, + downloadDocx, + exportEditorToDocx, + exportToDocx, + type DocxExportApiMethods, + type DocxExportMargins, + type DocxExportOperationOptions, + type DocxExportOptions, + type DocxExportOrientation, + type DocxExportPluginOptions, + type DocxTrackingExportOptions, + type DocxExportTransformMethods, +} from './docx-export-plugin'; + +// Export everything from exportDocx (including htmlToDocxBlob) +export * from './exportDocx'; + +// Export from exportTrackChanges, excluding types that conflict +export { + buildUserNameMap, + injectDocxTrackingTokens, + normalizeDate, + normalizeDateUtc, + toInitials, + type DocxExportComment, + type DocxExportDiscussion, + type DocxExportSuggestionMeta, + type DocxExportUser, + type InjectDocxTrackingTokensOptions, +} from './exportTrackChanges'; + +export * from './exportComments'; + +// ============================================================================ +// Shared +// ============================================================================ + +export * from './searchRange'; +export * from './types'; + +// Note: Document types (DocumentOptions, Margins, etc.) are already exported +// via './exportDocx' which re-exports them from html-to-docx + +// Export tracking token constants from html-to-docx +export { + DOCX_COMMENT_END_TOKEN_PREFIX, + DOCX_COMMENT_START_TOKEN_PREFIX, + DOCX_COMMENT_TOKEN_SUFFIX, + buildCommentEndToken, + buildCommentStartToken, + buildSuggestionEndToken, + buildSuggestionStartToken, + hasTrackingTokens, + splitDocxTrackingTokens, + type CommentPayload, + type StoredComment, + type SuggestionPayload, + type TrackingState, +} from './html-to-docx/tracking'; diff --git a/packages/docx-io/src/lib/injectDocxTrackingTokens.spec.ts b/packages/docx-io/src/lib/injectDocxTrackingTokens.spec.ts new file mode 100644 index 0000000000..fade90c697 --- /dev/null +++ b/packages/docx-io/src/lib/injectDocxTrackingTokens.spec.ts @@ -0,0 +1,669 @@ +import { describe, expect, it } from 'bun:test'; + +import { + buildUserNameMap, + createDiscussionsForTransientComments, + injectDocxTrackingTokens, + normalizeDate, + toInitials, + type DocxExportDiscussion, + type DocxExportSuggestionMeta, +} from './exportTrackChanges'; + +describe('injectDocxTrackingTokens', () => { + describe('utility functions', () => { + describe('toInitials', () => { + it('returns empty string for null/undefined', () => { + expect(toInitials(null)).toBe(''); + expect(toInitials(undefined)).toBe(''); + }); + + it('returns single initial for single name', () => { + expect(toInitials('John')).toBe('J'); + }); + + it('returns two initials for full name', () => { + expect(toInitials('John Doe')).toBe('JD'); + }); + + it('handles multiple names (takes first two)', () => { + expect(toInitials('John Michael Doe')).toBe('JM'); + }); + + it('handles extra whitespace', () => { + expect(toInitials(' John Doe ')).toBe('JD'); + }); + }); + + describe('normalizeDate', () => { + it('returns undefined for null/undefined', () => { + expect(normalizeDate(null)).toBeUndefined(); + expect(normalizeDate(undefined)).toBeUndefined(); + }); + + it('converts Date object to local time with Z suffix (Word convention)', () => { + const date = new Date('2024-01-15T12:00:00Z'); + const result = normalizeDate(date)!; + // Should be ISO 8601 with Z suffix (local time, Word convention) + expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/); + // The time should be the browser's local representation + const expected = [ + date.getFullYear(), + String(date.getMonth() + 1).padStart(2, '0'), + String(date.getDate()).padStart(2, '0'), + ].join('-'); + expect(result).toContain(expected); + }); + + it('converts valid string to local time with Z', () => { + const result = normalizeDate('2024-01-15'); + expect(result).toMatch(/^2024-01-1/); + expect(result).toMatch(/Z$/); + }); + + it('converts timestamp to local time with Z', () => { + const timestamp = new Date('2024-01-15T12:00:00Z').getTime(); + const result = normalizeDate(timestamp)!; + expect(result).toMatch(/Z$/); + // Local hours should match Date.getHours() + const d = new Date(timestamp); + const localHours = String(d.getHours()).padStart(2, '0'); + expect(result).toContain(`T${localHours}:`); + }); + + it('returns undefined for invalid date', () => { + expect(normalizeDate('invalid-date')).toBeUndefined(); + }); + }); + + describe('buildUserNameMap', () => { + it('returns empty map for null/undefined', () => { + expect(buildUserNameMap(null).size).toBe(0); + expect(buildUserNameMap(undefined).size).toBe(0); + }); + + it('extracts user names from discussions', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + user: { id: 'user-1', name: 'Alice' }, + userId: 'user-1', + }, + ]; + const map = buildUserNameMap(discussions); + expect(map.get('user-1')).toBe('Alice'); + }); + + it('extracts user names from comments', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'disc-1', + comments: [ + { + user: { id: 'user-2', name: 'Bob' }, + userId: 'user-2', + }, + ], + }, + ]; + const map = buildUserNameMap(discussions); + expect(map.get('user-2')).toBe('Bob'); + }); + }); + }); + + describe('token injection', () => { + it('returns empty array for empty input', () => { + const result = injectDocxTrackingTokens([]); + expect(result).toEqual([]); + }); + + it('does not modify nodes without marks', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Hello World' }], + }, + ]; + const result = injectDocxTrackingTokens(value); + expect((result[0] as any).children[0].text).toBe('Hello World'); + }); + + it('injects suggestion tokens for insertions', () => { + const value = [ + { + type: 'p', + children: [ + { + text: 'Hello', + suggestion_abc123: { + id: 'abc123', + type: 'insert', + userId: 'user-1', + } as DocxExportSuggestionMeta, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_INS_START:'); + expect(text).toContain('[[DOCX_INS_END:'); + expect(text).toContain('Hello'); + }); + + it('injects suggestion tokens for deletions', () => { + const value = [ + { + type: 'p', + children: [ + { + text: 'Deleted', + suggestion_def456: { + id: 'def456', + type: 'remove', + userId: 'user-1', + } as DocxExportSuggestionMeta, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_DEL_START:'); + expect(text).toContain('[[DOCX_DEL_END:'); + expect(text).toContain('Deleted'); + }); + + it('injects comment tokens', () => { + const discussions: DocxExportDiscussion[] = [ + { + id: 'cmt-1', + documentContent: 'Test comment', + user: { id: 'user-1', name: 'Alice' }, + }, + ]; + + const value = [ + { + type: 'p', + children: [ + { + text: 'Commented text', + comment_cmt1: true, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + discussions, + getCommentIds: (node) => { + const ids: string[] = []; + for (const key of Object.keys(node)) { + if (key.startsWith('comment_')) { + ids.push('cmt-1'); // Map comment_cmt1 to cmt-1 + } + } + return ids; + }, + }); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_CMT_START:'); + expect(text).toContain('[[DOCX_CMT_END:'); + expect(text).toContain('Commented text'); + }); + + it('handles multiple text nodes with same suggestion', () => { + const value = [ + { + type: 'p', + children: [ + { + text: 'Hello ', + suggestion_abc: { id: 'abc', type: 'insert' }, + }, + { + text: 'World', + suggestion_abc: { id: 'abc', type: 'insert' }, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value); + const text1 = (result[0] as any).children[0].text; + const text2 = (result[0] as any).children[1].text; + + // Start token should be at beginning of first node + expect(text1).toContain('[[DOCX_INS_START:'); + expect(text1).not.toContain('[[DOCX_INS_END:'); + + // End token should be at end of last node + expect(text2).toContain('[[DOCX_INS_END:'); + expect(text2).not.toContain('[[DOCX_INS_START:'); + }); + + it('handles nested suggestions and comments', () => { + const value = [ + { + type: 'p', + children: [ + { + text: 'Text', + suggestion_sugg1: { id: 'sugg1', type: 'insert' }, + comment_cmt1: true, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + getCommentIds: (node) => { + if ('comment_cmt1' in node) return ['cmt1']; + return []; + }, + }); + const text = (result[0] as any).children[0].text; + + // Both suggestion and comment tokens should be present + expect(text).toContain('[[DOCX_INS_START:'); + expect(text).toContain('[[DOCX_INS_END:'); + expect(text).toContain('[[DOCX_CMT_START:'); + expect(text).toContain('[[DOCX_CMT_END:'); + }); + + it('does not mutate original value', () => { + const original = [ + { + type: 'p', + children: [ + { + text: 'Original', + suggestion_abc: { id: 'abc', type: 'insert' }, + }, + ], + }, + ]; + + const originalText = original[0].children[0].text; + injectDocxTrackingTokens(original); + + expect(original[0].children[0].text).toBe(originalText); + }); + + it('handles block-level suggestions', () => { + const value = [ + { + type: 'p', + suggestion: { id: 'block-sugg', type: 'insert', userId: 'user-1' }, + children: [{ text: 'Block suggestion text' }], + }, + ]; + + const result = injectDocxTrackingTokens(value); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_INS_START:'); + expect(text).toContain('[[DOCX_INS_END:'); + }); + + it('uses custom getSuggestions function', () => { + const value = [ + { + type: 'p', + children: [ + { + text: 'Custom', + customSuggestionKey: { id: 'custom-1', type: 'insert' }, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + getSuggestions: (node) => { + const customData = (node as Record<string, unknown>) + .customSuggestionKey as DocxExportSuggestionMeta | undefined; + if (customData?.id) { + return [customData]; + } + return []; + }, + }); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_INS_START:'); + }); + + it('resolves author names from userNameMap', () => { + const userNameMap = new Map<string, string>(); + userNameMap.set('user-123', 'John Smith'); + + const value = [ + { + type: 'p', + children: [ + { + text: 'Authored text', + suggestion_abc: { + id: 'abc', + type: 'insert', + userId: 'user-123', + }, + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { userNameMap }); + const text = (result[0] as any).children[0].text; + + // The token should contain the resolved author name + expect(text).toContain('John%20Smith'); + }); + + it('handles mixed content with and without marks', () => { + const value = [ + { + type: 'p', + children: [ + { text: 'Normal ' }, + { text: 'marked', suggestion_abc: { id: 'abc', type: 'insert' } }, + { text: ' normal again' }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value); + + expect((result[0] as any).children[0].text).toBe('Normal '); + expect((result[0] as any).children[1].text).toContain( + '[[DOCX_INS_START:' + ); + expect((result[0] as any).children[1].text).toContain('[[DOCX_INS_END:'); + expect((result[0] as any).children[2].text).toBe(' normal again'); + }); + + it('handles deeply nested elements', () => { + const value = [ + { + type: 'blockquote', + children: [ + { + type: 'p', + children: [ + { + text: 'Nested', + suggestion_abc: { id: 'abc', type: 'insert' }, + }, + ], + }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value); + const text = (result[0] as any).children[0].children[0].text; + + expect(text).toContain('[[DOCX_INS_START:'); + expect(text).toContain('[[DOCX_INS_END:'); + }); + }); + + describe('transient comment export', () => { + describe('createDiscussionsForTransientComments', () => { + it('returns empty array for nodes without transient marks', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Hello World' }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + expect(discussions).toEqual([]); + }); + + it('creates discussion for single transient text node', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Transient comment', commentTransient: true }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + + expect(discussions.length).toBe(1); + expect(discussions[0].documentContent).toBe('Transient comment'); + expect(discussions[0].user?.name).toBe('Draft'); + expect(discussions[0].id).toBeDefined(); + }); + + it('uses custom author name', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Transient', commentTransient: true }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value, { + defaultAuthor: 'Anonymous User', + }); + + expect(discussions[0].user?.name).toBe('Anonymous User'); + }); + + it('uses custom transient key', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Custom key', myTransientKey: true }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value, { + transientKey: 'myTransientKey', + }); + + expect(discussions.length).toBe(1); + expect(discussions[0].documentContent).toBe('Custom key'); + }); + + it('groups consecutive transient nodes into single discussion', () => { + const value = [ + { + type: 'p', + children: [ + { text: 'Part 1 ', commentTransient: true }, + { text: 'Part 2', commentTransient: true }, + ], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + + expect(discussions.length).toBe(1); + expect(discussions[0].documentContent).toBe('Part 1 Part 2'); + }); + + it('creates separate discussions for non-consecutive transient nodes', () => { + const value = [ + { + type: 'p', + children: [ + { text: 'First', commentTransient: true }, + { text: ' normal ' }, + { text: 'Second', commentTransient: true }, + ], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + + expect(discussions.length).toBe(2); + expect(discussions[0].documentContent).toBe('First'); + expect(discussions[1].documentContent).toBe('Second'); + }); + + it('mutates nodes to add comment_<id> marks', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Transient', commentTransient: true }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + + const node = (value[0] as any).children[0]; + const commentKey = `comment_${discussions[0].id}`; + expect(node[commentKey]).toBe(true); + }); + + it('handles transient marks across multiple paragraphs', () => { + const value = [ + { + type: 'p', + children: [{ text: 'First para', commentTransient: true }], + }, + { + type: 'p', + children: [{ text: 'Second para', commentTransient: true }], + }, + ]; + const discussions = createDiscussionsForTransientComments(value); + + // Different paragraphs should create separate discussions + expect(discussions.length).toBe(2); + }); + }); + + describe('injectDocxTrackingTokens with includeTransientComments', () => { + it('ignores transient comments when option is false', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Transient', commentTransient: true }], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + includeTransientComments: false, + }); + const text = (result[0] as any).children[0].text; + + expect(text).not.toContain('[[DOCX_CMT_START:'); + expect(text).toBe('Transient'); + }); + + it('exports transient comments when option is true', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Transient comment', commentTransient: true }], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + includeTransientComments: true, + }); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_CMT_START:'); + expect(text).toContain('[[DOCX_CMT_END:'); + expect(text).toContain('Transient comment'); + }); + + it('uses defaultTransientAuthor option', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Authored', commentTransient: true }], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + includeTransientComments: true, + defaultTransientAuthor: 'Custom Author', + }); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('Custom%20Author'); + }); + + it('uses transientCommentKey option', () => { + const value = [ + { + type: 'p', + children: [{ text: 'Custom key', myKey: true }], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + includeTransientComments: true, + transientCommentKey: 'myKey', + }); + const text = (result[0] as any).children[0].text; + + expect(text).toContain('[[DOCX_CMT_START:'); + }); + + it('merges transient discussions with provided discussions', () => { + const existingDiscussions: DocxExportDiscussion[] = [ + { + id: 'existing-1', + documentContent: 'Existing comment', + user: { id: 'user-1', name: 'Alice' }, + }, + ]; + + const value = [ + { + type: 'p', + children: [ + { text: 'Existing', comment_existing1: true }, + { text: ' and transient', commentTransient: true }, + ], + }, + ]; + + const result = injectDocxTrackingTokens(value, { + discussions: existingDiscussions, + includeTransientComments: true, + getCommentIds: (node) => { + if ('comment_existing1' in node) return ['existing-1']; + const ids: string[] = []; + for (const key of Object.keys(node)) { + if (key.startsWith('comment_') && key !== 'comment_existing1') { + ids.push(key.slice('comment_'.length)); + } + } + return ids; + }, + }); + + const text1 = (result[0] as any).children[0].text; + const text2 = (result[0] as any).children[1].text; + + // Both existing and transient comments should have tokens + expect(text1).toContain('[[DOCX_CMT_START:'); + expect(text2).toContain('[[DOCX_CMT_START:'); + }); + + it('does not mutate original value', () => { + const original = [ + { + type: 'p', + children: [{ text: 'Original', commentTransient: true }], + }, + ]; + + const originalText = original[0].children[0].text; + injectDocxTrackingTokens(original, { includeTransientComments: true }); + + expect(original[0].children[0].text).toBe(originalText); + }); + }); + }); +}); diff --git a/packages/docx-io/src/lib/mammoth.js/LICENSE b/packages/docx-io/src/lib/mammoth.js/LICENSE new file mode 100644 index 0000000000..7e70661173 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013, Michael Williamson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/docx-io/src/lib/mammoth.js/browser/docx/files.js b/packages/docx-io/src/lib/mammoth.js/browser/docx/files.js new file mode 100644 index 0000000000..204cbbcf26 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/browser/docx/files.js @@ -0,0 +1,19 @@ +var promises = require('../../lib/promises'); + +exports.Files = Files; + +function Files() { + function read(uri) { + return promises.reject( + new Error( + "could not open external image: '" + + uri + + "'\ncannot open linked files from a web browser" + ) + ); + } + + return { + read, + }; +} diff --git a/packages/docx-io/src/lib/mammoth.js/browser/unzip.js b/packages/docx-io/src/lib/mammoth.js/browser/unzip.js new file mode 100644 index 0000000000..9aed9df974 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/browser/unzip.js @@ -0,0 +1,11 @@ +var promises = require('../lib/promises'); +var zipfile = require('../lib/zipfile'); + +exports.openZip = openZip; + +function openZip(options) { + if (options.arrayBuffer) { + return promises.resolve(zipfile.openArrayBuffer(options.arrayBuffer)); + } + return promises.reject(new Error('Could not find file in options')); +} diff --git a/packages/docx-io/src/lib/mammoth.js/index.ts b/packages/docx-io/src/lib/mammoth.js/index.ts new file mode 100644 index 0000000000..e7cccc036f --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './lib/index'; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/document-to-html.js b/packages/docx-io/src/lib/mammoth.js/lib/document-to-html.js new file mode 100644 index 0000000000..4ee781878f --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/document-to-html.js @@ -0,0 +1,641 @@ +var _ = require('underscore'); + +var promises = require('./promises'); +var documents = require('./documents'); +var htmlPaths = require('./styles/html-paths'); +var results = require('./results'); +var images = require('./images'); +var Html = require('./html'); +var writers = require('./writers'); + +exports.DocumentConverter = DocumentConverter; + +// Token prefixes for tracked changes - parsed by import-toolbar-button.tsx +var DOCX_INSERTION_START_TOKEN_PREFIX = '[[DOCX_INS_START:'; +var DOCX_INSERTION_END_TOKEN_PREFIX = '[[DOCX_INS_END:'; +var DOCX_INSERTION_TOKEN_SUFFIX = ']]'; +var DOCX_DELETION_START_TOKEN_PREFIX = '[[DOCX_DEL_START:'; +var DOCX_DELETION_END_TOKEN_PREFIX = '[[DOCX_DEL_END:'; +var DOCX_DELETION_TOKEN_SUFFIX = ']]'; + +// Token prefixes for comment ranges - parsed by import-toolbar-button.tsx +var DOCX_COMMENT_START_TOKEN_PREFIX = '[[DOCX_CMT_START:'; +var DOCX_COMMENT_END_TOKEN_PREFIX = '[[DOCX_CMT_END:'; +var DOCX_COMMENT_TOKEN_SUFFIX = ']]'; + +function DocumentConverter(options) { + return { + convertToHtml(element) { + var comments = _.indexBy( + element.type === documents.types.document ? element.comments : [], + 'commentId' + ); + var conversion = new DocumentConversion(options, comments); + return conversion.convertToHtml(element); + }, + }; +} + +function DocumentConversion(options, comments) { + var noteNumber = 1; + // Counter for generating unique IDs for tracked changes without explicit IDs + var trackedChangeIdCounter = 1; + + var noteReferences = []; + + var referencedComments = []; + + options = _.extend({ ignoreEmptyParagraphs: true }, options); + var idPrefix = options.idPrefix === undefined ? '' : options.idPrefix; + var ignoreEmptyParagraphs = options.ignoreEmptyParagraphs; + + var defaultParagraphStyle = htmlPaths.topLevelElement('p'); + + var styleMap = options.styleMap || []; + + function convertToHtml(document) { + var messages = []; + + var html = elementToHtml(document, messages, {}); + + var deferredNodes = []; + walkHtml(html, (node) => { + if (node.type === 'deferred') { + deferredNodes.push(node); + } + }); + var deferredValues = {}; + return promises + .mapSeries(deferredNodes, (deferred) => + deferred.value().then((value) => { + deferredValues[deferred.id] = value; + }) + ) + .then(() => { + function replaceDeferred(nodes) { + return flatMap(nodes, (node) => { + if (node.type === 'deferred') { + return deferredValues[node.id]; + } + if (node.children) { + return [ + _.extend({}, node, { + children: replaceDeferred(node.children), + }), + ]; + } + return [node]; + }); + } + var writer = writers.writer({ + prettyPrint: options.prettyPrint, + outputFormat: options.outputFormat, + }); + Html.write(writer, Html.simplify(replaceDeferred(html))); + return new results.Result(writer.asString(), messages); + }); + } + + function convertElements(elements, messages, options) { + return flatMap(elements, (element) => + elementToHtml(element, messages, options) + ); + } + + function elementToHtml(element, messages, options) { + if (!options) { + throw new Error('options not set'); + } + var handler = elementConverters[element.type]; + if (handler) { + return handler(element, messages, options); + } + return []; + } + + function convertParagraph(element, messages, options) { + return htmlPathForParagraph(element, messages).wrap(() => { + var content = convertElements(element.children, messages, options); + if (ignoreEmptyParagraphs) { + return content; + } + return [Html.forceWrite].concat(content); + }); + } + + function htmlPathForParagraph(element, messages) { + var style = findStyle(element); + + if (style) { + return style.to; + } + if (element.styleId) { + messages.push(unrecognisedStyleWarning('paragraph', element)); + } + return defaultParagraphStyle; + } + + function convertRun(run, messages, options) { + var nodes = () => convertElements(run.children, messages, options); + var paths = []; + if (run.highlight !== null) { + var path = findHtmlPath({ type: 'highlight', color: run.highlight }); + if (path) { + paths.push(path); + } + } + if (run.isSmallCaps) { + paths.push(findHtmlPathForRunProperty('smallCaps')); + } + if (run.isAllCaps) { + paths.push(findHtmlPathForRunProperty('allCaps')); + } + if (run.isStrikethrough) { + paths.push(findHtmlPathForRunProperty('strikethrough', 's')); + } + if (run.isUnderline) { + paths.push(findHtmlPathForRunProperty('underline')); + } + if (run.verticalAlignment === documents.verticalAlignment.subscript) { + paths.push(htmlPaths.element('sub', {}, { fresh: false })); + } + if (run.verticalAlignment === documents.verticalAlignment.superscript) { + paths.push(htmlPaths.element('sup', {}, { fresh: false })); + } + if (run.isItalic) { + paths.push(findHtmlPathForRunProperty('italic', 'em')); + } + if (run.isBold) { + paths.push(findHtmlPathForRunProperty('bold', 'strong')); + } + var stylePath = htmlPaths.empty; + var style = findStyle(run); + if (style) { + stylePath = style.to; + } else if (run.styleId) { + messages.push(unrecognisedStyleWarning('run', run)); + } + paths.push(stylePath); + + paths.forEach((path) => { + nodes = path.wrap.bind(path, nodes); + }); + + return nodes(); + } + + function findHtmlPathForRunProperty(elementType, defaultTagName) { + var path = findHtmlPath({ type: elementType }); + if (path) { + return path; + } + if (defaultTagName) { + return htmlPaths.element(defaultTagName, {}, { fresh: false }); + } + return htmlPaths.empty; + } + + function findHtmlPath(element, defaultPath) { + var style = findStyle(element); + return style ? style.to : defaultPath; + } + + function findStyle(element) { + for (var i = 0; i < styleMap.length; i++) { + if (styleMap[i].from.matches(element)) { + return styleMap[i]; + } + } + } + + function recoveringConvertImage(convertImage) { + return (image, messages) => + promises + .attempt(() => convertImage(image, messages)) + .caught((error) => { + messages.push(results.error(error)); + return []; + }); + } + + function noteHtmlId(note) { + return referentHtmlId(note.noteType, note.noteId); + } + + function noteRefHtmlId(note) { + return referenceHtmlId(note.noteType, note.noteId); + } + + function referentHtmlId(referenceType, referenceId) { + return htmlId(referenceType + '-' + referenceId); + } + + function referenceHtmlId(referenceType, referenceId) { + return htmlId(referenceType + '-ref-' + referenceId); + } + + function htmlId(suffix) { + return idPrefix + suffix; + } + + var defaultTablePath = htmlPaths.elements([ + htmlPaths.element('table', {}, { fresh: true }), + ]); + + function convertTable(element, messages, options) { + return findHtmlPath(element, defaultTablePath).wrap(() => + convertTableChildren(element, messages, options) + ); + } + + function convertTableChildren(element, messages, options) { + var bodyIndex = _.findIndex( + element.children, + (child) => child.type !== documents.types.tableRow || !child.isHeader + ); + if (bodyIndex === -1) { + bodyIndex = element.children.length; + } + var children; + if (bodyIndex === 0) { + children = convertElements( + element.children, + messages, + _.extend({}, options, { isTableHeader: false }) + ); + } else { + var headRows = convertElements( + element.children.slice(0, bodyIndex), + messages, + _.extend({}, options, { isTableHeader: true }) + ); + var bodyRows = convertElements( + element.children.slice(bodyIndex), + messages, + _.extend({}, options, { isTableHeader: false }) + ); + children = [ + Html.freshElement('thead', {}, headRows), + Html.freshElement('tbody', {}, bodyRows), + ]; + } + return [Html.forceWrite].concat(children); + } + + function convertTableRow(element, messages, options) { + var children = convertElements(element.children, messages, options); + return [Html.freshElement('tr', {}, [Html.forceWrite].concat(children))]; + } + + function convertTableCell(element, messages, options) { + var tagName = options.isTableHeader ? 'th' : 'td'; + var children = convertElements(element.children, messages, options); + var attributes = {}; + if (element.colSpan !== 1) { + attributes.colspan = element.colSpan.toString(); + } + if (element.rowSpan !== 1) { + attributes.rowspan = element.rowSpan.toString(); + } + + return [ + Html.freshElement( + tagName, + attributes, + [Html.forceWrite].concat(children) + ), + ]; + } + function getReplies(paraId) { + if (!paraId) return []; + var replies = []; + for (var id in comments) { + var c = comments[id]; + if (c.parentParaId === paraId) { + replies.push(c); + } + } + replies.sort((a, b) => ((a.date || '') > (b.date || '') ? 1 : -1)); + return replies; + } + + function buildCommentPayload(comment, messages, options) { + var payload = { + id: comment.commentId, + authorName: comment.authorName, + authorInitials: comment.authorInitials, + date: comment.date, + paraId: comment.paraId, + parentParaId: comment.parentParaId, + }; + + if (comment.body && comment.body.length > 0) { + payload.text = extractTextFromElements(comment.body); + try { + var richContent = convertElements(comment.body, messages, options); + payload.body = Html.simplify(richContent); + } catch (e) { + var detail = ''; + if (e && typeof e.message === 'string') { + detail = e.message; + } else if (typeof e === 'string') { + detail = e; + } + var message = + 'Failed to convert comment body for comment ' + + comment.commentId + + (detail ? ': ' + detail : ''); + var error = e instanceof Error ? e : new Error(message); + if (error) { + error.message = message; + } + messages.push(results.error(error)); + } + } + + // Recursive replies + var replies = getReplies(comment.paraId); + if (replies.length > 0) { + payload.replies = replies.map((r) => + buildCommentPayload(r, messages, options) + ); + } + + return payload; + } + + function convertCommentReference(reference, messages, options) { + var comment = comments[reference.commentId]; + if (!comment) return []; + + var payload = buildCommentPayload(comment, messages, options); + payload.isPoint = true; + + var startToken = + DOCX_COMMENT_START_TOKEN_PREFIX + + encodeURIComponent(JSON.stringify(payload)) + + DOCX_COMMENT_TOKEN_SUFFIX; + var endToken = + DOCX_COMMENT_END_TOKEN_PREFIX + + encodeURIComponent(reference.commentId) + + DOCX_COMMENT_TOKEN_SUFFIX; + + return [Html.text(startToken + endToken)]; + } + + function convertComment(referencedComment, messages, options) { + // Legacy support or ignore. We use inline tokens. + void referencedComment; + void messages; + void options; + return []; + } + + function convertBreak(element, messages, options) { + return htmlPathForBreak(element).wrap(() => []); + } + + function htmlPathForBreak(element) { + var style = findStyle(element); + if (style) { + return style.to; + } + if (element.breakType === 'line') { + return htmlPaths.topLevelElement('br'); + } + return htmlPaths.empty; + } + + var elementConverters = { + document(document, messages, options) { + var children = convertElements(document.children, messages, options); + var notes = noteReferences.map((noteReference) => + document.notes.resolve(noteReference) + ); + var notesNodes = convertElements(notes, messages, options); + return children.concat([ + Html.freshElement('ol', {}, notesNodes), + Html.freshElement( + 'dl', + {}, + flatMap(referencedComments, (referencedComment) => + convertComment(referencedComment, messages, options) + ) + ), + ]); + }, + paragraph: convertParagraph, + run: convertRun, + text(element, messages, options) { + void messages; + void options; + return [Html.text(element.value)]; + }, + tab(element, messages, options) { + return [Html.text('\t')]; + }, + hyperlink(element, messages, options) { + var href = element.anchor ? '#' + htmlId(element.anchor) : element.href; + var attributes = { href }; + if (element.targetFrame != null) { + attributes.target = element.targetFrame; + } + + var children = convertElements(element.children, messages, options); + return [Html.nonFreshElement('a', attributes, children)]; + }, + checkbox(element) { + var attributes = { type: 'checkbox' }; + if (element.checked) { + attributes['checked'] = 'checked'; + } + return [Html.freshElement('input', attributes)]; + }, + bookmarkStart(element, messages, options) { + var anchor = Html.freshElement( + 'a', + { + id: htmlId(element.name), + }, + [Html.forceWrite] + ); + return [anchor]; + }, + noteReference(element, messages, options) { + void messages; + void options; + noteReferences.push(element); + var anchor = Html.freshElement( + 'a', + { + href: '#' + noteHtmlId(element), + id: noteRefHtmlId(element), + }, + [Html.text('[' + noteNumber++ + ']')] + ); + + return [Html.freshElement('sup', {}, [anchor])]; + }, + note(element, messages, options) { + var children = convertElements(element.body, messages, options); + var backLink = Html.elementWithTag( + htmlPaths.element('p', {}, { fresh: false }), + [ + Html.text(' '), + Html.freshElement('a', { href: '#' + noteRefHtmlId(element) }, [ + Html.text('↑'), + ]), + ] + ); + var body = children.concat([backLink]); + + return Html.freshElement('li', { id: noteHtmlId(element) }, body); + }, + commentReference: convertCommentReference, + comment: convertComment, + commentRangeStart(element, messages, options) { + void options; + var comment = comments[element.commentId]; + if (!comment) { + messages.push( + results.warning( + 'Comment with ID ' + + element.commentId + + ' was referenced by a range but not found in the document' + ) + ); + // Still emit token to prevent errors if desired? + // No, if missing, we can't do much. + return []; + } + + var payload = buildCommentPayload(comment, messages, options); + + var token = + DOCX_COMMENT_START_TOKEN_PREFIX + + encodeURIComponent(JSON.stringify(payload)) + + DOCX_COMMENT_TOKEN_SUFFIX; + return [Html.text(token)]; + }, + commentRangeEnd(element) { + // Emit token for comment range end - will be parsed by import-toolbar-button.tsx + var token = + DOCX_COMMENT_END_TOKEN_PREFIX + + encodeURIComponent(element.commentId) + + DOCX_COMMENT_TOKEN_SUFFIX; + return [Html.text(token)]; + }, + inserted(element, messages, options) { + var children = convertElements(element.children, messages, options); + // Use Word's original changeId if available, otherwise generate one + var changeId = element.changeId || 'ins-' + trackedChangeIdCounter++; + var payload = encodeTrackedChangePayload({ + id: changeId, + author: element.author, + date: element.date, + }); + var startToken = + DOCX_INSERTION_START_TOKEN_PREFIX + + payload + + DOCX_INSERTION_TOKEN_SUFFIX; + var endToken = + DOCX_INSERTION_END_TOKEN_PREFIX + + changeId + + DOCX_INSERTION_TOKEN_SUFFIX; + return [Html.text(startToken)] + .concat(children) + .concat([Html.text(endToken)]); + }, + deleted(element, messages, options) { + var children = convertElements(element.children, messages, options); + // Use Word's original changeId if available, otherwise generate one + var changeId = element.changeId || 'del-' + trackedChangeIdCounter++; + var payload = encodeTrackedChangePayload({ + id: changeId, + author: element.author, + date: element.date, + }); + var startToken = + DOCX_DELETION_START_TOKEN_PREFIX + payload + DOCX_DELETION_TOKEN_SUFFIX; + var endToken = + DOCX_DELETION_END_TOKEN_PREFIX + changeId + DOCX_DELETION_TOKEN_SUFFIX; + return [Html.text(startToken)] + .concat(children) + .concat([Html.text(endToken)]); + }, + image: deferredConversion( + recoveringConvertImage(options.convertImage || images.dataUri) + ), + table: convertTable, + tableRow: convertTableRow, + tableCell: convertTableCell, + break: convertBreak, + }; + return { + convertToHtml, + }; +} + +var deferredId = 1; + +function encodeTrackedChangePayload(payload) { + return encodeURIComponent(JSON.stringify(payload)); +} + +function extractTextFromElements(elements) { + var text = ''; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type === 'text') { + text += element.value; + } else if (element.type === 'paragraph') { + text += extractTextFromElements(element.children || []) + '\n'; + } else if (element.children) { + text += extractTextFromElements(element.children); + } + } + return text; +} + +function deferredConversion(func) { + return (element, messages, options) => [ + { + type: 'deferred', + id: deferredId++, + value() { + return func(element, messages, options); + }, + }, + ]; +} + +function unrecognisedStyleWarning(type, element) { + return results.warning( + 'Unrecognised ' + + type + + " style: '" + + element.styleName + + "'" + + ' (Style ID: ' + + element.styleId + + ')' + ); +} + +function flatMap(values, func) { + return _.flatten(values.map(func), true); +} + +function walkHtml(nodes, callback) { + nodes.forEach((node) => { + callback(node); + if (node.children) { + walkHtml(node.children, callback); + } + }); +} + +var commentAuthorLabel = (exports.commentAuthorLabel = + function commentAuthorLabel(comment) { + return comment.authorInitials || ''; + }); diff --git a/packages/docx-io/src/lib/mammoth.js/lib/documents.js b/packages/docx-io/src/lib/mammoth.js/lib/documents.js new file mode 100644 index 0000000000..21cf810a9c --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/documents.js @@ -0,0 +1,300 @@ +var _ = require('underscore'); + +var types = (exports.types = { + document: 'document', + paragraph: 'paragraph', + run: 'run', + text: 'text', + tab: 'tab', + checkbox: 'checkbox', + hyperlink: 'hyperlink', + noteReference: 'noteReference', + image: 'image', + note: 'note', + commentReference: 'commentReference', + comment: 'comment', + commentRangeStart: 'commentRangeStart', + commentRangeEnd: 'commentRangeEnd', + inserted: 'inserted', + deleted: 'deleted', + table: 'table', + tableRow: 'tableRow', + tableCell: 'tableCell', + break: 'break', + bookmarkStart: 'bookmarkStart', +}); + +function Document(children, options) { + options = options || {}; + return { + type: types.document, + children, + notes: options.notes || new Notes({}), + comments: options.comments || [], + }; +} + +function Paragraph(children, properties) { + properties = properties || {}; + var indent = properties.indent || {}; + return { + type: types.paragraph, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + numbering: properties.numbering || null, + alignment: properties.alignment || null, + indent: { + start: indent.start || null, + end: indent.end || null, + firstLine: indent.firstLine || null, + hanging: indent.hanging || null, + }, + paraId: properties.paraId || null, + }; +} + +function Run(children, properties) { + properties = properties || {}; + return { + type: types.run, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + isBold: !!properties.isBold, + isUnderline: !!properties.isUnderline, + isItalic: !!properties.isItalic, + isStrikethrough: !!properties.isStrikethrough, + isAllCaps: !!properties.isAllCaps, + isSmallCaps: !!properties.isSmallCaps, + verticalAlignment: + properties.verticalAlignment || verticalAlignment.baseline, + font: properties.font || null, + fontSize: properties.fontSize || null, + highlight: properties.highlight || null, + }; +} + +var verticalAlignment = { + baseline: 'baseline', + superscript: 'superscript', + subscript: 'subscript', +}; + +function Text(value) { + return { + type: types.text, + value, + }; +} + +function Tab() { + return { + type: types.tab, + }; +} + +function Checkbox(options) { + return { + type: types.checkbox, + checked: options.checked, + }; +} + +function Hyperlink(children, options) { + return { + type: types.hyperlink, + children, + href: options.href, + anchor: options.anchor, + targetFrame: options.targetFrame, + }; +} + +function NoteReference(options) { + return { + type: types.noteReference, + noteType: options.noteType, + noteId: options.noteId, + }; +} + +function Notes(notes) { + this._notes = _.indexBy(notes, (note) => noteKey(note.noteType, note.noteId)); +} + +Notes.prototype.resolve = function (reference) { + return this.findNoteByKey(noteKey(reference.noteType, reference.noteId)); +}; + +Notes.prototype.findNoteByKey = function (key) { + return this._notes[key] || null; +}; + +function Note(options) { + return { + type: types.note, + noteType: options.noteType, + noteId: options.noteId, + body: options.body, + }; +} + +function commentReference(options) { + return { + type: types.commentReference, + commentId: options.commentId, + }; +} + +function comment(options) { + return { + type: types.comment, + commentId: options.commentId, + body: options.body, + authorName: options.authorName || null, + authorInitials: options.authorInitials || null, + date: options.date || null, + paraId: options.paraId || null, + parentParaId: options.parentParaId || null, + }; +} + +function commentRangeStart(options) { + return { + type: types.commentRangeStart, + commentId: options.commentId, + }; +} + +function commentRangeEnd(options) { + return { + type: types.commentRangeEnd, + commentId: options.commentId, + }; +} + +function inserted(children, options) { + options = options || {}; + return { + type: types.inserted, + children, + author: options.author || null, + date: options.date || null, + changeId: options.changeId || null, + }; +} + +function deleted(children, options) { + options = options || {}; + return { + type: types.deleted, + children, + author: options.author || null, + date: options.date || null, + changeId: options.changeId || null, + }; +} + +function noteKey(noteType, id) { + return noteType + '-' + id; +} + +function Image(options) { + return { + type: types.image, + // `read` is retained for backwards compatibility, but other read + // methods should be preferred. + read(encoding) { + if (encoding) { + return options.readImage(encoding); + } + return options + .readImage() + .then((arrayBuffer) => Buffer.from(arrayBuffer)); + }, + readAsArrayBuffer() { + return options.readImage(); + }, + readAsBase64String() { + return options.readImage('base64'); + }, + readAsBuffer() { + return options + .readImage() + .then((arrayBuffer) => Buffer.from(arrayBuffer)); + }, + altText: options.altText, + contentType: options.contentType, + }; +} + +function Table(children, properties) { + properties = properties || {}; + return { + type: types.table, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + }; +} + +function TableRow(children, options) { + options = options || {}; + return { + type: types.tableRow, + children, + isHeader: options.isHeader || false, + }; +} + +function TableCell(children, options) { + options = options || {}; + return { + type: types.tableCell, + children, + colSpan: options.colSpan == null ? 1 : options.colSpan, + rowSpan: options.rowSpan == null ? 1 : options.rowSpan, + }; +} + +function Break(breakType) { + return { + type: types['break'], + breakType, + }; +} + +function BookmarkStart(options) { + return { + type: types.bookmarkStart, + name: options.name, + }; +} + +exports.document = exports.Document = Document; +exports.paragraph = exports.Paragraph = Paragraph; +exports.run = exports.Run = Run; +exports.text = exports.Text = Text; +exports.tab = exports.Tab = Tab; +exports.checkbox = exports.Checkbox = Checkbox; +exports.Hyperlink = Hyperlink; +exports.noteReference = exports.NoteReference = NoteReference; +exports.Notes = Notes; +exports.Note = Note; +exports.commentReference = commentReference; +exports.comment = comment; +exports.commentRangeStart = commentRangeStart; +exports.commentRangeEnd = commentRangeEnd; +exports.inserted = inserted; +exports.deleted = deleted; +exports.Image = Image; +exports.Table = Table; +exports.TableRow = TableRow; +exports.TableCell = TableCell; +exports.lineBreak = Break('line'); +exports.pageBreak = Break('page'); +exports.columnBreak = Break('column'); +exports.BookmarkStart = BookmarkStart; + +exports.verticalAlignment = verticalAlignment; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/body-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/body-reader.js new file mode 100644 index 0000000000..9d9a682dfc --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/body-reader.js @@ -0,0 +1,892 @@ +exports.createBodyReader = createBodyReader; +exports._readNumberingProperties = readNumberingProperties; + +var dingbatToUnicode = require('dingbat-to-unicode'); +var _ = require('underscore'); + +var documents = require('../documents'); +var Result = require('../results').Result; +var warning = require('../results').warning; +var xml = require('../xml'); +var transforms = require('../transforms'); +var uris = require('./uris'); + +function createBodyReader(options) { + return { + readXmlElement(element) { + return new BodyReader(options).readXmlElement(element); + }, + readXmlElements(elements) { + return new BodyReader(options).readXmlElements(elements); + }, + }; +} + +function BodyReader(options) { + var complexFieldStack = []; + var currentInstrText = []; + + // When a paragraph is marked as deleted, its contents should be combined + // with the following paragraph. See 17.13.5.15 del (Deleted Paragraph) of + // ECMA-376 4th edition Part 1. + var deletedParagraphContents = []; + + var relationships = options.relationships; + var contentTypes = options.contentTypes; + var docxFile = options.docxFile; + var files = options.files; + var numbering = options.numbering; + var styles = options.styles; + + function readXmlElements(elements) { + var results = elements.map(readXmlElement); + return combineResults(results); + } + + function readXmlElement(element) { + if (element.type === 'element') { + var handler = xmlElementReaders[element.name]; + if (handler) { + return handler(element); + } + if (!Object.hasOwn(ignoreElements, element.name)) { + var message = warning( + 'An unrecognised element was ignored: ' + element.name + ); + return emptyResultWithMessages([message]); + } + } + return emptyResult(); + } + + function readParagraphProperties(element) { + return readParagraphStyle(element).map((style) => ({ + type: 'paragraphProperties', + styleId: style.styleId, + styleName: style.name, + alignment: element.firstOrEmpty('w:jc').attributes['w:val'], + numbering: readNumberingProperties( + style.styleId, + element.firstOrEmpty('w:numPr'), + numbering + ), + indent: readParagraphIndent(element.firstOrEmpty('w:ind')), + })); + } + + function readParagraphIndent(element) { + return { + start: element.attributes['w:start'] || element.attributes['w:left'], + end: element.attributes['w:end'] || element.attributes['w:right'], + firstLine: element.attributes['w:firstLine'], + hanging: element.attributes['w:hanging'], + }; + } + + function readRunProperties(element) { + return readRunStyle(element).map((style) => { + var fontSizeString = element.firstOrEmpty('w:sz').attributes['w:val']; + // w:sz gives the font size in half points, so halve the value to get the size in points + var fontSize = /^[0-9]+$/.test(fontSizeString) + ? Number.parseInt(fontSizeString, 10) / 2 + : null; + + return { + type: 'runProperties', + styleId: style.styleId, + styleName: style.name, + verticalAlignment: + element.firstOrEmpty('w:vertAlign').attributes['w:val'], + font: element.firstOrEmpty('w:rFonts').attributes['w:ascii'], + fontSize, + isBold: readBooleanElement(element.first('w:b')), + isUnderline: readUnderline(element.first('w:u')), + isItalic: readBooleanElement(element.first('w:i')), + isStrikethrough: readBooleanElement(element.first('w:strike')), + isAllCaps: readBooleanElement(element.first('w:caps')), + isSmallCaps: readBooleanElement(element.first('w:smallCaps')), + highlight: readHighlightValue( + element.firstOrEmpty('w:highlight').attributes['w:val'] + ), + }; + }); + } + + function readUnderline(element) { + if (element) { + var value = element.attributes['w:val']; + return ( + value !== undefined && + value !== 'false' && + value !== '0' && + value !== 'none' + ); + } + return false; + } + + function readBooleanElement(element) { + if (element) { + var value = element.attributes['w:val']; + return value !== 'false' && value !== '0'; + } + return false; + } + + function readBooleanAttributeValue(value) { + return value !== 'false' && value !== '0'; + } + + function readHighlightValue(value) { + if (!value || value === 'none') { + return null; + } + return value; + } + + function readParagraphStyle(element) { + return readStyle( + element, + 'w:pStyle', + 'Paragraph', + styles.findParagraphStyleById + ); + } + + function readRunStyle(element) { + return readStyle(element, 'w:rStyle', 'Run', styles.findCharacterStyleById); + } + + function readTableStyle(element) { + return readStyle(element, 'w:tblStyle', 'Table', styles.findTableStyleById); + } + + function readStyle(element, styleTagName, styleType, findStyleById) { + var messages = []; + var styleElement = element.first(styleTagName); + var styleId = null; + var name = null; + if (styleElement) { + styleId = styleElement.attributes['w:val']; + if (styleId) { + var style = findStyleById(styleId); + if (style) { + name = style.name; + } else { + messages.push(undefinedStyleWarning(styleType, styleId)); + } + } + } + return elementResultWithMessages({ styleId, name }, messages); + } + + function readFldChar(element) { + var type = element.attributes['w:fldCharType']; + if (type === 'begin') { + complexFieldStack.push({ type: 'begin', fldChar: element }); + currentInstrText = []; + } else if (type === 'end') { + var complexFieldEnd = complexFieldStack.pop(); + if (complexFieldEnd.type === 'begin') { + complexFieldEnd = parseCurrentInstrText(complexFieldEnd); + } + if (complexFieldEnd.type === 'checkbox') { + return elementResult( + documents.checkbox({ + checked: complexFieldEnd.checked, + }) + ); + } + } else if (type === 'separate') { + var complexFieldSeparate = complexFieldStack.pop(); + var complexField = parseCurrentInstrText(complexFieldSeparate); + complexFieldStack.push(complexField); + } + return emptyResult(); + } + + function currentHyperlinkOptions() { + var topHyperlink = _.last( + complexFieldStack.filter( + (complexField) => complexField.type === 'hyperlink' + ) + ); + return topHyperlink ? topHyperlink.options : null; + } + + function parseCurrentInstrText(complexField) { + return parseInstrText( + currentInstrText.join(''), + complexField.type === 'begin' ? complexField.fldChar : xml.emptyElement + ); + } + + function parseInstrText(instrText, fldChar) { + var externalLinkResult = /\s*HYPERLINK "(.*)"/.exec(instrText); + if (externalLinkResult) { + return { type: 'hyperlink', options: { href: externalLinkResult[1] } }; + } + + var internalLinkResult = /\s*HYPERLINK\s+\\l\s+"(.*)"/.exec(instrText); + if (internalLinkResult) { + return { type: 'hyperlink', options: { anchor: internalLinkResult[1] } }; + } + + var checkboxResult = /\s*FORMCHECKBOX\s*/.exec(instrText); + if (checkboxResult) { + var checkboxElement = fldChar + .firstOrEmpty('w:ffData') + .firstOrEmpty('w:checkBox'); + var checkedElement = checkboxElement.first('w:checked'); + var checked = + checkedElement == null + ? readBooleanElement(checkboxElement.first('w:default')) + : readBooleanElement(checkedElement); + return { type: 'checkbox', checked }; + } + + return { type: 'unknown' }; + } + + function readInstrText(element) { + currentInstrText.push(element.text()); + return emptyResult(); + } + + function readSymbol(element) { + // See 17.3.3.30 sym (Symbol Character) of ECMA-376 4th edition Part 1 + var font = element.attributes['w:font']; + var char = element.attributes['w:char']; + var unicodeCharacter = dingbatToUnicode.hex(font, char); + if (unicodeCharacter == null && /^F0..$/.test(char)) { + unicodeCharacter = dingbatToUnicode.hex(font, char.substring(2)); + } + + if (unicodeCharacter == null) { + return emptyResultWithMessages([ + warning( + 'A w:sym element with an unsupported character was ignored: char ' + + char + + ' in font ' + + font + ), + ]); + } + return elementResult(new documents.Text(unicodeCharacter.string)); + } + + function noteReferenceReader(noteType) { + return (element) => { + var noteId = element.attributes['w:id']; + return elementResult( + new documents.NoteReference({ + noteType, + noteId, + }) + ); + }; + } + + function readCommentReference(element) { + return elementResult( + documents.commentReference({ + commentId: element.attributes['w:id'], + }) + ); + } + + function readChildElements(element) { + return readXmlElements(element.children); + } + + var xmlElementReaders = { + 'w:p'(element) { + var paragraphPropertiesElement = element.firstOrEmpty('w:pPr'); + + var isDeleted = !!paragraphPropertiesElement + .firstOrEmpty('w:rPr') + .first('w:del'); + + if (isDeleted) { + element.children.forEach((child) => { + deletedParagraphContents.push(child); + }); + return emptyResult(); + } + var childrenXml = element.children; + if (deletedParagraphContents.length > 0) { + childrenXml = deletedParagraphContents.concat(childrenXml); + deletedParagraphContents = []; + } + return ReadResult.map( + readParagraphProperties(paragraphPropertiesElement), + readXmlElements(childrenXml), + (properties, children) => { + properties.paraId = element.attributes['wordml:paraId']; + return new documents.Paragraph(children, properties); + } + ).insertExtra(); + }, + 'w:r'(element) { + return ReadResult.map( + readRunProperties(element.firstOrEmpty('w:rPr')), + readXmlElements(element.children), + (properties, children) => { + var hyperlinkOptions = currentHyperlinkOptions(); + if (hyperlinkOptions !== null) { + children = [new documents.Hyperlink(children, hyperlinkOptions)]; + } + + return new documents.Run(children, properties); + } + ); + }, + 'w:fldChar': readFldChar, + 'w:instrText': readInstrText, + 'w:t'(element) { + return elementResult(new documents.Text(element.text())); + }, + 'w:tab'(element) { + return elementResult(new documents.Tab()); + }, + 'w:noBreakHyphen'() { + return elementResult(new documents.Text('\u2011')); + }, + 'w:softHyphen'(element) { + return elementResult(new documents.Text('\u00AD')); + }, + 'w:sym': readSymbol, + 'w:hyperlink'(element) { + var relationshipId = element.attributes['r:id']; + var anchor = element.attributes['w:anchor']; + return readXmlElements(element.children).map((children) => { + function create(options) { + var targetFrame = element.attributes['w:tgtFrame'] || null; + + return new documents.Hyperlink( + children, + _.extend({ targetFrame }, options) + ); + } + + if (relationshipId) { + var href = relationships.findTargetByRelationshipId(relationshipId); + if (anchor) { + href = uris.replaceFragment(href, anchor); + } + return create({ href }); + } + if (anchor) { + return create({ anchor }); + } + return children; + }); + }, + 'w:tbl': readTable, + 'w:tr': readTableRow, + 'w:tc': readTableCell, + 'w:footnoteReference': noteReferenceReader('footnote'), + 'w:endnoteReference': noteReferenceReader('endnote'), + 'w:commentReference': readCommentReference, + 'w:br'(element) { + var breakType = element.attributes['w:type']; + if (breakType == null || breakType === 'textWrapping') { + return elementResult(documents.lineBreak); + } + if (breakType === 'page') { + return elementResult(documents.pageBreak); + } + if (breakType === 'column') { + return elementResult(documents.columnBreak); + } + return emptyResultWithMessages([ + warning('Unsupported break type: ' + breakType), + ]); + }, + 'w:bookmarkStart'(element) { + var name = element.attributes['w:name']; + if (name === '_GoBack') { + return emptyResult(); + } + return elementResult(new documents.BookmarkStart({ name })); + }, + + 'mc:AlternateContent'(element) { + return readChildElements(element.firstOrEmpty('mc:Fallback')); + }, + + 'w:sdt'(element) { + var contentResult = readXmlElements( + element.firstOrEmpty('w:sdtContent').children + ); + return contentResult.map((content) => { + // From the WordML standard: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/3350cb64-931f-41f7-8824-f18b2568ce66 + // + // > A CT_SdtCheckbox element that specifies that the parent + // > structured document tag is a checkbox when displayed in the + // > document. The parent structured document tag contents MUST + // > contain a single character and optionally an additional + // > character in a deleted run. + + var checkbox = element.firstOrEmpty('w:sdtPr').first('wordml:checkbox'); + + if (checkbox) { + var checkedElement = checkbox.first('wordml:checked'); + var isChecked = + !!checkedElement && + readBooleanAttributeValue(checkedElement.attributes['wordml:val']); + var documentCheckbox = documents.checkbox({ + checked: isChecked, + }); + + var hasCheckbox = false; + var replacedContent = content.map( + transforms._elementsOfType(documents.types.text, (text) => { + if (text.value.length > 0 && !hasCheckbox) { + hasCheckbox = true; + return documentCheckbox; + } + return text; + }) + ); + + if (hasCheckbox) { + return replacedContent; + } + return documentCheckbox; + } + return content; + }); + }, + + // Tracked changes: wrap content in documents.inserted with author/date/changeId metadata + 'w:ins'(element) { + var author = element.attributes['w:author']; + var date = element.attributes['w:date']; + var changeId = element.attributes['w:id']; + return readXmlElements(element.children).map((children) => + documents.inserted(children, { + author, + date, + changeId, + }) + ); + }, + // Tracked deletions: wrap content in documents.deleted with author/date/changeId metadata + 'w:del'(element) { + var author = element.attributes['w:author']; + var date = element.attributes['w:date']; + var changeId = element.attributes['w:id']; + return readXmlElements(element.children).map((children) => + documents.deleted(children, { + author, + date, + changeId, + }) + ); + }, + // Handle deleted text within w:del elements + 'w:delText'(element) { + return elementResult(new documents.Text(element.text())); + }, + 'w:commentRangeStart'(element) { + return elementResult( + documents.commentRangeStart({ + commentId: element.attributes['w:id'], + }) + ); + }, + 'w:commentRangeEnd'(element) { + return elementResult( + documents.commentRangeEnd({ + commentId: element.attributes['w:id'], + }) + ); + }, + 'w:object': readChildElements, + 'w:smartTag': readChildElements, + 'w:drawing': readChildElements, + 'w:pict'(element) { + return readChildElements(element).toExtra(); + }, + 'v:roundrect': readChildElements, + 'v:shape': readChildElements, + 'v:textbox': readChildElements, + 'w:txbxContent': readChildElements, + 'wp:inline': readDrawingElement, + 'wp:anchor': readDrawingElement, + 'v:imagedata': readImageData, + 'v:group': readChildElements, + 'v:rect': readChildElements, + }; + + return { + readXmlElement, + readXmlElements, + }; + + function readTable(element) { + var propertiesResult = readTableProperties(element.firstOrEmpty('w:tblPr')); + return readXmlElements(element.children) + .flatMap(calculateRowSpans) + .flatMap((children) => + propertiesResult.map((properties) => + documents.Table(children, properties) + ) + ); + } + + function readTableProperties(element) { + return readTableStyle(element).map((style) => ({ + styleId: style.styleId, + styleName: style.name, + })); + } + + function readTableRow(element) { + var properties = element.firstOrEmpty('w:trPr'); + + // See 17.13.5.12 del (Deleted Table Row) of ECMA-376 4th edition Part 1 + var isDeleted = !!properties.first('w:del'); + if (isDeleted) { + return emptyResult(); + } + + var isHeader = !!properties.first('w:tblHeader'); + return readXmlElements(element.children).map((children) => + documents.TableRow(children, { isHeader }) + ); + } + + function readTableCell(element) { + return readXmlElements(element.children).map((children) => { + var properties = element.firstOrEmpty('w:tcPr'); + + var gridSpan = properties.firstOrEmpty('w:gridSpan').attributes['w:val']; + var colSpan = gridSpan ? Number.parseInt(gridSpan, 10) : 1; + + var cell = documents.TableCell(children, { colSpan }); + cell._vMerge = readVMerge(properties); + return cell; + }); + } + + function readVMerge(properties) { + var element = properties.first('w:vMerge'); + if (element) { + var val = element.attributes['w:val']; + return val === 'continue' || !val; + } + return null; + } + + function calculateRowSpans(rows) { + var unexpectedNonRows = _.any( + rows, + (row) => row.type !== documents.types.tableRow + ); + if (unexpectedNonRows) { + removeVMergeProperties(rows); + return elementResultWithMessages(rows, [ + warning( + 'unexpected non-row element in table, cell merging may be incorrect' + ), + ]); + } + var unexpectedNonCells = _.any(rows, (row) => + _.any(row.children, (cell) => cell.type !== documents.types.tableCell) + ); + if (unexpectedNonCells) { + removeVMergeProperties(rows); + return elementResultWithMessages(rows, [ + warning( + 'unexpected non-cell element in table row, cell merging may be incorrect' + ), + ]); + } + + var columns = {}; + + rows.forEach((row) => { + var cellIndex = 0; + row.children.forEach((cell) => { + if (cell._vMerge && columns[cellIndex]) { + columns[cellIndex].rowSpan++; + } else { + columns[cellIndex] = cell; + cell._vMerge = false; + } + cellIndex += cell.colSpan; + }); + }); + + rows.forEach((row) => { + row.children = row.children.filter((cell) => !cell._vMerge); + row.children.forEach((cell) => { + delete cell._vMerge; + }); + }); + + return elementResult(rows); + } + + function removeVMergeProperties(rows) { + rows.forEach((row) => { + var cells = transforms.getDescendantsOfType( + row, + documents.types.tableCell + ); + cells.forEach((cell) => { + delete cell._vMerge; + }); + }); + } + + function readDrawingElement(element) { + var blips = element + .getElementsByTagName('a:graphic') + .getElementsByTagName('a:graphicData') + .getElementsByTagName('pic:pic') + .getElementsByTagName('pic:blipFill') + .getElementsByTagName('a:blip'); + + return combineResults(blips.map(readBlip.bind(null, element))); + } + + function readBlip(element, blip) { + var propertiesElement = element.firstOrEmpty('wp:docPr'); + var properties = propertiesElement.attributes; + + var altText = isBlank(properties.descr) + ? properties.title + : properties.descr; + + var blipImageFile = findBlipImageFile(blip); + if (blipImageFile === null) { + return emptyResultWithMessages([ + warning('Could not find image file for a:blip element'), + ]); + } + + return readImage(blipImageFile, altText).map((imageElement) => { + var hlinkClickElement = propertiesElement.firstOrEmpty('a:hlinkClick'); + var relationshipId = hlinkClickElement.attributes['r:id']; + if (relationshipId) { + var href = relationships.findTargetByRelationshipId(relationshipId); + return new documents.Hyperlink([imageElement], { href }); + } + return imageElement; + }); + } + + function isBlank(value) { + return value == null || /^\s*$/.test(value); + } + + function findBlipImageFile(blip) { + var embedRelationshipId = blip.attributes['r:embed']; + var linkRelationshipId = blip.attributes['r:link']; + if (embedRelationshipId) { + return findEmbeddedImageFile(embedRelationshipId); + } + if (linkRelationshipId) { + var imagePath = + relationships.findTargetByRelationshipId(linkRelationshipId); + return { + path: imagePath, + read: files.read.bind(files, imagePath), + }; + } + return null; + } + + function readImageData(element) { + var relationshipId = element.attributes['r:id']; + + if (relationshipId) { + return readImage( + findEmbeddedImageFile(relationshipId), + element.attributes['o:title'] + ); + } + return emptyResultWithMessages([ + warning('A v:imagedata element without a relationship ID was ignored'), + ]); + } + + function findEmbeddedImageFile(relationshipId) { + var path = uris.uriToZipEntryName( + 'word', + relationships.findTargetByRelationshipId(relationshipId) + ); + return { + path, + read: docxFile.read.bind(docxFile, path), + }; + } + + function readImage(imageFile, altText) { + var contentType = contentTypes.findContentType(imageFile.path); + + var image = documents.Image({ + readImage: imageFile.read, + altText, + contentType, + }); + var warnings = supportedImageTypes[contentType] + ? [] + : [ + warning( + 'Image of type ' + + contentType + + ' is unlikely to display in web browsers' + ), + ]; + return elementResultWithMessages(image, warnings); + } + + function undefinedStyleWarning(type, styleId) { + return warning( + type + + ' style with ID ' + + styleId + + ' was referenced but not defined in the document' + ); + } +} + +function readNumberingProperties(styleId, element, numbering) { + var level = element.firstOrEmpty('w:ilvl').attributes['w:val']; + var numId = element.firstOrEmpty('w:numId').attributes['w:val']; + if (level !== undefined && numId !== undefined) { + return numbering.findLevel(numId, level); + } + + if (styleId != null) { + var levelByStyleId = numbering.findLevelByParagraphStyleId(styleId); + if (levelByStyleId != null) { + return levelByStyleId; + } + } + + // Some malformed documents define numbering levels without an index, and + // reference the numbering using a w:numPr element without a w:ilvl child. + // To handle such cases, we assume a level of 0 as a fallback. + if (numId !== undefined) { + return numbering.findLevel(numId, '0'); + } + + return null; +} + +var supportedImageTypes = { + 'image/png': true, + 'image/gif': true, + 'image/jpeg': true, + 'image/svg+xml': true, + 'image/tiff': true, +}; + +var ignoreElements = { + 'office-word:wrap': true, + 'v:shadow': true, + 'v:shapetype': true, + 'w:annotationRef': true, + 'w:bookmarkEnd': true, + 'w:sectPr': true, + 'w:proofErr': true, + 'w:lastRenderedPageBreak': true, + // w:commentRangeStart, w:commentRangeEnd are now handled by xmlElementReaders + // w:del and w:ins are now handled by xmlElementReaders for tracked changes support + 'w:footnoteRef': true, + 'w:endnoteRef': true, + 'w:pPr': true, + 'w:rPr': true, + 'w:tblPr': true, + 'w:tblGrid': true, + 'w:trPr': true, + 'w:tcPr': true, +}; + +function emptyResultWithMessages(messages) { + return new ReadResult(null, null, messages); +} + +function emptyResult() { + return new ReadResult(null); +} + +function elementResult(element) { + return new ReadResult(element); +} + +function elementResultWithMessages(element, messages) { + return new ReadResult(element, null, messages); +} + +function ReadResult(element, extra, messages) { + this.value = element || []; + this.extra = extra || []; + this._result = new Result( + { + element: this.value, + extra, + }, + messages + ); + this.messages = this._result.messages; +} + +ReadResult.prototype.toExtra = function () { + return new ReadResult( + null, + joinElements(this.extra, this.value), + this.messages + ); +}; + +ReadResult.prototype.insertExtra = function () { + var extra = this.extra; + if (extra && extra.length) { + return new ReadResult(joinElements(this.value, extra), null, this.messages); + } + return this; +}; + +ReadResult.prototype.map = function (func) { + var result = this._result.map((value) => func(value.element)); + return new ReadResult(result.value, this.extra, result.messages); +}; + +ReadResult.prototype.flatMap = function (func) { + var result = this._result.flatMap((value) => func(value.element)._result); + return new ReadResult( + result.value.element, + joinElements(this.extra, result.value.extra), + result.messages + ); +}; + +ReadResult.map = (first, second, func) => + new ReadResult( + func(first.value, second.value), + joinElements(first.extra, second.extra), + first.messages.concat(second.messages) + ); + +function combineResults(results) { + var result = Result.combine(_.pluck(results, '_result')); + return new ReadResult( + _.flatten(_.pluck(result.value, 'element')), + _.filter(_.flatten(_.pluck(result.value, 'extra')), identity), + result.messages + ); +} + +function joinElements(first, second) { + return _.flatten([first, second]); +} + +function identity(value) { + return value; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-extended-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-extended-reader.js new file mode 100644 index 0000000000..a852d82e3c --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-extended-reader.js @@ -0,0 +1,23 @@ +var documents = require('../documents'); +var Result = require('../results').Result; + +function createCommentsExtendedReader(bodyReader) { + function readCommentsExtendedXml(element) { + var mappings = {}; + element.children.forEach((child) => { + if (child.name === 'w15:commentEx') { + var paraId = child.attributes['w15:paraId']; + var parentParaId = child.attributes['w15:paraIdParent']; + var done = child.attributes['w15:done']; + if (paraId && parentParaId) { + mappings[paraId] = parentParaId; + } + } + }); + return new Result(mappings); + } + + return readCommentsExtendedXml; +} + +exports.createCommentsExtendedReader = createCommentsExtendedReader; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-reader.js new file mode 100644 index 0000000000..af07c84333 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/comments-reader.js @@ -0,0 +1,53 @@ +var documents = require('../documents'); +var Result = require('../results').Result; + +function createCommentsReader(bodyReader, commentsExtended, dateUtcMap) { + commentsExtended = commentsExtended || {}; + dateUtcMap = dateUtcMap || {}; + + function readCommentsXml(element) { + return Result.combine( + element.getElementsByTagName('w:comment').map(readCommentElement) + ); + } + + function readCommentElement(element) { + var id = element.attributes['w:id']; + + function readOptionalAttribute(name) { + return (element.attributes[name] || '').trim() || null; + } + + return bodyReader.readXmlElements(element.children).map((body) => { + var paraId = null; + if (body) { + for (var i = 0; i < body.length; i++) { + if (body[i].paraId) { + paraId = body[i].paraId; + break; + } + } + } + var parentParaId = paraId ? commentsExtended[paraId] : null; + + // Prefer dateUtc (real UTC from commentsExtensible.xml) over + // w:date (local time with fake Z, Word convention) + var dateFromXml = readOptionalAttribute('w:date'); + var resolvedDate = (paraId && dateUtcMap[paraId]) || dateFromXml; + + return documents.comment({ + commentId: id, + body, + authorName: readOptionalAttribute('w:author'), + authorInitials: readOptionalAttribute('w:initials'), + date: resolvedDate, + paraId, + parentParaId, + }); + }); + } + + return readCommentsXml; +} + +exports.createCommentsReader = createCommentsReader; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/content-types-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/content-types-reader.js new file mode 100644 index 0000000000..319694442a --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/content-types-reader.js @@ -0,0 +1,70 @@ +exports.readContentTypesFromXml = readContentTypesFromXml; + +var fallbackContentTypes = { + png: 'png', + gif: 'gif', + jpeg: 'jpeg', + jpg: 'jpeg', + tif: 'tiff', + tiff: 'tiff', + bmp: 'bmp', +}; + +exports.defaultContentTypes = contentTypes({}, {}); + +function readContentTypesFromXml(element) { + var extensionDefaults = {}; + var overrides = {}; + + if (!element || !element.children) { + return contentTypes(overrides, extensionDefaults); + } + + element.children.forEach((child) => { + if (!child || !child.attributes) return; + + if (child.name === 'content-types:Default') { + extensionDefaults[child.attributes.Extension] = + child.attributes.ContentType; + } + if (child.name === 'content-types:Override') { + var name = child.attributes.PartName; + if (name && name.charAt(0) === '/') { + name = name.substring(1); + } + if (name) { + overrides[name] = child.attributes.ContentType; + } + } + }); + return contentTypes(overrides, extensionDefaults); +} + +function contentTypes(overrides, extensionDefaults) { + return { + findContentType(path) { + if (!path) return null; + + var overrideContentType = overrides[path]; + if (overrideContentType) { + return overrideContentType; + } + var pathParts = path.split('.'); + var extension = pathParts[pathParts.length - 1]; + var extensionLower = extension.toLowerCase(); + if ( + Object.hasOwn(extensionDefaults, extension) || + Object.hasOwn(extensionDefaults, extensionLower) + ) { + return ( + extensionDefaults[extension] || extensionDefaults[extensionLower] + ); + } + var fallback = fallbackContentTypes[extensionLower]; + if (fallback) { + return 'image/' + fallback; + } + return null; + }, + }; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/document-xml-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/document-xml-reader.js new file mode 100644 index 0000000000..6fd01356dc --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/document-xml-reader.js @@ -0,0 +1,31 @@ +exports.DocumentXmlReader = DocumentXmlReader; + +var documents = require('../documents'); +var Result = require('../results').Result; + +function DocumentXmlReader(options) { + var bodyReader = options.bodyReader; + + function convertXmlToDocument(element) { + var body = element.first('w:body'); + + if (body == null) { + throw new Error( + 'Could not find the body element: are you sure this is a docx file?' + ); + } + + var result = bodyReader.readXmlElements(body.children).map( + (children) => + new documents.Document(children, { + notes: options.notes, + comments: options.comments, + }) + ); + return new Result(result.value, result.messages); + } + + return { + convertXmlToDocument, + }; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/docx-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/docx-reader.js new file mode 100644 index 0000000000..f948be38f1 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/docx-reader.js @@ -0,0 +1,310 @@ +exports.read = read; +exports._findPartPaths = findPartPaths; + +var promises = require('../promises'); +var documents = require('../documents'); +var Result = require('../results').Result; +var zipfile = require('../zipfile'); + +var readXmlFromZipFile = require('./office-xml-reader').readXmlFromZipFile; +var createBodyReader = require('./body-reader').createBodyReader; +var DocumentXmlReader = require('./document-xml-reader').DocumentXmlReader; +var relationshipsReader = require('./relationships-reader'); +var contentTypesReader = require('./content-types-reader'); +var numberingXml = require('./numbering-xml'); +var stylesReader = require('./styles-reader'); +var notesReader = require('./notes-reader'); +var commentsReader = require('./comments-reader'); +var commentsExtendedReader = require('./comments-extended-reader'); +var Files = require('./files').Files; + +function read(docxFile, input, options) { + input = input || {}; + options = options || {}; + + var files = new Files({ + externalFileAccess: options.externalFileAccess, + relativeToFile: input.path, + }); + + return promises + .props({ + contentTypes: readContentTypesFromZipFile(docxFile), + partPaths: findPartPaths(docxFile), + docxFile, + files, + }) + .also((result) => ({ + styles: readStylesFromZipFile(docxFile, result.partPaths.styles), + })) + .also((result) => ({ + numbering: readNumberingFromZipFile( + docxFile, + result.partPaths.numbering, + result.styles + ), + })) + .also((result) => ({ + commentsExtended: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsExtended + ).then((xml) => { + if (xml) { + return commentsExtendedReader.createCommentsExtendedReader()(xml); + } + return new Result({}); + }), + // Read commentsIds.xml (paraId → durableId) and + // commentsExtensible.xml (durableId → dateUtc) to build + // a paraId → dateUtc map for correcting Word's fake-Z dates. + dateUtcMap: promises + .props({ + idsXml: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsIds || 'word/commentsIds.xml' + ), + extXml: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsExtensible || 'word/commentsExtensible.xml' + ), + }) + .then((r) => { + var paraIdToDurable = {}; + if (r.idsXml) { + r.idsXml.children.forEach((child) => { + if (child.name === 'w16cid:commentId') { + var pid = child.attributes['w16cid:paraId']; + var did = child.attributes['w16cid:durableId']; + if (pid && did) paraIdToDurable[pid] = did; + } + }); + } + var durableToDateUtc = {}; + if (r.extXml) { + r.extXml.children.forEach((child) => { + if (child.name === 'w16cex:commentExtensible') { + var did = child.attributes['w16cex:durableId']; + var utc = child.attributes['w16cex:dateUtc']; + if (did && utc) durableToDateUtc[did] = utc; + } + }); + } + // Combine: paraId → durableId → dateUtc + var map = {}; + Object.keys(paraIdToDurable).forEach((pid) => { + var did = paraIdToDurable[pid]; + if (durableToDateUtc[did]) { + map[pid] = durableToDateUtc[did]; + } + }); + return new Result(map); + }), + })) + .also((result) => ({ + footnotes: readXmlFileWithBody( + result.partPaths.footnotes, + result, + (bodyReader, xml) => { + if (xml) { + return notesReader.createFootnotesReader(bodyReader)(xml); + } + return new Result([]); + } + ), + endnotes: readXmlFileWithBody( + result.partPaths.endnotes, + result, + (bodyReader, xml) => { + if (xml) { + return notesReader.createEndnotesReader(bodyReader)(xml); + } + return new Result([]); + } + ), + comments: readXmlFileWithBody( + result.partPaths.comments, + result, + (bodyReader, xml) => { + if (xml) { + return commentsReader.createCommentsReader( + bodyReader, + result.commentsExtended.value || {}, + result.dateUtcMap.value || {} + )(xml); + } + return new Result([]); + } + ), + })) + .also((result) => ({ + notes: result.footnotes.flatMap((footnotes) => + result.endnotes.map( + (endnotes) => new documents.Notes(footnotes.concat(endnotes)) + ) + ), + })) + .then((result) => + readXmlFileWithBody( + result.partPaths.mainDocument, + result, + (bodyReader, xml) => + result.notes.flatMap((notes) => + result.comments.flatMap((comments) => { + var reader = new DocumentXmlReader({ + bodyReader, + notes, + comments, + }); + return reader.convertXmlToDocument(xml); + }) + ) + ) + ); +} + +function findPartPaths(docxFile) { + return readPackageRelationships(docxFile).then((packageRelationships) => { + var mainDocumentPath = findPartPath({ + docxFile, + relationships: packageRelationships, + relationshipType: + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', + basePath: '', + fallbackPath: 'word/document.xml', + }); + + if (!docxFile.exists(mainDocumentPath)) { + throw new Error( + 'Could not find main document part. Are you sure this is a valid .docx file?' + ); + } + + return xmlFileReader({ + filename: relationshipsFilename(mainDocumentPath), + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, + })(docxFile).then((documentRelationships) => { + function findPartRelatedToMainDocument(name) { + return findPartPath({ + docxFile, + relationships: documentRelationships, + relationshipType: + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/' + + name, + basePath: zipfile.splitPath(mainDocumentPath).dirname, + fallbackPath: 'word/' + name + '.xml', + }); + } + + return { + mainDocument: mainDocumentPath, + comments: findPartRelatedToMainDocument('comments'), + commentsExtended: findPartPath({ + docxFile, + relationships: documentRelationships, + relationshipType: + 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended', + basePath: zipfile.splitPath(mainDocumentPath).dirname, + fallbackPath: 'word/commentsExtended.xml', + }), + endnotes: findPartRelatedToMainDocument('endnotes'), + footnotes: findPartRelatedToMainDocument('footnotes'), + numbering: findPartRelatedToMainDocument('numbering'), + styles: findPartRelatedToMainDocument('styles'), + }; + }); + }); +} + +function findPartPath(options) { + var docxFile = options.docxFile; + var relationships = options.relationships; + var relationshipType = options.relationshipType; + var basePath = options.basePath; + var fallbackPath = options.fallbackPath; + + var targets = relationships.findTargetsByType(relationshipType); + var normalisedTargets = targets.map((target) => + stripPrefix(zipfile.joinPath(basePath, target), '/') + ); + var validTargets = normalisedTargets.filter((target) => + docxFile.exists(target) + ); + if (validTargets.length === 0) { + return fallbackPath; + } + return validTargets[0]; +} + +function stripPrefix(value, prefix) { + if (value.substring(0, prefix.length) === prefix) { + return value.substring(prefix.length); + } + return value; +} + +function xmlFileReader(options) { + return (zipFile) => + readXmlFromZipFile(zipFile, options.filename).then((element) => + element ? options.readElement(element) : options.defaultValue + ); +} + +function readXmlFileWithBody(filename, options, func) { + var readRelationshipsFromZipFile = xmlFileReader({ + filename: relationshipsFilename(filename), + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, + }); + + return readRelationshipsFromZipFile(options.docxFile).then( + (relationships) => { + var bodyReader = new createBodyReader({ + relationships, + contentTypes: options.contentTypes, + docxFile: options.docxFile, + numbering: options.numbering, + styles: options.styles, + files: options.files, + }); + return readXmlFromZipFile(options.docxFile, filename).then((xml) => + func(bodyReader, xml) + ); + } + ); +} + +function relationshipsFilename(filename) { + var split = zipfile.splitPath(filename); + return zipfile.joinPath(split.dirname, '_rels', split.basename + '.rels'); +} + +var readContentTypesFromZipFile = xmlFileReader({ + filename: '[Content_Types].xml', + readElement: contentTypesReader.readContentTypesFromXml, + defaultValue: contentTypesReader.defaultContentTypes, +}); + +function readNumberingFromZipFile(zipFile, path, styles) { + return xmlFileReader({ + filename: path, + readElement(element) { + return numberingXml.readNumberingXml(element, { styles }); + }, + defaultValue: numberingXml.defaultNumbering, + })(zipFile); +} + +function readStylesFromZipFile(zipFile, path) { + return xmlFileReader({ + filename: path, + readElement: stylesReader.readStylesXml, + defaultValue: stylesReader.defaultStyles, + })(zipFile); +} + +var readPackageRelationships = xmlFileReader({ + filename: '_rels/.rels', + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, +}); diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/files.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/files.js new file mode 100644 index 0000000000..199b8e3951 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/files.js @@ -0,0 +1,107 @@ +var fs = require('fs'); +var os = require('os'); +var dirname = require('path').dirname; +var resolvePath = require('path').resolve; +var isAbsolutePath = require('path-is-absolute'); + +var promises = require('../promises'); + +exports.Files = Files; +exports.uriToPath = uriToPath; + +function Files(options) { + options = options || {}; + if (!options.externalFileAccess) { + return { + read(uri) { + return promises.reject( + new Error( + "could not read external image '" + + uri + + "', external file access is disabled" + ) + ); + }, + }; + } + + var base = options.relativeToFile ? dirname(options.relativeToFile) : null; + + function read(uri, encoding) { + return resolveUri(uri).then((path) => + readFile(path, encoding).caught((error) => { + var message = + "could not open external image: '" + + uri + + "' (document directory: '" + + base + + "')\n" + + error.message; + return promises.reject(new Error(message)); + }) + ); + } + + function resolveUri(uri) { + var path = uriToPath(uri); + if (isAbsolutePath(path)) { + return promises.resolve(path); + } + if (base) { + var resolved = resolvePath(base, path); + // Prevent path traversal attacks + if (!resolved.startsWith(base)) { + return promises.reject( + new Error("path traversal detected in external image: '" + uri + "'") + ); + } + return promises.resolve(resolved); + } + return promises.reject( + new Error( + "could not find external image '" + + uri + + "', path of input document is unknown" + ) + ); + } + + return { + read, + }; +} + +var readFile = promises.promisify(fs.readFile.bind(fs)); + +function uriToPath(uriString, platform) { + if (!platform) { + platform = os.platform(); + } + + // Use URL API with a dummy base for relative URIs + var uri; + try { + uri = new URL(uriString, 'file://localhost/'); + } catch (e) { + // Fallback for malformed URIs - treat as relative path + return decodeURIComponent(uriString); + } + + if (isLocalFileUri(uri) || isRelativeUri(uriString, uri)) { + var path = decodeURIComponent(uri.pathname); + if (platform === 'win32' && /^\/[a-z]:/i.test(path)) { + return path.slice(1); + } + return path; + } + throw new Error('Could not convert URI to path: ' + uriString); +} + +function isLocalFileUri(uri) { + return uri.protocol === 'file:' && (!uri.host || uri.host === 'localhost'); +} + +function isRelativeUri(uriString, uri) { + // Check if original string had no protocol (relative URI) + return !uriString.includes('://') && uri.protocol === 'file:'; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/notes-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/notes-reader.js new file mode 100644 index 0000000000..9425d4c188 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/notes-reader.js @@ -0,0 +1,30 @@ +var documents = require('../documents'); +var Result = require('../results').Result; + +exports.createFootnotesReader = createReader.bind(this, 'footnote'); +exports.createEndnotesReader = createReader.bind(this, 'endnote'); + +function createReader(noteType, bodyReader) { + function readNotesXml(element) { + return Result.combine( + element + .getElementsByTagName('w:' + noteType) + .filter(isFootnoteElement) + .map(readFootnoteElement) + ); + } + + function isFootnoteElement(element) { + var type = element.attributes['w:type']; + return type !== 'continuationSeparator' && type !== 'separator'; + } + + function readFootnoteElement(footnoteElement) { + var id = footnoteElement.attributes['w:id']; + return bodyReader + .readXmlElements(footnoteElement.children) + .map((body) => documents.Note({ noteType, noteId: id, body })); + } + + return readNotesXml; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/numbering-xml.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/numbering-xml.js new file mode 100644 index 0000000000..539d9fbdd6 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/numbering-xml.js @@ -0,0 +1,120 @@ +var _ = require('underscore'); + +exports.readNumberingXml = readNumberingXml; +exports.Numbering = Numbering; +exports.defaultNumbering = new Numbering( + {}, + {}, + { + findNumberingStyleById() { + return null; + }, + } +); + +function Numbering(nums, abstractNums, styles) { + var allLevels = _.flatten( + _.values(abstractNums).map((abstractNum) => _.values(abstractNum.levels)) + ); + + var levelsByParagraphStyleId = _.indexBy( + allLevels.filter((level) => level.paragraphStyleId != null), + 'paragraphStyleId' + ); + + function findLevel(numId, level) { + var num = nums[numId]; + if (num) { + var abstractNum = abstractNums[num.abstractNumId]; + if (!abstractNum) { + return null; + } + if (abstractNum.numStyleLink == null) { + return abstractNums[num.abstractNumId].levels[level]; + } + var style = styles.findNumberingStyleById(abstractNum.numStyleLink); + return findLevel(style.numId, level); + } + return null; + } + + function findLevelByParagraphStyleId(styleId) { + return levelsByParagraphStyleId[styleId] || null; + } + + return { + findLevel, + findLevelByParagraphStyleId, + }; +} + +function readNumberingXml(root, options) { + if (!options || !options.styles) { + throw new Error('styles is missing'); + } + + var abstractNums = readAbstractNums(root); + var nums = readNums(root, abstractNums); + return new Numbering(nums, abstractNums, options.styles); +} + +function readAbstractNums(root) { + var abstractNums = {}; + root.getElementsByTagName('w:abstractNum').forEach((element) => { + var id = element.attributes['w:abstractNumId']; + abstractNums[id] = readAbstractNum(element); + }); + return abstractNums; +} + +function readAbstractNum(element) { + var levels = {}; + + // Some malformed documents define numbering levels without an index, and + // reference the numbering using a w:numPr element without a w:ilvl child. + // To handle such cases, we assume a level of 0 as a fallback. + var levelWithoutIndex = null; + + element.getElementsByTagName('w:lvl').forEach((levelElement) => { + var levelIndex = levelElement.attributes['w:ilvl']; + var numFmt = levelElement.firstOrEmpty('w:numFmt').attributes['w:val']; + var isOrdered = numFmt !== 'bullet'; + var paragraphStyleId = + levelElement.firstOrEmpty('w:pStyle').attributes['w:val']; + + if (levelIndex === undefined) { + levelWithoutIndex = { + isOrdered, + level: '0', + paragraphStyleId, + }; + } else { + levels[levelIndex] = { + isOrdered, + level: levelIndex, + paragraphStyleId, + }; + } + }); + + if ( + levelWithoutIndex !== null && + levels[levelWithoutIndex.level] === undefined + ) { + levels[levelWithoutIndex.level] = levelWithoutIndex; + } + + var numStyleLink = element.firstOrEmpty('w:numStyleLink').attributes['w:val']; + + return { levels, numStyleLink }; +} + +function readNums(root) { + var nums = {}; + root.getElementsByTagName('w:num').forEach((element) => { + var numId = element.attributes['w:numId']; + var abstractNumId = element.first('w:abstractNumId').attributes['w:val']; + nums[numId] = { abstractNumId }; + }); + return nums; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/office-xml-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/office-xml-reader.js new file mode 100644 index 0000000000..f93e7c1412 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/office-xml-reader.js @@ -0,0 +1,76 @@ +var _ = require('underscore'); + +var promises = require('../promises'); +var xml = require('../xml'); + +exports.read = read; +exports.readXmlFromZipFile = readXmlFromZipFile; + +var xmlNamespaceMap = { + // Transitional format + 'http://schemas.openxmlformats.org/wordprocessingml/2006/main': 'w', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships': 'r', + 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing': + 'wp', + 'http://schemas.openxmlformats.org/drawingml/2006/main': 'a', + 'http://schemas.openxmlformats.org/drawingml/2006/picture': 'pic', + + // Strict format + 'http://purl.oclc.org/ooxml/wordprocessingml/main': 'w', + 'http://purl.oclc.org/ooxml/officeDocument/relationships': 'r', + 'http://purl.oclc.org/ooxml/drawingml/wordprocessingDrawing': 'wp', + 'http://purl.oclc.org/ooxml/drawingml/main': 'a', + 'http://purl.oclc.org/ooxml/drawingml/picture': 'pic', + + // Common + 'http://schemas.openxmlformats.org/package/2006/content-types': + 'content-types', + 'http://schemas.openxmlformats.org/package/2006/relationships': + 'relationships', + 'http://schemas.openxmlformats.org/markup-compatibility/2006': 'mc', + 'urn:schemas-microsoft-com:vml': 'v', + 'urn:schemas-microsoft-com:office:word': 'office-word', + + // [MS-DOCX]: Word Extensions to the Office Open XML (.docx) File Format + // https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/b839fe1f-e1ca-4fa6-8c26-5954d0abbccd + 'http://schemas.microsoft.com/office/word/2010/wordml': 'wordml', + + // Word 2012 extensions (comments threading via commentsExtended.xml) + 'http://schemas.microsoft.com/office/word/2012/wordml': 'w15', + + // Word 2016 extensions (commentsIds.xml — durable IDs) + 'http://schemas.microsoft.com/office/word/2016/wordml/cid': 'w16cid', + + // Word 2018 extensions (commentsExtensible.xml — dateUtc) + 'http://schemas.microsoft.com/office/word/2018/wordml/cex': 'w16cex', +}; + +function read(xmlString) { + return xml + .readString(xmlString, xmlNamespaceMap) + .then((document) => collapseAlternateContent(document)[0]); +} + +function readXmlFromZipFile(docxFile, path) { + if (docxFile.exists(path)) { + return docxFile.read(path, 'utf-8').then(stripUtf8Bom).then(read); + } + return promises.resolve(null); +} + +function stripUtf8Bom(xmlString) { + return xmlString.replace(/^\uFEFF/g, ''); +} + +function collapseAlternateContent(node) { + if (node.type === 'element') { + if (node.name === 'mc:AlternateContent') { + return node.firstOrEmpty('mc:Fallback').children; + } + node.children = _.flatten( + node.children.map(collapseAlternateContent, true) + ); + return [node]; + } + return [node]; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/relationships-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/relationships-reader.js new file mode 100644 index 0000000000..307dd368b9 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/relationships-reader.js @@ -0,0 +1,42 @@ +exports.readRelationships = readRelationships; +exports.defaultValue = new Relationships([]); +exports.Relationships = Relationships; + +function readRelationships(element) { + var relationships = []; + element.children.forEach((child) => { + if (child.name === 'relationships:Relationship') { + var relationship = { + relationshipId: child.attributes.Id, + target: child.attributes.Target, + type: child.attributes.Type, + }; + relationships.push(relationship); + } + }); + return new Relationships(relationships); +} + +function Relationships(relationships) { + var targetsByRelationshipId = {}; + relationships.forEach((relationship) => { + targetsByRelationshipId[relationship.relationshipId] = relationship.target; + }); + + var targetsByType = {}; + relationships.forEach((relationship) => { + if (!targetsByType[relationship.type]) { + targetsByType[relationship.type] = []; + } + targetsByType[relationship.type].push(relationship.target); + }); + + return { + findTargetByRelationshipId(relationshipId) { + return targetsByRelationshipId[relationshipId]; + }, + findTargetsByType(type) { + return targetsByType[type] || []; + }, + }; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/style-map.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/style-map.js new file mode 100644 index 0000000000..7c556715f9 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/style-map.js @@ -0,0 +1,81 @@ +var _ = require('underscore'); + +var promises = require('../promises'); +var xml = require('../xml'); + +exports.writeStyleMap = writeStyleMap; +exports.readStyleMap = readStyleMap; + +var schema = 'http://schemas.zwobble.org/mammoth/style-map'; +var styleMapPath = 'mammoth/style-map'; +var styleMapAbsolutePath = '/' + styleMapPath; + +function writeStyleMap(docxFile, styleMap) { + docxFile.write(styleMapPath, styleMap); + return updateRelationships(docxFile).then(() => updateContentTypes(docxFile)); +} + +function updateRelationships(docxFile) { + var path = 'word/_rels/document.xml.rels'; + var relationshipsUri = + 'http://schemas.openxmlformats.org/package/2006/relationships'; + var relationshipElementName = '{' + relationshipsUri + '}Relationship'; + return docxFile + .read(path, 'utf8') + .then(xml.readString) + .then((relationshipsContainer) => { + var relationships = relationshipsContainer.children; + addOrUpdateElement(relationships, relationshipElementName, 'Id', { + Id: 'rMammothStyleMap', + Type: schema, + Target: styleMapAbsolutePath, + }); + + var namespaces = { '': relationshipsUri }; + return docxFile.write( + path, + xml.writeString(relationshipsContainer, namespaces) + ); + }); +} + +function updateContentTypes(docxFile) { + var path = '[Content_Types].xml'; + var contentTypesUri = + 'http://schemas.openxmlformats.org/package/2006/content-types'; + var overrideName = '{' + contentTypesUri + '}Override'; + return docxFile + .read(path, 'utf8') + .then(xml.readString) + .then((typesElement) => { + var children = typesElement.children; + addOrUpdateElement(children, overrideName, 'PartName', { + PartName: styleMapAbsolutePath, + ContentType: 'text/prs.mammoth.style-map', + }); + var namespaces = { '': contentTypesUri }; + return docxFile.write(path, xml.writeString(typesElement, namespaces)); + }); +} + +function addOrUpdateElement(elements, name, identifyingAttribute, attributes) { + var existingElement = _.find( + elements, + (element) => + element.name === name && + element.attributes[identifyingAttribute] === + attributes[identifyingAttribute] + ); + if (existingElement) { + existingElement.attributes = attributes; + } else { + elements.push(xml.element(name, attributes)); + } +} + +function readStyleMap(docxFile) { + if (docxFile.exists(styleMapPath)) { + return docxFile.read(styleMapPath, 'utf8'); + } + return promises.resolve(null); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/styles-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/styles-reader.js new file mode 100644 index 0000000000..003bdf4fdc --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/styles-reader.js @@ -0,0 +1,98 @@ +exports.readStylesXml = readStylesXml; +exports.Styles = Styles; +exports.defaultStyles = new Styles({}, {}, {}, {}); + +function Styles( + paragraphStyles, + characterStyles, + tableStyles, + numberingStyles +) { + return { + findParagraphStyleById(styleId) { + return paragraphStyles[styleId]; + }, + findCharacterStyleById(styleId) { + return characterStyles[styleId]; + }, + findTableStyleById(styleId) { + return tableStyles[styleId]; + }, + findNumberingStyleById(styleId) { + return numberingStyles[styleId]; + }, + }; +} + +Styles.EMPTY = new Styles({}, {}, {}, {}); + +function readStylesXml(root) { + var paragraphStyles = {}; + var characterStyles = {}; + var tableStyles = {}; + var numberingStyles = {}; + + var styles = { + paragraph: paragraphStyles, + character: characterStyles, + table: tableStyles, + numbering: numberingStyles, + }; + + root.getElementsByTagName('w:style').forEach((styleElement) => { + var style = readStyleElement(styleElement); + var styleSet = styles[style.type]; + + // Per 17.7.4.17 style (Style Definition) of ECMA-376 4th edition Part 1: + // + // > If multiple style definitions each declare the same value for their + // > styleId, then the first such instance shall keep its current + // > identifier with all other instances being reassigned in any manner + // > desired. + // + // For the purpose of conversion, there's no point holding onto styles + // with reassigned style IDs, so we ignore such style definitions. + + if (styleSet && styleSet[style.styleId] === undefined) { + styleSet[style.styleId] = style; + } + }); + + return new Styles( + paragraphStyles, + characterStyles, + tableStyles, + numberingStyles + ); +} + +function readStyleElement(styleElement) { + var type = styleElement.attributes['w:type']; + + if (type === 'numbering') { + return readNumberingStyleElement(type, styleElement); + } + var styleId = readStyleId(styleElement); + var name = styleName(styleElement); + return { type, styleId, name }; +} + +function styleName(styleElement) { + var nameElement = styleElement.first('w:name'); + return nameElement ? nameElement.attributes['w:val'] : null; +} + +function readNumberingStyleElement(type, styleElement) { + var styleId = readStyleId(styleElement); + + var numId = styleElement + .firstOrEmpty('w:pPr') + .firstOrEmpty('w:numPr') + .firstOrEmpty('w:numId').attributes['w:val']; + + return { type, numId, styleId }; +} + +function readStyleId(styleElement) { + return styleElement.attributes['w:styleId']; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/docx/uris.js b/packages/docx-io/src/lib/mammoth.js/lib/docx/uris.js new file mode 100644 index 0000000000..718e06d2f6 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/docx/uris.js @@ -0,0 +1,19 @@ +exports.uriToZipEntryName = uriToZipEntryName; +exports.replaceFragment = replaceFragment; + +function uriToZipEntryName(base, uri) { + if (uri.charAt(0) === '/') { + return uri.substring(1); + } + // In general, we should check first and second for trailing and leading slashes, + // but in our specific case this seems to be sufficient + return base + '/' + uri; +} + +function replaceFragment(uri, fragment) { + var hashIndex = uri.indexOf('#'); + if (hashIndex !== -1) { + uri = uri.substring(0, hashIndex); + } + return uri + '#' + fragment; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/html/ast.js b/packages/docx-io/src/lib/mammoth.js/lib/html/ast.js new file mode 100644 index 0000000000..adb2180a82 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/html/ast.js @@ -0,0 +1,54 @@ +var htmlPaths = require('../styles/html-paths'); + +function nonFreshElement(tagName, attributes, children) { + return elementWithTag( + htmlPaths.element(tagName, attributes, { fresh: false }), + children + ); +} + +function freshElement(tagName, attributes, children) { + var tag = htmlPaths.element(tagName, attributes, { fresh: true }); + return elementWithTag(tag, children); +} + +function elementWithTag(tag, children) { + return { + type: 'element', + tag, + children: children || [], + }; +} + +function text(value) { + return { + type: 'text', + value, + }; +} + +var forceWrite = { + type: 'forceWrite', +}; + +exports.freshElement = freshElement; +exports.nonFreshElement = nonFreshElement; +exports.elementWithTag = elementWithTag; +exports.text = text; +exports.forceWrite = forceWrite; + +var voidTagNames = { + br: true, + hr: true, + img: true, + input: true, +}; + +function isVoidElement(node) { + return ( + (!node.children || node.children.length === 0) && + voidTagNames[node.tag.tagName] + ); +} + +exports.isVoidElement = isVoidElement; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/html/index.js b/packages/docx-io/src/lib/mammoth.js/lib/html/index.js new file mode 100644 index 0000000000..7b52863d58 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/html/index.js @@ -0,0 +1,41 @@ +var ast = require('./ast'); + +exports.freshElement = ast.freshElement; +exports.nonFreshElement = ast.nonFreshElement; +exports.elementWithTag = ast.elementWithTag; +exports.text = ast.text; +exports.forceWrite = ast.forceWrite; + +exports.simplify = require('./simplify'); + +function write(writer, nodes) { + nodes.forEach((node) => { + writeNode(writer, node); + }); +} + +function writeNode(writer, node) { + toStrings[node.type](writer, node); +} + +var toStrings = { + element: generateElementString, + text: generateTextString, + forceWrite() {}, +}; + +function generateElementString(writer, node) { + if (ast.isVoidElement(node)) { + writer.selfClosing(node.tag.tagName, node.tag.attributes); + } else { + writer.open(node.tag.tagName, node.tag.attributes); + write(writer, node.children); + writer.close(node.tag.tagName); + } +} + +function generateTextString(writer, node) { + writer.text(node.value); +} + +exports.write = write; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/html/simplify.js b/packages/docx-io/src/lib/mammoth.js/lib/html/simplify.js new file mode 100644 index 0000000000..2ab654d4dc --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/html/simplify.js @@ -0,0 +1,90 @@ +var _ = require('underscore'); + +var ast = require('./ast'); + +function simplify(nodes) { + return collapse(removeEmpty(nodes)); +} + +function collapse(nodes) { + var children = []; + + nodes.map(collapseNode).forEach((child) => { + appendChild(children, child); + }); + return children; +} + +function collapseNode(node) { + return collapsers[node.type](node); +} + +var collapsers = { + element: collapseElement, + text: identity, + forceWrite: identity, +}; + +function collapseElement(node) { + return ast.elementWithTag(node.tag, collapse(node.children)); +} + +function identity(value) { + return value; +} + +function appendChild(children, child) { + var lastChild = children[children.length - 1]; + if ( + child.type === 'element' && + !child.tag.fresh && + lastChild && + lastChild.type === 'element' && + child.tag.matchesElement(lastChild.tag) + ) { + if (child.tag.separator) { + appendChild(lastChild.children, ast.text(child.tag.separator)); + } + child.children.forEach((grandChild) => { + // Mutation is fine since simplifying elements create a copy of the children. + appendChild(lastChild.children, grandChild); + }); + } else { + children.push(child); + } +} + +function removeEmpty(nodes) { + return flatMap(nodes, (node) => emptiers[node.type](node)); +} + +function flatMap(values, func) { + return _.flatten(_.map(values, func), true); +} + +var emptiers = { + element: elementEmptier, + text: textEmptier, + forceWrite: neverEmpty, +}; + +function neverEmpty(node) { + return [node]; +} + +function elementEmptier(element) { + var children = removeEmpty(element.children); + if (children.length === 0 && !ast.isVoidElement(element)) { + return []; + } + return [ast.elementWithTag(element.tag, children)]; +} + +function textEmptier(node) { + if (node.value.length === 0) { + return []; + } + return [node]; +} + +module.exports = simplify; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/images.js b/packages/docx-io/src/lib/mammoth.js/lib/images.js new file mode 100644 index 0000000000..483fc03b5a --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/images.js @@ -0,0 +1,31 @@ +var _ = require('underscore'); + +var promises = require('./promises'); +var Html = require('./html'); + +exports.imgElement = imgElement; + +function imgElement(func) { + return (element, messages) => + promises.when(func(element)).then((result) => { + var attributes = {}; + if (element.altText) { + attributes.alt = element.altText; + } + _.extend(attributes, result); + + return [Html.freshElement('img', attributes)]; + }); +} + +// Undocumented, but retained for backwards-compatibility with 0.3.x +exports.inline = exports.imgElement; + +exports.dataUri = imgElement((element) => + element.readAsBase64String().then((imageBuffer) => { + var contentType = element.contentType || 'application/octet-stream'; + return { + src: 'data:' + contentType + ';base64,' + imageBuffer, + }; + }) +); diff --git a/packages/docx-io/src/lib/mammoth.js/lib/index.d.ts b/packages/docx-io/src/lib/mammoth.js/lib/index.d.ts new file mode 100644 index 0000000000..b061ffca29 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/index.d.ts @@ -0,0 +1,89 @@ +interface Mammoth { + convertToHtml: (input: Input, options?: Options) => Promise<Result>; + extractRawText: (input: Input) => Promise<Result>; + embedStyleMap: ( + input: Input, + styleMap: string + ) => Promise<{ + toArrayBuffer: () => ArrayBuffer; + toBuffer: () => Buffer; + }>; + images: Images; +} + +type Input = NodeJsInput | BrowserInput; + +type NodeJsInput = PathInput | BufferInput; + +interface PathInput { + path: string; +} + +interface BufferInput { + buffer: Buffer; +} + +type BrowserInput = ArrayBufferInput; + +interface ArrayBufferInput { + arrayBuffer: ArrayBuffer; +} + +interface Options { + styleMap?: string | Array<string>; + includeEmbeddedStyleMap?: boolean; + includeDefaultStyleMap?: boolean; + convertImage?: ImageConverter; + ignoreEmptyParagraphs?: boolean; + idPrefix?: string; + externalFileAccess?: boolean; + transformDocument?: (element: any) => any; +} + +interface ImageConverter { + __mammothBrand: 'ImageConverter'; +} + +interface Image { + contentType: string; + readAsArrayBuffer: () => Promise<ArrayBuffer>; + readAsBase64String: () => Promise<string>; + readAsBuffer: () => Promise<Buffer>; + read: ImageRead; +} + +interface ImageRead { + (): Promise<Buffer>; + (encoding: string): Promise<string>; +} + +interface ImageAttributes { + src: string; +} + +interface Images { + dataUri: ImageConverter; + imgElement: (f: (image: Image) => Promise<ImageAttributes>) => ImageConverter; +} + +interface Result { + value: string; + messages: Array<Message>; +} + +type Message = Warning | Error; + +interface Warning { + type: 'warning'; + message: string; +} + +interface Error { + type: 'error'; + message: string; + error: unknown; +} + +declare const mammoth: Mammoth; + +export = mammoth; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/index.js b/packages/docx-io/src/lib/mammoth.js/lib/index.js new file mode 100644 index 0000000000..e3c7553b7d --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/index.js @@ -0,0 +1,102 @@ +var _ = require('underscore'); + +var docxReader = require('./docx/docx-reader'); +var docxStyleMap = require('./docx/style-map'); +var DocumentConverter = require('./document-to-html').DocumentConverter; +var convertElementToRawText = require('./raw-text').convertElementToRawText; +var readStyle = require('./style-reader').readStyle; +var readOptions = require('./options-reader').readOptions; +var unzip = require('./unzip'); +var Result = require('./results').Result; + +exports.convertToHtml = convertToHtml; +exports.convertToMarkdown = convertToMarkdown; +exports.convert = convert; +exports.extractRawText = extractRawText; +exports.images = require('./images'); +exports.transforms = require('./transforms'); +exports.underline = require('./underline'); +exports.embedStyleMap = embedStyleMap; +exports.readEmbeddedStyleMap = readEmbeddedStyleMap; + +function convertToHtml(input, options) { + return convert(input, options); +} + +function convertToMarkdown(input, options) { + var markdownOptions = Object.create(options || {}); + markdownOptions.outputFormat = 'markdown'; + return convert(input, markdownOptions); +} + +function convert(input, options) { + options = readOptions(options); + + return unzip + .openZip(input) + .tap((docxFile) => + docxStyleMap.readStyleMap(docxFile).then((styleMap) => { + options.embeddedStyleMap = styleMap; + }) + ) + .then((docxFile) => + docxReader + .read(docxFile, input, options) + .then((documentResult) => documentResult.map(options.transformDocument)) + .then((documentResult) => + convertDocumentToHtml(documentResult, options) + ) + ); +} + +function readEmbeddedStyleMap(input) { + return unzip.openZip(input).then(docxStyleMap.readStyleMap); +} + +function convertDocumentToHtml(documentResult, options) { + var styleMapResult = parseStyleMap(options.readStyleMap()); + var parsedOptions = _.extend({}, options, { + styleMap: styleMapResult.value, + }); + var documentConverter = new DocumentConverter(parsedOptions); + + return documentResult.flatMapThen((document) => + styleMapResult.flatMapThen((styleMap) => + documentConverter.convertToHtml(document) + ) + ); +} + +function parseStyleMap(styleMap) { + return Result.combine((styleMap || []).map(readStyle)).map((styleMap) => + styleMap.filter((styleMapping) => !!styleMapping) + ); +} + +function extractRawText(input) { + return unzip + .openZip(input) + .then(docxReader.read) + .then((documentResult) => documentResult.map(convertElementToRawText)); +} + +function embedStyleMap(input, styleMap) { + return unzip + .openZip(input) + .tap((docxFile) => docxStyleMap.writeStyleMap(docxFile, styleMap)) + .then((docxFile) => docxFile.toArrayBuffer()) + .then((arrayBuffer) => ({ + toArrayBuffer() { + return arrayBuffer; + }, + toBuffer() { + return Buffer.from(arrayBuffer); + }, + })); +} + +exports.styleMapping = () => { + throw new Error( + 'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")' + ); +}; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/index.ts b/packages/docx-io/src/lib/mammoth.js/lib/index.ts new file mode 100644 index 0000000000..22af434fae --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/index.ts @@ -0,0 +1,5 @@ +/** + * @file Automatically generated by barrelsby. + */ + +export * from './index.js'; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/main.js b/packages/docx-io/src/lib/mammoth.js/lib/main.js new file mode 100644 index 0000000000..2ffe501d92 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/main.js @@ -0,0 +1,78 @@ +/* global process */ + +var fs = require('fs'); +var path = require('path'); + +var mammoth = require('./'); +var promises = require('./promises'); +var images = require('./images'); + +function main(argv) { + var docxPath = argv['docx-path']; + var outputPath = argv['output-path']; + var outputDir = argv.output_dir; + var outputFormat = argv.output_format; + var styleMapPath = argv.style_map; + + readStyleMap(styleMapPath) + .then((styleMap) => { + var options = { + styleMap, + outputFormat, + }; + + if (outputDir) { + var basename = path.basename(docxPath, '.docx'); + outputPath = path.join(outputDir, basename + '.html'); + var imageIndex = 0; + options.convertImage = images.imgElement((element) => { + imageIndex++; + var contentTypeParts = (element.contentType || '').split('/'); + var extension = contentTypeParts[1] || 'bin'; + var filename = imageIndex + '.' + extension; + + return element + .read() + .then((imageBuffer) => { + var imagePath = path.join(outputDir, filename); + return promises.nfcall(fs.writeFile, imagePath, imageBuffer); + }) + .then(() => ({ src: filename })); + }); + } + + return mammoth.convert({ path: docxPath }, options).then((result) => { + result.messages.forEach((message) => { + process.stderr.write(message.message); + process.stderr.write('\n'); + }); + + var outputStream = outputPath + ? fs.createWriteStream(outputPath) + : process.stdout; + + return new Promise((resolve, reject) => { + outputStream.write(result.value, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + }); + }) + .then(() => { + process.exit(0); + }) + .catch((err) => { + process.stderr.write('Error: ' + err.message + '\n'); + process.exit(1); + }); +} + +function readStyleMap(styleMapPath) { + if (styleMapPath) { + return promises.nfcall(fs.readFile, styleMapPath, 'utf8'); + } + return promises.resolve(null); +} + +module.exports = main; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/options-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/options-reader.js new file mode 100644 index 0000000000..81482a53db --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/options-reader.js @@ -0,0 +1,103 @@ +exports.readOptions = readOptions; + +var _ = require('underscore'); + +var defaultStyleMap = (exports._defaultStyleMap = [ + 'p.Heading1 => h1:fresh', + 'p.Heading2 => h2:fresh', + 'p.Heading3 => h3:fresh', + 'p.Heading4 => h4:fresh', + 'p.Heading5 => h5:fresh', + 'p.Heading6 => h6:fresh', + "p[style-name='Heading 1'] => h1:fresh", + "p[style-name='Heading 2'] => h2:fresh", + "p[style-name='Heading 3'] => h3:fresh", + "p[style-name='Heading 4'] => h4:fresh", + "p[style-name='Heading 5'] => h5:fresh", + "p[style-name='Heading 6'] => h6:fresh", + "p[style-name='heading 1'] => h1:fresh", + "p[style-name='heading 2'] => h2:fresh", + "p[style-name='heading 3'] => h3:fresh", + "p[style-name='heading 4'] => h4:fresh", + "p[style-name='heading 5'] => h5:fresh", + "p[style-name='heading 6'] => h6:fresh", + + // Apple Pages + 'p.Heading => h1:fresh', + "p[style-name='Heading'] => h1:fresh", + + "r[style-name='Strong'] => strong", + + "p[style-name='footnote text'] => p:fresh", + "r[style-name='footnote reference'] =>", + "p[style-name='endnote text'] => p:fresh", + "r[style-name='endnote reference'] =>", + "p[style-name='annotation text'] => p:fresh", + "r[style-name='annotation reference'] =>", + + // LibreOffice + "p[style-name='Footnote'] => p:fresh", + "r[style-name='Footnote anchor'] =>", + "p[style-name='Endnote'] => p:fresh", + "r[style-name='Endnote anchor'] =>", + + 'p:unordered-list(1) => ul > li:fresh', + 'p:unordered-list(2) => ul|ol > li > ul > li:fresh', + 'p:unordered-list(3) => ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:unordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:unordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:ordered-list(1) => ol > li:fresh', + 'p:ordered-list(2) => ul|ol > li > ol > li:fresh', + 'p:ordered-list(3) => ul|ol > li > ul|ol > li > ol > li:fresh', + 'p:ordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh', + 'p:ordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh', + + "r[style-name='Hyperlink'] =>", + + "p[style-name='Normal'] => p:fresh", + + // Apple Pages + 'p.Body => p:fresh', + "p[style-name='Body'] => p:fresh", +]); + +var standardOptions = (exports._standardOptions = { + externalFileAccess: false, + transformDocument: identity, + includeDefaultStyleMap: true, + includeEmbeddedStyleMap: true, +}); + +function readOptions(options) { + options = options || {}; + return _.extend({}, standardOptions, options, { + customStyleMap: readStyleMap(options.styleMap), + readStyleMap() { + var styleMap = this.customStyleMap; + if (this.includeEmbeddedStyleMap) { + styleMap = styleMap.concat(readStyleMap(this.embeddedStyleMap)); + } + if (this.includeDefaultStyleMap) { + styleMap = styleMap.concat(defaultStyleMap); + } + return styleMap; + }, + }); +} + +function readStyleMap(styleMap) { + if (!styleMap) { + return []; + } + if (_.isString(styleMap)) { + return styleMap + .split('\n') + .map((line) => line.trim()) + .filter((line) => line !== '' && line.charAt(0) !== '#'); + } + return styleMap; +} + +function identity(value) { + return value; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/promises.js b/packages/docx-io/src/lib/mammoth.js/lib/promises.js new file mode 100644 index 0000000000..03f6e31535 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/promises.js @@ -0,0 +1,42 @@ +var _ = require('underscore'); +var bluebird = require('bluebird/js/release/promise')(); + +exports.defer = defer; +exports.when = bluebird.resolve; +exports.resolve = bluebird.resolve; +exports.all = bluebird.all; +exports.props = bluebird.props; +exports.reject = bluebird.reject; +exports.promisify = bluebird.promisify; +exports.mapSeries = bluebird.mapSeries; +exports.attempt = bluebird.attempt; + +exports.nfcall = function (func) { + var args = Array.prototype.slice.call(arguments, 1); + var promisedFunc = bluebird.promisify(func); + return promisedFunc.apply(null, args); +}; + +bluebird.prototype.fail = bluebird.prototype.caught; + +bluebird.prototype.also = function (func) { + return this.then((value) => { + var returnValue = _.extend({}, value, func(value)); + return bluebird.props(returnValue); + }); +}; + +function defer() { + var resolve; + var reject; + var promise = new bluebird.Promise((resolveArg, rejectArg) => { + resolve = resolveArg; + reject = rejectArg; + }); + + return { + resolve, + reject, + promise, + }; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/raw-text.js b/packages/docx-io/src/lib/mammoth.js/lib/raw-text.js new file mode 100644 index 0000000000..c4fe8b6647 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/raw-text.js @@ -0,0 +1,14 @@ +var documents = require('./documents'); + +function convertElementToRawText(element) { + if (element.type === 'text') { + return element.value; + } + if (element.type === documents.types.tab) { + return '\t'; + } + var tail = element.type === 'paragraph' ? '\n\n' : ''; + return (element.children || []).map(convertElementToRawText).join('') + tail; +} + +exports.convertElementToRawText = convertElementToRawText; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/results.js b/packages/docx-io/src/lib/mammoth.js/lib/results.js new file mode 100644 index 0000000000..a201acc3f7 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/results.js @@ -0,0 +1,70 @@ +var _ = require('underscore'); + +exports.Result = Result; +exports.success = success; +exports.warning = warning; +exports.error = error; + +function Result(value, messages) { + this.value = value; + this.messages = messages || []; +} + +Result.prototype.map = function (func) { + return new Result(func(this.value), this.messages); +}; + +Result.prototype.flatMap = function (func) { + var funcResult = func(this.value); + return new Result(funcResult.value, combineMessages([this, funcResult])); +}; + +Result.prototype.flatMapThen = function (func) { + return func(this.value).then( + (otherResult) => + new Result(otherResult.value, combineMessages([this, otherResult])) + ); +}; + +Result.combine = (results) => { + var values = _.flatten(_.pluck(results, 'value')); + var messages = combineMessages(results); + return new Result(values, messages); +}; + +function success(value) { + return new Result(value, []); +} + +function warning(message) { + return { + type: 'warning', + message, + }; +} + +function error(exception) { + return { + type: 'error', + message: exception.message, + error: exception, + }; +} + +function combineMessages(results) { + var messages = []; + _.flatten(_.pluck(results, 'messages'), true).forEach((message) => { + if (!containsMessage(messages, message)) { + messages.push(message); + } + }); + return messages; +} + +function containsMessage(messages, message) { + return _.find(messages, isSameMessage.bind(null, message)) !== undefined; +} + +function isSameMessage(first, second) { + return first.type === second.type && first.message === second.message; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/style-reader.js b/packages/docx-io/src/lib/mammoth.js/lib/style-reader.js new file mode 100644 index 0000000000..bd86ab1394 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/style-reader.js @@ -0,0 +1,405 @@ +var _ = require('underscore'); +var lop = require('lop'); + +var documentMatchers = require('./styles/document-matchers'); +var htmlPaths = require('./styles/html-paths'); +var tokenise = require('./styles/parser/tokeniser').tokenise; +var results = require('./results'); + +exports.readHtmlPath = readHtmlPath; +exports.readDocumentMatcher = readDocumentMatcher; +exports.readStyle = readStyle; + +function readStyle(string) { + return parseString(styleRule, string); +} + +function createStyleRule() { + return lop.rules + .sequence( + lop.rules.sequence.capture(documentMatcherRule()), + lop.rules.tokenOfType('whitespace'), + lop.rules.tokenOfType('arrow'), + lop.rules.sequence.capture( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('whitespace'), + lop.rules.sequence.capture(htmlPathRule()) + ) + .head() + ) + ), + lop.rules.tokenOfType('end') + ) + .map((documentMatcher, htmlPath) => ({ + from: documentMatcher, + to: htmlPath.valueOrElse(htmlPaths.empty), + })); +} + +function readDocumentMatcher(string) { + return parseString(documentMatcherRule(), string); +} + +function documentMatcherRule() { + var sequence = lop.rules.sequence; + + var identifierToConstant = (identifier, constant) => + lop.rules.then(lop.rules.token('identifier', identifier), () => constant); + + var paragraphRule = identifierToConstant('p', documentMatchers.paragraph); + var runRule = identifierToConstant('r', documentMatchers.run); + + var elementTypeRule = lop.rules.firstOf( + 'p or r or table', + paragraphRule, + runRule + ); + + var styleIdRule = lop.rules + .sequence( + lop.rules.tokenOfType('dot'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule) + ) + .map((styleId) => ({ styleId })); + + var styleNameMatcherRule = lop.rules.firstOf( + 'style name matcher', + lop.rules.then( + lop.rules + .sequence( + lop.rules.tokenOfType('equals'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(stringRule) + ) + .head(), + (styleName) => ({ styleName: documentMatchers.equalTo(styleName) }) + ), + lop.rules.then( + lop.rules + .sequence( + lop.rules.tokenOfType('startsWith'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(stringRule) + ) + .head(), + (styleName) => ({ styleName: documentMatchers.startsWith(styleName) }) + ) + ); + + var styleNameRule = lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.token('identifier', 'style-name'), + lop.rules.sequence.capture(styleNameMatcherRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .head(); + + var listTypeRule = lop.rules.firstOf( + 'list type', + identifierToConstant('ordered-list', { isOrdered: true }), + identifierToConstant('unordered-list', { isOrdered: false }) + ); + var listRule = sequence( + lop.rules.tokenOfType('colon'), + sequence.capture(listTypeRule), + sequence.cut(), + lop.rules.tokenOfType('open-paren'), + sequence.capture(integerRule), + lop.rules.tokenOfType('close-paren') + ).map((listType, levelNumber) => ({ + list: { + isOrdered: listType.isOrdered, + levelIndex: levelNumber - 1, + }, + })); + + function createMatcherSuffixesRule(rules) { + var matcherSuffix = lop.rules.firstOf.apply( + lop.rules.firstOf, + ['matcher suffix'].concat(rules) + ); + var matcherSuffixes = lop.rules.zeroOrMore(matcherSuffix); + return lop.rules.then(matcherSuffixes, (suffixes) => { + var matcherOptions = {}; + suffixes.forEach((suffix) => { + _.extend(matcherOptions, suffix); + }); + return matcherOptions; + }); + } + + var paragraphOrRun = sequence( + sequence.capture(elementTypeRule), + sequence.capture( + createMatcherSuffixesRule([styleIdRule, styleNameRule, listRule]) + ) + ).map((createMatcher, matcherOptions) => createMatcher(matcherOptions)); + + var table = sequence( + lop.rules.token('identifier', 'table'), + sequence.capture(createMatcherSuffixesRule([styleIdRule, styleNameRule])) + ).map((options) => documentMatchers.table(options)); + + var bold = identifierToConstant('b', documentMatchers.bold); + var italic = identifierToConstant('i', documentMatchers.italic); + var underline = identifierToConstant('u', documentMatchers.underline); + var strikethrough = identifierToConstant( + 'strike', + documentMatchers.strikethrough + ); + var allCaps = identifierToConstant('all-caps', documentMatchers.allCaps); + var smallCaps = identifierToConstant( + 'small-caps', + documentMatchers.smallCaps + ); + + var highlight = sequence( + lop.rules.token('identifier', 'highlight'), + lop.rules.sequence.capture( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.token('identifier', 'color'), + lop.rules.tokenOfType('equals'), + lop.rules.sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .head() + ) + ) + ).map((color) => + documentMatchers.highlight({ + color: color.valueOrElse(undefined), + }) + ); + + var commentReference = identifierToConstant( + 'comment-reference', + documentMatchers.commentReference + ); + + var commentRangeStart = identifierToConstant( + 'comment-range-start', + documentMatchers.commentRangeStart + ); + + var commentRangeEnd = identifierToConstant( + 'comment-range-end', + documentMatchers.commentRangeEnd + ); + + var inserted = identifierToConstant('ins', documentMatchers.inserted); + + var deleted = identifierToConstant('del', documentMatchers.deleted); + + var breakMatcher = sequence( + lop.rules.token('identifier', 'br'), + sequence.cut(), + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.token('identifier', 'type'), + lop.rules.tokenOfType('equals'), + sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ).map((breakType) => { + switch (breakType) { + case 'line': + return documentMatchers.lineBreak; + case 'page': + return documentMatchers.pageBreak; + case 'column': + return documentMatchers.columnBreak; + default: + throw new Error('Unknown break type: ' + breakType); + } + }); + + return lop.rules.firstOf( + 'element type', + paragraphOrRun, + table, + bold, + italic, + underline, + strikethrough, + allCaps, + smallCaps, + highlight, + commentReference, + commentRangeStart, + commentRangeEnd, + inserted, + deleted, + breakMatcher + ); +} + +function readHtmlPath(string) { + return parseString(htmlPathRule(), string); +} + +function htmlPathRule() { + var capture = lop.rules.sequence.capture; + var whitespaceRule = lop.rules.tokenOfType('whitespace'); + var freshRule = lop.rules.then( + lop.rules.optional( + lop.rules.sequence( + lop.rules.tokenOfType('colon'), + lop.rules.token('identifier', 'fresh') + ) + ), + (option) => option.map(() => true).valueOrElse(false) + ); + + var separatorRule = lop.rules.then( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('colon'), + lop.rules.token('identifier', 'separator'), + lop.rules.tokenOfType('open-paren'), + capture(stringRule), + lop.rules.tokenOfType('close-paren') + ) + .head() + ), + (option) => option.valueOrElse('') + ); + + var tagNamesRule = lop.rules.oneOrMoreWithSeparator( + identifierRule, + lop.rules.tokenOfType('choice') + ); + + var styleElementRule = lop.rules + .sequence( + capture(tagNamesRule), + capture(lop.rules.zeroOrMore(attributeOrClassRule)), + capture(freshRule), + capture(separatorRule) + ) + .map((tagName, attributesList, fresh, separator) => { + var attributes = {}; + var options = {}; + attributesList.forEach((attribute) => { + if (attribute.append && attributes[attribute.name]) { + attributes[attribute.name] += ' ' + attribute.value; + } else { + attributes[attribute.name] = attribute.value; + } + }); + if (fresh) { + options.fresh = true; + } + if (separator) { + options.separator = separator; + } + return htmlPaths.element(tagName, attributes, options); + }); + + return lop.rules.firstOf( + 'html path', + lop.rules.then(lop.rules.tokenOfType('bang'), () => htmlPaths.ignore), + lop.rules.then( + lop.rules.zeroOrMoreWithSeparator( + styleElementRule, + lop.rules.sequence( + whitespaceRule, + lop.rules.tokenOfType('gt'), + whitespaceRule + ) + ), + htmlPaths.elements + ) + ); +} + +var identifierRule = lop.rules.then( + lop.rules.tokenOfType('identifier'), + decodeEscapeSequences +); +var integerRule = lop.rules.tokenOfType('integer'); + +var stringRule = lop.rules.then( + lop.rules.tokenOfType('string'), + decodeEscapeSequences +); + +var escapeSequences = { + n: '\n', + r: '\r', + t: '\t', +}; + +function decodeEscapeSequences(value) { + return value.replace( + /\\(.)/g, + (match, code) => escapeSequences[code] || code + ); +} + +var attributeRule = lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule), + lop.rules.tokenOfType('equals'), + lop.rules.sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .map((name, value) => ({ name, value, append: false })); + +var classRule = lop.rules + .sequence( + lop.rules.tokenOfType('dot'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule) + ) + .map((className) => ({ name: 'class', value: className, append: true })); + +var attributeOrClassRule = lop.rules.firstOf( + 'attribute or class', + attributeRule, + classRule +); + +function parseString(rule, string) { + var tokens = tokenise(string); + var parser = lop.Parser(); + var parseResult = parser.parseTokens(rule, tokens); + if (parseResult.isSuccess()) { + return results.success(parseResult.value()); + } + return new results.Result(null, [ + results.warning(describeFailure(string, parseResult)), + ]); +} + +function describeFailure(input, parseResult) { + return ( + 'Did not understand this style mapping, so ignored it: ' + + input + + '\n' + + parseResult.errors().map(describeError).join('\n') + ); +} + +function describeError(error) { + return ( + 'Error was at character number ' + + error.characterNumber() + + ': ' + + 'Expected ' + + error.expected + + ' but got ' + + error.actual + ); +} + +var styleRule = createStyleRule(); diff --git a/packages/docx-io/src/lib/mammoth.js/lib/styles/document-matchers.js b/packages/docx-io/src/lib/mammoth.js/lib/styles/document-matchers.js new file mode 100644 index 0000000000..0c32422f8e --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/styles/document-matchers.js @@ -0,0 +1,116 @@ +exports.paragraph = paragraph; +exports.run = run; +exports.table = table; +exports.bold = new Matcher('bold'); +exports.italic = new Matcher('italic'); +exports.underline = new Matcher('underline'); +exports.strikethrough = new Matcher('strikethrough'); +exports.allCaps = new Matcher('allCaps'); +exports.smallCaps = new Matcher('smallCaps'); +exports.highlight = highlight; +exports.commentReference = new Matcher('commentReference'); +exports.commentRangeStart = new Matcher('commentRangeStart'); +exports.commentRangeEnd = new Matcher('commentRangeEnd'); +exports.inserted = new Matcher('inserted'); +exports.deleted = new Matcher('deleted'); +exports.lineBreak = new BreakMatcher({ breakType: 'line' }); +exports.pageBreak = new BreakMatcher({ breakType: 'page' }); +exports.columnBreak = new BreakMatcher({ breakType: 'column' }); +exports.equalTo = equalTo; +exports.startsWith = startsWith; + +function paragraph(options) { + return new Matcher('paragraph', options); +} + +function run(options) { + return new Matcher('run', options); +} + +function table(options) { + return new Matcher('table', options); +} + +function highlight(options) { + return new HighlightMatcher(options); +} + +function Matcher(elementType, options) { + options = options || {}; + this._elementType = elementType; + this._styleId = options.styleId; + this._styleName = options.styleName; + if (options.list) { + this._listIndex = options.list.levelIndex; + this._listIsOrdered = options.list.isOrdered; + } +} + +Matcher.prototype.matches = function (element) { + return ( + element.type === this._elementType && + (this._styleId === undefined || element.styleId === this._styleId) && + (this._styleName === undefined || + (element.styleName && + this._styleName.operator( + this._styleName.operand, + element.styleName + ))) && + (this._listIndex === undefined || + isList(element, this._listIndex, this._listIsOrdered)) + ); +}; + +function HighlightMatcher(options) { + options = options || {}; + this._color = options.color; +} + +HighlightMatcher.prototype.matches = function (element) { + return ( + element.type === 'highlight' && + (this._color === undefined || element.color === this._color) + ); +}; + +function BreakMatcher(options) { + options = options || {}; + this._breakType = options.breakType; +} + +BreakMatcher.prototype.matches = function (element) { + return ( + element.type === 'break' && + (this._breakType === undefined || element.breakType === this._breakType) + ); +}; + +function isList(element, levelIndex, isOrdered) { + return ( + element.numbering && + element.numbering.level === levelIndex && + element.numbering.isOrdered === isOrdered + ); +} + +function equalTo(value) { + return { + operator: operatorEqualTo, + operand: value, + }; +} + +function startsWith(value) { + return { + operator: operatorStartsWith, + operand: value, + }; +} + +function operatorEqualTo(first, second) { + return first.toUpperCase() === second.toUpperCase(); +} + +function operatorStartsWith(first, second) { + return second.toUpperCase().indexOf(first.toUpperCase()) === 0; +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/styles/html-paths.js b/packages/docx-io/src/lib/mammoth.js/lib/styles/html-paths.js new file mode 100644 index 0000000000..d6de6f18b6 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/styles/html-paths.js @@ -0,0 +1,79 @@ +var _ = require('underscore'); + +var html = require('../html'); + +exports.topLevelElement = topLevelElement; +exports.elements = elements; +exports.element = element; + +function topLevelElement(tagName, attributes) { + return elements([element(tagName, attributes, { fresh: true })]); +} + +function elements(elementStyles) { + return new HtmlPath( + elementStyles.map((elementStyle) => { + if (_.isString(elementStyle)) { + return element(elementStyle); + } + return elementStyle; + }) + ); +} + +function HtmlPath(elements) { + this._elements = elements; +} + +HtmlPath.prototype.wrap = function wrap(children) { + var result = children(); + for (var index = this._elements.length - 1; index >= 0; index--) { + result = this._elements[index].wrapNodes(result); + } + return result; +}; + +function element(tagName, attributes, options) { + options = options || {}; + return new Element(tagName, attributes, options); +} + +function Element(tagName, attributes, options) { + var tagNames = {}; + if (_.isArray(tagName)) { + tagName.forEach((tagName) => { + tagNames[tagName] = true; + }); + tagName = tagName[0]; + } else { + tagNames[tagName] = true; + } + + this.tagName = tagName; + this.tagNames = tagNames; + this.attributes = attributes || {}; + this.fresh = options.fresh; + this.separator = options.separator; +} + +Element.prototype.matchesElement = function (element) { + return ( + this.tagNames[element.tagName] && + _.isEqual(this.attributes || {}, element.attributes || {}) + ); +}; + +Element.prototype.wrap = function wrap(generateNodes) { + return this.wrapNodes(generateNodes()); +}; + +Element.prototype.wrapNodes = function wrapNodes(nodes) { + return [html.elementWithTag(this, nodes)]; +}; + +exports.empty = elements([]); +exports.ignore = { + wrap() { + return []; + }, +}; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/styles/parser/tokeniser.js b/packages/docx-io/src/lib/mammoth.js/lib/styles/parser/tokeniser.js new file mode 100644 index 0000000000..2dbc2b932b --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/styles/parser/tokeniser.js @@ -0,0 +1,35 @@ +var lop = require('lop'); +var RegexTokeniser = lop.RegexTokeniser; + +exports.tokenise = tokenise; + +var stringPrefix = "'((?:\\\\.|[^'])*)"; + +function tokenise(string) { + var identifierCharacter = '(?:[a-zA-Z\\-_]|\\\\.)'; + var tokeniser = new RegexTokeniser([ + { + name: 'identifier', + regex: new RegExp( + '(' + identifierCharacter + '(?:' + identifierCharacter + '|[0-9])*)' + ), + }, + { name: 'dot', regex: /\./ }, + { name: 'colon', regex: /:/ }, + { name: 'gt', regex: />/ }, + { name: 'whitespace', regex: /\s+/ }, + { name: 'arrow', regex: /=>/ }, + { name: 'equals', regex: /=/ }, + { name: 'startsWith', regex: /\^=/ }, + { name: 'open-paren', regex: /\(/ }, + { name: 'close-paren', regex: /\)/ }, + { name: 'open-square-bracket', regex: /\[/ }, + { name: 'close-square-bracket', regex: /\]/ }, + { name: 'string', regex: new RegExp(stringPrefix + "'") }, + { name: 'unterminated-string', regex: new RegExp(stringPrefix) }, + { name: 'integer', regex: /([0-9]+)/ }, + { name: 'choice', regex: /\|/ }, + { name: 'bang', regex: /(!)/ }, + ]); + return tokeniser.tokenise(string); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/transforms.js b/packages/docx-io/src/lib/mammoth.js/lib/transforms.js new file mode 100644 index 0000000000..b8c726ff7b --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/transforms.js @@ -0,0 +1,60 @@ +var _ = require('underscore'); + +exports.paragraph = paragraph; +exports.run = run; +exports._elements = elements; +exports._elementsOfType = elementsOfType; +exports.getDescendantsOfType = getDescendantsOfType; +exports.getDescendants = getDescendants; + +function paragraph(transform) { + return elementsOfType('paragraph', transform); +} + +function run(transform) { + return elementsOfType('run', transform); +} + +function elementsOfType(elementType, transform) { + return elements((element) => { + if (element.type === elementType) { + return transform(element); + } + return element; + }); +} + +function elements(transform) { + return function transformElement(element) { + if (element.children) { + var children = _.map(element.children, transformElement); + element = _.extend({}, element, { children }); + } + return transform(element); + }; +} + +function getDescendantsOfType(element, type) { + return getDescendants(element).filter( + (descendant) => descendant.type === type + ); +} + +function getDescendants(element) { + var descendants = []; + + visitDescendants(element, (descendant) => { + descendants.push(descendant); + }); + + return descendants; +} + +function visitDescendants(element, visit) { + if (element.children) { + element.children.forEach((child) => { + visitDescendants(child, visit); + visit(child); + }); + } +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/underline.js b/packages/docx-io/src/lib/mammoth.js/lib/underline.js new file mode 100644 index 0000000000..cd4561730f --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/underline.js @@ -0,0 +1,8 @@ +var htmlPaths = require('./styles/html-paths'); +var Html = require('./html'); + +exports.element = element; + +function element(name) { + return (html) => Html.elementWithTag(htmlPaths.element(name), [html]); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/unzip.js b/packages/docx-io/src/lib/mammoth.js/lib/unzip.js new file mode 100644 index 0000000000..36301b25e9 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/unzip.js @@ -0,0 +1,21 @@ +var fs = require('fs'); + +var promises = require('./promises'); +var zipfile = require('./zipfile'); + +exports.openZip = openZip; + +var readFile = promises.promisify(fs.readFile); + +function openZip(options) { + if (options.path) { + return readFile(options.path).then(zipfile.openArrayBuffer); + } + if (options.buffer) { + return promises.resolve(zipfile.openArrayBuffer(options.buffer)); + } + if (options.file) { + return promises.resolve(options.file); + } + return promises.reject(new Error('Could not find file in options')); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/writers/html-writer.js b/packages/docx-io/src/lib/mammoth.js/lib/writers/html-writer.js new file mode 100644 index 0000000000..bffc06df23 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/writers/html-writer.js @@ -0,0 +1,158 @@ +var _ = require('underscore'); + +exports.writer = writer; + +function writer(options) { + options = options || {}; + if (options.prettyPrint) { + return prettyWriter(); + } + return simpleWriter(); +} + +var indentedElements = { + div: true, + p: true, + ul: true, + li: true, +}; + +function prettyWriter() { + var indentationLevel = 0; + var indentation = ' '; + var stack = []; + var start = true; + var inText = false; + + var writer = simpleWriter(); + + function open(tagName, attributes) { + if (indentedElements[tagName]) { + indent(); + } + stack.push(tagName); + writer.open(tagName, attributes); + if (indentedElements[tagName]) { + indentationLevel++; + } + start = false; + } + + function close(tagName) { + if (indentedElements[tagName]) { + indentationLevel--; + indent(); + } + stack.pop(); + writer.close(tagName); + } + + function text(value) { + startText(); + var currentIndent = ''; + for (var i = 0; i < indentationLevel; i++) { + currentIndent += indentation; + } + var text = isInPre() ? value : value.replace(/\n/g, '\n' + currentIndent); + writer.text(text); + } + + function selfClosing(tagName, attributes) { + indent(); + writer.selfClosing(tagName, attributes); + } + + function insideIndentedElement() { + return stack.length === 0 || indentedElements[stack[stack.length - 1]]; + } + + function startText() { + if (!inText) { + indent(); + inText = true; + } + } + + function indent() { + inText = false; + if (!start && insideIndentedElement() && !isInPre()) { + writer._append('\n'); + for (var i = 0; i < indentationLevel; i++) { + writer._append(indentation); + } + } + } + + function isInPre() { + return _.some(stack, (tagName) => tagName === 'pre'); + } + + return { + asString: writer.asString, + open, + close, + text, + selfClosing, + }; +} + +function simpleWriter() { + var fragments = []; + + function open(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push('<' + tagName + attributeString + '>'); + } + + function close(tagName) { + fragments.push('</' + tagName + '>'); + } + + function selfClosing(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push('<' + tagName + attributeString + ' />'); + } + + function generateAttributeString(attributes) { + return _.map( + attributes, + (value, key) => ' ' + key + '="' + escapeHtmlAttribute(value) + '"' + ).join(''); + } + + function text(value) { + fragments.push(escapeHtmlText(value)); + } + + function append(html) { + fragments.push(html); + } + + function asString() { + return fragments.join(''); + } + + return { + asString, + open, + close, + text, + selfClosing, + _append: append, + }; +} + +function escapeHtmlText(value) { + return value + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); +} + +function escapeHtmlAttribute(value) { + return value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/writers/index.js b/packages/docx-io/src/lib/mammoth.js/lib/writers/index.js new file mode 100644 index 0000000000..25ce93fcb4 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/writers/index.js @@ -0,0 +1,12 @@ +var htmlWriter = require('./html-writer'); +var markdownWriter = require('./markdown-writer'); + +exports.writer = writer; + +function writer(options) { + options = options || {}; + if (options.outputFormat === 'markdown') { + return markdownWriter.writer(); + } + return htmlWriter.writer(options); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/writers/markdown-writer.js b/packages/docx-io/src/lib/mammoth.js/lib/writers/markdown-writer.js new file mode 100644 index 0000000000..4ecc587da9 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/writers/markdown-writer.js @@ -0,0 +1,155 @@ +var _ = require('underscore'); + +function symmetricMarkdownElement(end) { + return markdownElement(end, end); +} + +function markdownElement(start, end) { + return () => ({ start, end }); +} + +function markdownLink(attributes) { + var href = attributes.href || ''; + if (href) { + return { + start: '[', + end: '](' + href + ')', + anchorPosition: 'before', + }; + } + return {}; +} + +function markdownImage(attributes) { + var src = attributes.src || ''; + var altText = attributes.alt || ''; + if (src || altText) { + return { start: '![' + altText + '](' + src + ')' }; + } + return {}; +} + +function markdownList(options) { + return (attributes, list) => ({ + start: list ? '\n' : '', + end: list ? '' : '\n', + list: { + isOrdered: options.isOrdered, + indent: list ? list.indent + 1 : 0, + count: 0, + }, + }); +} + +function markdownListItem(attributes, list, listItem) { + list = list || { indent: 0, isOrdered: false, count: 0 }; + list.count++; + listItem.hasClosed = false; + + var bullet = list.isOrdered ? list.count + '.' : '-'; + var start = repeatString('\t', list.indent) + bullet + ' '; + + return { + start, + end() { + if (!listItem.hasClosed) { + listItem.hasClosed = true; + return '\n'; + } + }, + }; +} + +var htmlToMarkdown = { + p: markdownElement('', '\n\n'), + br: markdownElement('', ' \n'), + ul: markdownList({ isOrdered: false }), + ol: markdownList({ isOrdered: true }), + li: markdownListItem, + strong: symmetricMarkdownElement('__'), + em: symmetricMarkdownElement('*'), + a: markdownLink, + img: markdownImage, +}; + +(() => { + for (var i = 1; i <= 6; i++) { + htmlToMarkdown['h' + i] = markdownElement( + repeatString('#', i) + ' ', + '\n\n' + ); + } +})(); + +function repeatString(value, count) { + return new Array(count + 1).join(value); +} + +function markdownWriter() { + var fragments = []; + var elementStack = []; + var list = null; + var listItem = {}; + + function open(tagName, attributes) { + attributes = attributes || {}; + + var createElement = htmlToMarkdown[tagName] || (() => ({})); + var element = createElement(attributes, list, listItem); + elementStack.push({ end: element.end, list }); + + if (element.list) { + list = element.list; + } + + var anchorBeforeStart = element.anchorPosition === 'before'; + if (anchorBeforeStart) { + writeAnchor(attributes); + } + + fragments.push(element.start || ''); + if (!anchorBeforeStart) { + writeAnchor(attributes); + } + } + + function writeAnchor(attributes) { + if (attributes.id) { + fragments.push('<a id="' + attributes.id + '"></a>'); + } + } + + function close(tagName) { + var element = elementStack.pop(); + list = element.list; + var end = _.isFunction(element.end) ? element.end() : element.end; + fragments.push(end || ''); + } + + function selfClosing(tagName, attributes) { + open(tagName, attributes); + close(tagName); + } + + function text(value) { + fragments.push(escapeMarkdown(value)); + } + + function asString() { + return fragments.join(''); + } + + return { + asString, + open, + close, + text, + selfClosing, + }; +} + +exports.writer = markdownWriter; + +function escapeMarkdown(value) { + return value.replace(/\\/g, '\\\\').replace(/([`*_{}[\]()#+\-.!])/g, '\\$1'); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/xml/index.js b/packages/docx-io/src/lib/mammoth.js/lib/xml/index.js new file mode 100644 index 0000000000..7673ad9bbe --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/xml/index.js @@ -0,0 +1,8 @@ +var nodes = require('./nodes'); + +exports.Element = nodes.Element; +exports.element = nodes.element; +exports.emptyElement = nodes.emptyElement; +exports.text = nodes.text; +exports.readString = require('./reader').readString; +exports.writeString = require('./writer').writeString; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/xml/nodes.js b/packages/docx-io/src/lib/mammoth.js/lib/xml/nodes.js new file mode 100644 index 0000000000..6465707f26 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/xml/nodes.js @@ -0,0 +1,62 @@ +var _ = require('underscore'); + +exports.Element = Element; +exports.element = (name, attributes, children) => + new Element(name, attributes, children); +exports.text = (value) => ({ + type: 'text', + value, +}); + +var emptyElement = (exports.emptyElement = { + first() { + return null; + }, + firstOrEmpty() { + return emptyElement; + }, + attributes: {}, + children: [], +}); + +function Element(name, attributes, children) { + this.type = 'element'; + this.name = name; + this.attributes = attributes || {}; + this.children = children || []; +} + +Element.prototype.first = function (name) { + return _.find(this.children, (child) => child.name === name); +}; + +Element.prototype.firstOrEmpty = function (name) { + return this.first(name) || emptyElement; +}; + +Element.prototype.getElementsByTagName = function (name) { + var elements = _.filter(this.children, (child) => child.name === name); + return toElementList(elements); +}; + +Element.prototype.text = function () { + if (this.children.length === 0) { + return ''; + } + if (this.children.length !== 1 || this.children[0].type !== 'text') { + throw new Error('Not implemented'); + } + return this.children[0].value; +}; + +var elementListPrototype = { + getElementsByTagName(name) { + return toElementList( + _.flatten(this.map((element) => element.getElementsByTagName(name), true)) + ); + }, +}; + +function toElementList(array) { + return _.extend(array, elementListPrototype); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/xml/reader.js b/packages/docx-io/src/lib/mammoth.js/lib/xml/reader.js new file mode 100644 index 0000000000..7a275bd345 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/xml/reader.js @@ -0,0 +1,68 @@ +var promises = require('../promises'); +var _ = require('underscore'); + +var xmldom = require('./xmldom'); +var nodes = require('./nodes'); +var Element = nodes.Element; + +exports.readString = readString; + +var Node = xmldom.Node; + +function readString(xmlString, namespaceMap) { + namespaceMap = namespaceMap || {}; + + try { + var document = xmldom.parseFromString(xmlString, 'text/xml'); + } catch (error) { + return promises.reject(error); + } + + if (document.documentElement.tagName === 'parsererror') { + return promises.reject(new Error(document.documentElement.textContent)); + } + + function convertNode(node) { + switch (node.nodeType) { + case Node.ELEMENT_NODE: + return convertElement(node); + case Node.TEXT_NODE: + return nodes.text(node.nodeValue); + } + } + + function convertElement(element) { + var convertedName = convertName(element); + + var convertedChildren = []; + _.forEach(element.childNodes, (childNode) => { + var convertedNode = convertNode(childNode); + if (convertedNode) { + convertedChildren.push(convertedNode); + } + }); + + var convertedAttributes = {}; + _.forEach(element.attributes, (attribute) => { + convertedAttributes[convertName(attribute)] = attribute.value; + }); + + return new Element(convertedName, convertedAttributes, convertedChildren); + } + + function convertName(node) { + if (node.namespaceURI) { + var mappedPrefix = namespaceMap[node.namespaceURI]; + var prefix; + if (mappedPrefix) { + prefix = mappedPrefix + ':'; + } else { + prefix = '{' + node.namespaceURI + '}'; + } + return prefix + node.localName; + } + return node.localName; + } + + return promises.resolve(convertNode(document.documentElement)); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/xml/writer.js b/packages/docx-io/src/lib/mammoth.js/lib/xml/writer.js new file mode 100644 index 0000000000..19c436ce65 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/xml/writer.js @@ -0,0 +1,69 @@ +var _ = require('underscore'); +var xmlbuilder = require('xmlbuilder'); + +exports.writeString = writeString; + +function writeString(root, namespaces) { + var uriToPrefix = _.invert(namespaces); + + var nodeWriters = { + element: writeElement, + text: writeTextNode, + }; + + function writeNode(builder, node) { + var writer = nodeWriters[node.type]; + if (!writer) { + throw new Error('Unknown node type: ' + node.type); + } + return writer(builder, node); + } + + function writeElement(builder, element) { + var elementBuilder = builder.element( + mapElementName(element.name), + element.attributes + ); + element.children.forEach((child) => { + writeNode(elementBuilder, child); + }); + } + + function mapElementName(name) { + var longFormMatch = /^\{(.*)\}(.*)$/.exec(name); + if (longFormMatch) { + var prefix = uriToPrefix[longFormMatch[1]]; + return prefix + (prefix === '' ? '' : ':') + longFormMatch[2]; + } + return name; + } + + function writeDocument(root) { + var builder = xmlbuilder.create(mapElementName(root.name), { + version: '1.0', + encoding: 'UTF-8', + standalone: true, + }); + + _.forEach(namespaces, (uri, prefix) => { + var key = 'xmlns' + (prefix === '' ? '' : ':' + prefix); + builder.attribute(key, uri); + }); + + // Apply root element attributes + _.forEach(root.attributes, (value, key) => { + builder.attribute(key, value); + }); + + root.children.forEach((child) => { + writeNode(builder, child); + }); + return builder.end(); + } + + return writeDocument(root); +} + +function writeTextNode(builder, node) { + builder.text(node.value); +} diff --git a/packages/docx-io/src/lib/mammoth.js/lib/xml/xmldom.js b/packages/docx-io/src/lib/mammoth.js/lib/xml/xmldom.js new file mode 100644 index 0000000000..0f399dbc2d --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/xml/xmldom.js @@ -0,0 +1,23 @@ +var xmldom = require('@xmldom/xmldom'); +var dom = require('@xmldom/xmldom/lib/dom'); + +function parseFromString(string) { + var errors = []; + + var domParser = new xmldom.DOMParser({ + errorHandler(level, message) { + errors.push({ level, message }); + }, + }); + + var document = domParser.parseFromString(string); + + if (errors.length === 0) { + return document; + } + var errorMessages = errors.map((e) => e.level + ': ' + e.message).join('\n'); + throw new Error(errorMessages); +} + +exports.parseFromString = parseFromString; +exports.Node = dom.Node; diff --git a/packages/docx-io/src/lib/mammoth.js/lib/zipfile.js b/packages/docx-io/src/lib/mammoth.js/lib/zipfile.js new file mode 100644 index 0000000000..35bf47284f --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/lib/zipfile.js @@ -0,0 +1,73 @@ +var base64js = require('base64-js'); +var JSZip = require('jszip'); + +exports.openArrayBuffer = openArrayBuffer; +exports.splitPath = splitPath; +exports.joinPath = joinPath; + +function openArrayBuffer(arrayBuffer) { + return JSZip.loadAsync(arrayBuffer).then((zipFile) => { + function exists(name) { + return zipFile.file(name) !== null; + } + + function read(name, encoding) { + var file = zipFile.file(name); + if (file === null) { + return Promise.reject(new Error('File not found in ZIP: ' + name)); + } + return file.async('uint8array').then((array) => { + if (encoding === 'base64') { + return base64js.fromByteArray(array); + } + if (encoding) { + var decoder = new TextDecoder(encoding); + return decoder.decode(array); + } + return array; + }); + } + + function write(name, contents) { + zipFile.file(name, contents); + } + + function toArrayBuffer() { + return zipFile.generateAsync({ type: 'arraybuffer' }); + } + + return { + exists, + read, + write, + toArrayBuffer, + }; + }); +} + +function splitPath(path) { + var lastIndex = path.lastIndexOf('/'); + if (lastIndex === -1) { + return { dirname: '', basename: path }; + } + return { + dirname: path.substring(0, lastIndex), + basename: path.substring(lastIndex + 1), + }; +} + +function joinPath() { + var nonEmptyPaths = Array.prototype.filter.call(arguments, (path) => path); + + var relevantPaths = []; + + nonEmptyPaths.forEach((path) => { + if (path.startsWith('/')) { + relevantPaths = [path]; + } else { + relevantPaths.push(path); + } + }); + + return relevantPaths.join('/'); +} diff --git a/packages/docx-io/src/lib/mammoth.js/mammoth.browser.js b/packages/docx-io/src/lib/mammoth.js/mammoth.browser.js new file mode 100644 index 0000000000..41b4e67ec7 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/mammoth.browser.js @@ -0,0 +1,24716 @@ +// Module: @xmldom/xmldom@0.8.11 +// License: MIT +// +// Module: base64-js@1.5.1 +// License: MIT +// +// Module: bluebird@3.4.7 +// License: MIT +// +// Module: buffer@4.9.2 +// License: MIT +// +// Module: dingbat-to-unicode@1.0.1 +// License: BSD-2-Clause +// +// Module: ieee754@1.2.1 +// License: BSD-3-Clause +// +// Module: isarray@1.0.0 +// License: MIT +// +// Module: jszip@3.10.1 +// License: (MIT OR GPL-3.0-or-later) +// +// Module: lop@0.4.2 +// License: BSD-2-Clause +// +// Module: mammoth@1.11.0 +// License: BSD-2-Clause +// +// Module: option@0.2.4 +// License: BSD-2-Clause +// +// Module: process@0.11.10 +// License: MIT +// +// Module: timers-browserify@1.4.2 +// License: MIT +// +// Module: underscore@1.13.7 +// License: MIT +// +// Module: xmlbuilder@10.1.1 +// License: MIT +// +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.mammoth = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ +var promises = require('../../lib/promises'); + +exports.Files = Files; + +function Files() { + function read(uri) { + return promises.reject( + new Error( + "could not open external image: '" + + uri + + "'\ncannot open linked files from a web browser" + ) + ); + } + + return { + read, + }; +} + +},{"../../lib/promises":24}],2:[function(require,module,exports){ +var promises = require('../lib/promises'); +var zipfile = require('../lib/zipfile'); + +exports.openZip = openZip; + +function openZip(options) { + if (options.arrayBuffer) { + return promises.resolve(zipfile.openArrayBuffer(options.arrayBuffer)); + } + return promises.reject(new Error('Could not find file in options')); +} + +},{"../lib/promises":24,"../lib/zipfile":41}],3:[function(require,module,exports){ +var _ = require('underscore'); + +var promises = require('./promises'); +var documents = require('./documents'); +var htmlPaths = require('./styles/html-paths'); +var results = require('./results'); +var images = require('./images'); +var Html = require('./html'); +var writers = require('./writers'); + +exports.DocumentConverter = DocumentConverter; + +// Token prefixes for tracked changes - parsed by import-toolbar-button.tsx +var DOCX_INSERTION_START_TOKEN_PREFIX = '[[DOCX_INS_START:'; +var DOCX_INSERTION_END_TOKEN_PREFIX = '[[DOCX_INS_END:'; +var DOCX_INSERTION_TOKEN_SUFFIX = ']]'; +var DOCX_DELETION_START_TOKEN_PREFIX = '[[DOCX_DEL_START:'; +var DOCX_DELETION_END_TOKEN_PREFIX = '[[DOCX_DEL_END:'; +var DOCX_DELETION_TOKEN_SUFFIX = ']]'; + +// Token prefixes for comment ranges - parsed by import-toolbar-button.tsx +var DOCX_COMMENT_START_TOKEN_PREFIX = '[[DOCX_CMT_START:'; +var DOCX_COMMENT_END_TOKEN_PREFIX = '[[DOCX_CMT_END:'; +var DOCX_COMMENT_TOKEN_SUFFIX = ']]'; + +function DocumentConverter(options) { + return { + convertToHtml(element) { + var comments = _.indexBy( + element.type === documents.types.document ? element.comments : [], + 'commentId' + ); + var conversion = new DocumentConversion(options, comments); + return conversion.convertToHtml(element); + }, + }; +} + +function DocumentConversion(options, comments) { + var noteNumber = 1; + // Counter for generating unique IDs for tracked changes without explicit IDs + var trackedChangeIdCounter = 1; + + var noteReferences = []; + + var referencedComments = []; + + options = _.extend({ ignoreEmptyParagraphs: true }, options); + var idPrefix = options.idPrefix === undefined ? '' : options.idPrefix; + var ignoreEmptyParagraphs = options.ignoreEmptyParagraphs; + + var defaultParagraphStyle = htmlPaths.topLevelElement('p'); + + var styleMap = options.styleMap || []; + + function convertToHtml(document) { + var messages = []; + + var html = elementToHtml(document, messages, {}); + + var deferredNodes = []; + walkHtml(html, (node) => { + if (node.type === 'deferred') { + deferredNodes.push(node); + } + }); + var deferredValues = {}; + return promises + .mapSeries(deferredNodes, (deferred) => + deferred.value().then((value) => { + deferredValues[deferred.id] = value; + }) + ) + .then(() => { + function replaceDeferred(nodes) { + return flatMap(nodes, (node) => { + if (node.type === 'deferred') { + return deferredValues[node.id]; + } + if (node.children) { + return [ + _.extend({}, node, { + children: replaceDeferred(node.children), + }), + ]; + } + return [node]; + }); + } + var writer = writers.writer({ + prettyPrint: options.prettyPrint, + outputFormat: options.outputFormat, + }); + Html.write(writer, Html.simplify(replaceDeferred(html))); + return new results.Result(writer.asString(), messages); + }); + } + + function convertElements(elements, messages, options) { + return flatMap(elements, (element) => + elementToHtml(element, messages, options) + ); + } + + function elementToHtml(element, messages, options) { + if (!options) { + throw new Error('options not set'); + } + var handler = elementConverters[element.type]; + if (handler) { + return handler(element, messages, options); + } + return []; + } + + function convertParagraph(element, messages, options) { + return htmlPathForParagraph(element, messages).wrap(() => { + var content = convertElements(element.children, messages, options); + if (ignoreEmptyParagraphs) { + return content; + } + return [Html.forceWrite].concat(content); + }); + } + + function htmlPathForParagraph(element, messages) { + var style = findStyle(element); + + if (style) { + return style.to; + } + if (element.styleId) { + messages.push(unrecognisedStyleWarning('paragraph', element)); + } + return defaultParagraphStyle; + } + + function convertRun(run, messages, options) { + var nodes = () => convertElements(run.children, messages, options); + var paths = []; + if (run.highlight !== null) { + var path = findHtmlPath({ type: 'highlight', color: run.highlight }); + if (path) { + paths.push(path); + } + } + if (run.isSmallCaps) { + paths.push(findHtmlPathForRunProperty('smallCaps')); + } + if (run.isAllCaps) { + paths.push(findHtmlPathForRunProperty('allCaps')); + } + if (run.isStrikethrough) { + paths.push(findHtmlPathForRunProperty('strikethrough', 's')); + } + if (run.isUnderline) { + paths.push(findHtmlPathForRunProperty('underline')); + } + if (run.verticalAlignment === documents.verticalAlignment.subscript) { + paths.push(htmlPaths.element('sub', {}, { fresh: false })); + } + if (run.verticalAlignment === documents.verticalAlignment.superscript) { + paths.push(htmlPaths.element('sup', {}, { fresh: false })); + } + if (run.isItalic) { + paths.push(findHtmlPathForRunProperty('italic', 'em')); + } + if (run.isBold) { + paths.push(findHtmlPathForRunProperty('bold', 'strong')); + } + var stylePath = htmlPaths.empty; + var style = findStyle(run); + if (style) { + stylePath = style.to; + } else if (run.styleId) { + messages.push(unrecognisedStyleWarning('run', run)); + } + paths.push(stylePath); + + paths.forEach((path) => { + nodes = path.wrap.bind(path, nodes); + }); + + return nodes(); + } + + function findHtmlPathForRunProperty(elementType, defaultTagName) { + var path = findHtmlPath({ type: elementType }); + if (path) { + return path; + } + if (defaultTagName) { + return htmlPaths.element(defaultTagName, {}, { fresh: false }); + } + return htmlPaths.empty; + } + + function findHtmlPath(element, defaultPath) { + var style = findStyle(element); + return style ? style.to : defaultPath; + } + + function findStyle(element) { + for (var i = 0; i < styleMap.length; i++) { + if (styleMap[i].from.matches(element)) { + return styleMap[i]; + } + } + } + + function recoveringConvertImage(convertImage) { + return (image, messages) => + promises + .attempt(() => convertImage(image, messages)) + .caught((error) => { + messages.push(results.error(error)); + return []; + }); + } + + function noteHtmlId(note) { + return referentHtmlId(note.noteType, note.noteId); + } + + function noteRefHtmlId(note) { + return referenceHtmlId(note.noteType, note.noteId); + } + + function referentHtmlId(referenceType, referenceId) { + return htmlId(referenceType + '-' + referenceId); + } + + function referenceHtmlId(referenceType, referenceId) { + return htmlId(referenceType + '-ref-' + referenceId); + } + + function htmlId(suffix) { + return idPrefix + suffix; + } + + var defaultTablePath = htmlPaths.elements([ + htmlPaths.element('table', {}, { fresh: true }), + ]); + + function convertTable(element, messages, options) { + return findHtmlPath(element, defaultTablePath).wrap(() => + convertTableChildren(element, messages, options) + ); + } + + function convertTableChildren(element, messages, options) { + var bodyIndex = _.findIndex( + element.children, + (child) => child.type !== documents.types.tableRow || !child.isHeader + ); + if (bodyIndex === -1) { + bodyIndex = element.children.length; + } + var children; + if (bodyIndex === 0) { + children = convertElements( + element.children, + messages, + _.extend({}, options, { isTableHeader: false }) + ); + } else { + var headRows = convertElements( + element.children.slice(0, bodyIndex), + messages, + _.extend({}, options, { isTableHeader: true }) + ); + var bodyRows = convertElements( + element.children.slice(bodyIndex), + messages, + _.extend({}, options, { isTableHeader: false }) + ); + children = [ + Html.freshElement('thead', {}, headRows), + Html.freshElement('tbody', {}, bodyRows), + ]; + } + return [Html.forceWrite].concat(children); + } + + function convertTableRow(element, messages, options) { + var children = convertElements(element.children, messages, options); + return [Html.freshElement('tr', {}, [Html.forceWrite].concat(children))]; + } + + function convertTableCell(element, messages, options) { + var tagName = options.isTableHeader ? 'th' : 'td'; + var children = convertElements(element.children, messages, options); + var attributes = {}; + if (element.colSpan !== 1) { + attributes.colspan = element.colSpan.toString(); + } + if (element.rowSpan !== 1) { + attributes.rowspan = element.rowSpan.toString(); + } + + return [ + Html.freshElement( + tagName, + attributes, + [Html.forceWrite].concat(children) + ), + ]; + } + function getReplies(paraId) { + if (!paraId) return []; + var replies = []; + for (var id in comments) { + var c = comments[id]; + if (c.parentParaId === paraId) { + replies.push(c); + } + } + replies.sort((a, b) => ((a.date || '') > (b.date || '') ? 1 : -1)); + return replies; + } + + function buildCommentPayload(comment, messages, options) { + var payload = { + id: comment.commentId, + authorName: comment.authorName, + authorInitials: comment.authorInitials, + date: comment.date, + paraId: comment.paraId, + parentParaId: comment.parentParaId, + }; + + if (comment.body && comment.body.length > 0) { + payload.text = extractTextFromElements(comment.body); + try { + var richContent = convertElements(comment.body, messages, options); + payload.body = Html.simplify(richContent); + } catch (e) { + var detail = ''; + if (e && typeof e.message === 'string') { + detail = e.message; + } else if (typeof e === 'string') { + detail = e; + } + var message = + 'Failed to convert comment body for comment ' + + comment.commentId + + (detail ? ': ' + detail : ''); + var error = e instanceof Error ? e : new Error(message); + if (error) { + error.message = message; + } + messages.push(results.error(error)); + } + } + + // Recursive replies + var replies = getReplies(comment.paraId); + if (replies.length > 0) { + payload.replies = replies.map((r) => + buildCommentPayload(r, messages, options) + ); + } + + return payload; + } + + function convertCommentReference(reference, messages, options) { + var comment = comments[reference.commentId]; + if (!comment) return []; + + var payload = buildCommentPayload(comment, messages, options); + payload.isPoint = true; + + var startToken = + DOCX_COMMENT_START_TOKEN_PREFIX + + encodeURIComponent(JSON.stringify(payload)) + + DOCX_COMMENT_TOKEN_SUFFIX; + var endToken = + DOCX_COMMENT_END_TOKEN_PREFIX + + encodeURIComponent(reference.commentId) + + DOCX_COMMENT_TOKEN_SUFFIX; + + return [Html.text(startToken + endToken)]; + } + + function convertComment(referencedComment, messages, options) { + // Legacy support or ignore. We use inline tokens. + void referencedComment; + void messages; + void options; + return []; + } + + function convertBreak(element, messages, options) { + return htmlPathForBreak(element).wrap(() => []); + } + + function htmlPathForBreak(element) { + var style = findStyle(element); + if (style) { + return style.to; + } + if (element.breakType === 'line') { + return htmlPaths.topLevelElement('br'); + } + return htmlPaths.empty; + } + + var elementConverters = { + document(document, messages, options) { + var children = convertElements(document.children, messages, options); + var notes = noteReferences.map((noteReference) => + document.notes.resolve(noteReference) + ); + var notesNodes = convertElements(notes, messages, options); + return children.concat([ + Html.freshElement('ol', {}, notesNodes), + Html.freshElement( + 'dl', + {}, + flatMap(referencedComments, (referencedComment) => + convertComment(referencedComment, messages, options) + ) + ), + ]); + }, + paragraph: convertParagraph, + run: convertRun, + text(element, messages, options) { + void messages; + void options; + return [Html.text(element.value)]; + }, + tab(element, messages, options) { + return [Html.text('\t')]; + }, + hyperlink(element, messages, options) { + var href = element.anchor ? '#' + htmlId(element.anchor) : element.href; + var attributes = { href }; + if (element.targetFrame != null) { + attributes.target = element.targetFrame; + } + + var children = convertElements(element.children, messages, options); + return [Html.nonFreshElement('a', attributes, children)]; + }, + checkbox(element) { + var attributes = { type: 'checkbox' }; + if (element.checked) { + attributes['checked'] = 'checked'; + } + return [Html.freshElement('input', attributes)]; + }, + bookmarkStart(element, messages, options) { + var anchor = Html.freshElement( + 'a', + { + id: htmlId(element.name), + }, + [Html.forceWrite] + ); + return [anchor]; + }, + noteReference(element, messages, options) { + void messages; + void options; + noteReferences.push(element); + var anchor = Html.freshElement( + 'a', + { + href: '#' + noteHtmlId(element), + id: noteRefHtmlId(element), + }, + [Html.text('[' + noteNumber++ + ']')] + ); + + return [Html.freshElement('sup', {}, [anchor])]; + }, + note(element, messages, options) { + var children = convertElements(element.body, messages, options); + var backLink = Html.elementWithTag( + htmlPaths.element('p', {}, { fresh: false }), + [ + Html.text(' '), + Html.freshElement('a', { href: '#' + noteRefHtmlId(element) }, [ + Html.text('↑'), + ]), + ] + ); + var body = children.concat([backLink]); + + return Html.freshElement('li', { id: noteHtmlId(element) }, body); + }, + commentReference: convertCommentReference, + comment: convertComment, + commentRangeStart(element, messages, options) { + void options; + var comment = comments[element.commentId]; + if (!comment) { + messages.push( + results.warning( + 'Comment with ID ' + + element.commentId + + ' was referenced by a range but not found in the document' + ) + ); + // Still emit token to prevent errors if desired? + // No, if missing, we can't do much. + return []; + } + + var payload = buildCommentPayload(comment, messages, options); + + var token = + DOCX_COMMENT_START_TOKEN_PREFIX + + encodeURIComponent(JSON.stringify(payload)) + + DOCX_COMMENT_TOKEN_SUFFIX; + return [Html.text(token)]; + }, + commentRangeEnd(element) { + // Emit token for comment range end - will be parsed by import-toolbar-button.tsx + var token = + DOCX_COMMENT_END_TOKEN_PREFIX + + encodeURIComponent(element.commentId) + + DOCX_COMMENT_TOKEN_SUFFIX; + return [Html.text(token)]; + }, + inserted(element, messages, options) { + var children = convertElements(element.children, messages, options); + // Use Word's original changeId if available, otherwise generate one + var changeId = element.changeId || 'ins-' + trackedChangeIdCounter++; + var payload = encodeTrackedChangePayload({ + id: changeId, + author: element.author, + date: element.date, + }); + var startToken = + DOCX_INSERTION_START_TOKEN_PREFIX + + payload + + DOCX_INSERTION_TOKEN_SUFFIX; + var endToken = + DOCX_INSERTION_END_TOKEN_PREFIX + + changeId + + DOCX_INSERTION_TOKEN_SUFFIX; + return [Html.text(startToken)] + .concat(children) + .concat([Html.text(endToken)]); + }, + deleted(element, messages, options) { + var children = convertElements(element.children, messages, options); + // Use Word's original changeId if available, otherwise generate one + var changeId = element.changeId || 'del-' + trackedChangeIdCounter++; + var payload = encodeTrackedChangePayload({ + id: changeId, + author: element.author, + date: element.date, + }); + var startToken = + DOCX_DELETION_START_TOKEN_PREFIX + payload + DOCX_DELETION_TOKEN_SUFFIX; + var endToken = + DOCX_DELETION_END_TOKEN_PREFIX + changeId + DOCX_DELETION_TOKEN_SUFFIX; + return [Html.text(startToken)] + .concat(children) + .concat([Html.text(endToken)]); + }, + image: deferredConversion( + recoveringConvertImage(options.convertImage || images.dataUri) + ), + table: convertTable, + tableRow: convertTableRow, + tableCell: convertTableCell, + break: convertBreak, + }; + return { + convertToHtml, + }; +} + +var deferredId = 1; + +function encodeTrackedChangePayload(payload) { + return encodeURIComponent(JSON.stringify(payload)); +} + +function extractTextFromElements(elements) { + var text = ''; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.type === 'text') { + text += element.value; + } else if (element.type === 'paragraph') { + text += extractTextFromElements(element.children || []) + '\n'; + } else if (element.children) { + text += extractTextFromElements(element.children); + } + } + return text; +} + +function deferredConversion(func) { + return (element, messages, options) => [ + { + type: 'deferred', + id: deferredId++, + value() { + return func(element, messages, options); + }, + }, + ]; +} + +function unrecognisedStyleWarning(type, element) { + return results.warning( + 'Unrecognised ' + + type + + " style: '" + + element.styleName + + "'" + + ' (Style ID: ' + + element.styleId + + ')' + ); +} + +function flatMap(values, func) { + return _.flatten(values.map(func), true); +} + +function walkHtml(nodes, callback) { + nodes.forEach((node) => { + callback(node); + if (node.children) { + walkHtml(node.children, callback); + } + }); +} + +var commentAuthorLabel = (exports.commentAuthorLabel = + function commentAuthorLabel(comment) { + return comment.authorInitials || ''; + }); + +},{"./documents":4,"./html":19,"./images":21,"./promises":24,"./results":26,"./styles/html-paths":29,"./writers":34,"underscore":104}],4:[function(require,module,exports){ +(function (Buffer){(function (){ +var _ = require('underscore'); + +var types = (exports.types = { + document: 'document', + paragraph: 'paragraph', + run: 'run', + text: 'text', + tab: 'tab', + checkbox: 'checkbox', + hyperlink: 'hyperlink', + noteReference: 'noteReference', + image: 'image', + note: 'note', + commentReference: 'commentReference', + comment: 'comment', + commentRangeStart: 'commentRangeStart', + commentRangeEnd: 'commentRangeEnd', + inserted: 'inserted', + deleted: 'deleted', + table: 'table', + tableRow: 'tableRow', + tableCell: 'tableCell', + break: 'break', + bookmarkStart: 'bookmarkStart', +}); + +function Document(children, options) { + options = options || {}; + return { + type: types.document, + children, + notes: options.notes || new Notes({}), + comments: options.comments || [], + }; +} + +function Paragraph(children, properties) { + properties = properties || {}; + var indent = properties.indent || {}; + return { + type: types.paragraph, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + numbering: properties.numbering || null, + alignment: properties.alignment || null, + indent: { + start: indent.start || null, + end: indent.end || null, + firstLine: indent.firstLine || null, + hanging: indent.hanging || null, + }, + paraId: properties.paraId || null, + }; +} + +function Run(children, properties) { + properties = properties || {}; + return { + type: types.run, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + isBold: !!properties.isBold, + isUnderline: !!properties.isUnderline, + isItalic: !!properties.isItalic, + isStrikethrough: !!properties.isStrikethrough, + isAllCaps: !!properties.isAllCaps, + isSmallCaps: !!properties.isSmallCaps, + verticalAlignment: + properties.verticalAlignment || verticalAlignment.baseline, + font: properties.font || null, + fontSize: properties.fontSize || null, + highlight: properties.highlight || null, + }; +} + +var verticalAlignment = { + baseline: 'baseline', + superscript: 'superscript', + subscript: 'subscript', +}; + +function Text(value) { + return { + type: types.text, + value, + }; +} + +function Tab() { + return { + type: types.tab, + }; +} + +function Checkbox(options) { + return { + type: types.checkbox, + checked: options.checked, + }; +} + +function Hyperlink(children, options) { + return { + type: types.hyperlink, + children, + href: options.href, + anchor: options.anchor, + targetFrame: options.targetFrame, + }; +} + +function NoteReference(options) { + return { + type: types.noteReference, + noteType: options.noteType, + noteId: options.noteId, + }; +} + +function Notes(notes) { + this._notes = _.indexBy(notes, (note) => noteKey(note.noteType, note.noteId)); +} + +Notes.prototype.resolve = function (reference) { + return this.findNoteByKey(noteKey(reference.noteType, reference.noteId)); +}; + +Notes.prototype.findNoteByKey = function (key) { + return this._notes[key] || null; +}; + +function Note(options) { + return { + type: types.note, + noteType: options.noteType, + noteId: options.noteId, + body: options.body, + }; +} + +function commentReference(options) { + return { + type: types.commentReference, + commentId: options.commentId, + }; +} + +function comment(options) { + return { + type: types.comment, + commentId: options.commentId, + body: options.body, + authorName: options.authorName || null, + authorInitials: options.authorInitials || null, + date: options.date || null, + paraId: options.paraId || null, + parentParaId: options.parentParaId || null, + }; +} + +function commentRangeStart(options) { + return { + type: types.commentRangeStart, + commentId: options.commentId, + }; +} + +function commentRangeEnd(options) { + return { + type: types.commentRangeEnd, + commentId: options.commentId, + }; +} + +function inserted(children, options) { + options = options || {}; + return { + type: types.inserted, + children, + author: options.author || null, + date: options.date || null, + changeId: options.changeId || null, + }; +} + +function deleted(children, options) { + options = options || {}; + return { + type: types.deleted, + children, + author: options.author || null, + date: options.date || null, + changeId: options.changeId || null, + }; +} + +function noteKey(noteType, id) { + return noteType + '-' + id; +} + +function Image(options) { + return { + type: types.image, + // `read` is retained for backwards compatibility, but other read + // methods should be preferred. + read(encoding) { + if (encoding) { + return options.readImage(encoding); + } + return options + .readImage() + .then((arrayBuffer) => Buffer.from(arrayBuffer)); + }, + readAsArrayBuffer() { + return options.readImage(); + }, + readAsBase64String() { + return options.readImage('base64'); + }, + readAsBuffer() { + return options + .readImage() + .then((arrayBuffer) => Buffer.from(arrayBuffer)); + }, + altText: options.altText, + contentType: options.contentType, + }; +} + +function Table(children, properties) { + properties = properties || {}; + return { + type: types.table, + children, + styleId: properties.styleId || null, + styleName: properties.styleName || null, + }; +} + +function TableRow(children, options) { + options = options || {}; + return { + type: types.tableRow, + children, + isHeader: options.isHeader || false, + }; +} + +function TableCell(children, options) { + options = options || {}; + return { + type: types.tableCell, + children, + colSpan: options.colSpan == null ? 1 : options.colSpan, + rowSpan: options.rowSpan == null ? 1 : options.rowSpan, + }; +} + +function Break(breakType) { + return { + type: types['break'], + breakType, + }; +} + +function BookmarkStart(options) { + return { + type: types.bookmarkStart, + name: options.name, + }; +} + +exports.document = exports.Document = Document; +exports.paragraph = exports.Paragraph = Paragraph; +exports.run = exports.Run = Run; +exports.text = exports.Text = Text; +exports.tab = exports.Tab = Tab; +exports.checkbox = exports.Checkbox = Checkbox; +exports.Hyperlink = Hyperlink; +exports.noteReference = exports.NoteReference = NoteReference; +exports.Notes = Notes; +exports.Note = Note; +exports.commentReference = commentReference; +exports.comment = comment; +exports.commentRangeStart = commentRangeStart; +exports.commentRangeEnd = commentRangeEnd; +exports.inserted = inserted; +exports.deleted = deleted; +exports.Image = Image; +exports.Table = Table; +exports.TableRow = TableRow; +exports.TableCell = TableCell; +exports.lineBreak = Break('line'); +exports.pageBreak = Break('page'); +exports.columnBreak = Break('column'); +exports.BookmarkStart = BookmarkStart; + +exports.verticalAlignment = verticalAlignment; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"buffer":84,"underscore":104}],5:[function(require,module,exports){ +exports.createBodyReader = createBodyReader; +exports._readNumberingProperties = readNumberingProperties; + +var dingbatToUnicode = require('dingbat-to-unicode'); +var _ = require('underscore'); + +var documents = require('../documents'); +var Result = require('../results').Result; +var warning = require('../results').warning; +var xml = require('../xml'); +var transforms = require('../transforms'); +var uris = require('./uris'); + +function createBodyReader(options) { + return { + readXmlElement(element) { + return new BodyReader(options).readXmlElement(element); + }, + readXmlElements(elements) { + return new BodyReader(options).readXmlElements(elements); + }, + }; +} + +function BodyReader(options) { + var complexFieldStack = []; + var currentInstrText = []; + + // When a paragraph is marked as deleted, its contents should be combined + // with the following paragraph. See 17.13.5.15 del (Deleted Paragraph) of + // ECMA-376 4th edition Part 1. + var deletedParagraphContents = []; + + var relationships = options.relationships; + var contentTypes = options.contentTypes; + var docxFile = options.docxFile; + var files = options.files; + var numbering = options.numbering; + var styles = options.styles; + + function readXmlElements(elements) { + var results = elements.map(readXmlElement); + return combineResults(results); + } + + function readXmlElement(element) { + if (element.type === 'element') { + var handler = xmlElementReaders[element.name]; + if (handler) { + return handler(element); + } + if (!Object.hasOwn(ignoreElements, element.name)) { + var message = warning( + 'An unrecognised element was ignored: ' + element.name + ); + return emptyResultWithMessages([message]); + } + } + return emptyResult(); + } + + function readParagraphProperties(element) { + return readParagraphStyle(element).map((style) => ({ + type: 'paragraphProperties', + styleId: style.styleId, + styleName: style.name, + alignment: element.firstOrEmpty('w:jc').attributes['w:val'], + numbering: readNumberingProperties( + style.styleId, + element.firstOrEmpty('w:numPr'), + numbering + ), + indent: readParagraphIndent(element.firstOrEmpty('w:ind')), + })); + } + + function readParagraphIndent(element) { + return { + start: element.attributes['w:start'] || element.attributes['w:left'], + end: element.attributes['w:end'] || element.attributes['w:right'], + firstLine: element.attributes['w:firstLine'], + hanging: element.attributes['w:hanging'], + }; + } + + function readRunProperties(element) { + return readRunStyle(element).map((style) => { + var fontSizeString = element.firstOrEmpty('w:sz').attributes['w:val']; + // w:sz gives the font size in half points, so halve the value to get the size in points + var fontSize = /^[0-9]+$/.test(fontSizeString) + ? Number.parseInt(fontSizeString, 10) / 2 + : null; + + return { + type: 'runProperties', + styleId: style.styleId, + styleName: style.name, + verticalAlignment: + element.firstOrEmpty('w:vertAlign').attributes['w:val'], + font: element.firstOrEmpty('w:rFonts').attributes['w:ascii'], + fontSize, + isBold: readBooleanElement(element.first('w:b')), + isUnderline: readUnderline(element.first('w:u')), + isItalic: readBooleanElement(element.first('w:i')), + isStrikethrough: readBooleanElement(element.first('w:strike')), + isAllCaps: readBooleanElement(element.first('w:caps')), + isSmallCaps: readBooleanElement(element.first('w:smallCaps')), + highlight: readHighlightValue( + element.firstOrEmpty('w:highlight').attributes['w:val'] + ), + }; + }); + } + + function readUnderline(element) { + if (element) { + var value = element.attributes['w:val']; + return ( + value !== undefined && + value !== 'false' && + value !== '0' && + value !== 'none' + ); + } + return false; + } + + function readBooleanElement(element) { + if (element) { + var value = element.attributes['w:val']; + return value !== 'false' && value !== '0'; + } + return false; + } + + function readBooleanAttributeValue(value) { + return value !== 'false' && value !== '0'; + } + + function readHighlightValue(value) { + if (!value || value === 'none') { + return null; + } + return value; + } + + function readParagraphStyle(element) { + return readStyle( + element, + 'w:pStyle', + 'Paragraph', + styles.findParagraphStyleById + ); + } + + function readRunStyle(element) { + return readStyle(element, 'w:rStyle', 'Run', styles.findCharacterStyleById); + } + + function readTableStyle(element) { + return readStyle(element, 'w:tblStyle', 'Table', styles.findTableStyleById); + } + + function readStyle(element, styleTagName, styleType, findStyleById) { + var messages = []; + var styleElement = element.first(styleTagName); + var styleId = null; + var name = null; + if (styleElement) { + styleId = styleElement.attributes['w:val']; + if (styleId) { + var style = findStyleById(styleId); + if (style) { + name = style.name; + } else { + messages.push(undefinedStyleWarning(styleType, styleId)); + } + } + } + return elementResultWithMessages({ styleId, name }, messages); + } + + function readFldChar(element) { + var type = element.attributes['w:fldCharType']; + if (type === 'begin') { + complexFieldStack.push({ type: 'begin', fldChar: element }); + currentInstrText = []; + } else if (type === 'end') { + var complexFieldEnd = complexFieldStack.pop(); + if (complexFieldEnd.type === 'begin') { + complexFieldEnd = parseCurrentInstrText(complexFieldEnd); + } + if (complexFieldEnd.type === 'checkbox') { + return elementResult( + documents.checkbox({ + checked: complexFieldEnd.checked, + }) + ); + } + } else if (type === 'separate') { + var complexFieldSeparate = complexFieldStack.pop(); + var complexField = parseCurrentInstrText(complexFieldSeparate); + complexFieldStack.push(complexField); + } + return emptyResult(); + } + + function currentHyperlinkOptions() { + var topHyperlink = _.last( + complexFieldStack.filter( + (complexField) => complexField.type === 'hyperlink' + ) + ); + return topHyperlink ? topHyperlink.options : null; + } + + function parseCurrentInstrText(complexField) { + return parseInstrText( + currentInstrText.join(''), + complexField.type === 'begin' ? complexField.fldChar : xml.emptyElement + ); + } + + function parseInstrText(instrText, fldChar) { + var externalLinkResult = /\s*HYPERLINK "(.*)"/.exec(instrText); + if (externalLinkResult) { + return { type: 'hyperlink', options: { href: externalLinkResult[1] } }; + } + + var internalLinkResult = /\s*HYPERLINK\s+\\l\s+"(.*)"/.exec(instrText); + if (internalLinkResult) { + return { type: 'hyperlink', options: { anchor: internalLinkResult[1] } }; + } + + var checkboxResult = /\s*FORMCHECKBOX\s*/.exec(instrText); + if (checkboxResult) { + var checkboxElement = fldChar + .firstOrEmpty('w:ffData') + .firstOrEmpty('w:checkBox'); + var checkedElement = checkboxElement.first('w:checked'); + var checked = + checkedElement == null + ? readBooleanElement(checkboxElement.first('w:default')) + : readBooleanElement(checkedElement); + return { type: 'checkbox', checked }; + } + + return { type: 'unknown' }; + } + + function readInstrText(element) { + currentInstrText.push(element.text()); + return emptyResult(); + } + + function readSymbol(element) { + // See 17.3.3.30 sym (Symbol Character) of ECMA-376 4th edition Part 1 + var font = element.attributes['w:font']; + var char = element.attributes['w:char']; + var unicodeCharacter = dingbatToUnicode.hex(font, char); + if (unicodeCharacter == null && /^F0..$/.test(char)) { + unicodeCharacter = dingbatToUnicode.hex(font, char.substring(2)); + } + + if (unicodeCharacter == null) { + return emptyResultWithMessages([ + warning( + 'A w:sym element with an unsupported character was ignored: char ' + + char + + ' in font ' + + font + ), + ]); + } + return elementResult(new documents.Text(unicodeCharacter.string)); + } + + function noteReferenceReader(noteType) { + return (element) => { + var noteId = element.attributes['w:id']; + return elementResult( + new documents.NoteReference({ + noteType, + noteId, + }) + ); + }; + } + + function readCommentReference(element) { + return elementResult( + documents.commentReference({ + commentId: element.attributes['w:id'], + }) + ); + } + + function readChildElements(element) { + return readXmlElements(element.children); + } + + var xmlElementReaders = { + 'w:p'(element) { + var paragraphPropertiesElement = element.firstOrEmpty('w:pPr'); + + var isDeleted = !!paragraphPropertiesElement + .firstOrEmpty('w:rPr') + .first('w:del'); + + if (isDeleted) { + element.children.forEach((child) => { + deletedParagraphContents.push(child); + }); + return emptyResult(); + } + var childrenXml = element.children; + if (deletedParagraphContents.length > 0) { + childrenXml = deletedParagraphContents.concat(childrenXml); + deletedParagraphContents = []; + } + return ReadResult.map( + readParagraphProperties(paragraphPropertiesElement), + readXmlElements(childrenXml), + (properties, children) => { + properties.paraId = element.attributes['wordml:paraId']; + return new documents.Paragraph(children, properties); + } + ).insertExtra(); + }, + 'w:r'(element) { + return ReadResult.map( + readRunProperties(element.firstOrEmpty('w:rPr')), + readXmlElements(element.children), + (properties, children) => { + var hyperlinkOptions = currentHyperlinkOptions(); + if (hyperlinkOptions !== null) { + children = [new documents.Hyperlink(children, hyperlinkOptions)]; + } + + return new documents.Run(children, properties); + } + ); + }, + 'w:fldChar': readFldChar, + 'w:instrText': readInstrText, + 'w:t'(element) { + return elementResult(new documents.Text(element.text())); + }, + 'w:tab'(element) { + return elementResult(new documents.Tab()); + }, + 'w:noBreakHyphen'() { + return elementResult(new documents.Text('\u2011')); + }, + 'w:softHyphen'(element) { + return elementResult(new documents.Text('\u00AD')); + }, + 'w:sym': readSymbol, + 'w:hyperlink'(element) { + var relationshipId = element.attributes['r:id']; + var anchor = element.attributes['w:anchor']; + return readXmlElements(element.children).map((children) => { + function create(options) { + var targetFrame = element.attributes['w:tgtFrame'] || null; + + return new documents.Hyperlink( + children, + _.extend({ targetFrame }, options) + ); + } + + if (relationshipId) { + var href = relationships.findTargetByRelationshipId(relationshipId); + if (anchor) { + href = uris.replaceFragment(href, anchor); + } + return create({ href }); + } + if (anchor) { + return create({ anchor }); + } + return children; + }); + }, + 'w:tbl': readTable, + 'w:tr': readTableRow, + 'w:tc': readTableCell, + 'w:footnoteReference': noteReferenceReader('footnote'), + 'w:endnoteReference': noteReferenceReader('endnote'), + 'w:commentReference': readCommentReference, + 'w:br'(element) { + var breakType = element.attributes['w:type']; + if (breakType == null || breakType === 'textWrapping') { + return elementResult(documents.lineBreak); + } + if (breakType === 'page') { + return elementResult(documents.pageBreak); + } + if (breakType === 'column') { + return elementResult(documents.columnBreak); + } + return emptyResultWithMessages([ + warning('Unsupported break type: ' + breakType), + ]); + }, + 'w:bookmarkStart'(element) { + var name = element.attributes['w:name']; + if (name === '_GoBack') { + return emptyResult(); + } + return elementResult(new documents.BookmarkStart({ name })); + }, + + 'mc:AlternateContent'(element) { + return readChildElements(element.firstOrEmpty('mc:Fallback')); + }, + + 'w:sdt'(element) { + var contentResult = readXmlElements( + element.firstOrEmpty('w:sdtContent').children + ); + return contentResult.map((content) => { + // From the WordML standard: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/3350cb64-931f-41f7-8824-f18b2568ce66 + // + // > A CT_SdtCheckbox element that specifies that the parent + // > structured document tag is a checkbox when displayed in the + // > document. The parent structured document tag contents MUST + // > contain a single character and optionally an additional + // > character in a deleted run. + + var checkbox = element.firstOrEmpty('w:sdtPr').first('wordml:checkbox'); + + if (checkbox) { + var checkedElement = checkbox.first('wordml:checked'); + var isChecked = + !!checkedElement && + readBooleanAttributeValue(checkedElement.attributes['wordml:val']); + var documentCheckbox = documents.checkbox({ + checked: isChecked, + }); + + var hasCheckbox = false; + var replacedContent = content.map( + transforms._elementsOfType(documents.types.text, (text) => { + if (text.value.length > 0 && !hasCheckbox) { + hasCheckbox = true; + return documentCheckbox; + } + return text; + }) + ); + + if (hasCheckbox) { + return replacedContent; + } + return documentCheckbox; + } + return content; + }); + }, + + // Tracked changes: wrap content in documents.inserted with author/date/changeId metadata + 'w:ins'(element) { + var author = element.attributes['w:author']; + var date = element.attributes['w:date']; + var changeId = element.attributes['w:id']; + return readXmlElements(element.children).map((children) => + documents.inserted(children, { + author, + date, + changeId, + }) + ); + }, + // Tracked deletions: wrap content in documents.deleted with author/date/changeId metadata + 'w:del'(element) { + var author = element.attributes['w:author']; + var date = element.attributes['w:date']; + var changeId = element.attributes['w:id']; + return readXmlElements(element.children).map((children) => + documents.deleted(children, { + author, + date, + changeId, + }) + ); + }, + // Handle deleted text within w:del elements + 'w:delText'(element) { + return elementResult(new documents.Text(element.text())); + }, + 'w:commentRangeStart'(element) { + return elementResult( + documents.commentRangeStart({ + commentId: element.attributes['w:id'], + }) + ); + }, + 'w:commentRangeEnd'(element) { + return elementResult( + documents.commentRangeEnd({ + commentId: element.attributes['w:id'], + }) + ); + }, + 'w:object': readChildElements, + 'w:smartTag': readChildElements, + 'w:drawing': readChildElements, + 'w:pict'(element) { + return readChildElements(element).toExtra(); + }, + 'v:roundrect': readChildElements, + 'v:shape': readChildElements, + 'v:textbox': readChildElements, + 'w:txbxContent': readChildElements, + 'wp:inline': readDrawingElement, + 'wp:anchor': readDrawingElement, + 'v:imagedata': readImageData, + 'v:group': readChildElements, + 'v:rect': readChildElements, + }; + + return { + readXmlElement, + readXmlElements, + }; + + function readTable(element) { + var propertiesResult = readTableProperties(element.firstOrEmpty('w:tblPr')); + return readXmlElements(element.children) + .flatMap(calculateRowSpans) + .flatMap((children) => + propertiesResult.map((properties) => + documents.Table(children, properties) + ) + ); + } + + function readTableProperties(element) { + return readTableStyle(element).map((style) => ({ + styleId: style.styleId, + styleName: style.name, + })); + } + + function readTableRow(element) { + var properties = element.firstOrEmpty('w:trPr'); + + // See 17.13.5.12 del (Deleted Table Row) of ECMA-376 4th edition Part 1 + var isDeleted = !!properties.first('w:del'); + if (isDeleted) { + return emptyResult(); + } + + var isHeader = !!properties.first('w:tblHeader'); + return readXmlElements(element.children).map((children) => + documents.TableRow(children, { isHeader }) + ); + } + + function readTableCell(element) { + return readXmlElements(element.children).map((children) => { + var properties = element.firstOrEmpty('w:tcPr'); + + var gridSpan = properties.firstOrEmpty('w:gridSpan').attributes['w:val']; + var colSpan = gridSpan ? Number.parseInt(gridSpan, 10) : 1; + + var cell = documents.TableCell(children, { colSpan }); + cell._vMerge = readVMerge(properties); + return cell; + }); + } + + function readVMerge(properties) { + var element = properties.first('w:vMerge'); + if (element) { + var val = element.attributes['w:val']; + return val === 'continue' || !val; + } + return null; + } + + function calculateRowSpans(rows) { + var unexpectedNonRows = _.any( + rows, + (row) => row.type !== documents.types.tableRow + ); + if (unexpectedNonRows) { + removeVMergeProperties(rows); + return elementResultWithMessages(rows, [ + warning( + 'unexpected non-row element in table, cell merging may be incorrect' + ), + ]); + } + var unexpectedNonCells = _.any(rows, (row) => + _.any(row.children, (cell) => cell.type !== documents.types.tableCell) + ); + if (unexpectedNonCells) { + removeVMergeProperties(rows); + return elementResultWithMessages(rows, [ + warning( + 'unexpected non-cell element in table row, cell merging may be incorrect' + ), + ]); + } + + var columns = {}; + + rows.forEach((row) => { + var cellIndex = 0; + row.children.forEach((cell) => { + if (cell._vMerge && columns[cellIndex]) { + columns[cellIndex].rowSpan++; + } else { + columns[cellIndex] = cell; + cell._vMerge = false; + } + cellIndex += cell.colSpan; + }); + }); + + rows.forEach((row) => { + row.children = row.children.filter((cell) => !cell._vMerge); + row.children.forEach((cell) => { + delete cell._vMerge; + }); + }); + + return elementResult(rows); + } + + function removeVMergeProperties(rows) { + rows.forEach((row) => { + var cells = transforms.getDescendantsOfType( + row, + documents.types.tableCell + ); + cells.forEach((cell) => { + delete cell._vMerge; + }); + }); + } + + function readDrawingElement(element) { + var blips = element + .getElementsByTagName('a:graphic') + .getElementsByTagName('a:graphicData') + .getElementsByTagName('pic:pic') + .getElementsByTagName('pic:blipFill') + .getElementsByTagName('a:blip'); + + return combineResults(blips.map(readBlip.bind(null, element))); + } + + function readBlip(element, blip) { + var propertiesElement = element.firstOrEmpty('wp:docPr'); + var properties = propertiesElement.attributes; + + var altText = isBlank(properties.descr) + ? properties.title + : properties.descr; + + var blipImageFile = findBlipImageFile(blip); + if (blipImageFile === null) { + return emptyResultWithMessages([ + warning('Could not find image file for a:blip element'), + ]); + } + + return readImage(blipImageFile, altText).map((imageElement) => { + var hlinkClickElement = propertiesElement.firstOrEmpty('a:hlinkClick'); + var relationshipId = hlinkClickElement.attributes['r:id']; + if (relationshipId) { + var href = relationships.findTargetByRelationshipId(relationshipId); + return new documents.Hyperlink([imageElement], { href }); + } + return imageElement; + }); + } + + function isBlank(value) { + return value == null || /^\s*$/.test(value); + } + + function findBlipImageFile(blip) { + var embedRelationshipId = blip.attributes['r:embed']; + var linkRelationshipId = blip.attributes['r:link']; + if (embedRelationshipId) { + return findEmbeddedImageFile(embedRelationshipId); + } + if (linkRelationshipId) { + var imagePath = + relationships.findTargetByRelationshipId(linkRelationshipId); + return { + path: imagePath, + read: files.read.bind(files, imagePath), + }; + } + return null; + } + + function readImageData(element) { + var relationshipId = element.attributes['r:id']; + + if (relationshipId) { + return readImage( + findEmbeddedImageFile(relationshipId), + element.attributes['o:title'] + ); + } + return emptyResultWithMessages([ + warning('A v:imagedata element without a relationship ID was ignored'), + ]); + } + + function findEmbeddedImageFile(relationshipId) { + var path = uris.uriToZipEntryName( + 'word', + relationships.findTargetByRelationshipId(relationshipId) + ); + return { + path, + read: docxFile.read.bind(docxFile, path), + }; + } + + function readImage(imageFile, altText) { + var contentType = contentTypes.findContentType(imageFile.path); + + var image = documents.Image({ + readImage: imageFile.read, + altText, + contentType, + }); + var warnings = supportedImageTypes[contentType] + ? [] + : [ + warning( + 'Image of type ' + + contentType + + ' is unlikely to display in web browsers' + ), + ]; + return elementResultWithMessages(image, warnings); + } + + function undefinedStyleWarning(type, styleId) { + return warning( + type + + ' style with ID ' + + styleId + + ' was referenced but not defined in the document' + ); + } +} + +function readNumberingProperties(styleId, element, numbering) { + var level = element.firstOrEmpty('w:ilvl').attributes['w:val']; + var numId = element.firstOrEmpty('w:numId').attributes['w:val']; + if (level !== undefined && numId !== undefined) { + return numbering.findLevel(numId, level); + } + + if (styleId != null) { + var levelByStyleId = numbering.findLevelByParagraphStyleId(styleId); + if (levelByStyleId != null) { + return levelByStyleId; + } + } + + // Some malformed documents define numbering levels without an index, and + // reference the numbering using a w:numPr element without a w:ilvl child. + // To handle such cases, we assume a level of 0 as a fallback. + if (numId !== undefined) { + return numbering.findLevel(numId, '0'); + } + + return null; +} + +var supportedImageTypes = { + 'image/png': true, + 'image/gif': true, + 'image/jpeg': true, + 'image/svg+xml': true, + 'image/tiff': true, +}; + +var ignoreElements = { + 'office-word:wrap': true, + 'v:shadow': true, + 'v:shapetype': true, + 'w:annotationRef': true, + 'w:bookmarkEnd': true, + 'w:sectPr': true, + 'w:proofErr': true, + 'w:lastRenderedPageBreak': true, + // w:commentRangeStart, w:commentRangeEnd are now handled by xmlElementReaders + // w:del and w:ins are now handled by xmlElementReaders for tracked changes support + 'w:footnoteRef': true, + 'w:endnoteRef': true, + 'w:pPr': true, + 'w:rPr': true, + 'w:tblPr': true, + 'w:tblGrid': true, + 'w:trPr': true, + 'w:tcPr': true, +}; + +function emptyResultWithMessages(messages) { + return new ReadResult(null, null, messages); +} + +function emptyResult() { + return new ReadResult(null); +} + +function elementResult(element) { + return new ReadResult(element); +} + +function elementResultWithMessages(element, messages) { + return new ReadResult(element, null, messages); +} + +function ReadResult(element, extra, messages) { + this.value = element || []; + this.extra = extra || []; + this._result = new Result( + { + element: this.value, + extra, + }, + messages + ); + this.messages = this._result.messages; +} + +ReadResult.prototype.toExtra = function () { + return new ReadResult( + null, + joinElements(this.extra, this.value), + this.messages + ); +}; + +ReadResult.prototype.insertExtra = function () { + var extra = this.extra; + if (extra && extra.length) { + return new ReadResult(joinElements(this.value, extra), null, this.messages); + } + return this; +}; + +ReadResult.prototype.map = function (func) { + var result = this._result.map((value) => func(value.element)); + return new ReadResult(result.value, this.extra, result.messages); +}; + +ReadResult.prototype.flatMap = function (func) { + var result = this._result.flatMap((value) => func(value.element)._result); + return new ReadResult( + result.value.element, + joinElements(this.extra, result.value.extra), + result.messages + ); +}; + +ReadResult.map = (first, second, func) => + new ReadResult( + func(first.value, second.value), + joinElements(first.extra, second.extra), + first.messages.concat(second.messages) + ); + +function combineResults(results) { + var result = Result.combine(_.pluck(results, '_result')); + return new ReadResult( + _.flatten(_.pluck(result.value, 'element')), + _.filter(_.flatten(_.pluck(result.value, 'extra')), identity), + result.messages + ); +} + +function joinElements(first, second) { + return _.flatten([first, second]); +} + +function identity(value) { + return value; +} + +},{"../documents":4,"../results":26,"../transforms":31,"../xml":36,"./uris":17,"dingbat-to-unicode":86,"underscore":104}],6:[function(require,module,exports){ +var documents = require('../documents'); +var Result = require('../results').Result; + +function createCommentsExtendedReader(bodyReader) { + function readCommentsExtendedXml(element) { + var mappings = {}; + element.children.forEach((child) => { + if (child.name === 'w15:commentEx') { + var paraId = child.attributes['w15:paraId']; + var parentParaId = child.attributes['w15:paraIdParent']; + var done = child.attributes['w15:done']; + if (paraId && parentParaId) { + mappings[paraId] = parentParaId; + } + } + }); + return new Result(mappings); + } + + return readCommentsExtendedXml; +} + +exports.createCommentsExtendedReader = createCommentsExtendedReader; + +},{"../documents":4,"../results":26}],7:[function(require,module,exports){ +var documents = require('../documents'); +var Result = require('../results').Result; + +function createCommentsReader(bodyReader, commentsExtended, dateUtcMap) { + commentsExtended = commentsExtended || {}; + dateUtcMap = dateUtcMap || {}; + + function readCommentsXml(element) { + return Result.combine( + element.getElementsByTagName('w:comment').map(readCommentElement) + ); + } + + function readCommentElement(element) { + var id = element.attributes['w:id']; + + function readOptionalAttribute(name) { + return (element.attributes[name] || '').trim() || null; + } + + return bodyReader.readXmlElements(element.children).map((body) => { + var paraId = null; + if (body) { + for (var i = 0; i < body.length; i++) { + if (body[i].paraId) { + paraId = body[i].paraId; + break; + } + } + } + var parentParaId = paraId ? commentsExtended[paraId] : null; + + // Prefer dateUtc (real UTC from commentsExtensible.xml) over + // w:date (local time with fake Z, Word convention) + var dateFromXml = readOptionalAttribute('w:date'); + var resolvedDate = (paraId && dateUtcMap[paraId]) || dateFromXml; + + return documents.comment({ + commentId: id, + body, + authorName: readOptionalAttribute('w:author'), + authorInitials: readOptionalAttribute('w:initials'), + date: resolvedDate, + paraId, + parentParaId, + }); + }); + } + + return readCommentsXml; +} + +exports.createCommentsReader = createCommentsReader; + +},{"../documents":4,"../results":26}],8:[function(require,module,exports){ +exports.readContentTypesFromXml = readContentTypesFromXml; + +var fallbackContentTypes = { + png: 'png', + gif: 'gif', + jpeg: 'jpeg', + jpg: 'jpeg', + tif: 'tiff', + tiff: 'tiff', + bmp: 'bmp', +}; + +exports.defaultContentTypes = contentTypes({}, {}); + +function readContentTypesFromXml(element) { + var extensionDefaults = {}; + var overrides = {}; + + if (!element || !element.children) { + return contentTypes(overrides, extensionDefaults); + } + + element.children.forEach((child) => { + if (!child || !child.attributes) return; + + if (child.name === 'content-types:Default') { + extensionDefaults[child.attributes.Extension] = + child.attributes.ContentType; + } + if (child.name === 'content-types:Override') { + var name = child.attributes.PartName; + if (name && name.charAt(0) === '/') { + name = name.substring(1); + } + if (name) { + overrides[name] = child.attributes.ContentType; + } + } + }); + return contentTypes(overrides, extensionDefaults); +} + +function contentTypes(overrides, extensionDefaults) { + return { + findContentType(path) { + if (!path) return null; + + var overrideContentType = overrides[path]; + if (overrideContentType) { + return overrideContentType; + } + var pathParts = path.split('.'); + var extension = pathParts[pathParts.length - 1]; + var extensionLower = extension.toLowerCase(); + if ( + Object.hasOwn(extensionDefaults, extension) || + Object.hasOwn(extensionDefaults, extensionLower) + ) { + return ( + extensionDefaults[extension] || extensionDefaults[extensionLower] + ); + } + var fallback = fallbackContentTypes[extensionLower]; + if (fallback) { + return 'image/' + fallback; + } + return null; + }, + }; +} + +},{}],9:[function(require,module,exports){ +exports.DocumentXmlReader = DocumentXmlReader; + +var documents = require('../documents'); +var Result = require('../results').Result; + +function DocumentXmlReader(options) { + var bodyReader = options.bodyReader; + + function convertXmlToDocument(element) { + var body = element.first('w:body'); + + if (body == null) { + throw new Error( + 'Could not find the body element: are you sure this is a docx file?' + ); + } + + var result = bodyReader.readXmlElements(body.children).map( + (children) => + new documents.Document(children, { + notes: options.notes, + comments: options.comments, + }) + ); + return new Result(result.value, result.messages); + } + + return { + convertXmlToDocument, + }; +} + +},{"../documents":4,"../results":26}],10:[function(require,module,exports){ +exports.read = read; +exports._findPartPaths = findPartPaths; + +var promises = require('../promises'); +var documents = require('../documents'); +var Result = require('../results').Result; +var zipfile = require('../zipfile'); + +var readXmlFromZipFile = require('./office-xml-reader').readXmlFromZipFile; +var createBodyReader = require('./body-reader').createBodyReader; +var DocumentXmlReader = require('./document-xml-reader').DocumentXmlReader; +var relationshipsReader = require('./relationships-reader'); +var contentTypesReader = require('./content-types-reader'); +var numberingXml = require('./numbering-xml'); +var stylesReader = require('./styles-reader'); +var notesReader = require('./notes-reader'); +var commentsReader = require('./comments-reader'); +var commentsExtendedReader = require('./comments-extended-reader'); +var Files = require('./files').Files; + +function read(docxFile, input, options) { + input = input || {}; + options = options || {}; + + var files = new Files({ + externalFileAccess: options.externalFileAccess, + relativeToFile: input.path, + }); + + return promises + .props({ + contentTypes: readContentTypesFromZipFile(docxFile), + partPaths: findPartPaths(docxFile), + docxFile, + files, + }) + .also((result) => ({ + styles: readStylesFromZipFile(docxFile, result.partPaths.styles), + })) + .also((result) => ({ + numbering: readNumberingFromZipFile( + docxFile, + result.partPaths.numbering, + result.styles + ), + })) + .also((result) => ({ + commentsExtended: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsExtended + ).then((xml) => { + if (xml) { + return commentsExtendedReader.createCommentsExtendedReader()(xml); + } + return new Result({}); + }), + // Read commentsIds.xml (paraId → durableId) and + // commentsExtensible.xml (durableId → dateUtc) to build + // a paraId → dateUtc map for correcting Word's fake-Z dates. + dateUtcMap: promises + .props({ + idsXml: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsIds || 'word/commentsIds.xml' + ), + extXml: readXmlFromZipFile( + result.docxFile, + result.partPaths.commentsExtensible || 'word/commentsExtensible.xml' + ), + }) + .then((r) => { + var paraIdToDurable = {}; + if (r.idsXml) { + r.idsXml.children.forEach((child) => { + if (child.name === 'w16cid:commentId') { + var pid = child.attributes['w16cid:paraId']; + var did = child.attributes['w16cid:durableId']; + if (pid && did) paraIdToDurable[pid] = did; + } + }); + } + var durableToDateUtc = {}; + if (r.extXml) { + r.extXml.children.forEach((child) => { + if (child.name === 'w16cex:commentExtensible') { + var did = child.attributes['w16cex:durableId']; + var utc = child.attributes['w16cex:dateUtc']; + if (did && utc) durableToDateUtc[did] = utc; + } + }); + } + // Combine: paraId → durableId → dateUtc + var map = {}; + Object.keys(paraIdToDurable).forEach((pid) => { + var did = paraIdToDurable[pid]; + if (durableToDateUtc[did]) { + map[pid] = durableToDateUtc[did]; + } + }); + return new Result(map); + }), + })) + .also((result) => ({ + footnotes: readXmlFileWithBody( + result.partPaths.footnotes, + result, + (bodyReader, xml) => { + if (xml) { + return notesReader.createFootnotesReader(bodyReader)(xml); + } + return new Result([]); + } + ), + endnotes: readXmlFileWithBody( + result.partPaths.endnotes, + result, + (bodyReader, xml) => { + if (xml) { + return notesReader.createEndnotesReader(bodyReader)(xml); + } + return new Result([]); + } + ), + comments: readXmlFileWithBody( + result.partPaths.comments, + result, + (bodyReader, xml) => { + if (xml) { + return commentsReader.createCommentsReader( + bodyReader, + result.commentsExtended.value || {}, + result.dateUtcMap.value || {} + )(xml); + } + return new Result([]); + } + ), + })) + .also((result) => ({ + notes: result.footnotes.flatMap((footnotes) => + result.endnotes.map( + (endnotes) => new documents.Notes(footnotes.concat(endnotes)) + ) + ), + })) + .then((result) => + readXmlFileWithBody( + result.partPaths.mainDocument, + result, + (bodyReader, xml) => + result.notes.flatMap((notes) => + result.comments.flatMap((comments) => { + var reader = new DocumentXmlReader({ + bodyReader, + notes, + comments, + }); + return reader.convertXmlToDocument(xml); + }) + ) + ) + ); +} + +function findPartPaths(docxFile) { + return readPackageRelationships(docxFile).then((packageRelationships) => { + var mainDocumentPath = findPartPath({ + docxFile, + relationships: packageRelationships, + relationshipType: + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', + basePath: '', + fallbackPath: 'word/document.xml', + }); + + if (!docxFile.exists(mainDocumentPath)) { + throw new Error( + 'Could not find main document part. Are you sure this is a valid .docx file?' + ); + } + + return xmlFileReader({ + filename: relationshipsFilename(mainDocumentPath), + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, + })(docxFile).then((documentRelationships) => { + function findPartRelatedToMainDocument(name) { + return findPartPath({ + docxFile, + relationships: documentRelationships, + relationshipType: + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/' + + name, + basePath: zipfile.splitPath(mainDocumentPath).dirname, + fallbackPath: 'word/' + name + '.xml', + }); + } + + return { + mainDocument: mainDocumentPath, + comments: findPartRelatedToMainDocument('comments'), + commentsExtended: findPartPath({ + docxFile, + relationships: documentRelationships, + relationshipType: + 'http://schemas.microsoft.com/office/2011/relationships/commentsExtended', + basePath: zipfile.splitPath(mainDocumentPath).dirname, + fallbackPath: 'word/commentsExtended.xml', + }), + endnotes: findPartRelatedToMainDocument('endnotes'), + footnotes: findPartRelatedToMainDocument('footnotes'), + numbering: findPartRelatedToMainDocument('numbering'), + styles: findPartRelatedToMainDocument('styles'), + }; + }); + }); +} + +function findPartPath(options) { + var docxFile = options.docxFile; + var relationships = options.relationships; + var relationshipType = options.relationshipType; + var basePath = options.basePath; + var fallbackPath = options.fallbackPath; + + var targets = relationships.findTargetsByType(relationshipType); + var normalisedTargets = targets.map((target) => + stripPrefix(zipfile.joinPath(basePath, target), '/') + ); + var validTargets = normalisedTargets.filter((target) => + docxFile.exists(target) + ); + if (validTargets.length === 0) { + return fallbackPath; + } + return validTargets[0]; +} + +function stripPrefix(value, prefix) { + if (value.substring(0, prefix.length) === prefix) { + return value.substring(prefix.length); + } + return value; +} + +function xmlFileReader(options) { + return (zipFile) => + readXmlFromZipFile(zipFile, options.filename).then((element) => + element ? options.readElement(element) : options.defaultValue + ); +} + +function readXmlFileWithBody(filename, options, func) { + var readRelationshipsFromZipFile = xmlFileReader({ + filename: relationshipsFilename(filename), + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, + }); + + return readRelationshipsFromZipFile(options.docxFile).then( + (relationships) => { + var bodyReader = new createBodyReader({ + relationships, + contentTypes: options.contentTypes, + docxFile: options.docxFile, + numbering: options.numbering, + styles: options.styles, + files: options.files, + }); + return readXmlFromZipFile(options.docxFile, filename).then((xml) => + func(bodyReader, xml) + ); + } + ); +} + +function relationshipsFilename(filename) { + var split = zipfile.splitPath(filename); + return zipfile.joinPath(split.dirname, '_rels', split.basename + '.rels'); +} + +var readContentTypesFromZipFile = xmlFileReader({ + filename: '[Content_Types].xml', + readElement: contentTypesReader.readContentTypesFromXml, + defaultValue: contentTypesReader.defaultContentTypes, +}); + +function readNumberingFromZipFile(zipFile, path, styles) { + return xmlFileReader({ + filename: path, + readElement(element) { + return numberingXml.readNumberingXml(element, { styles }); + }, + defaultValue: numberingXml.defaultNumbering, + })(zipFile); +} + +function readStylesFromZipFile(zipFile, path) { + return xmlFileReader({ + filename: path, + readElement: stylesReader.readStylesXml, + defaultValue: stylesReader.defaultStyles, + })(zipFile); +} + +var readPackageRelationships = xmlFileReader({ + filename: '_rels/.rels', + readElement: relationshipsReader.readRelationships, + defaultValue: relationshipsReader.defaultValue, +}); + +},{"../documents":4,"../promises":24,"../results":26,"../zipfile":41,"./body-reader":5,"./comments-extended-reader":6,"./comments-reader":7,"./content-types-reader":8,"./document-xml-reader":9,"./files":1,"./notes-reader":11,"./numbering-xml":12,"./office-xml-reader":13,"./relationships-reader":14,"./styles-reader":16}],11:[function(require,module,exports){ +var documents = require('../documents'); +var Result = require('../results').Result; + +exports.createFootnotesReader = createReader.bind(this, 'footnote'); +exports.createEndnotesReader = createReader.bind(this, 'endnote'); + +function createReader(noteType, bodyReader) { + function readNotesXml(element) { + return Result.combine( + element + .getElementsByTagName('w:' + noteType) + .filter(isFootnoteElement) + .map(readFootnoteElement) + ); + } + + function isFootnoteElement(element) { + var type = element.attributes['w:type']; + return type !== 'continuationSeparator' && type !== 'separator'; + } + + function readFootnoteElement(footnoteElement) { + var id = footnoteElement.attributes['w:id']; + return bodyReader + .readXmlElements(footnoteElement.children) + .map((body) => documents.Note({ noteType, noteId: id, body })); + } + + return readNotesXml; +} + +},{"../documents":4,"../results":26}],12:[function(require,module,exports){ +var _ = require('underscore'); + +exports.readNumberingXml = readNumberingXml; +exports.Numbering = Numbering; +exports.defaultNumbering = new Numbering( + {}, + {}, + { + findNumberingStyleById() { + return null; + }, + } +); + +function Numbering(nums, abstractNums, styles) { + var allLevels = _.flatten( + _.values(abstractNums).map((abstractNum) => _.values(abstractNum.levels)) + ); + + var levelsByParagraphStyleId = _.indexBy( + allLevels.filter((level) => level.paragraphStyleId != null), + 'paragraphStyleId' + ); + + function findLevel(numId, level) { + var num = nums[numId]; + if (num) { + var abstractNum = abstractNums[num.abstractNumId]; + if (!abstractNum) { + return null; + } + if (abstractNum.numStyleLink == null) { + return abstractNums[num.abstractNumId].levels[level]; + } + var style = styles.findNumberingStyleById(abstractNum.numStyleLink); + return findLevel(style.numId, level); + } + return null; + } + + function findLevelByParagraphStyleId(styleId) { + return levelsByParagraphStyleId[styleId] || null; + } + + return { + findLevel, + findLevelByParagraphStyleId, + }; +} + +function readNumberingXml(root, options) { + if (!options || !options.styles) { + throw new Error('styles is missing'); + } + + var abstractNums = readAbstractNums(root); + var nums = readNums(root, abstractNums); + return new Numbering(nums, abstractNums, options.styles); +} + +function readAbstractNums(root) { + var abstractNums = {}; + root.getElementsByTagName('w:abstractNum').forEach((element) => { + var id = element.attributes['w:abstractNumId']; + abstractNums[id] = readAbstractNum(element); + }); + return abstractNums; +} + +function readAbstractNum(element) { + var levels = {}; + + // Some malformed documents define numbering levels without an index, and + // reference the numbering using a w:numPr element without a w:ilvl child. + // To handle such cases, we assume a level of 0 as a fallback. + var levelWithoutIndex = null; + + element.getElementsByTagName('w:lvl').forEach((levelElement) => { + var levelIndex = levelElement.attributes['w:ilvl']; + var numFmt = levelElement.firstOrEmpty('w:numFmt').attributes['w:val']; + var isOrdered = numFmt !== 'bullet'; + var paragraphStyleId = + levelElement.firstOrEmpty('w:pStyle').attributes['w:val']; + + if (levelIndex === undefined) { + levelWithoutIndex = { + isOrdered, + level: '0', + paragraphStyleId, + }; + } else { + levels[levelIndex] = { + isOrdered, + level: levelIndex, + paragraphStyleId, + }; + } + }); + + if ( + levelWithoutIndex !== null && + levels[levelWithoutIndex.level] === undefined + ) { + levels[levelWithoutIndex.level] = levelWithoutIndex; + } + + var numStyleLink = element.firstOrEmpty('w:numStyleLink').attributes['w:val']; + + return { levels, numStyleLink }; +} + +function readNums(root) { + var nums = {}; + root.getElementsByTagName('w:num').forEach((element) => { + var numId = element.attributes['w:numId']; + var abstractNumId = element.first('w:abstractNumId').attributes['w:val']; + nums[numId] = { abstractNumId }; + }); + return nums; +} + +},{"underscore":104}],13:[function(require,module,exports){ +var _ = require('underscore'); + +var promises = require('../promises'); +var xml = require('../xml'); + +exports.read = read; +exports.readXmlFromZipFile = readXmlFromZipFile; + +var xmlNamespaceMap = { + // Transitional format + 'http://schemas.openxmlformats.org/wordprocessingml/2006/main': 'w', + 'http://schemas.openxmlformats.org/officeDocument/2006/relationships': 'r', + 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing': + 'wp', + 'http://schemas.openxmlformats.org/drawingml/2006/main': 'a', + 'http://schemas.openxmlformats.org/drawingml/2006/picture': 'pic', + + // Strict format + 'http://purl.oclc.org/ooxml/wordprocessingml/main': 'w', + 'http://purl.oclc.org/ooxml/officeDocument/relationships': 'r', + 'http://purl.oclc.org/ooxml/drawingml/wordprocessingDrawing': 'wp', + 'http://purl.oclc.org/ooxml/drawingml/main': 'a', + 'http://purl.oclc.org/ooxml/drawingml/picture': 'pic', + + // Common + 'http://schemas.openxmlformats.org/package/2006/content-types': + 'content-types', + 'http://schemas.openxmlformats.org/package/2006/relationships': + 'relationships', + 'http://schemas.openxmlformats.org/markup-compatibility/2006': 'mc', + 'urn:schemas-microsoft-com:vml': 'v', + 'urn:schemas-microsoft-com:office:word': 'office-word', + + // [MS-DOCX]: Word Extensions to the Office Open XML (.docx) File Format + // https://learn.microsoft.com/en-us/openspecs/office_standards/ms-docx/b839fe1f-e1ca-4fa6-8c26-5954d0abbccd + 'http://schemas.microsoft.com/office/word/2010/wordml': 'wordml', + + // Word 2012 extensions (comments threading via commentsExtended.xml) + 'http://schemas.microsoft.com/office/word/2012/wordml': 'w15', + + // Word 2016 extensions (commentsIds.xml — durable IDs) + 'http://schemas.microsoft.com/office/word/2016/wordml/cid': 'w16cid', + + // Word 2018 extensions (commentsExtensible.xml — dateUtc) + 'http://schemas.microsoft.com/office/word/2018/wordml/cex': 'w16cex', +}; + +function read(xmlString) { + return xml + .readString(xmlString, xmlNamespaceMap) + .then((document) => collapseAlternateContent(document)[0]); +} + +function readXmlFromZipFile(docxFile, path) { + if (docxFile.exists(path)) { + return docxFile.read(path, 'utf-8').then(stripUtf8Bom).then(read); + } + return promises.resolve(null); +} + +function stripUtf8Bom(xmlString) { + return xmlString.replace(/^\uFEFF/g, ''); +} + +function collapseAlternateContent(node) { + if (node.type === 'element') { + if (node.name === 'mc:AlternateContent') { + return node.firstOrEmpty('mc:Fallback').children; + } + node.children = _.flatten( + node.children.map(collapseAlternateContent, true) + ); + return [node]; + } + return [node]; +} + +},{"../promises":24,"../xml":36,"underscore":104}],14:[function(require,module,exports){ +exports.readRelationships = readRelationships; +exports.defaultValue = new Relationships([]); +exports.Relationships = Relationships; + +function readRelationships(element) { + var relationships = []; + element.children.forEach((child) => { + if (child.name === 'relationships:Relationship') { + var relationship = { + relationshipId: child.attributes.Id, + target: child.attributes.Target, + type: child.attributes.Type, + }; + relationships.push(relationship); + } + }); + return new Relationships(relationships); +} + +function Relationships(relationships) { + var targetsByRelationshipId = {}; + relationships.forEach((relationship) => { + targetsByRelationshipId[relationship.relationshipId] = relationship.target; + }); + + var targetsByType = {}; + relationships.forEach((relationship) => { + if (!targetsByType[relationship.type]) { + targetsByType[relationship.type] = []; + } + targetsByType[relationship.type].push(relationship.target); + }); + + return { + findTargetByRelationshipId(relationshipId) { + return targetsByRelationshipId[relationshipId]; + }, + findTargetsByType(type) { + return targetsByType[type] || []; + }, + }; +} + +},{}],15:[function(require,module,exports){ +var _ = require('underscore'); + +var promises = require('../promises'); +var xml = require('../xml'); + +exports.writeStyleMap = writeStyleMap; +exports.readStyleMap = readStyleMap; + +var schema = 'http://schemas.zwobble.org/mammoth/style-map'; +var styleMapPath = 'mammoth/style-map'; +var styleMapAbsolutePath = '/' + styleMapPath; + +function writeStyleMap(docxFile, styleMap) { + docxFile.write(styleMapPath, styleMap); + return updateRelationships(docxFile).then(() => updateContentTypes(docxFile)); +} + +function updateRelationships(docxFile) { + var path = 'word/_rels/document.xml.rels'; + var relationshipsUri = + 'http://schemas.openxmlformats.org/package/2006/relationships'; + var relationshipElementName = '{' + relationshipsUri + '}Relationship'; + return docxFile + .read(path, 'utf8') + .then(xml.readString) + .then((relationshipsContainer) => { + var relationships = relationshipsContainer.children; + addOrUpdateElement(relationships, relationshipElementName, 'Id', { + Id: 'rMammothStyleMap', + Type: schema, + Target: styleMapAbsolutePath, + }); + + var namespaces = { '': relationshipsUri }; + return docxFile.write( + path, + xml.writeString(relationshipsContainer, namespaces) + ); + }); +} + +function updateContentTypes(docxFile) { + var path = '[Content_Types].xml'; + var contentTypesUri = + 'http://schemas.openxmlformats.org/package/2006/content-types'; + var overrideName = '{' + contentTypesUri + '}Override'; + return docxFile + .read(path, 'utf8') + .then(xml.readString) + .then((typesElement) => { + var children = typesElement.children; + addOrUpdateElement(children, overrideName, 'PartName', { + PartName: styleMapAbsolutePath, + ContentType: 'text/prs.mammoth.style-map', + }); + var namespaces = { '': contentTypesUri }; + return docxFile.write(path, xml.writeString(typesElement, namespaces)); + }); +} + +function addOrUpdateElement(elements, name, identifyingAttribute, attributes) { + var existingElement = _.find( + elements, + (element) => + element.name === name && + element.attributes[identifyingAttribute] === + attributes[identifyingAttribute] + ); + if (existingElement) { + existingElement.attributes = attributes; + } else { + elements.push(xml.element(name, attributes)); + } +} + +function readStyleMap(docxFile) { + if (docxFile.exists(styleMapPath)) { + return docxFile.read(styleMapPath, 'utf8'); + } + return promises.resolve(null); +} + +},{"../promises":24,"../xml":36,"underscore":104}],16:[function(require,module,exports){ +exports.readStylesXml = readStylesXml; +exports.Styles = Styles; +exports.defaultStyles = new Styles({}, {}, {}, {}); + +function Styles( + paragraphStyles, + characterStyles, + tableStyles, + numberingStyles +) { + return { + findParagraphStyleById(styleId) { + return paragraphStyles[styleId]; + }, + findCharacterStyleById(styleId) { + return characterStyles[styleId]; + }, + findTableStyleById(styleId) { + return tableStyles[styleId]; + }, + findNumberingStyleById(styleId) { + return numberingStyles[styleId]; + }, + }; +} + +Styles.EMPTY = new Styles({}, {}, {}, {}); + +function readStylesXml(root) { + var paragraphStyles = {}; + var characterStyles = {}; + var tableStyles = {}; + var numberingStyles = {}; + + var styles = { + paragraph: paragraphStyles, + character: characterStyles, + table: tableStyles, + numbering: numberingStyles, + }; + + root.getElementsByTagName('w:style').forEach((styleElement) => { + var style = readStyleElement(styleElement); + var styleSet = styles[style.type]; + + // Per 17.7.4.17 style (Style Definition) of ECMA-376 4th edition Part 1: + // + // > If multiple style definitions each declare the same value for their + // > styleId, then the first such instance shall keep its current + // > identifier with all other instances being reassigned in any manner + // > desired. + // + // For the purpose of conversion, there's no point holding onto styles + // with reassigned style IDs, so we ignore such style definitions. + + if (styleSet && styleSet[style.styleId] === undefined) { + styleSet[style.styleId] = style; + } + }); + + return new Styles( + paragraphStyles, + characterStyles, + tableStyles, + numberingStyles + ); +} + +function readStyleElement(styleElement) { + var type = styleElement.attributes['w:type']; + + if (type === 'numbering') { + return readNumberingStyleElement(type, styleElement); + } + var styleId = readStyleId(styleElement); + var name = styleName(styleElement); + return { type, styleId, name }; +} + +function styleName(styleElement) { + var nameElement = styleElement.first('w:name'); + return nameElement ? nameElement.attributes['w:val'] : null; +} + +function readNumberingStyleElement(type, styleElement) { + var styleId = readStyleId(styleElement); + + var numId = styleElement + .firstOrEmpty('w:pPr') + .firstOrEmpty('w:numPr') + .firstOrEmpty('w:numId').attributes['w:val']; + + return { type, numId, styleId }; +} + +function readStyleId(styleElement) { + return styleElement.attributes['w:styleId']; +} + +},{}],17:[function(require,module,exports){ +exports.uriToZipEntryName = uriToZipEntryName; +exports.replaceFragment = replaceFragment; + +function uriToZipEntryName(base, uri) { + if (uri.charAt(0) === '/') { + return uri.substring(1); + } + // In general, we should check first and second for trailing and leading slashes, + // but in our specific case this seems to be sufficient + return base + '/' + uri; +} + +function replaceFragment(uri, fragment) { + var hashIndex = uri.indexOf('#'); + if (hashIndex !== -1) { + uri = uri.substring(0, hashIndex); + } + return uri + '#' + fragment; +} + +},{}],18:[function(require,module,exports){ +var htmlPaths = require('../styles/html-paths'); + +function nonFreshElement(tagName, attributes, children) { + return elementWithTag( + htmlPaths.element(tagName, attributes, { fresh: false }), + children + ); +} + +function freshElement(tagName, attributes, children) { + var tag = htmlPaths.element(tagName, attributes, { fresh: true }); + return elementWithTag(tag, children); +} + +function elementWithTag(tag, children) { + return { + type: 'element', + tag, + children: children || [], + }; +} + +function text(value) { + return { + type: 'text', + value, + }; +} + +var forceWrite = { + type: 'forceWrite', +}; + +exports.freshElement = freshElement; +exports.nonFreshElement = nonFreshElement; +exports.elementWithTag = elementWithTag; +exports.text = text; +exports.forceWrite = forceWrite; + +var voidTagNames = { + br: true, + hr: true, + img: true, + input: true, +}; + +function isVoidElement(node) { + return ( + (!node.children || node.children.length === 0) && + voidTagNames[node.tag.tagName] + ); +} + +exports.isVoidElement = isVoidElement; + +},{"../styles/html-paths":29}],19:[function(require,module,exports){ +var ast = require('./ast'); + +exports.freshElement = ast.freshElement; +exports.nonFreshElement = ast.nonFreshElement; +exports.elementWithTag = ast.elementWithTag; +exports.text = ast.text; +exports.forceWrite = ast.forceWrite; + +exports.simplify = require('./simplify'); + +function write(writer, nodes) { + nodes.forEach((node) => { + writeNode(writer, node); + }); +} + +function writeNode(writer, node) { + toStrings[node.type](writer, node); +} + +var toStrings = { + element: generateElementString, + text: generateTextString, + forceWrite() {}, +}; + +function generateElementString(writer, node) { + if (ast.isVoidElement(node)) { + writer.selfClosing(node.tag.tagName, node.tag.attributes); + } else { + writer.open(node.tag.tagName, node.tag.attributes); + write(writer, node.children); + writer.close(node.tag.tagName); + } +} + +function generateTextString(writer, node) { + writer.text(node.value); +} + +exports.write = write; + +},{"./ast":18,"./simplify":20}],20:[function(require,module,exports){ +var _ = require('underscore'); + +var ast = require('./ast'); + +function simplify(nodes) { + return collapse(removeEmpty(nodes)); +} + +function collapse(nodes) { + var children = []; + + nodes.map(collapseNode).forEach((child) => { + appendChild(children, child); + }); + return children; +} + +function collapseNode(node) { + return collapsers[node.type](node); +} + +var collapsers = { + element: collapseElement, + text: identity, + forceWrite: identity, +}; + +function collapseElement(node) { + return ast.elementWithTag(node.tag, collapse(node.children)); +} + +function identity(value) { + return value; +} + +function appendChild(children, child) { + var lastChild = children[children.length - 1]; + if ( + child.type === 'element' && + !child.tag.fresh && + lastChild && + lastChild.type === 'element' && + child.tag.matchesElement(lastChild.tag) + ) { + if (child.tag.separator) { + appendChild(lastChild.children, ast.text(child.tag.separator)); + } + child.children.forEach((grandChild) => { + // Mutation is fine since simplifying elements create a copy of the children. + appendChild(lastChild.children, grandChild); + }); + } else { + children.push(child); + } +} + +function removeEmpty(nodes) { + return flatMap(nodes, (node) => emptiers[node.type](node)); +} + +function flatMap(values, func) { + return _.flatten(_.map(values, func), true); +} + +var emptiers = { + element: elementEmptier, + text: textEmptier, + forceWrite: neverEmpty, +}; + +function neverEmpty(node) { + return [node]; +} + +function elementEmptier(element) { + var children = removeEmpty(element.children); + if (children.length === 0 && !ast.isVoidElement(element)) { + return []; + } + return [ast.elementWithTag(element.tag, children)]; +} + +function textEmptier(node) { + if (node.value.length === 0) { + return []; + } + return [node]; +} + +module.exports = simplify; + +},{"./ast":18,"underscore":104}],21:[function(require,module,exports){ +var _ = require('underscore'); + +var promises = require('./promises'); +var Html = require('./html'); + +exports.imgElement = imgElement; + +function imgElement(func) { + return (element, messages) => + promises.when(func(element)).then((result) => { + var attributes = {}; + if (element.altText) { + attributes.alt = element.altText; + } + _.extend(attributes, result); + + return [Html.freshElement('img', attributes)]; + }); +} + +// Undocumented, but retained for backwards-compatibility with 0.3.x +exports.inline = exports.imgElement; + +exports.dataUri = imgElement((element) => + element.readAsBase64String().then((imageBuffer) => { + var contentType = element.contentType || 'application/octet-stream'; + return { + src: 'data:' + contentType + ';base64,' + imageBuffer, + }; + }) +); + +},{"./html":19,"./promises":24,"underscore":104}],22:[function(require,module,exports){ +(function (Buffer){(function (){ +var _ = require('underscore'); + +var docxReader = require('./docx/docx-reader'); +var docxStyleMap = require('./docx/style-map'); +var DocumentConverter = require('./document-to-html').DocumentConverter; +var convertElementToRawText = require('./raw-text').convertElementToRawText; +var readStyle = require('./style-reader').readStyle; +var readOptions = require('./options-reader').readOptions; +var unzip = require('./unzip'); +var Result = require('./results').Result; + +exports.convertToHtml = convertToHtml; +exports.convertToMarkdown = convertToMarkdown; +exports.convert = convert; +exports.extractRawText = extractRawText; +exports.images = require('./images'); +exports.transforms = require('./transforms'); +exports.underline = require('./underline'); +exports.embedStyleMap = embedStyleMap; +exports.readEmbeddedStyleMap = readEmbeddedStyleMap; + +function convertToHtml(input, options) { + return convert(input, options); +} + +function convertToMarkdown(input, options) { + var markdownOptions = Object.create(options || {}); + markdownOptions.outputFormat = 'markdown'; + return convert(input, markdownOptions); +} + +function convert(input, options) { + options = readOptions(options); + + return unzip + .openZip(input) + .tap((docxFile) => + docxStyleMap.readStyleMap(docxFile).then((styleMap) => { + options.embeddedStyleMap = styleMap; + }) + ) + .then((docxFile) => + docxReader + .read(docxFile, input, options) + .then((documentResult) => documentResult.map(options.transformDocument)) + .then((documentResult) => + convertDocumentToHtml(documentResult, options) + ) + ); +} + +function readEmbeddedStyleMap(input) { + return unzip.openZip(input).then(docxStyleMap.readStyleMap); +} + +function convertDocumentToHtml(documentResult, options) { + var styleMapResult = parseStyleMap(options.readStyleMap()); + var parsedOptions = _.extend({}, options, { + styleMap: styleMapResult.value, + }); + var documentConverter = new DocumentConverter(parsedOptions); + + return documentResult.flatMapThen((document) => + styleMapResult.flatMapThen((styleMap) => + documentConverter.convertToHtml(document) + ) + ); +} + +function parseStyleMap(styleMap) { + return Result.combine((styleMap || []).map(readStyle)).map((styleMap) => + styleMap.filter((styleMapping) => !!styleMapping) + ); +} + +function extractRawText(input) { + return unzip + .openZip(input) + .then(docxReader.read) + .then((documentResult) => documentResult.map(convertElementToRawText)); +} + +function embedStyleMap(input, styleMap) { + return unzip + .openZip(input) + .tap((docxFile) => docxStyleMap.writeStyleMap(docxFile, styleMap)) + .then((docxFile) => docxFile.toArrayBuffer()) + .then((arrayBuffer) => ({ + toArrayBuffer() { + return arrayBuffer; + }, + toBuffer() { + return Buffer.from(arrayBuffer); + }, + })); +} + +exports.styleMapping = () => { + throw new Error( + 'Use a raw string instead of mammoth.styleMapping e.g. "p[style-name=\'Title\'] => h1" instead of mammoth.styleMapping("p[style-name=\'Title\'] => h1")' + ); +}; + +}).call(this)}).call(this,require("buffer").Buffer) +},{"./document-to-html":3,"./docx/docx-reader":10,"./docx/style-map":15,"./images":21,"./options-reader":23,"./raw-text":25,"./results":26,"./style-reader":27,"./transforms":31,"./underline":32,"./unzip":2,"buffer":84,"underscore":104}],23:[function(require,module,exports){ +exports.readOptions = readOptions; + +var _ = require('underscore'); + +var defaultStyleMap = (exports._defaultStyleMap = [ + 'p.Heading1 => h1:fresh', + 'p.Heading2 => h2:fresh', + 'p.Heading3 => h3:fresh', + 'p.Heading4 => h4:fresh', + 'p.Heading5 => h5:fresh', + 'p.Heading6 => h6:fresh', + "p[style-name='Heading 1'] => h1:fresh", + "p[style-name='Heading 2'] => h2:fresh", + "p[style-name='Heading 3'] => h3:fresh", + "p[style-name='Heading 4'] => h4:fresh", + "p[style-name='Heading 5'] => h5:fresh", + "p[style-name='Heading 6'] => h6:fresh", + "p[style-name='heading 1'] => h1:fresh", + "p[style-name='heading 2'] => h2:fresh", + "p[style-name='heading 3'] => h3:fresh", + "p[style-name='heading 4'] => h4:fresh", + "p[style-name='heading 5'] => h5:fresh", + "p[style-name='heading 6'] => h6:fresh", + + // Apple Pages + 'p.Heading => h1:fresh', + "p[style-name='Heading'] => h1:fresh", + + "r[style-name='Strong'] => strong", + + "p[style-name='footnote text'] => p:fresh", + "r[style-name='footnote reference'] =>", + "p[style-name='endnote text'] => p:fresh", + "r[style-name='endnote reference'] =>", + "p[style-name='annotation text'] => p:fresh", + "r[style-name='annotation reference'] =>", + + // LibreOffice + "p[style-name='Footnote'] => p:fresh", + "r[style-name='Footnote anchor'] =>", + "p[style-name='Endnote'] => p:fresh", + "r[style-name='Endnote anchor'] =>", + + 'p:unordered-list(1) => ul > li:fresh', + 'p:unordered-list(2) => ul|ol > li > ul > li:fresh', + 'p:unordered-list(3) => ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:unordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:unordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ul > li:fresh', + 'p:ordered-list(1) => ol > li:fresh', + 'p:ordered-list(2) => ul|ol > li > ol > li:fresh', + 'p:ordered-list(3) => ul|ol > li > ul|ol > li > ol > li:fresh', + 'p:ordered-list(4) => ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh', + 'p:ordered-list(5) => ul|ol > li > ul|ol > li > ul|ol > li > ul|ol > li > ol > li:fresh', + + "r[style-name='Hyperlink'] =>", + + "p[style-name='Normal'] => p:fresh", + + // Apple Pages + 'p.Body => p:fresh', + "p[style-name='Body'] => p:fresh", +]); + +var standardOptions = (exports._standardOptions = { + externalFileAccess: false, + transformDocument: identity, + includeDefaultStyleMap: true, + includeEmbeddedStyleMap: true, +}); + +function readOptions(options) { + options = options || {}; + return _.extend({}, standardOptions, options, { + customStyleMap: readStyleMap(options.styleMap), + readStyleMap() { + var styleMap = this.customStyleMap; + if (this.includeEmbeddedStyleMap) { + styleMap = styleMap.concat(readStyleMap(this.embeddedStyleMap)); + } + if (this.includeDefaultStyleMap) { + styleMap = styleMap.concat(defaultStyleMap); + } + return styleMap; + }, + }); +} + +function readStyleMap(styleMap) { + if (!styleMap) { + return []; + } + if (_.isString(styleMap)) { + return styleMap + .split('\n') + .map((line) => line.trim()) + .filter((line) => line !== '' && line.charAt(0) !== '#'); + } + return styleMap; +} + +function identity(value) { + return value; +} + +},{"underscore":104}],24:[function(require,module,exports){ +var _ = require('underscore'); +var bluebird = require('bluebird/js/release/promise')(); + +exports.defer = defer; +exports.when = bluebird.resolve; +exports.resolve = bluebird.resolve; +exports.all = bluebird.all; +exports.props = bluebird.props; +exports.reject = bluebird.reject; +exports.promisify = bluebird.promisify; +exports.mapSeries = bluebird.mapSeries; +exports.attempt = bluebird.attempt; + +exports.nfcall = function (func) { + var args = Array.prototype.slice.call(arguments, 1); + var promisedFunc = bluebird.promisify(func); + return promisedFunc.apply(null, args); +}; + +bluebird.prototype.fail = bluebird.prototype.caught; + +bluebird.prototype.also = function (func) { + return this.then((value) => { + var returnValue = _.extend({}, value, func(value)); + return bluebird.props(returnValue); + }); +}; + +function defer() { + var resolve; + var reject; + var promise = new bluebird.Promise((resolveArg, rejectArg) => { + resolve = resolveArg; + reject = rejectArg; + }); + + return { + resolve, + reject, + promise, + }; +} + +},{"bluebird/js/release/promise":69,"underscore":104}],25:[function(require,module,exports){ +var documents = require('./documents'); + +function convertElementToRawText(element) { + if (element.type === 'text') { + return element.value; + } + if (element.type === documents.types.tab) { + return '\t'; + } + var tail = element.type === 'paragraph' ? '\n\n' : ''; + return (element.children || []).map(convertElementToRawText).join('') + tail; +} + +exports.convertElementToRawText = convertElementToRawText; + +},{"./documents":4}],26:[function(require,module,exports){ +var _ = require('underscore'); + +exports.Result = Result; +exports.success = success; +exports.warning = warning; +exports.error = error; + +function Result(value, messages) { + this.value = value; + this.messages = messages || []; +} + +Result.prototype.map = function (func) { + return new Result(func(this.value), this.messages); +}; + +Result.prototype.flatMap = function (func) { + var funcResult = func(this.value); + return new Result(funcResult.value, combineMessages([this, funcResult])); +}; + +Result.prototype.flatMapThen = function (func) { + return func(this.value).then( + (otherResult) => + new Result(otherResult.value, combineMessages([this, otherResult])) + ); +}; + +Result.combine = (results) => { + var values = _.flatten(_.pluck(results, 'value')); + var messages = combineMessages(results); + return new Result(values, messages); +}; + +function success(value) { + return new Result(value, []); +} + +function warning(message) { + return { + type: 'warning', + message, + }; +} + +function error(exception) { + return { + type: 'error', + message: exception.message, + error: exception, + }; +} + +function combineMessages(results) { + var messages = []; + _.flatten(_.pluck(results, 'messages'), true).forEach((message) => { + if (!containsMessage(messages, message)) { + messages.push(message); + } + }); + return messages; +} + +function containsMessage(messages, message) { + return _.find(messages, isSameMessage.bind(null, message)) !== undefined; +} + +function isSameMessage(first, second) { + return first.type === second.type && first.message === second.message; +} + +},{"underscore":104}],27:[function(require,module,exports){ +var _ = require('underscore'); +var lop = require('lop'); + +var documentMatchers = require('./styles/document-matchers'); +var htmlPaths = require('./styles/html-paths'); +var tokenise = require('./styles/parser/tokeniser').tokenise; +var results = require('./results'); + +exports.readHtmlPath = readHtmlPath; +exports.readDocumentMatcher = readDocumentMatcher; +exports.readStyle = readStyle; + +function readStyle(string) { + return parseString(styleRule, string); +} + +function createStyleRule() { + return lop.rules + .sequence( + lop.rules.sequence.capture(documentMatcherRule()), + lop.rules.tokenOfType('whitespace'), + lop.rules.tokenOfType('arrow'), + lop.rules.sequence.capture( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('whitespace'), + lop.rules.sequence.capture(htmlPathRule()) + ) + .head() + ) + ), + lop.rules.tokenOfType('end') + ) + .map((documentMatcher, htmlPath) => ({ + from: documentMatcher, + to: htmlPath.valueOrElse(htmlPaths.empty), + })); +} + +function readDocumentMatcher(string) { + return parseString(documentMatcherRule(), string); +} + +function documentMatcherRule() { + var sequence = lop.rules.sequence; + + var identifierToConstant = (identifier, constant) => + lop.rules.then(lop.rules.token('identifier', identifier), () => constant); + + var paragraphRule = identifierToConstant('p', documentMatchers.paragraph); + var runRule = identifierToConstant('r', documentMatchers.run); + + var elementTypeRule = lop.rules.firstOf( + 'p or r or table', + paragraphRule, + runRule + ); + + var styleIdRule = lop.rules + .sequence( + lop.rules.tokenOfType('dot'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule) + ) + .map((styleId) => ({ styleId })); + + var styleNameMatcherRule = lop.rules.firstOf( + 'style name matcher', + lop.rules.then( + lop.rules + .sequence( + lop.rules.tokenOfType('equals'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(stringRule) + ) + .head(), + (styleName) => ({ styleName: documentMatchers.equalTo(styleName) }) + ), + lop.rules.then( + lop.rules + .sequence( + lop.rules.tokenOfType('startsWith'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(stringRule) + ) + .head(), + (styleName) => ({ styleName: documentMatchers.startsWith(styleName) }) + ) + ); + + var styleNameRule = lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.token('identifier', 'style-name'), + lop.rules.sequence.capture(styleNameMatcherRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .head(); + + var listTypeRule = lop.rules.firstOf( + 'list type', + identifierToConstant('ordered-list', { isOrdered: true }), + identifierToConstant('unordered-list', { isOrdered: false }) + ); + var listRule = sequence( + lop.rules.tokenOfType('colon'), + sequence.capture(listTypeRule), + sequence.cut(), + lop.rules.tokenOfType('open-paren'), + sequence.capture(integerRule), + lop.rules.tokenOfType('close-paren') + ).map((listType, levelNumber) => ({ + list: { + isOrdered: listType.isOrdered, + levelIndex: levelNumber - 1, + }, + })); + + function createMatcherSuffixesRule(rules) { + var matcherSuffix = lop.rules.firstOf.apply( + lop.rules.firstOf, + ['matcher suffix'].concat(rules) + ); + var matcherSuffixes = lop.rules.zeroOrMore(matcherSuffix); + return lop.rules.then(matcherSuffixes, (suffixes) => { + var matcherOptions = {}; + suffixes.forEach((suffix) => { + _.extend(matcherOptions, suffix); + }); + return matcherOptions; + }); + } + + var paragraphOrRun = sequence( + sequence.capture(elementTypeRule), + sequence.capture( + createMatcherSuffixesRule([styleIdRule, styleNameRule, listRule]) + ) + ).map((createMatcher, matcherOptions) => createMatcher(matcherOptions)); + + var table = sequence( + lop.rules.token('identifier', 'table'), + sequence.capture(createMatcherSuffixesRule([styleIdRule, styleNameRule])) + ).map((options) => documentMatchers.table(options)); + + var bold = identifierToConstant('b', documentMatchers.bold); + var italic = identifierToConstant('i', documentMatchers.italic); + var underline = identifierToConstant('u', documentMatchers.underline); + var strikethrough = identifierToConstant( + 'strike', + documentMatchers.strikethrough + ); + var allCaps = identifierToConstant('all-caps', documentMatchers.allCaps); + var smallCaps = identifierToConstant( + 'small-caps', + documentMatchers.smallCaps + ); + + var highlight = sequence( + lop.rules.token('identifier', 'highlight'), + lop.rules.sequence.capture( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.token('identifier', 'color'), + lop.rules.tokenOfType('equals'), + lop.rules.sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .head() + ) + ) + ).map((color) => + documentMatchers.highlight({ + color: color.valueOrElse(undefined), + }) + ); + + var commentReference = identifierToConstant( + 'comment-reference', + documentMatchers.commentReference + ); + + var commentRangeStart = identifierToConstant( + 'comment-range-start', + documentMatchers.commentRangeStart + ); + + var commentRangeEnd = identifierToConstant( + 'comment-range-end', + documentMatchers.commentRangeEnd + ); + + var inserted = identifierToConstant('ins', documentMatchers.inserted); + + var deleted = identifierToConstant('del', documentMatchers.deleted); + + var breakMatcher = sequence( + lop.rules.token('identifier', 'br'), + sequence.cut(), + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.token('identifier', 'type'), + lop.rules.tokenOfType('equals'), + sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ).map((breakType) => { + switch (breakType) { + case 'line': + return documentMatchers.lineBreak; + case 'page': + return documentMatchers.pageBreak; + case 'column': + return documentMatchers.columnBreak; + default: + throw new Error('Unknown break type: ' + breakType); + } + }); + + return lop.rules.firstOf( + 'element type', + paragraphOrRun, + table, + bold, + italic, + underline, + strikethrough, + allCaps, + smallCaps, + highlight, + commentReference, + commentRangeStart, + commentRangeEnd, + inserted, + deleted, + breakMatcher + ); +} + +function readHtmlPath(string) { + return parseString(htmlPathRule(), string); +} + +function htmlPathRule() { + var capture = lop.rules.sequence.capture; + var whitespaceRule = lop.rules.tokenOfType('whitespace'); + var freshRule = lop.rules.then( + lop.rules.optional( + lop.rules.sequence( + lop.rules.tokenOfType('colon'), + lop.rules.token('identifier', 'fresh') + ) + ), + (option) => option.map(() => true).valueOrElse(false) + ); + + var separatorRule = lop.rules.then( + lop.rules.optional( + lop.rules + .sequence( + lop.rules.tokenOfType('colon'), + lop.rules.token('identifier', 'separator'), + lop.rules.tokenOfType('open-paren'), + capture(stringRule), + lop.rules.tokenOfType('close-paren') + ) + .head() + ), + (option) => option.valueOrElse('') + ); + + var tagNamesRule = lop.rules.oneOrMoreWithSeparator( + identifierRule, + lop.rules.tokenOfType('choice') + ); + + var styleElementRule = lop.rules + .sequence( + capture(tagNamesRule), + capture(lop.rules.zeroOrMore(attributeOrClassRule)), + capture(freshRule), + capture(separatorRule) + ) + .map((tagName, attributesList, fresh, separator) => { + var attributes = {}; + var options = {}; + attributesList.forEach((attribute) => { + if (attribute.append && attributes[attribute.name]) { + attributes[attribute.name] += ' ' + attribute.value; + } else { + attributes[attribute.name] = attribute.value; + } + }); + if (fresh) { + options.fresh = true; + } + if (separator) { + options.separator = separator; + } + return htmlPaths.element(tagName, attributes, options); + }); + + return lop.rules.firstOf( + 'html path', + lop.rules.then(lop.rules.tokenOfType('bang'), () => htmlPaths.ignore), + lop.rules.then( + lop.rules.zeroOrMoreWithSeparator( + styleElementRule, + lop.rules.sequence( + whitespaceRule, + lop.rules.tokenOfType('gt'), + whitespaceRule + ) + ), + htmlPaths.elements + ) + ); +} + +var identifierRule = lop.rules.then( + lop.rules.tokenOfType('identifier'), + decodeEscapeSequences +); +var integerRule = lop.rules.tokenOfType('integer'); + +var stringRule = lop.rules.then( + lop.rules.tokenOfType('string'), + decodeEscapeSequences +); + +var escapeSequences = { + n: '\n', + r: '\r', + t: '\t', +}; + +function decodeEscapeSequences(value) { + return value.replace( + /\\(.)/g, + (match, code) => escapeSequences[code] || code + ); +} + +var attributeRule = lop.rules + .sequence( + lop.rules.tokenOfType('open-square-bracket'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule), + lop.rules.tokenOfType('equals'), + lop.rules.sequence.capture(stringRule), + lop.rules.tokenOfType('close-square-bracket') + ) + .map((name, value) => ({ name, value, append: false })); + +var classRule = lop.rules + .sequence( + lop.rules.tokenOfType('dot'), + lop.rules.sequence.cut(), + lop.rules.sequence.capture(identifierRule) + ) + .map((className) => ({ name: 'class', value: className, append: true })); + +var attributeOrClassRule = lop.rules.firstOf( + 'attribute or class', + attributeRule, + classRule +); + +function parseString(rule, string) { + var tokens = tokenise(string); + var parser = lop.Parser(); + var parseResult = parser.parseTokens(rule, tokens); + if (parseResult.isSuccess()) { + return results.success(parseResult.value()); + } + return new results.Result(null, [ + results.warning(describeFailure(string, parseResult)), + ]); +} + +function describeFailure(input, parseResult) { + return ( + 'Did not understand this style mapping, so ignored it: ' + + input + + '\n' + + parseResult.errors().map(describeError).join('\n') + ); +} + +function describeError(error) { + return ( + 'Error was at character number ' + + error.characterNumber() + + ': ' + + 'Expected ' + + error.expected + + ' but got ' + + error.actual + ); +} + +var styleRule = createStyleRule(); + +},{"./results":26,"./styles/document-matchers":28,"./styles/html-paths":29,"./styles/parser/tokeniser":30,"lop":90,"underscore":104}],28:[function(require,module,exports){ +exports.paragraph = paragraph; +exports.run = run; +exports.table = table; +exports.bold = new Matcher('bold'); +exports.italic = new Matcher('italic'); +exports.underline = new Matcher('underline'); +exports.strikethrough = new Matcher('strikethrough'); +exports.allCaps = new Matcher('allCaps'); +exports.smallCaps = new Matcher('smallCaps'); +exports.highlight = highlight; +exports.commentReference = new Matcher('commentReference'); +exports.commentRangeStart = new Matcher('commentRangeStart'); +exports.commentRangeEnd = new Matcher('commentRangeEnd'); +exports.inserted = new Matcher('inserted'); +exports.deleted = new Matcher('deleted'); +exports.lineBreak = new BreakMatcher({ breakType: 'line' }); +exports.pageBreak = new BreakMatcher({ breakType: 'page' }); +exports.columnBreak = new BreakMatcher({ breakType: 'column' }); +exports.equalTo = equalTo; +exports.startsWith = startsWith; + +function paragraph(options) { + return new Matcher('paragraph', options); +} + +function run(options) { + return new Matcher('run', options); +} + +function table(options) { + return new Matcher('table', options); +} + +function highlight(options) { + return new HighlightMatcher(options); +} + +function Matcher(elementType, options) { + options = options || {}; + this._elementType = elementType; + this._styleId = options.styleId; + this._styleName = options.styleName; + if (options.list) { + this._listIndex = options.list.levelIndex; + this._listIsOrdered = options.list.isOrdered; + } +} + +Matcher.prototype.matches = function (element) { + return ( + element.type === this._elementType && + (this._styleId === undefined || element.styleId === this._styleId) && + (this._styleName === undefined || + (element.styleName && + this._styleName.operator( + this._styleName.operand, + element.styleName + ))) && + (this._listIndex === undefined || + isList(element, this._listIndex, this._listIsOrdered)) + ); +}; + +function HighlightMatcher(options) { + options = options || {}; + this._color = options.color; +} + +HighlightMatcher.prototype.matches = function (element) { + return ( + element.type === 'highlight' && + (this._color === undefined || element.color === this._color) + ); +}; + +function BreakMatcher(options) { + options = options || {}; + this._breakType = options.breakType; +} + +BreakMatcher.prototype.matches = function (element) { + return ( + element.type === 'break' && + (this._breakType === undefined || element.breakType === this._breakType) + ); +}; + +function isList(element, levelIndex, isOrdered) { + return ( + element.numbering && + element.numbering.level === levelIndex && + element.numbering.isOrdered === isOrdered + ); +} + +function equalTo(value) { + return { + operator: operatorEqualTo, + operand: value, + }; +} + +function startsWith(value) { + return { + operator: operatorStartsWith, + operand: value, + }; +} + +function operatorEqualTo(first, second) { + return first.toUpperCase() === second.toUpperCase(); +} + +function operatorStartsWith(first, second) { + return second.toUpperCase().indexOf(first.toUpperCase()) === 0; +} + +},{}],29:[function(require,module,exports){ +var _ = require('underscore'); + +var html = require('../html'); + +exports.topLevelElement = topLevelElement; +exports.elements = elements; +exports.element = element; + +function topLevelElement(tagName, attributes) { + return elements([element(tagName, attributes, { fresh: true })]); +} + +function elements(elementStyles) { + return new HtmlPath( + elementStyles.map((elementStyle) => { + if (_.isString(elementStyle)) { + return element(elementStyle); + } + return elementStyle; + }) + ); +} + +function HtmlPath(elements) { + this._elements = elements; +} + +HtmlPath.prototype.wrap = function wrap(children) { + var result = children(); + for (var index = this._elements.length - 1; index >= 0; index--) { + result = this._elements[index].wrapNodes(result); + } + return result; +}; + +function element(tagName, attributes, options) { + options = options || {}; + return new Element(tagName, attributes, options); +} + +function Element(tagName, attributes, options) { + var tagNames = {}; + if (_.isArray(tagName)) { + tagName.forEach((tagName) => { + tagNames[tagName] = true; + }); + tagName = tagName[0]; + } else { + tagNames[tagName] = true; + } + + this.tagName = tagName; + this.tagNames = tagNames; + this.attributes = attributes || {}; + this.fresh = options.fresh; + this.separator = options.separator; +} + +Element.prototype.matchesElement = function (element) { + return ( + this.tagNames[element.tagName] && + _.isEqual(this.attributes || {}, element.attributes || {}) + ); +}; + +Element.prototype.wrap = function wrap(generateNodes) { + return this.wrapNodes(generateNodes()); +}; + +Element.prototype.wrapNodes = function wrapNodes(nodes) { + return [html.elementWithTag(this, nodes)]; +}; + +exports.empty = elements([]); +exports.ignore = { + wrap() { + return []; + }, +}; + +},{"../html":19,"underscore":104}],30:[function(require,module,exports){ +var lop = require('lop'); +var RegexTokeniser = lop.RegexTokeniser; + +exports.tokenise = tokenise; + +var stringPrefix = "'((?:\\\\.|[^'])*)"; + +function tokenise(string) { + var identifierCharacter = '(?:[a-zA-Z\\-_]|\\\\.)'; + var tokeniser = new RegexTokeniser([ + { + name: 'identifier', + regex: new RegExp( + '(' + identifierCharacter + '(?:' + identifierCharacter + '|[0-9])*)' + ), + }, + { name: 'dot', regex: /\./ }, + { name: 'colon', regex: /:/ }, + { name: 'gt', regex: />/ }, + { name: 'whitespace', regex: /\s+/ }, + { name: 'arrow', regex: /=>/ }, + { name: 'equals', regex: /=/ }, + { name: 'startsWith', regex: /\^=/ }, + { name: 'open-paren', regex: /\(/ }, + { name: 'close-paren', regex: /\)/ }, + { name: 'open-square-bracket', regex: /\[/ }, + { name: 'close-square-bracket', regex: /\]/ }, + { name: 'string', regex: new RegExp(stringPrefix + "'") }, + { name: 'unterminated-string', regex: new RegExp(stringPrefix) }, + { name: 'integer', regex: /([0-9]+)/ }, + { name: 'choice', regex: /\|/ }, + { name: 'bang', regex: /(!)/ }, + ]); + return tokeniser.tokenise(string); +} + +},{"lop":90}],31:[function(require,module,exports){ +var _ = require('underscore'); + +exports.paragraph = paragraph; +exports.run = run; +exports._elements = elements; +exports._elementsOfType = elementsOfType; +exports.getDescendantsOfType = getDescendantsOfType; +exports.getDescendants = getDescendants; + +function paragraph(transform) { + return elementsOfType('paragraph', transform); +} + +function run(transform) { + return elementsOfType('run', transform); +} + +function elementsOfType(elementType, transform) { + return elements((element) => { + if (element.type === elementType) { + return transform(element); + } + return element; + }); +} + +function elements(transform) { + return function transformElement(element) { + if (element.children) { + var children = _.map(element.children, transformElement); + element = _.extend({}, element, { children }); + } + return transform(element); + }; +} + +function getDescendantsOfType(element, type) { + return getDescendants(element).filter( + (descendant) => descendant.type === type + ); +} + +function getDescendants(element) { + var descendants = []; + + visitDescendants(element, (descendant) => { + descendants.push(descendant); + }); + + return descendants; +} + +function visitDescendants(element, visit) { + if (element.children) { + element.children.forEach((child) => { + visitDescendants(child, visit); + visit(child); + }); + } +} + +},{"underscore":104}],32:[function(require,module,exports){ +var htmlPaths = require('./styles/html-paths'); +var Html = require('./html'); + +exports.element = element; + +function element(name) { + return (html) => Html.elementWithTag(htmlPaths.element(name), [html]); +} + +},{"./html":19,"./styles/html-paths":29}],33:[function(require,module,exports){ +var _ = require('underscore'); + +exports.writer = writer; + +function writer(options) { + options = options || {}; + if (options.prettyPrint) { + return prettyWriter(); + } + return simpleWriter(); +} + +var indentedElements = { + div: true, + p: true, + ul: true, + li: true, +}; + +function prettyWriter() { + var indentationLevel = 0; + var indentation = ' '; + var stack = []; + var start = true; + var inText = false; + + var writer = simpleWriter(); + + function open(tagName, attributes) { + if (indentedElements[tagName]) { + indent(); + } + stack.push(tagName); + writer.open(tagName, attributes); + if (indentedElements[tagName]) { + indentationLevel++; + } + start = false; + } + + function close(tagName) { + if (indentedElements[tagName]) { + indentationLevel--; + indent(); + } + stack.pop(); + writer.close(tagName); + } + + function text(value) { + startText(); + var currentIndent = ''; + for (var i = 0; i < indentationLevel; i++) { + currentIndent += indentation; + } + var text = isInPre() ? value : value.replace(/\n/g, '\n' + currentIndent); + writer.text(text); + } + + function selfClosing(tagName, attributes) { + indent(); + writer.selfClosing(tagName, attributes); + } + + function insideIndentedElement() { + return stack.length === 0 || indentedElements[stack[stack.length - 1]]; + } + + function startText() { + if (!inText) { + indent(); + inText = true; + } + } + + function indent() { + inText = false; + if (!start && insideIndentedElement() && !isInPre()) { + writer._append('\n'); + for (var i = 0; i < indentationLevel; i++) { + writer._append(indentation); + } + } + } + + function isInPre() { + return _.some(stack, (tagName) => tagName === 'pre'); + } + + return { + asString: writer.asString, + open, + close, + text, + selfClosing, + }; +} + +function simpleWriter() { + var fragments = []; + + function open(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push('<' + tagName + attributeString + '>'); + } + + function close(tagName) { + fragments.push('</' + tagName + '>'); + } + + function selfClosing(tagName, attributes) { + var attributeString = generateAttributeString(attributes); + fragments.push('<' + tagName + attributeString + ' />'); + } + + function generateAttributeString(attributes) { + return _.map( + attributes, + (value, key) => ' ' + key + '="' + escapeHtmlAttribute(value) + '"' + ).join(''); + } + + function text(value) { + fragments.push(escapeHtmlText(value)); + } + + function append(html) { + fragments.push(html); + } + + function asString() { + return fragments.join(''); + } + + return { + asString, + open, + close, + text, + selfClosing, + _append: append, + }; +} + +function escapeHtmlText(value) { + return value + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); +} + +function escapeHtmlAttribute(value) { + return value + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/</g, '<') + .replace(/>/g, '>'); +} + +},{"underscore":104}],34:[function(require,module,exports){ +var htmlWriter = require('./html-writer'); +var markdownWriter = require('./markdown-writer'); + +exports.writer = writer; + +function writer(options) { + options = options || {}; + if (options.outputFormat === 'markdown') { + return markdownWriter.writer(); + } + return htmlWriter.writer(options); +} + +},{"./html-writer":33,"./markdown-writer":35}],35:[function(require,module,exports){ +var _ = require('underscore'); + +function symmetricMarkdownElement(end) { + return markdownElement(end, end); +} + +function markdownElement(start, end) { + return () => ({ start, end }); +} + +function markdownLink(attributes) { + var href = attributes.href || ''; + if (href) { + return { + start: '[', + end: '](' + href + ')', + anchorPosition: 'before', + }; + } + return {}; +} + +function markdownImage(attributes) { + var src = attributes.src || ''; + var altText = attributes.alt || ''; + if (src || altText) { + return { start: '![' + altText + '](' + src + ')' }; + } + return {}; +} + +function markdownList(options) { + return (attributes, list) => ({ + start: list ? '\n' : '', + end: list ? '' : '\n', + list: { + isOrdered: options.isOrdered, + indent: list ? list.indent + 1 : 0, + count: 0, + }, + }); +} + +function markdownListItem(attributes, list, listItem) { + list = list || { indent: 0, isOrdered: false, count: 0 }; + list.count++; + listItem.hasClosed = false; + + var bullet = list.isOrdered ? list.count + '.' : '-'; + var start = repeatString('\t', list.indent) + bullet + ' '; + + return { + start, + end() { + if (!listItem.hasClosed) { + listItem.hasClosed = true; + return '\n'; + } + }, + }; +} + +var htmlToMarkdown = { + p: markdownElement('', '\n\n'), + br: markdownElement('', ' \n'), + ul: markdownList({ isOrdered: false }), + ol: markdownList({ isOrdered: true }), + li: markdownListItem, + strong: symmetricMarkdownElement('__'), + em: symmetricMarkdownElement('*'), + a: markdownLink, + img: markdownImage, +}; + +(() => { + for (var i = 1; i <= 6; i++) { + htmlToMarkdown['h' + i] = markdownElement( + repeatString('#', i) + ' ', + '\n\n' + ); + } +})(); + +function repeatString(value, count) { + return new Array(count + 1).join(value); +} + +function markdownWriter() { + var fragments = []; + var elementStack = []; + var list = null; + var listItem = {}; + + function open(tagName, attributes) { + attributes = attributes || {}; + + var createElement = htmlToMarkdown[tagName] || (() => ({})); + var element = createElement(attributes, list, listItem); + elementStack.push({ end: element.end, list }); + + if (element.list) { + list = element.list; + } + + var anchorBeforeStart = element.anchorPosition === 'before'; + if (anchorBeforeStart) { + writeAnchor(attributes); + } + + fragments.push(element.start || ''); + if (!anchorBeforeStart) { + writeAnchor(attributes); + } + } + + function writeAnchor(attributes) { + if (attributes.id) { + fragments.push('<a id="' + attributes.id + '"></a>'); + } + } + + function close(tagName) { + var element = elementStack.pop(); + list = element.list; + var end = _.isFunction(element.end) ? element.end() : element.end; + fragments.push(end || ''); + } + + function selfClosing(tagName, attributes) { + open(tagName, attributes); + close(tagName); + } + + function text(value) { + fragments.push(escapeMarkdown(value)); + } + + function asString() { + return fragments.join(''); + } + + return { + asString, + open, + close, + text, + selfClosing, + }; +} + +exports.writer = markdownWriter; + +function escapeMarkdown(value) { + return value.replace(/\\/g, '\\\\').replace(/([`*_{}[\]()#+\-.!])/g, '\\$1'); +} + +},{"underscore":104}],36:[function(require,module,exports){ +var nodes = require('./nodes'); + +exports.Element = nodes.Element; +exports.element = nodes.element; +exports.emptyElement = nodes.emptyElement; +exports.text = nodes.text; +exports.readString = require('./reader').readString; +exports.writeString = require('./writer').writeString; + +},{"./nodes":37,"./reader":38,"./writer":39}],37:[function(require,module,exports){ +var _ = require('underscore'); + +exports.Element = Element; +exports.element = (name, attributes, children) => + new Element(name, attributes, children); +exports.text = (value) => ({ + type: 'text', + value, +}); + +var emptyElement = (exports.emptyElement = { + first() { + return null; + }, + firstOrEmpty() { + return emptyElement; + }, + attributes: {}, + children: [], +}); + +function Element(name, attributes, children) { + this.type = 'element'; + this.name = name; + this.attributes = attributes || {}; + this.children = children || []; +} + +Element.prototype.first = function (name) { + return _.find(this.children, (child) => child.name === name); +}; + +Element.prototype.firstOrEmpty = function (name) { + return this.first(name) || emptyElement; +}; + +Element.prototype.getElementsByTagName = function (name) { + var elements = _.filter(this.children, (child) => child.name === name); + return toElementList(elements); +}; + +Element.prototype.text = function () { + if (this.children.length === 0) { + return ''; + } + if (this.children.length !== 1 || this.children[0].type !== 'text') { + throw new Error('Not implemented'); + } + return this.children[0].value; +}; + +var elementListPrototype = { + getElementsByTagName(name) { + return toElementList( + _.flatten(this.map((element) => element.getElementsByTagName(name), true)) + ); + }, +}; + +function toElementList(array) { + return _.extend(array, elementListPrototype); +} + +},{"underscore":104}],38:[function(require,module,exports){ +var promises = require('../promises'); +var _ = require('underscore'); + +var xmldom = require('./xmldom'); +var nodes = require('./nodes'); +var Element = nodes.Element; + +exports.readString = readString; + +var Node = xmldom.Node; + +function readString(xmlString, namespaceMap) { + namespaceMap = namespaceMap || {}; + + try { + var document = xmldom.parseFromString(xmlString, 'text/xml'); + } catch (error) { + return promises.reject(error); + } + + if (document.documentElement.tagName === 'parsererror') { + return promises.reject(new Error(document.documentElement.textContent)); + } + + function convertNode(node) { + switch (node.nodeType) { + case Node.ELEMENT_NODE: + return convertElement(node); + case Node.TEXT_NODE: + return nodes.text(node.nodeValue); + } + } + + function convertElement(element) { + var convertedName = convertName(element); + + var convertedChildren = []; + _.forEach(element.childNodes, (childNode) => { + var convertedNode = convertNode(childNode); + if (convertedNode) { + convertedChildren.push(convertedNode); + } + }); + + var convertedAttributes = {}; + _.forEach(element.attributes, (attribute) => { + convertedAttributes[convertName(attribute)] = attribute.value; + }); + + return new Element(convertedName, convertedAttributes, convertedChildren); + } + + function convertName(node) { + if (node.namespaceURI) { + var mappedPrefix = namespaceMap[node.namespaceURI]; + var prefix; + if (mappedPrefix) { + prefix = mappedPrefix + ':'; + } else { + prefix = '{' + node.namespaceURI + '}'; + } + return prefix + node.localName; + } + return node.localName; + } + + return promises.resolve(convertNode(document.documentElement)); +} + +},{"../promises":24,"./nodes":37,"./xmldom":40,"underscore":104}],39:[function(require,module,exports){ +var _ = require('underscore'); +var xmlbuilder = require('xmlbuilder'); + +exports.writeString = writeString; + +function writeString(root, namespaces) { + var uriToPrefix = _.invert(namespaces); + + var nodeWriters = { + element: writeElement, + text: writeTextNode, + }; + + function writeNode(builder, node) { + var writer = nodeWriters[node.type]; + if (!writer) { + throw new Error('Unknown node type: ' + node.type); + } + return writer(builder, node); + } + + function writeElement(builder, element) { + var elementBuilder = builder.element( + mapElementName(element.name), + element.attributes + ); + element.children.forEach((child) => { + writeNode(elementBuilder, child); + }); + } + + function mapElementName(name) { + var longFormMatch = /^\{(.*)\}(.*)$/.exec(name); + if (longFormMatch) { + var prefix = uriToPrefix[longFormMatch[1]]; + return prefix + (prefix === '' ? '' : ':') + longFormMatch[2]; + } + return name; + } + + function writeDocument(root) { + var builder = xmlbuilder.create(mapElementName(root.name), { + version: '1.0', + encoding: 'UTF-8', + standalone: true, + }); + + _.forEach(namespaces, (uri, prefix) => { + var key = 'xmlns' + (prefix === '' ? '' : ':' + prefix); + builder.attribute(key, uri); + }); + + // Apply root element attributes + _.forEach(root.attributes, (value, key) => { + builder.attribute(key, value); + }); + + root.children.forEach((child) => { + writeNode(builder, child); + }); + return builder.end(); + } + + return writeDocument(root); +} + +function writeTextNode(builder, node) { + builder.text(node.value); +} + +},{"underscore":104,"xmlbuilder":127}],40:[function(require,module,exports){ +var xmldom = require('@xmldom/xmldom'); +var dom = require('@xmldom/xmldom/lib/dom'); + +function parseFromString(string) { + var errors = []; + + var domParser = new xmldom.DOMParser({ + errorHandler(level, message) { + errors.push({ level, message }); + }, + }); + + var document = domParser.parseFromString(string); + + if (errors.length === 0) { + return document; + } + var errorMessages = errors.map((e) => e.level + ': ' + e.message).join('\n'); + throw new Error(errorMessages); +} + +exports.parseFromString = parseFromString; +exports.Node = dom.Node; + +},{"@xmldom/xmldom":46,"@xmldom/xmldom/lib/dom":44}],41:[function(require,module,exports){ +var base64js = require('base64-js'); +var JSZip = require('jszip'); + +exports.openArrayBuffer = openArrayBuffer; +exports.splitPath = splitPath; +exports.joinPath = joinPath; + +function openArrayBuffer(arrayBuffer) { + return JSZip.loadAsync(arrayBuffer).then((zipFile) => { + function exists(name) { + return zipFile.file(name) !== null; + } + + function read(name, encoding) { + var file = zipFile.file(name); + if (file === null) { + return Promise.reject(new Error('File not found in ZIP: ' + name)); + } + return file.async('uint8array').then((array) => { + if (encoding === 'base64') { + return base64js.fromByteArray(array); + } + if (encoding) { + var decoder = new TextDecoder(encoding); + return decoder.decode(array); + } + return array; + }); + } + + function write(name, contents) { + zipFile.file(name, contents); + } + + function toArrayBuffer() { + return zipFile.generateAsync({ type: 'arraybuffer' }); + } + + return { + exists, + read, + write, + toArrayBuffer, + }; + }); +} + +function splitPath(path) { + var lastIndex = path.lastIndexOf('/'); + if (lastIndex === -1) { + return { dirname: '', basename: path }; + } + return { + dirname: path.substring(0, lastIndex), + basename: path.substring(lastIndex + 1), + }; +} + +function joinPath() { + var nonEmptyPaths = Array.prototype.filter.call(arguments, (path) => path); + + var relevantPaths = []; + + nonEmptyPaths.forEach((path) => { + if (path.startsWith('/')) { + relevantPaths = [path]; + } else { + relevantPaths.push(path); + } + }); + + return relevantPaths.join('/'); +} + +},{"base64-js":48,"jszip":89}],42:[function(require,module,exports){ +'use strict' + +/** + * Ponyfill for `Array.prototype.find` which is only available in ES6 runtimes. + * + * Works with anything that has a `length` property and index access properties, including NodeList. + * + * @template {unknown} T + * @param {Array<T> | ({length:number, [number]: T})} list + * @param {function (item: T, index: number, list:Array<T> | ({length:number, [number]: T})):boolean} predicate + * @param {Partial<Pick<ArrayConstructor['prototype'], 'find'>>?} ac `Array.prototype` by default, + * allows injecting a custom implementation in tests + * @returns {T | undefined} + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + * @see https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.find + */ +function find(list, predicate, ac) { + if (ac === undefined) { + ac = Array.prototype; + } + if (list && typeof ac.find === 'function') { + return ac.find.call(list, predicate); + } + for (var i = 0; i < list.length; i++) { + if (Object.prototype.hasOwnProperty.call(list, i)) { + var item = list[i]; + if (predicate.call(undefined, item, i, list)) { + return item; + } + } + } +} + +/** + * "Shallow freezes" an object to render it immutable. + * Uses `Object.freeze` if available, + * otherwise the immutability is only in the type. + * + * Is used to create "enum like" objects. + * + * @template T + * @param {T} object the object to freeze + * @param {Pick<ObjectConstructor, 'freeze'> = Object} oc `Object` by default, + * allows to inject custom object constructor for tests + * @returns {Readonly<T>} + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze + */ +function freeze(object, oc) { + if (oc === undefined) { + oc = Object + } + return oc && typeof oc.freeze === 'function' ? oc.freeze(object) : object +} + +/** + * Since we can not rely on `Object.assign` we provide a simplified version + * that is sufficient for our needs. + * + * @param {Object} target + * @param {Object | null | undefined} source + * + * @returns {Object} target + * @throws TypeError if target is not an object + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + * @see https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object.assign + */ +function assign(target, source) { + if (target === null || typeof target !== 'object') { + throw new TypeError('target is not an object') + } + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key] + } + } + return target +} + +/** + * All mime types that are allowed as input to `DOMParser.parseFromString` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#Argument02 MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#domparsersupportedtype WHATWG HTML Spec + * @see DOMParser.prototype.parseFromString + */ +var MIME_TYPE = freeze({ + /** + * `text/html`, the only mime type that triggers treating an XML document as HTML. + * + * @see DOMParser.SupportedType.isHTML + * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration + * @see https://en.wikipedia.org/wiki/HTML Wikipedia + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring WHATWG HTML Spec + */ + HTML: 'text/html', + + /** + * Helper method to check a mime type if it indicates an HTML document + * + * @param {string} [value] + * @returns {boolean} + * + * @see https://www.iana.org/assignments/media-types/text/html IANA MimeType registration + * @see https://en.wikipedia.org/wiki/HTML Wikipedia + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString MDN + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-domparser-parsefromstring */ + isHTML: function (value) { + return value === MIME_TYPE.HTML + }, + + /** + * `application/xml`, the standard mime type for XML documents. + * + * @see https://www.iana.org/assignments/media-types/application/xml IANA MimeType registration + * @see https://tools.ietf.org/html/rfc7303#section-9.1 RFC 7303 + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + XML_APPLICATION: 'application/xml', + + /** + * `text/html`, an alias for `application/xml`. + * + * @see https://tools.ietf.org/html/rfc7303#section-9.2 RFC 7303 + * @see https://www.iana.org/assignments/media-types/text/xml IANA MimeType registration + * @see https://en.wikipedia.org/wiki/XML_and_MIME Wikipedia + */ + XML_TEXT: 'text/xml', + + /** + * `application/xhtml+xml`, indicates an XML document that has the default HTML namespace, + * but is parsed as an XML document. + * + * @see https://www.iana.org/assignments/media-types/application/xhtml+xml IANA MimeType registration + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument WHATWG DOM Spec + * @see https://en.wikipedia.org/wiki/XHTML Wikipedia + */ + XML_XHTML_APPLICATION: 'application/xhtml+xml', + + /** + * `image/svg+xml`, + * + * @see https://www.iana.org/assignments/media-types/image/svg+xml IANA MimeType registration + * @see https://www.w3.org/TR/SVG11/ W3C SVG 1.1 + * @see https://en.wikipedia.org/wiki/Scalable_Vector_Graphics Wikipedia + */ + XML_SVG_IMAGE: 'image/svg+xml', +}) + +/** + * Namespaces that are used in this code base. + * + * @see http://www.w3.org/TR/REC-xml-names + */ +var NAMESPACE = freeze({ + /** + * The XHTML namespace. + * + * @see http://www.w3.org/1999/xhtml + */ + HTML: 'http://www.w3.org/1999/xhtml', + + /** + * Checks if `uri` equals `NAMESPACE.HTML`. + * + * @param {string} [uri] + * + * @see NAMESPACE.HTML + */ + isHTML: function (uri) { + return uri === NAMESPACE.HTML + }, + + /** + * The SVG namespace. + * + * @see http://www.w3.org/2000/svg + */ + SVG: 'http://www.w3.org/2000/svg', + + /** + * The `xml:` namespace. + * + * @see http://www.w3.org/XML/1998/namespace + */ + XML: 'http://www.w3.org/XML/1998/namespace', + + /** + * The `xmlns:` namespace + * + * @see https://www.w3.org/2000/xmlns/ + */ + XMLNS: 'http://www.w3.org/2000/xmlns/', +}) + +exports.assign = assign; +exports.find = find; +exports.freeze = freeze; +exports.MIME_TYPE = MIME_TYPE; +exports.NAMESPACE = NAMESPACE; + +},{}],43:[function(require,module,exports){ +var conventions = require("./conventions"); +var dom = require('./dom') +var entities = require('./entities'); +var sax = require('./sax'); + +var DOMImplementation = dom.DOMImplementation; + +var NAMESPACE = conventions.NAMESPACE; + +var ParseError = sax.ParseError; +var XMLReader = sax.XMLReader; + +/** + * Normalizes line ending according to https://www.w3.org/TR/xml11/#sec-line-ends: + * + * > XML parsed entities are often stored in computer files which, + * > for editing convenience, are organized into lines. + * > These lines are typically separated by some combination + * > of the characters CARRIAGE RETURN (#xD) and LINE FEED (#xA). + * > + * > To simplify the tasks of applications, the XML processor must behave + * > as if it normalized all line breaks in external parsed entities (including the document entity) + * > on input, before parsing, by translating all of the following to a single #xA character: + * > + * > 1. the two-character sequence #xD #xA + * > 2. the two-character sequence #xD #x85 + * > 3. the single character #x85 + * > 4. the single character #x2028 + * > 5. any #xD character that is not immediately followed by #xA or #x85. + * + * @param {string} input + * @returns {string} + */ +function normalizeLineEndings(input) { + return input + .replace(/\r[\n\u0085]/g, '\n') + .replace(/[\r\u0085\u2028]/g, '\n') +} + +/** + * @typedef Locator + * @property {number} [columnNumber] + * @property {number} [lineNumber] + */ + +/** + * @typedef DOMParserOptions + * @property {DOMHandler} [domBuilder] + * @property {Function} [errorHandler] + * @property {(string) => string} [normalizeLineEndings] used to replace line endings before parsing + * defaults to `normalizeLineEndings` + * @property {Locator} [locator] + * @property {Record<string, string>} [xmlns] + * + * @see normalizeLineEndings + */ + +/** + * The DOMParser interface provides the ability to parse XML or HTML source code + * from a string into a DOM `Document`. + * + * _xmldom is different from the spec in that it allows an `options` parameter, + * to override the default behavior._ + * + * @param {DOMParserOptions} [options] + * @constructor + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser + * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization + */ +function DOMParser(options){ + this.options = options ||{locator:{}}; +} + +DOMParser.prototype.parseFromString = function(source,mimeType){ + var options = this.options; + var sax = new XMLReader(); + var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler + var errorHandler = options.errorHandler; + var locator = options.locator; + var defaultNSMap = options.xmlns||{}; + var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1; + var entityMap = isHTML ? entities.HTML_ENTITIES : entities.XML_ENTITIES; + if(locator){ + domBuilder.setDocumentLocator(locator) + } + + sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); + sax.domBuilder = options.domBuilder || domBuilder; + if(isHTML){ + defaultNSMap[''] = NAMESPACE.HTML; + } + defaultNSMap.xml = defaultNSMap.xml || NAMESPACE.XML; + var normalize = options.normalizeLineEndings || normalizeLineEndings; + if (source && typeof source === 'string') { + sax.parse( + normalize(source), + defaultNSMap, + entityMap + ) + } else { + sax.errorHandler.error('invalid doc source') + } + return domBuilder.doc; +} +function buildErrorHandler(errorImpl,domBuilder,locator){ + if(!errorImpl){ + if(domBuilder instanceof DOMHandler){ + return domBuilder; + } + errorImpl = domBuilder ; + } + var errorHandler = {} + var isCallback = errorImpl instanceof Function; + locator = locator||{} + function build(key){ + var fn = errorImpl[key]; + if(!fn && isCallback){ + fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; + } + errorHandler[key] = fn && function(msg){ + fn('[xmldom '+key+']\t'+msg+_locator(locator)); + }||function(){}; + } + build('warning'); + build('error'); + build('fatalError'); + return errorHandler; +} + +//console.log('#\n\n\n\n\n\n\n####') +/** + * +ContentHandler+ErrorHandler + * +LexicalHandler+EntityResolver2 + * -DeclHandler-DTDHandler + * + * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler + * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 + * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html + */ +function DOMHandler() { + this.cdata = false; +} +function position(locator,node){ + node.lineNumber = locator.lineNumber; + node.columnNumber = locator.columnNumber; +} +/** + * @see org.xml.sax.ContentHandler#startDocument + * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html + */ +DOMHandler.prototype = { + startDocument : function() { + this.doc = new DOMImplementation().createDocument(null, null, null); + if (this.locator) { + this.doc.documentURI = this.locator.systemId; + } + }, + startElement:function(namespaceURI, localName, qName, attrs) { + var doc = this.doc; + var el = doc.createElementNS(namespaceURI, qName||localName); + var len = attrs.length; + appendElement(this, el); + this.currentElement = el; + + this.locator && position(this.locator,el) + for (var i = 0 ; i < len; i++) { + var namespaceURI = attrs.getURI(i); + var value = attrs.getValue(i); + var qName = attrs.getQName(i); + var attr = doc.createAttributeNS(namespaceURI, qName); + this.locator &&position(attrs.getLocator(i),attr); + attr.value = attr.nodeValue = value; + el.setAttributeNode(attr) + } + }, + endElement:function(namespaceURI, localName, qName) { + var current = this.currentElement + var tagName = current.tagName; + this.currentElement = current.parentNode; + }, + startPrefixMapping:function(prefix, uri) { + }, + endPrefixMapping:function(prefix) { + }, + processingInstruction:function(target, data) { + var ins = this.doc.createProcessingInstruction(target, data); + this.locator && position(this.locator,ins) + appendElement(this, ins); + }, + ignorableWhitespace:function(ch, start, length) { + }, + characters:function(chars, start, length) { + chars = _toString.apply(this,arguments) + //console.log(chars) + if(chars){ + if (this.cdata) { + var charNode = this.doc.createCDATASection(chars); + } else { + var charNode = this.doc.createTextNode(chars); + } + if(this.currentElement){ + this.currentElement.appendChild(charNode); + }else if(/^\s*$/.test(chars)){ + this.doc.appendChild(charNode); + //process xml + } + this.locator && position(this.locator,charNode) + } + }, + skippedEntity:function(name) { + }, + endDocument:function() { + this.doc.normalize(); + }, + setDocumentLocator:function (locator) { + if(this.locator = locator){// && !('lineNumber' in locator)){ + locator.lineNumber = 0; + } + }, + //LexicalHandler + comment:function(chars, start, length) { + chars = _toString.apply(this,arguments) + var comm = this.doc.createComment(chars); + this.locator && position(this.locator,comm) + appendElement(this, comm); + }, + + startCDATA:function() { + //used in characters() methods + this.cdata = true; + }, + endCDATA:function() { + this.cdata = false; + }, + + startDTD:function(name, publicId, systemId) { + var impl = this.doc.implementation; + if (impl && impl.createDocumentType) { + var dt = impl.createDocumentType(name, publicId, systemId); + this.locator && position(this.locator,dt) + appendElement(this, dt); + this.doc.doctype = dt; + } + }, + /** + * @see org.xml.sax.ErrorHandler + * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html + */ + warning:function(error) { + console.warn('[xmldom warning]\t'+error,_locator(this.locator)); + }, + error:function(error) { + console.error('[xmldom error]\t'+error,_locator(this.locator)); + }, + fatalError:function(error) { + throw new ParseError(error, this.locator); + } +} +function _locator(l){ + if(l){ + return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' + } +} +function _toString(chars,start,length){ + if(typeof chars == 'string'){ + return chars.substr(start,length) + }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") + if(chars.length >= start+length || start){ + return new java.lang.String(chars,start,length)+''; + } + return chars; + } +} + +/* + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html + * used method of org.xml.sax.ext.LexicalHandler: + * #comment(chars, start, length) + * #startCDATA() + * #endCDATA() + * #startDTD(name, publicId, systemId) + * + * + * IGNORED method of org.xml.sax.ext.LexicalHandler: + * #endDTD() + * #startEntity(name) + * #endEntity(name) + * + * + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html + * IGNORED method of org.xml.sax.ext.DeclHandler + * #attributeDecl(eName, aName, type, mode, value) + * #elementDecl(name, model) + * #externalEntityDecl(name, publicId, systemId) + * #internalEntityDecl(name, value) + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html + * IGNORED method of org.xml.sax.EntityResolver2 + * #resolveEntity(String name,String publicId,String baseURI,String systemId) + * #resolveEntity(publicId, systemId) + * #getExternalSubset(name, baseURI) + * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html + * IGNORED method of org.xml.sax.DTDHandler + * #notationDecl(name, publicId, systemId) {}; + * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; + */ +"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ + DOMHandler.prototype[key] = function(){return null} +}) + +/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ +function appendElement (hander,node) { + if (!hander.currentElement) { + hander.doc.appendChild(node); + } else { + hander.currentElement.appendChild(node); + } +}//appendChild and setAttributeNS are preformance key + +exports.__DOMHandler = DOMHandler; +exports.normalizeLineEndings = normalizeLineEndings; +exports.DOMParser = DOMParser; + +},{"./conventions":42,"./dom":44,"./entities":45,"./sax":47}],44:[function(require,module,exports){ +var conventions = require("./conventions"); + +var find = conventions.find; +var NAMESPACE = conventions.NAMESPACE; + +/** + * A prerequisite for `[].filter`, to drop elements that are empty + * @param {string} input + * @returns {boolean} + */ +function notEmptyString (input) { + return input !== '' +} +/** + * @see https://infra.spec.whatwg.org/#split-on-ascii-whitespace + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * + * @param {string} input + * @returns {string[]} (can be empty) + */ +function splitOnASCIIWhitespace(input) { + // U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE + return input ? input.split(/[\t\n\f\r ]+/).filter(notEmptyString) : [] +} + +/** + * Adds element as a key to current if it is not already present. + * + * @param {Record<string, boolean | undefined>} current + * @param {string} element + * @returns {Record<string, boolean | undefined>} + */ +function orderedSetReducer (current, element) { + if (!current.hasOwnProperty(element)) { + current[element] = true; + } + return current; +} + +/** + * @see https://infra.spec.whatwg.org/#ordered-set + * @param {string} input + * @returns {string[]} + */ +function toOrderedSet(input) { + if (!input) return []; + var list = splitOnASCIIWhitespace(input); + return Object.keys(list.reduce(orderedSetReducer, {})) +} + +/** + * Uses `list.indexOf` to implement something like `Array.prototype.includes`, + * which we can not rely on being available. + * + * @param {any[]} list + * @returns {function(any): boolean} + */ +function arrayIncludes (list) { + return function(element) { + return list && list.indexOf(element) !== -1; + } +} + +function copy(src,dest){ + for(var p in src){ + if (Object.prototype.hasOwnProperty.call(src, p)) { + dest[p] = src[p]; + } + } +} + +/** +^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? +^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? + */ +function _extends(Class,Super){ + var pt = Class.prototype; + if(!(pt instanceof Super)){ + function t(){}; + t.prototype = Super.prototype; + t = new t(); + copy(pt,t); + Class.prototype = pt = t; + } + if(pt.constructor != Class){ + if(typeof Class != 'function'){ + console.error("unknown Class:"+Class) + } + pt.constructor = Class + } +} + +// Node Types +var NodeType = {} +var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; +var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; +var TEXT_NODE = NodeType.TEXT_NODE = 3; +var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; +var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; +var ENTITY_NODE = NodeType.ENTITY_NODE = 6; +var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; +var COMMENT_NODE = NodeType.COMMENT_NODE = 8; +var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; +var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; +var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; +var NOTATION_NODE = NodeType.NOTATION_NODE = 12; + +// ExceptionCode +var ExceptionCode = {} +var ExceptionMessage = {}; +var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = ((ExceptionMessage[1]="Index size error"),1); +var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = ((ExceptionMessage[2]="DOMString size error"),2); +var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = ((ExceptionMessage[3]="Hierarchy request error"),3); +var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = ((ExceptionMessage[4]="Wrong document"),4); +var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = ((ExceptionMessage[5]="Invalid character"),5); +var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = ((ExceptionMessage[6]="No data allowed"),6); +var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7); +var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = ((ExceptionMessage[8]="Not found"),8); +var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = ((ExceptionMessage[9]="Not supported"),9); +var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = ((ExceptionMessage[10]="Attribute in use"),10); +//level2 +var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = ((ExceptionMessage[11]="Invalid state"),11); +var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = ((ExceptionMessage[12]="Syntax error"),12); +var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((ExceptionMessage[13]="Invalid modification"),13); +var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); +var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); + +/** + * DOM Level 2 + * Object DOMException + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html + * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html + */ +function DOMException(code, message) { + if(message instanceof Error){ + var error = message; + }else{ + error = this; + Error.call(this, ExceptionMessage[code]); + this.message = ExceptionMessage[code]; + if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException); + } + error.code = code; + if(message) this.message = this.message + ": " + message; + return error; +}; +DOMException.prototype = Error.prototype; +copy(ExceptionCode,DOMException) + +/** + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 + * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live. + * The items in the NodeList are accessible via an integral index, starting from 0. + */ +function NodeList() { +}; +NodeList.prototype = { + /** + * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. + * @standard level1 + */ + length:0, + /** + * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null. + * @standard level1 + * @param index unsigned long + * Index into the collection. + * @return Node + * The node at the indexth position in the NodeList, or null if that is not a valid index. + */ + item: function(index) { + return index >= 0 && index < this.length ? this[index] : null; + }, + toString:function(isHTML,nodeFilter){ + for(var buf = [], i = 0;i<this.length;i++){ + serializeToString(this[i],buf,isHTML,nodeFilter); + } + return buf.join(''); + }, + /** + * @private + * @param {function (Node):boolean} predicate + * @returns {Node[]} + */ + filter: function (predicate) { + return Array.prototype.filter.call(this, predicate); + }, + /** + * @private + * @param {Node} item + * @returns {number} + */ + indexOf: function (item) { + return Array.prototype.indexOf.call(this, item); + }, +}; + +function LiveNodeList(node,refresh){ + this._node = node; + this._refresh = refresh + _updateLiveList(this); +} +function _updateLiveList(list){ + var inc = list._node._inc || list._node.ownerDocument._inc; + if (list._inc !== inc) { + var ls = list._refresh(list._node); + __set__(list,'length',ls.length); + if (!list.$$length || ls.length < list.$$length) { + for (var i = ls.length; i in list; i++) { + if (Object.prototype.hasOwnProperty.call(list, i)) { + delete list[i]; + } + } + } + copy(ls,list); + list._inc = inc; + } +} +LiveNodeList.prototype.item = function(i){ + _updateLiveList(this); + return this[i] || null; +} + +_extends(LiveNodeList,NodeList); + +/** + * Objects implementing the NamedNodeMap interface are used + * to represent collections of nodes that can be accessed by name. + * Note that NamedNodeMap does not inherit from NodeList; + * NamedNodeMaps are not maintained in any particular order. + * Objects contained in an object implementing NamedNodeMap may also be accessed by an ordinal index, + * but this is simply to allow convenient enumeration of the contents of a NamedNodeMap, + * and does not imply that the DOM specifies an order to these Nodes. + * NamedNodeMap objects in the DOM are live. + * used for attributes or DocumentType entities + */ +function NamedNodeMap() { +}; + +function _findNodeIndex(list,node){ + var i = list.length; + while(i--){ + if(list[i] === node){return i} + } +} + +function _addNamedNode(el,list,newAttr,oldAttr){ + if(oldAttr){ + list[_findNodeIndex(list,oldAttr)] = newAttr; + }else{ + list[list.length++] = newAttr; + } + if(el){ + newAttr.ownerElement = el; + var doc = el.ownerDocument; + if(doc){ + oldAttr && _onRemoveAttribute(doc,el,oldAttr); + _onAddAttribute(doc,el,newAttr); + } + } +} +function _removeNamedNode(el,list,attr){ + //console.log('remove attr:'+attr) + var i = _findNodeIndex(list,attr); + if(i>=0){ + var lastIndex = list.length-1 + while(i<lastIndex){ + list[i] = list[++i] + } + list.length = lastIndex; + if(el){ + var doc = el.ownerDocument; + if(doc){ + _onRemoveAttribute(doc,el,attr); + attr.ownerElement = null; + } + } + }else{ + throw new DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr)) + } +} +NamedNodeMap.prototype = { + length:0, + item:NodeList.prototype.item, + getNamedItem: function(key) { +// if(key.indexOf(':')>0 || key == 'xmlns'){ +// return null; +// } + //console.log() + var i = this.length; + while(i--){ + var attr = this[i]; + //console.log(attr.nodeName,key) + if(attr.nodeName == key){ + return attr; + } + } + }, + setNamedItem: function(attr) { + var el = attr.ownerElement; + if(el && el!=this._ownerElement){ + throw new DOMException(INUSE_ATTRIBUTE_ERR); + } + var oldAttr = this.getNamedItem(attr.nodeName); + _addNamedNode(this._ownerElement,this,attr,oldAttr); + return oldAttr; + }, + /* returns Node */ + setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR + var el = attr.ownerElement, oldAttr; + if(el && el!=this._ownerElement){ + throw new DOMException(INUSE_ATTRIBUTE_ERR); + } + oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName); + _addNamedNode(this._ownerElement,this,attr,oldAttr); + return oldAttr; + }, + + /* returns Node */ + removeNamedItem: function(key) { + var attr = this.getNamedItem(key); + _removeNamedNode(this._ownerElement,this,attr); + return attr; + + + },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR + + //for level2 + removeNamedItemNS:function(namespaceURI,localName){ + var attr = this.getNamedItemNS(namespaceURI,localName); + _removeNamedNode(this._ownerElement,this,attr); + return attr; + }, + getNamedItemNS: function(namespaceURI, localName) { + var i = this.length; + while(i--){ + var node = this[i]; + if(node.localName == localName && node.namespaceURI == namespaceURI){ + return node; + } + } + return null; + } +}; + +/** + * The DOMImplementation interface represents an object providing methods + * which are not dependent on any particular document. + * Such an object is returned by the `Document.implementation` property. + * + * __The individual methods describe the differences compared to the specs.__ + * + * @constructor + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 DOM Level 1 Core (Initial) + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-102161490 DOM Level 2 Core + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-102161490 DOM Level 3 Core + * @see https://dom.spec.whatwg.org/#domimplementation DOM Living Standard + */ +function DOMImplementation() { +} + +DOMImplementation.prototype = { + /** + * The DOMImplementation.hasFeature() method returns a Boolean flag indicating if a given feature is supported. + * The different implementations fairly diverged in what kind of features were reported. + * The latest version of the spec settled to force this method to always return true, where the functionality was accurate and in use. + * + * @deprecated It is deprecated and modern browsers return true in all cases. + * + * @param {string} feature + * @param {string} [version] + * @returns {boolean} always true + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/hasFeature MDN + * @see https://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-5CED94D7 DOM Level 1 Core + * @see https://dom.spec.whatwg.org/#dom-domimplementation-hasfeature DOM Living Standard + */ + hasFeature: function(feature, version) { + return true; + }, + /** + * Creates an XML Document object of the specified type with its document element. + * + * __It behaves slightly different from the description in the living standard__: + * - There is no interface/class `XMLDocument`, it returns a `Document` instance. + * - `contentType`, `encoding`, `mode`, `origin`, `url` fields are currently not declared. + * - this implementation is not validating names or qualified names + * (when parsing XML strings, the SAX parser takes care of that) + * + * @param {string|null} namespaceURI + * @param {string} qualifiedName + * @param {DocumentType=null} doctype + * @returns {Document} + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocument MDN + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocument DOM Level 2 Core (initial) + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocument DOM Level 2 Core + * + * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract + * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names + * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names + */ + createDocument: function(namespaceURI, qualifiedName, doctype){ + var doc = new Document(); + doc.implementation = this; + doc.childNodes = new NodeList(); + doc.doctype = doctype || null; + if (doctype){ + doc.appendChild(doctype); + } + if (qualifiedName){ + var root = doc.createElementNS(namespaceURI, qualifiedName); + doc.appendChild(root); + } + return doc; + }, + /** + * Returns a doctype, with the given `qualifiedName`, `publicId`, and `systemId`. + * + * __This behavior is slightly different from the in the specs__: + * - this implementation is not validating names or qualified names + * (when parsing XML strings, the SAX parser takes care of that) + * + * @param {string} qualifiedName + * @param {string} [publicId] + * @param {string} [systemId] + * @returns {DocumentType} which can either be used with `DOMImplementation.createDocument` upon document creation + * or can be put into the document via methods like `Node.insertBefore()` or `Node.replaceChild()` + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMImplementation/createDocumentType MDN + * @see https://www.w3.org/TR/DOM-Level-2-Core/core.html#Level-2-Core-DOM-createDocType DOM Level 2 Core + * @see https://dom.spec.whatwg.org/#dom-domimplementation-createdocumenttype DOM Living Standard + * + * @see https://dom.spec.whatwg.org/#validate-and-extract DOM: Validate and extract + * @see https://www.w3.org/TR/xml/#NT-NameStartChar XML Spec: Names + * @see https://www.w3.org/TR/xml-names/#ns-qualnames XML Namespaces: Qualified names + */ + createDocumentType: function(qualifiedName, publicId, systemId){ + var node = new DocumentType(); + node.name = qualifiedName; + node.nodeName = qualifiedName; + node.publicId = publicId || ''; + node.systemId = systemId || ''; + + return node; + } +}; + + +/** + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 + */ + +function Node() { +}; + +Node.prototype = { + firstChild : null, + lastChild : null, + previousSibling : null, + nextSibling : null, + attributes : null, + parentNode : null, + childNodes : null, + ownerDocument : null, + nodeValue : null, + namespaceURI : null, + prefix : null, + localName : null, + // Modified in DOM Level 2: + insertBefore:function(newChild, refChild){//raises + return _insertBefore(this,newChild,refChild); + }, + replaceChild:function(newChild, oldChild){//raises + _insertBefore(this, newChild,oldChild, assertPreReplacementValidityInDocument); + if(oldChild){ + this.removeChild(oldChild); + } + }, + removeChild:function(oldChild){ + return _removeChild(this,oldChild); + }, + appendChild:function(newChild){ + return this.insertBefore(newChild,null); + }, + hasChildNodes:function(){ + return this.firstChild != null; + }, + cloneNode:function(deep){ + return cloneNode(this.ownerDocument||this,this,deep); + }, + // Modified in DOM Level 2: + normalize:function(){ + var child = this.firstChild; + while(child){ + var next = child.nextSibling; + if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){ + this.removeChild(next); + child.appendData(next.data); + }else{ + child.normalize(); + child = next; + } + } + }, + // Introduced in DOM Level 2: + isSupported:function(feature, version){ + return this.ownerDocument.implementation.hasFeature(feature,version); + }, + // Introduced in DOM Level 2: + hasAttributes:function(){ + return this.attributes.length>0; + }, + /** + * Look up the prefix associated to the given namespace URI, starting from this node. + * **The default namespace declarations are ignored by this method.** + * See Namespace Prefix Lookup for details on the algorithm used by this method. + * + * _Note: The implementation seems to be incomplete when compared to the algorithm described in the specs._ + * + * @param {string | null} namespaceURI + * @returns {string | null} + * @see https://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespacePrefix + * @see https://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespacePrefixAlgo + * @see https://dom.spec.whatwg.org/#dom-node-lookupprefix + * @see https://github.com/xmldom/xmldom/issues/322 + */ + lookupPrefix:function(namespaceURI){ + var el = this; + while(el){ + var map = el._nsMap; + //console.dir(map) + if(map){ + for(var n in map){ + if (Object.prototype.hasOwnProperty.call(map, n) && map[n] === namespaceURI) { + return n; + } + } + } + el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; + } + return null; + }, + // Introduced in DOM Level 3: + lookupNamespaceURI:function(prefix){ + var el = this; + while(el){ + var map = el._nsMap; + //console.dir(map) + if(map){ + if(Object.prototype.hasOwnProperty.call(map, prefix)){ + return map[prefix] ; + } + } + el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; + } + return null; + }, + // Introduced in DOM Level 3: + isDefaultNamespace:function(namespaceURI){ + var prefix = this.lookupPrefix(namespaceURI); + return prefix == null; + } +}; + + +function _xmlEncoder(c){ + return c == '<' && '<' || + c == '>' && '>' || + c == '&' && '&' || + c == '"' && '"' || + '&#'+c.charCodeAt()+';' +} + + +copy(NodeType,Node); +copy(NodeType,Node.prototype); + +/** + * @param callback return true for continue,false for break + * @return boolean true: break visit; + */ +function _visitNode(node,callback){ + if(callback(node)){ + return true; + } + if(node = node.firstChild){ + do{ + if(_visitNode(node,callback)){return true} + }while(node=node.nextSibling) + } +} + + + +function Document(){ + this.ownerDocument = this; +} + +function _onAddAttribute(doc,el,newAttr){ + doc && doc._inc++; + var ns = newAttr.namespaceURI ; + if(ns === NAMESPACE.XMLNS){ + //update namespace + el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value + } +} + +function _onRemoveAttribute(doc,el,newAttr,remove){ + doc && doc._inc++; + var ns = newAttr.namespaceURI ; + if(ns === NAMESPACE.XMLNS){ + //update namespace + delete el._nsMap[newAttr.prefix?newAttr.localName:''] + } +} + +/** + * Updates `el.childNodes`, updating the indexed items and it's `length`. + * Passing `newChild` means it will be appended. + * Otherwise it's assumed that an item has been removed, + * and `el.firstNode` and it's `.nextSibling` are used + * to walk the current list of child nodes. + * + * @param {Document} doc + * @param {Node} el + * @param {Node} [newChild] + * @private + */ +function _onUpdateChild (doc, el, newChild) { + if(doc && doc._inc){ + doc._inc++; + //update childNodes + var cs = el.childNodes; + if (newChild) { + cs[cs.length++] = newChild; + } else { + var child = el.firstChild; + var i = 0; + while (child) { + cs[i++] = child; + child = child.nextSibling; + } + cs.length = i; + delete cs[cs.length]; + } + } +} + +/** + * Removes the connections between `parentNode` and `child` + * and any existing `child.previousSibling` or `child.nextSibling`. + * + * @see https://github.com/xmldom/xmldom/issues/135 + * @see https://github.com/xmldom/xmldom/issues/145 + * + * @param {Node} parentNode + * @param {Node} child + * @returns {Node} the child that was removed. + * @private + */ +function _removeChild (parentNode, child) { + var previous = child.previousSibling; + var next = child.nextSibling; + if (previous) { + previous.nextSibling = next; + } else { + parentNode.firstChild = next; + } + if (next) { + next.previousSibling = previous; + } else { + parentNode.lastChild = previous; + } + child.parentNode = null; + child.previousSibling = null; + child.nextSibling = null; + _onUpdateChild(parentNode.ownerDocument, parentNode); + return child; +} + +/** + * Returns `true` if `node` can be a parent for insertion. + * @param {Node} node + * @returns {boolean} + */ +function hasValidParentNodeType(node) { + return ( + node && + (node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE) + ); +} + +/** + * Returns `true` if `node` can be inserted according to it's `nodeType`. + * @param {Node} node + * @returns {boolean} + */ +function hasInsertableNodeType(node) { + return ( + node && + (isElementNode(node) || + isTextNode(node) || + isDocTypeNode(node) || + node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || + node.nodeType === Node.COMMENT_NODE || + node.nodeType === Node.PROCESSING_INSTRUCTION_NODE) + ); +} + +/** + * Returns true if `node` is a DOCTYPE node + * @param {Node} node + * @returns {boolean} + */ +function isDocTypeNode(node) { + return node && node.nodeType === Node.DOCUMENT_TYPE_NODE; +} + +/** + * Returns true if the node is an element + * @param {Node} node + * @returns {boolean} + */ +function isElementNode(node) { + return node && node.nodeType === Node.ELEMENT_NODE; +} +/** + * Returns true if `node` is a text node + * @param {Node} node + * @returns {boolean} + */ +function isTextNode(node) { + return node && node.nodeType === Node.TEXT_NODE; +} + +/** + * Check if en element node can be inserted before `child`, or at the end if child is falsy, + * according to the presence and position of a doctype node on the same level. + * + * @param {Document} doc The document node + * @param {Node} child the node that would become the nextSibling if the element would be inserted + * @returns {boolean} `true` if an element can be inserted before child + * @private + * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + */ +function isElementInsertionPossible(doc, child) { + var parentChildNodes = doc.childNodes || []; + if (find(parentChildNodes, isElementNode) || isDocTypeNode(child)) { + return false; + } + var docTypeNode = find(parentChildNodes, isDocTypeNode); + return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); +} + +/** + * Check if en element node can be inserted before `child`, or at the end if child is falsy, + * according to the presence and position of a doctype node on the same level. + * + * @param {Node} doc The document node + * @param {Node} child the node that would become the nextSibling if the element would be inserted + * @returns {boolean} `true` if an element can be inserted before child + * @private + * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + */ +function isElementReplacementPossible(doc, child) { + var parentChildNodes = doc.childNodes || []; + + function hasElementChildThatIsNotChild(node) { + return isElementNode(node) && node !== child; + } + + if (find(parentChildNodes, hasElementChildThatIsNotChild)) { + return false; + } + var docTypeNode = find(parentChildNodes, isDocTypeNode); + return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child)); +} + +/** + * @private + * Steps 1-5 of the checks before inserting and before replacing a child are the same. + * + * @param {Node} parent the parent node to insert `node` into + * @param {Node} node the node to insert + * @param {Node=} child the node that should become the `nextSibling` of `node` + * @returns {Node} + * @throws DOMException for several node combinations that would create a DOM that is not well-formed. + * @throws DOMException if `child` is provided but is not a child of `parent`. + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreInsertionValidity1to5(parent, node, child) { + // 1. If `parent` is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException. + if (!hasValidParentNodeType(parent)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType); + } + // 2. If `node` is a host-including inclusive ancestor of `parent`, then throw a "HierarchyRequestError" DOMException. + // not implemented! + // 3. If `child` is non-null and its parent is not `parent`, then throw a "NotFoundError" DOMException. + if (child && child.parentNode !== parent) { + throw new DOMException(NOT_FOUND_ERR, 'child not in parent'); + } + if ( + // 4. If `node` is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException. + !hasInsertableNodeType(node) || + // 5. If either `node` is a Text node and `parent` is a document, + // the sax parser currently adds top level text nodes, this will be fixed in 0.9.0 + // || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE) + // or `node` is a doctype and `parent` is not a document, then throw a "HierarchyRequestError" DOMException. + (isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE) + ) { + throw new DOMException( + HIERARCHY_REQUEST_ERR, + 'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType + ); + } +} + +/** + * @private + * Step 6 of the checks before inserting and before replacing a child are different. + * + * @param {Document} parent the parent node to insert `node` into + * @param {Node} node the node to insert + * @param {Node | undefined} child the node that should become the `nextSibling` of `node` + * @returns {Node} + * @throws DOMException for several node combinations that would create a DOM that is not well-formed. + * @throws DOMException if `child` is provided but is not a child of `parent`. + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreInsertionValidityInDocument(parent, node, child) { + var parentChildNodes = parent.childNodes || []; + var nodeChildNodes = node.childNodes || []; + + // DocumentFragment + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + var nodeChildElements = nodeChildNodes.filter(isElementNode); + // If node has more than one element child or has a Text node child. + if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); + } + // Otherwise, if `node` has one element child and either `parent` has an element child, + // `child` is a doctype, or `child` is non-null and a doctype is following `child`. + if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); + } + } + // Element + if (isElementNode(node)) { + // `parent` has an element child, `child` is a doctype, + // or `child` is non-null and a doctype is following `child`. + if (!isElementInsertionPossible(parent, child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); + } + } + // DocumentType + if (isDocTypeNode(node)) { + // `parent` has a doctype child, + if (find(parentChildNodes, isDocTypeNode)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); + } + var parentElementChild = find(parentChildNodes, isElementNode); + // `child` is non-null and an element is preceding `child`, + if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); + } + // or `child` is null and `parent` has an element child. + if (!child && parentElementChild) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present'); + } + } +} + +/** + * @private + * Step 6 of the checks before inserting and before replacing a child are different. + * + * @param {Document} parent the parent node to insert `node` into + * @param {Node} node the node to insert + * @param {Node | undefined} child the node that should become the `nextSibling` of `node` + * @returns {Node} + * @throws DOMException for several node combinations that would create a DOM that is not well-formed. + * @throws DOMException if `child` is provided but is not a child of `parent`. + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + * @see https://dom.spec.whatwg.org/#concept-node-replace + */ +function assertPreReplacementValidityInDocument(parent, node, child) { + var parentChildNodes = parent.childNodes || []; + var nodeChildNodes = node.childNodes || []; + + // DocumentFragment + if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + var nodeChildElements = nodeChildNodes.filter(isElementNode); + // If `node` has more than one element child or has a Text node child. + if (nodeChildElements.length > 1 || find(nodeChildNodes, isTextNode)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment'); + } + // Otherwise, if `node` has one element child and either `parent` has an element child that is not `child` or a doctype is following `child`. + if (nodeChildElements.length === 1 && !isElementReplacementPossible(parent, child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype'); + } + } + // Element + if (isElementNode(node)) { + // `parent` has an element child that is not `child` or a doctype is following `child`. + if (!isElementReplacementPossible(parent, child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype'); + } + } + // DocumentType + if (isDocTypeNode(node)) { + function hasDoctypeChildThatIsNotChild(node) { + return isDocTypeNode(node) && node !== child; + } + + // `parent` has a doctype child that is not `child`, + if (find(parentChildNodes, hasDoctypeChildThatIsNotChild)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed'); + } + var parentElementChild = find(parentChildNodes, isElementNode); + // or an element is preceding `child`. + if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) { + throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element'); + } + } +} + +/** + * @private + * @param {Node} parent the parent node to insert `node` into + * @param {Node} node the node to insert + * @param {Node=} child the node that should become the `nextSibling` of `node` + * @returns {Node} + * @throws DOMException for several node combinations that would create a DOM that is not well-formed. + * @throws DOMException if `child` is provided but is not a child of `parent`. + * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity + */ +function _insertBefore(parent, node, child, _inDocumentAssertion) { + // To ensure pre-insertion validity of a node into a parent before a child, run these steps: + assertPreInsertionValidity1to5(parent, node, child); + + // If parent is a document, and any of the statements below, switched on the interface node implements, + // are true, then throw a "HierarchyRequestError" DOMException. + if (parent.nodeType === Node.DOCUMENT_NODE) { + (_inDocumentAssertion || assertPreInsertionValidityInDocument)(parent, node, child); + } + + var cp = node.parentNode; + if(cp){ + cp.removeChild(node);//remove and update + } + if(node.nodeType === DOCUMENT_FRAGMENT_NODE){ + var newFirst = node.firstChild; + if (newFirst == null) { + return node; + } + var newLast = node.lastChild; + }else{ + newFirst = newLast = node; + } + var pre = child ? child.previousSibling : parent.lastChild; + + newFirst.previousSibling = pre; + newLast.nextSibling = child; + + + if(pre){ + pre.nextSibling = newFirst; + }else{ + parent.firstChild = newFirst; + } + if(child == null){ + parent.lastChild = newLast; + }else{ + child.previousSibling = newLast; + } + do{ + newFirst.parentNode = parent; + // Update ownerDocument for each node being inserted + var targetDoc = parent.ownerDocument || parent; + _updateOwnerDocument(newFirst, targetDoc); + }while(newFirst !== newLast && (newFirst= newFirst.nextSibling)) + _onUpdateChild(parent.ownerDocument||parent, parent); + //console.log(parent.lastChild.nextSibling == null) + if (node.nodeType == DOCUMENT_FRAGMENT_NODE) { + node.firstChild = node.lastChild = null; + } + return node; +} + +/** + * Recursively updates the ownerDocument property for a node and all its descendants + * @param {Node} node + * @param {Document} newOwnerDocument + * @private + */ +function _updateOwnerDocument(node, newOwnerDocument) { + if (node.ownerDocument === newOwnerDocument) { + return; + } + + node.ownerDocument = newOwnerDocument; + + // Update attributes if this is an element + if (node.nodeType === ELEMENT_NODE && node.attributes) { + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes.item(i); + if (attr) { + attr.ownerDocument = newOwnerDocument; + } + } + } + + // Recursively update child nodes + var child = node.firstChild; + while (child) { + _updateOwnerDocument(child, newOwnerDocument); + child = child.nextSibling; + } +} + +/** + * Appends `newChild` to `parentNode`. + * If `newChild` is already connected to a `parentNode` it is first removed from it. + * + * @see https://github.com/xmldom/xmldom/issues/135 + * @see https://github.com/xmldom/xmldom/issues/145 + * @param {Node} parentNode + * @param {Node} newChild + * @returns {Node} + * @private + */ +function _appendSingleChild (parentNode, newChild) { + if (newChild.parentNode) { + newChild.parentNode.removeChild(newChild); + } + newChild.parentNode = parentNode; + newChild.previousSibling = parentNode.lastChild; + newChild.nextSibling = null; + if (newChild.previousSibling) { + newChild.previousSibling.nextSibling = newChild; + } else { + parentNode.firstChild = newChild; + } + parentNode.lastChild = newChild; + _onUpdateChild(parentNode.ownerDocument, parentNode, newChild); + + // Update ownerDocument for the new child and all its descendants + var targetDoc = parentNode.ownerDocument || parentNode; + _updateOwnerDocument(newChild, targetDoc); + + return newChild; +} + +Document.prototype = { + //implementation : null, + nodeName : '#document', + nodeType : DOCUMENT_NODE, + /** + * The DocumentType node of the document. + * + * @readonly + * @type DocumentType + */ + doctype : null, + documentElement : null, + _inc : 1, + + insertBefore : function(newChild, refChild){//raises + if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){ + var child = newChild.firstChild; + while(child){ + var next = child.nextSibling; + this.insertBefore(child,refChild); + child = next; + } + return newChild; + } + _insertBefore(this, newChild, refChild); + _updateOwnerDocument(newChild, this); + if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) { + this.documentElement = newChild; + } + + return newChild; + }, + removeChild : function(oldChild){ + if(this.documentElement == oldChild){ + this.documentElement = null; + } + return _removeChild(this,oldChild); + }, + replaceChild: function (newChild, oldChild) { + //raises + _insertBefore(this, newChild, oldChild, assertPreReplacementValidityInDocument); + _updateOwnerDocument(newChild, this); + if (oldChild) { + this.removeChild(oldChild); + } + if (isElementNode(newChild)) { + this.documentElement = newChild; + } + }, + // Introduced in DOM Level 2: + importNode : function(importedNode,deep){ + return importNode(this,importedNode,deep); + }, + // Introduced in DOM Level 2: + getElementById : function(id){ + var rtv = null; + _visitNode(this.documentElement,function(node){ + if(node.nodeType == ELEMENT_NODE){ + if(node.getAttribute('id') == id){ + rtv = node; + return true; + } + } + }) + return rtv; + }, + + /** + * The `getElementsByClassName` method of `Document` interface returns an array-like object + * of all child elements which have **all** of the given class name(s). + * + * Returns an empty list if `classeNames` is an empty string or only contains HTML white space characters. + * + * + * Warning: This is a live LiveNodeList. + * Changes in the DOM will reflect in the array as the changes occur. + * If an element selected by this array no longer qualifies for the selector, + * it will automatically be removed. Be aware of this for iteration purposes. + * + * @param {string} classNames is a string representing the class name(s) to match; multiple class names are separated by (ASCII-)whitespace + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName + * @see https://dom.spec.whatwg.org/#concept-getelementsbyclassname + */ + getElementsByClassName: function(classNames) { + var classNamesSet = toOrderedSet(classNames) + return new LiveNodeList(this, function(base) { + var ls = []; + if (classNamesSet.length > 0) { + _visitNode(base.documentElement, function(node) { + if(node !== base && node.nodeType === ELEMENT_NODE) { + var nodeClassNames = node.getAttribute('class') + // can be null if the attribute does not exist + if (nodeClassNames) { + // before splitting and iterating just compare them for the most common case + var matches = classNames === nodeClassNames; + if (!matches) { + var nodeClassNamesSet = toOrderedSet(nodeClassNames) + matches = classNamesSet.every(arrayIncludes(nodeClassNamesSet)) + } + if(matches) { + ls.push(node); + } + } + } + }); + } + return ls; + }); + }, + + //document factory method: + createElement : function(tagName){ + var node = new Element(); + node.ownerDocument = this; + node.nodeName = tagName; + node.tagName = tagName; + node.localName = tagName; + node.childNodes = new NodeList(); + var attrs = node.attributes = new NamedNodeMap(); + attrs._ownerElement = node; + return node; + }, + createDocumentFragment : function(){ + var node = new DocumentFragment(); + node.ownerDocument = this; + node.childNodes = new NodeList(); + return node; + }, + createTextNode : function(data){ + var node = new Text(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createComment : function(data){ + var node = new Comment(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createCDATASection : function(data){ + var node = new CDATASection(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createProcessingInstruction : function(target,data){ + var node = new ProcessingInstruction(); + node.ownerDocument = this; + node.tagName = node.nodeName = node.target = target; + node.nodeValue = node.data = data; + return node; + }, + createAttribute : function(name){ + var node = new Attr(); + node.ownerDocument = this; + node.name = name; + node.nodeName = name; + node.localName = name; + node.specified = true; + return node; + }, + createEntityReference : function(name){ + var node = new EntityReference(); + node.ownerDocument = this; + node.nodeName = name; + return node; + }, + // Introduced in DOM Level 2: + createElementNS : function(namespaceURI,qualifiedName){ + var node = new Element(); + var pl = qualifiedName.split(':'); + var attrs = node.attributes = new NamedNodeMap(); + node.childNodes = new NodeList(); + node.ownerDocument = this; + node.nodeName = qualifiedName; + node.tagName = qualifiedName; + node.namespaceURI = namespaceURI; + if(pl.length == 2){ + node.prefix = pl[0]; + node.localName = pl[1]; + }else{ + //el.prefix = null; + node.localName = qualifiedName; + } + attrs._ownerElement = node; + return node; + }, + // Introduced in DOM Level 2: + createAttributeNS : function(namespaceURI,qualifiedName){ + var node = new Attr(); + var pl = qualifiedName.split(':'); + node.ownerDocument = this; + node.nodeName = qualifiedName; + node.name = qualifiedName; + node.namespaceURI = namespaceURI; + node.specified = true; + if(pl.length == 2){ + node.prefix = pl[0]; + node.localName = pl[1]; + }else{ + //el.prefix = null; + node.localName = qualifiedName; + } + return node; + } +}; +_extends(Document,Node); + + +function Element() { + this._nsMap = {}; +}; +Element.prototype = { + nodeType : ELEMENT_NODE, + hasAttribute : function(name){ + return this.getAttributeNode(name)!=null; + }, + getAttribute : function(name){ + var attr = this.getAttributeNode(name); + return attr && attr.value || ''; + }, + getAttributeNode : function(name){ + return this.attributes.getNamedItem(name); + }, + setAttribute : function(name, value){ + var attr = this.ownerDocument.createAttribute(name); + attr.value = attr.nodeValue = "" + value; + this.setAttributeNode(attr) + }, + removeAttribute : function(name){ + var attr = this.getAttributeNode(name) + attr && this.removeAttributeNode(attr); + }, + + //four real opeartion method + appendChild:function(newChild){ + if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ + return this.insertBefore(newChild,null); + }else{ + return _appendSingleChild(this,newChild); + } + }, + setAttributeNode : function(newAttr){ + return this.attributes.setNamedItem(newAttr); + }, + setAttributeNodeNS : function(newAttr){ + return this.attributes.setNamedItemNS(newAttr); + }, + removeAttributeNode : function(oldAttr){ + //console.log(this == oldAttr.ownerElement) + return this.attributes.removeNamedItem(oldAttr.nodeName); + }, + //get real attribute name,and remove it by removeAttributeNode + removeAttributeNS : function(namespaceURI, localName){ + var old = this.getAttributeNodeNS(namespaceURI, localName); + old && this.removeAttributeNode(old); + }, + + hasAttributeNS : function(namespaceURI, localName){ + return this.getAttributeNodeNS(namespaceURI, localName)!=null; + }, + getAttributeNS : function(namespaceURI, localName){ + var attr = this.getAttributeNodeNS(namespaceURI, localName); + return attr && attr.value || ''; + }, + setAttributeNS : function(namespaceURI, qualifiedName, value){ + var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); + attr.value = attr.nodeValue = "" + value; + this.setAttributeNode(attr) + }, + getAttributeNodeNS : function(namespaceURI, localName){ + return this.attributes.getNamedItemNS(namespaceURI, localName); + }, + + getElementsByTagName : function(tagName){ + return new LiveNodeList(this,function(base){ + var ls = []; + _visitNode(base,function(node){ + if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){ + ls.push(node); + } + }); + return ls; + }); + }, + getElementsByTagNameNS : function(namespaceURI, localName){ + return new LiveNodeList(this,function(base){ + var ls = []; + _visitNode(base,function(node){ + if(node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)){ + ls.push(node); + } + }); + return ls; + + }); + } +}; +Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; +Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; + + +_extends(Element,Node); +function Attr() { +}; +Attr.prototype.nodeType = ATTRIBUTE_NODE; +_extends(Attr,Node); + + +function CharacterData() { +}; +CharacterData.prototype = { + data : '', + substringData : function(offset, count) { + return this.data.substring(offset, offset+count); + }, + appendData: function(text) { + text = this.data+text; + this.nodeValue = this.data = text; + this.length = text.length; + }, + insertData: function(offset,text) { + this.replaceData(offset,0,text); + + }, + appendChild:function(newChild){ + throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]) + }, + deleteData: function(offset, count) { + this.replaceData(offset,count,""); + }, + replaceData: function(offset, count, text) { + var start = this.data.substring(0,offset); + var end = this.data.substring(offset+count); + text = start + text + end; + this.nodeValue = this.data = text; + this.length = text.length; + } +} +_extends(CharacterData,Node); +function Text() { +}; +Text.prototype = { + nodeName : "#text", + nodeType : TEXT_NODE, + splitText : function(offset) { + var text = this.data; + var newText = text.substring(offset); + text = text.substring(0, offset); + this.data = this.nodeValue = text; + this.length = text.length; + var newNode = this.ownerDocument.createTextNode(newText); + if(this.parentNode){ + this.parentNode.insertBefore(newNode, this.nextSibling); + } + return newNode; + } +} +_extends(Text,CharacterData); +function Comment() { +}; +Comment.prototype = { + nodeName : "#comment", + nodeType : COMMENT_NODE +} +_extends(Comment,CharacterData); + +function CDATASection() { +}; +CDATASection.prototype = { + nodeName : "#cdata-section", + nodeType : CDATA_SECTION_NODE +} +_extends(CDATASection,CharacterData); + + +function DocumentType() { +}; +DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; +_extends(DocumentType,Node); + +function Notation() { +}; +Notation.prototype.nodeType = NOTATION_NODE; +_extends(Notation,Node); + +function Entity() { +}; +Entity.prototype.nodeType = ENTITY_NODE; +_extends(Entity,Node); + +function EntityReference() { +}; +EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; +_extends(EntityReference,Node); + +function DocumentFragment() { +}; +DocumentFragment.prototype.nodeName = "#document-fragment"; +DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; +_extends(DocumentFragment,Node); + + +function ProcessingInstruction() { +} +ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; +_extends(ProcessingInstruction,Node); +function XMLSerializer(){} +XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){ + return nodeSerializeToString.call(node,isHtml,nodeFilter); +} +Node.prototype.toString = nodeSerializeToString; +function nodeSerializeToString(isHtml,nodeFilter){ + var buf = []; + var refNode = this.nodeType == 9 && this.documentElement || this; + var prefix = refNode.prefix; + var uri = refNode.namespaceURI; + + if(uri && prefix == null){ + //console.log(prefix) + var prefix = refNode.lookupPrefix(uri); + if(prefix == null){ + //isHTML = true; + var visibleNamespaces=[ + {namespace:uri,prefix:null} + //{namespace:uri,prefix:''} + ] + } + } + serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces); + //console.log('###',this.nodeType,uri,prefix,buf.join('')) + return buf.join(''); +} + +function needNamespaceDefine(node, isHTML, visibleNamespaces) { + var prefix = node.prefix || ''; + var uri = node.namespaceURI; + // According to [Namespaces in XML 1.0](https://www.w3.org/TR/REC-xml-names/#ns-using) , + // and more specifically https://www.w3.org/TR/REC-xml-names/#nsc-NoPrefixUndecl : + // > In a namespace declaration for a prefix [...], the attribute value MUST NOT be empty. + // in a similar manner [Namespaces in XML 1.1](https://www.w3.org/TR/xml-names11/#ns-using) + // and more specifically https://www.w3.org/TR/xml-names11/#nsc-NSDeclared : + // > [...] Furthermore, the attribute value [...] must not be an empty string. + // so serializing empty namespace value like xmlns:ds="" would produce an invalid XML document. + if (!uri) { + return false; + } + if (prefix === "xml" && uri === NAMESPACE.XML || uri === NAMESPACE.XMLNS) { + return false; + } + + var i = visibleNamespaces.length + while (i--) { + var ns = visibleNamespaces[i]; + // get namespace prefix + if (ns.prefix === prefix) { + return ns.namespace !== uri; + } + } + return true; +} +/** + * Well-formed constraint: No < in Attribute Values + * > The replacement text of any entity referred to directly or indirectly + * > in an attribute value must not contain a <. + * @see https://www.w3.org/TR/xml11/#CleanAttrVals + * @see https://www.w3.org/TR/xml11/#NT-AttValue + * + * Literal whitespace other than space that appear in attribute values + * are serialized as their entity references, so they will be preserved. + * (In contrast to whitespace literals in the input which are normalized to spaces) + * @see https://www.w3.org/TR/xml11/#AVNormalize + * @see https://w3c.github.io/DOM-Parsing/#serializing-an-element-s-attributes + */ +function addSerializedAttribute(buf, qualifiedName, value) { + buf.push(' ', qualifiedName, '="', value.replace(/[<>&"\t\n\r]/g, _xmlEncoder), '"') +} + +function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ + if (!visibleNamespaces) { + visibleNamespaces = []; + } + + if(nodeFilter){ + node = nodeFilter(node); + if(node){ + if(typeof node == 'string'){ + buf.push(node); + return; + } + }else{ + return; + } + //buf.sort.apply(attrs, attributeSorter); + } + + switch(node.nodeType){ + case ELEMENT_NODE: + var attrs = node.attributes; + var len = attrs.length; + var child = node.firstChild; + var nodeName = node.tagName; + + isHTML = NAMESPACE.isHTML(node.namespaceURI) || isHTML + + var prefixedNodeName = nodeName + if (!isHTML && !node.prefix && node.namespaceURI) { + var defaultNS + // lookup current default ns from `xmlns` attribute + for (var ai = 0; ai < attrs.length; ai++) { + if (attrs.item(ai).name === 'xmlns') { + defaultNS = attrs.item(ai).value + break + } + } + if (!defaultNS) { + // lookup current default ns in visibleNamespaces + for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { + var namespace = visibleNamespaces[nsi] + if (namespace.prefix === '' && namespace.namespace === node.namespaceURI) { + defaultNS = namespace.namespace + break + } + } + } + if (defaultNS !== node.namespaceURI) { + for (var nsi = visibleNamespaces.length - 1; nsi >= 0; nsi--) { + var namespace = visibleNamespaces[nsi] + if (namespace.namespace === node.namespaceURI) { + if (namespace.prefix) { + prefixedNodeName = namespace.prefix + ':' + nodeName + } + break + } + } + } + } + + buf.push('<', prefixedNodeName); + + for(var i=0;i<len;i++){ + // add namespaces for attributes + var attr = attrs.item(i); + if (attr.prefix == 'xmlns') { + visibleNamespaces.push({ prefix: attr.localName, namespace: attr.value }); + }else if(attr.nodeName == 'xmlns'){ + visibleNamespaces.push({ prefix: '', namespace: attr.value }); + } + } + + for(var i=0;i<len;i++){ + var attr = attrs.item(i); + if (needNamespaceDefine(attr,isHTML, visibleNamespaces)) { + var prefix = attr.prefix||''; + var uri = attr.namespaceURI; + addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); + visibleNamespaces.push({ prefix: prefix, namespace:uri }); + } + serializeToString(attr,buf,isHTML,nodeFilter,visibleNamespaces); + } + + // add namespace for current node + if (nodeName === prefixedNodeName && needNamespaceDefine(node, isHTML, visibleNamespaces)) { + var prefix = node.prefix||''; + var uri = node.namespaceURI; + addSerializedAttribute(buf, prefix ? 'xmlns:' + prefix : "xmlns", uri); + visibleNamespaces.push({ prefix: prefix, namespace:uri }); + } + + if(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){ + buf.push('>'); + //if is cdata child node + if(isHTML && /^script$/i.test(nodeName)){ + while(child){ + if(child.data){ + buf.push(child.data); + }else{ + serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); + } + child = child.nextSibling; + } + }else + { + while(child){ + serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); + child = child.nextSibling; + } + } + buf.push('</',prefixedNodeName,'>'); + }else{ + buf.push('/>'); + } + // remove added visible namespaces + //visibleNamespaces.length = startVisibleNamespaces; + return; + case DOCUMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + var child = node.firstChild; + while(child){ + serializeToString(child, buf, isHTML, nodeFilter, visibleNamespaces.slice()); + child = child.nextSibling; + } + return; + case ATTRIBUTE_NODE: + return addSerializedAttribute(buf, node.name, node.value); + case TEXT_NODE: + /** + * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, + * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. + * If they are needed elsewhere, they must be escaped using either numeric character references or the strings + * `&` and `<` respectively. + * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, + * be escaped using either `>` or a character reference when it appears in the string `]]>` in content, + * when that string is not marking the end of a CDATA section. + * + * In the content of elements, character data is any string of characters + * which does not contain the start-delimiter of any markup + * and does not include the CDATA-section-close delimiter, `]]>`. + * + * @see https://www.w3.org/TR/xml/#NT-CharData + * @see https://w3c.github.io/DOM-Parsing/#xml-serializing-a-text-node + */ + return buf.push(node.data + .replace(/[<&>]/g,_xmlEncoder) + ); + case CDATA_SECTION_NODE: + return buf.push( '<![CDATA[',node.data,']]>'); + case COMMENT_NODE: + return buf.push( "<!--",node.data,"-->"); + case DOCUMENT_TYPE_NODE: + var pubid = node.publicId; + var sysid = node.systemId; + buf.push('<!DOCTYPE ',node.name); + if(pubid){ + buf.push(' PUBLIC ', pubid); + if (sysid && sysid!='.') { + buf.push(' ', sysid); + } + buf.push('>'); + }else if(sysid && sysid!='.'){ + buf.push(' SYSTEM ', sysid, '>'); + }else{ + var sub = node.internalSubset; + if(sub){ + buf.push(" [",sub,"]"); + } + buf.push(">"); + } + return; + case PROCESSING_INSTRUCTION_NODE: + return buf.push( "<?",node.target," ",node.data,"?>"); + case ENTITY_REFERENCE_NODE: + return buf.push( '&',node.nodeName,';'); + //case ENTITY_NODE: + //case NOTATION_NODE: + default: + buf.push('??',node.nodeName); + } +} +function importNode(doc,node,deep){ + var node2; + switch (node.nodeType) { + case ELEMENT_NODE: + node2 = node.cloneNode(false); + node2.ownerDocument = doc; + //var attrs = node2.attributes; + //var len = attrs.length; + //for(var i=0;i<len;i++){ + //node2.setAttributeNodeNS(importNode(doc,attrs.item(i),deep)); + //} + case DOCUMENT_FRAGMENT_NODE: + break; + case ATTRIBUTE_NODE: + deep = true; + break; + //case ENTITY_REFERENCE_NODE: + //case PROCESSING_INSTRUCTION_NODE: + ////case TEXT_NODE: + //case CDATA_SECTION_NODE: + //case COMMENT_NODE: + // deep = false; + // break; + //case DOCUMENT_NODE: + //case DOCUMENT_TYPE_NODE: + //cannot be imported. + //case ENTITY_NODE: + //case NOTATION_NODE: + //can not hit in level3 + //default:throw e; + } + if(!node2){ + node2 = node.cloneNode(false);//false + } + node2.ownerDocument = doc; + node2.parentNode = null; + if(deep){ + var child = node.firstChild; + while(child){ + node2.appendChild(importNode(doc,child,deep)); + child = child.nextSibling; + } + } + return node2; +} +// +//var _relationMap = {firstChild:1,lastChild:1,previousSibling:1,nextSibling:1, +// attributes:1,childNodes:1,parentNode:1,documentElement:1,doctype,}; +function cloneNode(doc,node,deep){ + var node2 = new node.constructor(); + for (var n in node) { + if (Object.prototype.hasOwnProperty.call(node, n)) { + var v = node[n]; + if (typeof v != "object") { + if (v != node2[n]) { + node2[n] = v; + } + } + } + } + if(node.childNodes){ + node2.childNodes = new NodeList(); + } + node2.ownerDocument = doc; + switch (node2.nodeType) { + case ELEMENT_NODE: + var attrs = node.attributes; + var attrs2 = node2.attributes = new NamedNodeMap(); + var len = attrs.length + attrs2._ownerElement = node2; + for(var i=0;i<len;i++){ + node2.setAttributeNode(cloneNode(doc,attrs.item(i),true)); + } + break;; + case ATTRIBUTE_NODE: + deep = true; + } + if(deep){ + var child = node.firstChild; + while(child){ + node2.appendChild(cloneNode(doc,child,deep)); + child = child.nextSibling; + } + } + return node2; +} + +function __set__(object,key,value){ + object[key] = value +} +//do dynamic +try{ + if(Object.defineProperty){ + Object.defineProperty(LiveNodeList.prototype,'length',{ + get:function(){ + _updateLiveList(this); + return this.$$length; + } + }); + + Object.defineProperty(Node.prototype,'textContent',{ + get:function(){ + return getTextContent(this); + }, + + set:function(data){ + switch(this.nodeType){ + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + while(this.firstChild){ + this.removeChild(this.firstChild); + } + if(data || String(data)){ + this.appendChild(this.ownerDocument.createTextNode(data)); + } + break; + + default: + this.data = data; + this.value = data; + this.nodeValue = data; + } + } + }) + + function getTextContent(node){ + switch(node.nodeType){ + case ELEMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + var buf = []; + node = node.firstChild; + while(node){ + if(node.nodeType!==7 && node.nodeType !==8){ + buf.push(getTextContent(node)); + } + node = node.nextSibling; + } + return buf.join(''); + default: + return node.nodeValue; + } + } + + __set__ = function(object,key,value){ + //console.log(value) + object['$$'+key] = value + } + } +}catch(e){//ie8 +} + +//if(typeof require == 'function'){ + exports.DocumentType = DocumentType; + exports.DOMException = DOMException; + exports.DOMImplementation = DOMImplementation; + exports.Element = Element; + exports.Node = Node; + exports.NodeList = NodeList; + exports.XMLSerializer = XMLSerializer; +//} + +},{"./conventions":42}],45:[function(require,module,exports){ +'use strict'; + +var freeze = require('./conventions').freeze; + +/** + * The entities that are predefined in every XML document. + * + * @see https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-predefined-ent W3C XML 1.1 + * @see https://www.w3.org/TR/2008/REC-xml-20081126/#sec-predefined-ent W3C XML 1.0 + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML Wikipedia + */ +exports.XML_ENTITIES = freeze({ + amp: '&', + apos: "'", + gt: '>', + lt: '<', + quot: '"', +}); + +/** + * A map of all entities that are detected in an HTML document. + * They contain all entries from `XML_ENTITIES`. + * + * @see XML_ENTITIES + * @see DOMParser.parseFromString + * @see DOMImplementation.prototype.createHTMLDocument + * @see https://html.spec.whatwg.org/#named-character-references WHATWG HTML(5) Spec + * @see https://html.spec.whatwg.org/entities.json JSON + * @see https://www.w3.org/TR/xml-entity-names/ W3C XML Entity Names + * @see https://www.w3.org/TR/html4/sgml/entities.html W3C HTML4/SGML + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Character_entity_references_in_HTML Wikipedia (HTML) + * @see https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Entities_representing_special_characters_in_XHTML Wikpedia (XHTML) + */ +exports.HTML_ENTITIES = freeze({ + Aacute: '\u00C1', + aacute: '\u00E1', + Abreve: '\u0102', + abreve: '\u0103', + ac: '\u223E', + acd: '\u223F', + acE: '\u223E\u0333', + Acirc: '\u00C2', + acirc: '\u00E2', + acute: '\u00B4', + Acy: '\u0410', + acy: '\u0430', + AElig: '\u00C6', + aelig: '\u00E6', + af: '\u2061', + Afr: '\uD835\uDD04', + afr: '\uD835\uDD1E', + Agrave: '\u00C0', + agrave: '\u00E0', + alefsym: '\u2135', + aleph: '\u2135', + Alpha: '\u0391', + alpha: '\u03B1', + Amacr: '\u0100', + amacr: '\u0101', + amalg: '\u2A3F', + AMP: '\u0026', + amp: '\u0026', + And: '\u2A53', + and: '\u2227', + andand: '\u2A55', + andd: '\u2A5C', + andslope: '\u2A58', + andv: '\u2A5A', + ang: '\u2220', + ange: '\u29A4', + angle: '\u2220', + angmsd: '\u2221', + angmsdaa: '\u29A8', + angmsdab: '\u29A9', + angmsdac: '\u29AA', + angmsdad: '\u29AB', + angmsdae: '\u29AC', + angmsdaf: '\u29AD', + angmsdag: '\u29AE', + angmsdah: '\u29AF', + angrt: '\u221F', + angrtvb: '\u22BE', + angrtvbd: '\u299D', + angsph: '\u2222', + angst: '\u00C5', + angzarr: '\u237C', + Aogon: '\u0104', + aogon: '\u0105', + Aopf: '\uD835\uDD38', + aopf: '\uD835\uDD52', + ap: '\u2248', + apacir: '\u2A6F', + apE: '\u2A70', + ape: '\u224A', + apid: '\u224B', + apos: '\u0027', + ApplyFunction: '\u2061', + approx: '\u2248', + approxeq: '\u224A', + Aring: '\u00C5', + aring: '\u00E5', + Ascr: '\uD835\uDC9C', + ascr: '\uD835\uDCB6', + Assign: '\u2254', + ast: '\u002A', + asymp: '\u2248', + asympeq: '\u224D', + Atilde: '\u00C3', + atilde: '\u00E3', + Auml: '\u00C4', + auml: '\u00E4', + awconint: '\u2233', + awint: '\u2A11', + backcong: '\u224C', + backepsilon: '\u03F6', + backprime: '\u2035', + backsim: '\u223D', + backsimeq: '\u22CD', + Backslash: '\u2216', + Barv: '\u2AE7', + barvee: '\u22BD', + Barwed: '\u2306', + barwed: '\u2305', + barwedge: '\u2305', + bbrk: '\u23B5', + bbrktbrk: '\u23B6', + bcong: '\u224C', + Bcy: '\u0411', + bcy: '\u0431', + bdquo: '\u201E', + becaus: '\u2235', + Because: '\u2235', + because: '\u2235', + bemptyv: '\u29B0', + bepsi: '\u03F6', + bernou: '\u212C', + Bernoullis: '\u212C', + Beta: '\u0392', + beta: '\u03B2', + beth: '\u2136', + between: '\u226C', + Bfr: '\uD835\uDD05', + bfr: '\uD835\uDD1F', + bigcap: '\u22C2', + bigcirc: '\u25EF', + bigcup: '\u22C3', + bigodot: '\u2A00', + bigoplus: '\u2A01', + bigotimes: '\u2A02', + bigsqcup: '\u2A06', + bigstar: '\u2605', + bigtriangledown: '\u25BD', + bigtriangleup: '\u25B3', + biguplus: '\u2A04', + bigvee: '\u22C1', + bigwedge: '\u22C0', + bkarow: '\u290D', + blacklozenge: '\u29EB', + blacksquare: '\u25AA', + blacktriangle: '\u25B4', + blacktriangledown: '\u25BE', + blacktriangleleft: '\u25C2', + blacktriangleright: '\u25B8', + blank: '\u2423', + blk12: '\u2592', + blk14: '\u2591', + blk34: '\u2593', + block: '\u2588', + bne: '\u003D\u20E5', + bnequiv: '\u2261\u20E5', + bNot: '\u2AED', + bnot: '\u2310', + Bopf: '\uD835\uDD39', + bopf: '\uD835\uDD53', + bot: '\u22A5', + bottom: '\u22A5', + bowtie: '\u22C8', + boxbox: '\u29C9', + boxDL: '\u2557', + boxDl: '\u2556', + boxdL: '\u2555', + boxdl: '\u2510', + boxDR: '\u2554', + boxDr: '\u2553', + boxdR: '\u2552', + boxdr: '\u250C', + boxH: '\u2550', + boxh: '\u2500', + boxHD: '\u2566', + boxHd: '\u2564', + boxhD: '\u2565', + boxhd: '\u252C', + boxHU: '\u2569', + boxHu: '\u2567', + boxhU: '\u2568', + boxhu: '\u2534', + boxminus: '\u229F', + boxplus: '\u229E', + boxtimes: '\u22A0', + boxUL: '\u255D', + boxUl: '\u255C', + boxuL: '\u255B', + boxul: '\u2518', + boxUR: '\u255A', + boxUr: '\u2559', + boxuR: '\u2558', + boxur: '\u2514', + boxV: '\u2551', + boxv: '\u2502', + boxVH: '\u256C', + boxVh: '\u256B', + boxvH: '\u256A', + boxvh: '\u253C', + boxVL: '\u2563', + boxVl: '\u2562', + boxvL: '\u2561', + boxvl: '\u2524', + boxVR: '\u2560', + boxVr: '\u255F', + boxvR: '\u255E', + boxvr: '\u251C', + bprime: '\u2035', + Breve: '\u02D8', + breve: '\u02D8', + brvbar: '\u00A6', + Bscr: '\u212C', + bscr: '\uD835\uDCB7', + bsemi: '\u204F', + bsim: '\u223D', + bsime: '\u22CD', + bsol: '\u005C', + bsolb: '\u29C5', + bsolhsub: '\u27C8', + bull: '\u2022', + bullet: '\u2022', + bump: '\u224E', + bumpE: '\u2AAE', + bumpe: '\u224F', + Bumpeq: '\u224E', + bumpeq: '\u224F', + Cacute: '\u0106', + cacute: '\u0107', + Cap: '\u22D2', + cap: '\u2229', + capand: '\u2A44', + capbrcup: '\u2A49', + capcap: '\u2A4B', + capcup: '\u2A47', + capdot: '\u2A40', + CapitalDifferentialD: '\u2145', + caps: '\u2229\uFE00', + caret: '\u2041', + caron: '\u02C7', + Cayleys: '\u212D', + ccaps: '\u2A4D', + Ccaron: '\u010C', + ccaron: '\u010D', + Ccedil: '\u00C7', + ccedil: '\u00E7', + Ccirc: '\u0108', + ccirc: '\u0109', + Cconint: '\u2230', + ccups: '\u2A4C', + ccupssm: '\u2A50', + Cdot: '\u010A', + cdot: '\u010B', + cedil: '\u00B8', + Cedilla: '\u00B8', + cemptyv: '\u29B2', + cent: '\u00A2', + CenterDot: '\u00B7', + centerdot: '\u00B7', + Cfr: '\u212D', + cfr: '\uD835\uDD20', + CHcy: '\u0427', + chcy: '\u0447', + check: '\u2713', + checkmark: '\u2713', + Chi: '\u03A7', + chi: '\u03C7', + cir: '\u25CB', + circ: '\u02C6', + circeq: '\u2257', + circlearrowleft: '\u21BA', + circlearrowright: '\u21BB', + circledast: '\u229B', + circledcirc: '\u229A', + circleddash: '\u229D', + CircleDot: '\u2299', + circledR: '\u00AE', + circledS: '\u24C8', + CircleMinus: '\u2296', + CirclePlus: '\u2295', + CircleTimes: '\u2297', + cirE: '\u29C3', + cire: '\u2257', + cirfnint: '\u2A10', + cirmid: '\u2AEF', + cirscir: '\u29C2', + ClockwiseContourIntegral: '\u2232', + CloseCurlyDoubleQuote: '\u201D', + CloseCurlyQuote: '\u2019', + clubs: '\u2663', + clubsuit: '\u2663', + Colon: '\u2237', + colon: '\u003A', + Colone: '\u2A74', + colone: '\u2254', + coloneq: '\u2254', + comma: '\u002C', + commat: '\u0040', + comp: '\u2201', + compfn: '\u2218', + complement: '\u2201', + complexes: '\u2102', + cong: '\u2245', + congdot: '\u2A6D', + Congruent: '\u2261', + Conint: '\u222F', + conint: '\u222E', + ContourIntegral: '\u222E', + Copf: '\u2102', + copf: '\uD835\uDD54', + coprod: '\u2210', + Coproduct: '\u2210', + COPY: '\u00A9', + copy: '\u00A9', + copysr: '\u2117', + CounterClockwiseContourIntegral: '\u2233', + crarr: '\u21B5', + Cross: '\u2A2F', + cross: '\u2717', + Cscr: '\uD835\uDC9E', + cscr: '\uD835\uDCB8', + csub: '\u2ACF', + csube: '\u2AD1', + csup: '\u2AD0', + csupe: '\u2AD2', + ctdot: '\u22EF', + cudarrl: '\u2938', + cudarrr: '\u2935', + cuepr: '\u22DE', + cuesc: '\u22DF', + cularr: '\u21B6', + cularrp: '\u293D', + Cup: '\u22D3', + cup: '\u222A', + cupbrcap: '\u2A48', + CupCap: '\u224D', + cupcap: '\u2A46', + cupcup: '\u2A4A', + cupdot: '\u228D', + cupor: '\u2A45', + cups: '\u222A\uFE00', + curarr: '\u21B7', + curarrm: '\u293C', + curlyeqprec: '\u22DE', + curlyeqsucc: '\u22DF', + curlyvee: '\u22CE', + curlywedge: '\u22CF', + curren: '\u00A4', + curvearrowleft: '\u21B6', + curvearrowright: '\u21B7', + cuvee: '\u22CE', + cuwed: '\u22CF', + cwconint: '\u2232', + cwint: '\u2231', + cylcty: '\u232D', + Dagger: '\u2021', + dagger: '\u2020', + daleth: '\u2138', + Darr: '\u21A1', + dArr: '\u21D3', + darr: '\u2193', + dash: '\u2010', + Dashv: '\u2AE4', + dashv: '\u22A3', + dbkarow: '\u290F', + dblac: '\u02DD', + Dcaron: '\u010E', + dcaron: '\u010F', + Dcy: '\u0414', + dcy: '\u0434', + DD: '\u2145', + dd: '\u2146', + ddagger: '\u2021', + ddarr: '\u21CA', + DDotrahd: '\u2911', + ddotseq: '\u2A77', + deg: '\u00B0', + Del: '\u2207', + Delta: '\u0394', + delta: '\u03B4', + demptyv: '\u29B1', + dfisht: '\u297F', + Dfr: '\uD835\uDD07', + dfr: '\uD835\uDD21', + dHar: '\u2965', + dharl: '\u21C3', + dharr: '\u21C2', + DiacriticalAcute: '\u00B4', + DiacriticalDot: '\u02D9', + DiacriticalDoubleAcute: '\u02DD', + DiacriticalGrave: '\u0060', + DiacriticalTilde: '\u02DC', + diam: '\u22C4', + Diamond: '\u22C4', + diamond: '\u22C4', + diamondsuit: '\u2666', + diams: '\u2666', + die: '\u00A8', + DifferentialD: '\u2146', + digamma: '\u03DD', + disin: '\u22F2', + div: '\u00F7', + divide: '\u00F7', + divideontimes: '\u22C7', + divonx: '\u22C7', + DJcy: '\u0402', + djcy: '\u0452', + dlcorn: '\u231E', + dlcrop: '\u230D', + dollar: '\u0024', + Dopf: '\uD835\uDD3B', + dopf: '\uD835\uDD55', + Dot: '\u00A8', + dot: '\u02D9', + DotDot: '\u20DC', + doteq: '\u2250', + doteqdot: '\u2251', + DotEqual: '\u2250', + dotminus: '\u2238', + dotplus: '\u2214', + dotsquare: '\u22A1', + doublebarwedge: '\u2306', + DoubleContourIntegral: '\u222F', + DoubleDot: '\u00A8', + DoubleDownArrow: '\u21D3', + DoubleLeftArrow: '\u21D0', + DoubleLeftRightArrow: '\u21D4', + DoubleLeftTee: '\u2AE4', + DoubleLongLeftArrow: '\u27F8', + DoubleLongLeftRightArrow: '\u27FA', + DoubleLongRightArrow: '\u27F9', + DoubleRightArrow: '\u21D2', + DoubleRightTee: '\u22A8', + DoubleUpArrow: '\u21D1', + DoubleUpDownArrow: '\u21D5', + DoubleVerticalBar: '\u2225', + DownArrow: '\u2193', + Downarrow: '\u21D3', + downarrow: '\u2193', + DownArrowBar: '\u2913', + DownArrowUpArrow: '\u21F5', + DownBreve: '\u0311', + downdownarrows: '\u21CA', + downharpoonleft: '\u21C3', + downharpoonright: '\u21C2', + DownLeftRightVector: '\u2950', + DownLeftTeeVector: '\u295E', + DownLeftVector: '\u21BD', + DownLeftVectorBar: '\u2956', + DownRightTeeVector: '\u295F', + DownRightVector: '\u21C1', + DownRightVectorBar: '\u2957', + DownTee: '\u22A4', + DownTeeArrow: '\u21A7', + drbkarow: '\u2910', + drcorn: '\u231F', + drcrop: '\u230C', + Dscr: '\uD835\uDC9F', + dscr: '\uD835\uDCB9', + DScy: '\u0405', + dscy: '\u0455', + dsol: '\u29F6', + Dstrok: '\u0110', + dstrok: '\u0111', + dtdot: '\u22F1', + dtri: '\u25BF', + dtrif: '\u25BE', + duarr: '\u21F5', + duhar: '\u296F', + dwangle: '\u29A6', + DZcy: '\u040F', + dzcy: '\u045F', + dzigrarr: '\u27FF', + Eacute: '\u00C9', + eacute: '\u00E9', + easter: '\u2A6E', + Ecaron: '\u011A', + ecaron: '\u011B', + ecir: '\u2256', + Ecirc: '\u00CA', + ecirc: '\u00EA', + ecolon: '\u2255', + Ecy: '\u042D', + ecy: '\u044D', + eDDot: '\u2A77', + Edot: '\u0116', + eDot: '\u2251', + edot: '\u0117', + ee: '\u2147', + efDot: '\u2252', + Efr: '\uD835\uDD08', + efr: '\uD835\uDD22', + eg: '\u2A9A', + Egrave: '\u00C8', + egrave: '\u00E8', + egs: '\u2A96', + egsdot: '\u2A98', + el: '\u2A99', + Element: '\u2208', + elinters: '\u23E7', + ell: '\u2113', + els: '\u2A95', + elsdot: '\u2A97', + Emacr: '\u0112', + emacr: '\u0113', + empty: '\u2205', + emptyset: '\u2205', + EmptySmallSquare: '\u25FB', + emptyv: '\u2205', + EmptyVerySmallSquare: '\u25AB', + emsp: '\u2003', + emsp13: '\u2004', + emsp14: '\u2005', + ENG: '\u014A', + eng: '\u014B', + ensp: '\u2002', + Eogon: '\u0118', + eogon: '\u0119', + Eopf: '\uD835\uDD3C', + eopf: '\uD835\uDD56', + epar: '\u22D5', + eparsl: '\u29E3', + eplus: '\u2A71', + epsi: '\u03B5', + Epsilon: '\u0395', + epsilon: '\u03B5', + epsiv: '\u03F5', + eqcirc: '\u2256', + eqcolon: '\u2255', + eqsim: '\u2242', + eqslantgtr: '\u2A96', + eqslantless: '\u2A95', + Equal: '\u2A75', + equals: '\u003D', + EqualTilde: '\u2242', + equest: '\u225F', + Equilibrium: '\u21CC', + equiv: '\u2261', + equivDD: '\u2A78', + eqvparsl: '\u29E5', + erarr: '\u2971', + erDot: '\u2253', + Escr: '\u2130', + escr: '\u212F', + esdot: '\u2250', + Esim: '\u2A73', + esim: '\u2242', + Eta: '\u0397', + eta: '\u03B7', + ETH: '\u00D0', + eth: '\u00F0', + Euml: '\u00CB', + euml: '\u00EB', + euro: '\u20AC', + excl: '\u0021', + exist: '\u2203', + Exists: '\u2203', + expectation: '\u2130', + ExponentialE: '\u2147', + exponentiale: '\u2147', + fallingdotseq: '\u2252', + Fcy: '\u0424', + fcy: '\u0444', + female: '\u2640', + ffilig: '\uFB03', + fflig: '\uFB00', + ffllig: '\uFB04', + Ffr: '\uD835\uDD09', + ffr: '\uD835\uDD23', + filig: '\uFB01', + FilledSmallSquare: '\u25FC', + FilledVerySmallSquare: '\u25AA', + fjlig: '\u0066\u006A', + flat: '\u266D', + fllig: '\uFB02', + fltns: '\u25B1', + fnof: '\u0192', + Fopf: '\uD835\uDD3D', + fopf: '\uD835\uDD57', + ForAll: '\u2200', + forall: '\u2200', + fork: '\u22D4', + forkv: '\u2AD9', + Fouriertrf: '\u2131', + fpartint: '\u2A0D', + frac12: '\u00BD', + frac13: '\u2153', + frac14: '\u00BC', + frac15: '\u2155', + frac16: '\u2159', + frac18: '\u215B', + frac23: '\u2154', + frac25: '\u2156', + frac34: '\u00BE', + frac35: '\u2157', + frac38: '\u215C', + frac45: '\u2158', + frac56: '\u215A', + frac58: '\u215D', + frac78: '\u215E', + frasl: '\u2044', + frown: '\u2322', + Fscr: '\u2131', + fscr: '\uD835\uDCBB', + gacute: '\u01F5', + Gamma: '\u0393', + gamma: '\u03B3', + Gammad: '\u03DC', + gammad: '\u03DD', + gap: '\u2A86', + Gbreve: '\u011E', + gbreve: '\u011F', + Gcedil: '\u0122', + Gcirc: '\u011C', + gcirc: '\u011D', + Gcy: '\u0413', + gcy: '\u0433', + Gdot: '\u0120', + gdot: '\u0121', + gE: '\u2267', + ge: '\u2265', + gEl: '\u2A8C', + gel: '\u22DB', + geq: '\u2265', + geqq: '\u2267', + geqslant: '\u2A7E', + ges: '\u2A7E', + gescc: '\u2AA9', + gesdot: '\u2A80', + gesdoto: '\u2A82', + gesdotol: '\u2A84', + gesl: '\u22DB\uFE00', + gesles: '\u2A94', + Gfr: '\uD835\uDD0A', + gfr: '\uD835\uDD24', + Gg: '\u22D9', + gg: '\u226B', + ggg: '\u22D9', + gimel: '\u2137', + GJcy: '\u0403', + gjcy: '\u0453', + gl: '\u2277', + gla: '\u2AA5', + glE: '\u2A92', + glj: '\u2AA4', + gnap: '\u2A8A', + gnapprox: '\u2A8A', + gnE: '\u2269', + gne: '\u2A88', + gneq: '\u2A88', + gneqq: '\u2269', + gnsim: '\u22E7', + Gopf: '\uD835\uDD3E', + gopf: '\uD835\uDD58', + grave: '\u0060', + GreaterEqual: '\u2265', + GreaterEqualLess: '\u22DB', + GreaterFullEqual: '\u2267', + GreaterGreater: '\u2AA2', + GreaterLess: '\u2277', + GreaterSlantEqual: '\u2A7E', + GreaterTilde: '\u2273', + Gscr: '\uD835\uDCA2', + gscr: '\u210A', + gsim: '\u2273', + gsime: '\u2A8E', + gsiml: '\u2A90', + Gt: '\u226B', + GT: '\u003E', + gt: '\u003E', + gtcc: '\u2AA7', + gtcir: '\u2A7A', + gtdot: '\u22D7', + gtlPar: '\u2995', + gtquest: '\u2A7C', + gtrapprox: '\u2A86', + gtrarr: '\u2978', + gtrdot: '\u22D7', + gtreqless: '\u22DB', + gtreqqless: '\u2A8C', + gtrless: '\u2277', + gtrsim: '\u2273', + gvertneqq: '\u2269\uFE00', + gvnE: '\u2269\uFE00', + Hacek: '\u02C7', + hairsp: '\u200A', + half: '\u00BD', + hamilt: '\u210B', + HARDcy: '\u042A', + hardcy: '\u044A', + hArr: '\u21D4', + harr: '\u2194', + harrcir: '\u2948', + harrw: '\u21AD', + Hat: '\u005E', + hbar: '\u210F', + Hcirc: '\u0124', + hcirc: '\u0125', + hearts: '\u2665', + heartsuit: '\u2665', + hellip: '\u2026', + hercon: '\u22B9', + Hfr: '\u210C', + hfr: '\uD835\uDD25', + HilbertSpace: '\u210B', + hksearow: '\u2925', + hkswarow: '\u2926', + hoarr: '\u21FF', + homtht: '\u223B', + hookleftarrow: '\u21A9', + hookrightarrow: '\u21AA', + Hopf: '\u210D', + hopf: '\uD835\uDD59', + horbar: '\u2015', + HorizontalLine: '\u2500', + Hscr: '\u210B', + hscr: '\uD835\uDCBD', + hslash: '\u210F', + Hstrok: '\u0126', + hstrok: '\u0127', + HumpDownHump: '\u224E', + HumpEqual: '\u224F', + hybull: '\u2043', + hyphen: '\u2010', + Iacute: '\u00CD', + iacute: '\u00ED', + ic: '\u2063', + Icirc: '\u00CE', + icirc: '\u00EE', + Icy: '\u0418', + icy: '\u0438', + Idot: '\u0130', + IEcy: '\u0415', + iecy: '\u0435', + iexcl: '\u00A1', + iff: '\u21D4', + Ifr: '\u2111', + ifr: '\uD835\uDD26', + Igrave: '\u00CC', + igrave: '\u00EC', + ii: '\u2148', + iiiint: '\u2A0C', + iiint: '\u222D', + iinfin: '\u29DC', + iiota: '\u2129', + IJlig: '\u0132', + ijlig: '\u0133', + Im: '\u2111', + Imacr: '\u012A', + imacr: '\u012B', + image: '\u2111', + ImaginaryI: '\u2148', + imagline: '\u2110', + imagpart: '\u2111', + imath: '\u0131', + imof: '\u22B7', + imped: '\u01B5', + Implies: '\u21D2', + in: '\u2208', + incare: '\u2105', + infin: '\u221E', + infintie: '\u29DD', + inodot: '\u0131', + Int: '\u222C', + int: '\u222B', + intcal: '\u22BA', + integers: '\u2124', + Integral: '\u222B', + intercal: '\u22BA', + Intersection: '\u22C2', + intlarhk: '\u2A17', + intprod: '\u2A3C', + InvisibleComma: '\u2063', + InvisibleTimes: '\u2062', + IOcy: '\u0401', + iocy: '\u0451', + Iogon: '\u012E', + iogon: '\u012F', + Iopf: '\uD835\uDD40', + iopf: '\uD835\uDD5A', + Iota: '\u0399', + iota: '\u03B9', + iprod: '\u2A3C', + iquest: '\u00BF', + Iscr: '\u2110', + iscr: '\uD835\uDCBE', + isin: '\u2208', + isindot: '\u22F5', + isinE: '\u22F9', + isins: '\u22F4', + isinsv: '\u22F3', + isinv: '\u2208', + it: '\u2062', + Itilde: '\u0128', + itilde: '\u0129', + Iukcy: '\u0406', + iukcy: '\u0456', + Iuml: '\u00CF', + iuml: '\u00EF', + Jcirc: '\u0134', + jcirc: '\u0135', + Jcy: '\u0419', + jcy: '\u0439', + Jfr: '\uD835\uDD0D', + jfr: '\uD835\uDD27', + jmath: '\u0237', + Jopf: '\uD835\uDD41', + jopf: '\uD835\uDD5B', + Jscr: '\uD835\uDCA5', + jscr: '\uD835\uDCBF', + Jsercy: '\u0408', + jsercy: '\u0458', + Jukcy: '\u0404', + jukcy: '\u0454', + Kappa: '\u039A', + kappa: '\u03BA', + kappav: '\u03F0', + Kcedil: '\u0136', + kcedil: '\u0137', + Kcy: '\u041A', + kcy: '\u043A', + Kfr: '\uD835\uDD0E', + kfr: '\uD835\uDD28', + kgreen: '\u0138', + KHcy: '\u0425', + khcy: '\u0445', + KJcy: '\u040C', + kjcy: '\u045C', + Kopf: '\uD835\uDD42', + kopf: '\uD835\uDD5C', + Kscr: '\uD835\uDCA6', + kscr: '\uD835\uDCC0', + lAarr: '\u21DA', + Lacute: '\u0139', + lacute: '\u013A', + laemptyv: '\u29B4', + lagran: '\u2112', + Lambda: '\u039B', + lambda: '\u03BB', + Lang: '\u27EA', + lang: '\u27E8', + langd: '\u2991', + langle: '\u27E8', + lap: '\u2A85', + Laplacetrf: '\u2112', + laquo: '\u00AB', + Larr: '\u219E', + lArr: '\u21D0', + larr: '\u2190', + larrb: '\u21E4', + larrbfs: '\u291F', + larrfs: '\u291D', + larrhk: '\u21A9', + larrlp: '\u21AB', + larrpl: '\u2939', + larrsim: '\u2973', + larrtl: '\u21A2', + lat: '\u2AAB', + lAtail: '\u291B', + latail: '\u2919', + late: '\u2AAD', + lates: '\u2AAD\uFE00', + lBarr: '\u290E', + lbarr: '\u290C', + lbbrk: '\u2772', + lbrace: '\u007B', + lbrack: '\u005B', + lbrke: '\u298B', + lbrksld: '\u298F', + lbrkslu: '\u298D', + Lcaron: '\u013D', + lcaron: '\u013E', + Lcedil: '\u013B', + lcedil: '\u013C', + lceil: '\u2308', + lcub: '\u007B', + Lcy: '\u041B', + lcy: '\u043B', + ldca: '\u2936', + ldquo: '\u201C', + ldquor: '\u201E', + ldrdhar: '\u2967', + ldrushar: '\u294B', + ldsh: '\u21B2', + lE: '\u2266', + le: '\u2264', + LeftAngleBracket: '\u27E8', + LeftArrow: '\u2190', + Leftarrow: '\u21D0', + leftarrow: '\u2190', + LeftArrowBar: '\u21E4', + LeftArrowRightArrow: '\u21C6', + leftarrowtail: '\u21A2', + LeftCeiling: '\u2308', + LeftDoubleBracket: '\u27E6', + LeftDownTeeVector: '\u2961', + LeftDownVector: '\u21C3', + LeftDownVectorBar: '\u2959', + LeftFloor: '\u230A', + leftharpoondown: '\u21BD', + leftharpoonup: '\u21BC', + leftleftarrows: '\u21C7', + LeftRightArrow: '\u2194', + Leftrightarrow: '\u21D4', + leftrightarrow: '\u2194', + leftrightarrows: '\u21C6', + leftrightharpoons: '\u21CB', + leftrightsquigarrow: '\u21AD', + LeftRightVector: '\u294E', + LeftTee: '\u22A3', + LeftTeeArrow: '\u21A4', + LeftTeeVector: '\u295A', + leftthreetimes: '\u22CB', + LeftTriangle: '\u22B2', + LeftTriangleBar: '\u29CF', + LeftTriangleEqual: '\u22B4', + LeftUpDownVector: '\u2951', + LeftUpTeeVector: '\u2960', + LeftUpVector: '\u21BF', + LeftUpVectorBar: '\u2958', + LeftVector: '\u21BC', + LeftVectorBar: '\u2952', + lEg: '\u2A8B', + leg: '\u22DA', + leq: '\u2264', + leqq: '\u2266', + leqslant: '\u2A7D', + les: '\u2A7D', + lescc: '\u2AA8', + lesdot: '\u2A7F', + lesdoto: '\u2A81', + lesdotor: '\u2A83', + lesg: '\u22DA\uFE00', + lesges: '\u2A93', + lessapprox: '\u2A85', + lessdot: '\u22D6', + lesseqgtr: '\u22DA', + lesseqqgtr: '\u2A8B', + LessEqualGreater: '\u22DA', + LessFullEqual: '\u2266', + LessGreater: '\u2276', + lessgtr: '\u2276', + LessLess: '\u2AA1', + lesssim: '\u2272', + LessSlantEqual: '\u2A7D', + LessTilde: '\u2272', + lfisht: '\u297C', + lfloor: '\u230A', + Lfr: '\uD835\uDD0F', + lfr: '\uD835\uDD29', + lg: '\u2276', + lgE: '\u2A91', + lHar: '\u2962', + lhard: '\u21BD', + lharu: '\u21BC', + lharul: '\u296A', + lhblk: '\u2584', + LJcy: '\u0409', + ljcy: '\u0459', + Ll: '\u22D8', + ll: '\u226A', + llarr: '\u21C7', + llcorner: '\u231E', + Lleftarrow: '\u21DA', + llhard: '\u296B', + lltri: '\u25FA', + Lmidot: '\u013F', + lmidot: '\u0140', + lmoust: '\u23B0', + lmoustache: '\u23B0', + lnap: '\u2A89', + lnapprox: '\u2A89', + lnE: '\u2268', + lne: '\u2A87', + lneq: '\u2A87', + lneqq: '\u2268', + lnsim: '\u22E6', + loang: '\u27EC', + loarr: '\u21FD', + lobrk: '\u27E6', + LongLeftArrow: '\u27F5', + Longleftarrow: '\u27F8', + longleftarrow: '\u27F5', + LongLeftRightArrow: '\u27F7', + Longleftrightarrow: '\u27FA', + longleftrightarrow: '\u27F7', + longmapsto: '\u27FC', + LongRightArrow: '\u27F6', + Longrightarrow: '\u27F9', + longrightarrow: '\u27F6', + looparrowleft: '\u21AB', + looparrowright: '\u21AC', + lopar: '\u2985', + Lopf: '\uD835\uDD43', + lopf: '\uD835\uDD5D', + loplus: '\u2A2D', + lotimes: '\u2A34', + lowast: '\u2217', + lowbar: '\u005F', + LowerLeftArrow: '\u2199', + LowerRightArrow: '\u2198', + loz: '\u25CA', + lozenge: '\u25CA', + lozf: '\u29EB', + lpar: '\u0028', + lparlt: '\u2993', + lrarr: '\u21C6', + lrcorner: '\u231F', + lrhar: '\u21CB', + lrhard: '\u296D', + lrm: '\u200E', + lrtri: '\u22BF', + lsaquo: '\u2039', + Lscr: '\u2112', + lscr: '\uD835\uDCC1', + Lsh: '\u21B0', + lsh: '\u21B0', + lsim: '\u2272', + lsime: '\u2A8D', + lsimg: '\u2A8F', + lsqb: '\u005B', + lsquo: '\u2018', + lsquor: '\u201A', + Lstrok: '\u0141', + lstrok: '\u0142', + Lt: '\u226A', + LT: '\u003C', + lt: '\u003C', + ltcc: '\u2AA6', + ltcir: '\u2A79', + ltdot: '\u22D6', + lthree: '\u22CB', + ltimes: '\u22C9', + ltlarr: '\u2976', + ltquest: '\u2A7B', + ltri: '\u25C3', + ltrie: '\u22B4', + ltrif: '\u25C2', + ltrPar: '\u2996', + lurdshar: '\u294A', + luruhar: '\u2966', + lvertneqq: '\u2268\uFE00', + lvnE: '\u2268\uFE00', + macr: '\u00AF', + male: '\u2642', + malt: '\u2720', + maltese: '\u2720', + Map: '\u2905', + map: '\u21A6', + mapsto: '\u21A6', + mapstodown: '\u21A7', + mapstoleft: '\u21A4', + mapstoup: '\u21A5', + marker: '\u25AE', + mcomma: '\u2A29', + Mcy: '\u041C', + mcy: '\u043C', + mdash: '\u2014', + mDDot: '\u223A', + measuredangle: '\u2221', + MediumSpace: '\u205F', + Mellintrf: '\u2133', + Mfr: '\uD835\uDD10', + mfr: '\uD835\uDD2A', + mho: '\u2127', + micro: '\u00B5', + mid: '\u2223', + midast: '\u002A', + midcir: '\u2AF0', + middot: '\u00B7', + minus: '\u2212', + minusb: '\u229F', + minusd: '\u2238', + minusdu: '\u2A2A', + MinusPlus: '\u2213', + mlcp: '\u2ADB', + mldr: '\u2026', + mnplus: '\u2213', + models: '\u22A7', + Mopf: '\uD835\uDD44', + mopf: '\uD835\uDD5E', + mp: '\u2213', + Mscr: '\u2133', + mscr: '\uD835\uDCC2', + mstpos: '\u223E', + Mu: '\u039C', + mu: '\u03BC', + multimap: '\u22B8', + mumap: '\u22B8', + nabla: '\u2207', + Nacute: '\u0143', + nacute: '\u0144', + nang: '\u2220\u20D2', + nap: '\u2249', + napE: '\u2A70\u0338', + napid: '\u224B\u0338', + napos: '\u0149', + napprox: '\u2249', + natur: '\u266E', + natural: '\u266E', + naturals: '\u2115', + nbsp: '\u00A0', + nbump: '\u224E\u0338', + nbumpe: '\u224F\u0338', + ncap: '\u2A43', + Ncaron: '\u0147', + ncaron: '\u0148', + Ncedil: '\u0145', + ncedil: '\u0146', + ncong: '\u2247', + ncongdot: '\u2A6D\u0338', + ncup: '\u2A42', + Ncy: '\u041D', + ncy: '\u043D', + ndash: '\u2013', + ne: '\u2260', + nearhk: '\u2924', + neArr: '\u21D7', + nearr: '\u2197', + nearrow: '\u2197', + nedot: '\u2250\u0338', + NegativeMediumSpace: '\u200B', + NegativeThickSpace: '\u200B', + NegativeThinSpace: '\u200B', + NegativeVeryThinSpace: '\u200B', + nequiv: '\u2262', + nesear: '\u2928', + nesim: '\u2242\u0338', + NestedGreaterGreater: '\u226B', + NestedLessLess: '\u226A', + NewLine: '\u000A', + nexist: '\u2204', + nexists: '\u2204', + Nfr: '\uD835\uDD11', + nfr: '\uD835\uDD2B', + ngE: '\u2267\u0338', + nge: '\u2271', + ngeq: '\u2271', + ngeqq: '\u2267\u0338', + ngeqslant: '\u2A7E\u0338', + nges: '\u2A7E\u0338', + nGg: '\u22D9\u0338', + ngsim: '\u2275', + nGt: '\u226B\u20D2', + ngt: '\u226F', + ngtr: '\u226F', + nGtv: '\u226B\u0338', + nhArr: '\u21CE', + nharr: '\u21AE', + nhpar: '\u2AF2', + ni: '\u220B', + nis: '\u22FC', + nisd: '\u22FA', + niv: '\u220B', + NJcy: '\u040A', + njcy: '\u045A', + nlArr: '\u21CD', + nlarr: '\u219A', + nldr: '\u2025', + nlE: '\u2266\u0338', + nle: '\u2270', + nLeftarrow: '\u21CD', + nleftarrow: '\u219A', + nLeftrightarrow: '\u21CE', + nleftrightarrow: '\u21AE', + nleq: '\u2270', + nleqq: '\u2266\u0338', + nleqslant: '\u2A7D\u0338', + nles: '\u2A7D\u0338', + nless: '\u226E', + nLl: '\u22D8\u0338', + nlsim: '\u2274', + nLt: '\u226A\u20D2', + nlt: '\u226E', + nltri: '\u22EA', + nltrie: '\u22EC', + nLtv: '\u226A\u0338', + nmid: '\u2224', + NoBreak: '\u2060', + NonBreakingSpace: '\u00A0', + Nopf: '\u2115', + nopf: '\uD835\uDD5F', + Not: '\u2AEC', + not: '\u00AC', + NotCongruent: '\u2262', + NotCupCap: '\u226D', + NotDoubleVerticalBar: '\u2226', + NotElement: '\u2209', + NotEqual: '\u2260', + NotEqualTilde: '\u2242\u0338', + NotExists: '\u2204', + NotGreater: '\u226F', + NotGreaterEqual: '\u2271', + NotGreaterFullEqual: '\u2267\u0338', + NotGreaterGreater: '\u226B\u0338', + NotGreaterLess: '\u2279', + NotGreaterSlantEqual: '\u2A7E\u0338', + NotGreaterTilde: '\u2275', + NotHumpDownHump: '\u224E\u0338', + NotHumpEqual: '\u224F\u0338', + notin: '\u2209', + notindot: '\u22F5\u0338', + notinE: '\u22F9\u0338', + notinva: '\u2209', + notinvb: '\u22F7', + notinvc: '\u22F6', + NotLeftTriangle: '\u22EA', + NotLeftTriangleBar: '\u29CF\u0338', + NotLeftTriangleEqual: '\u22EC', + NotLess: '\u226E', + NotLessEqual: '\u2270', + NotLessGreater: '\u2278', + NotLessLess: '\u226A\u0338', + NotLessSlantEqual: '\u2A7D\u0338', + NotLessTilde: '\u2274', + NotNestedGreaterGreater: '\u2AA2\u0338', + NotNestedLessLess: '\u2AA1\u0338', + notni: '\u220C', + notniva: '\u220C', + notnivb: '\u22FE', + notnivc: '\u22FD', + NotPrecedes: '\u2280', + NotPrecedesEqual: '\u2AAF\u0338', + NotPrecedesSlantEqual: '\u22E0', + NotReverseElement: '\u220C', + NotRightTriangle: '\u22EB', + NotRightTriangleBar: '\u29D0\u0338', + NotRightTriangleEqual: '\u22ED', + NotSquareSubset: '\u228F\u0338', + NotSquareSubsetEqual: '\u22E2', + NotSquareSuperset: '\u2290\u0338', + NotSquareSupersetEqual: '\u22E3', + NotSubset: '\u2282\u20D2', + NotSubsetEqual: '\u2288', + NotSucceeds: '\u2281', + NotSucceedsEqual: '\u2AB0\u0338', + NotSucceedsSlantEqual: '\u22E1', + NotSucceedsTilde: '\u227F\u0338', + NotSuperset: '\u2283\u20D2', + NotSupersetEqual: '\u2289', + NotTilde: '\u2241', + NotTildeEqual: '\u2244', + NotTildeFullEqual: '\u2247', + NotTildeTilde: '\u2249', + NotVerticalBar: '\u2224', + npar: '\u2226', + nparallel: '\u2226', + nparsl: '\u2AFD\u20E5', + npart: '\u2202\u0338', + npolint: '\u2A14', + npr: '\u2280', + nprcue: '\u22E0', + npre: '\u2AAF\u0338', + nprec: '\u2280', + npreceq: '\u2AAF\u0338', + nrArr: '\u21CF', + nrarr: '\u219B', + nrarrc: '\u2933\u0338', + nrarrw: '\u219D\u0338', + nRightarrow: '\u21CF', + nrightarrow: '\u219B', + nrtri: '\u22EB', + nrtrie: '\u22ED', + nsc: '\u2281', + nsccue: '\u22E1', + nsce: '\u2AB0\u0338', + Nscr: '\uD835\uDCA9', + nscr: '\uD835\uDCC3', + nshortmid: '\u2224', + nshortparallel: '\u2226', + nsim: '\u2241', + nsime: '\u2244', + nsimeq: '\u2244', + nsmid: '\u2224', + nspar: '\u2226', + nsqsube: '\u22E2', + nsqsupe: '\u22E3', + nsub: '\u2284', + nsubE: '\u2AC5\u0338', + nsube: '\u2288', + nsubset: '\u2282\u20D2', + nsubseteq: '\u2288', + nsubseteqq: '\u2AC5\u0338', + nsucc: '\u2281', + nsucceq: '\u2AB0\u0338', + nsup: '\u2285', + nsupE: '\u2AC6\u0338', + nsupe: '\u2289', + nsupset: '\u2283\u20D2', + nsupseteq: '\u2289', + nsupseteqq: '\u2AC6\u0338', + ntgl: '\u2279', + Ntilde: '\u00D1', + ntilde: '\u00F1', + ntlg: '\u2278', + ntriangleleft: '\u22EA', + ntrianglelefteq: '\u22EC', + ntriangleright: '\u22EB', + ntrianglerighteq: '\u22ED', + Nu: '\u039D', + nu: '\u03BD', + num: '\u0023', + numero: '\u2116', + numsp: '\u2007', + nvap: '\u224D\u20D2', + nVDash: '\u22AF', + nVdash: '\u22AE', + nvDash: '\u22AD', + nvdash: '\u22AC', + nvge: '\u2265\u20D2', + nvgt: '\u003E\u20D2', + nvHarr: '\u2904', + nvinfin: '\u29DE', + nvlArr: '\u2902', + nvle: '\u2264\u20D2', + nvlt: '\u003C\u20D2', + nvltrie: '\u22B4\u20D2', + nvrArr: '\u2903', + nvrtrie: '\u22B5\u20D2', + nvsim: '\u223C\u20D2', + nwarhk: '\u2923', + nwArr: '\u21D6', + nwarr: '\u2196', + nwarrow: '\u2196', + nwnear: '\u2927', + Oacute: '\u00D3', + oacute: '\u00F3', + oast: '\u229B', + ocir: '\u229A', + Ocirc: '\u00D4', + ocirc: '\u00F4', + Ocy: '\u041E', + ocy: '\u043E', + odash: '\u229D', + Odblac: '\u0150', + odblac: '\u0151', + odiv: '\u2A38', + odot: '\u2299', + odsold: '\u29BC', + OElig: '\u0152', + oelig: '\u0153', + ofcir: '\u29BF', + Ofr: '\uD835\uDD12', + ofr: '\uD835\uDD2C', + ogon: '\u02DB', + Ograve: '\u00D2', + ograve: '\u00F2', + ogt: '\u29C1', + ohbar: '\u29B5', + ohm: '\u03A9', + oint: '\u222E', + olarr: '\u21BA', + olcir: '\u29BE', + olcross: '\u29BB', + oline: '\u203E', + olt: '\u29C0', + Omacr: '\u014C', + omacr: '\u014D', + Omega: '\u03A9', + omega: '\u03C9', + Omicron: '\u039F', + omicron: '\u03BF', + omid: '\u29B6', + ominus: '\u2296', + Oopf: '\uD835\uDD46', + oopf: '\uD835\uDD60', + opar: '\u29B7', + OpenCurlyDoubleQuote: '\u201C', + OpenCurlyQuote: '\u2018', + operp: '\u29B9', + oplus: '\u2295', + Or: '\u2A54', + or: '\u2228', + orarr: '\u21BB', + ord: '\u2A5D', + order: '\u2134', + orderof: '\u2134', + ordf: '\u00AA', + ordm: '\u00BA', + origof: '\u22B6', + oror: '\u2A56', + orslope: '\u2A57', + orv: '\u2A5B', + oS: '\u24C8', + Oscr: '\uD835\uDCAA', + oscr: '\u2134', + Oslash: '\u00D8', + oslash: '\u00F8', + osol: '\u2298', + Otilde: '\u00D5', + otilde: '\u00F5', + Otimes: '\u2A37', + otimes: '\u2297', + otimesas: '\u2A36', + Ouml: '\u00D6', + ouml: '\u00F6', + ovbar: '\u233D', + OverBar: '\u203E', + OverBrace: '\u23DE', + OverBracket: '\u23B4', + OverParenthesis: '\u23DC', + par: '\u2225', + para: '\u00B6', + parallel: '\u2225', + parsim: '\u2AF3', + parsl: '\u2AFD', + part: '\u2202', + PartialD: '\u2202', + Pcy: '\u041F', + pcy: '\u043F', + percnt: '\u0025', + period: '\u002E', + permil: '\u2030', + perp: '\u22A5', + pertenk: '\u2031', + Pfr: '\uD835\uDD13', + pfr: '\uD835\uDD2D', + Phi: '\u03A6', + phi: '\u03C6', + phiv: '\u03D5', + phmmat: '\u2133', + phone: '\u260E', + Pi: '\u03A0', + pi: '\u03C0', + pitchfork: '\u22D4', + piv: '\u03D6', + planck: '\u210F', + planckh: '\u210E', + plankv: '\u210F', + plus: '\u002B', + plusacir: '\u2A23', + plusb: '\u229E', + pluscir: '\u2A22', + plusdo: '\u2214', + plusdu: '\u2A25', + pluse: '\u2A72', + PlusMinus: '\u00B1', + plusmn: '\u00B1', + plussim: '\u2A26', + plustwo: '\u2A27', + pm: '\u00B1', + Poincareplane: '\u210C', + pointint: '\u2A15', + Popf: '\u2119', + popf: '\uD835\uDD61', + pound: '\u00A3', + Pr: '\u2ABB', + pr: '\u227A', + prap: '\u2AB7', + prcue: '\u227C', + prE: '\u2AB3', + pre: '\u2AAF', + prec: '\u227A', + precapprox: '\u2AB7', + preccurlyeq: '\u227C', + Precedes: '\u227A', + PrecedesEqual: '\u2AAF', + PrecedesSlantEqual: '\u227C', + PrecedesTilde: '\u227E', + preceq: '\u2AAF', + precnapprox: '\u2AB9', + precneqq: '\u2AB5', + precnsim: '\u22E8', + precsim: '\u227E', + Prime: '\u2033', + prime: '\u2032', + primes: '\u2119', + prnap: '\u2AB9', + prnE: '\u2AB5', + prnsim: '\u22E8', + prod: '\u220F', + Product: '\u220F', + profalar: '\u232E', + profline: '\u2312', + profsurf: '\u2313', + prop: '\u221D', + Proportion: '\u2237', + Proportional: '\u221D', + propto: '\u221D', + prsim: '\u227E', + prurel: '\u22B0', + Pscr: '\uD835\uDCAB', + pscr: '\uD835\uDCC5', + Psi: '\u03A8', + psi: '\u03C8', + puncsp: '\u2008', + Qfr: '\uD835\uDD14', + qfr: '\uD835\uDD2E', + qint: '\u2A0C', + Qopf: '\u211A', + qopf: '\uD835\uDD62', + qprime: '\u2057', + Qscr: '\uD835\uDCAC', + qscr: '\uD835\uDCC6', + quaternions: '\u210D', + quatint: '\u2A16', + quest: '\u003F', + questeq: '\u225F', + QUOT: '\u0022', + quot: '\u0022', + rAarr: '\u21DB', + race: '\u223D\u0331', + Racute: '\u0154', + racute: '\u0155', + radic: '\u221A', + raemptyv: '\u29B3', + Rang: '\u27EB', + rang: '\u27E9', + rangd: '\u2992', + range: '\u29A5', + rangle: '\u27E9', + raquo: '\u00BB', + Rarr: '\u21A0', + rArr: '\u21D2', + rarr: '\u2192', + rarrap: '\u2975', + rarrb: '\u21E5', + rarrbfs: '\u2920', + rarrc: '\u2933', + rarrfs: '\u291E', + rarrhk: '\u21AA', + rarrlp: '\u21AC', + rarrpl: '\u2945', + rarrsim: '\u2974', + Rarrtl: '\u2916', + rarrtl: '\u21A3', + rarrw: '\u219D', + rAtail: '\u291C', + ratail: '\u291A', + ratio: '\u2236', + rationals: '\u211A', + RBarr: '\u2910', + rBarr: '\u290F', + rbarr: '\u290D', + rbbrk: '\u2773', + rbrace: '\u007D', + rbrack: '\u005D', + rbrke: '\u298C', + rbrksld: '\u298E', + rbrkslu: '\u2990', + Rcaron: '\u0158', + rcaron: '\u0159', + Rcedil: '\u0156', + rcedil: '\u0157', + rceil: '\u2309', + rcub: '\u007D', + Rcy: '\u0420', + rcy: '\u0440', + rdca: '\u2937', + rdldhar: '\u2969', + rdquo: '\u201D', + rdquor: '\u201D', + rdsh: '\u21B3', + Re: '\u211C', + real: '\u211C', + realine: '\u211B', + realpart: '\u211C', + reals: '\u211D', + rect: '\u25AD', + REG: '\u00AE', + reg: '\u00AE', + ReverseElement: '\u220B', + ReverseEquilibrium: '\u21CB', + ReverseUpEquilibrium: '\u296F', + rfisht: '\u297D', + rfloor: '\u230B', + Rfr: '\u211C', + rfr: '\uD835\uDD2F', + rHar: '\u2964', + rhard: '\u21C1', + rharu: '\u21C0', + rharul: '\u296C', + Rho: '\u03A1', + rho: '\u03C1', + rhov: '\u03F1', + RightAngleBracket: '\u27E9', + RightArrow: '\u2192', + Rightarrow: '\u21D2', + rightarrow: '\u2192', + RightArrowBar: '\u21E5', + RightArrowLeftArrow: '\u21C4', + rightarrowtail: '\u21A3', + RightCeiling: '\u2309', + RightDoubleBracket: '\u27E7', + RightDownTeeVector: '\u295D', + RightDownVector: '\u21C2', + RightDownVectorBar: '\u2955', + RightFloor: '\u230B', + rightharpoondown: '\u21C1', + rightharpoonup: '\u21C0', + rightleftarrows: '\u21C4', + rightleftharpoons: '\u21CC', + rightrightarrows: '\u21C9', + rightsquigarrow: '\u219D', + RightTee: '\u22A2', + RightTeeArrow: '\u21A6', + RightTeeVector: '\u295B', + rightthreetimes: '\u22CC', + RightTriangle: '\u22B3', + RightTriangleBar: '\u29D0', + RightTriangleEqual: '\u22B5', + RightUpDownVector: '\u294F', + RightUpTeeVector: '\u295C', + RightUpVector: '\u21BE', + RightUpVectorBar: '\u2954', + RightVector: '\u21C0', + RightVectorBar: '\u2953', + ring: '\u02DA', + risingdotseq: '\u2253', + rlarr: '\u21C4', + rlhar: '\u21CC', + rlm: '\u200F', + rmoust: '\u23B1', + rmoustache: '\u23B1', + rnmid: '\u2AEE', + roang: '\u27ED', + roarr: '\u21FE', + robrk: '\u27E7', + ropar: '\u2986', + Ropf: '\u211D', + ropf: '\uD835\uDD63', + roplus: '\u2A2E', + rotimes: '\u2A35', + RoundImplies: '\u2970', + rpar: '\u0029', + rpargt: '\u2994', + rppolint: '\u2A12', + rrarr: '\u21C9', + Rrightarrow: '\u21DB', + rsaquo: '\u203A', + Rscr: '\u211B', + rscr: '\uD835\uDCC7', + Rsh: '\u21B1', + rsh: '\u21B1', + rsqb: '\u005D', + rsquo: '\u2019', + rsquor: '\u2019', + rthree: '\u22CC', + rtimes: '\u22CA', + rtri: '\u25B9', + rtrie: '\u22B5', + rtrif: '\u25B8', + rtriltri: '\u29CE', + RuleDelayed: '\u29F4', + ruluhar: '\u2968', + rx: '\u211E', + Sacute: '\u015A', + sacute: '\u015B', + sbquo: '\u201A', + Sc: '\u2ABC', + sc: '\u227B', + scap: '\u2AB8', + Scaron: '\u0160', + scaron: '\u0161', + sccue: '\u227D', + scE: '\u2AB4', + sce: '\u2AB0', + Scedil: '\u015E', + scedil: '\u015F', + Scirc: '\u015C', + scirc: '\u015D', + scnap: '\u2ABA', + scnE: '\u2AB6', + scnsim: '\u22E9', + scpolint: '\u2A13', + scsim: '\u227F', + Scy: '\u0421', + scy: '\u0441', + sdot: '\u22C5', + sdotb: '\u22A1', + sdote: '\u2A66', + searhk: '\u2925', + seArr: '\u21D8', + searr: '\u2198', + searrow: '\u2198', + sect: '\u00A7', + semi: '\u003B', + seswar: '\u2929', + setminus: '\u2216', + setmn: '\u2216', + sext: '\u2736', + Sfr: '\uD835\uDD16', + sfr: '\uD835\uDD30', + sfrown: '\u2322', + sharp: '\u266F', + SHCHcy: '\u0429', + shchcy: '\u0449', + SHcy: '\u0428', + shcy: '\u0448', + ShortDownArrow: '\u2193', + ShortLeftArrow: '\u2190', + shortmid: '\u2223', + shortparallel: '\u2225', + ShortRightArrow: '\u2192', + ShortUpArrow: '\u2191', + shy: '\u00AD', + Sigma: '\u03A3', + sigma: '\u03C3', + sigmaf: '\u03C2', + sigmav: '\u03C2', + sim: '\u223C', + simdot: '\u2A6A', + sime: '\u2243', + simeq: '\u2243', + simg: '\u2A9E', + simgE: '\u2AA0', + siml: '\u2A9D', + simlE: '\u2A9F', + simne: '\u2246', + simplus: '\u2A24', + simrarr: '\u2972', + slarr: '\u2190', + SmallCircle: '\u2218', + smallsetminus: '\u2216', + smashp: '\u2A33', + smeparsl: '\u29E4', + smid: '\u2223', + smile: '\u2323', + smt: '\u2AAA', + smte: '\u2AAC', + smtes: '\u2AAC\uFE00', + SOFTcy: '\u042C', + softcy: '\u044C', + sol: '\u002F', + solb: '\u29C4', + solbar: '\u233F', + Sopf: '\uD835\uDD4A', + sopf: '\uD835\uDD64', + spades: '\u2660', + spadesuit: '\u2660', + spar: '\u2225', + sqcap: '\u2293', + sqcaps: '\u2293\uFE00', + sqcup: '\u2294', + sqcups: '\u2294\uFE00', + Sqrt: '\u221A', + sqsub: '\u228F', + sqsube: '\u2291', + sqsubset: '\u228F', + sqsubseteq: '\u2291', + sqsup: '\u2290', + sqsupe: '\u2292', + sqsupset: '\u2290', + sqsupseteq: '\u2292', + squ: '\u25A1', + Square: '\u25A1', + square: '\u25A1', + SquareIntersection: '\u2293', + SquareSubset: '\u228F', + SquareSubsetEqual: '\u2291', + SquareSuperset: '\u2290', + SquareSupersetEqual: '\u2292', + SquareUnion: '\u2294', + squarf: '\u25AA', + squf: '\u25AA', + srarr: '\u2192', + Sscr: '\uD835\uDCAE', + sscr: '\uD835\uDCC8', + ssetmn: '\u2216', + ssmile: '\u2323', + sstarf: '\u22C6', + Star: '\u22C6', + star: '\u2606', + starf: '\u2605', + straightepsilon: '\u03F5', + straightphi: '\u03D5', + strns: '\u00AF', + Sub: '\u22D0', + sub: '\u2282', + subdot: '\u2ABD', + subE: '\u2AC5', + sube: '\u2286', + subedot: '\u2AC3', + submult: '\u2AC1', + subnE: '\u2ACB', + subne: '\u228A', + subplus: '\u2ABF', + subrarr: '\u2979', + Subset: '\u22D0', + subset: '\u2282', + subseteq: '\u2286', + subseteqq: '\u2AC5', + SubsetEqual: '\u2286', + subsetneq: '\u228A', + subsetneqq: '\u2ACB', + subsim: '\u2AC7', + subsub: '\u2AD5', + subsup: '\u2AD3', + succ: '\u227B', + succapprox: '\u2AB8', + succcurlyeq: '\u227D', + Succeeds: '\u227B', + SucceedsEqual: '\u2AB0', + SucceedsSlantEqual: '\u227D', + SucceedsTilde: '\u227F', + succeq: '\u2AB0', + succnapprox: '\u2ABA', + succneqq: '\u2AB6', + succnsim: '\u22E9', + succsim: '\u227F', + SuchThat: '\u220B', + Sum: '\u2211', + sum: '\u2211', + sung: '\u266A', + Sup: '\u22D1', + sup: '\u2283', + sup1: '\u00B9', + sup2: '\u00B2', + sup3: '\u00B3', + supdot: '\u2ABE', + supdsub: '\u2AD8', + supE: '\u2AC6', + supe: '\u2287', + supedot: '\u2AC4', + Superset: '\u2283', + SupersetEqual: '\u2287', + suphsol: '\u27C9', + suphsub: '\u2AD7', + suplarr: '\u297B', + supmult: '\u2AC2', + supnE: '\u2ACC', + supne: '\u228B', + supplus: '\u2AC0', + Supset: '\u22D1', + supset: '\u2283', + supseteq: '\u2287', + supseteqq: '\u2AC6', + supsetneq: '\u228B', + supsetneqq: '\u2ACC', + supsim: '\u2AC8', + supsub: '\u2AD4', + supsup: '\u2AD6', + swarhk: '\u2926', + swArr: '\u21D9', + swarr: '\u2199', + swarrow: '\u2199', + swnwar: '\u292A', + szlig: '\u00DF', + Tab: '\u0009', + target: '\u2316', + Tau: '\u03A4', + tau: '\u03C4', + tbrk: '\u23B4', + Tcaron: '\u0164', + tcaron: '\u0165', + Tcedil: '\u0162', + tcedil: '\u0163', + Tcy: '\u0422', + tcy: '\u0442', + tdot: '\u20DB', + telrec: '\u2315', + Tfr: '\uD835\uDD17', + tfr: '\uD835\uDD31', + there4: '\u2234', + Therefore: '\u2234', + therefore: '\u2234', + Theta: '\u0398', + theta: '\u03B8', + thetasym: '\u03D1', + thetav: '\u03D1', + thickapprox: '\u2248', + thicksim: '\u223C', + ThickSpace: '\u205F\u200A', + thinsp: '\u2009', + ThinSpace: '\u2009', + thkap: '\u2248', + thksim: '\u223C', + THORN: '\u00DE', + thorn: '\u00FE', + Tilde: '\u223C', + tilde: '\u02DC', + TildeEqual: '\u2243', + TildeFullEqual: '\u2245', + TildeTilde: '\u2248', + times: '\u00D7', + timesb: '\u22A0', + timesbar: '\u2A31', + timesd: '\u2A30', + tint: '\u222D', + toea: '\u2928', + top: '\u22A4', + topbot: '\u2336', + topcir: '\u2AF1', + Topf: '\uD835\uDD4B', + topf: '\uD835\uDD65', + topfork: '\u2ADA', + tosa: '\u2929', + tprime: '\u2034', + TRADE: '\u2122', + trade: '\u2122', + triangle: '\u25B5', + triangledown: '\u25BF', + triangleleft: '\u25C3', + trianglelefteq: '\u22B4', + triangleq: '\u225C', + triangleright: '\u25B9', + trianglerighteq: '\u22B5', + tridot: '\u25EC', + trie: '\u225C', + triminus: '\u2A3A', + TripleDot: '\u20DB', + triplus: '\u2A39', + trisb: '\u29CD', + tritime: '\u2A3B', + trpezium: '\u23E2', + Tscr: '\uD835\uDCAF', + tscr: '\uD835\uDCC9', + TScy: '\u0426', + tscy: '\u0446', + TSHcy: '\u040B', + tshcy: '\u045B', + Tstrok: '\u0166', + tstrok: '\u0167', + twixt: '\u226C', + twoheadleftarrow: '\u219E', + twoheadrightarrow: '\u21A0', + Uacute: '\u00DA', + uacute: '\u00FA', + Uarr: '\u219F', + uArr: '\u21D1', + uarr: '\u2191', + Uarrocir: '\u2949', + Ubrcy: '\u040E', + ubrcy: '\u045E', + Ubreve: '\u016C', + ubreve: '\u016D', + Ucirc: '\u00DB', + ucirc: '\u00FB', + Ucy: '\u0423', + ucy: '\u0443', + udarr: '\u21C5', + Udblac: '\u0170', + udblac: '\u0171', + udhar: '\u296E', + ufisht: '\u297E', + Ufr: '\uD835\uDD18', + ufr: '\uD835\uDD32', + Ugrave: '\u00D9', + ugrave: '\u00F9', + uHar: '\u2963', + uharl: '\u21BF', + uharr: '\u21BE', + uhblk: '\u2580', + ulcorn: '\u231C', + ulcorner: '\u231C', + ulcrop: '\u230F', + ultri: '\u25F8', + Umacr: '\u016A', + umacr: '\u016B', + uml: '\u00A8', + UnderBar: '\u005F', + UnderBrace: '\u23DF', + UnderBracket: '\u23B5', + UnderParenthesis: '\u23DD', + Union: '\u22C3', + UnionPlus: '\u228E', + Uogon: '\u0172', + uogon: '\u0173', + Uopf: '\uD835\uDD4C', + uopf: '\uD835\uDD66', + UpArrow: '\u2191', + Uparrow: '\u21D1', + uparrow: '\u2191', + UpArrowBar: '\u2912', + UpArrowDownArrow: '\u21C5', + UpDownArrow: '\u2195', + Updownarrow: '\u21D5', + updownarrow: '\u2195', + UpEquilibrium: '\u296E', + upharpoonleft: '\u21BF', + upharpoonright: '\u21BE', + uplus: '\u228E', + UpperLeftArrow: '\u2196', + UpperRightArrow: '\u2197', + Upsi: '\u03D2', + upsi: '\u03C5', + upsih: '\u03D2', + Upsilon: '\u03A5', + upsilon: '\u03C5', + UpTee: '\u22A5', + UpTeeArrow: '\u21A5', + upuparrows: '\u21C8', + urcorn: '\u231D', + urcorner: '\u231D', + urcrop: '\u230E', + Uring: '\u016E', + uring: '\u016F', + urtri: '\u25F9', + Uscr: '\uD835\uDCB0', + uscr: '\uD835\uDCCA', + utdot: '\u22F0', + Utilde: '\u0168', + utilde: '\u0169', + utri: '\u25B5', + utrif: '\u25B4', + uuarr: '\u21C8', + Uuml: '\u00DC', + uuml: '\u00FC', + uwangle: '\u29A7', + vangrt: '\u299C', + varepsilon: '\u03F5', + varkappa: '\u03F0', + varnothing: '\u2205', + varphi: '\u03D5', + varpi: '\u03D6', + varpropto: '\u221D', + vArr: '\u21D5', + varr: '\u2195', + varrho: '\u03F1', + varsigma: '\u03C2', + varsubsetneq: '\u228A\uFE00', + varsubsetneqq: '\u2ACB\uFE00', + varsupsetneq: '\u228B\uFE00', + varsupsetneqq: '\u2ACC\uFE00', + vartheta: '\u03D1', + vartriangleleft: '\u22B2', + vartriangleright: '\u22B3', + Vbar: '\u2AEB', + vBar: '\u2AE8', + vBarv: '\u2AE9', + Vcy: '\u0412', + vcy: '\u0432', + VDash: '\u22AB', + Vdash: '\u22A9', + vDash: '\u22A8', + vdash: '\u22A2', + Vdashl: '\u2AE6', + Vee: '\u22C1', + vee: '\u2228', + veebar: '\u22BB', + veeeq: '\u225A', + vellip: '\u22EE', + Verbar: '\u2016', + verbar: '\u007C', + Vert: '\u2016', + vert: '\u007C', + VerticalBar: '\u2223', + VerticalLine: '\u007C', + VerticalSeparator: '\u2758', + VerticalTilde: '\u2240', + VeryThinSpace: '\u200A', + Vfr: '\uD835\uDD19', + vfr: '\uD835\uDD33', + vltri: '\u22B2', + vnsub: '\u2282\u20D2', + vnsup: '\u2283\u20D2', + Vopf: '\uD835\uDD4D', + vopf: '\uD835\uDD67', + vprop: '\u221D', + vrtri: '\u22B3', + Vscr: '\uD835\uDCB1', + vscr: '\uD835\uDCCB', + vsubnE: '\u2ACB\uFE00', + vsubne: '\u228A\uFE00', + vsupnE: '\u2ACC\uFE00', + vsupne: '\u228B\uFE00', + Vvdash: '\u22AA', + vzigzag: '\u299A', + Wcirc: '\u0174', + wcirc: '\u0175', + wedbar: '\u2A5F', + Wedge: '\u22C0', + wedge: '\u2227', + wedgeq: '\u2259', + weierp: '\u2118', + Wfr: '\uD835\uDD1A', + wfr: '\uD835\uDD34', + Wopf: '\uD835\uDD4E', + wopf: '\uD835\uDD68', + wp: '\u2118', + wr: '\u2240', + wreath: '\u2240', + Wscr: '\uD835\uDCB2', + wscr: '\uD835\uDCCC', + xcap: '\u22C2', + xcirc: '\u25EF', + xcup: '\u22C3', + xdtri: '\u25BD', + Xfr: '\uD835\uDD1B', + xfr: '\uD835\uDD35', + xhArr: '\u27FA', + xharr: '\u27F7', + Xi: '\u039E', + xi: '\u03BE', + xlArr: '\u27F8', + xlarr: '\u27F5', + xmap: '\u27FC', + xnis: '\u22FB', + xodot: '\u2A00', + Xopf: '\uD835\uDD4F', + xopf: '\uD835\uDD69', + xoplus: '\u2A01', + xotime: '\u2A02', + xrArr: '\u27F9', + xrarr: '\u27F6', + Xscr: '\uD835\uDCB3', + xscr: '\uD835\uDCCD', + xsqcup: '\u2A06', + xuplus: '\u2A04', + xutri: '\u25B3', + xvee: '\u22C1', + xwedge: '\u22C0', + Yacute: '\u00DD', + yacute: '\u00FD', + YAcy: '\u042F', + yacy: '\u044F', + Ycirc: '\u0176', + ycirc: '\u0177', + Ycy: '\u042B', + ycy: '\u044B', + yen: '\u00A5', + Yfr: '\uD835\uDD1C', + yfr: '\uD835\uDD36', + YIcy: '\u0407', + yicy: '\u0457', + Yopf: '\uD835\uDD50', + yopf: '\uD835\uDD6A', + Yscr: '\uD835\uDCB4', + yscr: '\uD835\uDCCE', + YUcy: '\u042E', + yucy: '\u044E', + Yuml: '\u0178', + yuml: '\u00FF', + Zacute: '\u0179', + zacute: '\u017A', + Zcaron: '\u017D', + zcaron: '\u017E', + Zcy: '\u0417', + zcy: '\u0437', + Zdot: '\u017B', + zdot: '\u017C', + zeetrf: '\u2128', + ZeroWidthSpace: '\u200B', + Zeta: '\u0396', + zeta: '\u03B6', + Zfr: '\u2128', + zfr: '\uD835\uDD37', + ZHcy: '\u0416', + zhcy: '\u0436', + zigrarr: '\u21DD', + Zopf: '\u2124', + zopf: '\uD835\uDD6B', + Zscr: '\uD835\uDCB5', + zscr: '\uD835\uDCCF', + zwj: '\u200D', + zwnj: '\u200C', +}); + +/** + * @deprecated use `HTML_ENTITIES` instead + * @see HTML_ENTITIES + */ +exports.entityMap = exports.HTML_ENTITIES; + +},{"./conventions":42}],46:[function(require,module,exports){ +var dom = require('./dom') +exports.DOMImplementation = dom.DOMImplementation +exports.XMLSerializer = dom.XMLSerializer +exports.DOMParser = require('./dom-parser').DOMParser + +},{"./dom":44,"./dom-parser":43}],47:[function(require,module,exports){ +var NAMESPACE = require("./conventions").NAMESPACE; + +//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] +//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] +//[5] Name ::= NameStartChar (NameChar)* +var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF +var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); +var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$'); +//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ +//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') + +//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE +//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE +var S_TAG = 0;//tag name offerring +var S_ATTR = 1;//attr name offerring +var S_ATTR_SPACE=2;//attr name end and space offer +var S_EQ = 3;//=space? +var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only) +var S_ATTR_END = 5;//attr value end and no space(quot end) +var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer) +var S_TAG_CLOSE = 7;//closed el<el /> + +/** + * Creates an error that will not be caught by XMLReader aka the SAX parser. + * + * @param {string} message + * @param {any?} locator Optional, can provide details about the location in the source + * @constructor + */ +function ParseError(message, locator) { + this.message = message + this.locator = locator + if(Error.captureStackTrace) Error.captureStackTrace(this, ParseError); +} +ParseError.prototype = new Error(); +ParseError.prototype.name = ParseError.name + +function XMLReader(){ + +} + +XMLReader.prototype = { + parse:function(source,defaultNSMap,entityMap){ + var domBuilder = this.domBuilder; + domBuilder.startDocument(); + _copy(defaultNSMap ,defaultNSMap = {}) + parse(source,defaultNSMap,entityMap, + domBuilder,this.errorHandler); + domBuilder.endDocument(); + } +} +function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){ + function fixedFromCharCode(code) { + // String.prototype.fromCharCode does not supports + // > 2 bytes unicode chars directly + if (code > 0xffff) { + code -= 0x10000; + var surrogate1 = 0xd800 + (code >> 10) + , surrogate2 = 0xdc00 + (code & 0x3ff); + + return String.fromCharCode(surrogate1, surrogate2); + } else { + return String.fromCharCode(code); + } + } + function entityReplacer(a){ + var k = a.slice(1,-1); + if (Object.hasOwnProperty.call(entityMap, k)) { + return entityMap[k]; + }else if(k.charAt(0) === '#'){ + return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x'))) + }else{ + errorHandler.error('entity not found:'+a); + return a; + } + } + function appendText(end){//has some bugs + if(end>start){ + var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer); + locator&&position(start); + domBuilder.characters(xt,0,end-start); + start = end + } + } + function position(p,m){ + while(p>=lineEnd && (m = linePattern.exec(source))){ + lineStart = m.index; + lineEnd = lineStart + m[0].length; + locator.lineNumber++; + //console.log('line++:',locator,startPos,endPos) + } + locator.columnNumber = p-lineStart+1; + } + var lineStart = 0; + var lineEnd = 0; + var linePattern = /.*(?:\r\n?|\n)|.*$/g + var locator = domBuilder.locator; + + var parseStack = [{currentNSMap:defaultNSMapCopy}] + var closeMap = {}; + var start = 0; + while(true){ + try{ + var tagStart = source.indexOf('<',start); + if(tagStart<0){ + if(!source.substr(start).match(/^\s*$/)){ + var doc = domBuilder.doc; + var text = doc.createTextNode(source.substr(start)); + doc.appendChild(text); + domBuilder.currentElement = text; + } + return; + } + if(tagStart>start){ + appendText(tagStart); + } + switch(source.charAt(tagStart+1)){ + case '/': + var end = source.indexOf('>',tagStart+3); + var tagName = source.substring(tagStart + 2, end).replace(/[ \t\n\r]+$/g, ''); + var config = parseStack.pop(); + if(end<0){ + + tagName = source.substring(tagStart+2).replace(/[\s<].*/,''); + errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName); + end = tagStart+1+tagName.length; + }else if(tagName.match(/\s</)){ + tagName = tagName.replace(/[\s<].*/,''); + errorHandler.error("end tag name: "+tagName+' maybe not complete'); + end = tagStart+1+tagName.length; + } + var localNSMap = config.localNSMap; + var endMatch = config.tagName == tagName; + var endIgnoreCaseMach = endMatch || config.tagName&&config.tagName.toLowerCase() == tagName.toLowerCase() + if(endIgnoreCaseMach){ + domBuilder.endElement(config.uri,config.localName,tagName); + if(localNSMap){ + for (var prefix in localNSMap) { + if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) { + domBuilder.endPrefixMapping(prefix); + } + } + } + if(!endMatch){ + errorHandler.fatalError("end tag name: "+tagName+' is not match the current start tagName:'+config.tagName ); // No known test case + } + }else{ + parseStack.push(config) + } + + end++; + break; + // end elment + case '?':// <?...?> + locator&&position(tagStart); + end = parseInstruction(source,tagStart,domBuilder); + break; + case '!':// <!doctype,<![CDATA,<!-- + locator&&position(tagStart); + end = parseDCC(source,tagStart,domBuilder,errorHandler); + break; + default: + locator&&position(tagStart); + var el = new ElementAttributes(); + var currentNSMap = parseStack[parseStack.length-1].currentNSMap; + //elStartEnd + var end = parseElementStartPart(source,tagStart,el,currentNSMap,entityReplacer,errorHandler); + var len = el.length; + + + if(!el.closed && fixSelfClosed(source,end,el.tagName,closeMap)){ + el.closed = true; + if(!entityMap.nbsp){ + errorHandler.warning('unclosed xml attribute'); + } + } + if(locator && len){ + var locator2 = copyLocator(locator,{}); + //try{//attribute position fixed + for(var i = 0;i<len;i++){ + var a = el[i]; + position(a.offset); + a.locator = copyLocator(locator,{}); + } + domBuilder.locator = locator2 + if(appendElement(el,domBuilder,currentNSMap)){ + parseStack.push(el) + } + domBuilder.locator = locator; + }else{ + if(appendElement(el,domBuilder,currentNSMap)){ + parseStack.push(el) + } + } + + if (NAMESPACE.isHTML(el.uri) && !el.closed) { + end = parseHtmlSpecialContent(source,end,el.tagName,entityReplacer,domBuilder) + } else { + end++; + } + } + }catch(e){ + if (e instanceof ParseError) { + throw e; + } + errorHandler.error('element parse error: '+e) + end = -1; + } + if(end>start){ + start = end; + }else{ + //TODO: 这里有可能sax回退,有位置错误风险 + appendText(Math.max(tagStart,start)+1); + } + } +} +function copyLocator(f,t){ + t.lineNumber = f.lineNumber; + t.columnNumber = f.columnNumber; + return t; +} + +/** + * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); + * @return end of the elementStartPart(end of elementEndPart for selfClosed el) + */ +function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){ + + /** + * @param {string} qname + * @param {string} value + * @param {number} startIndex + */ + function addAttribute(qname, value, startIndex) { + if (el.attributeNames.hasOwnProperty(qname)) { + errorHandler.fatalError('Attribute ' + qname + ' redefined') + } + el.addValue( + qname, + // @see https://www.w3.org/TR/xml/#AVNormalize + // since the xmldom sax parser does not "interpret" DTD the following is not implemented: + // - recursive replacement of (DTD) entity references + // - trimming and collapsing multiple spaces into a single one for attributes that are not of type CDATA + value.replace(/[\t\n\r]/g, ' ').replace(/&#?\w+;/g, entityReplacer), + startIndex + ) + } + var attrName; + var value; + var p = ++start; + var s = S_TAG;//status + while(true){ + var c = source.charAt(p); + switch(c){ + case '=': + if(s === S_ATTR){//attrName + attrName = source.slice(start,p); + s = S_EQ; + }else if(s === S_ATTR_SPACE){ + s = S_EQ; + }else{ + //fatalError: equal must after attrName or space after attrName + throw new Error('attribute equal must after attrName'); // No known test case + } + break; + case '\'': + case '"': + if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE + ){//equal + if(s === S_ATTR){ + errorHandler.warning('attribute value must after "="') + attrName = source.slice(start,p) + } + start = p+1; + p = source.indexOf(c,start) + if(p>0){ + value = source.slice(start, p); + addAttribute(attrName, value, start-1); + s = S_ATTR_END; + }else{ + //fatalError: no end quot match + throw new Error('attribute value no end \''+c+'\' match'); + } + }else if(s == S_ATTR_NOQUOT_VALUE){ + value = source.slice(start, p); + addAttribute(attrName, value, start); + errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!'); + start = p+1; + s = S_ATTR_END + }else{ + //fatalError: no equal before + throw new Error('attribute value must after "="'); // No known test case + } + break; + case '/': + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + s =S_TAG_CLOSE; + el.closed = true; + case S_ATTR_NOQUOT_VALUE: + case S_ATTR: + break; + case S_ATTR_SPACE: + el.closed = true; + break; + //case S_EQ: + default: + throw new Error("attribute invalid close char('/')") // No known test case + } + break; + case ''://end document + errorHandler.error('unexpected end of input'); + if(s == S_TAG){ + el.setTagName(source.slice(start,p)); + } + return p; + case '>': + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p)); + case S_ATTR_END: + case S_TAG_SPACE: + case S_TAG_CLOSE: + break;//normal + case S_ATTR_NOQUOT_VALUE://Compatible state + case S_ATTR: + value = source.slice(start,p); + if(value.slice(-1) === '/'){ + el.closed = true; + value = value.slice(0,-1) + } + case S_ATTR_SPACE: + if(s === S_ATTR_SPACE){ + value = attrName; + } + if(s == S_ATTR_NOQUOT_VALUE){ + errorHandler.warning('attribute "'+value+'" missed quot(")!'); + addAttribute(attrName, value, start) + }else{ + if(!NAMESPACE.isHTML(currentNSMap['']) || !value.match(/^(?:disabled|checked|selected)$/i)){ + errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!') + } + addAttribute(value, value, start) + } + break; + case S_EQ: + throw new Error('attribute value missed!!'); + } +// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) + return p; + /*xml space '\x20' | #x9 | #xD | #xA; */ + case '\u0080': + c = ' '; + default: + if(c<= ' '){//space + switch(s){ + case S_TAG: + el.setTagName(source.slice(start,p));//tagName + s = S_TAG_SPACE; + break; + case S_ATTR: + attrName = source.slice(start,p) + s = S_ATTR_SPACE; + break; + case S_ATTR_NOQUOT_VALUE: + var value = source.slice(start, p); + errorHandler.warning('attribute "'+value+'" missed quot(")!!'); + addAttribute(attrName, value, start) + case S_ATTR_END: + s = S_TAG_SPACE; + break; + //case S_TAG_SPACE: + //case S_EQ: + //case S_ATTR_SPACE: + // void();break; + //case S_TAG_CLOSE: + //ignore warning + } + }else{//not space +//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE +//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE + switch(s){ + //case S_TAG:void();break; + //case S_ATTR:void();break; + //case S_ATTR_NOQUOT_VALUE:void();break; + case S_ATTR_SPACE: + var tagName = el.tagName; + if (!NAMESPACE.isHTML(currentNSMap['']) || !attrName.match(/^(?:disabled|checked|selected)$/i)) { + errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!') + } + addAttribute(attrName, attrName, start); + start = p; + s = S_ATTR; + break; + case S_ATTR_END: + errorHandler.warning('attribute space is required"'+attrName+'"!!') + case S_TAG_SPACE: + s = S_ATTR; + start = p; + break; + case S_EQ: + s = S_ATTR_NOQUOT_VALUE; + start = p; + break; + case S_TAG_CLOSE: + throw new Error("elements closed character '/' and '>' must be connected to"); + } + } + }//end outer switch + //console.log('p++',p) + p++; + } +} +/** + * @return true if has new namespace define + */ +function appendElement(el,domBuilder,currentNSMap){ + var tagName = el.tagName; + var localNSMap = null; + //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; + var i = el.length; + while(i--){ + var a = el[i]; + var qName = a.qName; + var value = a.value; + var nsp = qName.indexOf(':'); + if(nsp>0){ + var prefix = a.prefix = qName.slice(0,nsp); + var localName = qName.slice(nsp+1); + var nsPrefix = prefix === 'xmlns' && localName + }else{ + localName = qName; + prefix = null + nsPrefix = qName === 'xmlns' && '' + } + //can not set prefix,because prefix !== '' + a.localName = localName ; + //prefix == null for no ns prefix attribute + if(nsPrefix !== false){//hack!! + if(localNSMap == null){ + localNSMap = {} + //console.log(currentNSMap,0) + _copy(currentNSMap,currentNSMap={}) + //console.log(currentNSMap,1) + } + currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; + a.uri = NAMESPACE.XMLNS + domBuilder.startPrefixMapping(nsPrefix, value) + } + } + var i = el.length; + while(i--){ + a = el[i]; + var prefix = a.prefix; + if(prefix){//no prefix attribute has no namespace + if(prefix === 'xml'){ + a.uri = NAMESPACE.XML; + }if(prefix !== 'xmlns'){ + a.uri = currentNSMap[prefix || ''] + + //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} + } + } + } + var nsp = tagName.indexOf(':'); + if(nsp>0){ + prefix = el.prefix = tagName.slice(0,nsp); + localName = el.localName = tagName.slice(nsp+1); + }else{ + prefix = null;//important!! + localName = el.localName = tagName; + } + //no prefix element has default namespace + var ns = el.uri = currentNSMap[prefix || '']; + domBuilder.startElement(ns,localName,tagName,el); + //endPrefixMapping and startPrefixMapping have not any help for dom builder + //localNSMap = null + if(el.closed){ + domBuilder.endElement(ns,localName,tagName); + if(localNSMap){ + for (prefix in localNSMap) { + if (Object.prototype.hasOwnProperty.call(localNSMap, prefix)) { + domBuilder.endPrefixMapping(prefix); + } + } + } + }else{ + el.currentNSMap = currentNSMap; + el.localNSMap = localNSMap; + //parseStack.push(el); + return true; + } +} +function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){ + if(/^(?:script|textarea)$/i.test(tagName)){ + var elEndStart = source.indexOf('</'+tagName+'>',elStartEnd); + var text = source.substring(elStartEnd+1,elEndStart); + if(/[&<]/.test(text)){ + if(/^script$/i.test(tagName)){ + //if(!/\]\]>/.test(text)){ + //lexHandler.startCDATA(); + domBuilder.characters(text,0,text.length); + //lexHandler.endCDATA(); + return elEndStart; + //} + }//}else{//text area + text = text.replace(/&#?\w+;/g,entityReplacer); + domBuilder.characters(text,0,text.length); + return elEndStart; + //} + + } + } + return elStartEnd+1; +} +function fixSelfClosed(source,elStartEnd,tagName,closeMap){ + //if(tagName in closeMap){ + var pos = closeMap[tagName]; + if(pos == null){ + //console.log(tagName) + pos = source.lastIndexOf('</'+tagName+'>') + if(pos<elStartEnd){//忘记闭合 + pos = source.lastIndexOf('</'+tagName) + } + closeMap[tagName] =pos + } + return pos<elStartEnd; + //} +} + +function _copy (source, target) { + for (var n in source) { + if (Object.prototype.hasOwnProperty.call(source, n)) { + target[n] = source[n]; + } + } +} + +function parseDCC(source,start,domBuilder,errorHandler){//sure start with '<!' + var next= source.charAt(start+2) + switch(next){ + case '-': + if(source.charAt(start + 3) === '-'){ + var end = source.indexOf('-->',start+4); + //append comment source.substring(4,end)//<!-- + if(end>start){ + domBuilder.comment(source,start+4,end-start-4); + return end+3; + }else{ + errorHandler.error("Unclosed comment"); + return -1; + } + }else{ + //error + return -1; + } + default: + if(source.substr(start+3,6) == 'CDATA['){ + var end = source.indexOf(']]>',start+9); + domBuilder.startCDATA(); + domBuilder.characters(source,start+9,end-start-9); + domBuilder.endCDATA() + return end+3; + } + //<!DOCTYPE + //startDTD(java.lang.String name, java.lang.String publicId, java.lang.String systemId) + var matchs = split(source,start); + var len = matchs.length; + if(len>1 && /!doctype/i.test(matchs[0][0])){ + var name = matchs[1][0]; + var pubid = false; + var sysid = false; + if(len>3){ + if(/^public$/i.test(matchs[2][0])){ + pubid = matchs[3][0]; + sysid = len>4 && matchs[4][0]; + }else if(/^system$/i.test(matchs[2][0])){ + sysid = matchs[3][0]; + } + } + var lastMatch = matchs[len-1] + domBuilder.startDTD(name, pubid, sysid); + domBuilder.endDTD(); + + return lastMatch.index+lastMatch[0].length + } + } + return -1; +} + + + +function parseInstruction(source,start,domBuilder){ + var end = source.indexOf('?>',start); + if(end){ + var match = source.substring(start,end).match(/^<\?(\S*)\s*([\s\S]*?)\s*$/); + if(match){ + var len = match[0].length; + domBuilder.processingInstruction(match[1], match[2]) ; + return end+2; + }else{//error + return -1; + } + } + return -1; +} + +function ElementAttributes(){ + this.attributeNames = {} +} +ElementAttributes.prototype = { + setTagName:function(tagName){ + if(!tagNamePattern.test(tagName)){ + throw new Error('invalid tagName:'+tagName) + } + this.tagName = tagName + }, + addValue:function(qName, value, offset) { + if(!tagNamePattern.test(qName)){ + throw new Error('invalid attribute:'+qName) + } + this.attributeNames[qName] = this.length; + this[this.length++] = {qName:qName,value:value,offset:offset} + }, + length:0, + getLocalName:function(i){return this[i].localName}, + getLocator:function(i){return this[i].locator}, + getQName:function(i){return this[i].qName}, + getURI:function(i){return this[i].uri}, + getValue:function(i){return this[i].value} +// ,getIndex:function(uri, localName)){ +// if(localName){ +// +// }else{ +// var qName = uri +// } +// }, +// getValue:function(){return this.getValue(this.getIndex.apply(this,arguments))}, +// getType:function(uri,localName){} +// getType:function(i){}, +} + + + +function split(source,start){ + var match; + var buf = []; + var reg = /'[^']+'|"[^"]+"|[^\s<>\/=]+=?|(\/?\s*>|<)/g; + reg.lastIndex = start; + reg.exec(source);//skip < + while(match = reg.exec(source)){ + buf.push(match); + if(match[1])return buf; + } +} + +exports.XMLReader = XMLReader; +exports.ParseError = ParseError; + +},{"./conventions":42}],48:[function(require,module,exports){ +'use strict' + +exports.byteLength = byteLength +exports.toByteArray = toByteArray +exports.fromByteArray = fromByteArray + +var lookup = [] +var revLookup = [] +var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array + +var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +for (var i = 0, len = code.length; i < len; ++i) { + lookup[i] = code[i] + revLookup[code.charCodeAt(i)] = i +} + +// Support decoding URL-safe base64 strings, as Node.js does. +// See: https://en.wikipedia.org/wiki/Base64#URL_applications +revLookup['-'.charCodeAt(0)] = 62 +revLookup['_'.charCodeAt(0)] = 63 + +function getLens (b64) { + var len = b64.length + + if (len % 4 > 0) { + throw new Error('Invalid string. Length must be a multiple of 4') + } + + // Trim off extra bytes after placeholder bytes are found + // See: https://github.com/beatgammit/base64-js/issues/42 + var validLen = b64.indexOf('=') + if (validLen === -1) validLen = len + + var placeHoldersLen = validLen === len + ? 0 + : 4 - (validLen % 4) + + return [validLen, placeHoldersLen] +} + +// base64 is 4/3 + up to two characters of the original data +function byteLength (b64) { + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function _byteLength (b64, validLen, placeHoldersLen) { + return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen +} + +function toByteArray (b64) { + var tmp + var lens = getLens(b64) + var validLen = lens[0] + var placeHoldersLen = lens[1] + + var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen)) + + var curByte = 0 + + // if there are placeholders, only get up to the last complete 4 chars + var len = placeHoldersLen > 0 + ? validLen - 4 + : validLen + + var i + for (i = 0; i < len; i += 4) { + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 2) { + tmp = + (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + } + + if (placeHoldersLen === 1) { + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + + return arr +} + +function tripletToBase64 (num) { + return lookup[num >> 18 & 0x3F] + + lookup[num >> 12 & 0x3F] + + lookup[num >> 6 & 0x3F] + + lookup[num & 0x3F] +} + +function encodeChunk (uint8, start, end) { + var tmp + var output = [] + for (var i = start; i < end; i += 3) { + tmp = + ((uint8[i] << 16) & 0xFF0000) + + ((uint8[i + 1] << 8) & 0xFF00) + + (uint8[i + 2] & 0xFF) + output.push(tripletToBase64(tmp)) + } + return output.join('') +} + +function fromByteArray (uint8) { + var tmp + var len = uint8.length + var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes + var parts = [] + var maxChunkLength = 16383 // must be multiple of 3 + + // go through the array every three bytes, we'll deal with trailing stuff later + for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { + parts.push(encodeChunk(uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength))) + } + + // pad the end with zeros, but make sure to not forget the extra bytes + if (extraBytes === 1) { + tmp = uint8[len - 1] + parts.push( + lookup[tmp >> 2] + + lookup[(tmp << 4) & 0x3F] + + '==' + ) + } else if (extraBytes === 2) { + tmp = (uint8[len - 2] << 8) + uint8[len - 1] + parts.push( + lookup[tmp >> 10] + + lookup[(tmp >> 4) & 0x3F] + + lookup[(tmp << 2) & 0x3F] + + '=' + ) + } + + return parts.join('') +} + +},{}],49:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise) { +var SomePromiseArray = Promise._SomePromiseArray; +function any(promises) { + var ret = new SomePromiseArray(promises); + var promise = ret.promise(); + ret.setHowMany(1); + ret.setUnwrap(); + ret.init(); + return promise; +} + +Promise.any = function (promises) { + return any(promises); +}; + +Promise.prototype.any = function () { + return any(this); +}; + +}; + +},{}],50:[function(require,module,exports){ +(function (process){(function (){ +"use strict"; +var firstLineError; +try {throw new Error(); } catch (e) {firstLineError = e;} +var schedule = require("./schedule"); +var Queue = require("./queue"); +var util = require("./util"); + +function Async() { + this._customScheduler = false; + this._isTickUsed = false; + this._lateQueue = new Queue(16); + this._normalQueue = new Queue(16); + this._haveDrainedQueues = false; + this._trampolineEnabled = true; + var self = this; + this.drainQueues = function () { + self._drainQueues(); + }; + this._schedule = schedule; +} + +Async.prototype.setScheduler = function(fn) { + var prev = this._schedule; + this._schedule = fn; + this._customScheduler = true; + return prev; +}; + +Async.prototype.hasCustomScheduler = function() { + return this._customScheduler; +}; + +Async.prototype.enableTrampoline = function() { + this._trampolineEnabled = true; +}; + +Async.prototype.disableTrampolineIfNecessary = function() { + if (util.hasDevTools) { + this._trampolineEnabled = false; + } +}; + +Async.prototype.haveItemsQueued = function () { + return this._isTickUsed || this._haveDrainedQueues; +}; + + +Async.prototype.fatalError = function(e, isNode) { + if (isNode) { + process.stderr.write("Fatal " + (e instanceof Error ? e.stack : e) + + "\n"); + process.exit(2); + } else { + this.throwLater(e); + } +}; + +Async.prototype.throwLater = function(fn, arg) { + if (arguments.length === 1) { + arg = fn; + fn = function () { throw arg; }; + } + if (typeof setTimeout !== "undefined") { + setTimeout(function() { + fn(arg); + }, 0); + } else try { + this._schedule(function() { + fn(arg); + }); + } catch (e) { + throw new Error("No async scheduler available\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } +}; + +function AsyncInvokeLater(fn, receiver, arg) { + this._lateQueue.push(fn, receiver, arg); + this._queueTick(); +} + +function AsyncInvoke(fn, receiver, arg) { + this._normalQueue.push(fn, receiver, arg); + this._queueTick(); +} + +function AsyncSettlePromises(promise) { + this._normalQueue._pushOne(promise); + this._queueTick(); +} + +if (!util.hasDevTools) { + Async.prototype.invokeLater = AsyncInvokeLater; + Async.prototype.invoke = AsyncInvoke; + Async.prototype.settlePromises = AsyncSettlePromises; +} else { + Async.prototype.invokeLater = function (fn, receiver, arg) { + if (this._trampolineEnabled) { + AsyncInvokeLater.call(this, fn, receiver, arg); + } else { + this._schedule(function() { + setTimeout(function() { + fn.call(receiver, arg); + }, 100); + }); + } + }; + + Async.prototype.invoke = function (fn, receiver, arg) { + if (this._trampolineEnabled) { + AsyncInvoke.call(this, fn, receiver, arg); + } else { + this._schedule(function() { + fn.call(receiver, arg); + }); + } + }; + + Async.prototype.settlePromises = function(promise) { + if (this._trampolineEnabled) { + AsyncSettlePromises.call(this, promise); + } else { + this._schedule(function() { + promise._settlePromises(); + }); + } + }; +} + +Async.prototype._drainQueue = function(queue) { + while (queue.length() > 0) { + var fn = queue.shift(); + if (typeof fn !== "function") { + fn._settlePromises(); + continue; + } + var receiver = queue.shift(); + var arg = queue.shift(); + fn.call(receiver, arg); + } +}; + +Async.prototype._drainQueues = function () { + this._drainQueue(this._normalQueue); + this._reset(); + this._haveDrainedQueues = true; + this._drainQueue(this._lateQueue); +}; + +Async.prototype._queueTick = function () { + if (!this._isTickUsed) { + this._isTickUsed = true; + this._schedule(this.drainQueues); + } +}; + +Async.prototype._reset = function () { + this._isTickUsed = false; +}; + +module.exports = Async; +module.exports.firstLineError = firstLineError; + +}).call(this)}).call(this,require('_process')) +},{"./queue":73,"./schedule":76,"./util":83,"_process":102}],51:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL, tryConvertToPromise, debug) { +var calledBind = false; +var rejectThis = function(_, e) { + this._reject(e); +}; + +var targetRejected = function(e, context) { + context.promiseRejectionQueued = true; + context.bindingPromise._then(rejectThis, rejectThis, null, this, e); +}; + +var bindingResolved = function(thisArg, context) { + if (((this._bitField & 50397184) === 0)) { + this._resolveCallback(context.target); + } +}; + +var bindingRejected = function(e, context) { + if (!context.promiseRejectionQueued) this._reject(e); +}; + +Promise.prototype.bind = function (thisArg) { + if (!calledBind) { + calledBind = true; + Promise.prototype._propagateFrom = debug.propagateFromFunction(); + Promise.prototype._boundValue = debug.boundValueFunction(); + } + var maybePromise = tryConvertToPromise(thisArg); + var ret = new Promise(INTERNAL); + ret._propagateFrom(this, 1); + var target = this._target(); + ret._setBoundTo(maybePromise); + if (maybePromise instanceof Promise) { + var context = { + promiseRejectionQueued: false, + promise: ret, + target: target, + bindingPromise: maybePromise + }; + target._then(INTERNAL, targetRejected, undefined, ret, context); + maybePromise._then( + bindingResolved, bindingRejected, undefined, ret, context); + ret._setOnCancel(maybePromise); + } else { + ret._resolveCallback(target); + } + return ret; +}; + +Promise.prototype._setBoundTo = function (obj) { + if (obj !== undefined) { + this._bitField = this._bitField | 2097152; + this._boundTo = obj; + } else { + this._bitField = this._bitField & (~2097152); + } +}; + +Promise.prototype._isBound = function () { + return (this._bitField & 2097152) === 2097152; +}; + +Promise.bind = function (thisArg, value) { + return Promise.resolve(value).bind(thisArg); +}; +}; + +},{}],52:[function(require,module,exports){ +"use strict"; +var cr = Object.create; +if (cr) { + var callerCache = cr(null); + var getterCache = cr(null); + callerCache[" size"] = getterCache[" size"] = 0; +} + +module.exports = function(Promise) { +var util = require("./util"); +var canEvaluate = util.canEvaluate; +var isIdentifier = util.isIdentifier; + +var getMethodCaller; +var getGetter; +if (!false) { +var makeMethodCaller = function (methodName) { + return new Function("ensureMethod", " \n\ + return function(obj) { \n\ + 'use strict' \n\ + var len = this.length; \n\ + ensureMethod(obj, 'methodName'); \n\ + switch(len) { \n\ + case 1: return obj.methodName(this[0]); \n\ + case 2: return obj.methodName(this[0], this[1]); \n\ + case 3: return obj.methodName(this[0], this[1], this[2]); \n\ + case 0: return obj.methodName(); \n\ + default: \n\ + return obj.methodName.apply(obj, this); \n\ + } \n\ + }; \n\ + ".replace(/methodName/g, methodName))(ensureMethod); +}; + +var makeGetter = function (propertyName) { + return new Function("obj", " \n\ + 'use strict'; \n\ + return obj.propertyName; \n\ + ".replace("propertyName", propertyName)); +}; + +var getCompiled = function(name, compiler, cache) { + var ret = cache[name]; + if (typeof ret !== "function") { + if (!isIdentifier(name)) { + return null; + } + ret = compiler(name); + cache[name] = ret; + cache[" size"]++; + if (cache[" size"] > 512) { + var keys = Object.keys(cache); + for (var i = 0; i < 256; ++i) delete cache[keys[i]]; + cache[" size"] = keys.length - 256; + } + } + return ret; +}; + +getMethodCaller = function(name) { + return getCompiled(name, makeMethodCaller, callerCache); +}; + +getGetter = function(name) { + return getCompiled(name, makeGetter, getterCache); +}; +} + +function ensureMethod(obj, methodName) { + var fn; + if (obj != null) fn = obj[methodName]; + if (typeof fn !== "function") { + var message = "Object " + util.classString(obj) + " has no method '" + + util.toString(methodName) + "'"; + throw new Promise.TypeError(message); + } + return fn; +} + +function caller(obj) { + var methodName = this.pop(); + var fn = ensureMethod(obj, methodName); + return fn.apply(obj, this); +} +Promise.prototype.call = function (methodName) { + var $_len = arguments.length;var args = new Array(Math.max($_len - 1, 0)); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}; + if (!false) { + if (canEvaluate) { + var maybeCaller = getMethodCaller(methodName); + if (maybeCaller !== null) { + return this._then( + maybeCaller, undefined, undefined, args, undefined); + } + } + } + args.push(methodName); + return this._then(caller, undefined, undefined, args, undefined); +}; + +function namedGetter(obj) { + return obj[this]; +} +function indexedGetter(obj) { + var index = +this; + if (index < 0) index = Math.max(0, index + obj.length); + return obj[index]; +} +Promise.prototype.get = function (propertyName) { + var isIndex = (typeof propertyName === "number"); + var getter; + if (!isIndex) { + if (canEvaluate) { + var maybeGetter = getGetter(propertyName); + getter = maybeGetter !== null ? maybeGetter : namedGetter; + } else { + getter = namedGetter; + } + } else { + getter = indexedGetter; + } + return this._then(getter, undefined, undefined, propertyName, undefined); +}; +}; + +},{"./util":83}],53:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, PromiseArray, apiRejection, debug) { +var util = require("./util"); +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var async = Promise._async; + +Promise.prototype["break"] = Promise.prototype.cancel = function() { + if (!debug.cancellation()) return this._warn("cancellation is disabled"); + + var promise = this; + var child = promise; + while (promise._isCancellable()) { + if (!promise._cancelBy(child)) { + if (child._isFollowing()) { + child._followee().cancel(); + } else { + child._cancelBranched(); + } + break; + } + + var parent = promise._cancellationParent; + if (parent == null || !parent._isCancellable()) { + if (promise._isFollowing()) { + promise._followee().cancel(); + } else { + promise._cancelBranched(); + } + break; + } else { + if (promise._isFollowing()) promise._followee().cancel(); + promise._setWillBeCancelled(); + child = promise; + promise = parent; + } + } +}; + +Promise.prototype._branchHasCancelled = function() { + this._branchesRemainingToCancel--; +}; + +Promise.prototype._enoughBranchesHaveCancelled = function() { + return this._branchesRemainingToCancel === undefined || + this._branchesRemainingToCancel <= 0; +}; + +Promise.prototype._cancelBy = function(canceller) { + if (canceller === this) { + this._branchesRemainingToCancel = 0; + this._invokeOnCancel(); + return true; + } else { + this._branchHasCancelled(); + if (this._enoughBranchesHaveCancelled()) { + this._invokeOnCancel(); + return true; + } + } + return false; +}; + +Promise.prototype._cancelBranched = function() { + if (this._enoughBranchesHaveCancelled()) { + this._cancel(); + } +}; + +Promise.prototype._cancel = function() { + if (!this._isCancellable()) return; + this._setCancelled(); + async.invoke(this._cancelPromises, this, undefined); +}; + +Promise.prototype._cancelPromises = function() { + if (this._length() > 0) this._settlePromises(); +}; + +Promise.prototype._unsetOnCancel = function() { + this._onCancelField = undefined; +}; + +Promise.prototype._isCancellable = function() { + return this.isPending() && !this._isCancelled(); +}; + +Promise.prototype.isCancellable = function() { + return this.isPending() && !this.isCancelled(); +}; + +Promise.prototype._doInvokeOnCancel = function(onCancelCallback, internalOnly) { + if (util.isArray(onCancelCallback)) { + for (var i = 0; i < onCancelCallback.length; ++i) { + this._doInvokeOnCancel(onCancelCallback[i], internalOnly); + } + } else if (onCancelCallback !== undefined) { + if (typeof onCancelCallback === "function") { + if (!internalOnly) { + var e = tryCatch(onCancelCallback).call(this._boundValue()); + if (e === errorObj) { + this._attachExtraTrace(e.e); + async.throwLater(e.e); + } + } + } else { + onCancelCallback._resultCancelled(this); + } + } +}; + +Promise.prototype._invokeOnCancel = function() { + var onCancelCallback = this._onCancel(); + this._unsetOnCancel(); + async.invoke(this._doInvokeOnCancel, this, onCancelCallback); +}; + +Promise.prototype._invokeInternalOnCancel = function() { + if (this._isCancellable()) { + this._doInvokeOnCancel(this._onCancel(), true); + this._unsetOnCancel(); + } +}; + +Promise.prototype._resultCancelled = function() { + this.cancel(); +}; + +}; + +},{"./util":83}],54:[function(require,module,exports){ +"use strict"; +module.exports = function(NEXT_FILTER) { +var util = require("./util"); +var getKeys = require("./es5").keys; +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; + +function catchFilter(instances, cb, promise) { + return function(e) { + var boundTo = promise._boundValue(); + predicateLoop: for (var i = 0; i < instances.length; ++i) { + var item = instances[i]; + + if (item === Error || + (item != null && item.prototype instanceof Error)) { + if (e instanceof item) { + return tryCatch(cb).call(boundTo, e); + } + } else if (typeof item === "function") { + var matchesPredicate = tryCatch(item).call(boundTo, e); + if (matchesPredicate === errorObj) { + return matchesPredicate; + } else if (matchesPredicate) { + return tryCatch(cb).call(boundTo, e); + } + } else if (util.isObject(e)) { + var keys = getKeys(item); + for (var j = 0; j < keys.length; ++j) { + var key = keys[j]; + if (item[key] != e[key]) { + continue predicateLoop; + } + } + return tryCatch(cb).call(boundTo, e); + } + } + return NEXT_FILTER; + }; +} + +return catchFilter; +}; + +},{"./es5":60,"./util":83}],55:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise) { +var longStackTraces = false; +var contextStack = []; + +Promise.prototype._promiseCreated = function() {}; +Promise.prototype._pushContext = function() {}; +Promise.prototype._popContext = function() {return null;}; +Promise._peekContext = Promise.prototype._peekContext = function() {}; + +function Context() { + this._trace = new Context.CapturedTrace(peekContext()); +} +Context.prototype._pushContext = function () { + if (this._trace !== undefined) { + this._trace._promiseCreated = null; + contextStack.push(this._trace); + } +}; + +Context.prototype._popContext = function () { + if (this._trace !== undefined) { + var trace = contextStack.pop(); + var ret = trace._promiseCreated; + trace._promiseCreated = null; + return ret; + } + return null; +}; + +function createContext() { + if (longStackTraces) return new Context(); +} + +function peekContext() { + var lastIndex = contextStack.length - 1; + if (lastIndex >= 0) { + return contextStack[lastIndex]; + } + return undefined; +} +Context.CapturedTrace = null; +Context.create = createContext; +Context.deactivateLongStackTraces = function() {}; +Context.activateLongStackTraces = function() { + var Promise_pushContext = Promise.prototype._pushContext; + var Promise_popContext = Promise.prototype._popContext; + var Promise_PeekContext = Promise._peekContext; + var Promise_peekContext = Promise.prototype._peekContext; + var Promise_promiseCreated = Promise.prototype._promiseCreated; + Context.deactivateLongStackTraces = function() { + Promise.prototype._pushContext = Promise_pushContext; + Promise.prototype._popContext = Promise_popContext; + Promise._peekContext = Promise_PeekContext; + Promise.prototype._peekContext = Promise_peekContext; + Promise.prototype._promiseCreated = Promise_promiseCreated; + longStackTraces = false; + }; + longStackTraces = true; + Promise.prototype._pushContext = Context.prototype._pushContext; + Promise.prototype._popContext = Context.prototype._popContext; + Promise._peekContext = Promise.prototype._peekContext = peekContext; + Promise.prototype._promiseCreated = function() { + var ctx = this._peekContext(); + if (ctx && ctx._promiseCreated == null) ctx._promiseCreated = this; + }; +}; +return Context; +}; + +},{}],56:[function(require,module,exports){ +(function (process){(function (){ +"use strict"; +module.exports = function(Promise, Context) { +var getDomain = Promise._getDomain; +var async = Promise._async; +var Warning = require("./errors").Warning; +var util = require("./util"); +var canAttachTrace = util.canAttachTrace; +var unhandledRejectionHandled; +var possiblyUnhandledRejection; +var bluebirdFramePattern = + /[\\\/]bluebird[\\\/]js[\\\/](release|debug|instrumented)/; +var nodeFramePattern = /\((?:timers\.js):\d+:\d+\)/; +var parseLinePattern = /[\/<\(](.+?):(\d+):(\d+)\)?\s*$/; +var stackFramePattern = null; +var formatStack = null; +var indentStackFrames = false; +var printWarning; +var debugging = !!(util.env("BLUEBIRD_DEBUG") != 0 && + (false || + util.env("BLUEBIRD_DEBUG") || + util.env("NODE_ENV") === "development")); + +var warnings = !!(util.env("BLUEBIRD_WARNINGS") != 0 && + (debugging || util.env("BLUEBIRD_WARNINGS"))); + +var longStackTraces = !!(util.env("BLUEBIRD_LONG_STACK_TRACES") != 0 && + (debugging || util.env("BLUEBIRD_LONG_STACK_TRACES"))); + +var wForgottenReturn = util.env("BLUEBIRD_W_FORGOTTEN_RETURN") != 0 && + (warnings || !!util.env("BLUEBIRD_W_FORGOTTEN_RETURN")); + +Promise.prototype.suppressUnhandledRejections = function() { + var target = this._target(); + target._bitField = ((target._bitField & (~1048576)) | + 524288); +}; + +Promise.prototype._ensurePossibleRejectionHandled = function () { + if ((this._bitField & 524288) !== 0) return; + this._setRejectionIsUnhandled(); + async.invokeLater(this._notifyUnhandledRejection, this, undefined); +}; + +Promise.prototype._notifyUnhandledRejectionIsHandled = function () { + fireRejectionEvent("rejectionHandled", + unhandledRejectionHandled, undefined, this); +}; + +Promise.prototype._setReturnedNonUndefined = function() { + this._bitField = this._bitField | 268435456; +}; + +Promise.prototype._returnedNonUndefined = function() { + return (this._bitField & 268435456) !== 0; +}; + +Promise.prototype._notifyUnhandledRejection = function () { + if (this._isRejectionUnhandled()) { + var reason = this._settledValue(); + this._setUnhandledRejectionIsNotified(); + fireRejectionEvent("unhandledRejection", + possiblyUnhandledRejection, reason, this); + } +}; + +Promise.prototype._setUnhandledRejectionIsNotified = function () { + this._bitField = this._bitField | 262144; +}; + +Promise.prototype._unsetUnhandledRejectionIsNotified = function () { + this._bitField = this._bitField & (~262144); +}; + +Promise.prototype._isUnhandledRejectionNotified = function () { + return (this._bitField & 262144) > 0; +}; + +Promise.prototype._setRejectionIsUnhandled = function () { + this._bitField = this._bitField | 1048576; +}; + +Promise.prototype._unsetRejectionIsUnhandled = function () { + this._bitField = this._bitField & (~1048576); + if (this._isUnhandledRejectionNotified()) { + this._unsetUnhandledRejectionIsNotified(); + this._notifyUnhandledRejectionIsHandled(); + } +}; + +Promise.prototype._isRejectionUnhandled = function () { + return (this._bitField & 1048576) > 0; +}; + +Promise.prototype._warn = function(message, shouldUseOwnTrace, promise) { + return warn(message, shouldUseOwnTrace, promise || this); +}; + +Promise.onPossiblyUnhandledRejection = function (fn) { + var domain = getDomain(); + possiblyUnhandledRejection = + typeof fn === "function" ? (domain === null ? + fn : util.domainBind(domain, fn)) + : undefined; +}; + +Promise.onUnhandledRejectionHandled = function (fn) { + var domain = getDomain(); + unhandledRejectionHandled = + typeof fn === "function" ? (domain === null ? + fn : util.domainBind(domain, fn)) + : undefined; +}; + +var disableLongStackTraces = function() {}; +Promise.longStackTraces = function () { + if (async.haveItemsQueued() && !config.longStackTraces) { + throw new Error("cannot enable long stack traces after promises have been created\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + if (!config.longStackTraces && longStackTracesIsSupported()) { + var Promise_captureStackTrace = Promise.prototype._captureStackTrace; + var Promise_attachExtraTrace = Promise.prototype._attachExtraTrace; + config.longStackTraces = true; + disableLongStackTraces = function() { + if (async.haveItemsQueued() && !config.longStackTraces) { + throw new Error("cannot enable long stack traces after promises have been created\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + Promise.prototype._captureStackTrace = Promise_captureStackTrace; + Promise.prototype._attachExtraTrace = Promise_attachExtraTrace; + Context.deactivateLongStackTraces(); + async.enableTrampoline(); + config.longStackTraces = false; + }; + Promise.prototype._captureStackTrace = longStackTracesCaptureStackTrace; + Promise.prototype._attachExtraTrace = longStackTracesAttachExtraTrace; + Context.activateLongStackTraces(); + async.disableTrampolineIfNecessary(); + } +}; + +Promise.hasLongStackTraces = function () { + return config.longStackTraces && longStackTracesIsSupported(); +}; + +var fireDomEvent = (function() { + try { + if (typeof CustomEvent === "function") { + var event = new CustomEvent("CustomEvent"); + util.global.dispatchEvent(event); + return function(name, event) { + var domEvent = new CustomEvent(name.toLowerCase(), { + detail: event, + cancelable: true + }); + return !util.global.dispatchEvent(domEvent); + }; + } else if (typeof Event === "function") { + var event = new Event("CustomEvent"); + util.global.dispatchEvent(event); + return function(name, event) { + var domEvent = new Event(name.toLowerCase(), { + cancelable: true + }); + domEvent.detail = event; + return !util.global.dispatchEvent(domEvent); + }; + } else { + var event = document.createEvent("CustomEvent"); + event.initCustomEvent("testingtheevent", false, true, {}); + util.global.dispatchEvent(event); + return function(name, event) { + var domEvent = document.createEvent("CustomEvent"); + domEvent.initCustomEvent(name.toLowerCase(), false, true, + event); + return !util.global.dispatchEvent(domEvent); + }; + } + } catch (e) {} + return function() { + return false; + }; +})(); + +var fireGlobalEvent = (function() { + if (util.isNode) { + return function() { + return process.emit.apply(process, arguments); + }; + } else { + if (!util.global) { + return function() { + return false; + }; + } + return function(name) { + var methodName = "on" + name.toLowerCase(); + var method = util.global[methodName]; + if (!method) return false; + method.apply(util.global, [].slice.call(arguments, 1)); + return true; + }; + } +})(); + +function generatePromiseLifecycleEventObject(name, promise) { + return {promise: promise}; +} + +var eventToObjectGenerator = { + promiseCreated: generatePromiseLifecycleEventObject, + promiseFulfilled: generatePromiseLifecycleEventObject, + promiseRejected: generatePromiseLifecycleEventObject, + promiseResolved: generatePromiseLifecycleEventObject, + promiseCancelled: generatePromiseLifecycleEventObject, + promiseChained: function(name, promise, child) { + return {promise: promise, child: child}; + }, + warning: function(name, warning) { + return {warning: warning}; + }, + unhandledRejection: function (name, reason, promise) { + return {reason: reason, promise: promise}; + }, + rejectionHandled: generatePromiseLifecycleEventObject +}; + +var activeFireEvent = function (name) { + var globalEventFired = false; + try { + globalEventFired = fireGlobalEvent.apply(null, arguments); + } catch (e) { + async.throwLater(e); + globalEventFired = true; + } + + var domEventFired = false; + try { + domEventFired = fireDomEvent(name, + eventToObjectGenerator[name].apply(null, arguments)); + } catch (e) { + async.throwLater(e); + domEventFired = true; + } + + return domEventFired || globalEventFired; +}; + +Promise.config = function(opts) { + opts = Object(opts); + if ("longStackTraces" in opts) { + if (opts.longStackTraces) { + Promise.longStackTraces(); + } else if (!opts.longStackTraces && Promise.hasLongStackTraces()) { + disableLongStackTraces(); + } + } + if ("warnings" in opts) { + var warningsOption = opts.warnings; + config.warnings = !!warningsOption; + wForgottenReturn = config.warnings; + + if (util.isObject(warningsOption)) { + if ("wForgottenReturn" in warningsOption) { + wForgottenReturn = !!warningsOption.wForgottenReturn; + } + } + } + if ("cancellation" in opts && opts.cancellation && !config.cancellation) { + if (async.haveItemsQueued()) { + throw new Error( + "cannot enable cancellation after promises are in use"); + } + Promise.prototype._clearCancellationData = + cancellationClearCancellationData; + Promise.prototype._propagateFrom = cancellationPropagateFrom; + Promise.prototype._onCancel = cancellationOnCancel; + Promise.prototype._setOnCancel = cancellationSetOnCancel; + Promise.prototype._attachCancellationCallback = + cancellationAttachCancellationCallback; + Promise.prototype._execute = cancellationExecute; + propagateFromFunction = cancellationPropagateFrom; + config.cancellation = true; + } + if ("monitoring" in opts) { + if (opts.monitoring && !config.monitoring) { + config.monitoring = true; + Promise.prototype._fireEvent = activeFireEvent; + } else if (!opts.monitoring && config.monitoring) { + config.monitoring = false; + Promise.prototype._fireEvent = defaultFireEvent; + } + } + return Promise; +}; + +function defaultFireEvent() { return false; } + +Promise.prototype._fireEvent = defaultFireEvent; +Promise.prototype._execute = function(executor, resolve, reject) { + try { + executor(resolve, reject); + } catch (e) { + return e; + } +}; +Promise.prototype._onCancel = function () {}; +Promise.prototype._setOnCancel = function (handler) { ; }; +Promise.prototype._attachCancellationCallback = function(onCancel) { + ; +}; +Promise.prototype._captureStackTrace = function () {}; +Promise.prototype._attachExtraTrace = function () {}; +Promise.prototype._clearCancellationData = function() {}; +Promise.prototype._propagateFrom = function (parent, flags) { + ; + ; +}; + +function cancellationExecute(executor, resolve, reject) { + var promise = this; + try { + executor(resolve, reject, function(onCancel) { + if (typeof onCancel !== "function") { + throw new TypeError("onCancel must be a function, got: " + + util.toString(onCancel)); + } + promise._attachCancellationCallback(onCancel); + }); + } catch (e) { + return e; + } +} + +function cancellationAttachCancellationCallback(onCancel) { + if (!this._isCancellable()) return this; + + var previousOnCancel = this._onCancel(); + if (previousOnCancel !== undefined) { + if (util.isArray(previousOnCancel)) { + previousOnCancel.push(onCancel); + } else { + this._setOnCancel([previousOnCancel, onCancel]); + } + } else { + this._setOnCancel(onCancel); + } +} + +function cancellationOnCancel() { + return this._onCancelField; +} + +function cancellationSetOnCancel(onCancel) { + this._onCancelField = onCancel; +} + +function cancellationClearCancellationData() { + this._cancellationParent = undefined; + this._onCancelField = undefined; +} + +function cancellationPropagateFrom(parent, flags) { + if ((flags & 1) !== 0) { + this._cancellationParent = parent; + var branchesRemainingToCancel = parent._branchesRemainingToCancel; + if (branchesRemainingToCancel === undefined) { + branchesRemainingToCancel = 0; + } + parent._branchesRemainingToCancel = branchesRemainingToCancel + 1; + } + if ((flags & 2) !== 0 && parent._isBound()) { + this._setBoundTo(parent._boundTo); + } +} + +function bindingPropagateFrom(parent, flags) { + if ((flags & 2) !== 0 && parent._isBound()) { + this._setBoundTo(parent._boundTo); + } +} +var propagateFromFunction = bindingPropagateFrom; + +function boundValueFunction() { + var ret = this._boundTo; + if (ret !== undefined) { + if (ret instanceof Promise) { + if (ret.isFulfilled()) { + return ret.value(); + } else { + return undefined; + } + } + } + return ret; +} + +function longStackTracesCaptureStackTrace() { + this._trace = new CapturedTrace(this._peekContext()); +} + +function longStackTracesAttachExtraTrace(error, ignoreSelf) { + if (canAttachTrace(error)) { + var trace = this._trace; + if (trace !== undefined) { + if (ignoreSelf) trace = trace._parent; + } + if (trace !== undefined) { + trace.attachExtraTrace(error); + } else if (!error.__stackCleaned__) { + var parsed = parseStackAndMessage(error); + util.notEnumerableProp(error, "stack", + parsed.message + "\n" + parsed.stack.join("\n")); + util.notEnumerableProp(error, "__stackCleaned__", true); + } + } +} + +function checkForgottenReturns(returnValue, promiseCreated, name, promise, + parent) { + if (returnValue === undefined && promiseCreated !== null && + wForgottenReturn) { + if (parent !== undefined && parent._returnedNonUndefined()) return; + if ((promise._bitField & 65535) === 0) return; + + if (name) name = name + " "; + var handlerLine = ""; + var creatorLine = ""; + if (promiseCreated._trace) { + var traceLines = promiseCreated._trace.stack.split("\n"); + var stack = cleanStack(traceLines); + for (var i = stack.length - 1; i >= 0; --i) { + var line = stack[i]; + if (!nodeFramePattern.test(line)) { + var lineMatches = line.match(parseLinePattern); + if (lineMatches) { + handlerLine = "at " + lineMatches[1] + + ":" + lineMatches[2] + ":" + lineMatches[3] + " "; + } + break; + } + } + + if (stack.length > 0) { + var firstUserLine = stack[0]; + for (var i = 0; i < traceLines.length; ++i) { + + if (traceLines[i] === firstUserLine) { + if (i > 0) { + creatorLine = "\n" + traceLines[i - 1]; + } + break; + } + } + + } + } + var msg = "a promise was created in a " + name + + "handler " + handlerLine + "but was not returned from it, " + + "see http://goo.gl/rRqMUw" + + creatorLine; + promise._warn(msg, true, promiseCreated); + } +} + +function deprecated(name, replacement) { + var message = name + + " is deprecated and will be removed in a future version."; + if (replacement) message += " Use " + replacement + " instead."; + return warn(message); +} + +function warn(message, shouldUseOwnTrace, promise) { + if (!config.warnings) return; + var warning = new Warning(message); + var ctx; + if (shouldUseOwnTrace) { + promise._attachExtraTrace(warning); + } else if (config.longStackTraces && (ctx = Promise._peekContext())) { + ctx.attachExtraTrace(warning); + } else { + var parsed = parseStackAndMessage(warning); + warning.stack = parsed.message + "\n" + parsed.stack.join("\n"); + } + + if (!activeFireEvent("warning", warning)) { + formatAndLogError(warning, "", true); + } +} + +function reconstructStack(message, stacks) { + for (var i = 0; i < stacks.length - 1; ++i) { + stacks[i].push("From previous event:"); + stacks[i] = stacks[i].join("\n"); + } + if (i < stacks.length) { + stacks[i] = stacks[i].join("\n"); + } + return message + "\n" + stacks.join("\n"); +} + +function removeDuplicateOrEmptyJumps(stacks) { + for (var i = 0; i < stacks.length; ++i) { + if (stacks[i].length === 0 || + ((i + 1 < stacks.length) && stacks[i][0] === stacks[i+1][0])) { + stacks.splice(i, 1); + i--; + } + } +} + +function removeCommonRoots(stacks) { + var current = stacks[0]; + for (var i = 1; i < stacks.length; ++i) { + var prev = stacks[i]; + var currentLastIndex = current.length - 1; + var currentLastLine = current[currentLastIndex]; + var commonRootMeetPoint = -1; + + for (var j = prev.length - 1; j >= 0; --j) { + if (prev[j] === currentLastLine) { + commonRootMeetPoint = j; + break; + } + } + + for (var j = commonRootMeetPoint; j >= 0; --j) { + var line = prev[j]; + if (current[currentLastIndex] === line) { + current.pop(); + currentLastIndex--; + } else { + break; + } + } + current = prev; + } +} + +function cleanStack(stack) { + var ret = []; + for (var i = 0; i < stack.length; ++i) { + var line = stack[i]; + var isTraceLine = " (No stack trace)" === line || + stackFramePattern.test(line); + var isInternalFrame = isTraceLine && shouldIgnore(line); + if (isTraceLine && !isInternalFrame) { + if (indentStackFrames && line.charAt(0) !== " ") { + line = " " + line; + } + ret.push(line); + } + } + return ret; +} + +function stackFramesAsArray(error) { + var stack = error.stack.replace(/\s+$/g, "").split("\n"); + for (var i = 0; i < stack.length; ++i) { + var line = stack[i]; + if (" (No stack trace)" === line || stackFramePattern.test(line)) { + break; + } + } + if (i > 0 && error.name != "SyntaxError") { + stack = stack.slice(i); + } + return stack; +} + +function parseStackAndMessage(error) { + var stack = error.stack; + var message = error.toString(); + stack = typeof stack === "string" && stack.length > 0 + ? stackFramesAsArray(error) : [" (No stack trace)"]; + return { + message: message, + stack: error.name == "SyntaxError" ? stack : cleanStack(stack) + }; +} + +function formatAndLogError(error, title, isSoft) { + if (typeof console !== "undefined") { + var message; + if (util.isObject(error)) { + var stack = error.stack; + message = title + formatStack(stack, error); + } else { + message = title + String(error); + } + if (typeof printWarning === "function") { + printWarning(message, isSoft); + } else if (typeof console.log === "function" || + typeof console.log === "object") { + console.log(message); + } + } +} + +function fireRejectionEvent(name, localHandler, reason, promise) { + var localEventFired = false; + try { + if (typeof localHandler === "function") { + localEventFired = true; + if (name === "rejectionHandled") { + localHandler(promise); + } else { + localHandler(reason, promise); + } + } + } catch (e) { + async.throwLater(e); + } + + if (name === "unhandledRejection") { + if (!activeFireEvent(name, reason, promise) && !localEventFired) { + formatAndLogError(reason, "Unhandled rejection "); + } + } else { + activeFireEvent(name, promise); + } +} + +function formatNonError(obj) { + var str; + if (typeof obj === "function") { + str = "[function " + + (obj.name || "anonymous") + + "]"; + } else { + str = obj && typeof obj.toString === "function" + ? obj.toString() : util.toString(obj); + var ruselessToString = /\[object [a-zA-Z0-9$_]+\]/; + if (ruselessToString.test(str)) { + try { + var newStr = JSON.stringify(obj); + str = newStr; + } + catch(e) { + + } + } + if (str.length === 0) { + str = "(empty array)"; + } + } + return ("(<" + snip(str) + ">, no stack trace)"); +} + +function snip(str) { + var maxChars = 41; + if (str.length < maxChars) { + return str; + } + return str.substr(0, maxChars - 3) + "..."; +} + +function longStackTracesIsSupported() { + return typeof captureStackTrace === "function"; +} + +var shouldIgnore = function() { return false; }; +var parseLineInfoRegex = /[\/<\(]([^:\/]+):(\d+):(?:\d+)\)?\s*$/; +function parseLineInfo(line) { + var matches = line.match(parseLineInfoRegex); + if (matches) { + return { + fileName: matches[1], + line: parseInt(matches[2], 10) + }; + } +} + +function setBounds(firstLineError, lastLineError) { + if (!longStackTracesIsSupported()) return; + var firstStackLines = firstLineError.stack.split("\n"); + var lastStackLines = lastLineError.stack.split("\n"); + var firstIndex = -1; + var lastIndex = -1; + var firstFileName; + var lastFileName; + for (var i = 0; i < firstStackLines.length; ++i) { + var result = parseLineInfo(firstStackLines[i]); + if (result) { + firstFileName = result.fileName; + firstIndex = result.line; + break; + } + } + for (var i = 0; i < lastStackLines.length; ++i) { + var result = parseLineInfo(lastStackLines[i]); + if (result) { + lastFileName = result.fileName; + lastIndex = result.line; + break; + } + } + if (firstIndex < 0 || lastIndex < 0 || !firstFileName || !lastFileName || + firstFileName !== lastFileName || firstIndex >= lastIndex) { + return; + } + + shouldIgnore = function(line) { + if (bluebirdFramePattern.test(line)) return true; + var info = parseLineInfo(line); + if (info) { + if (info.fileName === firstFileName && + (firstIndex <= info.line && info.line <= lastIndex)) { + return true; + } + } + return false; + }; +} + +function CapturedTrace(parent) { + this._parent = parent; + this._promisesCreated = 0; + var length = this._length = 1 + (parent === undefined ? 0 : parent._length); + captureStackTrace(this, CapturedTrace); + if (length > 32) this.uncycle(); +} +util.inherits(CapturedTrace, Error); +Context.CapturedTrace = CapturedTrace; + +CapturedTrace.prototype.uncycle = function() { + var length = this._length; + if (length < 2) return; + var nodes = []; + var stackToIndex = {}; + + for (var i = 0, node = this; node !== undefined; ++i) { + nodes.push(node); + node = node._parent; + } + length = this._length = i; + for (var i = length - 1; i >= 0; --i) { + var stack = nodes[i].stack; + if (stackToIndex[stack] === undefined) { + stackToIndex[stack] = i; + } + } + for (var i = 0; i < length; ++i) { + var currentStack = nodes[i].stack; + var index = stackToIndex[currentStack]; + if (index !== undefined && index !== i) { + if (index > 0) { + nodes[index - 1]._parent = undefined; + nodes[index - 1]._length = 1; + } + nodes[i]._parent = undefined; + nodes[i]._length = 1; + var cycleEdgeNode = i > 0 ? nodes[i - 1] : this; + + if (index < length - 1) { + cycleEdgeNode._parent = nodes[index + 1]; + cycleEdgeNode._parent.uncycle(); + cycleEdgeNode._length = + cycleEdgeNode._parent._length + 1; + } else { + cycleEdgeNode._parent = undefined; + cycleEdgeNode._length = 1; + } + var currentChildLength = cycleEdgeNode._length + 1; + for (var j = i - 2; j >= 0; --j) { + nodes[j]._length = currentChildLength; + currentChildLength++; + } + return; + } + } +}; + +CapturedTrace.prototype.attachExtraTrace = function(error) { + if (error.__stackCleaned__) return; + this.uncycle(); + var parsed = parseStackAndMessage(error); + var message = parsed.message; + var stacks = [parsed.stack]; + + var trace = this; + while (trace !== undefined) { + stacks.push(cleanStack(trace.stack.split("\n"))); + trace = trace._parent; + } + removeCommonRoots(stacks); + removeDuplicateOrEmptyJumps(stacks); + util.notEnumerableProp(error, "stack", reconstructStack(message, stacks)); + util.notEnumerableProp(error, "__stackCleaned__", true); +}; + +var captureStackTrace = (function stackDetection() { + var v8stackFramePattern = /^\s*at\s*/; + var v8stackFormatter = function(stack, error) { + if (typeof stack === "string") return stack; + + if (error.name !== undefined && + error.message !== undefined) { + return error.toString(); + } + return formatNonError(error); + }; + + if (typeof Error.stackTraceLimit === "number" && + typeof Error.captureStackTrace === "function") { + Error.stackTraceLimit += 6; + stackFramePattern = v8stackFramePattern; + formatStack = v8stackFormatter; + var captureStackTrace = Error.captureStackTrace; + + shouldIgnore = function(line) { + return bluebirdFramePattern.test(line); + }; + return function(receiver, ignoreUntil) { + Error.stackTraceLimit += 6; + captureStackTrace(receiver, ignoreUntil); + Error.stackTraceLimit -= 6; + }; + } + var err = new Error(); + + if (typeof err.stack === "string" && + err.stack.split("\n")[0].indexOf("stackDetection@") >= 0) { + stackFramePattern = /@/; + formatStack = v8stackFormatter; + indentStackFrames = true; + return function captureStackTrace(o) { + o.stack = new Error().stack; + }; + } + + var hasStackAfterThrow; + try { throw new Error(); } + catch(e) { + hasStackAfterThrow = ("stack" in e); + } + if (!("stack" in err) && hasStackAfterThrow && + typeof Error.stackTraceLimit === "number") { + stackFramePattern = v8stackFramePattern; + formatStack = v8stackFormatter; + return function captureStackTrace(o) { + Error.stackTraceLimit += 6; + try { throw new Error(); } + catch(e) { o.stack = e.stack; } + Error.stackTraceLimit -= 6; + }; + } + + formatStack = function(stack, error) { + if (typeof stack === "string") return stack; + + if ((typeof error === "object" || + typeof error === "function") && + error.name !== undefined && + error.message !== undefined) { + return error.toString(); + } + return formatNonError(error); + }; + + return null; + +})([]); + +if (typeof console !== "undefined" && typeof console.warn !== "undefined") { + printWarning = function (message) { + console.warn(message); + }; + if (util.isNode && process.stderr.isTTY) { + printWarning = function(message, isSoft) { + var color = isSoft ? "\u001b[33m" : "\u001b[31m"; + console.warn(color + message + "\u001b[0m\n"); + }; + } else if (!util.isNode && typeof (new Error().stack) === "string") { + printWarning = function(message, isSoft) { + console.warn("%c" + message, + isSoft ? "color: darkorange" : "color: red"); + }; + } +} + +var config = { + warnings: warnings, + longStackTraces: false, + cancellation: false, + monitoring: false +}; + +if (longStackTraces) Promise.longStackTraces(); + +return { + longStackTraces: function() { + return config.longStackTraces; + }, + warnings: function() { + return config.warnings; + }, + cancellation: function() { + return config.cancellation; + }, + monitoring: function() { + return config.monitoring; + }, + propagateFromFunction: function() { + return propagateFromFunction; + }, + boundValueFunction: function() { + return boundValueFunction; + }, + checkForgottenReturns: checkForgottenReturns, + setBounds: setBounds, + warn: warn, + deprecated: deprecated, + CapturedTrace: CapturedTrace, + fireDomEvent: fireDomEvent, + fireGlobalEvent: fireGlobalEvent +}; +}; + +}).call(this)}).call(this,require('_process')) +},{"./errors":59,"./util":83,"_process":102}],57:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise) { +function returner() { + return this.value; +} +function thrower() { + throw this.reason; +} + +Promise.prototype["return"] = +Promise.prototype.thenReturn = function (value) { + if (value instanceof Promise) value.suppressUnhandledRejections(); + return this._then( + returner, undefined, undefined, {value: value}, undefined); +}; + +Promise.prototype["throw"] = +Promise.prototype.thenThrow = function (reason) { + return this._then( + thrower, undefined, undefined, {reason: reason}, undefined); +}; + +Promise.prototype.catchThrow = function (reason) { + if (arguments.length <= 1) { + return this._then( + undefined, thrower, undefined, {reason: reason}, undefined); + } else { + var _reason = arguments[1]; + var handler = function() {throw _reason;}; + return this.caught(reason, handler); + } +}; + +Promise.prototype.catchReturn = function (value) { + if (arguments.length <= 1) { + if (value instanceof Promise) value.suppressUnhandledRejections(); + return this._then( + undefined, returner, undefined, {value: value}, undefined); + } else { + var _value = arguments[1]; + if (_value instanceof Promise) _value.suppressUnhandledRejections(); + var handler = function() {return _value;}; + return this.caught(value, handler); + } +}; +}; + +},{}],58:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var PromiseReduce = Promise.reduce; +var PromiseAll = Promise.all; + +function promiseAllThis() { + return PromiseAll(this); +} + +function PromiseMapSeries(promises, fn) { + return PromiseReduce(promises, fn, INTERNAL, INTERNAL); +} + +Promise.prototype.each = function (fn) { + return PromiseReduce(this, fn, INTERNAL, 0) + ._then(promiseAllThis, undefined, undefined, this, undefined); +}; + +Promise.prototype.mapSeries = function (fn) { + return PromiseReduce(this, fn, INTERNAL, INTERNAL); +}; + +Promise.each = function (promises, fn) { + return PromiseReduce(promises, fn, INTERNAL, 0) + ._then(promiseAllThis, undefined, undefined, promises, undefined); +}; + +Promise.mapSeries = PromiseMapSeries; +}; + + +},{}],59:[function(require,module,exports){ +"use strict"; +var es5 = require("./es5"); +var Objectfreeze = es5.freeze; +var util = require("./util"); +var inherits = util.inherits; +var notEnumerableProp = util.notEnumerableProp; + +function subError(nameProperty, defaultMessage) { + function SubError(message) { + if (!(this instanceof SubError)) return new SubError(message); + notEnumerableProp(this, "message", + typeof message === "string" ? message : defaultMessage); + notEnumerableProp(this, "name", nameProperty); + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } else { + Error.call(this); + } + } + inherits(SubError, Error); + return SubError; +} + +var _TypeError, _RangeError; +var Warning = subError("Warning", "warning"); +var CancellationError = subError("CancellationError", "cancellation error"); +var TimeoutError = subError("TimeoutError", "timeout error"); +var AggregateError = subError("AggregateError", "aggregate error"); +try { + _TypeError = TypeError; + _RangeError = RangeError; +} catch(e) { + _TypeError = subError("TypeError", "type error"); + _RangeError = subError("RangeError", "range error"); +} + +var methods = ("join pop push shift unshift slice filter forEach some " + + "every map indexOf lastIndexOf reduce reduceRight sort reverse").split(" "); + +for (var i = 0; i < methods.length; ++i) { + if (typeof Array.prototype[methods[i]] === "function") { + AggregateError.prototype[methods[i]] = Array.prototype[methods[i]]; + } +} + +es5.defineProperty(AggregateError.prototype, "length", { + value: 0, + configurable: false, + writable: true, + enumerable: true +}); +AggregateError.prototype["isOperational"] = true; +var level = 0; +AggregateError.prototype.toString = function() { + var indent = Array(level * 4 + 1).join(" "); + var ret = "\n" + indent + "AggregateError of:" + "\n"; + level++; + indent = Array(level * 4 + 1).join(" "); + for (var i = 0; i < this.length; ++i) { + var str = this[i] === this ? "[Circular AggregateError]" : this[i] + ""; + var lines = str.split("\n"); + for (var j = 0; j < lines.length; ++j) { + lines[j] = indent + lines[j]; + } + str = lines.join("\n"); + ret += str + "\n"; + } + level--; + return ret; +}; + +function OperationalError(message) { + if (!(this instanceof OperationalError)) + return new OperationalError(message); + notEnumerableProp(this, "name", "OperationalError"); + notEnumerableProp(this, "message", message); + this.cause = message; + this["isOperational"] = true; + + if (message instanceof Error) { + notEnumerableProp(this, "message", message.message); + notEnumerableProp(this, "stack", message.stack); + } else if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + +} +inherits(OperationalError, Error); + +var errorTypes = Error["__BluebirdErrorTypes__"]; +if (!errorTypes) { + errorTypes = Objectfreeze({ + CancellationError: CancellationError, + TimeoutError: TimeoutError, + OperationalError: OperationalError, + RejectionError: OperationalError, + AggregateError: AggregateError + }); + es5.defineProperty(Error, "__BluebirdErrorTypes__", { + value: errorTypes, + writable: false, + enumerable: false, + configurable: false + }); +} + +module.exports = { + Error: Error, + TypeError: _TypeError, + RangeError: _RangeError, + CancellationError: errorTypes.CancellationError, + OperationalError: errorTypes.OperationalError, + TimeoutError: errorTypes.TimeoutError, + AggregateError: errorTypes.AggregateError, + Warning: Warning +}; + +},{"./es5":60,"./util":83}],60:[function(require,module,exports){ +var isES5 = (function(){ + "use strict"; + return this === undefined; +})(); + +if (isES5) { + module.exports = { + freeze: Object.freeze, + defineProperty: Object.defineProperty, + getDescriptor: Object.getOwnPropertyDescriptor, + keys: Object.keys, + names: Object.getOwnPropertyNames, + getPrototypeOf: Object.getPrototypeOf, + isArray: Array.isArray, + isES5: isES5, + propertyIsWritable: function(obj, prop) { + var descriptor = Object.getOwnPropertyDescriptor(obj, prop); + return !!(!descriptor || descriptor.writable || descriptor.set); + } + }; +} else { + var has = {}.hasOwnProperty; + var str = {}.toString; + var proto = {}.constructor.prototype; + + var ObjectKeys = function (o) { + var ret = []; + for (var key in o) { + if (has.call(o, key)) { + ret.push(key); + } + } + return ret; + }; + + var ObjectGetDescriptor = function(o, key) { + return {value: o[key]}; + }; + + var ObjectDefineProperty = function (o, key, desc) { + o[key] = desc.value; + return o; + }; + + var ObjectFreeze = function (obj) { + return obj; + }; + + var ObjectGetPrototypeOf = function (obj) { + try { + return Object(obj).constructor.prototype; + } + catch (e) { + return proto; + } + }; + + var ArrayIsArray = function (obj) { + try { + return str.call(obj) === "[object Array]"; + } + catch(e) { + return false; + } + }; + + module.exports = { + isArray: ArrayIsArray, + keys: ObjectKeys, + names: ObjectKeys, + defineProperty: ObjectDefineProperty, + getDescriptor: ObjectGetDescriptor, + freeze: ObjectFreeze, + getPrototypeOf: ObjectGetPrototypeOf, + isES5: isES5, + propertyIsWritable: function() { + return true; + } + }; +} + +},{}],61:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var PromiseMap = Promise.map; + +Promise.prototype.filter = function (fn, options) { + return PromiseMap(this, fn, options, INTERNAL); +}; + +Promise.filter = function (promises, fn, options) { + return PromiseMap(promises, fn, options, INTERNAL); +}; +}; + +},{}],62:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, tryConvertToPromise) { +var util = require("./util"); +var CancellationError = Promise.CancellationError; +var errorObj = util.errorObj; + +function PassThroughHandlerContext(promise, type, handler) { + this.promise = promise; + this.type = type; + this.handler = handler; + this.called = false; + this.cancelPromise = null; +} + +PassThroughHandlerContext.prototype.isFinallyHandler = function() { + return this.type === 0; +}; + +function FinallyHandlerCancelReaction(finallyHandler) { + this.finallyHandler = finallyHandler; +} + +FinallyHandlerCancelReaction.prototype._resultCancelled = function() { + checkCancel(this.finallyHandler); +}; + +function checkCancel(ctx, reason) { + if (ctx.cancelPromise != null) { + if (arguments.length > 1) { + ctx.cancelPromise._reject(reason); + } else { + ctx.cancelPromise._cancel(); + } + ctx.cancelPromise = null; + return true; + } + return false; +} + +function succeed() { + return finallyHandler.call(this, this.promise._target()._settledValue()); +} +function fail(reason) { + if (checkCancel(this, reason)) return; + errorObj.e = reason; + return errorObj; +} +function finallyHandler(reasonOrValue) { + var promise = this.promise; + var handler = this.handler; + + if (!this.called) { + this.called = true; + var ret = this.isFinallyHandler() + ? handler.call(promise._boundValue()) + : handler.call(promise._boundValue(), reasonOrValue); + if (ret !== undefined) { + promise._setReturnedNonUndefined(); + var maybePromise = tryConvertToPromise(ret, promise); + if (maybePromise instanceof Promise) { + if (this.cancelPromise != null) { + if (maybePromise._isCancelled()) { + var reason = + new CancellationError("late cancellation observer"); + promise._attachExtraTrace(reason); + errorObj.e = reason; + return errorObj; + } else if (maybePromise.isPending()) { + maybePromise._attachCancellationCallback( + new FinallyHandlerCancelReaction(this)); + } + } + return maybePromise._then( + succeed, fail, undefined, this, undefined); + } + } + } + + if (promise.isRejected()) { + checkCancel(this); + errorObj.e = reasonOrValue; + return errorObj; + } else { + checkCancel(this); + return reasonOrValue; + } +} + +Promise.prototype._passThrough = function(handler, type, success, fail) { + if (typeof handler !== "function") return this.then(); + return this._then(success, + fail, + undefined, + new PassThroughHandlerContext(this, type, handler), + undefined); +}; + +Promise.prototype.lastly = +Promise.prototype["finally"] = function (handler) { + return this._passThrough(handler, + 0, + finallyHandler, + finallyHandler); +}; + +Promise.prototype.tap = function (handler) { + return this._passThrough(handler, 1, finallyHandler); +}; + +return PassThroughHandlerContext; +}; + +},{"./util":83}],63:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, + apiRejection, + INTERNAL, + tryConvertToPromise, + Proxyable, + debug) { +var errors = require("./errors"); +var TypeError = errors.TypeError; +var util = require("./util"); +var errorObj = util.errorObj; +var tryCatch = util.tryCatch; +var yieldHandlers = []; + +function promiseFromYieldHandler(value, yieldHandlers, traceParent) { + for (var i = 0; i < yieldHandlers.length; ++i) { + traceParent._pushContext(); + var result = tryCatch(yieldHandlers[i])(value); + traceParent._popContext(); + if (result === errorObj) { + traceParent._pushContext(); + var ret = Promise.reject(errorObj.e); + traceParent._popContext(); + return ret; + } + var maybePromise = tryConvertToPromise(result, traceParent); + if (maybePromise instanceof Promise) return maybePromise; + } + return null; +} + +function PromiseSpawn(generatorFunction, receiver, yieldHandler, stack) { + if (debug.cancellation()) { + var internal = new Promise(INTERNAL); + var _finallyPromise = this._finallyPromise = new Promise(INTERNAL); + this._promise = internal.lastly(function() { + return _finallyPromise; + }); + internal._captureStackTrace(); + internal._setOnCancel(this); + } else { + var promise = this._promise = new Promise(INTERNAL); + promise._captureStackTrace(); + } + this._stack = stack; + this._generatorFunction = generatorFunction; + this._receiver = receiver; + this._generator = undefined; + this._yieldHandlers = typeof yieldHandler === "function" + ? [yieldHandler].concat(yieldHandlers) + : yieldHandlers; + this._yieldedPromise = null; + this._cancellationPhase = false; +} +util.inherits(PromiseSpawn, Proxyable); + +PromiseSpawn.prototype._isResolved = function() { + return this._promise === null; +}; + +PromiseSpawn.prototype._cleanup = function() { + this._promise = this._generator = null; + if (debug.cancellation() && this._finallyPromise !== null) { + this._finallyPromise._fulfill(); + this._finallyPromise = null; + } +}; + +PromiseSpawn.prototype._promiseCancelled = function() { + if (this._isResolved()) return; + var implementsReturn = typeof this._generator["return"] !== "undefined"; + + var result; + if (!implementsReturn) { + var reason = new Promise.CancellationError( + "generator .return() sentinel"); + Promise.coroutine.returnSentinel = reason; + this._promise._attachExtraTrace(reason); + this._promise._pushContext(); + result = tryCatch(this._generator["throw"]).call(this._generator, + reason); + this._promise._popContext(); + } else { + this._promise._pushContext(); + result = tryCatch(this._generator["return"]).call(this._generator, + undefined); + this._promise._popContext(); + } + this._cancellationPhase = true; + this._yieldedPromise = null; + this._continue(result); +}; + +PromiseSpawn.prototype._promiseFulfilled = function(value) { + this._yieldedPromise = null; + this._promise._pushContext(); + var result = tryCatch(this._generator.next).call(this._generator, value); + this._promise._popContext(); + this._continue(result); +}; + +PromiseSpawn.prototype._promiseRejected = function(reason) { + this._yieldedPromise = null; + this._promise._attachExtraTrace(reason); + this._promise._pushContext(); + var result = tryCatch(this._generator["throw"]) + .call(this._generator, reason); + this._promise._popContext(); + this._continue(result); +}; + +PromiseSpawn.prototype._resultCancelled = function() { + if (this._yieldedPromise instanceof Promise) { + var promise = this._yieldedPromise; + this._yieldedPromise = null; + promise.cancel(); + } +}; + +PromiseSpawn.prototype.promise = function () { + return this._promise; +}; + +PromiseSpawn.prototype._run = function () { + this._generator = this._generatorFunction.call(this._receiver); + this._receiver = + this._generatorFunction = undefined; + this._promiseFulfilled(undefined); +}; + +PromiseSpawn.prototype._continue = function (result) { + var promise = this._promise; + if (result === errorObj) { + this._cleanup(); + if (this._cancellationPhase) { + return promise.cancel(); + } else { + return promise._rejectCallback(result.e, false); + } + } + + var value = result.value; + if (result.done === true) { + this._cleanup(); + if (this._cancellationPhase) { + return promise.cancel(); + } else { + return promise._resolveCallback(value); + } + } else { + var maybePromise = tryConvertToPromise(value, this._promise); + if (!(maybePromise instanceof Promise)) { + maybePromise = + promiseFromYieldHandler(maybePromise, + this._yieldHandlers, + this._promise); + if (maybePromise === null) { + this._promiseRejected( + new TypeError( + "A value %s was yielded that could not be treated as a promise\u000a\u000a See http://goo.gl/MqrFmX\u000a\u000a".replace("%s", value) + + "From coroutine:\u000a" + + this._stack.split("\n").slice(1, -7).join("\n") + ) + ); + return; + } + } + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + ; + if (((bitField & 50397184) === 0)) { + this._yieldedPromise = maybePromise; + maybePromise._proxy(this, null); + } else if (((bitField & 33554432) !== 0)) { + Promise._async.invoke( + this._promiseFulfilled, this, maybePromise._value() + ); + } else if (((bitField & 16777216) !== 0)) { + Promise._async.invoke( + this._promiseRejected, this, maybePromise._reason() + ); + } else { + this._promiseCancelled(); + } + } +}; + +Promise.coroutine = function (generatorFunction, options) { + if (typeof generatorFunction !== "function") { + throw new TypeError("generatorFunction must be a function\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + var yieldHandler = Object(options).yieldHandler; + var PromiseSpawn$ = PromiseSpawn; + var stack = new Error().stack; + return function () { + var generator = generatorFunction.apply(this, arguments); + var spawn = new PromiseSpawn$(undefined, undefined, yieldHandler, + stack); + var ret = spawn.promise(); + spawn._generator = generator; + spawn._promiseFulfilled(undefined); + return ret; + }; +}; + +Promise.coroutine.addYieldHandler = function(fn) { + if (typeof fn !== "function") { + throw new TypeError("expecting a function but got " + util.classString(fn)); + } + yieldHandlers.push(fn); +}; + +Promise.spawn = function (generatorFunction) { + debug.deprecated("Promise.spawn()", "Promise.coroutine()"); + if (typeof generatorFunction !== "function") { + return apiRejection("generatorFunction must be a function\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + var spawn = new PromiseSpawn(generatorFunction, this); + var ret = spawn.promise(); + spawn._run(Promise.spawn); + return ret; +}; +}; + +},{"./errors":59,"./util":83}],64:[function(require,module,exports){ +"use strict"; +module.exports = +function(Promise, PromiseArray, tryConvertToPromise, INTERNAL, async, + getDomain) { +var util = require("./util"); +var canEvaluate = util.canEvaluate; +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var reject; + +if (!false) { +if (canEvaluate) { + var thenCallback = function(i) { + return new Function("value", "holder", " \n\ + 'use strict'; \n\ + holder.pIndex = value; \n\ + holder.checkFulfillment(this); \n\ + ".replace(/Index/g, i)); + }; + + var promiseSetter = function(i) { + return new Function("promise", "holder", " \n\ + 'use strict'; \n\ + holder.pIndex = promise; \n\ + ".replace(/Index/g, i)); + }; + + var generateHolderClass = function(total) { + var props = new Array(total); + for (var i = 0; i < props.length; ++i) { + props[i] = "this.p" + (i+1); + } + var assignment = props.join(" = ") + " = null;"; + var cancellationCode= "var promise;\n" + props.map(function(prop) { + return " \n\ + promise = " + prop + "; \n\ + if (promise instanceof Promise) { \n\ + promise.cancel(); \n\ + } \n\ + "; + }).join("\n"); + var passedArguments = props.join(", "); + var name = "Holder$" + total; + + + var code = "return function(tryCatch, errorObj, Promise, async) { \n\ + 'use strict'; \n\ + function [TheName](fn) { \n\ + [TheProperties] \n\ + this.fn = fn; \n\ + this.asyncNeeded = true; \n\ + this.now = 0; \n\ + } \n\ + \n\ + [TheName].prototype._callFunction = function(promise) { \n\ + promise._pushContext(); \n\ + var ret = tryCatch(this.fn)([ThePassedArguments]); \n\ + promise._popContext(); \n\ + if (ret === errorObj) { \n\ + promise._rejectCallback(ret.e, false); \n\ + } else { \n\ + promise._resolveCallback(ret); \n\ + } \n\ + }; \n\ + \n\ + [TheName].prototype.checkFulfillment = function(promise) { \n\ + var now = ++this.now; \n\ + if (now === [TheTotal]) { \n\ + if (this.asyncNeeded) { \n\ + async.invoke(this._callFunction, this, promise); \n\ + } else { \n\ + this._callFunction(promise); \n\ + } \n\ + \n\ + } \n\ + }; \n\ + \n\ + [TheName].prototype._resultCancelled = function() { \n\ + [CancellationCode] \n\ + }; \n\ + \n\ + return [TheName]; \n\ + }(tryCatch, errorObj, Promise, async); \n\ + "; + + code = code.replace(/\[TheName\]/g, name) + .replace(/\[TheTotal\]/g, total) + .replace(/\[ThePassedArguments\]/g, passedArguments) + .replace(/\[TheProperties\]/g, assignment) + .replace(/\[CancellationCode\]/g, cancellationCode); + + return new Function("tryCatch", "errorObj", "Promise", "async", code) + (tryCatch, errorObj, Promise, async); + }; + + var holderClasses = []; + var thenCallbacks = []; + var promiseSetters = []; + + for (var i = 0; i < 8; ++i) { + holderClasses.push(generateHolderClass(i + 1)); + thenCallbacks.push(thenCallback(i + 1)); + promiseSetters.push(promiseSetter(i + 1)); + } + + reject = function (reason) { + this._reject(reason); + }; +}} + +Promise.join = function () { + var last = arguments.length - 1; + var fn; + if (last > 0 && typeof arguments[last] === "function") { + fn = arguments[last]; + if (!false) { + if (last <= 8 && canEvaluate) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + var HolderClass = holderClasses[last - 1]; + var holder = new HolderClass(fn); + var callbacks = thenCallbacks; + + for (var i = 0; i < last; ++i) { + var maybePromise = tryConvertToPromise(arguments[i], ret); + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + ; + if (((bitField & 50397184) === 0)) { + maybePromise._then(callbacks[i], reject, + undefined, ret, holder); + promiseSetters[i](maybePromise, holder); + holder.asyncNeeded = false; + } else if (((bitField & 33554432) !== 0)) { + callbacks[i].call(ret, + maybePromise._value(), holder); + } else if (((bitField & 16777216) !== 0)) { + ret._reject(maybePromise._reason()); + } else { + ret._cancel(); + } + } else { + callbacks[i].call(ret, maybePromise, holder); + } + } + + if (!ret._isFateSealed()) { + if (holder.asyncNeeded) { + var domain = getDomain(); + if (domain !== null) { + holder.fn = util.domainBind(domain, holder.fn); + } + } + ret._setAsyncGuaranteed(); + ret._setOnCancel(holder); + } + return ret; + } + } + } + var $_len = arguments.length;var args = new Array($_len); for(var $_i = 0; $_i < $_len; ++$_i) {args[$_i] = arguments[$_i];}; + if (fn) args.pop(); + var ret = new PromiseArray(args).promise(); + return fn !== undefined ? ret.spread(fn) : ret; +}; + +}; + +},{"./util":83}],65:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, + PromiseArray, + apiRejection, + tryConvertToPromise, + INTERNAL, + debug) { +var getDomain = Promise._getDomain; +var util = require("./util"); +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; +var async = Promise._async; + +function MappingPromiseArray(promises, fn, limit, _filter) { + this.constructor$(promises); + this._promise._captureStackTrace(); + var domain = getDomain(); + this._callback = domain === null ? fn : util.domainBind(domain, fn); + this._preservedValues = _filter === INTERNAL + ? new Array(this.length()) + : null; + this._limit = limit; + this._inFlight = 0; + this._queue = []; + async.invoke(this._asyncInit, this, undefined); +} +util.inherits(MappingPromiseArray, PromiseArray); + +MappingPromiseArray.prototype._asyncInit = function() { + this._init$(undefined, -2); +}; + +MappingPromiseArray.prototype._init = function () {}; + +MappingPromiseArray.prototype._promiseFulfilled = function (value, index) { + var values = this._values; + var length = this.length(); + var preservedValues = this._preservedValues; + var limit = this._limit; + + if (index < 0) { + index = (index * -1) - 1; + values[index] = value; + if (limit >= 1) { + this._inFlight--; + this._drainQueue(); + if (this._isResolved()) return true; + } + } else { + if (limit >= 1 && this._inFlight >= limit) { + values[index] = value; + this._queue.push(index); + return false; + } + if (preservedValues !== null) preservedValues[index] = value; + + var promise = this._promise; + var callback = this._callback; + var receiver = promise._boundValue(); + promise._pushContext(); + var ret = tryCatch(callback).call(receiver, value, index, length); + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, + promiseCreated, + preservedValues !== null ? "Promise.filter" : "Promise.map", + promise + ); + if (ret === errorObj) { + this._reject(ret.e); + return true; + } + + var maybePromise = tryConvertToPromise(ret, this._promise); + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + var bitField = maybePromise._bitField; + ; + if (((bitField & 50397184) === 0)) { + if (limit >= 1) this._inFlight++; + values[index] = maybePromise; + maybePromise._proxy(this, (index + 1) * -1); + return false; + } else if (((bitField & 33554432) !== 0)) { + ret = maybePromise._value(); + } else if (((bitField & 16777216) !== 0)) { + this._reject(maybePromise._reason()); + return true; + } else { + this._cancel(); + return true; + } + } + values[index] = ret; + } + var totalResolved = ++this._totalResolved; + if (totalResolved >= length) { + if (preservedValues !== null) { + this._filter(values, preservedValues); + } else { + this._resolve(values); + } + return true; + } + return false; +}; + +MappingPromiseArray.prototype._drainQueue = function () { + var queue = this._queue; + var limit = this._limit; + var values = this._values; + while (queue.length > 0 && this._inFlight < limit) { + if (this._isResolved()) return; + var index = queue.pop(); + this._promiseFulfilled(values[index], index); + } +}; + +MappingPromiseArray.prototype._filter = function (booleans, values) { + var len = values.length; + var ret = new Array(len); + var j = 0; + for (var i = 0; i < len; ++i) { + if (booleans[i]) ret[j++] = values[i]; + } + ret.length = j; + this._resolve(ret); +}; + +MappingPromiseArray.prototype.preservedValues = function () { + return this._preservedValues; +}; + +function map(promises, fn, options, _filter) { + if (typeof fn !== "function") { + return apiRejection("expecting a function but got " + util.classString(fn)); + } + + var limit = 0; + if (options !== undefined) { + if (typeof options === "object" && options !== null) { + if (typeof options.concurrency !== "number") { + return Promise.reject( + new TypeError("'concurrency' must be a number but it is " + + util.classString(options.concurrency))); + } + limit = options.concurrency; + } else { + return Promise.reject(new TypeError( + "options argument must be an object but it is " + + util.classString(options))); + } + } + limit = typeof limit === "number" && + isFinite(limit) && limit >= 1 ? limit : 0; + return new MappingPromiseArray(promises, fn, limit, _filter).promise(); +} + +Promise.prototype.map = function (fn, options) { + return map(this, fn, options, null); +}; + +Promise.map = function (promises, fn, options, _filter) { + return map(promises, fn, options, _filter); +}; + + +}; + +},{"./util":83}],66:[function(require,module,exports){ +"use strict"; +module.exports = +function(Promise, INTERNAL, tryConvertToPromise, apiRejection, debug) { +var util = require("./util"); +var tryCatch = util.tryCatch; + +Promise.method = function (fn) { + if (typeof fn !== "function") { + throw new Promise.TypeError("expecting a function but got " + util.classString(fn)); + } + return function () { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._pushContext(); + var value = tryCatch(fn).apply(this, arguments); + var promiseCreated = ret._popContext(); + debug.checkForgottenReturns( + value, promiseCreated, "Promise.method", ret); + ret._resolveFromSyncValue(value); + return ret; + }; +}; + +Promise.attempt = Promise["try"] = function (fn) { + if (typeof fn !== "function") { + return apiRejection("expecting a function but got " + util.classString(fn)); + } + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._pushContext(); + var value; + if (arguments.length > 1) { + debug.deprecated("calling Promise.try with more than 1 argument"); + var arg = arguments[1]; + var ctx = arguments[2]; + value = util.isArray(arg) ? tryCatch(fn).apply(ctx, arg) + : tryCatch(fn).call(ctx, arg); + } else { + value = tryCatch(fn)(); + } + var promiseCreated = ret._popContext(); + debug.checkForgottenReturns( + value, promiseCreated, "Promise.try", ret); + ret._resolveFromSyncValue(value); + return ret; +}; + +Promise.prototype._resolveFromSyncValue = function (value) { + if (value === util.errorObj) { + this._rejectCallback(value.e, false); + } else { + this._resolveCallback(value, true); + } +}; +}; + +},{"./util":83}],67:[function(require,module,exports){ +"use strict"; +var util = require("./util"); +var maybeWrapAsError = util.maybeWrapAsError; +var errors = require("./errors"); +var OperationalError = errors.OperationalError; +var es5 = require("./es5"); + +function isUntypedError(obj) { + return obj instanceof Error && + es5.getPrototypeOf(obj) === Error.prototype; +} + +var rErrorKey = /^(?:name|message|stack|cause)$/; +function wrapAsOperationalError(obj) { + var ret; + if (isUntypedError(obj)) { + ret = new OperationalError(obj); + ret.name = obj.name; + ret.message = obj.message; + ret.stack = obj.stack; + var keys = es5.keys(obj); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (!rErrorKey.test(key)) { + ret[key] = obj[key]; + } + } + return ret; + } + util.markAsOriginatingFromRejection(obj); + return obj; +} + +function nodebackForPromise(promise, multiArgs) { + return function(err, value) { + if (promise === null) return; + if (err) { + var wrapped = wrapAsOperationalError(maybeWrapAsError(err)); + promise._attachExtraTrace(wrapped); + promise._reject(wrapped); + } else if (!multiArgs) { + promise._fulfill(value); + } else { + var $_len = arguments.length;var args = new Array(Math.max($_len - 1, 0)); for(var $_i = 1; $_i < $_len; ++$_i) {args[$_i - 1] = arguments[$_i];}; + promise._fulfill(args); + } + promise = null; + }; +} + +module.exports = nodebackForPromise; + +},{"./errors":59,"./es5":60,"./util":83}],68:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise) { +var util = require("./util"); +var async = Promise._async; +var tryCatch = util.tryCatch; +var errorObj = util.errorObj; + +function spreadAdapter(val, nodeback) { + var promise = this; + if (!util.isArray(val)) return successAdapter.call(promise, val, nodeback); + var ret = + tryCatch(nodeback).apply(promise._boundValue(), [null].concat(val)); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} + +function successAdapter(val, nodeback) { + var promise = this; + var receiver = promise._boundValue(); + var ret = val === undefined + ? tryCatch(nodeback).call(receiver, null) + : tryCatch(nodeback).call(receiver, null, val); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} +function errorAdapter(reason, nodeback) { + var promise = this; + if (!reason) { + var newReason = new Error(reason + ""); + newReason.cause = reason; + reason = newReason; + } + var ret = tryCatch(nodeback).call(promise._boundValue(), reason); + if (ret === errorObj) { + async.throwLater(ret.e); + } +} + +Promise.prototype.asCallback = Promise.prototype.nodeify = function (nodeback, + options) { + if (typeof nodeback == "function") { + var adapter = successAdapter; + if (options !== undefined && Object(options).spread) { + adapter = spreadAdapter; + } + this._then( + adapter, + errorAdapter, + undefined, + this, + nodeback + ); + } + return this; +}; +}; + +},{"./util":83}],69:[function(require,module,exports){ +(function (process){(function (){ +"use strict"; +module.exports = function() { +var makeSelfResolutionError = function () { + return new TypeError("circular promise resolution chain\u000a\u000a See http://goo.gl/MqrFmX\u000a"); +}; +var reflectHandler = function() { + return new Promise.PromiseInspection(this._target()); +}; +var apiRejection = function(msg) { + return Promise.reject(new TypeError(msg)); +}; +function Proxyable() {} +var UNDEFINED_BINDING = {}; +var util = require("./util"); + +var getDomain; +if (util.isNode) { + getDomain = function() { + var ret = process.domain; + if (ret === undefined) ret = null; + return ret; + }; +} else { + getDomain = function() { + return null; + }; +} +util.notEnumerableProp(Promise, "_getDomain", getDomain); + +var es5 = require("./es5"); +var Async = require("./async"); +var async = new Async(); +es5.defineProperty(Promise, "_async", {value: async}); +var errors = require("./errors"); +var TypeError = Promise.TypeError = errors.TypeError; +Promise.RangeError = errors.RangeError; +var CancellationError = Promise.CancellationError = errors.CancellationError; +Promise.TimeoutError = errors.TimeoutError; +Promise.OperationalError = errors.OperationalError; +Promise.RejectionError = errors.OperationalError; +Promise.AggregateError = errors.AggregateError; +var INTERNAL = function(){}; +var APPLY = {}; +var NEXT_FILTER = {}; +var tryConvertToPromise = require("./thenables")(Promise, INTERNAL); +var PromiseArray = + require("./promise_array")(Promise, INTERNAL, + tryConvertToPromise, apiRejection, Proxyable); +var Context = require("./context")(Promise); + /*jshint unused:false*/ +var createContext = Context.create; +var debug = require("./debuggability")(Promise, Context); +var CapturedTrace = debug.CapturedTrace; +var PassThroughHandlerContext = + require("./finally")(Promise, tryConvertToPromise); +var catchFilter = require("./catch_filter")(NEXT_FILTER); +var nodebackForPromise = require("./nodeback"); +var errorObj = util.errorObj; +var tryCatch = util.tryCatch; +function check(self, executor) { + if (typeof executor !== "function") { + throw new TypeError("expecting a function but got " + util.classString(executor)); + } + if (self.constructor !== Promise) { + throw new TypeError("the promise constructor cannot be invoked directly\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } +} + +function Promise(executor) { + this._bitField = 0; + this._fulfillmentHandler0 = undefined; + this._rejectionHandler0 = undefined; + this._promise0 = undefined; + this._receiver0 = undefined; + if (executor !== INTERNAL) { + check(this, executor); + this._resolveFromExecutor(executor); + } + this._promiseCreated(); + this._fireEvent("promiseCreated", this); +} + +Promise.prototype.toString = function () { + return "[object Promise]"; +}; + +Promise.prototype.caught = Promise.prototype["catch"] = function (fn) { + var len = arguments.length; + if (len > 1) { + var catchInstances = new Array(len - 1), + j = 0, i; + for (i = 0; i < len - 1; ++i) { + var item = arguments[i]; + if (util.isObject(item)) { + catchInstances[j++] = item; + } else { + return apiRejection("expecting an object but got " + + "A catch statement predicate " + util.classString(item)); + } + } + catchInstances.length = j; + fn = arguments[i]; + return this.then(undefined, catchFilter(catchInstances, fn, this)); + } + return this.then(undefined, fn); +}; + +Promise.prototype.reflect = function () { + return this._then(reflectHandler, + reflectHandler, undefined, this, undefined); +}; + +Promise.prototype.then = function (didFulfill, didReject) { + if (debug.warnings() && arguments.length > 0 && + typeof didFulfill !== "function" && + typeof didReject !== "function") { + var msg = ".then() only accepts functions but was passed: " + + util.classString(didFulfill); + if (arguments.length > 1) { + msg += ", " + util.classString(didReject); + } + this._warn(msg); + } + return this._then(didFulfill, didReject, undefined, undefined, undefined); +}; + +Promise.prototype.done = function (didFulfill, didReject) { + var promise = + this._then(didFulfill, didReject, undefined, undefined, undefined); + promise._setIsFinal(); +}; + +Promise.prototype.spread = function (fn) { + if (typeof fn !== "function") { + return apiRejection("expecting a function but got " + util.classString(fn)); + } + return this.all()._then(fn, undefined, undefined, APPLY, undefined); +}; + +Promise.prototype.toJSON = function () { + var ret = { + isFulfilled: false, + isRejected: false, + fulfillmentValue: undefined, + rejectionReason: undefined + }; + if (this.isFulfilled()) { + ret.fulfillmentValue = this.value(); + ret.isFulfilled = true; + } else if (this.isRejected()) { + ret.rejectionReason = this.reason(); + ret.isRejected = true; + } + return ret; +}; + +Promise.prototype.all = function () { + if (arguments.length > 0) { + this._warn(".all() was passed arguments but it does not take any"); + } + return new PromiseArray(this).promise(); +}; + +Promise.prototype.error = function (fn) { + return this.caught(util.originatesFromRejection, fn); +}; + +Promise.getNewLibraryCopy = module.exports; + +Promise.is = function (val) { + return val instanceof Promise; +}; + +Promise.fromNode = Promise.fromCallback = function(fn) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + var multiArgs = arguments.length > 1 ? !!Object(arguments[1]).multiArgs + : false; + var result = tryCatch(fn)(nodebackForPromise(ret, multiArgs)); + if (result === errorObj) { + ret._rejectCallback(result.e, true); + } + if (!ret._isFateSealed()) ret._setAsyncGuaranteed(); + return ret; +}; + +Promise.all = function (promises) { + return new PromiseArray(promises).promise(); +}; + +Promise.cast = function (obj) { + var ret = tryConvertToPromise(obj); + if (!(ret instanceof Promise)) { + ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._setFulfilled(); + ret._rejectionHandler0 = obj; + } + return ret; +}; + +Promise.resolve = Promise.fulfilled = Promise.cast; + +Promise.reject = Promise.rejected = function (reason) { + var ret = new Promise(INTERNAL); + ret._captureStackTrace(); + ret._rejectCallback(reason, true); + return ret; +}; + +Promise.setScheduler = function(fn) { + if (typeof fn !== "function") { + throw new TypeError("expecting a function but got " + util.classString(fn)); + } + return async.setScheduler(fn); +}; + +Promise.prototype._then = function ( + didFulfill, + didReject, + _, receiver, + internalData +) { + var haveInternalData = internalData !== undefined; + var promise = haveInternalData ? internalData : new Promise(INTERNAL); + var target = this._target(); + var bitField = target._bitField; + + if (!haveInternalData) { + promise._propagateFrom(this, 3); + promise._captureStackTrace(); + if (receiver === undefined && + ((this._bitField & 2097152) !== 0)) { + if (!((bitField & 50397184) === 0)) { + receiver = this._boundValue(); + } else { + receiver = target === this ? undefined : this._boundTo; + } + } + this._fireEvent("promiseChained", this, promise); + } + + var domain = getDomain(); + if (!((bitField & 50397184) === 0)) { + var handler, value, settler = target._settlePromiseCtx; + if (((bitField & 33554432) !== 0)) { + value = target._rejectionHandler0; + handler = didFulfill; + } else if (((bitField & 16777216) !== 0)) { + value = target._fulfillmentHandler0; + handler = didReject; + target._unsetRejectionIsUnhandled(); + } else { + settler = target._settlePromiseLateCancellationObserver; + value = new CancellationError("late cancellation observer"); + target._attachExtraTrace(value); + handler = didReject; + } + + async.invoke(settler, target, { + handler: domain === null ? handler + : (typeof handler === "function" && + util.domainBind(domain, handler)), + promise: promise, + receiver: receiver, + value: value + }); + } else { + target._addCallbacks(didFulfill, didReject, promise, receiver, domain); + } + + return promise; +}; + +Promise.prototype._length = function () { + return this._bitField & 65535; +}; + +Promise.prototype._isFateSealed = function () { + return (this._bitField & 117506048) !== 0; +}; + +Promise.prototype._isFollowing = function () { + return (this._bitField & 67108864) === 67108864; +}; + +Promise.prototype._setLength = function (len) { + this._bitField = (this._bitField & -65536) | + (len & 65535); +}; + +Promise.prototype._setFulfilled = function () { + this._bitField = this._bitField | 33554432; + this._fireEvent("promiseFulfilled", this); +}; + +Promise.prototype._setRejected = function () { + this._bitField = this._bitField | 16777216; + this._fireEvent("promiseRejected", this); +}; + +Promise.prototype._setFollowing = function () { + this._bitField = this._bitField | 67108864; + this._fireEvent("promiseResolved", this); +}; + +Promise.prototype._setIsFinal = function () { + this._bitField = this._bitField | 4194304; +}; + +Promise.prototype._isFinal = function () { + return (this._bitField & 4194304) > 0; +}; + +Promise.prototype._unsetCancelled = function() { + this._bitField = this._bitField & (~65536); +}; + +Promise.prototype._setCancelled = function() { + this._bitField = this._bitField | 65536; + this._fireEvent("promiseCancelled", this); +}; + +Promise.prototype._setWillBeCancelled = function() { + this._bitField = this._bitField | 8388608; +}; + +Promise.prototype._setAsyncGuaranteed = function() { + if (async.hasCustomScheduler()) return; + this._bitField = this._bitField | 134217728; +}; + +Promise.prototype._receiverAt = function (index) { + var ret = index === 0 ? this._receiver0 : this[ + index * 4 - 4 + 3]; + if (ret === UNDEFINED_BINDING) { + return undefined; + } else if (ret === undefined && this._isBound()) { + return this._boundValue(); + } + return ret; +}; + +Promise.prototype._promiseAt = function (index) { + return this[ + index * 4 - 4 + 2]; +}; + +Promise.prototype._fulfillmentHandlerAt = function (index) { + return this[ + index * 4 - 4 + 0]; +}; + +Promise.prototype._rejectionHandlerAt = function (index) { + return this[ + index * 4 - 4 + 1]; +}; + +Promise.prototype._boundValue = function() {}; + +Promise.prototype._migrateCallback0 = function (follower) { + var bitField = follower._bitField; + var fulfill = follower._fulfillmentHandler0; + var reject = follower._rejectionHandler0; + var promise = follower._promise0; + var receiver = follower._receiverAt(0); + if (receiver === undefined) receiver = UNDEFINED_BINDING; + this._addCallbacks(fulfill, reject, promise, receiver, null); +}; + +Promise.prototype._migrateCallbackAt = function (follower, index) { + var fulfill = follower._fulfillmentHandlerAt(index); + var reject = follower._rejectionHandlerAt(index); + var promise = follower._promiseAt(index); + var receiver = follower._receiverAt(index); + if (receiver === undefined) receiver = UNDEFINED_BINDING; + this._addCallbacks(fulfill, reject, promise, receiver, null); +}; + +Promise.prototype._addCallbacks = function ( + fulfill, + reject, + promise, + receiver, + domain +) { + var index = this._length(); + + if (index >= 65535 - 4) { + index = 0; + this._setLength(0); + } + + if (index === 0) { + this._promise0 = promise; + this._receiver0 = receiver; + if (typeof fulfill === "function") { + this._fulfillmentHandler0 = + domain === null ? fulfill : util.domainBind(domain, fulfill); + } + if (typeof reject === "function") { + this._rejectionHandler0 = + domain === null ? reject : util.domainBind(domain, reject); + } + } else { + var base = index * 4 - 4; + this[base + 2] = promise; + this[base + 3] = receiver; + if (typeof fulfill === "function") { + this[base + 0] = + domain === null ? fulfill : util.domainBind(domain, fulfill); + } + if (typeof reject === "function") { + this[base + 1] = + domain === null ? reject : util.domainBind(domain, reject); + } + } + this._setLength(index + 1); + return index; +}; + +Promise.prototype._proxy = function (proxyable, arg) { + this._addCallbacks(undefined, undefined, arg, proxyable, null); +}; + +Promise.prototype._resolveCallback = function(value, shouldBind) { + if (((this._bitField & 117506048) !== 0)) return; + if (value === this) + return this._rejectCallback(makeSelfResolutionError(), false); + var maybePromise = tryConvertToPromise(value, this); + if (!(maybePromise instanceof Promise)) return this._fulfill(value); + + if (shouldBind) this._propagateFrom(maybePromise, 2); + + var promise = maybePromise._target(); + + if (promise === this) { + this._reject(makeSelfResolutionError()); + return; + } + + var bitField = promise._bitField; + if (((bitField & 50397184) === 0)) { + var len = this._length(); + if (len > 0) promise._migrateCallback0(this); + for (var i = 1; i < len; ++i) { + promise._migrateCallbackAt(this, i); + } + this._setFollowing(); + this._setLength(0); + this._setFollowee(promise); + } else if (((bitField & 33554432) !== 0)) { + this._fulfill(promise._value()); + } else if (((bitField & 16777216) !== 0)) { + this._reject(promise._reason()); + } else { + var reason = new CancellationError("late cancellation observer"); + promise._attachExtraTrace(reason); + this._reject(reason); + } +}; + +Promise.prototype._rejectCallback = +function(reason, synchronous, ignoreNonErrorWarnings) { + var trace = util.ensureErrorObject(reason); + var hasStack = trace === reason; + if (!hasStack && !ignoreNonErrorWarnings && debug.warnings()) { + var message = "a promise was rejected with a non-error: " + + util.classString(reason); + this._warn(message, true); + } + this._attachExtraTrace(trace, synchronous ? hasStack : false); + this._reject(reason); +}; + +Promise.prototype._resolveFromExecutor = function (executor) { + var promise = this; + this._captureStackTrace(); + this._pushContext(); + var synchronous = true; + var r = this._execute(executor, function(value) { + promise._resolveCallback(value); + }, function (reason) { + promise._rejectCallback(reason, synchronous); + }); + synchronous = false; + this._popContext(); + + if (r !== undefined) { + promise._rejectCallback(r, true); + } +}; + +Promise.prototype._settlePromiseFromHandler = function ( + handler, receiver, value, promise +) { + var bitField = promise._bitField; + if (((bitField & 65536) !== 0)) return; + promise._pushContext(); + var x; + if (receiver === APPLY) { + if (!value || typeof value.length !== "number") { + x = errorObj; + x.e = new TypeError("cannot .spread() a non-array: " + + util.classString(value)); + } else { + x = tryCatch(handler).apply(this._boundValue(), value); + } + } else { + x = tryCatch(handler).call(receiver, value); + } + var promiseCreated = promise._popContext(); + bitField = promise._bitField; + if (((bitField & 65536) !== 0)) return; + + if (x === NEXT_FILTER) { + promise._reject(value); + } else if (x === errorObj) { + promise._rejectCallback(x.e, false); + } else { + debug.checkForgottenReturns(x, promiseCreated, "", promise, this); + promise._resolveCallback(x); + } +}; + +Promise.prototype._target = function() { + var ret = this; + while (ret._isFollowing()) ret = ret._followee(); + return ret; +}; + +Promise.prototype._followee = function() { + return this._rejectionHandler0; +}; + +Promise.prototype._setFollowee = function(promise) { + this._rejectionHandler0 = promise; +}; + +Promise.prototype._settlePromise = function(promise, handler, receiver, value) { + var isPromise = promise instanceof Promise; + var bitField = this._bitField; + var asyncGuaranteed = ((bitField & 134217728) !== 0); + if (((bitField & 65536) !== 0)) { + if (isPromise) promise._invokeInternalOnCancel(); + + if (receiver instanceof PassThroughHandlerContext && + receiver.isFinallyHandler()) { + receiver.cancelPromise = promise; + if (tryCatch(handler).call(receiver, value) === errorObj) { + promise._reject(errorObj.e); + } + } else if (handler === reflectHandler) { + promise._fulfill(reflectHandler.call(receiver)); + } else if (receiver instanceof Proxyable) { + receiver._promiseCancelled(promise); + } else if (isPromise || promise instanceof PromiseArray) { + promise._cancel(); + } else { + receiver.cancel(); + } + } else if (typeof handler === "function") { + if (!isPromise) { + handler.call(receiver, value, promise); + } else { + if (asyncGuaranteed) promise._setAsyncGuaranteed(); + this._settlePromiseFromHandler(handler, receiver, value, promise); + } + } else if (receiver instanceof Proxyable) { + if (!receiver._isResolved()) { + if (((bitField & 33554432) !== 0)) { + receiver._promiseFulfilled(value, promise); + } else { + receiver._promiseRejected(value, promise); + } + } + } else if (isPromise) { + if (asyncGuaranteed) promise._setAsyncGuaranteed(); + if (((bitField & 33554432) !== 0)) { + promise._fulfill(value); + } else { + promise._reject(value); + } + } +}; + +Promise.prototype._settlePromiseLateCancellationObserver = function(ctx) { + var handler = ctx.handler; + var promise = ctx.promise; + var receiver = ctx.receiver; + var value = ctx.value; + if (typeof handler === "function") { + if (!(promise instanceof Promise)) { + handler.call(receiver, value, promise); + } else { + this._settlePromiseFromHandler(handler, receiver, value, promise); + } + } else if (promise instanceof Promise) { + promise._reject(value); + } +}; + +Promise.prototype._settlePromiseCtx = function(ctx) { + this._settlePromise(ctx.promise, ctx.handler, ctx.receiver, ctx.value); +}; + +Promise.prototype._settlePromise0 = function(handler, value, bitField) { + var promise = this._promise0; + var receiver = this._receiverAt(0); + this._promise0 = undefined; + this._receiver0 = undefined; + this._settlePromise(promise, handler, receiver, value); +}; + +Promise.prototype._clearCallbackDataAtIndex = function(index) { + var base = index * 4 - 4; + this[base + 2] = + this[base + 3] = + this[base + 0] = + this[base + 1] = undefined; +}; + +Promise.prototype._fulfill = function (value) { + var bitField = this._bitField; + if (((bitField & 117506048) >>> 16)) return; + if (value === this) { + var err = makeSelfResolutionError(); + this._attachExtraTrace(err); + return this._reject(err); + } + this._setFulfilled(); + this._rejectionHandler0 = value; + + if ((bitField & 65535) > 0) { + if (((bitField & 134217728) !== 0)) { + this._settlePromises(); + } else { + async.settlePromises(this); + } + } +}; + +Promise.prototype._reject = function (reason) { + var bitField = this._bitField; + if (((bitField & 117506048) >>> 16)) return; + this._setRejected(); + this._fulfillmentHandler0 = reason; + + if (this._isFinal()) { + return async.fatalError(reason, util.isNode); + } + + if ((bitField & 65535) > 0) { + async.settlePromises(this); + } else { + this._ensurePossibleRejectionHandled(); + } +}; + +Promise.prototype._fulfillPromises = function (len, value) { + for (var i = 1; i < len; i++) { + var handler = this._fulfillmentHandlerAt(i); + var promise = this._promiseAt(i); + var receiver = this._receiverAt(i); + this._clearCallbackDataAtIndex(i); + this._settlePromise(promise, handler, receiver, value); + } +}; + +Promise.prototype._rejectPromises = function (len, reason) { + for (var i = 1; i < len; i++) { + var handler = this._rejectionHandlerAt(i); + var promise = this._promiseAt(i); + var receiver = this._receiverAt(i); + this._clearCallbackDataAtIndex(i); + this._settlePromise(promise, handler, receiver, reason); + } +}; + +Promise.prototype._settlePromises = function () { + var bitField = this._bitField; + var len = (bitField & 65535); + + if (len > 0) { + if (((bitField & 16842752) !== 0)) { + var reason = this._fulfillmentHandler0; + this._settlePromise0(this._rejectionHandler0, reason, bitField); + this._rejectPromises(len, reason); + } else { + var value = this._rejectionHandler0; + this._settlePromise0(this._fulfillmentHandler0, value, bitField); + this._fulfillPromises(len, value); + } + this._setLength(0); + } + this._clearCancellationData(); +}; + +Promise.prototype._settledValue = function() { + var bitField = this._bitField; + if (((bitField & 33554432) !== 0)) { + return this._rejectionHandler0; + } else if (((bitField & 16777216) !== 0)) { + return this._fulfillmentHandler0; + } +}; + +function deferResolve(v) {this.promise._resolveCallback(v);} +function deferReject(v) {this.promise._rejectCallback(v, false);} + +Promise.defer = Promise.pending = function() { + debug.deprecated("Promise.defer", "new Promise"); + var promise = new Promise(INTERNAL); + return { + promise: promise, + resolve: deferResolve, + reject: deferReject + }; +}; + +util.notEnumerableProp(Promise, + "_makeSelfResolutionError", + makeSelfResolutionError); + +require("./method")(Promise, INTERNAL, tryConvertToPromise, apiRejection, + debug); +require("./bind")(Promise, INTERNAL, tryConvertToPromise, debug); +require("./cancel")(Promise, PromiseArray, apiRejection, debug); +require("./direct_resolve")(Promise); +require("./synchronous_inspection")(Promise); +require("./join")( + Promise, PromiseArray, tryConvertToPromise, INTERNAL, async, getDomain); +Promise.Promise = Promise; +Promise.version = "3.4.7"; +require('./map.js')(Promise, PromiseArray, apiRejection, tryConvertToPromise, INTERNAL, debug); +require('./call_get.js')(Promise); +require('./using.js')(Promise, apiRejection, tryConvertToPromise, createContext, INTERNAL, debug); +require('./timers.js')(Promise, INTERNAL, debug); +require('./generators.js')(Promise, apiRejection, INTERNAL, tryConvertToPromise, Proxyable, debug); +require('./nodeify.js')(Promise); +require('./promisify.js')(Promise, INTERNAL); +require('./props.js')(Promise, PromiseArray, tryConvertToPromise, apiRejection); +require('./race.js')(Promise, INTERNAL, tryConvertToPromise, apiRejection); +require('./reduce.js')(Promise, PromiseArray, apiRejection, tryConvertToPromise, INTERNAL, debug); +require('./settle.js')(Promise, PromiseArray, debug); +require('./some.js')(Promise, PromiseArray, apiRejection); +require('./filter.js')(Promise, INTERNAL); +require('./each.js')(Promise, INTERNAL); +require('./any.js')(Promise); + + util.toFastProperties(Promise); + util.toFastProperties(Promise.prototype); + function fillTypes(value) { + var p = new Promise(INTERNAL); + p._fulfillmentHandler0 = value; + p._rejectionHandler0 = value; + p._promise0 = value; + p._receiver0 = value; + } + // Complete slack tracking, opt out of field-type tracking and + // stabilize map + fillTypes({a: 1}); + fillTypes({b: 2}); + fillTypes({c: 3}); + fillTypes(1); + fillTypes(function(){}); + fillTypes(undefined); + fillTypes(false); + fillTypes(new Promise(INTERNAL)); + debug.setBounds(Async.firstLineError, util.lastLineError); + return Promise; + +}; + +}).call(this)}).call(this,require('_process')) +},{"./any.js":49,"./async":50,"./bind":51,"./call_get.js":52,"./cancel":53,"./catch_filter":54,"./context":55,"./debuggability":56,"./direct_resolve":57,"./each.js":58,"./errors":59,"./es5":60,"./filter.js":61,"./finally":62,"./generators.js":63,"./join":64,"./map.js":65,"./method":66,"./nodeback":67,"./nodeify.js":68,"./promise_array":70,"./promisify.js":71,"./props.js":72,"./race.js":74,"./reduce.js":75,"./settle.js":77,"./some.js":78,"./synchronous_inspection":79,"./thenables":80,"./timers.js":81,"./using.js":82,"./util":83,"_process":102}],70:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL, tryConvertToPromise, + apiRejection, Proxyable) { +var util = require("./util"); +var isArray = util.isArray; + +function toResolutionValue(val) { + switch(val) { + case -2: return []; + case -3: return {}; + } +} + +function PromiseArray(values) { + var promise = this._promise = new Promise(INTERNAL); + if (values instanceof Promise) { + promise._propagateFrom(values, 3); + } + promise._setOnCancel(this); + this._values = values; + this._length = 0; + this._totalResolved = 0; + this._init(undefined, -2); +} +util.inherits(PromiseArray, Proxyable); + +PromiseArray.prototype.length = function () { + return this._length; +}; + +PromiseArray.prototype.promise = function () { + return this._promise; +}; + +PromiseArray.prototype._init = function init(_, resolveValueIfEmpty) { + var values = tryConvertToPromise(this._values, this._promise); + if (values instanceof Promise) { + values = values._target(); + var bitField = values._bitField; + ; + this._values = values; + + if (((bitField & 50397184) === 0)) { + this._promise._setAsyncGuaranteed(); + return values._then( + init, + this._reject, + undefined, + this, + resolveValueIfEmpty + ); + } else if (((bitField & 33554432) !== 0)) { + values = values._value(); + } else if (((bitField & 16777216) !== 0)) { + return this._reject(values._reason()); + } else { + return this._cancel(); + } + } + values = util.asArray(values); + if (values === null) { + var err = apiRejection( + "expecting an array or an iterable object but got " + util.classString(values)).reason(); + this._promise._rejectCallback(err, false); + return; + } + + if (values.length === 0) { + if (resolveValueIfEmpty === -5) { + this._resolveEmptyArray(); + } + else { + this._resolve(toResolutionValue(resolveValueIfEmpty)); + } + return; + } + this._iterate(values); +}; + +PromiseArray.prototype._iterate = function(values) { + var len = this.getActualLength(values.length); + this._length = len; + this._values = this.shouldCopyValues() ? new Array(len) : this._values; + var result = this._promise; + var isResolved = false; + var bitField = null; + for (var i = 0; i < len; ++i) { + var maybePromise = tryConvertToPromise(values[i], result); + + if (maybePromise instanceof Promise) { + maybePromise = maybePromise._target(); + bitField = maybePromise._bitField; + } else { + bitField = null; + } + + if (isResolved) { + if (bitField !== null) { + maybePromise.suppressUnhandledRejections(); + } + } else if (bitField !== null) { + if (((bitField & 50397184) === 0)) { + maybePromise._proxy(this, i); + this._values[i] = maybePromise; + } else if (((bitField & 33554432) !== 0)) { + isResolved = this._promiseFulfilled(maybePromise._value(), i); + } else if (((bitField & 16777216) !== 0)) { + isResolved = this._promiseRejected(maybePromise._reason(), i); + } else { + isResolved = this._promiseCancelled(i); + } + } else { + isResolved = this._promiseFulfilled(maybePromise, i); + } + } + if (!isResolved) result._setAsyncGuaranteed(); +}; + +PromiseArray.prototype._isResolved = function () { + return this._values === null; +}; + +PromiseArray.prototype._resolve = function (value) { + this._values = null; + this._promise._fulfill(value); +}; + +PromiseArray.prototype._cancel = function() { + if (this._isResolved() || !this._promise._isCancellable()) return; + this._values = null; + this._promise._cancel(); +}; + +PromiseArray.prototype._reject = function (reason) { + this._values = null; + this._promise._rejectCallback(reason, false); +}; + +PromiseArray.prototype._promiseFulfilled = function (value, index) { + this._values[index] = value; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + this._resolve(this._values); + return true; + } + return false; +}; + +PromiseArray.prototype._promiseCancelled = function() { + this._cancel(); + return true; +}; + +PromiseArray.prototype._promiseRejected = function (reason) { + this._totalResolved++; + this._reject(reason); + return true; +}; + +PromiseArray.prototype._resultCancelled = function() { + if (this._isResolved()) return; + var values = this._values; + this._cancel(); + if (values instanceof Promise) { + values.cancel(); + } else { + for (var i = 0; i < values.length; ++i) { + if (values[i] instanceof Promise) { + values[i].cancel(); + } + } + } +}; + +PromiseArray.prototype.shouldCopyValues = function () { + return true; +}; + +PromiseArray.prototype.getActualLength = function (len) { + return len; +}; + +return PromiseArray; +}; + +},{"./util":83}],71:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var THIS = {}; +var util = require("./util"); +var nodebackForPromise = require("./nodeback"); +var withAppended = util.withAppended; +var maybeWrapAsError = util.maybeWrapAsError; +var canEvaluate = util.canEvaluate; +var TypeError = require("./errors").TypeError; +var defaultSuffix = "Async"; +var defaultPromisified = {__isPromisified__: true}; +var noCopyProps = [ + "arity", "length", + "name", + "arguments", + "caller", + "callee", + "prototype", + "__isPromisified__" +]; +var noCopyPropsPattern = new RegExp("^(?:" + noCopyProps.join("|") + ")$"); + +var defaultFilter = function(name) { + return util.isIdentifier(name) && + name.charAt(0) !== "_" && + name !== "constructor"; +}; + +function propsFilter(key) { + return !noCopyPropsPattern.test(key); +} + +function isPromisified(fn) { + try { + return fn.__isPromisified__ === true; + } + catch (e) { + return false; + } +} + +function hasPromisified(obj, key, suffix) { + var val = util.getDataPropertyOrDefault(obj, key + suffix, + defaultPromisified); + return val ? isPromisified(val) : false; +} +function checkValid(ret, suffix, suffixRegexp) { + for (var i = 0; i < ret.length; i += 2) { + var key = ret[i]; + if (suffixRegexp.test(key)) { + var keyWithoutAsyncSuffix = key.replace(suffixRegexp, ""); + for (var j = 0; j < ret.length; j += 2) { + if (ret[j] === keyWithoutAsyncSuffix) { + throw new TypeError("Cannot promisify an API that has normal methods with '%s'-suffix\u000a\u000a See http://goo.gl/MqrFmX\u000a" + .replace("%s", suffix)); + } + } + } + } +} + +function promisifiableMethods(obj, suffix, suffixRegexp, filter) { + var keys = util.inheritedDataKeys(obj); + var ret = []; + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var value = obj[key]; + var passesDefaultFilter = filter === defaultFilter + ? true : defaultFilter(key, value, obj); + if (typeof value === "function" && + !isPromisified(value) && + !hasPromisified(obj, key, suffix) && + filter(key, value, obj, passesDefaultFilter)) { + ret.push(key, value); + } + } + checkValid(ret, suffix, suffixRegexp); + return ret; +} + +var escapeIdentRegex = function(str) { + return str.replace(/([$])/, "\\$"); +}; + +var makeNodePromisifiedEval; +if (!false) { +var switchCaseArgumentOrder = function(likelyArgumentCount) { + var ret = [likelyArgumentCount]; + var min = Math.max(0, likelyArgumentCount - 1 - 3); + for(var i = likelyArgumentCount - 1; i >= min; --i) { + ret.push(i); + } + for(var i = likelyArgumentCount + 1; i <= 3; ++i) { + ret.push(i); + } + return ret; +}; + +var argumentSequence = function(argumentCount) { + return util.filledRange(argumentCount, "_arg", ""); +}; + +var parameterDeclaration = function(parameterCount) { + return util.filledRange( + Math.max(parameterCount, 3), "_arg", ""); +}; + +var parameterCount = function(fn) { + if (typeof fn.length === "number") { + return Math.max(Math.min(fn.length, 1023 + 1), 0); + } + return 0; +}; + +makeNodePromisifiedEval = +function(callback, receiver, originalName, fn, _, multiArgs) { + var newParameterCount = Math.max(0, parameterCount(fn) - 1); + var argumentOrder = switchCaseArgumentOrder(newParameterCount); + var shouldProxyThis = typeof callback === "string" || receiver === THIS; + + function generateCallForArgumentCount(count) { + var args = argumentSequence(count).join(", "); + var comma = count > 0 ? ", " : ""; + var ret; + if (shouldProxyThis) { + ret = "ret = callback.call(this, {{args}}, nodeback); break;\n"; + } else { + ret = receiver === undefined + ? "ret = callback({{args}}, nodeback); break;\n" + : "ret = callback.call(receiver, {{args}}, nodeback); break;\n"; + } + return ret.replace("{{args}}", args).replace(", ", comma); + } + + function generateArgumentSwitchCase() { + var ret = ""; + for (var i = 0; i < argumentOrder.length; ++i) { + ret += "case " + argumentOrder[i] +":" + + generateCallForArgumentCount(argumentOrder[i]); + } + + ret += " \n\ + default: \n\ + var args = new Array(len + 1); \n\ + var i = 0; \n\ + for (var i = 0; i < len; ++i) { \n\ + args[i] = arguments[i]; \n\ + } \n\ + args[i] = nodeback; \n\ + [CodeForCall] \n\ + break; \n\ + ".replace("[CodeForCall]", (shouldProxyThis + ? "ret = callback.apply(this, args);\n" + : "ret = callback.apply(receiver, args);\n")); + return ret; + } + + var getFunctionCode = typeof callback === "string" + ? ("this != null ? this['"+callback+"'] : fn") + : "fn"; + var body = "'use strict'; \n\ + var ret = function (Parameters) { \n\ + 'use strict'; \n\ + var len = arguments.length; \n\ + var promise = new Promise(INTERNAL); \n\ + promise._captureStackTrace(); \n\ + var nodeback = nodebackForPromise(promise, " + multiArgs + "); \n\ + var ret; \n\ + var callback = tryCatch([GetFunctionCode]); \n\ + switch(len) { \n\ + [CodeForSwitchCase] \n\ + } \n\ + if (ret === errorObj) { \n\ + promise._rejectCallback(maybeWrapAsError(ret.e), true, true);\n\ + } \n\ + if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); \n\ + return promise; \n\ + }; \n\ + notEnumerableProp(ret, '__isPromisified__', true); \n\ + return ret; \n\ + ".replace("[CodeForSwitchCase]", generateArgumentSwitchCase()) + .replace("[GetFunctionCode]", getFunctionCode); + body = body.replace("Parameters", parameterDeclaration(newParameterCount)); + return new Function("Promise", + "fn", + "receiver", + "withAppended", + "maybeWrapAsError", + "nodebackForPromise", + "tryCatch", + "errorObj", + "notEnumerableProp", + "INTERNAL", + body)( + Promise, + fn, + receiver, + withAppended, + maybeWrapAsError, + nodebackForPromise, + util.tryCatch, + util.errorObj, + util.notEnumerableProp, + INTERNAL); +}; +} + +function makeNodePromisifiedClosure(callback, receiver, _, fn, __, multiArgs) { + var defaultThis = (function() {return this;})(); + var method = callback; + if (typeof method === "string") { + callback = fn; + } + function promisified() { + var _receiver = receiver; + if (receiver === THIS) _receiver = this; + var promise = new Promise(INTERNAL); + promise._captureStackTrace(); + var cb = typeof method === "string" && this !== defaultThis + ? this[method] : callback; + var fn = nodebackForPromise(promise, multiArgs); + try { + cb.apply(_receiver, withAppended(arguments, fn)); + } catch(e) { + promise._rejectCallback(maybeWrapAsError(e), true, true); + } + if (!promise._isFateSealed()) promise._setAsyncGuaranteed(); + return promise; + } + util.notEnumerableProp(promisified, "__isPromisified__", true); + return promisified; +} + +var makeNodePromisified = canEvaluate + ? makeNodePromisifiedEval + : makeNodePromisifiedClosure; + +function promisifyAll(obj, suffix, filter, promisifier, multiArgs) { + var suffixRegexp = new RegExp(escapeIdentRegex(suffix) + "$"); + var methods = + promisifiableMethods(obj, suffix, suffixRegexp, filter); + + for (var i = 0, len = methods.length; i < len; i+= 2) { + var key = methods[i]; + var fn = methods[i+1]; + var promisifiedKey = key + suffix; + if (promisifier === makeNodePromisified) { + obj[promisifiedKey] = + makeNodePromisified(key, THIS, key, fn, suffix, multiArgs); + } else { + var promisified = promisifier(fn, function() { + return makeNodePromisified(key, THIS, key, + fn, suffix, multiArgs); + }); + util.notEnumerableProp(promisified, "__isPromisified__", true); + obj[promisifiedKey] = promisified; + } + } + util.toFastProperties(obj); + return obj; +} + +function promisify(callback, receiver, multiArgs) { + return makeNodePromisified(callback, receiver, undefined, + callback, null, multiArgs); +} + +Promise.promisify = function (fn, options) { + if (typeof fn !== "function") { + throw new TypeError("expecting a function but got " + util.classString(fn)); + } + if (isPromisified(fn)) { + return fn; + } + options = Object(options); + var receiver = options.context === undefined ? THIS : options.context; + var multiArgs = !!options.multiArgs; + var ret = promisify(fn, receiver, multiArgs); + util.copyDescriptors(fn, ret, propsFilter); + return ret; +}; + +Promise.promisifyAll = function (target, options) { + if (typeof target !== "function" && typeof target !== "object") { + throw new TypeError("the target of promisifyAll must be an object or a function\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + options = Object(options); + var multiArgs = !!options.multiArgs; + var suffix = options.suffix; + if (typeof suffix !== "string") suffix = defaultSuffix; + var filter = options.filter; + if (typeof filter !== "function") filter = defaultFilter; + var promisifier = options.promisifier; + if (typeof promisifier !== "function") promisifier = makeNodePromisified; + + if (!util.isIdentifier(suffix)) { + throw new RangeError("suffix must be a valid identifier\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + + var keys = util.inheritedDataKeys(target); + for (var i = 0; i < keys.length; ++i) { + var value = target[keys[i]]; + if (keys[i] !== "constructor" && + util.isClass(value)) { + promisifyAll(value.prototype, suffix, filter, promisifier, + multiArgs); + promisifyAll(value, suffix, filter, promisifier, multiArgs); + } + } + + return promisifyAll(target, suffix, filter, promisifier, multiArgs); +}; +}; + + +},{"./errors":59,"./nodeback":67,"./util":83}],72:[function(require,module,exports){ +"use strict"; +module.exports = function( + Promise, PromiseArray, tryConvertToPromise, apiRejection) { +var util = require("./util"); +var isObject = util.isObject; +var es5 = require("./es5"); +var Es6Map; +if (typeof Map === "function") Es6Map = Map; + +var mapToEntries = (function() { + var index = 0; + var size = 0; + + function extractEntry(value, key) { + this[index] = value; + this[index + size] = key; + index++; + } + + return function mapToEntries(map) { + size = map.size; + index = 0; + var ret = new Array(map.size * 2); + map.forEach(extractEntry, ret); + return ret; + }; +})(); + +var entriesToMap = function(entries) { + var ret = new Es6Map(); + var length = entries.length / 2 | 0; + for (var i = 0; i < length; ++i) { + var key = entries[length + i]; + var value = entries[i]; + ret.set(key, value); + } + return ret; +}; + +function PropertiesPromiseArray(obj) { + var isMap = false; + var entries; + if (Es6Map !== undefined && obj instanceof Es6Map) { + entries = mapToEntries(obj); + isMap = true; + } else { + var keys = es5.keys(obj); + var len = keys.length; + entries = new Array(len * 2); + for (var i = 0; i < len; ++i) { + var key = keys[i]; + entries[i] = obj[key]; + entries[i + len] = key; + } + } + this.constructor$(entries); + this._isMap = isMap; + this._init$(undefined, -3); +} +util.inherits(PropertiesPromiseArray, PromiseArray); + +PropertiesPromiseArray.prototype._init = function () {}; + +PropertiesPromiseArray.prototype._promiseFulfilled = function (value, index) { + this._values[index] = value; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + var val; + if (this._isMap) { + val = entriesToMap(this._values); + } else { + val = {}; + var keyOffset = this.length(); + for (var i = 0, len = this.length(); i < len; ++i) { + val[this._values[i + keyOffset]] = this._values[i]; + } + } + this._resolve(val); + return true; + } + return false; +}; + +PropertiesPromiseArray.prototype.shouldCopyValues = function () { + return false; +}; + +PropertiesPromiseArray.prototype.getActualLength = function (len) { + return len >> 1; +}; + +function props(promises) { + var ret; + var castValue = tryConvertToPromise(promises); + + if (!isObject(castValue)) { + return apiRejection("cannot await properties of a non-object\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } else if (castValue instanceof Promise) { + ret = castValue._then( + Promise.props, undefined, undefined, undefined, undefined); + } else { + ret = new PropertiesPromiseArray(castValue).promise(); + } + + if (castValue instanceof Promise) { + ret._propagateFrom(castValue, 2); + } + return ret; +} + +Promise.prototype.props = function () { + return props(this); +}; + +Promise.props = function (promises) { + return props(promises); +}; +}; + +},{"./es5":60,"./util":83}],73:[function(require,module,exports){ +"use strict"; +function arrayMove(src, srcIndex, dst, dstIndex, len) { + for (var j = 0; j < len; ++j) { + dst[j + dstIndex] = src[j + srcIndex]; + src[j + srcIndex] = void 0; + } +} + +function Queue(capacity) { + this._capacity = capacity; + this._length = 0; + this._front = 0; +} + +Queue.prototype._willBeOverCapacity = function (size) { + return this._capacity < size; +}; + +Queue.prototype._pushOne = function (arg) { + var length = this.length(); + this._checkCapacity(length + 1); + var i = (this._front + length) & (this._capacity - 1); + this[i] = arg; + this._length = length + 1; +}; + +Queue.prototype.push = function (fn, receiver, arg) { + var length = this.length() + 3; + if (this._willBeOverCapacity(length)) { + this._pushOne(fn); + this._pushOne(receiver); + this._pushOne(arg); + return; + } + var j = this._front + length - 3; + this._checkCapacity(length); + var wrapMask = this._capacity - 1; + this[(j + 0) & wrapMask] = fn; + this[(j + 1) & wrapMask] = receiver; + this[(j + 2) & wrapMask] = arg; + this._length = length; +}; + +Queue.prototype.shift = function () { + var front = this._front, + ret = this[front]; + + this[front] = undefined; + this._front = (front + 1) & (this._capacity - 1); + this._length--; + return ret; +}; + +Queue.prototype.length = function () { + return this._length; +}; + +Queue.prototype._checkCapacity = function (size) { + if (this._capacity < size) { + this._resizeTo(this._capacity << 1); + } +}; + +Queue.prototype._resizeTo = function (capacity) { + var oldCapacity = this._capacity; + this._capacity = capacity; + var front = this._front; + var length = this._length; + var moveItemsCount = (front + length) & (oldCapacity - 1); + arrayMove(this, 0, this, oldCapacity, moveItemsCount); +}; + +module.exports = Queue; + +},{}],74:[function(require,module,exports){ +"use strict"; +module.exports = function( + Promise, INTERNAL, tryConvertToPromise, apiRejection) { +var util = require("./util"); + +var raceLater = function (promise) { + return promise.then(function(array) { + return race(array, promise); + }); +}; + +function race(promises, parent) { + var maybePromise = tryConvertToPromise(promises); + + if (maybePromise instanceof Promise) { + return raceLater(maybePromise); + } else { + promises = util.asArray(promises); + if (promises === null) + return apiRejection("expecting an array or an iterable object but got " + util.classString(promises)); + } + + var ret = new Promise(INTERNAL); + if (parent !== undefined) { + ret._propagateFrom(parent, 3); + } + var fulfill = ret._fulfill; + var reject = ret._reject; + for (var i = 0, len = promises.length; i < len; ++i) { + var val = promises[i]; + + if (val === undefined && !(i in promises)) { + continue; + } + + Promise.cast(val)._then(fulfill, reject, undefined, ret, null); + } + return ret; +} + +Promise.race = function (promises) { + return race(promises, undefined); +}; + +Promise.prototype.race = function () { + return race(this, undefined); +}; + +}; + +},{"./util":83}],75:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, + PromiseArray, + apiRejection, + tryConvertToPromise, + INTERNAL, + debug) { +var getDomain = Promise._getDomain; +var util = require("./util"); +var tryCatch = util.tryCatch; + +function ReductionPromiseArray(promises, fn, initialValue, _each) { + this.constructor$(promises); + var domain = getDomain(); + this._fn = domain === null ? fn : util.domainBind(domain, fn); + if (initialValue !== undefined) { + initialValue = Promise.resolve(initialValue); + initialValue._attachCancellationCallback(this); + } + this._initialValue = initialValue; + this._currentCancellable = null; + if(_each === INTERNAL) { + this._eachValues = Array(this._length); + } else if (_each === 0) { + this._eachValues = null; + } else { + this._eachValues = undefined; + } + this._promise._captureStackTrace(); + this._init$(undefined, -5); +} +util.inherits(ReductionPromiseArray, PromiseArray); + +ReductionPromiseArray.prototype._gotAccum = function(accum) { + if (this._eachValues !== undefined && + this._eachValues !== null && + accum !== INTERNAL) { + this._eachValues.push(accum); + } +}; + +ReductionPromiseArray.prototype._eachComplete = function(value) { + if (this._eachValues !== null) { + this._eachValues.push(value); + } + return this._eachValues; +}; + +ReductionPromiseArray.prototype._init = function() {}; + +ReductionPromiseArray.prototype._resolveEmptyArray = function() { + this._resolve(this._eachValues !== undefined ? this._eachValues + : this._initialValue); +}; + +ReductionPromiseArray.prototype.shouldCopyValues = function () { + return false; +}; + +ReductionPromiseArray.prototype._resolve = function(value) { + this._promise._resolveCallback(value); + this._values = null; +}; + +ReductionPromiseArray.prototype._resultCancelled = function(sender) { + if (sender === this._initialValue) return this._cancel(); + if (this._isResolved()) return; + this._resultCancelled$(); + if (this._currentCancellable instanceof Promise) { + this._currentCancellable.cancel(); + } + if (this._initialValue instanceof Promise) { + this._initialValue.cancel(); + } +}; + +ReductionPromiseArray.prototype._iterate = function (values) { + this._values = values; + var value; + var i; + var length = values.length; + if (this._initialValue !== undefined) { + value = this._initialValue; + i = 0; + } else { + value = Promise.resolve(values[0]); + i = 1; + } + + this._currentCancellable = value; + + if (!value.isRejected()) { + for (; i < length; ++i) { + var ctx = { + accum: null, + value: values[i], + index: i, + length: length, + array: this + }; + value = value._then(gotAccum, undefined, undefined, ctx, undefined); + } + } + + if (this._eachValues !== undefined) { + value = value + ._then(this._eachComplete, undefined, undefined, this, undefined); + } + value._then(completed, completed, undefined, value, this); +}; + +Promise.prototype.reduce = function (fn, initialValue) { + return reduce(this, fn, initialValue, null); +}; + +Promise.reduce = function (promises, fn, initialValue, _each) { + return reduce(promises, fn, initialValue, _each); +}; + +function completed(valueOrReason, array) { + if (this.isFulfilled()) { + array._resolve(valueOrReason); + } else { + array._reject(valueOrReason); + } +} + +function reduce(promises, fn, initialValue, _each) { + if (typeof fn !== "function") { + return apiRejection("expecting a function but got " + util.classString(fn)); + } + var array = new ReductionPromiseArray(promises, fn, initialValue, _each); + return array.promise(); +} + +function gotAccum(accum) { + this.accum = accum; + this.array._gotAccum(accum); + var value = tryConvertToPromise(this.value, this.array._promise); + if (value instanceof Promise) { + this.array._currentCancellable = value; + return value._then(gotValue, undefined, undefined, this, undefined); + } else { + return gotValue.call(this, value); + } +} + +function gotValue(value) { + var array = this.array; + var promise = array._promise; + var fn = tryCatch(array._fn); + promise._pushContext(); + var ret; + if (array._eachValues !== undefined) { + ret = fn.call(promise._boundValue(), value, this.index, this.length); + } else { + ret = fn.call(promise._boundValue(), + this.accum, value, this.index, this.length); + } + if (ret instanceof Promise) { + array._currentCancellable = ret; + } + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, + promiseCreated, + array._eachValues !== undefined ? "Promise.each" : "Promise.reduce", + promise + ); + return ret; +} +}; + +},{"./util":83}],76:[function(require,module,exports){ +(function (process,global,setImmediate){(function (){ +"use strict"; +var util = require("./util"); +var schedule; +var noAsyncScheduler = function() { + throw new Error("No async scheduler available\u000a\u000a See http://goo.gl/MqrFmX\u000a"); +}; +var NativePromise = util.getNativePromise(); +if (util.isNode && typeof MutationObserver === "undefined") { + var GlobalSetImmediate = global.setImmediate; + var ProcessNextTick = process.nextTick; + schedule = util.isRecentNode + ? function(fn) { GlobalSetImmediate.call(global, fn); } + : function(fn) { ProcessNextTick.call(process, fn); }; +} else if (typeof NativePromise === "function" && + typeof NativePromise.resolve === "function") { + var nativePromise = NativePromise.resolve(); + schedule = function(fn) { + nativePromise.then(fn); + }; +} else if ((typeof MutationObserver !== "undefined") && + !(typeof window !== "undefined" && + window.navigator && + (window.navigator.standalone || window.cordova))) { + schedule = (function() { + var div = document.createElement("div"); + var opts = {attributes: true}; + var toggleScheduled = false; + var div2 = document.createElement("div"); + var o2 = new MutationObserver(function() { + div.classList.toggle("foo"); + toggleScheduled = false; + }); + o2.observe(div2, opts); + + var scheduleToggle = function() { + if (toggleScheduled) return; + toggleScheduled = true; + div2.classList.toggle("foo"); + }; + + return function schedule(fn) { + var o = new MutationObserver(function() { + o.disconnect(); + fn(); + }); + o.observe(div, opts); + scheduleToggle(); + }; + })(); +} else if (typeof setImmediate !== "undefined") { + schedule = function (fn) { + setImmediate(fn); + }; +} else if (typeof setTimeout !== "undefined") { + schedule = function (fn) { + setTimeout(fn, 0); + }; +} else { + schedule = noAsyncScheduler; +} +module.exports = schedule; + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate) +},{"./util":83,"_process":102,"timers":103}],77:[function(require,module,exports){ +"use strict"; +module.exports = + function(Promise, PromiseArray, debug) { +var PromiseInspection = Promise.PromiseInspection; +var util = require("./util"); + +function SettledPromiseArray(values) { + this.constructor$(values); +} +util.inherits(SettledPromiseArray, PromiseArray); + +SettledPromiseArray.prototype._promiseResolved = function (index, inspection) { + this._values[index] = inspection; + var totalResolved = ++this._totalResolved; + if (totalResolved >= this._length) { + this._resolve(this._values); + return true; + } + return false; +}; + +SettledPromiseArray.prototype._promiseFulfilled = function (value, index) { + var ret = new PromiseInspection(); + ret._bitField = 33554432; + ret._settledValueField = value; + return this._promiseResolved(index, ret); +}; +SettledPromiseArray.prototype._promiseRejected = function (reason, index) { + var ret = new PromiseInspection(); + ret._bitField = 16777216; + ret._settledValueField = reason; + return this._promiseResolved(index, ret); +}; + +Promise.settle = function (promises) { + debug.deprecated(".settle()", ".reflect()"); + return new SettledPromiseArray(promises).promise(); +}; + +Promise.prototype.settle = function () { + return Promise.settle(this); +}; +}; + +},{"./util":83}],78:[function(require,module,exports){ +"use strict"; +module.exports = +function(Promise, PromiseArray, apiRejection) { +var util = require("./util"); +var RangeError = require("./errors").RangeError; +var AggregateError = require("./errors").AggregateError; +var isArray = util.isArray; +var CANCELLATION = {}; + + +function SomePromiseArray(values) { + this.constructor$(values); + this._howMany = 0; + this._unwrap = false; + this._initialized = false; +} +util.inherits(SomePromiseArray, PromiseArray); + +SomePromiseArray.prototype._init = function () { + if (!this._initialized) { + return; + } + if (this._howMany === 0) { + this._resolve([]); + return; + } + this._init$(undefined, -5); + var isArrayResolved = isArray(this._values); + if (!this._isResolved() && + isArrayResolved && + this._howMany > this._canPossiblyFulfill()) { + this._reject(this._getRangeError(this.length())); + } +}; + +SomePromiseArray.prototype.init = function () { + this._initialized = true; + this._init(); +}; + +SomePromiseArray.prototype.setUnwrap = function () { + this._unwrap = true; +}; + +SomePromiseArray.prototype.howMany = function () { + return this._howMany; +}; + +SomePromiseArray.prototype.setHowMany = function (count) { + this._howMany = count; +}; + +SomePromiseArray.prototype._promiseFulfilled = function (value) { + this._addFulfilled(value); + if (this._fulfilled() === this.howMany()) { + this._values.length = this.howMany(); + if (this.howMany() === 1 && this._unwrap) { + this._resolve(this._values[0]); + } else { + this._resolve(this._values); + } + return true; + } + return false; + +}; +SomePromiseArray.prototype._promiseRejected = function (reason) { + this._addRejected(reason); + return this._checkOutcome(); +}; + +SomePromiseArray.prototype._promiseCancelled = function () { + if (this._values instanceof Promise || this._values == null) { + return this._cancel(); + } + this._addRejected(CANCELLATION); + return this._checkOutcome(); +}; + +SomePromiseArray.prototype._checkOutcome = function() { + if (this.howMany() > this._canPossiblyFulfill()) { + var e = new AggregateError(); + for (var i = this.length(); i < this._values.length; ++i) { + if (this._values[i] !== CANCELLATION) { + e.push(this._values[i]); + } + } + if (e.length > 0) { + this._reject(e); + } else { + this._cancel(); + } + return true; + } + return false; +}; + +SomePromiseArray.prototype._fulfilled = function () { + return this._totalResolved; +}; + +SomePromiseArray.prototype._rejected = function () { + return this._values.length - this.length(); +}; + +SomePromiseArray.prototype._addRejected = function (reason) { + this._values.push(reason); +}; + +SomePromiseArray.prototype._addFulfilled = function (value) { + this._values[this._totalResolved++] = value; +}; + +SomePromiseArray.prototype._canPossiblyFulfill = function () { + return this.length() - this._rejected(); +}; + +SomePromiseArray.prototype._getRangeError = function (count) { + var message = "Input array must contain at least " + + this._howMany + " items but contains only " + count + " items"; + return new RangeError(message); +}; + +SomePromiseArray.prototype._resolveEmptyArray = function () { + this._reject(this._getRangeError(0)); +}; + +function some(promises, howMany) { + if ((howMany | 0) !== howMany || howMany < 0) { + return apiRejection("expecting a positive integer\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + var ret = new SomePromiseArray(promises); + var promise = ret.promise(); + ret.setHowMany(howMany); + ret.init(); + return promise; +} + +Promise.some = function (promises, howMany) { + return some(promises, howMany); +}; + +Promise.prototype.some = function (howMany) { + return some(this, howMany); +}; + +Promise._SomePromiseArray = SomePromiseArray; +}; + +},{"./errors":59,"./util":83}],79:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise) { +function PromiseInspection(promise) { + if (promise !== undefined) { + promise = promise._target(); + this._bitField = promise._bitField; + this._settledValueField = promise._isFateSealed() + ? promise._settledValue() : undefined; + } + else { + this._bitField = 0; + this._settledValueField = undefined; + } +} + +PromiseInspection.prototype._settledValue = function() { + return this._settledValueField; +}; + +var value = PromiseInspection.prototype.value = function () { + if (!this.isFulfilled()) { + throw new TypeError("cannot get fulfillment value of a non-fulfilled promise\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + return this._settledValue(); +}; + +var reason = PromiseInspection.prototype.error = +PromiseInspection.prototype.reason = function () { + if (!this.isRejected()) { + throw new TypeError("cannot get rejection reason of a non-rejected promise\u000a\u000a See http://goo.gl/MqrFmX\u000a"); + } + return this._settledValue(); +}; + +var isFulfilled = PromiseInspection.prototype.isFulfilled = function() { + return (this._bitField & 33554432) !== 0; +}; + +var isRejected = PromiseInspection.prototype.isRejected = function () { + return (this._bitField & 16777216) !== 0; +}; + +var isPending = PromiseInspection.prototype.isPending = function () { + return (this._bitField & 50397184) === 0; +}; + +var isResolved = PromiseInspection.prototype.isResolved = function () { + return (this._bitField & 50331648) !== 0; +}; + +PromiseInspection.prototype.isCancelled = function() { + return (this._bitField & 8454144) !== 0; +}; + +Promise.prototype.__isCancelled = function() { + return (this._bitField & 65536) === 65536; +}; + +Promise.prototype._isCancelled = function() { + return this._target().__isCancelled(); +}; + +Promise.prototype.isCancelled = function() { + return (this._target()._bitField & 8454144) !== 0; +}; + +Promise.prototype.isPending = function() { + return isPending.call(this._target()); +}; + +Promise.prototype.isRejected = function() { + return isRejected.call(this._target()); +}; + +Promise.prototype.isFulfilled = function() { + return isFulfilled.call(this._target()); +}; + +Promise.prototype.isResolved = function() { + return isResolved.call(this._target()); +}; + +Promise.prototype.value = function() { + return value.call(this._target()); +}; + +Promise.prototype.reason = function() { + var target = this._target(); + target._unsetRejectionIsUnhandled(); + return reason.call(target); +}; + +Promise.prototype._value = function() { + return this._settledValue(); +}; + +Promise.prototype._reason = function() { + this._unsetRejectionIsUnhandled(); + return this._settledValue(); +}; + +Promise.PromiseInspection = PromiseInspection; +}; + +},{}],80:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL) { +var util = require("./util"); +var errorObj = util.errorObj; +var isObject = util.isObject; + +function tryConvertToPromise(obj, context) { + if (isObject(obj)) { + if (obj instanceof Promise) return obj; + var then = getThen(obj); + if (then === errorObj) { + if (context) context._pushContext(); + var ret = Promise.reject(then.e); + if (context) context._popContext(); + return ret; + } else if (typeof then === "function") { + if (isAnyBluebirdPromise(obj)) { + var ret = new Promise(INTERNAL); + obj._then( + ret._fulfill, + ret._reject, + undefined, + ret, + null + ); + return ret; + } + return doThenable(obj, then, context); + } + } + return obj; +} + +function doGetThen(obj) { + return obj.then; +} + +function getThen(obj) { + try { + return doGetThen(obj); + } catch (e) { + errorObj.e = e; + return errorObj; + } +} + +var hasProp = {}.hasOwnProperty; +function isAnyBluebirdPromise(obj) { + try { + return hasProp.call(obj, "_promise0"); + } catch (e) { + return false; + } +} + +function doThenable(x, then, context) { + var promise = new Promise(INTERNAL); + var ret = promise; + if (context) context._pushContext(); + promise._captureStackTrace(); + if (context) context._popContext(); + var synchronous = true; + var result = util.tryCatch(then).call(x, resolve, reject); + synchronous = false; + + if (promise && result === errorObj) { + promise._rejectCallback(result.e, true, true); + promise = null; + } + + function resolve(value) { + if (!promise) return; + promise._resolveCallback(value); + promise = null; + } + + function reject(reason) { + if (!promise) return; + promise._rejectCallback(reason, synchronous, true); + promise = null; + } + return ret; +} + +return tryConvertToPromise; +}; + +},{"./util":83}],81:[function(require,module,exports){ +"use strict"; +module.exports = function(Promise, INTERNAL, debug) { +var util = require("./util"); +var TimeoutError = Promise.TimeoutError; + +function HandleWrapper(handle) { + this.handle = handle; +} + +HandleWrapper.prototype._resultCancelled = function() { + clearTimeout(this.handle); +}; + +var afterValue = function(value) { return delay(+this).thenReturn(value); }; +var delay = Promise.delay = function (ms, value) { + var ret; + var handle; + if (value !== undefined) { + ret = Promise.resolve(value) + ._then(afterValue, null, null, ms, undefined); + if (debug.cancellation() && value instanceof Promise) { + ret._setOnCancel(value); + } + } else { + ret = new Promise(INTERNAL); + handle = setTimeout(function() { ret._fulfill(); }, +ms); + if (debug.cancellation()) { + ret._setOnCancel(new HandleWrapper(handle)); + } + ret._captureStackTrace(); + } + ret._setAsyncGuaranteed(); + return ret; +}; + +Promise.prototype.delay = function (ms) { + return delay(ms, this); +}; + +var afterTimeout = function (promise, message, parent) { + var err; + if (typeof message !== "string") { + if (message instanceof Error) { + err = message; + } else { + err = new TimeoutError("operation timed out"); + } + } else { + err = new TimeoutError(message); + } + util.markAsOriginatingFromRejection(err); + promise._attachExtraTrace(err); + promise._reject(err); + + if (parent != null) { + parent.cancel(); + } +}; + +function successClear(value) { + clearTimeout(this.handle); + return value; +} + +function failureClear(reason) { + clearTimeout(this.handle); + throw reason; +} + +Promise.prototype.timeout = function (ms, message) { + ms = +ms; + var ret, parent; + + var handleWrapper = new HandleWrapper(setTimeout(function timeoutTimeout() { + if (ret.isPending()) { + afterTimeout(ret, message, parent); + } + }, ms)); + + if (debug.cancellation()) { + parent = this.then(); + ret = parent._then(successClear, failureClear, + undefined, handleWrapper, undefined); + ret._setOnCancel(handleWrapper); + } else { + ret = this._then(successClear, failureClear, + undefined, handleWrapper, undefined); + } + + return ret; +}; + +}; + +},{"./util":83}],82:[function(require,module,exports){ +"use strict"; +module.exports = function (Promise, apiRejection, tryConvertToPromise, + createContext, INTERNAL, debug) { + var util = require("./util"); + var TypeError = require("./errors").TypeError; + var inherits = require("./util").inherits; + var errorObj = util.errorObj; + var tryCatch = util.tryCatch; + var NULL = {}; + + function thrower(e) { + setTimeout(function(){throw e;}, 0); + } + + function castPreservingDisposable(thenable) { + var maybePromise = tryConvertToPromise(thenable); + if (maybePromise !== thenable && + typeof thenable._isDisposable === "function" && + typeof thenable._getDisposer === "function" && + thenable._isDisposable()) { + maybePromise._setDisposable(thenable._getDisposer()); + } + return maybePromise; + } + function dispose(resources, inspection) { + var i = 0; + var len = resources.length; + var ret = new Promise(INTERNAL); + function iterator() { + if (i >= len) return ret._fulfill(); + var maybePromise = castPreservingDisposable(resources[i++]); + if (maybePromise instanceof Promise && + maybePromise._isDisposable()) { + try { + maybePromise = tryConvertToPromise( + maybePromise._getDisposer().tryDispose(inspection), + resources.promise); + } catch (e) { + return thrower(e); + } + if (maybePromise instanceof Promise) { + return maybePromise._then(iterator, thrower, + null, null, null); + } + } + iterator(); + } + iterator(); + return ret; + } + + function Disposer(data, promise, context) { + this._data = data; + this._promise = promise; + this._context = context; + } + + Disposer.prototype.data = function () { + return this._data; + }; + + Disposer.prototype.promise = function () { + return this._promise; + }; + + Disposer.prototype.resource = function () { + if (this.promise().isFulfilled()) { + return this.promise().value(); + } + return NULL; + }; + + Disposer.prototype.tryDispose = function(inspection) { + var resource = this.resource(); + var context = this._context; + if (context !== undefined) context._pushContext(); + var ret = resource !== NULL + ? this.doDispose(resource, inspection) : null; + if (context !== undefined) context._popContext(); + this._promise._unsetDisposable(); + this._data = null; + return ret; + }; + + Disposer.isDisposer = function (d) { + return (d != null && + typeof d.resource === "function" && + typeof d.tryDispose === "function"); + }; + + function FunctionDisposer(fn, promise, context) { + this.constructor$(fn, promise, context); + } + inherits(FunctionDisposer, Disposer); + + FunctionDisposer.prototype.doDispose = function (resource, inspection) { + var fn = this.data(); + return fn.call(resource, resource, inspection); + }; + + function maybeUnwrapDisposer(value) { + if (Disposer.isDisposer(value)) { + this.resources[this.index]._setDisposable(value); + return value.promise(); + } + return value; + } + + function ResourceList(length) { + this.length = length; + this.promise = null; + this[length-1] = null; + } + + ResourceList.prototype._resultCancelled = function() { + var len = this.length; + for (var i = 0; i < len; ++i) { + var item = this[i]; + if (item instanceof Promise) { + item.cancel(); + } + } + }; + + Promise.using = function () { + var len = arguments.length; + if (len < 2) return apiRejection( + "you must pass at least 2 arguments to Promise.using"); + var fn = arguments[len - 1]; + if (typeof fn !== "function") { + return apiRejection("expecting a function but got " + util.classString(fn)); + } + var input; + var spreadArgs = true; + if (len === 2 && Array.isArray(arguments[0])) { + input = arguments[0]; + len = input.length; + spreadArgs = false; + } else { + input = arguments; + len--; + } + var resources = new ResourceList(len); + for (var i = 0; i < len; ++i) { + var resource = input[i]; + if (Disposer.isDisposer(resource)) { + var disposer = resource; + resource = resource.promise(); + resource._setDisposable(disposer); + } else { + var maybePromise = tryConvertToPromise(resource); + if (maybePromise instanceof Promise) { + resource = + maybePromise._then(maybeUnwrapDisposer, null, null, { + resources: resources, + index: i + }, undefined); + } + } + resources[i] = resource; + } + + var reflectedResources = new Array(resources.length); + for (var i = 0; i < reflectedResources.length; ++i) { + reflectedResources[i] = Promise.resolve(resources[i]).reflect(); + } + + var resultPromise = Promise.all(reflectedResources) + .then(function(inspections) { + for (var i = 0; i < inspections.length; ++i) { + var inspection = inspections[i]; + if (inspection.isRejected()) { + errorObj.e = inspection.error(); + return errorObj; + } else if (!inspection.isFulfilled()) { + resultPromise.cancel(); + return; + } + inspections[i] = inspection.value(); + } + promise._pushContext(); + + fn = tryCatch(fn); + var ret = spreadArgs + ? fn.apply(undefined, inspections) : fn(inspections); + var promiseCreated = promise._popContext(); + debug.checkForgottenReturns( + ret, promiseCreated, "Promise.using", promise); + return ret; + }); + + var promise = resultPromise.lastly(function() { + var inspection = new Promise.PromiseInspection(resultPromise); + return dispose(resources, inspection); + }); + resources.promise = promise; + promise._setOnCancel(resources); + return promise; + }; + + Promise.prototype._setDisposable = function (disposer) { + this._bitField = this._bitField | 131072; + this._disposer = disposer; + }; + + Promise.prototype._isDisposable = function () { + return (this._bitField & 131072) > 0; + }; + + Promise.prototype._getDisposer = function () { + return this._disposer; + }; + + Promise.prototype._unsetDisposable = function () { + this._bitField = this._bitField & (~131072); + this._disposer = undefined; + }; + + Promise.prototype.disposer = function (fn) { + if (typeof fn === "function") { + return new FunctionDisposer(fn, this, createContext()); + } + throw new TypeError(); + }; + +}; + +},{"./errors":59,"./util":83}],83:[function(require,module,exports){ +(function (process,global){(function (){ +"use strict"; +var es5 = require("./es5"); +var canEvaluate = typeof navigator == "undefined"; + +var errorObj = {e: {}}; +var tryCatchTarget; +var globalObject = typeof self !== "undefined" ? self : + typeof window !== "undefined" ? window : + typeof global !== "undefined" ? global : + this !== undefined ? this : null; + +function tryCatcher() { + try { + var target = tryCatchTarget; + tryCatchTarget = null; + return target.apply(this, arguments); + } catch (e) { + errorObj.e = e; + return errorObj; + } +} +function tryCatch(fn) { + tryCatchTarget = fn; + return tryCatcher; +} + +var inherits = function(Child, Parent) { + var hasProp = {}.hasOwnProperty; + + function T() { + this.constructor = Child; + this.constructor$ = Parent; + for (var propertyName in Parent.prototype) { + if (hasProp.call(Parent.prototype, propertyName) && + propertyName.charAt(propertyName.length-1) !== "$" + ) { + this[propertyName + "$"] = Parent.prototype[propertyName]; + } + } + } + T.prototype = Parent.prototype; + Child.prototype = new T(); + return Child.prototype; +}; + + +function isPrimitive(val) { + return val == null || val === true || val === false || + typeof val === "string" || typeof val === "number"; + +} + +function isObject(value) { + return typeof value === "function" || + typeof value === "object" && value !== null; +} + +function maybeWrapAsError(maybeError) { + if (!isPrimitive(maybeError)) return maybeError; + + return new Error(safeToString(maybeError)); +} + +function withAppended(target, appendee) { + var len = target.length; + var ret = new Array(len + 1); + var i; + for (i = 0; i < len; ++i) { + ret[i] = target[i]; + } + ret[i] = appendee; + return ret; +} + +function getDataPropertyOrDefault(obj, key, defaultValue) { + if (es5.isES5) { + var desc = Object.getOwnPropertyDescriptor(obj, key); + + if (desc != null) { + return desc.get == null && desc.set == null + ? desc.value + : defaultValue; + } + } else { + return {}.hasOwnProperty.call(obj, key) ? obj[key] : undefined; + } +} + +function notEnumerableProp(obj, name, value) { + if (isPrimitive(obj)) return obj; + var descriptor = { + value: value, + configurable: true, + enumerable: false, + writable: true + }; + es5.defineProperty(obj, name, descriptor); + return obj; +} + +function thrower(r) { + throw r; +} + +var inheritedDataKeys = (function() { + var excludedPrototypes = [ + Array.prototype, + Object.prototype, + Function.prototype + ]; + + var isExcludedProto = function(val) { + for (var i = 0; i < excludedPrototypes.length; ++i) { + if (excludedPrototypes[i] === val) { + return true; + } + } + return false; + }; + + if (es5.isES5) { + var getKeys = Object.getOwnPropertyNames; + return function(obj) { + var ret = []; + var visitedKeys = Object.create(null); + while (obj != null && !isExcludedProto(obj)) { + var keys; + try { + keys = getKeys(obj); + } catch (e) { + return ret; + } + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (visitedKeys[key]) continue; + visitedKeys[key] = true; + var desc = Object.getOwnPropertyDescriptor(obj, key); + if (desc != null && desc.get == null && desc.set == null) { + ret.push(key); + } + } + obj = es5.getPrototypeOf(obj); + } + return ret; + }; + } else { + var hasProp = {}.hasOwnProperty; + return function(obj) { + if (isExcludedProto(obj)) return []; + var ret = []; + + /*jshint forin:false */ + enumeration: for (var key in obj) { + if (hasProp.call(obj, key)) { + ret.push(key); + } else { + for (var i = 0; i < excludedPrototypes.length; ++i) { + if (hasProp.call(excludedPrototypes[i], key)) { + continue enumeration; + } + } + ret.push(key); + } + } + return ret; + }; + } + +})(); + +var thisAssignmentPattern = /this\s*\.\s*\S+\s*=/; +function isClass(fn) { + try { + if (typeof fn === "function") { + var keys = es5.names(fn.prototype); + + var hasMethods = es5.isES5 && keys.length > 1; + var hasMethodsOtherThanConstructor = keys.length > 0 && + !(keys.length === 1 && keys[0] === "constructor"); + var hasThisAssignmentAndStaticMethods = + thisAssignmentPattern.test(fn + "") && es5.names(fn).length > 0; + + if (hasMethods || hasMethodsOtherThanConstructor || + hasThisAssignmentAndStaticMethods) { + return true; + } + } + return false; + } catch (e) { + return false; + } +} + +function toFastProperties(obj) { + /*jshint -W027,-W055,-W031*/ + function FakeConstructor() {} + FakeConstructor.prototype = obj; + var l = 8; + while (l--) new FakeConstructor(); + return obj; + eval(obj); +} + +var rident = /^[a-z$_][a-z$_0-9]*$/i; +function isIdentifier(str) { + return rident.test(str); +} + +function filledRange(count, prefix, suffix) { + var ret = new Array(count); + for(var i = 0; i < count; ++i) { + ret[i] = prefix + i + suffix; + } + return ret; +} + +function safeToString(obj) { + try { + return obj + ""; + } catch (e) { + return "[no string representation]"; + } +} + +function isError(obj) { + return obj !== null && + typeof obj === "object" && + typeof obj.message === "string" && + typeof obj.name === "string"; +} + +function markAsOriginatingFromRejection(e) { + try { + notEnumerableProp(e, "isOperational", true); + } + catch(ignore) {} +} + +function originatesFromRejection(e) { + if (e == null) return false; + return ((e instanceof Error["__BluebirdErrorTypes__"].OperationalError) || + e["isOperational"] === true); +} + +function canAttachTrace(obj) { + return isError(obj) && es5.propertyIsWritable(obj, "stack"); +} + +var ensureErrorObject = (function() { + if (!("stack" in new Error())) { + return function(value) { + if (canAttachTrace(value)) return value; + try {throw new Error(safeToString(value));} + catch(err) {return err;} + }; + } else { + return function(value) { + if (canAttachTrace(value)) return value; + return new Error(safeToString(value)); + }; + } +})(); + +function classString(obj) { + return {}.toString.call(obj); +} + +function copyDescriptors(from, to, filter) { + var keys = es5.names(from); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + if (filter(key)) { + try { + es5.defineProperty(to, key, es5.getDescriptor(from, key)); + } catch (ignore) {} + } + } +} + +var asArray = function(v) { + if (es5.isArray(v)) { + return v; + } + return null; +}; + +if (typeof Symbol !== "undefined" && Symbol.iterator) { + var ArrayFrom = typeof Array.from === "function" ? function(v) { + return Array.from(v); + } : function(v) { + var ret = []; + var it = v[Symbol.iterator](); + var itResult; + while (!((itResult = it.next()).done)) { + ret.push(itResult.value); + } + return ret; + }; + + asArray = function(v) { + if (es5.isArray(v)) { + return v; + } else if (v != null && typeof v[Symbol.iterator] === "function") { + return ArrayFrom(v); + } + return null; + }; +} + +var isNode = typeof process !== "undefined" && + classString(process).toLowerCase() === "[object process]"; + +var hasEnvVariables = typeof process !== "undefined" && + typeof process.env !== "undefined"; + +function env(key) { + return hasEnvVariables ? process.env[key] : undefined; +} + +function getNativePromise() { + if (typeof Promise === "function") { + try { + var promise = new Promise(function(){}); + if ({}.toString.call(promise) === "[object Promise]") { + return Promise; + } + } catch (e) {} + } +} + +function domainBind(self, cb) { + return self.bind(cb); +} + +var ret = { + isClass: isClass, + isIdentifier: isIdentifier, + inheritedDataKeys: inheritedDataKeys, + getDataPropertyOrDefault: getDataPropertyOrDefault, + thrower: thrower, + isArray: es5.isArray, + asArray: asArray, + notEnumerableProp: notEnumerableProp, + isPrimitive: isPrimitive, + isObject: isObject, + isError: isError, + canEvaluate: canEvaluate, + errorObj: errorObj, + tryCatch: tryCatch, + inherits: inherits, + withAppended: withAppended, + maybeWrapAsError: maybeWrapAsError, + toFastProperties: toFastProperties, + filledRange: filledRange, + toString: safeToString, + canAttachTrace: canAttachTrace, + ensureErrorObject: ensureErrorObject, + originatesFromRejection: originatesFromRejection, + markAsOriginatingFromRejection: markAsOriginatingFromRejection, + classString: classString, + copyDescriptors: copyDescriptors, + hasDevTools: typeof chrome !== "undefined" && chrome && + typeof chrome.loadTimes === "function", + isNode: isNode, + hasEnvVariables: hasEnvVariables, + env: env, + global: globalObject, + getNativePromise: getNativePromise, + domainBind: domainBind +}; +ret.isRecentNode = ret.isNode && (function() { + var version = process.versions.node.split(".").map(Number); + return (version[0] === 0 && version[1] > 10) || (version[0] > 0); +})(); + +if (ret.isNode) ret.toFastProperties(process); + +try {throw new Error(); } catch (e) {ret.lastLineError = e;} +module.exports = ret; + +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"./es5":60,"_process":102}],84:[function(require,module,exports){ +(function (global,Buffer){(function (){ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh <http://feross.org> + * @license MIT + */ + +'use strict' + +var base64 = require('base64-js') +var ieee754 = require('ieee754') +var isArray = require('isarray') + +exports.Buffer = Buffer +exports.SlowBuffer = SlowBuffer +exports.INSPECT_MAX_BYTES = 50 + +/** + * If `Buffer.TYPED_ARRAY_SUPPORT`: + * === true Use Uint8Array implementation (fastest) + * === false Use Object implementation (most compatible, even IE6) + * + * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, + * Opera 11.6+, iOS 4.2+. + * + * Due to various browser bugs, sometimes the Object implementation will be used even + * when the browser supports typed arrays. + * + * Note: + * + * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances, + * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. + * + * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. + * + * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of + * incorrect length in some situations. + + * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they + * get the Object implementation, which is slower but behaves correctly. + */ +Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined + ? global.TYPED_ARRAY_SUPPORT + : typedArraySupport() + +/* + * Export kMaxLength after typed array support is determined. + */ +exports.kMaxLength = kMaxLength() + +function typedArraySupport () { + try { + var arr = new Uint8Array(1) + arr.__proto__ = {__proto__: Uint8Array.prototype, foo: function () { return 42 }} + return arr.foo() === 42 && // typed array instances can be augmented + typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` + arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray` + } catch (e) { + return false + } +} + +function kMaxLength () { + return Buffer.TYPED_ARRAY_SUPPORT + ? 0x7fffffff + : 0x3fffffff +} + +function createBuffer (that, length) { + if (kMaxLength() < length) { + throw new RangeError('Invalid typed array length') + } + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = new Uint8Array(length) + that.__proto__ = Buffer.prototype + } else { + // Fallback: Return an object instance of the Buffer class + if (that === null) { + that = new Buffer(length) + } + that.length = length + } + + return that +} + +/** + * The Buffer constructor returns instances of `Uint8Array` that have their + * prototype changed to `Buffer.prototype`. Furthermore, `Buffer` is a subclass of + * `Uint8Array`, so the returned instances will have all the node `Buffer` methods + * and the `Uint8Array` methods. Square bracket notation works as expected -- it + * returns a single octet. + * + * The `Uint8Array` prototype remains unmodified. + */ + +function Buffer (arg, encodingOrOffset, length) { + if (!Buffer.TYPED_ARRAY_SUPPORT && !(this instanceof Buffer)) { + return new Buffer(arg, encodingOrOffset, length) + } + + // Common case. + if (typeof arg === 'number') { + if (typeof encodingOrOffset === 'string') { + throw new Error( + 'If encoding is specified then the first argument must be a string' + ) + } + return allocUnsafe(this, arg) + } + return from(this, arg, encodingOrOffset, length) +} + +Buffer.poolSize = 8192 // not used by this implementation + +// TODO: Legacy, not needed anymore. Remove in next major version. +Buffer._augment = function (arr) { + arr.__proto__ = Buffer.prototype + return arr +} + +function from (that, value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('"value" argument must not be a number') + } + + if (typeof ArrayBuffer !== 'undefined' && value instanceof ArrayBuffer) { + return fromArrayBuffer(that, value, encodingOrOffset, length) + } + + if (typeof value === 'string') { + return fromString(that, value, encodingOrOffset) + } + + return fromObject(that, value) +} + +/** + * Functionally equivalent to Buffer(arg, encoding) but throws a TypeError + * if value is a number. + * Buffer.from(str[, encoding]) + * Buffer.from(array) + * Buffer.from(buffer) + * Buffer.from(arrayBuffer[, byteOffset[, length]]) + **/ +Buffer.from = function (value, encodingOrOffset, length) { + return from(null, value, encodingOrOffset, length) +} + +if (Buffer.TYPED_ARRAY_SUPPORT) { + Buffer.prototype.__proto__ = Uint8Array.prototype + Buffer.__proto__ = Uint8Array + if (typeof Symbol !== 'undefined' && Symbol.species && + Buffer[Symbol.species] === Buffer) { + // Fix subarray() in ES2016. See: https://github.com/feross/buffer/pull/97 + Object.defineProperty(Buffer, Symbol.species, { + value: null, + configurable: true + }) + } +} + +function assertSize (size) { + if (typeof size !== 'number') { + throw new TypeError('"size" argument must be a number') + } else if (size < 0) { + throw new RangeError('"size" argument must not be negative') + } +} + +function alloc (that, size, fill, encoding) { + assertSize(size) + if (size <= 0) { + return createBuffer(that, size) + } + if (fill !== undefined) { + // Only pay attention to encoding if it's a string. This + // prevents accidentally sending in a number that would + // be interpretted as a start offset. + return typeof encoding === 'string' + ? createBuffer(that, size).fill(fill, encoding) + : createBuffer(that, size).fill(fill) + } + return createBuffer(that, size) +} + +/** + * Creates a new filled Buffer instance. + * alloc(size[, fill[, encoding]]) + **/ +Buffer.alloc = function (size, fill, encoding) { + return alloc(null, size, fill, encoding) +} + +function allocUnsafe (that, size) { + assertSize(size) + that = createBuffer(that, size < 0 ? 0 : checked(size) | 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) { + for (var i = 0; i < size; ++i) { + that[i] = 0 + } + } + return that +} + +/** + * Equivalent to Buffer(num), by default creates a non-zero-filled Buffer instance. + * */ +Buffer.allocUnsafe = function (size) { + return allocUnsafe(null, size) +} +/** + * Equivalent to SlowBuffer(num), by default creates a non-zero-filled Buffer instance. + */ +Buffer.allocUnsafeSlow = function (size) { + return allocUnsafe(null, size) +} + +function fromString (that, string, encoding) { + if (typeof encoding !== 'string' || encoding === '') { + encoding = 'utf8' + } + + if (!Buffer.isEncoding(encoding)) { + throw new TypeError('"encoding" must be a valid string encoding') + } + + var length = byteLength(string, encoding) | 0 + that = createBuffer(that, length) + + var actual = that.write(string, encoding) + + if (actual !== length) { + // Writing a hex string, for example, that contains invalid characters will + // cause everything after the first invalid character to be ignored. (e.g. + // 'abxxcd' will be treated as 'ab') + that = that.slice(0, actual) + } + + return that +} + +function fromArrayLike (that, array) { + var length = array.length < 0 ? 0 : checked(array.length) | 0 + that = createBuffer(that, length) + for (var i = 0; i < length; i += 1) { + that[i] = array[i] & 255 + } + return that +} + +function fromArrayBuffer (that, array, byteOffset, length) { + array.byteLength // this throws if `array` is not a valid ArrayBuffer + + if (byteOffset < 0 || array.byteLength < byteOffset) { + throw new RangeError('\'offset\' is out of bounds') + } + + if (array.byteLength < byteOffset + (length || 0)) { + throw new RangeError('\'length\' is out of bounds') + } + + if (byteOffset === undefined && length === undefined) { + array = new Uint8Array(array) + } else if (length === undefined) { + array = new Uint8Array(array, byteOffset) + } else { + array = new Uint8Array(array, byteOffset, length) + } + + if (Buffer.TYPED_ARRAY_SUPPORT) { + // Return an augmented `Uint8Array` instance, for best performance + that = array + that.__proto__ = Buffer.prototype + } else { + // Fallback: Return an object instance of the Buffer class + that = fromArrayLike(that, array) + } + return that +} + +function fromObject (that, obj) { + if (Buffer.isBuffer(obj)) { + var len = checked(obj.length) | 0 + that = createBuffer(that, len) + + if (that.length === 0) { + return that + } + + obj.copy(that, 0, 0, len) + return that + } + + if (obj) { + if ((typeof ArrayBuffer !== 'undefined' && + obj.buffer instanceof ArrayBuffer) || 'length' in obj) { + if (typeof obj.length !== 'number' || isnan(obj.length)) { + return createBuffer(that, 0) + } + return fromArrayLike(that, obj) + } + + if (obj.type === 'Buffer' && isArray(obj.data)) { + return fromArrayLike(that, obj.data) + } + } + + throw new TypeError('First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.') +} + +function checked (length) { + // Note: cannot use `length < kMaxLength()` here because that fails when + // length is NaN (which is otherwise coerced to zero.) + if (length >= kMaxLength()) { + throw new RangeError('Attempt to allocate Buffer larger than maximum ' + + 'size: 0x' + kMaxLength().toString(16) + ' bytes') + } + return length | 0 +} + +function SlowBuffer (length) { + if (+length != length) { + length = 0 + } + return Buffer.alloc(+length) +} + +Buffer.isBuffer = function isBuffer (b) { + return !!(b != null && b._isBuffer) +} + +Buffer.compare = function compare (a, b) { + if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + throw new TypeError('Arguments must be Buffers') + } + + if (a === b) return 0 + + var x = a.length + var y = b.length + + for (var i = 0, len = Math.min(x, y); i < len; ++i) { + if (a[i] !== b[i]) { + x = a[i] + y = b[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +Buffer.isEncoding = function isEncoding (encoding) { + switch (String(encoding).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'latin1': + case 'binary': + case 'base64': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return true + default: + return false + } +} + +Buffer.concat = function concat (list, length) { + if (!isArray(list)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + + if (list.length === 0) { + return Buffer.alloc(0) + } + + var i + if (length === undefined) { + length = 0 + for (i = 0; i < list.length; ++i) { + length += list[i].length + } + } + + var buffer = Buffer.allocUnsafe(length) + var pos = 0 + for (i = 0; i < list.length; ++i) { + var buf = list[i] + if (!Buffer.isBuffer(buf)) { + throw new TypeError('"list" argument must be an Array of Buffers') + } + buf.copy(buffer, pos) + pos += buf.length + } + return buffer +} + +function byteLength (string, encoding) { + if (Buffer.isBuffer(string)) { + return string.length + } + if (typeof ArrayBuffer !== 'undefined' && typeof ArrayBuffer.isView === 'function' && + (ArrayBuffer.isView(string) || string instanceof ArrayBuffer)) { + return string.byteLength + } + if (typeof string !== 'string') { + string = '' + string + } + + var len = string.length + if (len === 0) return 0 + + // Use a for loop to avoid recursion + var loweredCase = false + for (;;) { + switch (encoding) { + case 'ascii': + case 'latin1': + case 'binary': + return len + case 'utf8': + case 'utf-8': + case undefined: + return utf8ToBytes(string).length + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return len * 2 + case 'hex': + return len >>> 1 + case 'base64': + return base64ToBytes(string).length + default: + if (loweredCase) return utf8ToBytes(string).length // assume utf8 + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} +Buffer.byteLength = byteLength + +function slowToString (encoding, start, end) { + var loweredCase = false + + // No need to verify that "this.length <= MAX_UINT32" since it's a read-only + // property of a typed array. + + // This behaves neither like String nor Uint8Array in that we set start/end + // to their upper/lower bounds if the value passed is out of range. + // undefined is handled specially as per ECMA-262 6th Edition, + // Section 13.3.3.7 Runtime Semantics: KeyedBindingInitialization. + if (start === undefined || start < 0) { + start = 0 + } + // Return early if start > this.length. Done here to prevent potential uint32 + // coercion fail below. + if (start > this.length) { + return '' + } + + if (end === undefined || end > this.length) { + end = this.length + } + + if (end <= 0) { + return '' + } + + // Force coersion to uint32. This will also coerce falsey/NaN values to 0. + end >>>= 0 + start >>>= 0 + + if (end <= start) { + return '' + } + + if (!encoding) encoding = 'utf8' + + while (true) { + switch (encoding) { + case 'hex': + return hexSlice(this, start, end) + + case 'utf8': + case 'utf-8': + return utf8Slice(this, start, end) + + case 'ascii': + return asciiSlice(this, start, end) + + case 'latin1': + case 'binary': + return latin1Slice(this, start, end) + + case 'base64': + return base64Slice(this, start, end) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return utf16leSlice(this, start, end) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = (encoding + '').toLowerCase() + loweredCase = true + } + } +} + +// The property is used by `Buffer.isBuffer` and `is-buffer` (in Safari 5-7) to detect +// Buffer instances. +Buffer.prototype._isBuffer = true + +function swap (b, n, m) { + var i = b[n] + b[n] = b[m] + b[m] = i +} + +Buffer.prototype.swap16 = function swap16 () { + var len = this.length + if (len % 2 !== 0) { + throw new RangeError('Buffer size must be a multiple of 16-bits') + } + for (var i = 0; i < len; i += 2) { + swap(this, i, i + 1) + } + return this +} + +Buffer.prototype.swap32 = function swap32 () { + var len = this.length + if (len % 4 !== 0) { + throw new RangeError('Buffer size must be a multiple of 32-bits') + } + for (var i = 0; i < len; i += 4) { + swap(this, i, i + 3) + swap(this, i + 1, i + 2) + } + return this +} + +Buffer.prototype.swap64 = function swap64 () { + var len = this.length + if (len % 8 !== 0) { + throw new RangeError('Buffer size must be a multiple of 64-bits') + } + for (var i = 0; i < len; i += 8) { + swap(this, i, i + 7) + swap(this, i + 1, i + 6) + swap(this, i + 2, i + 5) + swap(this, i + 3, i + 4) + } + return this +} + +Buffer.prototype.toString = function toString () { + var length = this.length | 0 + if (length === 0) return '' + if (arguments.length === 0) return utf8Slice(this, 0, length) + return slowToString.apply(this, arguments) +} + +Buffer.prototype.equals = function equals (b) { + if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer') + if (this === b) return true + return Buffer.compare(this, b) === 0 +} + +Buffer.prototype.inspect = function inspect () { + var str = '' + var max = exports.INSPECT_MAX_BYTES + if (this.length > 0) { + str = this.toString('hex', 0, max).match(/.{2}/g).join(' ') + if (this.length > max) str += ' ... ' + } + return '<Buffer ' + str + '>' +} + +Buffer.prototype.compare = function compare (target, start, end, thisStart, thisEnd) { + if (!Buffer.isBuffer(target)) { + throw new TypeError('Argument must be a Buffer') + } + + if (start === undefined) { + start = 0 + } + if (end === undefined) { + end = target ? target.length : 0 + } + if (thisStart === undefined) { + thisStart = 0 + } + if (thisEnd === undefined) { + thisEnd = this.length + } + + if (start < 0 || end > target.length || thisStart < 0 || thisEnd > this.length) { + throw new RangeError('out of range index') + } + + if (thisStart >= thisEnd && start >= end) { + return 0 + } + if (thisStart >= thisEnd) { + return -1 + } + if (start >= end) { + return 1 + } + + start >>>= 0 + end >>>= 0 + thisStart >>>= 0 + thisEnd >>>= 0 + + if (this === target) return 0 + + var x = thisEnd - thisStart + var y = end - start + var len = Math.min(x, y) + + var thisCopy = this.slice(thisStart, thisEnd) + var targetCopy = target.slice(start, end) + + for (var i = 0; i < len; ++i) { + if (thisCopy[i] !== targetCopy[i]) { + x = thisCopy[i] + y = targetCopy[i] + break + } + } + + if (x < y) return -1 + if (y < x) return 1 + return 0 +} + +// Finds either the first index of `val` in `buffer` at offset >= `byteOffset`, +// OR the last index of `val` in `buffer` at offset <= `byteOffset`. +// +// Arguments: +// - buffer - a Buffer to search +// - val - a string, Buffer, or number +// - byteOffset - an index into `buffer`; will be clamped to an int32 +// - encoding - an optional encoding, relevant is val is a string +// - dir - true for indexOf, false for lastIndexOf +function bidirectionalIndexOf (buffer, val, byteOffset, encoding, dir) { + // Empty buffer means no match + if (buffer.length === 0) return -1 + + // Normalize byteOffset + if (typeof byteOffset === 'string') { + encoding = byteOffset + byteOffset = 0 + } else if (byteOffset > 0x7fffffff) { + byteOffset = 0x7fffffff + } else if (byteOffset < -0x80000000) { + byteOffset = -0x80000000 + } + byteOffset = +byteOffset // Coerce to Number. + if (isNaN(byteOffset)) { + // byteOffset: it it's undefined, null, NaN, "foo", etc, search whole buffer + byteOffset = dir ? 0 : (buffer.length - 1) + } + + // Normalize byteOffset: negative offsets start from the end of the buffer + if (byteOffset < 0) byteOffset = buffer.length + byteOffset + if (byteOffset >= buffer.length) { + if (dir) return -1 + else byteOffset = buffer.length - 1 + } else if (byteOffset < 0) { + if (dir) byteOffset = 0 + else return -1 + } + + // Normalize val + if (typeof val === 'string') { + val = Buffer.from(val, encoding) + } + + // Finally, search either indexOf (if dir is true) or lastIndexOf + if (Buffer.isBuffer(val)) { + // Special case: looking for empty string/buffer always fails + if (val.length === 0) { + return -1 + } + return arrayIndexOf(buffer, val, byteOffset, encoding, dir) + } else if (typeof val === 'number') { + val = val & 0xFF // Search for a byte value [0-255] + if (Buffer.TYPED_ARRAY_SUPPORT && + typeof Uint8Array.prototype.indexOf === 'function') { + if (dir) { + return Uint8Array.prototype.indexOf.call(buffer, val, byteOffset) + } else { + return Uint8Array.prototype.lastIndexOf.call(buffer, val, byteOffset) + } + } + return arrayIndexOf(buffer, [ val ], byteOffset, encoding, dir) + } + + throw new TypeError('val must be string, number or Buffer') +} + +function arrayIndexOf (arr, val, byteOffset, encoding, dir) { + var indexSize = 1 + var arrLength = arr.length + var valLength = val.length + + if (encoding !== undefined) { + encoding = String(encoding).toLowerCase() + if (encoding === 'ucs2' || encoding === 'ucs-2' || + encoding === 'utf16le' || encoding === 'utf-16le') { + if (arr.length < 2 || val.length < 2) { + return -1 + } + indexSize = 2 + arrLength /= 2 + valLength /= 2 + byteOffset /= 2 + } + } + + function read (buf, i) { + if (indexSize === 1) { + return buf[i] + } else { + return buf.readUInt16BE(i * indexSize) + } + } + + var i + if (dir) { + var foundIndex = -1 + for (i = byteOffset; i < arrLength; i++) { + if (read(arr, i) === read(val, foundIndex === -1 ? 0 : i - foundIndex)) { + if (foundIndex === -1) foundIndex = i + if (i - foundIndex + 1 === valLength) return foundIndex * indexSize + } else { + if (foundIndex !== -1) i -= i - foundIndex + foundIndex = -1 + } + } + } else { + if (byteOffset + valLength > arrLength) byteOffset = arrLength - valLength + for (i = byteOffset; i >= 0; i--) { + var found = true + for (var j = 0; j < valLength; j++) { + if (read(arr, i + j) !== read(val, j)) { + found = false + break + } + } + if (found) return i + } + } + + return -1 +} + +Buffer.prototype.includes = function includes (val, byteOffset, encoding) { + return this.indexOf(val, byteOffset, encoding) !== -1 +} + +Buffer.prototype.indexOf = function indexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, true) +} + +Buffer.prototype.lastIndexOf = function lastIndexOf (val, byteOffset, encoding) { + return bidirectionalIndexOf(this, val, byteOffset, encoding, false) +} + +function hexWrite (buf, string, offset, length) { + offset = Number(offset) || 0 + var remaining = buf.length - offset + if (!length) { + length = remaining + } else { + length = Number(length) + if (length > remaining) { + length = remaining + } + } + + // must be an even number of digits + var strLen = string.length + if (strLen % 2 !== 0) throw new TypeError('Invalid hex string') + + if (length > strLen / 2) { + length = strLen / 2 + } + for (var i = 0; i < length; ++i) { + var parsed = parseInt(string.substr(i * 2, 2), 16) + if (isNaN(parsed)) return i + buf[offset + i] = parsed + } + return i +} + +function utf8Write (buf, string, offset, length) { + return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length) +} + +function asciiWrite (buf, string, offset, length) { + return blitBuffer(asciiToBytes(string), buf, offset, length) +} + +function latin1Write (buf, string, offset, length) { + return asciiWrite(buf, string, offset, length) +} + +function base64Write (buf, string, offset, length) { + return blitBuffer(base64ToBytes(string), buf, offset, length) +} + +function ucs2Write (buf, string, offset, length) { + return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length) +} + +Buffer.prototype.write = function write (string, offset, length, encoding) { + // Buffer#write(string) + if (offset === undefined) { + encoding = 'utf8' + length = this.length + offset = 0 + // Buffer#write(string, encoding) + } else if (length === undefined && typeof offset === 'string') { + encoding = offset + length = this.length + offset = 0 + // Buffer#write(string, offset[, length][, encoding]) + } else if (isFinite(offset)) { + offset = offset | 0 + if (isFinite(length)) { + length = length | 0 + if (encoding === undefined) encoding = 'utf8' + } else { + encoding = length + length = undefined + } + // legacy write(string, encoding, offset, length) - remove in v0.13 + } else { + throw new Error( + 'Buffer.write(string, encoding, offset[, length]) is no longer supported' + ) + } + + var remaining = this.length - offset + if (length === undefined || length > remaining) length = remaining + + if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) { + throw new RangeError('Attempt to write outside buffer bounds') + } + + if (!encoding) encoding = 'utf8' + + var loweredCase = false + for (;;) { + switch (encoding) { + case 'hex': + return hexWrite(this, string, offset, length) + + case 'utf8': + case 'utf-8': + return utf8Write(this, string, offset, length) + + case 'ascii': + return asciiWrite(this, string, offset, length) + + case 'latin1': + case 'binary': + return latin1Write(this, string, offset, length) + + case 'base64': + // Warning: maxLength not taken into account in base64Write + return base64Write(this, string, offset, length) + + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return ucs2Write(this, string, offset, length) + + default: + if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding) + encoding = ('' + encoding).toLowerCase() + loweredCase = true + } + } +} + +Buffer.prototype.toJSON = function toJSON () { + return { + type: 'Buffer', + data: Array.prototype.slice.call(this._arr || this, 0) + } +} + +function base64Slice (buf, start, end) { + if (start === 0 && end === buf.length) { + return base64.fromByteArray(buf) + } else { + return base64.fromByteArray(buf.slice(start, end)) + } +} + +function utf8Slice (buf, start, end) { + end = Math.min(buf.length, end) + var res = [] + + var i = start + while (i < end) { + var firstByte = buf[i] + var codePoint = null + var bytesPerSequence = (firstByte > 0xEF) ? 4 + : (firstByte > 0xDF) ? 3 + : (firstByte > 0xBF) ? 2 + : 1 + + if (i + bytesPerSequence <= end) { + var secondByte, thirdByte, fourthByte, tempCodePoint + + switch (bytesPerSequence) { + case 1: + if (firstByte < 0x80) { + codePoint = firstByte + } + break + case 2: + secondByte = buf[i + 1] + if ((secondByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F) + if (tempCodePoint > 0x7F) { + codePoint = tempCodePoint + } + } + break + case 3: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F) + if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) { + codePoint = tempCodePoint + } + } + break + case 4: + secondByte = buf[i + 1] + thirdByte = buf[i + 2] + fourthByte = buf[i + 3] + if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) { + tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F) + if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) { + codePoint = tempCodePoint + } + } + } + } + + if (codePoint === null) { + // we did not generate a valid codePoint so insert a + // replacement char (U+FFFD) and advance only 1 byte + codePoint = 0xFFFD + bytesPerSequence = 1 + } else if (codePoint > 0xFFFF) { + // encode to utf16 (surrogate pair dance) + codePoint -= 0x10000 + res.push(codePoint >>> 10 & 0x3FF | 0xD800) + codePoint = 0xDC00 | codePoint & 0x3FF + } + + res.push(codePoint) + i += bytesPerSequence + } + + return decodeCodePointsArray(res) +} + +// Based on http://stackoverflow.com/a/22747272/680742, the browser with +// the lowest limit is Chrome, with 0x10000 args. +// We go 1 magnitude less, for safety +var MAX_ARGUMENTS_LENGTH = 0x1000 + +function decodeCodePointsArray (codePoints) { + var len = codePoints.length + if (len <= MAX_ARGUMENTS_LENGTH) { + return String.fromCharCode.apply(String, codePoints) // avoid extra slice() + } + + // Decode in chunks to avoid "call stack size exceeded". + var res = '' + var i = 0 + while (i < len) { + res += String.fromCharCode.apply( + String, + codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH) + ) + } + return res +} + +function asciiSlice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i] & 0x7F) + } + return ret +} + +function latin1Slice (buf, start, end) { + var ret = '' + end = Math.min(buf.length, end) + + for (var i = start; i < end; ++i) { + ret += String.fromCharCode(buf[i]) + } + return ret +} + +function hexSlice (buf, start, end) { + var len = buf.length + + if (!start || start < 0) start = 0 + if (!end || end < 0 || end > len) end = len + + var out = '' + for (var i = start; i < end; ++i) { + out += toHex(buf[i]) + } + return out +} + +function utf16leSlice (buf, start, end) { + var bytes = buf.slice(start, end) + var res = '' + for (var i = 0; i < bytes.length; i += 2) { + res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256) + } + return res +} + +Buffer.prototype.slice = function slice (start, end) { + var len = this.length + start = ~~start + end = end === undefined ? len : ~~end + + if (start < 0) { + start += len + if (start < 0) start = 0 + } else if (start > len) { + start = len + } + + if (end < 0) { + end += len + if (end < 0) end = 0 + } else if (end > len) { + end = len + } + + if (end < start) end = start + + var newBuf + if (Buffer.TYPED_ARRAY_SUPPORT) { + newBuf = this.subarray(start, end) + newBuf.__proto__ = Buffer.prototype + } else { + var sliceLen = end - start + newBuf = new Buffer(sliceLen, undefined) + for (var i = 0; i < sliceLen; ++i) { + newBuf[i] = this[i + start] + } + } + + return newBuf +} + +/* + * Need to make sure that buffer isn't trying to write out of bounds. + */ +function checkOffset (offset, ext, length) { + if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint') + if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length') +} + +Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + + return val +} + +Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + checkOffset(offset, byteLength, this.length) + } + + var val = this[offset + --byteLength] + var mul = 1 + while (byteLength > 0 && (mul *= 0x100)) { + val += this[offset + --byteLength] * mul + } + + return val +} + +Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + return this[offset] +} + +Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return this[offset] | (this[offset + 1] << 8) +} + +Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + return (this[offset] << 8) | this[offset + 1] +} + +Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return ((this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16)) + + (this[offset + 3] * 0x1000000) +} + +Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] * 0x1000000) + + ((this[offset + 1] << 16) | + (this[offset + 2] << 8) | + this[offset + 3]) +} + +Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var val = this[offset] + var mul = 1 + var i = 0 + while (++i < byteLength && (mul *= 0x100)) { + val += this[offset + i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) { + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) checkOffset(offset, byteLength, this.length) + + var i = byteLength + var mul = 1 + var val = this[offset + --i] + while (i > 0 && (mul *= 0x100)) { + val += this[offset + --i] * mul + } + mul *= 0x80 + + if (val >= mul) val -= Math.pow(2, 8 * byteLength) + + return val +} + +Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) { + if (!noAssert) checkOffset(offset, 1, this.length) + if (!(this[offset] & 0x80)) return (this[offset]) + return ((0xff - this[offset] + 1) * -1) +} + +Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset] | (this[offset + 1] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 2, this.length) + var val = this[offset + 1] | (this[offset] << 8) + return (val & 0x8000) ? val | 0xFFFF0000 : val +} + +Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset]) | + (this[offset + 1] << 8) | + (this[offset + 2] << 16) | + (this[offset + 3] << 24) +} + +Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + + return (this[offset] << 24) | + (this[offset + 1] << 16) | + (this[offset + 2] << 8) | + (this[offset + 3]) +} + +Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, true, 23, 4) +} + +Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 4, this.length) + return ieee754.read(this, offset, false, 23, 4) +} + +Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, true, 52, 8) +} + +Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) { + if (!noAssert) checkOffset(offset, 8, this.length) + return ieee754.read(this, offset, false, 52, 8) +} + +function checkInt (buf, value, offset, ext, max, min) { + if (!Buffer.isBuffer(buf)) throw new TypeError('"buffer" argument must be a Buffer instance') + if (value > max || value < min) throw new RangeError('"value" argument is out of bounds') + if (offset + ext > buf.length) throw new RangeError('Index out of range') +} + +Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var mul = 1 + var i = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + byteLength = byteLength | 0 + if (!noAssert) { + var maxBytes = Math.pow(2, 8 * byteLength) - 1 + checkInt(this, value, offset, byteLength, maxBytes, 0) + } + + var i = byteLength - 1 + var mul = 1 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + this[offset + i] = (value / mul) & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + this[offset] = (value & 0xff) + return offset + 1 +} + +function objectWriteUInt16 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; ++i) { + buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> + (littleEndian ? i : 1 - i) * 8 + } +} + +Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +function objectWriteUInt32 (buf, value, offset, littleEndian) { + if (value < 0) value = 0xffffffff + value + 1 + for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; ++i) { + buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff + } +} + +Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset + 3] = (value >>> 24) + this[offset + 2] = (value >>> 16) + this[offset + 1] = (value >>> 8) + this[offset] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = 0 + var mul = 1 + var sub = 0 + this[offset] = value & 0xFF + while (++i < byteLength && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i - 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) { + var limit = Math.pow(2, 8 * byteLength - 1) + + checkInt(this, value, offset, byteLength, limit - 1, -limit) + } + + var i = byteLength - 1 + var mul = 1 + var sub = 0 + this[offset + i] = value & 0xFF + while (--i >= 0 && (mul *= 0x100)) { + if (value < 0 && sub === 0 && this[offset + i + 1] !== 0) { + sub = 1 + } + this[offset + i] = ((value / mul) >> 0) - sub & 0xFF + } + + return offset + byteLength +} + +Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80) + if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value) + if (value < 0) value = 0xff + value + 1 + this[offset] = (value & 0xff) + return offset + 1 +} + +Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + } else { + objectWriteUInt16(this, value, offset, true) + } + return offset + 2 +} + +Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 8) + this[offset + 1] = (value & 0xff) + } else { + objectWriteUInt16(this, value, offset, false) + } + return offset + 2 +} + +Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value & 0xff) + this[offset + 1] = (value >>> 8) + this[offset + 2] = (value >>> 16) + this[offset + 3] = (value >>> 24) + } else { + objectWriteUInt32(this, value, offset, true) + } + return offset + 4 +} + +Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) { + value = +value + offset = offset | 0 + if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000) + if (value < 0) value = 0xffffffff + value + 1 + if (Buffer.TYPED_ARRAY_SUPPORT) { + this[offset] = (value >>> 24) + this[offset + 1] = (value >>> 16) + this[offset + 2] = (value >>> 8) + this[offset + 3] = (value & 0xff) + } else { + objectWriteUInt32(this, value, offset, false) + } + return offset + 4 +} + +function checkIEEE754 (buf, value, offset, ext, max, min) { + if (offset + ext > buf.length) throw new RangeError('Index out of range') + if (offset < 0) throw new RangeError('Index out of range') +} + +function writeFloat (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38) + } + ieee754.write(buf, value, offset, littleEndian, 23, 4) + return offset + 4 +} + +Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) { + return writeFloat(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) { + return writeFloat(this, value, offset, false, noAssert) +} + +function writeDouble (buf, value, offset, littleEndian, noAssert) { + if (!noAssert) { + checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308) + } + ieee754.write(buf, value, offset, littleEndian, 52, 8) + return offset + 8 +} + +Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) { + return writeDouble(this, value, offset, true, noAssert) +} + +Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) { + return writeDouble(this, value, offset, false, noAssert) +} + +// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) +Buffer.prototype.copy = function copy (target, targetStart, start, end) { + if (!start) start = 0 + if (!end && end !== 0) end = this.length + if (targetStart >= target.length) targetStart = target.length + if (!targetStart) targetStart = 0 + if (end > 0 && end < start) end = start + + // Copy 0 bytes; we're done + if (end === start) return 0 + if (target.length === 0 || this.length === 0) return 0 + + // Fatal error conditions + if (targetStart < 0) { + throw new RangeError('targetStart out of bounds') + } + if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds') + if (end < 0) throw new RangeError('sourceEnd out of bounds') + + // Are we oob? + if (end > this.length) end = this.length + if (target.length - targetStart < end - start) { + end = target.length - targetStart + start + } + + var len = end - start + var i + + if (this === target && start < targetStart && targetStart < end) { + // descending copy from end + for (i = len - 1; i >= 0; --i) { + target[i + targetStart] = this[i + start] + } + } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { + // ascending copy from start + for (i = 0; i < len; ++i) { + target[i + targetStart] = this[i + start] + } + } else { + Uint8Array.prototype.set.call( + target, + this.subarray(start, start + len), + targetStart + ) + } + + return len +} + +// Usage: +// buffer.fill(number[, offset[, end]]) +// buffer.fill(buffer[, offset[, end]]) +// buffer.fill(string[, offset[, end]][, encoding]) +Buffer.prototype.fill = function fill (val, start, end, encoding) { + // Handle string cases: + if (typeof val === 'string') { + if (typeof start === 'string') { + encoding = start + start = 0 + end = this.length + } else if (typeof end === 'string') { + encoding = end + end = this.length + } + if (val.length === 1) { + var code = val.charCodeAt(0) + if (code < 256) { + val = code + } + } + if (encoding !== undefined && typeof encoding !== 'string') { + throw new TypeError('encoding must be a string') + } + if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { + throw new TypeError('Unknown encoding: ' + encoding) + } + } else if (typeof val === 'number') { + val = val & 255 + } + + // Invalid ranges are not set to a default, so can range check early. + if (start < 0 || this.length < start || this.length < end) { + throw new RangeError('Out of range index') + } + + if (end <= start) { + return this + } + + start = start >>> 0 + end = end === undefined ? this.length : end >>> 0 + + if (!val) val = 0 + + var i + if (typeof val === 'number') { + for (i = start; i < end; ++i) { + this[i] = val + } + } else { + var bytes = Buffer.isBuffer(val) + ? val + : utf8ToBytes(new Buffer(val, encoding).toString()) + var len = bytes.length + for (i = 0; i < end - start; ++i) { + this[i + start] = bytes[i % len] + } + } + + return this +} + +// HELPER FUNCTIONS +// ================ + +var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g + +function base64clean (str) { + // Node strips out invalid characters like \n and \t from the string, base64-js does not + str = stringtrim(str).replace(INVALID_BASE64_RE, '') + // Node converts strings with length < 2 to '' + if (str.length < 2) return '' + // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not + while (str.length % 4 !== 0) { + str = str + '=' + } + return str +} + +function stringtrim (str) { + if (str.trim) return str.trim() + return str.replace(/^\s+|\s+$/g, '') +} + +function toHex (n) { + if (n < 16) return '0' + n.toString(16) + return n.toString(16) +} + +function utf8ToBytes (string, units) { + units = units || Infinity + var codePoint + var length = string.length + var leadSurrogate = null + var bytes = [] + + for (var i = 0; i < length; ++i) { + codePoint = string.charCodeAt(i) + + // is surrogate component + if (codePoint > 0xD7FF && codePoint < 0xE000) { + // last char was a lead + if (!leadSurrogate) { + // no lead yet + if (codePoint > 0xDBFF) { + // unexpected trail + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } else if (i + 1 === length) { + // unpaired lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + continue + } + + // valid lead + leadSurrogate = codePoint + + continue + } + + // 2 leads in a row + if (codePoint < 0xDC00) { + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + leadSurrogate = codePoint + continue + } + + // valid surrogate pair + codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000 + } else if (leadSurrogate) { + // valid bmp char, but last char was a lead + if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD) + } + + leadSurrogate = null + + // encode utf8 + if (codePoint < 0x80) { + if ((units -= 1) < 0) break + bytes.push(codePoint) + } else if (codePoint < 0x800) { + if ((units -= 2) < 0) break + bytes.push( + codePoint >> 0x6 | 0xC0, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x10000) { + if ((units -= 3) < 0) break + bytes.push( + codePoint >> 0xC | 0xE0, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else if (codePoint < 0x110000) { + if ((units -= 4) < 0) break + bytes.push( + codePoint >> 0x12 | 0xF0, + codePoint >> 0xC & 0x3F | 0x80, + codePoint >> 0x6 & 0x3F | 0x80, + codePoint & 0x3F | 0x80 + ) + } else { + throw new Error('Invalid code point') + } + } + + return bytes +} + +function asciiToBytes (str) { + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + // Node's code seems to be doing this and not & 0x7F.. + byteArray.push(str.charCodeAt(i) & 0xFF) + } + return byteArray +} + +function utf16leToBytes (str, units) { + var c, hi, lo + var byteArray = [] + for (var i = 0; i < str.length; ++i) { + if ((units -= 2) < 0) break + + c = str.charCodeAt(i) + hi = c >> 8 + lo = c % 256 + byteArray.push(lo) + byteArray.push(hi) + } + + return byteArray +} + +function base64ToBytes (str) { + return base64.toByteArray(base64clean(str)) +} + +function blitBuffer (src, dst, offset, length) { + for (var i = 0; i < length; ++i) { + if ((i + offset >= dst.length) || (i >= src.length)) break + dst[i + offset] = src[i] + } + return i +} + +function isnan (val) { + return val !== val +} + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) +},{"base64-js":48,"buffer":84,"ieee754":87,"isarray":88}],85:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var dingbats = [ + { "Typeface name": "Symbol", "Dingbat dec": "32", "Dingbat hex": "20", "Unicode dec": "32", "Unicode hex": "20" }, + { "Typeface name": "Symbol", "Dingbat dec": "33", "Dingbat hex": "21", "Unicode dec": "33", "Unicode hex": "21" }, + { "Typeface name": "Symbol", "Dingbat dec": "34", "Dingbat hex": "22", "Unicode dec": "8704", "Unicode hex": "2200" }, + { "Typeface name": "Symbol", "Dingbat dec": "35", "Dingbat hex": "23", "Unicode dec": "35", "Unicode hex": "23" }, + { "Typeface name": "Symbol", "Dingbat dec": "36", "Dingbat hex": "24", "Unicode dec": "8707", "Unicode hex": "2203" }, + { "Typeface name": "Symbol", "Dingbat dec": "37", "Dingbat hex": "25", "Unicode dec": "37", "Unicode hex": "25" }, + { "Typeface name": "Symbol", "Dingbat dec": "38", "Dingbat hex": "26", "Unicode dec": "38", "Unicode hex": "26" }, + { "Typeface name": "Symbol", "Dingbat dec": "39", "Dingbat hex": "27", "Unicode dec": "8717", "Unicode hex": "220D" }, + { "Typeface name": "Symbol", "Dingbat dec": "40", "Dingbat hex": "28", "Unicode dec": "40", "Unicode hex": "28" }, + { "Typeface name": "Symbol", "Dingbat dec": "41", "Dingbat hex": "29", "Unicode dec": "41", "Unicode hex": "29" }, + { "Typeface name": "Symbol", "Dingbat dec": "42", "Dingbat hex": "2A", "Unicode dec": "42", "Unicode hex": "2A" }, + { "Typeface name": "Symbol", "Dingbat dec": "43", "Dingbat hex": "2B", "Unicode dec": "43", "Unicode hex": "2B" }, + { "Typeface name": "Symbol", "Dingbat dec": "44", "Dingbat hex": "2C", "Unicode dec": "44", "Unicode hex": "2C" }, + { "Typeface name": "Symbol", "Dingbat dec": "45", "Dingbat hex": "2D", "Unicode dec": "8722", "Unicode hex": "2212" }, + { "Typeface name": "Symbol", "Dingbat dec": "46", "Dingbat hex": "2E", "Unicode dec": "46", "Unicode hex": "2E" }, + { "Typeface name": "Symbol", "Dingbat dec": "47", "Dingbat hex": "2F", "Unicode dec": "47", "Unicode hex": "2F" }, + { "Typeface name": "Symbol", "Dingbat dec": "48", "Dingbat hex": "30", "Unicode dec": "48", "Unicode hex": "30" }, + { "Typeface name": "Symbol", "Dingbat dec": "49", "Dingbat hex": "31", "Unicode dec": "49", "Unicode hex": "31" }, + { "Typeface name": "Symbol", "Dingbat dec": "50", "Dingbat hex": "32", "Unicode dec": "50", "Unicode hex": "32" }, + { "Typeface name": "Symbol", "Dingbat dec": "51", "Dingbat hex": "33", "Unicode dec": "51", "Unicode hex": "33" }, + { "Typeface name": "Symbol", "Dingbat dec": "52", "Dingbat hex": "34", "Unicode dec": "52", "Unicode hex": "34" }, + { "Typeface name": "Symbol", "Dingbat dec": "53", "Dingbat hex": "35", "Unicode dec": "53", "Unicode hex": "35" }, + { "Typeface name": "Symbol", "Dingbat dec": "54", "Dingbat hex": "36", "Unicode dec": "54", "Unicode hex": "36" }, + { "Typeface name": "Symbol", "Dingbat dec": "55", "Dingbat hex": "37", "Unicode dec": "55", "Unicode hex": "37" }, + { "Typeface name": "Symbol", "Dingbat dec": "56", "Dingbat hex": "38", "Unicode dec": "56", "Unicode hex": "38" }, + { "Typeface name": "Symbol", "Dingbat dec": "57", "Dingbat hex": "39", "Unicode dec": "57", "Unicode hex": "39" }, + { "Typeface name": "Symbol", "Dingbat dec": "58", "Dingbat hex": "3A", "Unicode dec": "58", "Unicode hex": "3A" }, + { "Typeface name": "Symbol", "Dingbat dec": "59", "Dingbat hex": "3B", "Unicode dec": "59", "Unicode hex": "3B" }, + { "Typeface name": "Symbol", "Dingbat dec": "60", "Dingbat hex": "3C", "Unicode dec": "60", "Unicode hex": "3C" }, + { "Typeface name": "Symbol", "Dingbat dec": "61", "Dingbat hex": "3D", "Unicode dec": "61", "Unicode hex": "3D" }, + { "Typeface name": "Symbol", "Dingbat dec": "62", "Dingbat hex": "3E", "Unicode dec": "62", "Unicode hex": "3E" }, + { "Typeface name": "Symbol", "Dingbat dec": "63", "Dingbat hex": "3F", "Unicode dec": "63", "Unicode hex": "3F" }, + { "Typeface name": "Symbol", "Dingbat dec": "64", "Dingbat hex": "40", "Unicode dec": "8773", "Unicode hex": "2245" }, + { "Typeface name": "Symbol", "Dingbat dec": "65", "Dingbat hex": "41", "Unicode dec": "913", "Unicode hex": "391" }, + { "Typeface name": "Symbol", "Dingbat dec": "66", "Dingbat hex": "42", "Unicode dec": "914", "Unicode hex": "392" }, + { "Typeface name": "Symbol", "Dingbat dec": "67", "Dingbat hex": "43", "Unicode dec": "935", "Unicode hex": "3A7" }, + { "Typeface name": "Symbol", "Dingbat dec": "68", "Dingbat hex": "44", "Unicode dec": "916", "Unicode hex": "394" }, + { "Typeface name": "Symbol", "Dingbat dec": "69", "Dingbat hex": "45", "Unicode dec": "917", "Unicode hex": "395" }, + { "Typeface name": "Symbol", "Dingbat dec": "70", "Dingbat hex": "46", "Unicode dec": "934", "Unicode hex": "3A6" }, + { "Typeface name": "Symbol", "Dingbat dec": "71", "Dingbat hex": "47", "Unicode dec": "915", "Unicode hex": "393" }, + { "Typeface name": "Symbol", "Dingbat dec": "72", "Dingbat hex": "48", "Unicode dec": "919", "Unicode hex": "397" }, + { "Typeface name": "Symbol", "Dingbat dec": "73", "Dingbat hex": "49", "Unicode dec": "921", "Unicode hex": "399" }, + { "Typeface name": "Symbol", "Dingbat dec": "74", "Dingbat hex": "4A", "Unicode dec": "977", "Unicode hex": "3D1" }, + { "Typeface name": "Symbol", "Dingbat dec": "75", "Dingbat hex": "4B", "Unicode dec": "922", "Unicode hex": "39A" }, + { "Typeface name": "Symbol", "Dingbat dec": "76", "Dingbat hex": "4C", "Unicode dec": "923", "Unicode hex": "39B" }, + { "Typeface name": "Symbol", "Dingbat dec": "77", "Dingbat hex": "4D", "Unicode dec": "924", "Unicode hex": "39C" }, + { "Typeface name": "Symbol", "Dingbat dec": "78", "Dingbat hex": "4E", "Unicode dec": "925", "Unicode hex": "39D" }, + { "Typeface name": "Symbol", "Dingbat dec": "79", "Dingbat hex": "4F", "Unicode dec": "927", "Unicode hex": "39F" }, + { "Typeface name": "Symbol", "Dingbat dec": "80", "Dingbat hex": "50", "Unicode dec": "928", "Unicode hex": "3A0" }, + { "Typeface name": "Symbol", "Dingbat dec": "81", "Dingbat hex": "51", "Unicode dec": "920", "Unicode hex": "398" }, + { "Typeface name": "Symbol", "Dingbat dec": "82", "Dingbat hex": "52", "Unicode dec": "929", "Unicode hex": "3A1" }, + { "Typeface name": "Symbol", "Dingbat dec": "83", "Dingbat hex": "53", "Unicode dec": "931", "Unicode hex": "3A3" }, + { "Typeface name": "Symbol", "Dingbat dec": "84", "Dingbat hex": "54", "Unicode dec": "932", "Unicode hex": "3A4" }, + { "Typeface name": "Symbol", "Dingbat dec": "85", "Dingbat hex": "55", "Unicode dec": "933", "Unicode hex": "3A5" }, + { "Typeface name": "Symbol", "Dingbat dec": "86", "Dingbat hex": "56", "Unicode dec": "962", "Unicode hex": "3C2" }, + { "Typeface name": "Symbol", "Dingbat dec": "87", "Dingbat hex": "57", "Unicode dec": "937", "Unicode hex": "3A9" }, + { "Typeface name": "Symbol", "Dingbat dec": "88", "Dingbat hex": "58", "Unicode dec": "926", "Unicode hex": "39E" }, + { "Typeface name": "Symbol", "Dingbat dec": "89", "Dingbat hex": "59", "Unicode dec": "936", "Unicode hex": "3A8" }, + { "Typeface name": "Symbol", "Dingbat dec": "90", "Dingbat hex": "5A", "Unicode dec": "918", "Unicode hex": "396" }, + { "Typeface name": "Symbol", "Dingbat dec": "91", "Dingbat hex": "5B", "Unicode dec": "91", "Unicode hex": "5B" }, + { "Typeface name": "Symbol", "Dingbat dec": "92", "Dingbat hex": "5C", "Unicode dec": "8756", "Unicode hex": "2234" }, + { "Typeface name": "Symbol", "Dingbat dec": "93", "Dingbat hex": "5D", "Unicode dec": "93", "Unicode hex": "5D" }, + { "Typeface name": "Symbol", "Dingbat dec": "94", "Dingbat hex": "5E", "Unicode dec": "8869", "Unicode hex": "22A5" }, + { "Typeface name": "Symbol", "Dingbat dec": "95", "Dingbat hex": "5F", "Unicode dec": "95", "Unicode hex": "5F" }, + { "Typeface name": "Symbol", "Dingbat dec": "96", "Dingbat hex": "60", "Unicode dec": "8254", "Unicode hex": "203E" }, + { "Typeface name": "Symbol", "Dingbat dec": "97", "Dingbat hex": "61", "Unicode dec": "945", "Unicode hex": "3B1" }, + { "Typeface name": "Symbol", "Dingbat dec": "98", "Dingbat hex": "62", "Unicode dec": "946", "Unicode hex": "3B2" }, + { "Typeface name": "Symbol", "Dingbat dec": "99", "Dingbat hex": "63", "Unicode dec": "967", "Unicode hex": "3C7" }, + { "Typeface name": "Symbol", "Dingbat dec": "100", "Dingbat hex": "64", "Unicode dec": "948", "Unicode hex": "3B4" }, + { "Typeface name": "Symbol", "Dingbat dec": "101", "Dingbat hex": "65", "Unicode dec": "949", "Unicode hex": "3B5" }, + { "Typeface name": "Symbol", "Dingbat dec": "102", "Dingbat hex": "66", "Unicode dec": "966", "Unicode hex": "3C6" }, + { "Typeface name": "Symbol", "Dingbat dec": "103", "Dingbat hex": "67", "Unicode dec": "947", "Unicode hex": "3B3" }, + { "Typeface name": "Symbol", "Dingbat dec": "104", "Dingbat hex": "68", "Unicode dec": "951", "Unicode hex": "3B7" }, + { "Typeface name": "Symbol", "Dingbat dec": "105", "Dingbat hex": "69", "Unicode dec": "953", "Unicode hex": "3B9" }, + { "Typeface name": "Symbol", "Dingbat dec": "106", "Dingbat hex": "6A", "Unicode dec": "981", "Unicode hex": "3D5" }, + { "Typeface name": "Symbol", "Dingbat dec": "107", "Dingbat hex": "6B", "Unicode dec": "954", "Unicode hex": "3BA" }, + { "Typeface name": "Symbol", "Dingbat dec": "108", "Dingbat hex": "6C", "Unicode dec": "955", "Unicode hex": "3BB" }, + { "Typeface name": "Symbol", "Dingbat dec": "109", "Dingbat hex": "6D", "Unicode dec": "956", "Unicode hex": "3BC" }, + { "Typeface name": "Symbol", "Dingbat dec": "110", "Dingbat hex": "6E", "Unicode dec": "957", "Unicode hex": "3BD" }, + { "Typeface name": "Symbol", "Dingbat dec": "111", "Dingbat hex": "6F", "Unicode dec": "959", "Unicode hex": "3BF" }, + { "Typeface name": "Symbol", "Dingbat dec": "112", "Dingbat hex": "70", "Unicode dec": "960", "Unicode hex": "3C0" }, + { "Typeface name": "Symbol", "Dingbat dec": "113", "Dingbat hex": "71", "Unicode dec": "952", "Unicode hex": "3B8" }, + { "Typeface name": "Symbol", "Dingbat dec": "114", "Dingbat hex": "72", "Unicode dec": "961", "Unicode hex": "3C1" }, + { "Typeface name": "Symbol", "Dingbat dec": "115", "Dingbat hex": "73", "Unicode dec": "963", "Unicode hex": "3C3" }, + { "Typeface name": "Symbol", "Dingbat dec": "116", "Dingbat hex": "74", "Unicode dec": "964", "Unicode hex": "3C4" }, + { "Typeface name": "Symbol", "Dingbat dec": "117", "Dingbat hex": "75", "Unicode dec": "965", "Unicode hex": "3C5" }, + { "Typeface name": "Symbol", "Dingbat dec": "118", "Dingbat hex": "76", "Unicode dec": "982", "Unicode hex": "3D6" }, + { "Typeface name": "Symbol", "Dingbat dec": "119", "Dingbat hex": "77", "Unicode dec": "969", "Unicode hex": "3C9" }, + { "Typeface name": "Symbol", "Dingbat dec": "120", "Dingbat hex": "78", "Unicode dec": "958", "Unicode hex": "3BE" }, + { "Typeface name": "Symbol", "Dingbat dec": "121", "Dingbat hex": "79", "Unicode dec": "968", "Unicode hex": "3C8" }, + { "Typeface name": "Symbol", "Dingbat dec": "122", "Dingbat hex": "7A", "Unicode dec": "950", "Unicode hex": "3B6" }, + { "Typeface name": "Symbol", "Dingbat dec": "123", "Dingbat hex": "7B", "Unicode dec": "123", "Unicode hex": "7B" }, + { "Typeface name": "Symbol", "Dingbat dec": "124", "Dingbat hex": "7C", "Unicode dec": "124", "Unicode hex": "7C" }, + { "Typeface name": "Symbol", "Dingbat dec": "125", "Dingbat hex": "7D", "Unicode dec": "125", "Unicode hex": "7D" }, + { "Typeface name": "Symbol", "Dingbat dec": "126", "Dingbat hex": "7E", "Unicode dec": "126", "Unicode hex": "7E" }, + { "Typeface name": "Symbol", "Dingbat dec": "160", "Dingbat hex": "A0", "Unicode dec": "8364", "Unicode hex": "20AC" }, + { "Typeface name": "Symbol", "Dingbat dec": "161", "Dingbat hex": "A1", "Unicode dec": "978", "Unicode hex": "3D2" }, + { "Typeface name": "Symbol", "Dingbat dec": "162", "Dingbat hex": "A2", "Unicode dec": "8242", "Unicode hex": "2032" }, + { "Typeface name": "Symbol", "Dingbat dec": "163", "Dingbat hex": "A3", "Unicode dec": "8804", "Unicode hex": "2264" }, + { "Typeface name": "Symbol", "Dingbat dec": "164", "Dingbat hex": "A4", "Unicode dec": "8260", "Unicode hex": "2044" }, + { "Typeface name": "Symbol", "Dingbat dec": "165", "Dingbat hex": "A5", "Unicode dec": "8734", "Unicode hex": "221E" }, + { "Typeface name": "Symbol", "Dingbat dec": "166", "Dingbat hex": "A6", "Unicode dec": "402", "Unicode hex": "192" }, + { "Typeface name": "Symbol", "Dingbat dec": "167", "Dingbat hex": "A7", "Unicode dec": "9827", "Unicode hex": "2663" }, + { "Typeface name": "Symbol", "Dingbat dec": "168", "Dingbat hex": "A8", "Unicode dec": "9830", "Unicode hex": "2666" }, + { "Typeface name": "Symbol", "Dingbat dec": "169", "Dingbat hex": "A9", "Unicode dec": "9829", "Unicode hex": "2665" }, + { "Typeface name": "Symbol", "Dingbat dec": "170", "Dingbat hex": "AA", "Unicode dec": "9824", "Unicode hex": "2660" }, + { "Typeface name": "Symbol", "Dingbat dec": "171", "Dingbat hex": "AB", "Unicode dec": "8596", "Unicode hex": "2194" }, + { "Typeface name": "Symbol", "Dingbat dec": "172", "Dingbat hex": "AC", "Unicode dec": "8592", "Unicode hex": "2190" }, + { "Typeface name": "Symbol", "Dingbat dec": "173", "Dingbat hex": "AD", "Unicode dec": "8593", "Unicode hex": "2191" }, + { "Typeface name": "Symbol", "Dingbat dec": "174", "Dingbat hex": "AE", "Unicode dec": "8594", "Unicode hex": "2192" }, + { "Typeface name": "Symbol", "Dingbat dec": "175", "Dingbat hex": "AF", "Unicode dec": "8595", "Unicode hex": "2193" }, + { "Typeface name": "Symbol", "Dingbat dec": "176", "Dingbat hex": "B0", "Unicode dec": "176", "Unicode hex": "B0" }, + { "Typeface name": "Symbol", "Dingbat dec": "177", "Dingbat hex": "B1", "Unicode dec": "177", "Unicode hex": "B1" }, + { "Typeface name": "Symbol", "Dingbat dec": "178", "Dingbat hex": "B2", "Unicode dec": "8243", "Unicode hex": "2033" }, + { "Typeface name": "Symbol", "Dingbat dec": "179", "Dingbat hex": "B3", "Unicode dec": "8805", "Unicode hex": "2265" }, + { "Typeface name": "Symbol", "Dingbat dec": "180", "Dingbat hex": "B4", "Unicode dec": "215", "Unicode hex": "D7" }, + { "Typeface name": "Symbol", "Dingbat dec": "181", "Dingbat hex": "B5", "Unicode dec": "8733", "Unicode hex": "221D" }, + { "Typeface name": "Symbol", "Dingbat dec": "182", "Dingbat hex": "B6", "Unicode dec": "8706", "Unicode hex": "2202" }, + { "Typeface name": "Symbol", "Dingbat dec": "183", "Dingbat hex": "B7", "Unicode dec": "8226", "Unicode hex": "2022" }, + { "Typeface name": "Symbol", "Dingbat dec": "184", "Dingbat hex": "B8", "Unicode dec": "247", "Unicode hex": "F7" }, + { "Typeface name": "Symbol", "Dingbat dec": "185", "Dingbat hex": "B9", "Unicode dec": "8800", "Unicode hex": "2260" }, + { "Typeface name": "Symbol", "Dingbat dec": "186", "Dingbat hex": "BA", "Unicode dec": "8801", "Unicode hex": "2261" }, + { "Typeface name": "Symbol", "Dingbat dec": "187", "Dingbat hex": "BB", "Unicode dec": "8776", "Unicode hex": "2248" }, + { "Typeface name": "Symbol", "Dingbat dec": "188", "Dingbat hex": "BC", "Unicode dec": "8230", "Unicode hex": "2026" }, + { "Typeface name": "Symbol", "Dingbat dec": "189", "Dingbat hex": "BD", "Unicode dec": "9168", "Unicode hex": "23D0" }, + { "Typeface name": "Symbol", "Dingbat dec": "190", "Dingbat hex": "BE", "Unicode dec": "9135", "Unicode hex": "23AF" }, + { "Typeface name": "Symbol", "Dingbat dec": "191", "Dingbat hex": "BF", "Unicode dec": "8629", "Unicode hex": "21B5" }, + { "Typeface name": "Symbol", "Dingbat dec": "192", "Dingbat hex": "C0", "Unicode dec": "8501", "Unicode hex": "2135" }, + { "Typeface name": "Symbol", "Dingbat dec": "193", "Dingbat hex": "C1", "Unicode dec": "8465", "Unicode hex": "2111" }, + { "Typeface name": "Symbol", "Dingbat dec": "194", "Dingbat hex": "C2", "Unicode dec": "8476", "Unicode hex": "211C" }, + { "Typeface name": "Symbol", "Dingbat dec": "195", "Dingbat hex": "C3", "Unicode dec": "8472", "Unicode hex": "2118" }, + { "Typeface name": "Symbol", "Dingbat dec": "196", "Dingbat hex": "C4", "Unicode dec": "8855", "Unicode hex": "2297" }, + { "Typeface name": "Symbol", "Dingbat dec": "197", "Dingbat hex": "C5", "Unicode dec": "8853", "Unicode hex": "2295" }, + { "Typeface name": "Symbol", "Dingbat dec": "198", "Dingbat hex": "C6", "Unicode dec": "8709", "Unicode hex": "2205" }, + { "Typeface name": "Symbol", "Dingbat dec": "199", "Dingbat hex": "C7", "Unicode dec": "8745", "Unicode hex": "2229" }, + { "Typeface name": "Symbol", "Dingbat dec": "200", "Dingbat hex": "C8", "Unicode dec": "8746", "Unicode hex": "222A" }, + { "Typeface name": "Symbol", "Dingbat dec": "201", "Dingbat hex": "C9", "Unicode dec": "8835", "Unicode hex": "2283" }, + { "Typeface name": "Symbol", "Dingbat dec": "202", "Dingbat hex": "CA", "Unicode dec": "8839", "Unicode hex": "2287" }, + { "Typeface name": "Symbol", "Dingbat dec": "203", "Dingbat hex": "CB", "Unicode dec": "8836", "Unicode hex": "2284" }, + { "Typeface name": "Symbol", "Dingbat dec": "204", "Dingbat hex": "CC", "Unicode dec": "8834", "Unicode hex": "2282" }, + { "Typeface name": "Symbol", "Dingbat dec": "205", "Dingbat hex": "CD", "Unicode dec": "8838", "Unicode hex": "2286" }, + { "Typeface name": "Symbol", "Dingbat dec": "206", "Dingbat hex": "CE", "Unicode dec": "8712", "Unicode hex": "2208" }, + { "Typeface name": "Symbol", "Dingbat dec": "207", "Dingbat hex": "CF", "Unicode dec": "8713", "Unicode hex": "2209" }, + { "Typeface name": "Symbol", "Dingbat dec": "208", "Dingbat hex": "D0", "Unicode dec": "8736", "Unicode hex": "2220" }, + { "Typeface name": "Symbol", "Dingbat dec": "209", "Dingbat hex": "D1", "Unicode dec": "8711", "Unicode hex": "2207" }, + { "Typeface name": "Symbol", "Dingbat dec": "210", "Dingbat hex": "D2", "Unicode dec": "174", "Unicode hex": "AE" }, + { "Typeface name": "Symbol", "Dingbat dec": "211", "Dingbat hex": "D3", "Unicode dec": "169", "Unicode hex": "A9" }, + { "Typeface name": "Symbol", "Dingbat dec": "212", "Dingbat hex": "D4", "Unicode dec": "8482", "Unicode hex": "2122" }, + { "Typeface name": "Symbol", "Dingbat dec": "213", "Dingbat hex": "D5", "Unicode dec": "8719", "Unicode hex": "220F" }, + { "Typeface name": "Symbol", "Dingbat dec": "214", "Dingbat hex": "D6", "Unicode dec": "8730", "Unicode hex": "221A" }, + { "Typeface name": "Symbol", "Dingbat dec": "215", "Dingbat hex": "D7", "Unicode dec": "8901", "Unicode hex": "22C5" }, + { "Typeface name": "Symbol", "Dingbat dec": "216", "Dingbat hex": "D8", "Unicode dec": "172", "Unicode hex": "AC" }, + { "Typeface name": "Symbol", "Dingbat dec": "217", "Dingbat hex": "D9", "Unicode dec": "8743", "Unicode hex": "2227" }, + { "Typeface name": "Symbol", "Dingbat dec": "218", "Dingbat hex": "DA", "Unicode dec": "8744", "Unicode hex": "2228" }, + { "Typeface name": "Symbol", "Dingbat dec": "219", "Dingbat hex": "DB", "Unicode dec": "8660", "Unicode hex": "21D4" }, + { "Typeface name": "Symbol", "Dingbat dec": "220", "Dingbat hex": "DC", "Unicode dec": "8656", "Unicode hex": "21D0" }, + { "Typeface name": "Symbol", "Dingbat dec": "221", "Dingbat hex": "DD", "Unicode dec": "8657", "Unicode hex": "21D1" }, + { "Typeface name": "Symbol", "Dingbat dec": "222", "Dingbat hex": "DE", "Unicode dec": "8658", "Unicode hex": "21D2" }, + { "Typeface name": "Symbol", "Dingbat dec": "223", "Dingbat hex": "DF", "Unicode dec": "8659", "Unicode hex": "21D3" }, + { "Typeface name": "Symbol", "Dingbat dec": "224", "Dingbat hex": "E0", "Unicode dec": "9674", "Unicode hex": "25CA" }, + { "Typeface name": "Symbol", "Dingbat dec": "225", "Dingbat hex": "E1", "Unicode dec": "12296", "Unicode hex": "3008" }, + { "Typeface name": "Symbol", "Dingbat dec": "226", "Dingbat hex": "E2", "Unicode dec": "174", "Unicode hex": "AE" }, + { "Typeface name": "Symbol", "Dingbat dec": "227", "Dingbat hex": "E3", "Unicode dec": "169", "Unicode hex": "A9" }, + { "Typeface name": "Symbol", "Dingbat dec": "228", "Dingbat hex": "E4", "Unicode dec": "8482", "Unicode hex": "2122" }, + { "Typeface name": "Symbol", "Dingbat dec": "229", "Dingbat hex": "E5", "Unicode dec": "8721", "Unicode hex": "2211" }, + { "Typeface name": "Symbol", "Dingbat dec": "230", "Dingbat hex": "E6", "Unicode dec": "9115", "Unicode hex": "239B" }, + { "Typeface name": "Symbol", "Dingbat dec": "231", "Dingbat hex": "E7", "Unicode dec": "9116", "Unicode hex": "239C" }, + { "Typeface name": "Symbol", "Dingbat dec": "232", "Dingbat hex": "E8", "Unicode dec": "9117", "Unicode hex": "239D" }, + { "Typeface name": "Symbol", "Dingbat dec": "233", "Dingbat hex": "E9", "Unicode dec": "9121", "Unicode hex": "23A1" }, + { "Typeface name": "Symbol", "Dingbat dec": "234", "Dingbat hex": "EA", "Unicode dec": "9122", "Unicode hex": "23A2" }, + { "Typeface name": "Symbol", "Dingbat dec": "235", "Dingbat hex": "EB", "Unicode dec": "9123", "Unicode hex": "23A3" }, + { "Typeface name": "Symbol", "Dingbat dec": "236", "Dingbat hex": "EC", "Unicode dec": "9127", "Unicode hex": "23A7" }, + { "Typeface name": "Symbol", "Dingbat dec": "237", "Dingbat hex": "ED", "Unicode dec": "9128", "Unicode hex": "23A8" }, + { "Typeface name": "Symbol", "Dingbat dec": "238", "Dingbat hex": "EE", "Unicode dec": "9129", "Unicode hex": "23A9" }, + { "Typeface name": "Symbol", "Dingbat dec": "239", "Dingbat hex": "EF", "Unicode dec": "9130", "Unicode hex": "23AA" }, + { "Typeface name": "Symbol", "Dingbat dec": "240", "Dingbat hex": "F0", "Unicode dec": "63743", "Unicode hex": "F8FF" }, + { "Typeface name": "Symbol", "Dingbat dec": "241", "Dingbat hex": "F1", "Unicode dec": "12297", "Unicode hex": "3009" }, + { "Typeface name": "Symbol", "Dingbat dec": "242", "Dingbat hex": "F2", "Unicode dec": "8747", "Unicode hex": "222B" }, + { "Typeface name": "Symbol", "Dingbat dec": "243", "Dingbat hex": "F3", "Unicode dec": "8992", "Unicode hex": "2320" }, + { "Typeface name": "Symbol", "Dingbat dec": "244", "Dingbat hex": "F4", "Unicode dec": "9134", "Unicode hex": "23AE" }, + { "Typeface name": "Symbol", "Dingbat dec": "245", "Dingbat hex": "F5", "Unicode dec": "8993", "Unicode hex": "2321" }, + { "Typeface name": "Symbol", "Dingbat dec": "246", "Dingbat hex": "F6", "Unicode dec": "9118", "Unicode hex": "239E" }, + { "Typeface name": "Symbol", "Dingbat dec": "247", "Dingbat hex": "F7", "Unicode dec": "9119", "Unicode hex": "239F" }, + { "Typeface name": "Symbol", "Dingbat dec": "248", "Dingbat hex": "F8", "Unicode dec": "9120", "Unicode hex": "23A0" }, + { "Typeface name": "Symbol", "Dingbat dec": "249", "Dingbat hex": "F9", "Unicode dec": "9124", "Unicode hex": "23A4" }, + { "Typeface name": "Symbol", "Dingbat dec": "250", "Dingbat hex": "FA", "Unicode dec": "9125", "Unicode hex": "23A5" }, + { "Typeface name": "Symbol", "Dingbat dec": "251", "Dingbat hex": "FB", "Unicode dec": "9126", "Unicode hex": "23A6" }, + { "Typeface name": "Symbol", "Dingbat dec": "252", "Dingbat hex": "FC", "Unicode dec": "9131", "Unicode hex": "23AB" }, + { "Typeface name": "Symbol", "Dingbat dec": "253", "Dingbat hex": "FD", "Unicode dec": "9132", "Unicode hex": "23AC" }, + { "Typeface name": "Symbol", "Dingbat dec": "254", "Dingbat hex": "FE", "Unicode dec": "9133", "Unicode hex": "23AD" }, + { "Typeface name": "Webdings", "Dingbat dec": "32", "Dingbat hex": "20", "Unicode dec": "32", "Unicode hex": "20" }, + { "Typeface name": "Webdings", "Dingbat dec": "33", "Dingbat hex": "21", "Unicode dec": "128375", "Unicode hex": "1F577" }, + { "Typeface name": "Webdings", "Dingbat dec": "34", "Dingbat hex": "22", "Unicode dec": "128376", "Unicode hex": "1F578" }, + { "Typeface name": "Webdings", "Dingbat dec": "35", "Dingbat hex": "23", "Unicode dec": "128370", "Unicode hex": "1F572" }, + { "Typeface name": "Webdings", "Dingbat dec": "36", "Dingbat hex": "24", "Unicode dec": "128374", "Unicode hex": "1F576" }, + { "Typeface name": "Webdings", "Dingbat dec": "37", "Dingbat hex": "25", "Unicode dec": "127942", "Unicode hex": "1F3C6" }, + { "Typeface name": "Webdings", "Dingbat dec": "38", "Dingbat hex": "26", "Unicode dec": "127894", "Unicode hex": "1F396" }, + { "Typeface name": "Webdings", "Dingbat dec": "39", "Dingbat hex": "27", "Unicode dec": "128391", "Unicode hex": "1F587" }, + { "Typeface name": "Webdings", "Dingbat dec": "40", "Dingbat hex": "28", "Unicode dec": "128488", "Unicode hex": "1F5E8" }, + { "Typeface name": "Webdings", "Dingbat dec": "41", "Dingbat hex": "29", "Unicode dec": "128489", "Unicode hex": "1F5E9" }, + { "Typeface name": "Webdings", "Dingbat dec": "42", "Dingbat hex": "2A", "Unicode dec": "128496", "Unicode hex": "1F5F0" }, + { "Typeface name": "Webdings", "Dingbat dec": "43", "Dingbat hex": "2B", "Unicode dec": "128497", "Unicode hex": "1F5F1" }, + { "Typeface name": "Webdings", "Dingbat dec": "44", "Dingbat hex": "2C", "Unicode dec": "127798", "Unicode hex": "1F336" }, + { "Typeface name": "Webdings", "Dingbat dec": "45", "Dingbat hex": "2D", "Unicode dec": "127895", "Unicode hex": "1F397" }, + { "Typeface name": "Webdings", "Dingbat dec": "46", "Dingbat hex": "2E", "Unicode dec": "128638", "Unicode hex": "1F67E" }, + { "Typeface name": "Webdings", "Dingbat dec": "47", "Dingbat hex": "2F", "Unicode dec": "128636", "Unicode hex": "1F67C" }, + { "Typeface name": "Webdings", "Dingbat dec": "48", "Dingbat hex": "30", "Unicode dec": "128469", "Unicode hex": "1F5D5" }, + { "Typeface name": "Webdings", "Dingbat dec": "49", "Dingbat hex": "31", "Unicode dec": "128470", "Unicode hex": "1F5D6" }, + { "Typeface name": "Webdings", "Dingbat dec": "50", "Dingbat hex": "32", "Unicode dec": "128471", "Unicode hex": "1F5D7" }, + { "Typeface name": "Webdings", "Dingbat dec": "51", "Dingbat hex": "33", "Unicode dec": "9204", "Unicode hex": "23F4" }, + { "Typeface name": "Webdings", "Dingbat dec": "52", "Dingbat hex": "34", "Unicode dec": "9205", "Unicode hex": "23F5" }, + { "Typeface name": "Webdings", "Dingbat dec": "53", "Dingbat hex": "35", "Unicode dec": "9206", "Unicode hex": "23F6" }, + { "Typeface name": "Webdings", "Dingbat dec": "54", "Dingbat hex": "36", "Unicode dec": "9207", "Unicode hex": "23F7" }, + { "Typeface name": "Webdings", "Dingbat dec": "55", "Dingbat hex": "37", "Unicode dec": "9194", "Unicode hex": "23EA" }, + { "Typeface name": "Webdings", "Dingbat dec": "56", "Dingbat hex": "38", "Unicode dec": "9193", "Unicode hex": "23E9" }, + { "Typeface name": "Webdings", "Dingbat dec": "57", "Dingbat hex": "39", "Unicode dec": "9198", "Unicode hex": "23EE" }, + { "Typeface name": "Webdings", "Dingbat dec": "58", "Dingbat hex": "3A", "Unicode dec": "9197", "Unicode hex": "23ED" }, + { "Typeface name": "Webdings", "Dingbat dec": "59", "Dingbat hex": "3B", "Unicode dec": "9208", "Unicode hex": "23F8" }, + { "Typeface name": "Webdings", "Dingbat dec": "60", "Dingbat hex": "3C", "Unicode dec": "9209", "Unicode hex": "23F9" }, + { "Typeface name": "Webdings", "Dingbat dec": "61", "Dingbat hex": "3D", "Unicode dec": "9210", "Unicode hex": "23FA" }, + { "Typeface name": "Webdings", "Dingbat dec": "62", "Dingbat hex": "3E", "Unicode dec": "128474", "Unicode hex": "1F5DA" }, + { "Typeface name": "Webdings", "Dingbat dec": "63", "Dingbat hex": "3F", "Unicode dec": "128499", "Unicode hex": "1F5F3" }, + { "Typeface name": "Webdings", "Dingbat dec": "64", "Dingbat hex": "40", "Unicode dec": "128736", "Unicode hex": "1F6E0" }, + { "Typeface name": "Webdings", "Dingbat dec": "65", "Dingbat hex": "41", "Unicode dec": "127959", "Unicode hex": "1F3D7" }, + { "Typeface name": "Webdings", "Dingbat dec": "66", "Dingbat hex": "42", "Unicode dec": "127960", "Unicode hex": "1F3D8" }, + { "Typeface name": "Webdings", "Dingbat dec": "67", "Dingbat hex": "43", "Unicode dec": "127961", "Unicode hex": "1F3D9" }, + { "Typeface name": "Webdings", "Dingbat dec": "68", "Dingbat hex": "44", "Unicode dec": "127962", "Unicode hex": "1F3DA" }, + { "Typeface name": "Webdings", "Dingbat dec": "69", "Dingbat hex": "45", "Unicode dec": "127964", "Unicode hex": "1F3DC" }, + { "Typeface name": "Webdings", "Dingbat dec": "70", "Dingbat hex": "46", "Unicode dec": "127981", "Unicode hex": "1F3ED" }, + { "Typeface name": "Webdings", "Dingbat dec": "71", "Dingbat hex": "47", "Unicode dec": "127963", "Unicode hex": "1F3DB" }, + { "Typeface name": "Webdings", "Dingbat dec": "72", "Dingbat hex": "48", "Unicode dec": "127968", "Unicode hex": "1F3E0" }, + { "Typeface name": "Webdings", "Dingbat dec": "73", "Dingbat hex": "49", "Unicode dec": "127958", "Unicode hex": "1F3D6" }, + { "Typeface name": "Webdings", "Dingbat dec": "74", "Dingbat hex": "4A", "Unicode dec": "127965", "Unicode hex": "1F3DD" }, + { "Typeface name": "Webdings", "Dingbat dec": "75", "Dingbat hex": "4B", "Unicode dec": "128739", "Unicode hex": "1F6E3" }, + { "Typeface name": "Webdings", "Dingbat dec": "76", "Dingbat hex": "4C", "Unicode dec": "128269", "Unicode hex": "1F50D" }, + { "Typeface name": "Webdings", "Dingbat dec": "77", "Dingbat hex": "4D", "Unicode dec": "127956", "Unicode hex": "1F3D4" }, + { "Typeface name": "Webdings", "Dingbat dec": "78", "Dingbat hex": "4E", "Unicode dec": "128065", "Unicode hex": "1F441" }, + { "Typeface name": "Webdings", "Dingbat dec": "79", "Dingbat hex": "4F", "Unicode dec": "128066", "Unicode hex": "1F442" }, + { "Typeface name": "Webdings", "Dingbat dec": "80", "Dingbat hex": "50", "Unicode dec": "127966", "Unicode hex": "1F3DE" }, + { "Typeface name": "Webdings", "Dingbat dec": "81", "Dingbat hex": "51", "Unicode dec": "127957", "Unicode hex": "1F3D5" }, + { "Typeface name": "Webdings", "Dingbat dec": "82", "Dingbat hex": "52", "Unicode dec": "128740", "Unicode hex": "1F6E4" }, + { "Typeface name": "Webdings", "Dingbat dec": "83", "Dingbat hex": "53", "Unicode dec": "127967", "Unicode hex": "1F3DF" }, + { "Typeface name": "Webdings", "Dingbat dec": "84", "Dingbat hex": "54", "Unicode dec": "128755", "Unicode hex": "1F6F3" }, + { "Typeface name": "Webdings", "Dingbat dec": "85", "Dingbat hex": "55", "Unicode dec": "128364", "Unicode hex": "1F56C" }, + { "Typeface name": "Webdings", "Dingbat dec": "86", "Dingbat hex": "56", "Unicode dec": "128363", "Unicode hex": "1F56B" }, + { "Typeface name": "Webdings", "Dingbat dec": "87", "Dingbat hex": "57", "Unicode dec": "128360", "Unicode hex": "1F568" }, + { "Typeface name": "Webdings", "Dingbat dec": "88", "Dingbat hex": "58", "Unicode dec": "128264", "Unicode hex": "1F508" }, + { "Typeface name": "Webdings", "Dingbat dec": "89", "Dingbat hex": "59", "Unicode dec": "127892", "Unicode hex": "1F394" }, + { "Typeface name": "Webdings", "Dingbat dec": "90", "Dingbat hex": "5A", "Unicode dec": "127893", "Unicode hex": "1F395" }, + { "Typeface name": "Webdings", "Dingbat dec": "91", "Dingbat hex": "5B", "Unicode dec": "128492", "Unicode hex": "1F5EC" }, + { "Typeface name": "Webdings", "Dingbat dec": "92", "Dingbat hex": "5C", "Unicode dec": "128637", "Unicode hex": "1F67D" }, + { "Typeface name": "Webdings", "Dingbat dec": "93", "Dingbat hex": "5D", "Unicode dec": "128493", "Unicode hex": "1F5ED" }, + { "Typeface name": "Webdings", "Dingbat dec": "94", "Dingbat hex": "5E", "Unicode dec": "128490", "Unicode hex": "1F5EA" }, + { "Typeface name": "Webdings", "Dingbat dec": "95", "Dingbat hex": "5F", "Unicode dec": "128491", "Unicode hex": "1F5EB" }, + { "Typeface name": "Webdings", "Dingbat dec": "96", "Dingbat hex": "60", "Unicode dec": "11156", "Unicode hex": "2B94" }, + { "Typeface name": "Webdings", "Dingbat dec": "97", "Dingbat hex": "61", "Unicode dec": "10004", "Unicode hex": "2714" }, + { "Typeface name": "Webdings", "Dingbat dec": "98", "Dingbat hex": "62", "Unicode dec": "128690", "Unicode hex": "1F6B2" }, + { "Typeface name": "Webdings", "Dingbat dec": "99", "Dingbat hex": "63", "Unicode dec": "11036", "Unicode hex": "2B1C" }, + { "Typeface name": "Webdings", "Dingbat dec": "100", "Dingbat hex": "64", "Unicode dec": "128737", "Unicode hex": "1F6E1" }, + { "Typeface name": "Webdings", "Dingbat dec": "101", "Dingbat hex": "65", "Unicode dec": "128230", "Unicode hex": "1F4E6" }, + { "Typeface name": "Webdings", "Dingbat dec": "102", "Dingbat hex": "66", "Unicode dec": "128753", "Unicode hex": "1F6F1" }, + { "Typeface name": "Webdings", "Dingbat dec": "103", "Dingbat hex": "67", "Unicode dec": "11035", "Unicode hex": "2B1B" }, + { "Typeface name": "Webdings", "Dingbat dec": "104", "Dingbat hex": "68", "Unicode dec": "128657", "Unicode hex": "1F691" }, + { "Typeface name": "Webdings", "Dingbat dec": "105", "Dingbat hex": "69", "Unicode dec": "128712", "Unicode hex": "1F6C8" }, + { "Typeface name": "Webdings", "Dingbat dec": "106", "Dingbat hex": "6A", "Unicode dec": "128745", "Unicode hex": "1F6E9" }, + { "Typeface name": "Webdings", "Dingbat dec": "107", "Dingbat hex": "6B", "Unicode dec": "128752", "Unicode hex": "1F6F0" }, + { "Typeface name": "Webdings", "Dingbat dec": "108", "Dingbat hex": "6C", "Unicode dec": "128968", "Unicode hex": "1F7C8" }, + { "Typeface name": "Webdings", "Dingbat dec": "109", "Dingbat hex": "6D", "Unicode dec": "128372", "Unicode hex": "1F574" }, + { "Typeface name": "Webdings", "Dingbat dec": "110", "Dingbat hex": "6E", "Unicode dec": "11044", "Unicode hex": "2B24" }, + { "Typeface name": "Webdings", "Dingbat dec": "111", "Dingbat hex": "6F", "Unicode dec": "128741", "Unicode hex": "1F6E5" }, + { "Typeface name": "Webdings", "Dingbat dec": "112", "Dingbat hex": "70", "Unicode dec": "128660", "Unicode hex": "1F694" }, + { "Typeface name": "Webdings", "Dingbat dec": "113", "Dingbat hex": "71", "Unicode dec": "128472", "Unicode hex": "1F5D8" }, + { "Typeface name": "Webdings", "Dingbat dec": "114", "Dingbat hex": "72", "Unicode dec": "128473", "Unicode hex": "1F5D9" }, + { "Typeface name": "Webdings", "Dingbat dec": "115", "Dingbat hex": "73", "Unicode dec": "10067", "Unicode hex": "2753" }, + { "Typeface name": "Webdings", "Dingbat dec": "116", "Dingbat hex": "74", "Unicode dec": "128754", "Unicode hex": "1F6F2" }, + { "Typeface name": "Webdings", "Dingbat dec": "117", "Dingbat hex": "75", "Unicode dec": "128647", "Unicode hex": "1F687" }, + { "Typeface name": "Webdings", "Dingbat dec": "118", "Dingbat hex": "76", "Unicode dec": "128653", "Unicode hex": "1F68D" }, + { "Typeface name": "Webdings", "Dingbat dec": "119", "Dingbat hex": "77", "Unicode dec": "9971", "Unicode hex": "26F3" }, + { "Typeface name": "Webdings", "Dingbat dec": "120", "Dingbat hex": "78", "Unicode dec": "10680", "Unicode hex": "29B8" }, + { "Typeface name": "Webdings", "Dingbat dec": "121", "Dingbat hex": "79", "Unicode dec": "8854", "Unicode hex": "2296" }, + { "Typeface name": "Webdings", "Dingbat dec": "122", "Dingbat hex": "7A", "Unicode dec": "128685", "Unicode hex": "1F6AD" }, + { "Typeface name": "Webdings", "Dingbat dec": "123", "Dingbat hex": "7B", "Unicode dec": "128494", "Unicode hex": "1F5EE" }, + { "Typeface name": "Webdings", "Dingbat dec": "124", "Dingbat hex": "7C", "Unicode dec": "9168", "Unicode hex": "23D0" }, + { "Typeface name": "Webdings", "Dingbat dec": "125", "Dingbat hex": "7D", "Unicode dec": "128495", "Unicode hex": "1F5EF" }, + { "Typeface name": "Webdings", "Dingbat dec": "126", "Dingbat hex": "7E", "Unicode dec": "128498", "Unicode hex": "1F5F2" }, + { "Typeface name": "Webdings", "Dingbat dec": "128", "Dingbat hex": "80", "Unicode dec": "128697", "Unicode hex": "1F6B9" }, + { "Typeface name": "Webdings", "Dingbat dec": "129", "Dingbat hex": "81", "Unicode dec": "128698", "Unicode hex": "1F6BA" }, + { "Typeface name": "Webdings", "Dingbat dec": "130", "Dingbat hex": "82", "Unicode dec": "128713", "Unicode hex": "1F6C9" }, + { "Typeface name": "Webdings", "Dingbat dec": "131", "Dingbat hex": "83", "Unicode dec": "128714", "Unicode hex": "1F6CA" }, + { "Typeface name": "Webdings", "Dingbat dec": "132", "Dingbat hex": "84", "Unicode dec": "128700", "Unicode hex": "1F6BC" }, + { "Typeface name": "Webdings", "Dingbat dec": "133", "Dingbat hex": "85", "Unicode dec": "128125", "Unicode hex": "1F47D" }, + { "Typeface name": "Webdings", "Dingbat dec": "134", "Dingbat hex": "86", "Unicode dec": "127947", "Unicode hex": "1F3CB" }, + { "Typeface name": "Webdings", "Dingbat dec": "135", "Dingbat hex": "87", "Unicode dec": "9975", "Unicode hex": "26F7" }, + { "Typeface name": "Webdings", "Dingbat dec": "136", "Dingbat hex": "88", "Unicode dec": "127938", "Unicode hex": "1F3C2" }, + { "Typeface name": "Webdings", "Dingbat dec": "137", "Dingbat hex": "89", "Unicode dec": "127948", "Unicode hex": "1F3CC" }, + { "Typeface name": "Webdings", "Dingbat dec": "138", "Dingbat hex": "8A", "Unicode dec": "127946", "Unicode hex": "1F3CA" }, + { "Typeface name": "Webdings", "Dingbat dec": "139", "Dingbat hex": "8B", "Unicode dec": "127940", "Unicode hex": "1F3C4" }, + { "Typeface name": "Webdings", "Dingbat dec": "140", "Dingbat hex": "8C", "Unicode dec": "127949", "Unicode hex": "1F3CD" }, + { "Typeface name": "Webdings", "Dingbat dec": "141", "Dingbat hex": "8D", "Unicode dec": "127950", "Unicode hex": "1F3CE" }, + { "Typeface name": "Webdings", "Dingbat dec": "142", "Dingbat hex": "8E", "Unicode dec": "128664", "Unicode hex": "1F698" }, + { "Typeface name": "Webdings", "Dingbat dec": "143", "Dingbat hex": "8F", "Unicode dec": "128480", "Unicode hex": "1F5E0" }, + { "Typeface name": "Webdings", "Dingbat dec": "144", "Dingbat hex": "90", "Unicode dec": "128738", "Unicode hex": "1F6E2" }, + { "Typeface name": "Webdings", "Dingbat dec": "145", "Dingbat hex": "91", "Unicode dec": "128176", "Unicode hex": "1F4B0" }, + { "Typeface name": "Webdings", "Dingbat dec": "146", "Dingbat hex": "92", "Unicode dec": "127991", "Unicode hex": "1F3F7" }, + { "Typeface name": "Webdings", "Dingbat dec": "147", "Dingbat hex": "93", "Unicode dec": "128179", "Unicode hex": "1F4B3" }, + { "Typeface name": "Webdings", "Dingbat dec": "148", "Dingbat hex": "94", "Unicode dec": "128106", "Unicode hex": "1F46A" }, + { "Typeface name": "Webdings", "Dingbat dec": "149", "Dingbat hex": "95", "Unicode dec": "128481", "Unicode hex": "1F5E1" }, + { "Typeface name": "Webdings", "Dingbat dec": "150", "Dingbat hex": "96", "Unicode dec": "128482", "Unicode hex": "1F5E2" }, + { "Typeface name": "Webdings", "Dingbat dec": "151", "Dingbat hex": "97", "Unicode dec": "128483", "Unicode hex": "1F5E3" }, + { "Typeface name": "Webdings", "Dingbat dec": "152", "Dingbat hex": "98", "Unicode dec": "10031", "Unicode hex": "272F" }, + { "Typeface name": "Webdings", "Dingbat dec": "153", "Dingbat hex": "99", "Unicode dec": "128388", "Unicode hex": "1F584" }, + { "Typeface name": "Webdings", "Dingbat dec": "154", "Dingbat hex": "9A", "Unicode dec": "128389", "Unicode hex": "1F585" }, + { "Typeface name": "Webdings", "Dingbat dec": "155", "Dingbat hex": "9B", "Unicode dec": "128387", "Unicode hex": "1F583" }, + { "Typeface name": "Webdings", "Dingbat dec": "156", "Dingbat hex": "9C", "Unicode dec": "128390", "Unicode hex": "1F586" }, + { "Typeface name": "Webdings", "Dingbat dec": "157", "Dingbat hex": "9D", "Unicode dec": "128441", "Unicode hex": "1F5B9" }, + { "Typeface name": "Webdings", "Dingbat dec": "158", "Dingbat hex": "9E", "Unicode dec": "128442", "Unicode hex": "1F5BA" }, + { "Typeface name": "Webdings", "Dingbat dec": "159", "Dingbat hex": "9F", "Unicode dec": "128443", "Unicode hex": "1F5BB" }, + { "Typeface name": "Webdings", "Dingbat dec": "160", "Dingbat hex": "A0", "Unicode dec": "128373", "Unicode hex": "1F575" }, + { "Typeface name": "Webdings", "Dingbat dec": "161", "Dingbat hex": "A1", "Unicode dec": "128368", "Unicode hex": "1F570" }, + { "Typeface name": "Webdings", "Dingbat dec": "162", "Dingbat hex": "A2", "Unicode dec": "128445", "Unicode hex": "1F5BD" }, + { "Typeface name": "Webdings", "Dingbat dec": "163", "Dingbat hex": "A3", "Unicode dec": "128446", "Unicode hex": "1F5BE" }, + { "Typeface name": "Webdings", "Dingbat dec": "164", "Dingbat hex": "A4", "Unicode dec": "128203", "Unicode hex": "1F4CB" }, + { "Typeface name": "Webdings", "Dingbat dec": "165", "Dingbat hex": "A5", "Unicode dec": "128466", "Unicode hex": "1F5D2" }, + { "Typeface name": "Webdings", "Dingbat dec": "166", "Dingbat hex": "A6", "Unicode dec": "128467", "Unicode hex": "1F5D3" }, + { "Typeface name": "Webdings", "Dingbat dec": "167", "Dingbat hex": "A7", "Unicode dec": "128366", "Unicode hex": "1F56E" }, + { "Typeface name": "Webdings", "Dingbat dec": "168", "Dingbat hex": "A8", "Unicode dec": "128218", "Unicode hex": "1F4DA" }, + { "Typeface name": "Webdings", "Dingbat dec": "169", "Dingbat hex": "A9", "Unicode dec": "128478", "Unicode hex": "1F5DE" }, + { "Typeface name": "Webdings", "Dingbat dec": "170", "Dingbat hex": "AA", "Unicode dec": "128479", "Unicode hex": "1F5DF" }, + { "Typeface name": "Webdings", "Dingbat dec": "171", "Dingbat hex": "AB", "Unicode dec": "128451", "Unicode hex": "1F5C3" }, + { "Typeface name": "Webdings", "Dingbat dec": "172", "Dingbat hex": "AC", "Unicode dec": "128450", "Unicode hex": "1F5C2" }, + { "Typeface name": "Webdings", "Dingbat dec": "173", "Dingbat hex": "AD", "Unicode dec": "128444", "Unicode hex": "1F5BC" }, + { "Typeface name": "Webdings", "Dingbat dec": "174", "Dingbat hex": "AE", "Unicode dec": "127917", "Unicode hex": "1F3AD" }, + { "Typeface name": "Webdings", "Dingbat dec": "175", "Dingbat hex": "AF", "Unicode dec": "127900", "Unicode hex": "1F39C" }, + { "Typeface name": "Webdings", "Dingbat dec": "176", "Dingbat hex": "B0", "Unicode dec": "127896", "Unicode hex": "1F398" }, + { "Typeface name": "Webdings", "Dingbat dec": "177", "Dingbat hex": "B1", "Unicode dec": "127897", "Unicode hex": "1F399" }, + { "Typeface name": "Webdings", "Dingbat dec": "178", "Dingbat hex": "B2", "Unicode dec": "127911", "Unicode hex": "1F3A7" }, + { "Typeface name": "Webdings", "Dingbat dec": "179", "Dingbat hex": "B3", "Unicode dec": "128191", "Unicode hex": "1F4BF" }, + { "Typeface name": "Webdings", "Dingbat dec": "180", "Dingbat hex": "B4", "Unicode dec": "127902", "Unicode hex": "1F39E" }, + { "Typeface name": "Webdings", "Dingbat dec": "181", "Dingbat hex": "B5", "Unicode dec": "128247", "Unicode hex": "1F4F7" }, + { "Typeface name": "Webdings", "Dingbat dec": "182", "Dingbat hex": "B6", "Unicode dec": "127903", "Unicode hex": "1F39F" }, + { "Typeface name": "Webdings", "Dingbat dec": "183", "Dingbat hex": "B7", "Unicode dec": "127916", "Unicode hex": "1F3AC" }, + { "Typeface name": "Webdings", "Dingbat dec": "184", "Dingbat hex": "B8", "Unicode dec": "128253", "Unicode hex": "1F4FD" }, + { "Typeface name": "Webdings", "Dingbat dec": "185", "Dingbat hex": "B9", "Unicode dec": "128249", "Unicode hex": "1F4F9" }, + { "Typeface name": "Webdings", "Dingbat dec": "186", "Dingbat hex": "BA", "Unicode dec": "128254", "Unicode hex": "1F4FE" }, + { "Typeface name": "Webdings", "Dingbat dec": "187", "Dingbat hex": "BB", "Unicode dec": "128251", "Unicode hex": "1F4FB" }, + { "Typeface name": "Webdings", "Dingbat dec": "188", "Dingbat hex": "BC", "Unicode dec": "127898", "Unicode hex": "1F39A" }, + { "Typeface name": "Webdings", "Dingbat dec": "189", "Dingbat hex": "BD", "Unicode dec": "127899", "Unicode hex": "1F39B" }, + { "Typeface name": "Webdings", "Dingbat dec": "190", "Dingbat hex": "BE", "Unicode dec": "128250", "Unicode hex": "1F4FA" }, + { "Typeface name": "Webdings", "Dingbat dec": "191", "Dingbat hex": "BF", "Unicode dec": "128187", "Unicode hex": "1F4BB" }, + { "Typeface name": "Webdings", "Dingbat dec": "192", "Dingbat hex": "C0", "Unicode dec": "128421", "Unicode hex": "1F5A5" }, + { "Typeface name": "Webdings", "Dingbat dec": "193", "Dingbat hex": "C1", "Unicode dec": "128422", "Unicode hex": "1F5A6" }, + { "Typeface name": "Webdings", "Dingbat dec": "194", "Dingbat hex": "C2", "Unicode dec": "128423", "Unicode hex": "1F5A7" }, + { "Typeface name": "Webdings", "Dingbat dec": "195", "Dingbat hex": "C3", "Unicode dec": "128377", "Unicode hex": "1F579" }, + { "Typeface name": "Webdings", "Dingbat dec": "196", "Dingbat hex": "C4", "Unicode dec": "127918", "Unicode hex": "1F3AE" }, + { "Typeface name": "Webdings", "Dingbat dec": "197", "Dingbat hex": "C5", "Unicode dec": "128379", "Unicode hex": "1F57B" }, + { "Typeface name": "Webdings", "Dingbat dec": "198", "Dingbat hex": "C6", "Unicode dec": "128380", "Unicode hex": "1F57C" }, + { "Typeface name": "Webdings", "Dingbat dec": "199", "Dingbat hex": "C7", "Unicode dec": "128223", "Unicode hex": "1F4DF" }, + { "Typeface name": "Webdings", "Dingbat dec": "200", "Dingbat hex": "C8", "Unicode dec": "128385", "Unicode hex": "1F581" }, + { "Typeface name": "Webdings", "Dingbat dec": "201", "Dingbat hex": "C9", "Unicode dec": "128384", "Unicode hex": "1F580" }, + { "Typeface name": "Webdings", "Dingbat dec": "202", "Dingbat hex": "CA", "Unicode dec": "128424", "Unicode hex": "1F5A8" }, + { "Typeface name": "Webdings", "Dingbat dec": "203", "Dingbat hex": "CB", "Unicode dec": "128425", "Unicode hex": "1F5A9" }, + { "Typeface name": "Webdings", "Dingbat dec": "204", "Dingbat hex": "CC", "Unicode dec": "128447", "Unicode hex": "1F5BF" }, + { "Typeface name": "Webdings", "Dingbat dec": "205", "Dingbat hex": "CD", "Unicode dec": "128426", "Unicode hex": "1F5AA" }, + { "Typeface name": "Webdings", "Dingbat dec": "206", "Dingbat hex": "CE", "Unicode dec": "128476", "Unicode hex": "1F5DC" }, + { "Typeface name": "Webdings", "Dingbat dec": "207", "Dingbat hex": "CF", "Unicode dec": "128274", "Unicode hex": "1F512" }, + { "Typeface name": "Webdings", "Dingbat dec": "208", "Dingbat hex": "D0", "Unicode dec": "128275", "Unicode hex": "1F513" }, + { "Typeface name": "Webdings", "Dingbat dec": "209", "Dingbat hex": "D1", "Unicode dec": "128477", "Unicode hex": "1F5DD" }, + { "Typeface name": "Webdings", "Dingbat dec": "210", "Dingbat hex": "D2", "Unicode dec": "128229", "Unicode hex": "1F4E5" }, + { "Typeface name": "Webdings", "Dingbat dec": "211", "Dingbat hex": "D3", "Unicode dec": "128228", "Unicode hex": "1F4E4" }, + { "Typeface name": "Webdings", "Dingbat dec": "212", "Dingbat hex": "D4", "Unicode dec": "128371", "Unicode hex": "1F573" }, + { "Typeface name": "Webdings", "Dingbat dec": "213", "Dingbat hex": "D5", "Unicode dec": "127779", "Unicode hex": "1F323" }, + { "Typeface name": "Webdings", "Dingbat dec": "214", "Dingbat hex": "D6", "Unicode dec": "127780", "Unicode hex": "1F324" }, + { "Typeface name": "Webdings", "Dingbat dec": "215", "Dingbat hex": "D7", "Unicode dec": "127781", "Unicode hex": "1F325" }, + { "Typeface name": "Webdings", "Dingbat dec": "216", "Dingbat hex": "D8", "Unicode dec": "127782", "Unicode hex": "1F326" }, + { "Typeface name": "Webdings", "Dingbat dec": "217", "Dingbat hex": "D9", "Unicode dec": "9729", "Unicode hex": "2601" }, + { "Typeface name": "Webdings", "Dingbat dec": "218", "Dingbat hex": "DA", "Unicode dec": "127784", "Unicode hex": "1F328" }, + { "Typeface name": "Webdings", "Dingbat dec": "219", "Dingbat hex": "DB", "Unicode dec": "127783", "Unicode hex": "1F327" }, + { "Typeface name": "Webdings", "Dingbat dec": "220", "Dingbat hex": "DC", "Unicode dec": "127785", "Unicode hex": "1F329" }, + { "Typeface name": "Webdings", "Dingbat dec": "221", "Dingbat hex": "DD", "Unicode dec": "127786", "Unicode hex": "1F32A" }, + { "Typeface name": "Webdings", "Dingbat dec": "222", "Dingbat hex": "DE", "Unicode dec": "127788", "Unicode hex": "1F32C" }, + { "Typeface name": "Webdings", "Dingbat dec": "223", "Dingbat hex": "DF", "Unicode dec": "127787", "Unicode hex": "1F32B" }, + { "Typeface name": "Webdings", "Dingbat dec": "224", "Dingbat hex": "E0", "Unicode dec": "127772", "Unicode hex": "1F31C" }, + { "Typeface name": "Webdings", "Dingbat dec": "225", "Dingbat hex": "E1", "Unicode dec": "127777", "Unicode hex": "1F321" }, + { "Typeface name": "Webdings", "Dingbat dec": "226", "Dingbat hex": "E2", "Unicode dec": "128715", "Unicode hex": "1F6CB" }, + { "Typeface name": "Webdings", "Dingbat dec": "227", "Dingbat hex": "E3", "Unicode dec": "128719", "Unicode hex": "1F6CF" }, + { "Typeface name": "Webdings", "Dingbat dec": "228", "Dingbat hex": "E4", "Unicode dec": "127869", "Unicode hex": "1F37D" }, + { "Typeface name": "Webdings", "Dingbat dec": "229", "Dingbat hex": "E5", "Unicode dec": "127864", "Unicode hex": "1F378" }, + { "Typeface name": "Webdings", "Dingbat dec": "230", "Dingbat hex": "E6", "Unicode dec": "128718", "Unicode hex": "1F6CE" }, + { "Typeface name": "Webdings", "Dingbat dec": "231", "Dingbat hex": "E7", "Unicode dec": "128717", "Unicode hex": "1F6CD" }, + { "Typeface name": "Webdings", "Dingbat dec": "232", "Dingbat hex": "E8", "Unicode dec": "9413", "Unicode hex": "24C5" }, + { "Typeface name": "Webdings", "Dingbat dec": "233", "Dingbat hex": "E9", "Unicode dec": "9855", "Unicode hex": "267F" }, + { "Typeface name": "Webdings", "Dingbat dec": "234", "Dingbat hex": "EA", "Unicode dec": "128710", "Unicode hex": "1F6C6" }, + { "Typeface name": "Webdings", "Dingbat dec": "235", "Dingbat hex": "EB", "Unicode dec": "128392", "Unicode hex": "1F588" }, + { "Typeface name": "Webdings", "Dingbat dec": "236", "Dingbat hex": "EC", "Unicode dec": "127891", "Unicode hex": "1F393" }, + { "Typeface name": "Webdings", "Dingbat dec": "237", "Dingbat hex": "ED", "Unicode dec": "128484", "Unicode hex": "1F5E4" }, + { "Typeface name": "Webdings", "Dingbat dec": "238", "Dingbat hex": "EE", "Unicode dec": "128485", "Unicode hex": "1F5E5" }, + { "Typeface name": "Webdings", "Dingbat dec": "239", "Dingbat hex": "EF", "Unicode dec": "128486", "Unicode hex": "1F5E6" }, + { "Typeface name": "Webdings", "Dingbat dec": "240", "Dingbat hex": "F0", "Unicode dec": "128487", "Unicode hex": "1F5E7" }, + { "Typeface name": "Webdings", "Dingbat dec": "241", "Dingbat hex": "F1", "Unicode dec": "128746", "Unicode hex": "1F6EA" }, + { "Typeface name": "Webdings", "Dingbat dec": "242", "Dingbat hex": "F2", "Unicode dec": "128063", "Unicode hex": "1F43F" }, + { "Typeface name": "Webdings", "Dingbat dec": "243", "Dingbat hex": "F3", "Unicode dec": "128038", "Unicode hex": "1F426" }, + { "Typeface name": "Webdings", "Dingbat dec": "244", "Dingbat hex": "F4", "Unicode dec": "128031", "Unicode hex": "1F41F" }, + { "Typeface name": "Webdings", "Dingbat dec": "245", "Dingbat hex": "F5", "Unicode dec": "128021", "Unicode hex": "1F415" }, + { "Typeface name": "Webdings", "Dingbat dec": "246", "Dingbat hex": "F6", "Unicode dec": "128008", "Unicode hex": "1F408" }, + { "Typeface name": "Webdings", "Dingbat dec": "247", "Dingbat hex": "F7", "Unicode dec": "128620", "Unicode hex": "1F66C" }, + { "Typeface name": "Webdings", "Dingbat dec": "248", "Dingbat hex": "F8", "Unicode dec": "128622", "Unicode hex": "1F66E" }, + { "Typeface name": "Webdings", "Dingbat dec": "249", "Dingbat hex": "F9", "Unicode dec": "128621", "Unicode hex": "1F66D" }, + { "Typeface name": "Webdings", "Dingbat dec": "250", "Dingbat hex": "FA", "Unicode dec": "128623", "Unicode hex": "1F66F" }, + { "Typeface name": "Webdings", "Dingbat dec": "251", "Dingbat hex": "FB", "Unicode dec": "128506", "Unicode hex": "1F5FA" }, + { "Typeface name": "Webdings", "Dingbat dec": "252", "Dingbat hex": "FC", "Unicode dec": "127757", "Unicode hex": "1F30D" }, + { "Typeface name": "Webdings", "Dingbat dec": "253", "Dingbat hex": "FD", "Unicode dec": "127759", "Unicode hex": "1F30F" }, + { "Typeface name": "Webdings", "Dingbat dec": "254", "Dingbat hex": "FE", "Unicode dec": "127758", "Unicode hex": "1F30E" }, + { "Typeface name": "Webdings", "Dingbat dec": "255", "Dingbat hex": "FF", "Unicode dec": "128330", "Unicode hex": "1F54A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "32", "Dingbat hex": "20", "Unicode dec": "32", "Unicode hex": "20" }, + { "Typeface name": "Wingdings", "Dingbat dec": "33", "Dingbat hex": "21", "Unicode dec": "128393", "Unicode hex": "1F589" }, + { "Typeface name": "Wingdings", "Dingbat dec": "34", "Dingbat hex": "22", "Unicode dec": "9986", "Unicode hex": "2702" }, + { "Typeface name": "Wingdings", "Dingbat dec": "35", "Dingbat hex": "23", "Unicode dec": "9985", "Unicode hex": "2701" }, + { "Typeface name": "Wingdings", "Dingbat dec": "36", "Dingbat hex": "24", "Unicode dec": "128083", "Unicode hex": "1F453" }, + { "Typeface name": "Wingdings", "Dingbat dec": "37", "Dingbat hex": "25", "Unicode dec": "128365", "Unicode hex": "1F56D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "38", "Dingbat hex": "26", "Unicode dec": "128366", "Unicode hex": "1F56E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "39", "Dingbat hex": "27", "Unicode dec": "128367", "Unicode hex": "1F56F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "40", "Dingbat hex": "28", "Unicode dec": "128383", "Unicode hex": "1F57F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "41", "Dingbat hex": "29", "Unicode dec": "9990", "Unicode hex": "2706" }, + { "Typeface name": "Wingdings", "Dingbat dec": "42", "Dingbat hex": "2A", "Unicode dec": "128386", "Unicode hex": "1F582" }, + { "Typeface name": "Wingdings", "Dingbat dec": "43", "Dingbat hex": "2B", "Unicode dec": "128387", "Unicode hex": "1F583" }, + { "Typeface name": "Wingdings", "Dingbat dec": "44", "Dingbat hex": "2C", "Unicode dec": "128234", "Unicode hex": "1F4EA" }, + { "Typeface name": "Wingdings", "Dingbat dec": "45", "Dingbat hex": "2D", "Unicode dec": "128235", "Unicode hex": "1F4EB" }, + { "Typeface name": "Wingdings", "Dingbat dec": "46", "Dingbat hex": "2E", "Unicode dec": "128236", "Unicode hex": "1F4EC" }, + { "Typeface name": "Wingdings", "Dingbat dec": "47", "Dingbat hex": "2F", "Unicode dec": "128237", "Unicode hex": "1F4ED" }, + { "Typeface name": "Wingdings", "Dingbat dec": "48", "Dingbat hex": "30", "Unicode dec": "128448", "Unicode hex": "1F5C0" }, + { "Typeface name": "Wingdings", "Dingbat dec": "49", "Dingbat hex": "31", "Unicode dec": "128449", "Unicode hex": "1F5C1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "50", "Dingbat hex": "32", "Unicode dec": "128462", "Unicode hex": "1F5CE" }, + { "Typeface name": "Wingdings", "Dingbat dec": "51", "Dingbat hex": "33", "Unicode dec": "128463", "Unicode hex": "1F5CF" }, + { "Typeface name": "Wingdings", "Dingbat dec": "52", "Dingbat hex": "34", "Unicode dec": "128464", "Unicode hex": "1F5D0" }, + { "Typeface name": "Wingdings", "Dingbat dec": "53", "Dingbat hex": "35", "Unicode dec": "128452", "Unicode hex": "1F5C4" }, + { "Typeface name": "Wingdings", "Dingbat dec": "54", "Dingbat hex": "36", "Unicode dec": "8987", "Unicode hex": "231B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "55", "Dingbat hex": "37", "Unicode dec": "128430", "Unicode hex": "1F5AE" }, + { "Typeface name": "Wingdings", "Dingbat dec": "56", "Dingbat hex": "38", "Unicode dec": "128432", "Unicode hex": "1F5B0" }, + { "Typeface name": "Wingdings", "Dingbat dec": "57", "Dingbat hex": "39", "Unicode dec": "128434", "Unicode hex": "1F5B2" }, + { "Typeface name": "Wingdings", "Dingbat dec": "58", "Dingbat hex": "3A", "Unicode dec": "128435", "Unicode hex": "1F5B3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "59", "Dingbat hex": "3B", "Unicode dec": "128436", "Unicode hex": "1F5B4" }, + { "Typeface name": "Wingdings", "Dingbat dec": "60", "Dingbat hex": "3C", "Unicode dec": "128427", "Unicode hex": "1F5AB" }, + { "Typeface name": "Wingdings", "Dingbat dec": "61", "Dingbat hex": "3D", "Unicode dec": "128428", "Unicode hex": "1F5AC" }, + { "Typeface name": "Wingdings", "Dingbat dec": "62", "Dingbat hex": "3E", "Unicode dec": "9991", "Unicode hex": "2707" }, + { "Typeface name": "Wingdings", "Dingbat dec": "63", "Dingbat hex": "3F", "Unicode dec": "9997", "Unicode hex": "270D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "64", "Dingbat hex": "40", "Unicode dec": "128398", "Unicode hex": "1F58E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "65", "Dingbat hex": "41", "Unicode dec": "9996", "Unicode hex": "270C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "66", "Dingbat hex": "42", "Unicode dec": "128399", "Unicode hex": "1F58F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "67", "Dingbat hex": "43", "Unicode dec": "128077", "Unicode hex": "1F44D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "68", "Dingbat hex": "44", "Unicode dec": "128078", "Unicode hex": "1F44E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "69", "Dingbat hex": "45", "Unicode dec": "9756", "Unicode hex": "261C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "70", "Dingbat hex": "46", "Unicode dec": "9758", "Unicode hex": "261E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "71", "Dingbat hex": "47", "Unicode dec": "9757", "Unicode hex": "261D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "72", "Dingbat hex": "48", "Unicode dec": "9759", "Unicode hex": "261F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "73", "Dingbat hex": "49", "Unicode dec": "128400", "Unicode hex": "1F590" }, + { "Typeface name": "Wingdings", "Dingbat dec": "74", "Dingbat hex": "4A", "Unicode dec": "9786", "Unicode hex": "263A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "75", "Dingbat hex": "4B", "Unicode dec": "128528", "Unicode hex": "1F610" }, + { "Typeface name": "Wingdings", "Dingbat dec": "76", "Dingbat hex": "4C", "Unicode dec": "9785", "Unicode hex": "2639" }, + { "Typeface name": "Wingdings", "Dingbat dec": "77", "Dingbat hex": "4D", "Unicode dec": "128163", "Unicode hex": "1F4A3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "78", "Dingbat hex": "4E", "Unicode dec": "128369", "Unicode hex": "1F571" }, + { "Typeface name": "Wingdings", "Dingbat dec": "79", "Dingbat hex": "4F", "Unicode dec": "127987", "Unicode hex": "1F3F3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "80", "Dingbat hex": "50", "Unicode dec": "127985", "Unicode hex": "1F3F1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "81", "Dingbat hex": "51", "Unicode dec": "9992", "Unicode hex": "2708" }, + { "Typeface name": "Wingdings", "Dingbat dec": "82", "Dingbat hex": "52", "Unicode dec": "9788", "Unicode hex": "263C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "83", "Dingbat hex": "53", "Unicode dec": "127778", "Unicode hex": "1F322" }, + { "Typeface name": "Wingdings", "Dingbat dec": "84", "Dingbat hex": "54", "Unicode dec": "10052", "Unicode hex": "2744" }, + { "Typeface name": "Wingdings", "Dingbat dec": "85", "Dingbat hex": "55", "Unicode dec": "128326", "Unicode hex": "1F546" }, + { "Typeface name": "Wingdings", "Dingbat dec": "86", "Dingbat hex": "56", "Unicode dec": "10014", "Unicode hex": "271E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "87", "Dingbat hex": "57", "Unicode dec": "128328", "Unicode hex": "1F548" }, + { "Typeface name": "Wingdings", "Dingbat dec": "88", "Dingbat hex": "58", "Unicode dec": "10016", "Unicode hex": "2720" }, + { "Typeface name": "Wingdings", "Dingbat dec": "89", "Dingbat hex": "59", "Unicode dec": "10017", "Unicode hex": "2721" }, + { "Typeface name": "Wingdings", "Dingbat dec": "90", "Dingbat hex": "5A", "Unicode dec": "9770", "Unicode hex": "262A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "91", "Dingbat hex": "5B", "Unicode dec": "9775", "Unicode hex": "262F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "92", "Dingbat hex": "5C", "Unicode dec": "128329", "Unicode hex": "1F549" }, + { "Typeface name": "Wingdings", "Dingbat dec": "93", "Dingbat hex": "5D", "Unicode dec": "9784", "Unicode hex": "2638" }, + { "Typeface name": "Wingdings", "Dingbat dec": "94", "Dingbat hex": "5E", "Unicode dec": "9800", "Unicode hex": "2648" }, + { "Typeface name": "Wingdings", "Dingbat dec": "95", "Dingbat hex": "5F", "Unicode dec": "9801", "Unicode hex": "2649" }, + { "Typeface name": "Wingdings", "Dingbat dec": "96", "Dingbat hex": "60", "Unicode dec": "9802", "Unicode hex": "264A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "97", "Dingbat hex": "61", "Unicode dec": "9803", "Unicode hex": "264B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "98", "Dingbat hex": "62", "Unicode dec": "9804", "Unicode hex": "264C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "99", "Dingbat hex": "63", "Unicode dec": "9805", "Unicode hex": "264D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "100", "Dingbat hex": "64", "Unicode dec": "9806", "Unicode hex": "264E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "101", "Dingbat hex": "65", "Unicode dec": "9807", "Unicode hex": "264F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "102", "Dingbat hex": "66", "Unicode dec": "9808", "Unicode hex": "2650" }, + { "Typeface name": "Wingdings", "Dingbat dec": "103", "Dingbat hex": "67", "Unicode dec": "9809", "Unicode hex": "2651" }, + { "Typeface name": "Wingdings", "Dingbat dec": "104", "Dingbat hex": "68", "Unicode dec": "9810", "Unicode hex": "2652" }, + { "Typeface name": "Wingdings", "Dingbat dec": "105", "Dingbat hex": "69", "Unicode dec": "9811", "Unicode hex": "2653" }, + { "Typeface name": "Wingdings", "Dingbat dec": "106", "Dingbat hex": "6A", "Unicode dec": "128624", "Unicode hex": "1F670" }, + { "Typeface name": "Wingdings", "Dingbat dec": "107", "Dingbat hex": "6B", "Unicode dec": "128629", "Unicode hex": "1F675" }, + { "Typeface name": "Wingdings", "Dingbat dec": "108", "Dingbat hex": "6C", "Unicode dec": "9899", "Unicode hex": "26AB" }, + { "Typeface name": "Wingdings", "Dingbat dec": "109", "Dingbat hex": "6D", "Unicode dec": "128318", "Unicode hex": "1F53E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "110", "Dingbat hex": "6E", "Unicode dec": "9724", "Unicode hex": "25FC" }, + { "Typeface name": "Wingdings", "Dingbat dec": "111", "Dingbat hex": "6F", "Unicode dec": "128911", "Unicode hex": "1F78F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "112", "Dingbat hex": "70", "Unicode dec": "128912", "Unicode hex": "1F790" }, + { "Typeface name": "Wingdings", "Dingbat dec": "113", "Dingbat hex": "71", "Unicode dec": "10065", "Unicode hex": "2751" }, + { "Typeface name": "Wingdings", "Dingbat dec": "114", "Dingbat hex": "72", "Unicode dec": "10066", "Unicode hex": "2752" }, + { "Typeface name": "Wingdings", "Dingbat dec": "115", "Dingbat hex": "73", "Unicode dec": "128927", "Unicode hex": "1F79F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "116", "Dingbat hex": "74", "Unicode dec": "10731", "Unicode hex": "29EB" }, + { "Typeface name": "Wingdings", "Dingbat dec": "117", "Dingbat hex": "75", "Unicode dec": "9670", "Unicode hex": "25C6" }, + { "Typeface name": "Wingdings", "Dingbat dec": "118", "Dingbat hex": "76", "Unicode dec": "10070", "Unicode hex": "2756" }, + { "Typeface name": "Wingdings", "Dingbat dec": "119", "Dingbat hex": "77", "Unicode dec": "11049", "Unicode hex": "2B29" }, + { "Typeface name": "Wingdings", "Dingbat dec": "120", "Dingbat hex": "78", "Unicode dec": "8999", "Unicode hex": "2327" }, + { "Typeface name": "Wingdings", "Dingbat dec": "121", "Dingbat hex": "79", "Unicode dec": "11193", "Unicode hex": "2BB9" }, + { "Typeface name": "Wingdings", "Dingbat dec": "122", "Dingbat hex": "7A", "Unicode dec": "8984", "Unicode hex": "2318" }, + { "Typeface name": "Wingdings", "Dingbat dec": "123", "Dingbat hex": "7B", "Unicode dec": "127989", "Unicode hex": "1F3F5" }, + { "Typeface name": "Wingdings", "Dingbat dec": "124", "Dingbat hex": "7C", "Unicode dec": "127990", "Unicode hex": "1F3F6" }, + { "Typeface name": "Wingdings", "Dingbat dec": "125", "Dingbat hex": "7D", "Unicode dec": "128630", "Unicode hex": "1F676" }, + { "Typeface name": "Wingdings", "Dingbat dec": "126", "Dingbat hex": "7E", "Unicode dec": "128631", "Unicode hex": "1F677" }, + { "Typeface name": "Wingdings", "Dingbat dec": "127", "Dingbat hex": "7F", "Unicode dec": "9647", "Unicode hex": "25AF" }, + { "Typeface name": "Wingdings", "Dingbat dec": "128", "Dingbat hex": "80", "Unicode dec": "127243", "Unicode hex": "1F10B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "129", "Dingbat hex": "81", "Unicode dec": "10112", "Unicode hex": "2780" }, + { "Typeface name": "Wingdings", "Dingbat dec": "130", "Dingbat hex": "82", "Unicode dec": "10113", "Unicode hex": "2781" }, + { "Typeface name": "Wingdings", "Dingbat dec": "131", "Dingbat hex": "83", "Unicode dec": "10114", "Unicode hex": "2782" }, + { "Typeface name": "Wingdings", "Dingbat dec": "132", "Dingbat hex": "84", "Unicode dec": "10115", "Unicode hex": "2783" }, + { "Typeface name": "Wingdings", "Dingbat dec": "133", "Dingbat hex": "85", "Unicode dec": "10116", "Unicode hex": "2784" }, + { "Typeface name": "Wingdings", "Dingbat dec": "134", "Dingbat hex": "86", "Unicode dec": "10117", "Unicode hex": "2785" }, + { "Typeface name": "Wingdings", "Dingbat dec": "135", "Dingbat hex": "87", "Unicode dec": "10118", "Unicode hex": "2786" }, + { "Typeface name": "Wingdings", "Dingbat dec": "136", "Dingbat hex": "88", "Unicode dec": "10119", "Unicode hex": "2787" }, + { "Typeface name": "Wingdings", "Dingbat dec": "137", "Dingbat hex": "89", "Unicode dec": "10120", "Unicode hex": "2788" }, + { "Typeface name": "Wingdings", "Dingbat dec": "138", "Dingbat hex": "8A", "Unicode dec": "10121", "Unicode hex": "2789" }, + { "Typeface name": "Wingdings", "Dingbat dec": "139", "Dingbat hex": "8B", "Unicode dec": "127244", "Unicode hex": "1F10C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "140", "Dingbat hex": "8C", "Unicode dec": "10122", "Unicode hex": "278A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "141", "Dingbat hex": "8D", "Unicode dec": "10123", "Unicode hex": "278B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "142", "Dingbat hex": "8E", "Unicode dec": "10124", "Unicode hex": "278C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "143", "Dingbat hex": "8F", "Unicode dec": "10125", "Unicode hex": "278D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "144", "Dingbat hex": "90", "Unicode dec": "10126", "Unicode hex": "278E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "145", "Dingbat hex": "91", "Unicode dec": "10127", "Unicode hex": "278F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "146", "Dingbat hex": "92", "Unicode dec": "10128", "Unicode hex": "2790" }, + { "Typeface name": "Wingdings", "Dingbat dec": "147", "Dingbat hex": "93", "Unicode dec": "10129", "Unicode hex": "2791" }, + { "Typeface name": "Wingdings", "Dingbat dec": "148", "Dingbat hex": "94", "Unicode dec": "10130", "Unicode hex": "2792" }, + { "Typeface name": "Wingdings", "Dingbat dec": "149", "Dingbat hex": "95", "Unicode dec": "10131", "Unicode hex": "2793" }, + { "Typeface name": "Wingdings", "Dingbat dec": "150", "Dingbat hex": "96", "Unicode dec": "128610", "Unicode hex": "1F662" }, + { "Typeface name": "Wingdings", "Dingbat dec": "151", "Dingbat hex": "97", "Unicode dec": "128608", "Unicode hex": "1F660" }, + { "Typeface name": "Wingdings", "Dingbat dec": "152", "Dingbat hex": "98", "Unicode dec": "128609", "Unicode hex": "1F661" }, + { "Typeface name": "Wingdings", "Dingbat dec": "153", "Dingbat hex": "99", "Unicode dec": "128611", "Unicode hex": "1F663" }, + { "Typeface name": "Wingdings", "Dingbat dec": "154", "Dingbat hex": "9A", "Unicode dec": "128606", "Unicode hex": "1F65E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "155", "Dingbat hex": "9B", "Unicode dec": "128604", "Unicode hex": "1F65C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "156", "Dingbat hex": "9C", "Unicode dec": "128605", "Unicode hex": "1F65D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "157", "Dingbat hex": "9D", "Unicode dec": "128607", "Unicode hex": "1F65F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "158", "Dingbat hex": "9E", "Unicode dec": "8729", "Unicode hex": "2219" }, + { "Typeface name": "Wingdings", "Dingbat dec": "159", "Dingbat hex": "9F", "Unicode dec": "8226", "Unicode hex": "2022" }, + { "Typeface name": "Wingdings", "Dingbat dec": "160", "Dingbat hex": "A0", "Unicode dec": "11037", "Unicode hex": "2B1D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "161", "Dingbat hex": "A1", "Unicode dec": "11096", "Unicode hex": "2B58" }, + { "Typeface name": "Wingdings", "Dingbat dec": "162", "Dingbat hex": "A2", "Unicode dec": "128902", "Unicode hex": "1F786" }, + { "Typeface name": "Wingdings", "Dingbat dec": "163", "Dingbat hex": "A3", "Unicode dec": "128904", "Unicode hex": "1F788" }, + { "Typeface name": "Wingdings", "Dingbat dec": "164", "Dingbat hex": "A4", "Unicode dec": "128906", "Unicode hex": "1F78A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "165", "Dingbat hex": "A5", "Unicode dec": "128907", "Unicode hex": "1F78B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "166", "Dingbat hex": "A6", "Unicode dec": "128319", "Unicode hex": "1F53F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "167", "Dingbat hex": "A7", "Unicode dec": "9642", "Unicode hex": "25AA" }, + { "Typeface name": "Wingdings", "Dingbat dec": "168", "Dingbat hex": "A8", "Unicode dec": "128910", "Unicode hex": "1F78E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "169", "Dingbat hex": "A9", "Unicode dec": "128961", "Unicode hex": "1F7C1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "170", "Dingbat hex": "AA", "Unicode dec": "128965", "Unicode hex": "1F7C5" }, + { "Typeface name": "Wingdings", "Dingbat dec": "171", "Dingbat hex": "AB", "Unicode dec": "9733", "Unicode hex": "2605" }, + { "Typeface name": "Wingdings", "Dingbat dec": "172", "Dingbat hex": "AC", "Unicode dec": "128971", "Unicode hex": "1F7CB" }, + { "Typeface name": "Wingdings", "Dingbat dec": "173", "Dingbat hex": "AD", "Unicode dec": "128975", "Unicode hex": "1F7CF" }, + { "Typeface name": "Wingdings", "Dingbat dec": "174", "Dingbat hex": "AE", "Unicode dec": "128979", "Unicode hex": "1F7D3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "175", "Dingbat hex": "AF", "Unicode dec": "128977", "Unicode hex": "1F7D1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "176", "Dingbat hex": "B0", "Unicode dec": "11216", "Unicode hex": "2BD0" }, + { "Typeface name": "Wingdings", "Dingbat dec": "177", "Dingbat hex": "B1", "Unicode dec": "8982", "Unicode hex": "2316" }, + { "Typeface name": "Wingdings", "Dingbat dec": "178", "Dingbat hex": "B2", "Unicode dec": "11214", "Unicode hex": "2BCE" }, + { "Typeface name": "Wingdings", "Dingbat dec": "179", "Dingbat hex": "B3", "Unicode dec": "11215", "Unicode hex": "2BCF" }, + { "Typeface name": "Wingdings", "Dingbat dec": "180", "Dingbat hex": "B4", "Unicode dec": "11217", "Unicode hex": "2BD1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "181", "Dingbat hex": "B5", "Unicode dec": "10026", "Unicode hex": "272A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "182", "Dingbat hex": "B6", "Unicode dec": "10032", "Unicode hex": "2730" }, + { "Typeface name": "Wingdings", "Dingbat dec": "183", "Dingbat hex": "B7", "Unicode dec": "128336", "Unicode hex": "1F550" }, + { "Typeface name": "Wingdings", "Dingbat dec": "184", "Dingbat hex": "B8", "Unicode dec": "128337", "Unicode hex": "1F551" }, + { "Typeface name": "Wingdings", "Dingbat dec": "185", "Dingbat hex": "B9", "Unicode dec": "128338", "Unicode hex": "1F552" }, + { "Typeface name": "Wingdings", "Dingbat dec": "186", "Dingbat hex": "BA", "Unicode dec": "128339", "Unicode hex": "1F553" }, + { "Typeface name": "Wingdings", "Dingbat dec": "187", "Dingbat hex": "BB", "Unicode dec": "128340", "Unicode hex": "1F554" }, + { "Typeface name": "Wingdings", "Dingbat dec": "188", "Dingbat hex": "BC", "Unicode dec": "128341", "Unicode hex": "1F555" }, + { "Typeface name": "Wingdings", "Dingbat dec": "189", "Dingbat hex": "BD", "Unicode dec": "128342", "Unicode hex": "1F556" }, + { "Typeface name": "Wingdings", "Dingbat dec": "190", "Dingbat hex": "BE", "Unicode dec": "128343", "Unicode hex": "1F557" }, + { "Typeface name": "Wingdings", "Dingbat dec": "191", "Dingbat hex": "BF", "Unicode dec": "128344", "Unicode hex": "1F558" }, + { "Typeface name": "Wingdings", "Dingbat dec": "192", "Dingbat hex": "C0", "Unicode dec": "128345", "Unicode hex": "1F559" }, + { "Typeface name": "Wingdings", "Dingbat dec": "193", "Dingbat hex": "C1", "Unicode dec": "128346", "Unicode hex": "1F55A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "194", "Dingbat hex": "C2", "Unicode dec": "128347", "Unicode hex": "1F55B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "195", "Dingbat hex": "C3", "Unicode dec": "11184", "Unicode hex": "2BB0" }, + { "Typeface name": "Wingdings", "Dingbat dec": "196", "Dingbat hex": "C4", "Unicode dec": "11185", "Unicode hex": "2BB1" }, + { "Typeface name": "Wingdings", "Dingbat dec": "197", "Dingbat hex": "C5", "Unicode dec": "11186", "Unicode hex": "2BB2" }, + { "Typeface name": "Wingdings", "Dingbat dec": "198", "Dingbat hex": "C6", "Unicode dec": "11187", "Unicode hex": "2BB3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "199", "Dingbat hex": "C7", "Unicode dec": "11188", "Unicode hex": "2BB4" }, + { "Typeface name": "Wingdings", "Dingbat dec": "200", "Dingbat hex": "C8", "Unicode dec": "11189", "Unicode hex": "2BB5" }, + { "Typeface name": "Wingdings", "Dingbat dec": "201", "Dingbat hex": "C9", "Unicode dec": "11190", "Unicode hex": "2BB6" }, + { "Typeface name": "Wingdings", "Dingbat dec": "202", "Dingbat hex": "CA", "Unicode dec": "11191", "Unicode hex": "2BB7" }, + { "Typeface name": "Wingdings", "Dingbat dec": "203", "Dingbat hex": "CB", "Unicode dec": "128618", "Unicode hex": "1F66A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "204", "Dingbat hex": "CC", "Unicode dec": "128619", "Unicode hex": "1F66B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "205", "Dingbat hex": "CD", "Unicode dec": "128597", "Unicode hex": "1F655" }, + { "Typeface name": "Wingdings", "Dingbat dec": "206", "Dingbat hex": "CE", "Unicode dec": "128596", "Unicode hex": "1F654" }, + { "Typeface name": "Wingdings", "Dingbat dec": "207", "Dingbat hex": "CF", "Unicode dec": "128599", "Unicode hex": "1F657" }, + { "Typeface name": "Wingdings", "Dingbat dec": "208", "Dingbat hex": "D0", "Unicode dec": "128598", "Unicode hex": "1F656" }, + { "Typeface name": "Wingdings", "Dingbat dec": "209", "Dingbat hex": "D1", "Unicode dec": "128592", "Unicode hex": "1F650" }, + { "Typeface name": "Wingdings", "Dingbat dec": "210", "Dingbat hex": "D2", "Unicode dec": "128593", "Unicode hex": "1F651" }, + { "Typeface name": "Wingdings", "Dingbat dec": "211", "Dingbat hex": "D3", "Unicode dec": "128594", "Unicode hex": "1F652" }, + { "Typeface name": "Wingdings", "Dingbat dec": "212", "Dingbat hex": "D4", "Unicode dec": "128595", "Unicode hex": "1F653" }, + { "Typeface name": "Wingdings", "Dingbat dec": "213", "Dingbat hex": "D5", "Unicode dec": "9003", "Unicode hex": "232B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "214", "Dingbat hex": "D6", "Unicode dec": "8998", "Unicode hex": "2326" }, + { "Typeface name": "Wingdings", "Dingbat dec": "215", "Dingbat hex": "D7", "Unicode dec": "11160", "Unicode hex": "2B98" }, + { "Typeface name": "Wingdings", "Dingbat dec": "216", "Dingbat hex": "D8", "Unicode dec": "11162", "Unicode hex": "2B9A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "217", "Dingbat hex": "D9", "Unicode dec": "11161", "Unicode hex": "2B99" }, + { "Typeface name": "Wingdings", "Dingbat dec": "218", "Dingbat hex": "DA", "Unicode dec": "11163", "Unicode hex": "2B9B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "219", "Dingbat hex": "DB", "Unicode dec": "11144", "Unicode hex": "2B88" }, + { "Typeface name": "Wingdings", "Dingbat dec": "220", "Dingbat hex": "DC", "Unicode dec": "11146", "Unicode hex": "2B8A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "221", "Dingbat hex": "DD", "Unicode dec": "11145", "Unicode hex": "2B89" }, + { "Typeface name": "Wingdings", "Dingbat dec": "222", "Dingbat hex": "DE", "Unicode dec": "11147", "Unicode hex": "2B8B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "223", "Dingbat hex": "DF", "Unicode dec": "129128", "Unicode hex": "1F868" }, + { "Typeface name": "Wingdings", "Dingbat dec": "224", "Dingbat hex": "E0", "Unicode dec": "129130", "Unicode hex": "1F86A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "225", "Dingbat hex": "E1", "Unicode dec": "129129", "Unicode hex": "1F869" }, + { "Typeface name": "Wingdings", "Dingbat dec": "226", "Dingbat hex": "E2", "Unicode dec": "129131", "Unicode hex": "1F86B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "227", "Dingbat hex": "E3", "Unicode dec": "129132", "Unicode hex": "1F86C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "228", "Dingbat hex": "E4", "Unicode dec": "129133", "Unicode hex": "1F86D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "229", "Dingbat hex": "E5", "Unicode dec": "129135", "Unicode hex": "1F86F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "230", "Dingbat hex": "E6", "Unicode dec": "129134", "Unicode hex": "1F86E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "231", "Dingbat hex": "E7", "Unicode dec": "129144", "Unicode hex": "1F878" }, + { "Typeface name": "Wingdings", "Dingbat dec": "232", "Dingbat hex": "E8", "Unicode dec": "129146", "Unicode hex": "1F87A" }, + { "Typeface name": "Wingdings", "Dingbat dec": "233", "Dingbat hex": "E9", "Unicode dec": "129145", "Unicode hex": "1F879" }, + { "Typeface name": "Wingdings", "Dingbat dec": "234", "Dingbat hex": "EA", "Unicode dec": "129147", "Unicode hex": "1F87B" }, + { "Typeface name": "Wingdings", "Dingbat dec": "235", "Dingbat hex": "EB", "Unicode dec": "129148", "Unicode hex": "1F87C" }, + { "Typeface name": "Wingdings", "Dingbat dec": "236", "Dingbat hex": "EC", "Unicode dec": "129149", "Unicode hex": "1F87D" }, + { "Typeface name": "Wingdings", "Dingbat dec": "237", "Dingbat hex": "ED", "Unicode dec": "129151", "Unicode hex": "1F87F" }, + { "Typeface name": "Wingdings", "Dingbat dec": "238", "Dingbat hex": "EE", "Unicode dec": "129150", "Unicode hex": "1F87E" }, + { "Typeface name": "Wingdings", "Dingbat dec": "239", "Dingbat hex": "EF", "Unicode dec": "8678", "Unicode hex": "21E6" }, + { "Typeface name": "Wingdings", "Dingbat dec": "240", "Dingbat hex": "F0", "Unicode dec": "8680", "Unicode hex": "21E8" }, + { "Typeface name": "Wingdings", "Dingbat dec": "241", "Dingbat hex": "F1", "Unicode dec": "8679", "Unicode hex": "21E7" }, + { "Typeface name": "Wingdings", "Dingbat dec": "242", "Dingbat hex": "F2", "Unicode dec": "8681", "Unicode hex": "21E9" }, + { "Typeface name": "Wingdings", "Dingbat dec": "243", "Dingbat hex": "F3", "Unicode dec": "11012", "Unicode hex": "2B04" }, + { "Typeface name": "Wingdings", "Dingbat dec": "244", "Dingbat hex": "F4", "Unicode dec": "8691", "Unicode hex": "21F3" }, + { "Typeface name": "Wingdings", "Dingbat dec": "245", "Dingbat hex": "F5", "Unicode dec": "11009", "Unicode hex": "2B01" }, + { "Typeface name": "Wingdings", "Dingbat dec": "246", "Dingbat hex": "F6", "Unicode dec": "11008", "Unicode hex": "2B00" }, + { "Typeface name": "Wingdings", "Dingbat dec": "247", "Dingbat hex": "F7", "Unicode dec": "11011", "Unicode hex": "2B03" }, + { "Typeface name": "Wingdings", "Dingbat dec": "248", "Dingbat hex": "F8", "Unicode dec": "11010", "Unicode hex": "2B02" }, + { "Typeface name": "Wingdings", "Dingbat dec": "249", "Dingbat hex": "F9", "Unicode dec": "129196", "Unicode hex": "1F8AC" }, + { "Typeface name": "Wingdings", "Dingbat dec": "250", "Dingbat hex": "FA", "Unicode dec": "129197", "Unicode hex": "1F8AD" }, + { "Typeface name": "Wingdings", "Dingbat dec": "251", "Dingbat hex": "FB", "Unicode dec": "128502", "Unicode hex": "1F5F6" }, + { "Typeface name": "Wingdings", "Dingbat dec": "252", "Dingbat hex": "FC", "Unicode dec": "10003", "Unicode hex": "2713" }, + { "Typeface name": "Wingdings", "Dingbat dec": "253", "Dingbat hex": "FD", "Unicode dec": "128503", "Unicode hex": "1F5F7" }, + { "Typeface name": "Wingdings", "Dingbat dec": "254", "Dingbat hex": "FE", "Unicode dec": "128505", "Unicode hex": "1F5F9" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "32", "Dingbat hex": "20", "Unicode dec": "32", "Unicode hex": "20" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "33", "Dingbat hex": "21", "Unicode dec": "128394", "Unicode hex": "1F58A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "34", "Dingbat hex": "22", "Unicode dec": "128395", "Unicode hex": "1F58B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "35", "Dingbat hex": "23", "Unicode dec": "128396", "Unicode hex": "1F58C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "36", "Dingbat hex": "24", "Unicode dec": "128397", "Unicode hex": "1F58D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "37", "Dingbat hex": "25", "Unicode dec": "9988", "Unicode hex": "2704" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "38", "Dingbat hex": "26", "Unicode dec": "9984", "Unicode hex": "2700" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "39", "Dingbat hex": "27", "Unicode dec": "128382", "Unicode hex": "1F57E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "40", "Dingbat hex": "28", "Unicode dec": "128381", "Unicode hex": "1F57D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "41", "Dingbat hex": "29", "Unicode dec": "128453", "Unicode hex": "1F5C5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "42", "Dingbat hex": "2A", "Unicode dec": "128454", "Unicode hex": "1F5C6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "43", "Dingbat hex": "2B", "Unicode dec": "128455", "Unicode hex": "1F5C7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "44", "Dingbat hex": "2C", "Unicode dec": "128456", "Unicode hex": "1F5C8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "45", "Dingbat hex": "2D", "Unicode dec": "128457", "Unicode hex": "1F5C9" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "46", "Dingbat hex": "2E", "Unicode dec": "128458", "Unicode hex": "1F5CA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "47", "Dingbat hex": "2F", "Unicode dec": "128459", "Unicode hex": "1F5CB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "48", "Dingbat hex": "30", "Unicode dec": "128460", "Unicode hex": "1F5CC" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "49", "Dingbat hex": "31", "Unicode dec": "128461", "Unicode hex": "1F5CD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "50", "Dingbat hex": "32", "Unicode dec": "128203", "Unicode hex": "1F4CB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "51", "Dingbat hex": "33", "Unicode dec": "128465", "Unicode hex": "1F5D1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "52", "Dingbat hex": "34", "Unicode dec": "128468", "Unicode hex": "1F5D4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "53", "Dingbat hex": "35", "Unicode dec": "128437", "Unicode hex": "1F5B5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "54", "Dingbat hex": "36", "Unicode dec": "128438", "Unicode hex": "1F5B6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "55", "Dingbat hex": "37", "Unicode dec": "128439", "Unicode hex": "1F5B7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "56", "Dingbat hex": "38", "Unicode dec": "128440", "Unicode hex": "1F5B8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "57", "Dingbat hex": "39", "Unicode dec": "128429", "Unicode hex": "1F5AD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "58", "Dingbat hex": "3A", "Unicode dec": "128431", "Unicode hex": "1F5AF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "59", "Dingbat hex": "3B", "Unicode dec": "128433", "Unicode hex": "1F5B1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "60", "Dingbat hex": "3C", "Unicode dec": "128402", "Unicode hex": "1F592" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "61", "Dingbat hex": "3D", "Unicode dec": "128403", "Unicode hex": "1F593" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "62", "Dingbat hex": "3E", "Unicode dec": "128408", "Unicode hex": "1F598" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "63", "Dingbat hex": "3F", "Unicode dec": "128409", "Unicode hex": "1F599" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "64", "Dingbat hex": "40", "Unicode dec": "128410", "Unicode hex": "1F59A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "65", "Dingbat hex": "41", "Unicode dec": "128411", "Unicode hex": "1F59B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "66", "Dingbat hex": "42", "Unicode dec": "128072", "Unicode hex": "1F448" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "67", "Dingbat hex": "43", "Unicode dec": "128073", "Unicode hex": "1F449" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "68", "Dingbat hex": "44", "Unicode dec": "128412", "Unicode hex": "1F59C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "69", "Dingbat hex": "45", "Unicode dec": "128413", "Unicode hex": "1F59D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "70", "Dingbat hex": "46", "Unicode dec": "128414", "Unicode hex": "1F59E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "71", "Dingbat hex": "47", "Unicode dec": "128415", "Unicode hex": "1F59F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "72", "Dingbat hex": "48", "Unicode dec": "128416", "Unicode hex": "1F5A0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "73", "Dingbat hex": "49", "Unicode dec": "128417", "Unicode hex": "1F5A1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "74", "Dingbat hex": "4A", "Unicode dec": "128070", "Unicode hex": "1F446" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "75", "Dingbat hex": "4B", "Unicode dec": "128071", "Unicode hex": "1F447" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "76", "Dingbat hex": "4C", "Unicode dec": "128418", "Unicode hex": "1F5A2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "77", "Dingbat hex": "4D", "Unicode dec": "128419", "Unicode hex": "1F5A3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "78", "Dingbat hex": "4E", "Unicode dec": "128401", "Unicode hex": "1F591" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "79", "Dingbat hex": "4F", "Unicode dec": "128500", "Unicode hex": "1F5F4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "80", "Dingbat hex": "50", "Unicode dec": "128504", "Unicode hex": "1F5F8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "81", "Dingbat hex": "51", "Unicode dec": "128501", "Unicode hex": "1F5F5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "82", "Dingbat hex": "52", "Unicode dec": "9745", "Unicode hex": "2611" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "83", "Dingbat hex": "53", "Unicode dec": "11197", "Unicode hex": "2BBD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "84", "Dingbat hex": "54", "Unicode dec": "9746", "Unicode hex": "2612" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "85", "Dingbat hex": "55", "Unicode dec": "11198", "Unicode hex": "2BBE" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "86", "Dingbat hex": "56", "Unicode dec": "11199", "Unicode hex": "2BBF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "87", "Dingbat hex": "57", "Unicode dec": "128711", "Unicode hex": "1F6C7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "88", "Dingbat hex": "58", "Unicode dec": "10680", "Unicode hex": "29B8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "89", "Dingbat hex": "59", "Unicode dec": "128625", "Unicode hex": "1F671" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "90", "Dingbat hex": "5A", "Unicode dec": "128628", "Unicode hex": "1F674" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "91", "Dingbat hex": "5B", "Unicode dec": "128626", "Unicode hex": "1F672" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "92", "Dingbat hex": "5C", "Unicode dec": "128627", "Unicode hex": "1F673" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "93", "Dingbat hex": "5D", "Unicode dec": "8253", "Unicode hex": "203D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "94", "Dingbat hex": "5E", "Unicode dec": "128633", "Unicode hex": "1F679" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "95", "Dingbat hex": "5F", "Unicode dec": "128634", "Unicode hex": "1F67A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "96", "Dingbat hex": "60", "Unicode dec": "128635", "Unicode hex": "1F67B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "97", "Dingbat hex": "61", "Unicode dec": "128614", "Unicode hex": "1F666" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "98", "Dingbat hex": "62", "Unicode dec": "128612", "Unicode hex": "1F664" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "99", "Dingbat hex": "63", "Unicode dec": "128613", "Unicode hex": "1F665" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "100", "Dingbat hex": "64", "Unicode dec": "128615", "Unicode hex": "1F667" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "101", "Dingbat hex": "65", "Unicode dec": "128602", "Unicode hex": "1F65A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "102", "Dingbat hex": "66", "Unicode dec": "128600", "Unicode hex": "1F658" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "103", "Dingbat hex": "67", "Unicode dec": "128601", "Unicode hex": "1F659" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "104", "Dingbat hex": "68", "Unicode dec": "128603", "Unicode hex": "1F65B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "105", "Dingbat hex": "69", "Unicode dec": "9450", "Unicode hex": "24EA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "106", "Dingbat hex": "6A", "Unicode dec": "9312", "Unicode hex": "2460" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "107", "Dingbat hex": "6B", "Unicode dec": "9313", "Unicode hex": "2461" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "108", "Dingbat hex": "6C", "Unicode dec": "9314", "Unicode hex": "2462" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "109", "Dingbat hex": "6D", "Unicode dec": "9315", "Unicode hex": "2463" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "110", "Dingbat hex": "6E", "Unicode dec": "9316", "Unicode hex": "2464" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "111", "Dingbat hex": "6F", "Unicode dec": "9317", "Unicode hex": "2465" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "112", "Dingbat hex": "70", "Unicode dec": "9318", "Unicode hex": "2466" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "113", "Dingbat hex": "71", "Unicode dec": "9319", "Unicode hex": "2467" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "114", "Dingbat hex": "72", "Unicode dec": "9320", "Unicode hex": "2468" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "115", "Dingbat hex": "73", "Unicode dec": "9321", "Unicode hex": "2469" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "116", "Dingbat hex": "74", "Unicode dec": "9471", "Unicode hex": "24FF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "117", "Dingbat hex": "75", "Unicode dec": "10102", "Unicode hex": "2776" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "118", "Dingbat hex": "76", "Unicode dec": "10103", "Unicode hex": "2777" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "119", "Dingbat hex": "77", "Unicode dec": "10104", "Unicode hex": "2778" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "120", "Dingbat hex": "78", "Unicode dec": "10105", "Unicode hex": "2779" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "121", "Dingbat hex": "79", "Unicode dec": "10106", "Unicode hex": "277A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "122", "Dingbat hex": "7A", "Unicode dec": "10107", "Unicode hex": "277B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "123", "Dingbat hex": "7B", "Unicode dec": "10108", "Unicode hex": "277C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "124", "Dingbat hex": "7C", "Unicode dec": "10109", "Unicode hex": "277D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "125", "Dingbat hex": "7D", "Unicode dec": "10110", "Unicode hex": "277E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "126", "Dingbat hex": "7E", "Unicode dec": "10111", "Unicode hex": "277F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "128", "Dingbat hex": "80", "Unicode dec": "9737", "Unicode hex": "2609" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "129", "Dingbat hex": "81", "Unicode dec": "127765", "Unicode hex": "1F315" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "130", "Dingbat hex": "82", "Unicode dec": "9789", "Unicode hex": "263D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "131", "Dingbat hex": "83", "Unicode dec": "9790", "Unicode hex": "263E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "132", "Dingbat hex": "84", "Unicode dec": "11839", "Unicode hex": "2E3F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "133", "Dingbat hex": "85", "Unicode dec": "10013", "Unicode hex": "271D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "134", "Dingbat hex": "86", "Unicode dec": "128327", "Unicode hex": "1F547" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "135", "Dingbat hex": "87", "Unicode dec": "128348", "Unicode hex": "1F55C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "136", "Dingbat hex": "88", "Unicode dec": "128349", "Unicode hex": "1F55D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "137", "Dingbat hex": "89", "Unicode dec": "128350", "Unicode hex": "1F55E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "138", "Dingbat hex": "8A", "Unicode dec": "128351", "Unicode hex": "1F55F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "139", "Dingbat hex": "8B", "Unicode dec": "128352", "Unicode hex": "1F560" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "140", "Dingbat hex": "8C", "Unicode dec": "128353", "Unicode hex": "1F561" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "141", "Dingbat hex": "8D", "Unicode dec": "128354", "Unicode hex": "1F562" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "142", "Dingbat hex": "8E", "Unicode dec": "128355", "Unicode hex": "1F563" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "143", "Dingbat hex": "8F", "Unicode dec": "128356", "Unicode hex": "1F564" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "144", "Dingbat hex": "90", "Unicode dec": "128357", "Unicode hex": "1F565" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "145", "Dingbat hex": "91", "Unicode dec": "128358", "Unicode hex": "1F566" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "146", "Dingbat hex": "92", "Unicode dec": "128359", "Unicode hex": "1F567" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "147", "Dingbat hex": "93", "Unicode dec": "128616", "Unicode hex": "1F668" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "148", "Dingbat hex": "94", "Unicode dec": "128617", "Unicode hex": "1F669" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "149", "Dingbat hex": "95", "Unicode dec": "8901", "Unicode hex": "22C5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "150", "Dingbat hex": "96", "Unicode dec": "128900", "Unicode hex": "1F784" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "151", "Dingbat hex": "97", "Unicode dec": "10625", "Unicode hex": "2981" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "152", "Dingbat hex": "98", "Unicode dec": "9679", "Unicode hex": "25CF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "153", "Dingbat hex": "99", "Unicode dec": "9675", "Unicode hex": "25CB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "154", "Dingbat hex": "9A", "Unicode dec": "128901", "Unicode hex": "1F785" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "155", "Dingbat hex": "9B", "Unicode dec": "128903", "Unicode hex": "1F787" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "156", "Dingbat hex": "9C", "Unicode dec": "128905", "Unicode hex": "1F789" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "157", "Dingbat hex": "9D", "Unicode dec": "8857", "Unicode hex": "2299" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "158", "Dingbat hex": "9E", "Unicode dec": "10687", "Unicode hex": "29BF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "159", "Dingbat hex": "9F", "Unicode dec": "128908", "Unicode hex": "1F78C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "160", "Dingbat hex": "A0", "Unicode dec": "128909", "Unicode hex": "1F78D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "161", "Dingbat hex": "A1", "Unicode dec": "9726", "Unicode hex": "25FE" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "162", "Dingbat hex": "A2", "Unicode dec": "9632", "Unicode hex": "25A0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "163", "Dingbat hex": "A3", "Unicode dec": "9633", "Unicode hex": "25A1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "164", "Dingbat hex": "A4", "Unicode dec": "128913", "Unicode hex": "1F791" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "165", "Dingbat hex": "A5", "Unicode dec": "128914", "Unicode hex": "1F792" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "166", "Dingbat hex": "A6", "Unicode dec": "128915", "Unicode hex": "1F793" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "167", "Dingbat hex": "A7", "Unicode dec": "128916", "Unicode hex": "1F794" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "168", "Dingbat hex": "A8", "Unicode dec": "9635", "Unicode hex": "25A3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "169", "Dingbat hex": "A9", "Unicode dec": "128917", "Unicode hex": "1F795" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "170", "Dingbat hex": "AA", "Unicode dec": "128918", "Unicode hex": "1F796" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "171", "Dingbat hex": "AB", "Unicode dec": "128919", "Unicode hex": "1F797" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "172", "Dingbat hex": "AC", "Unicode dec": "128920", "Unicode hex": "1F798" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "173", "Dingbat hex": "AD", "Unicode dec": "11049", "Unicode hex": "2B29" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "174", "Dingbat hex": "AE", "Unicode dec": "11045", "Unicode hex": "2B25" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "175", "Dingbat hex": "AF", "Unicode dec": "9671", "Unicode hex": "25C7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "176", "Dingbat hex": "B0", "Unicode dec": "128922", "Unicode hex": "1F79A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "177", "Dingbat hex": "B1", "Unicode dec": "9672", "Unicode hex": "25C8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "178", "Dingbat hex": "B2", "Unicode dec": "128923", "Unicode hex": "1F79B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "179", "Dingbat hex": "B3", "Unicode dec": "128924", "Unicode hex": "1F79C" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "180", "Dingbat hex": "B4", "Unicode dec": "128925", "Unicode hex": "1F79D" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "181", "Dingbat hex": "B5", "Unicode dec": "128926", "Unicode hex": "1F79E" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "182", "Dingbat hex": "B6", "Unicode dec": "11050", "Unicode hex": "2B2A" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "183", "Dingbat hex": "B7", "Unicode dec": "11047", "Unicode hex": "2B27" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "184", "Dingbat hex": "B8", "Unicode dec": "9674", "Unicode hex": "25CA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "185", "Dingbat hex": "B9", "Unicode dec": "128928", "Unicode hex": "1F7A0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "186", "Dingbat hex": "BA", "Unicode dec": "9686", "Unicode hex": "25D6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "187", "Dingbat hex": "BB", "Unicode dec": "9687", "Unicode hex": "25D7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "188", "Dingbat hex": "BC", "Unicode dec": "11210", "Unicode hex": "2BCA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "189", "Dingbat hex": "BD", "Unicode dec": "11211", "Unicode hex": "2BCB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "190", "Dingbat hex": "BE", "Unicode dec": "11200", "Unicode hex": "2BC0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "191", "Dingbat hex": "BF", "Unicode dec": "11201", "Unicode hex": "2BC1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "192", "Dingbat hex": "C0", "Unicode dec": "11039", "Unicode hex": "2B1F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "193", "Dingbat hex": "C1", "Unicode dec": "11202", "Unicode hex": "2BC2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "194", "Dingbat hex": "C2", "Unicode dec": "11043", "Unicode hex": "2B23" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "195", "Dingbat hex": "C3", "Unicode dec": "11042", "Unicode hex": "2B22" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "196", "Dingbat hex": "C4", "Unicode dec": "11203", "Unicode hex": "2BC3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "197", "Dingbat hex": "C5", "Unicode dec": "11204", "Unicode hex": "2BC4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "198", "Dingbat hex": "C6", "Unicode dec": "128929", "Unicode hex": "1F7A1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "199", "Dingbat hex": "C7", "Unicode dec": "128930", "Unicode hex": "1F7A2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "200", "Dingbat hex": "C8", "Unicode dec": "128931", "Unicode hex": "1F7A3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "201", "Dingbat hex": "C9", "Unicode dec": "128932", "Unicode hex": "1F7A4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "202", "Dingbat hex": "CA", "Unicode dec": "128933", "Unicode hex": "1F7A5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "203", "Dingbat hex": "CB", "Unicode dec": "128934", "Unicode hex": "1F7A6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "204", "Dingbat hex": "CC", "Unicode dec": "128935", "Unicode hex": "1F7A7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "205", "Dingbat hex": "CD", "Unicode dec": "128936", "Unicode hex": "1F7A8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "206", "Dingbat hex": "CE", "Unicode dec": "128937", "Unicode hex": "1F7A9" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "207", "Dingbat hex": "CF", "Unicode dec": "128938", "Unicode hex": "1F7AA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "208", "Dingbat hex": "D0", "Unicode dec": "128939", "Unicode hex": "1F7AB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "209", "Dingbat hex": "D1", "Unicode dec": "128940", "Unicode hex": "1F7AC" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "210", "Dingbat hex": "D2", "Unicode dec": "128941", "Unicode hex": "1F7AD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "211", "Dingbat hex": "D3", "Unicode dec": "128942", "Unicode hex": "1F7AE" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "212", "Dingbat hex": "D4", "Unicode dec": "128943", "Unicode hex": "1F7AF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "213", "Dingbat hex": "D5", "Unicode dec": "128944", "Unicode hex": "1F7B0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "214", "Dingbat hex": "D6", "Unicode dec": "128945", "Unicode hex": "1F7B1" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "215", "Dingbat hex": "D7", "Unicode dec": "128946", "Unicode hex": "1F7B2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "216", "Dingbat hex": "D8", "Unicode dec": "128947", "Unicode hex": "1F7B3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "217", "Dingbat hex": "D9", "Unicode dec": "128948", "Unicode hex": "1F7B4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "218", "Dingbat hex": "DA", "Unicode dec": "128949", "Unicode hex": "1F7B5" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "219", "Dingbat hex": "DB", "Unicode dec": "128950", "Unicode hex": "1F7B6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "220", "Dingbat hex": "DC", "Unicode dec": "128951", "Unicode hex": "1F7B7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "221", "Dingbat hex": "DD", "Unicode dec": "128952", "Unicode hex": "1F7B8" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "222", "Dingbat hex": "DE", "Unicode dec": "128953", "Unicode hex": "1F7B9" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "223", "Dingbat hex": "DF", "Unicode dec": "128954", "Unicode hex": "1F7BA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "224", "Dingbat hex": "E0", "Unicode dec": "128955", "Unicode hex": "1F7BB" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "225", "Dingbat hex": "E1", "Unicode dec": "128956", "Unicode hex": "1F7BC" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "226", "Dingbat hex": "E2", "Unicode dec": "128957", "Unicode hex": "1F7BD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "227", "Dingbat hex": "E3", "Unicode dec": "128958", "Unicode hex": "1F7BE" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "228", "Dingbat hex": "E4", "Unicode dec": "128959", "Unicode hex": "1F7BF" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "229", "Dingbat hex": "E5", "Unicode dec": "128960", "Unicode hex": "1F7C0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "230", "Dingbat hex": "E6", "Unicode dec": "128962", "Unicode hex": "1F7C2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "231", "Dingbat hex": "E7", "Unicode dec": "128964", "Unicode hex": "1F7C4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "232", "Dingbat hex": "E8", "Unicode dec": "128966", "Unicode hex": "1F7C6" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "233", "Dingbat hex": "E9", "Unicode dec": "128969", "Unicode hex": "1F7C9" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "234", "Dingbat hex": "EA", "Unicode dec": "128970", "Unicode hex": "1F7CA" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "235", "Dingbat hex": "EB", "Unicode dec": "10038", "Unicode hex": "2736" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "236", "Dingbat hex": "EC", "Unicode dec": "128972", "Unicode hex": "1F7CC" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "237", "Dingbat hex": "ED", "Unicode dec": "128974", "Unicode hex": "1F7CE" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "238", "Dingbat hex": "EE", "Unicode dec": "128976", "Unicode hex": "1F7D0" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "239", "Dingbat hex": "EF", "Unicode dec": "128978", "Unicode hex": "1F7D2" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "240", "Dingbat hex": "F0", "Unicode dec": "10041", "Unicode hex": "2739" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "241", "Dingbat hex": "F1", "Unicode dec": "128963", "Unicode hex": "1F7C3" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "242", "Dingbat hex": "F2", "Unicode dec": "128967", "Unicode hex": "1F7C7" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "243", "Dingbat hex": "F3", "Unicode dec": "10031", "Unicode hex": "272F" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "244", "Dingbat hex": "F4", "Unicode dec": "128973", "Unicode hex": "1F7CD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "245", "Dingbat hex": "F5", "Unicode dec": "128980", "Unicode hex": "1F7D4" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "246", "Dingbat hex": "F6", "Unicode dec": "11212", "Unicode hex": "2BCC" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "247", "Dingbat hex": "F7", "Unicode dec": "11213", "Unicode hex": "2BCD" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "248", "Dingbat hex": "F8", "Unicode dec": "8251", "Unicode hex": "203B" }, + { "Typeface name": "Wingdings 2", "Dingbat dec": "249", "Dingbat hex": "F9", "Unicode dec": "8258", "Unicode hex": "2042" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "32", "Dingbat hex": "20", "Unicode dec": "32", "Unicode hex": "20" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "33", "Dingbat hex": "21", "Unicode dec": "11104", "Unicode hex": "2B60" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "34", "Dingbat hex": "22", "Unicode dec": "11106", "Unicode hex": "2B62" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "35", "Dingbat hex": "23", "Unicode dec": "11105", "Unicode hex": "2B61" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "36", "Dingbat hex": "24", "Unicode dec": "11107", "Unicode hex": "2B63" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "37", "Dingbat hex": "25", "Unicode dec": "11110", "Unicode hex": "2B66" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "38", "Dingbat hex": "26", "Unicode dec": "11111", "Unicode hex": "2B67" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "39", "Dingbat hex": "27", "Unicode dec": "11113", "Unicode hex": "2B69" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "40", "Dingbat hex": "28", "Unicode dec": "11112", "Unicode hex": "2B68" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "41", "Dingbat hex": "29", "Unicode dec": "11120", "Unicode hex": "2B70" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "42", "Dingbat hex": "2A", "Unicode dec": "11122", "Unicode hex": "2B72" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "43", "Dingbat hex": "2B", "Unicode dec": "11121", "Unicode hex": "2B71" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "44", "Dingbat hex": "2C", "Unicode dec": "11123", "Unicode hex": "2B73" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "45", "Dingbat hex": "2D", "Unicode dec": "11126", "Unicode hex": "2B76" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "46", "Dingbat hex": "2E", "Unicode dec": "11128", "Unicode hex": "2B78" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "47", "Dingbat hex": "2F", "Unicode dec": "11131", "Unicode hex": "2B7B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "48", "Dingbat hex": "30", "Unicode dec": "11133", "Unicode hex": "2B7D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "49", "Dingbat hex": "31", "Unicode dec": "11108", "Unicode hex": "2B64" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "50", "Dingbat hex": "32", "Unicode dec": "11109", "Unicode hex": "2B65" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "51", "Dingbat hex": "33", "Unicode dec": "11114", "Unicode hex": "2B6A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "52", "Dingbat hex": "34", "Unicode dec": "11116", "Unicode hex": "2B6C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "53", "Dingbat hex": "35", "Unicode dec": "11115", "Unicode hex": "2B6B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "54", "Dingbat hex": "36", "Unicode dec": "11117", "Unicode hex": "2B6D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "55", "Dingbat hex": "37", "Unicode dec": "11085", "Unicode hex": "2B4D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "56", "Dingbat hex": "38", "Unicode dec": "11168", "Unicode hex": "2BA0" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "57", "Dingbat hex": "39", "Unicode dec": "11169", "Unicode hex": "2BA1" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "58", "Dingbat hex": "3A", "Unicode dec": "11170", "Unicode hex": "2BA2" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "59", "Dingbat hex": "3B", "Unicode dec": "11171", "Unicode hex": "2BA3" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "60", "Dingbat hex": "3C", "Unicode dec": "11172", "Unicode hex": "2BA4" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "61", "Dingbat hex": "3D", "Unicode dec": "11173", "Unicode hex": "2BA5" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "62", "Dingbat hex": "3E", "Unicode dec": "11174", "Unicode hex": "2BA6" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "63", "Dingbat hex": "3F", "Unicode dec": "11175", "Unicode hex": "2BA7" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "64", "Dingbat hex": "40", "Unicode dec": "11152", "Unicode hex": "2B90" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "65", "Dingbat hex": "41", "Unicode dec": "11153", "Unicode hex": "2B91" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "66", "Dingbat hex": "42", "Unicode dec": "11154", "Unicode hex": "2B92" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "67", "Dingbat hex": "43", "Unicode dec": "11155", "Unicode hex": "2B93" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "68", "Dingbat hex": "44", "Unicode dec": "11136", "Unicode hex": "2B80" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "69", "Dingbat hex": "45", "Unicode dec": "11139", "Unicode hex": "2B83" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "70", "Dingbat hex": "46", "Unicode dec": "11134", "Unicode hex": "2B7E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "71", "Dingbat hex": "47", "Unicode dec": "11135", "Unicode hex": "2B7F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "72", "Dingbat hex": "48", "Unicode dec": "11140", "Unicode hex": "2B84" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "73", "Dingbat hex": "49", "Unicode dec": "11142", "Unicode hex": "2B86" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "74", "Dingbat hex": "4A", "Unicode dec": "11141", "Unicode hex": "2B85" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "75", "Dingbat hex": "4B", "Unicode dec": "11143", "Unicode hex": "2B87" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "76", "Dingbat hex": "4C", "Unicode dec": "11151", "Unicode hex": "2B8F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "77", "Dingbat hex": "4D", "Unicode dec": "11149", "Unicode hex": "2B8D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "78", "Dingbat hex": "4E", "Unicode dec": "11150", "Unicode hex": "2B8E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "79", "Dingbat hex": "4F", "Unicode dec": "11148", "Unicode hex": "2B8C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "80", "Dingbat hex": "50", "Unicode dec": "11118", "Unicode hex": "2B6E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "81", "Dingbat hex": "51", "Unicode dec": "11119", "Unicode hex": "2B6F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "82", "Dingbat hex": "52", "Unicode dec": "9099", "Unicode hex": "238B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "83", "Dingbat hex": "53", "Unicode dec": "8996", "Unicode hex": "2324" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "84", "Dingbat hex": "54", "Unicode dec": "8963", "Unicode hex": "2303" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "85", "Dingbat hex": "55", "Unicode dec": "8997", "Unicode hex": "2325" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "86", "Dingbat hex": "56", "Unicode dec": "9251", "Unicode hex": "2423" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "87", "Dingbat hex": "57", "Unicode dec": "9085", "Unicode hex": "237D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "88", "Dingbat hex": "58", "Unicode dec": "8682", "Unicode hex": "21EA" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "89", "Dingbat hex": "59", "Unicode dec": "11192", "Unicode hex": "2BB8" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "90", "Dingbat hex": "5A", "Unicode dec": "129184", "Unicode hex": "1F8A0" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "91", "Dingbat hex": "5B", "Unicode dec": "129185", "Unicode hex": "1F8A1" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "92", "Dingbat hex": "5C", "Unicode dec": "129186", "Unicode hex": "1F8A2" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "93", "Dingbat hex": "5D", "Unicode dec": "129187", "Unicode hex": "1F8A3" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "94", "Dingbat hex": "5E", "Unicode dec": "129188", "Unicode hex": "1F8A4" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "95", "Dingbat hex": "5F", "Unicode dec": "129189", "Unicode hex": "1F8A5" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "96", "Dingbat hex": "60", "Unicode dec": "129190", "Unicode hex": "1F8A6" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "97", "Dingbat hex": "61", "Unicode dec": "129191", "Unicode hex": "1F8A7" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "98", "Dingbat hex": "62", "Unicode dec": "129192", "Unicode hex": "1F8A8" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "99", "Dingbat hex": "63", "Unicode dec": "129193", "Unicode hex": "1F8A9" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "100", "Dingbat hex": "64", "Unicode dec": "129194", "Unicode hex": "1F8AA" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "101", "Dingbat hex": "65", "Unicode dec": "129195", "Unicode hex": "1F8AB" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "102", "Dingbat hex": "66", "Unicode dec": "129104", "Unicode hex": "1F850" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "103", "Dingbat hex": "67", "Unicode dec": "129106", "Unicode hex": "1F852" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "104", "Dingbat hex": "68", "Unicode dec": "129105", "Unicode hex": "1F851" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "105", "Dingbat hex": "69", "Unicode dec": "129107", "Unicode hex": "1F853" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "106", "Dingbat hex": "6A", "Unicode dec": "129108", "Unicode hex": "1F854" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "107", "Dingbat hex": "6B", "Unicode dec": "129109", "Unicode hex": "1F855" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "108", "Dingbat hex": "6C", "Unicode dec": "129111", "Unicode hex": "1F857" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "109", "Dingbat hex": "6D", "Unicode dec": "129110", "Unicode hex": "1F856" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "110", "Dingbat hex": "6E", "Unicode dec": "129112", "Unicode hex": "1F858" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "111", "Dingbat hex": "6F", "Unicode dec": "129113", "Unicode hex": "1F859" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "112", "Dingbat hex": "70", "Unicode dec": "9650", "Unicode hex": "25B2" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "113", "Dingbat hex": "71", "Unicode dec": "9660", "Unicode hex": "25BC" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "114", "Dingbat hex": "72", "Unicode dec": "9651", "Unicode hex": "25B3" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "115", "Dingbat hex": "73", "Unicode dec": "9661", "Unicode hex": "25BD" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "116", "Dingbat hex": "74", "Unicode dec": "9664", "Unicode hex": "25C0" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "117", "Dingbat hex": "75", "Unicode dec": "9654", "Unicode hex": "25B6" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "118", "Dingbat hex": "76", "Unicode dec": "9665", "Unicode hex": "25C1" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "119", "Dingbat hex": "77", "Unicode dec": "9655", "Unicode hex": "25B7" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "120", "Dingbat hex": "78", "Unicode dec": "9699", "Unicode hex": "25E3" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "121", "Dingbat hex": "79", "Unicode dec": "9698", "Unicode hex": "25E2" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "122", "Dingbat hex": "7A", "Unicode dec": "9700", "Unicode hex": "25E4" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "123", "Dingbat hex": "7B", "Unicode dec": "9701", "Unicode hex": "25E5" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "124", "Dingbat hex": "7C", "Unicode dec": "128896", "Unicode hex": "1F780" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "125", "Dingbat hex": "7D", "Unicode dec": "128898", "Unicode hex": "1F782" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "126", "Dingbat hex": "7E", "Unicode dec": "128897", "Unicode hex": "1F781" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "128", "Dingbat hex": "80", "Unicode dec": "128899", "Unicode hex": "1F783" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "129", "Dingbat hex": "81", "Unicode dec": "11205", "Unicode hex": "2BC5" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "130", "Dingbat hex": "82", "Unicode dec": "11206", "Unicode hex": "2BC6" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "131", "Dingbat hex": "83", "Unicode dec": "11207", "Unicode hex": "2BC7" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "132", "Dingbat hex": "84", "Unicode dec": "11208", "Unicode hex": "2BC8" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "133", "Dingbat hex": "85", "Unicode dec": "11164", "Unicode hex": "2B9C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "134", "Dingbat hex": "86", "Unicode dec": "11166", "Unicode hex": "2B9E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "135", "Dingbat hex": "87", "Unicode dec": "11165", "Unicode hex": "2B9D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "136", "Dingbat hex": "88", "Unicode dec": "11167", "Unicode hex": "2B9F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "137", "Dingbat hex": "89", "Unicode dec": "129040", "Unicode hex": "1F810" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "138", "Dingbat hex": "8A", "Unicode dec": "129042", "Unicode hex": "1F812" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "139", "Dingbat hex": "8B", "Unicode dec": "129041", "Unicode hex": "1F811" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "140", "Dingbat hex": "8C", "Unicode dec": "129043", "Unicode hex": "1F813" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "141", "Dingbat hex": "8D", "Unicode dec": "129044", "Unicode hex": "1F814" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "142", "Dingbat hex": "8E", "Unicode dec": "129046", "Unicode hex": "1F816" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "143", "Dingbat hex": "8F", "Unicode dec": "129045", "Unicode hex": "1F815" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "144", "Dingbat hex": "90", "Unicode dec": "129047", "Unicode hex": "1F817" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "145", "Dingbat hex": "91", "Unicode dec": "129048", "Unicode hex": "1F818" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "146", "Dingbat hex": "92", "Unicode dec": "129050", "Unicode hex": "1F81A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "147", "Dingbat hex": "93", "Unicode dec": "129049", "Unicode hex": "1F819" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "148", "Dingbat hex": "94", "Unicode dec": "129051", "Unicode hex": "1F81B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "149", "Dingbat hex": "95", "Unicode dec": "129052", "Unicode hex": "1F81C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "150", "Dingbat hex": "96", "Unicode dec": "129054", "Unicode hex": "1F81E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "151", "Dingbat hex": "97", "Unicode dec": "129053", "Unicode hex": "1F81D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "152", "Dingbat hex": "98", "Unicode dec": "129055", "Unicode hex": "1F81F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "153", "Dingbat hex": "99", "Unicode dec": "129024", "Unicode hex": "1F800" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "154", "Dingbat hex": "9A", "Unicode dec": "129026", "Unicode hex": "1F802" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "155", "Dingbat hex": "9B", "Unicode dec": "129025", "Unicode hex": "1F801" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "156", "Dingbat hex": "9C", "Unicode dec": "129027", "Unicode hex": "1F803" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "157", "Dingbat hex": "9D", "Unicode dec": "129028", "Unicode hex": "1F804" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "158", "Dingbat hex": "9E", "Unicode dec": "129030", "Unicode hex": "1F806" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "159", "Dingbat hex": "9F", "Unicode dec": "129029", "Unicode hex": "1F805" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "160", "Dingbat hex": "A0", "Unicode dec": "129031", "Unicode hex": "1F807" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "161", "Dingbat hex": "A1", "Unicode dec": "129032", "Unicode hex": "1F808" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "162", "Dingbat hex": "A2", "Unicode dec": "129034", "Unicode hex": "1F80A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "163", "Dingbat hex": "A3", "Unicode dec": "129033", "Unicode hex": "1F809" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "164", "Dingbat hex": "A4", "Unicode dec": "129035", "Unicode hex": "1F80B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "165", "Dingbat hex": "A5", "Unicode dec": "129056", "Unicode hex": "1F820" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "166", "Dingbat hex": "A6", "Unicode dec": "129058", "Unicode hex": "1F822" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "167", "Dingbat hex": "A7", "Unicode dec": "129060", "Unicode hex": "1F824" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "168", "Dingbat hex": "A8", "Unicode dec": "129062", "Unicode hex": "1F826" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "169", "Dingbat hex": "A9", "Unicode dec": "129064", "Unicode hex": "1F828" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "170", "Dingbat hex": "AA", "Unicode dec": "129066", "Unicode hex": "1F82A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "171", "Dingbat hex": "AB", "Unicode dec": "129068", "Unicode hex": "1F82C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "172", "Dingbat hex": "AC", "Unicode dec": "129180", "Unicode hex": "1F89C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "173", "Dingbat hex": "AD", "Unicode dec": "129181", "Unicode hex": "1F89D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "174", "Dingbat hex": "AE", "Unicode dec": "129182", "Unicode hex": "1F89E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "175", "Dingbat hex": "AF", "Unicode dec": "129183", "Unicode hex": "1F89F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "176", "Dingbat hex": "B0", "Unicode dec": "129070", "Unicode hex": "1F82E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "177", "Dingbat hex": "B1", "Unicode dec": "129072", "Unicode hex": "1F830" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "178", "Dingbat hex": "B2", "Unicode dec": "129074", "Unicode hex": "1F832" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "179", "Dingbat hex": "B3", "Unicode dec": "129076", "Unicode hex": "1F834" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "180", "Dingbat hex": "B4", "Unicode dec": "129078", "Unicode hex": "1F836" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "181", "Dingbat hex": "B5", "Unicode dec": "129080", "Unicode hex": "1F838" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "182", "Dingbat hex": "B6", "Unicode dec": "129082", "Unicode hex": "1F83A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "183", "Dingbat hex": "B7", "Unicode dec": "129081", "Unicode hex": "1F839" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "184", "Dingbat hex": "B8", "Unicode dec": "129083", "Unicode hex": "1F83B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "185", "Dingbat hex": "B9", "Unicode dec": "129176", "Unicode hex": "1F898" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "186", "Dingbat hex": "BA", "Unicode dec": "129178", "Unicode hex": "1F89A" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "187", "Dingbat hex": "BB", "Unicode dec": "129177", "Unicode hex": "1F899" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "188", "Dingbat hex": "BC", "Unicode dec": "129179", "Unicode hex": "1F89B" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "189", "Dingbat hex": "BD", "Unicode dec": "129084", "Unicode hex": "1F83C" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "190", "Dingbat hex": "BE", "Unicode dec": "129086", "Unicode hex": "1F83E" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "191", "Dingbat hex": "BF", "Unicode dec": "129085", "Unicode hex": "1F83D" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "192", "Dingbat hex": "C0", "Unicode dec": "129087", "Unicode hex": "1F83F" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "193", "Dingbat hex": "C1", "Unicode dec": "129088", "Unicode hex": "1F840" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "194", "Dingbat hex": "C2", "Unicode dec": "129090", "Unicode hex": "1F842" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "195", "Dingbat hex": "C3", "Unicode dec": "129089", "Unicode hex": "1F841" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "196", "Dingbat hex": "C4", "Unicode dec": "129091", "Unicode hex": "1F843" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "197", "Dingbat hex": "C5", "Unicode dec": "129092", "Unicode hex": "1F844" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "198", "Dingbat hex": "C6", "Unicode dec": "129094", "Unicode hex": "1F846" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "199", "Dingbat hex": "C7", "Unicode dec": "129093", "Unicode hex": "1F845" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "200", "Dingbat hex": "C8", "Unicode dec": "129095", "Unicode hex": "1F847" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "201", "Dingbat hex": "C9", "Unicode dec": "11176", "Unicode hex": "2BA8" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "202", "Dingbat hex": "CA", "Unicode dec": "11177", "Unicode hex": "2BA9" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "203", "Dingbat hex": "CB", "Unicode dec": "11178", "Unicode hex": "2BAA" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "204", "Dingbat hex": "CC", "Unicode dec": "11179", "Unicode hex": "2BAB" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "205", "Dingbat hex": "CD", "Unicode dec": "11180", "Unicode hex": "2BAC" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "206", "Dingbat hex": "CE", "Unicode dec": "11181", "Unicode hex": "2BAD" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "207", "Dingbat hex": "CF", "Unicode dec": "11182", "Unicode hex": "2BAE" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "208", "Dingbat hex": "D0", "Unicode dec": "11183", "Unicode hex": "2BAF" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "209", "Dingbat hex": "D1", "Unicode dec": "129120", "Unicode hex": "1F860" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "210", "Dingbat hex": "D2", "Unicode dec": "129122", "Unicode hex": "1F862" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "211", "Dingbat hex": "D3", "Unicode dec": "129121", "Unicode hex": "1F861" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "212", "Dingbat hex": "D4", "Unicode dec": "129123", "Unicode hex": "1F863" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "213", "Dingbat hex": "D5", "Unicode dec": "129124", "Unicode hex": "1F864" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "214", "Dingbat hex": "D6", "Unicode dec": "129125", "Unicode hex": "1F865" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "215", "Dingbat hex": "D7", "Unicode dec": "129127", "Unicode hex": "1F867" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "216", "Dingbat hex": "D8", "Unicode dec": "129126", "Unicode hex": "1F866" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "217", "Dingbat hex": "D9", "Unicode dec": "129136", "Unicode hex": "1F870" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "218", "Dingbat hex": "DA", "Unicode dec": "129138", "Unicode hex": "1F872" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "219", "Dingbat hex": "DB", "Unicode dec": "129137", "Unicode hex": "1F871" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "220", "Dingbat hex": "DC", "Unicode dec": "129139", "Unicode hex": "1F873" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "221", "Dingbat hex": "DD", "Unicode dec": "129140", "Unicode hex": "1F874" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "222", "Dingbat hex": "DE", "Unicode dec": "129141", "Unicode hex": "1F875" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "223", "Dingbat hex": "DF", "Unicode dec": "129143", "Unicode hex": "1F877" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "224", "Dingbat hex": "E0", "Unicode dec": "129142", "Unicode hex": "1F876" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "225", "Dingbat hex": "E1", "Unicode dec": "129152", "Unicode hex": "1F880" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "226", "Dingbat hex": "E2", "Unicode dec": "129154", "Unicode hex": "1F882" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "227", "Dingbat hex": "E3", "Unicode dec": "129153", "Unicode hex": "1F881" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "228", "Dingbat hex": "E4", "Unicode dec": "129155", "Unicode hex": "1F883" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "229", "Dingbat hex": "E5", "Unicode dec": "129156", "Unicode hex": "1F884" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "230", "Dingbat hex": "E6", "Unicode dec": "129157", "Unicode hex": "1F885" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "231", "Dingbat hex": "E7", "Unicode dec": "129159", "Unicode hex": "1F887" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "232", "Dingbat hex": "E8", "Unicode dec": "129158", "Unicode hex": "1F886" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "233", "Dingbat hex": "E9", "Unicode dec": "129168", "Unicode hex": "1F890" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "234", "Dingbat hex": "EA", "Unicode dec": "129170", "Unicode hex": "1F892" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "235", "Dingbat hex": "EB", "Unicode dec": "129169", "Unicode hex": "1F891" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "236", "Dingbat hex": "EC", "Unicode dec": "129171", "Unicode hex": "1F893" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "237", "Dingbat hex": "ED", "Unicode dec": "129172", "Unicode hex": "1F894" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "238", "Dingbat hex": "EE", "Unicode dec": "129174", "Unicode hex": "1F896" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "239", "Dingbat hex": "EF", "Unicode dec": "129173", "Unicode hex": "1F895" }, + { "Typeface name": "Wingdings 3", "Dingbat dec": "240", "Dingbat hex": "F0", "Unicode dec": "129175", "Unicode hex": "1F897" }, +]; +exports.default = dingbats; + +},{}],86:[function(require,module,exports){ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.hex = exports.dec = exports.codePoint = void 0; +var dingbats_1 = __importDefault(require("./dingbats")); +var dingbatsByCodePoint = {}; +var fromCodePoint = String.fromCodePoint ? String.fromCodePoint : fromCodePointPolyfill; +for (var _i = 0, dingbats_2 = dingbats_1.default; _i < dingbats_2.length; _i++) { + var dingbat = dingbats_2[_i]; + var codePoint_1 = parseInt(dingbat["Unicode dec"], 10); + var scalarValue = { + codePoint: codePoint_1, + string: fromCodePoint(codePoint_1), + }; + dingbatsByCodePoint[dingbat["Typeface name"].toUpperCase() + "_" + dingbat["Dingbat dec"]] = scalarValue; +} +function codePoint(typeface, codePoint) { + return dingbatsByCodePoint[typeface.toUpperCase() + "_" + codePoint]; +} +exports.codePoint = codePoint; +function dec(typeface, dec) { + return codePoint(typeface, parseInt(dec, 10)); +} +exports.dec = dec; +function hex(typeface, hex) { + return codePoint(typeface, parseInt(hex, 16)); +} +exports.hex = hex; +function fromCodePointPolyfill(codePoint) { + if (codePoint <= 0xFFFF) { + // BMP + return String.fromCharCode(codePoint); + } + else { + // Astral + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + var highSurrogate = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800; + var lowSurrogate = (codePoint - 0x10000) % 0x400 + 0xDC00; + return String.fromCharCode(highSurrogate, lowSurrogate); + } +} +; + +},{"./dingbats":85}],87:[function(require,module,exports){ +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */ +exports.read = function (buffer, offset, isLE, mLen, nBytes) { + var e, m + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var nBits = -7 + var i = isLE ? (nBytes - 1) : 0 + var d = isLE ? -1 : 1 + var s = buffer[offset + i] + + i += d + + e = s & ((1 << (-nBits)) - 1) + s >>= (-nBits) + nBits += eLen + for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + m = e & ((1 << (-nBits)) - 1) + e >>= (-nBits) + nBits += mLen + for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {} + + if (e === 0) { + e = 1 - eBias + } else if (e === eMax) { + return m ? NaN : ((s ? -1 : 1) * Infinity) + } else { + m = m + Math.pow(2, mLen) + e = e - eBias + } + return (s ? -1 : 1) * m * Math.pow(2, e - mLen) +} + +exports.write = function (buffer, value, offset, isLE, mLen, nBytes) { + var e, m, c + var eLen = (nBytes * 8) - mLen - 1 + var eMax = (1 << eLen) - 1 + var eBias = eMax >> 1 + var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0) + var i = isLE ? 0 : (nBytes - 1) + var d = isLE ? 1 : -1 + var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0 + + value = Math.abs(value) + + if (isNaN(value) || value === Infinity) { + m = isNaN(value) ? 1 : 0 + e = eMax + } else { + e = Math.floor(Math.log(value) / Math.LN2) + if (value * (c = Math.pow(2, -e)) < 1) { + e-- + c *= 2 + } + if (e + eBias >= 1) { + value += rt / c + } else { + value += rt * Math.pow(2, 1 - eBias) + } + if (value * c >= 2) { + e++ + c /= 2 + } + + if (e + eBias >= eMax) { + m = 0 + e = eMax + } else if (e + eBias >= 1) { + m = ((value * c) - 1) * Math.pow(2, mLen) + e = e + eBias + } else { + m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen) + e = 0 + } + } + + for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {} + + e = (e << mLen) | m + eLen += mLen + for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {} + + buffer[offset + i - d] |= s * 128 +} + +},{}],88:[function(require,module,exports){ +var toString = {}.toString; + +module.exports = Array.isArray || function (arr) { + return toString.call(arr) == '[object Array]'; +}; + +},{}],89:[function(require,module,exports){ +(function (process,global,Buffer,__argument0,__argument1,__argument2,__argument3,setImmediate){(function (){ +/*! + +JSZip v3.10.1 - A JavaScript class for generating and reading zip files +<http://stuartk.com/jszip> + +(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com> +Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown. + +JSZip uses the library pako released under the MIT license : +https://github.com/nodeca/pako/blob/main/LICENSE +*/ + +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function A(e,t){var r,n="";for(r=0;r<t;r++)n+=String.fromCharCode(255&e),e>>>=8;return n}function n(e,t,r,n,i,s){var a,o,h=e.file,u=e.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),c=I.transformTo("string",O.utf8encode(h.name)),d=h.comment,p=I.transformTo("string",s(d)),m=I.transformTo("string",O.utf8encode(d)),_=c.length!==h.name.length,g=m.length!==d.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===i?(C=798,z|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(e){return 63&(e||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+c,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(n,4)+f+b+p}}var I=e("../utils"),i=e("../stream/GenericWorker"),O=e("../utf8"),B=e("../crc32"),R=e("../signature");function s(e,t,r,n){i.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,i),s.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,i.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},s.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=n(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=n(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return R.DATA_DESCRIPTOR+A(e.crc32,4)+A(e.compressedSize,4)+A(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t<this.dirRecords.length;t++)this.push({data:this.dirRecords[t],meta:{percent:100}});var r=this.bytesWritten-e,n=function(e,t,r,n,i){var s=I.transformTo("string",i(n));return R.CENTRAL_DIRECTORY_END+"\0\0\0\0"+A(e,2)+A(e,2)+A(t,4)+A(r,4)+A(s.length,2)+s}(this.dirRecords.length,r,e,this.zipComment,this.encodeFileName);this.push({data:n,meta:{percent:100}})},s.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),this.isPaused?this.previous.pause():this.previous.resume()},s.prototype.registerPrevious=function(e){this._sources.push(e);var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.closedSource(t.previous.streamInfo),t._sources.length?t.prepareNextSource():t.end()}),e.on("error",function(e){t.error(e)}),this},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||this.generatedError?void 0:(this.end(),!0))},s.prototype.error=function(e){var t=this._sources;if(!i.prototype.error.call(this,e))return!1;for(var r=0;r<t.length;r++)try{t[r].error(e)}catch(e){}return!0},s.prototype.lock=function(){i.prototype.lock.call(this);for(var e=this._sources,t=0;t<e.length;t++)e[t].lock()},t.exports=s},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(e,t,r){"use strict";var u=e("../compressions"),n=e("./ZipFileWorker");r.generateWorker=function(e,a,t){var o=new n(a.streamFiles,t,a.platform,a.encodeFileName),h=0;try{e.forEach(function(e,t){h++;var r=function(e,t){var r=e||t,n=u[r];if(!n)throw new Error(r+" is not a valid compression method !");return n}(t.options.compression,a.compression),n=t.options.compressionOptions||a.compressionOptions||{},i=t.dir,s=t.date;t._compressWorker(r,n).withStreamInfo("file",{name:e,dir:i,date:s,comment:t.comment||"",unixPermissions:t.unixPermissions,dosPermissions:t.dosPermissions}).pipe(o)}),o.entriesCount=h}catch(e){o.error(e)}return o}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(e,t,r){"use strict";function n(){if(!(this instanceof n))return new n;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files=Object.create(null),this.comment=null,this.root="",this.clone=function(){var e=new n;for(var t in this)"function"!=typeof this[t]&&(e[t]=this[t]);return e}}(n.prototype=e("./object")).loadAsync=e("./load"),n.support=e("./support"),n.defaults=e("./defaults"),n.version="3.10.1",n.loadAsync=function(e,t){return(new n).loadAsync(e,t)},n.external=e("./external"),t.exports=n},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(e,t,r){"use strict";var u=e("./utils"),i=e("./external"),n=e("./utf8"),s=e("./zipEntries"),a=e("./stream/Crc32Probe"),l=e("./nodejsUtils");function f(n){return new i.Promise(function(e,t){var r=n.decompressed.getContentWorker().pipe(new a);r.on("error",function(e){t(e)}).on("end",function(){r.streamInfo.crc32!==n.decompressed.crc32?t(new Error("Corrupted zip : CRC32 mismatch")):e()}).resume()})}t.exports=function(e,o){var h=this;return o=u.extend(o||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:n.utf8decode}),l.isNode&&l.isStream(e)?i.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):u.prepareContent("the loaded zip file",e,!0,o.optimizedBinaryString,o.base64).then(function(e){var t=new s(o);return t.load(e),t}).then(function(e){var t=[i.Promise.resolve(e)],r=e.files;if(o.checkCRC32)for(var n=0;n<r.length;n++)t.push(f(r[n]));return i.Promise.all(t)}).then(function(e){for(var t=e.shift(),r=t.files,n=0;n<r.length;n++){var i=r[n],s=i.fileNameStr,a=u.resolve(i.fileNameStr);h.file(a,i.decompressed,{binary:!0,optimizedBinaryString:!0,date:i.date,dir:i.dir,comment:i.fileCommentStr.length?i.fileCommentStr:null,unixPermissions:i.unixPermissions,dosPermissions:i.dosPermissions,createFolders:o.createFolders}),i.dir||(h.file(a).unsafeOriginalName=s)}return t.zipComment.length&&(h.comment=t.zipComment),h})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../stream/GenericWorker");function s(e,t){i.call(this,"Nodejs stream input adapter for "+e),this._upstreamEnded=!1,this._bindStream(t)}n.inherits(s,i),s.prototype._bindStream=function(e){var t=this;(this._stream=e).pause(),e.on("data",function(e){t.push({data:e,meta:{percent:0}})}).on("error",function(e){t.isPaused?this.generatedError=e:t.error(e)}).on("end",function(){t.isPaused?t._upstreamEnded=!0:t.end()})},s.prototype.pause=function(){return!!i.prototype.pause.call(this)&&(this._stream.pause(),!0)},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},t.exports=s},{"../stream/GenericWorker":28,"../utils":32}],13:[function(e,t,r){"use strict";var i=e("readable-stream").Readable;function n(e,t,r){i.call(this,t),this._helper=e;var n=this;e.on("data",function(e,t){n.push(e)||n._helper.pause(),r&&r(t)}).on("error",function(e){n.emit("error",e)}).on("end",function(){n.push(null)})}e("../utils").inherits(n,i),n.prototype._read=function(){this._helper.resume()},t.exports=n},{"../utils":32,"readable-stream":16}],14:[function(e,t,r){"use strict";t.exports={isNode:"undefined"!=typeof Buffer,newBufferFrom:function(e,t){if(Buffer.from&&Buffer.from!==Uint8Array.from)return Buffer.from(e,t);if("number"==typeof e)throw new Error('The "data" argument must not be a number');return new Buffer(e,t)},allocBuffer:function(e){if(Buffer.alloc)return Buffer.alloc(e);var t=new Buffer(e);return t.fill(0),t},isBuffer:function(e){return Buffer.isBuffer(e)},isStream:function(e){return e&&"function"==typeof e.on&&"function"==typeof e.pause&&"function"==typeof e.resume}}},{}],15:[function(e,t,r){"use strict";function s(e,t,r){var n,i=u.getTypeOf(t),s=u.extend(r||{},f);s.date=s.date||new Date,null!==s.compression&&(s.compression=s.compression.toUpperCase()),"string"==typeof s.unixPermissions&&(s.unixPermissions=parseInt(s.unixPermissions,8)),s.unixPermissions&&16384&s.unixPermissions&&(s.dir=!0),s.dosPermissions&&16&s.dosPermissions&&(s.dir=!0),s.dir&&(e=g(e)),s.createFolders&&(n=_(e))&&b.call(this,n,!0);var a="string"===i&&!1===s.binary&&!1===s.base64;r&&void 0!==r.binary||(s.binary=!a),(t instanceof c&&0===t.uncompressedSize||s.dir||!t||0===t.length)&&(s.base64=!1,s.binary=!0,t="",s.compression="STORE",i="string");var o=null;o=t instanceof c||t instanceof l?t:p.isNode&&p.isStream(t)?new m(e,t):u.prepareContent(e,t,s.binary,s.optimizedBinaryString,s.base64);var h=new d(e,o,s);this.files[e]=h}var i=e("./utf8"),u=e("./utils"),l=e("./stream/GenericWorker"),a=e("./stream/StreamHelper"),f=e("./defaults"),c=e("./compressedObject"),d=e("./zipObject"),o=e("./generate"),p=e("./nodejsUtils"),m=e("./nodejs/NodejsStreamInputAdapter"),_=function(e){"/"===e.slice(-1)&&(e=e.substring(0,e.length-1));var t=e.lastIndexOf("/");return 0<t?e.substring(0,t):""},g=function(e){return"/"!==e.slice(-1)&&(e+="/"),e},b=function(e,t){return t=void 0!==t?t:f.createFolders,e=g(e),this.files[e]||s.call(this,e,null,{dir:!0,createFolders:t}),this.files[e]};function h(e){return"[object RegExp]"===Object.prototype.toString.call(e)}var n={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},forEach:function(e){var t,r,n;for(t in this.files)n=this.files[t],(r=t.slice(this.root.length,t.length))&&t.slice(0,this.root.length)===this.root&&e(r,n)},filter:function(r){var n=[];return this.forEach(function(e,t){r(e,t)&&n.push(t)}),n},file:function(e,t,r){if(1!==arguments.length)return e=this.root+e,s.call(this,e,t,r),this;if(h(e)){var n=e;return this.filter(function(e,t){return!t.dir&&n.test(e)})}var i=this.files[this.root+e];return i&&!i.dir?i:null},folder:function(r){if(!r)return this;if(h(r))return this.filter(function(e,t){return t.dir&&r.test(e)});var e=this.root+r,t=b.call(this,e),n=this.clone();return n.root=t.name,n},remove:function(r){r=this.root+r;var e=this.files[r];if(e||("/"!==r.slice(-1)&&(r+="/"),e=this.files[r]),e&&!e.dir)delete this.files[r];else for(var t=this.filter(function(e,t){return t.name.slice(0,r.length)===r}),n=0;n<t.length;n++)delete this.files[t[n].name];return this},generate:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},generateInternalStream:function(e){var t,r={};try{if((r=u.extend(e||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:i.utf8encode})).type=r.type.toLowerCase(),r.compression=r.compression.toUpperCase(),"binarystring"===r.type&&(r.type="string"),!r.type)throw new Error("No output type specified.");u.checkSupport(r.type),"darwin"!==r.platform&&"freebsd"!==r.platform&&"linux"!==r.platform&&"sunos"!==r.platform||(r.platform="UNIX"),"win32"===r.platform&&(r.platform="DOS");var n=r.comment||this.comment||"";t=o.generateWorker(this,r,n)}catch(e){(t=new l("error")).error(e)}return new a(t,r.type||"string",r.mimeType)},generateAsync:function(e,t){return this.generateInternalStream(e).accumulate(t)},generateNodeStream:function(e,t){return(e=e||{}).type||(e.type="nodebuffer"),this.generateInternalStream(e).toNodejsStream(t)}};t.exports=n},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(e,t,r){"use strict";t.exports=e("stream")},{stream:void 0}],17:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e);for(var t=0;t<this.data.length;t++)e[t]=255&e[t]}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data[this.zero+e]},i.prototype.lastIndexOfSignature=function(e){for(var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.length-4;0<=s;--s)if(this.data[s]===t&&this.data[s+1]===r&&this.data[s+2]===n&&this.data[s+3]===i)return s-this.zero;return-1},i.prototype.readAndCheckSignature=function(e){var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.readData(4);return t===s[0]&&r===s[1]&&n===s[2]&&i===s[3]},i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return[];var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],18:[function(e,t,r){"use strict";var n=e("../utils");function i(e){this.data=e,this.length=e.length,this.index=0,this.zero=0}i.prototype={checkOffset:function(e){this.checkIndex(this.index+e)},checkIndex:function(e){if(this.length<this.zero+e||e<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+e+"). Corrupted zip ?")},setIndex:function(e){this.checkIndex(e),this.index=e},skip:function(e){this.setIndex(this.index+e)},byteAt:function(){},readInt:function(e){var t,r=0;for(this.checkOffset(e),t=this.index+e-1;t>=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r<this._listeners[e].length;r++)this._listeners[e][r].call(this,t)},pipe:function(e){return e.registerPrevious(this)},registerPrevious:function(e){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=e.streamInfo,this.mergeStreamInfo(),this.previous=e;var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.end()}),e.on("error",function(e){t.error(e)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;var e=this.isPaused=!1;return this.generatedError&&(this.error(this.generatedError),e=!0),this.previous&&this.previous.resume(),!e},flush:function(){},processChunk:function(e){this.push(e)},withStreamInfo:function(e,t){return this.extraStreamInfo[e]=t,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var e in this.extraStreamInfo)Object.prototype.hasOwnProperty.call(this.extraStreamInfo,e)&&(this.streamInfo[e]=this.extraStreamInfo[e])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var e="Worker "+this.name;return this.previous?this.previous+" -> "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),u=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,o){return new a.Promise(function(t,r){var n=[],i=e._internalType,s=e._outputType,a=e._mimeType;e.on("data",function(e,t){n.push(e),o&&o(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return u.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r<t.length;r++)s+=t[r].length;switch(e){case"string":return t.join("");case"array":return Array.prototype.concat.apply([],t);case"uint8array":for(i=new Uint8Array(s),r=0;r<t.length;r++)i.set(t[r],n),n+=t[r].length;return i;case"nodebuffer":return Buffer.concat(t);default:throw new Error("concat : unsupported type '"+e+"'")}}(i,n),a);t(e)}catch(e){r(e)}n=[]}).resume()})}function f(e,t,r){var n=t;switch(t){case"blob":case"arraybuffer":n="uint8array";break;case"base64":n="string"}try{this._internalType=n,this._outputType=t,this._mimeType=r,h.checkSupport(n),this._worker=e.pipe(new i(n)),e.lock()}catch(e){this._worker=new s("error"),this._worker.error(e)}}f.prototype={accumulate:function(e){return l(this,e)},on:function(e,t){var r=this;return"data"===e?this._worker.on(e,function(e){t.call(r,e.data,e.meta)}):this._worker.on(e,function(){h.delay(t,arguments,r)}),this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(e){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new o(this,{objectMode:"nodebuffer"!==this._outputType},e)}},t.exports=f},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(e,t,r){"use strict";if(r.base64=!0,r.array=!0,r.string=!0,r.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,r.nodebuffer="undefined"!=typeof Buffer,r.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)r.blob=!1;else{var n=new ArrayBuffer(0);try{r.blob=0===new Blob([n],{type:"application/zip"}).size}catch(e){try{var i=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);i.append(n),r.blob=0===i.getBlob("application/zip").size}catch(e){r.blob=!1}}}try{r.nodestream=!!e("readable-stream").Readable}catch(e){r.nodestream=!1}},{"readable-stream":16}],31:[function(e,t,s){"use strict";for(var o=e("./utils"),h=e("./support"),r=e("./nodejsUtils"),n=e("./stream/GenericWorker"),u=new Array(256),i=0;i<256;i++)u[i]=252<=i?6:248<=i?5:240<=i?4:224<=i?3:192<=i?2:1;u[254]=u[254]=1;function a(){n.call(this,"utf-8 decode"),this.leftOver=null}function l(){n.call(this,"utf-8 encode")}s.utf8encode=function(e){return h.nodebuffer?r.newBufferFrom(e,"utf-8"):function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=h.uint8array?new Uint8Array(o):new Array(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return h.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t<s;)if((n=e[t++])<128)a[r++]=n;else if(4<(i=u[n]))a[r++]=65533,t+=i-1;else{for(n&=2===i?31:3===i?15:7;1<i&&t<s;)n=n<<6|63&e[t++],i--;1<i?a[r++]=65533:n<65536?a[r++]=n:(n-=65536,a[r++]=55296|n>>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(h.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(h.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}(t),i=t;n!==t.length&&(h.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,n),l.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,a){"use strict";var o=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),u=e("./external");function n(e){return e}function l(e,t){for(var r=0;r<e.length;++r)t[r]=255&e.charCodeAt(r);return t}e("setimmediate"),a.newBlob=function(t,r){a.checkSupport("blob");try{return new Blob([t],{type:r})}catch(e){try{var n=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);return n.append(t),n.getBlob(r)}catch(e){throw new Error("Bug : can't construct the Blob.")}}};var i={stringifyByChunk:function(e,t,r){var n=[],i=0,s=e.length;if(s<=r)return String.fromCharCode.apply(null,e);for(;i<s;)"array"===t||"nodebuffer"===t?n.push(String.fromCharCode.apply(null,e.slice(i,Math.min(i+r,s)))):n.push(String.fromCharCode.apply(null,e.subarray(i,Math.min(i+r,s)))),i+=r;return n.join("")},stringifyByChar:function(e){for(var t="",r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return t},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(e){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(e){return!1}}()}};function s(e){var t=65536,r=a.getTypeOf(e),n=!0;if("uint8array"===r?n=i.applyCanBeUsed.uint8array:"nodebuffer"===r&&(n=i.applyCanBeUsed.nodebuffer),n)for(;1<t;)try{return i.stringifyByChunk(e,r,t)}catch(e){t=Math.floor(t/2)}return i.stringifyByChar(e)}function f(e,t){for(var r=0;r<e.length;r++)t[r]=e[r];return t}a.applyFromCharCode=s;var c={};c.string={string:n,array:function(e){return l(e,new Array(e.length))},arraybuffer:function(e){return c.string.uint8array(e).buffer},uint8array:function(e){return l(e,new Uint8Array(e.length))},nodebuffer:function(e){return l(e,r.allocBuffer(e.length))}},c.array={string:s,array:n,arraybuffer:function(e){return new Uint8Array(e).buffer},uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(e)}},c.arraybuffer={string:function(e){return s(new Uint8Array(e))},array:function(e){return f(new Uint8Array(e),new Array(e.byteLength))},arraybuffer:n,uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(new Uint8Array(e))}},c.uint8array={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return e.buffer},uint8array:n,nodebuffer:function(e){return r.newBufferFrom(e)}},c.nodebuffer={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return c.nodebuffer.uint8array(e).buffer},uint8array:function(e){return f(e,new Uint8Array(e.length))},nodebuffer:n},a.transformTo=function(e,t){if(t=t||"",!e)return t;a.checkSupport(e);var r=a.getTypeOf(t);return c[r][e](t)},a.resolve=function(e){for(var t=e.split("/"),r=[],n=0;n<t.length;n++){var i=t[n];"."===i||""===i&&0!==n&&n!==t.length-1||(".."===i?r.pop():r.push(i))}return r.join("/")},a.getTypeOf=function(e){return"string"==typeof e?"string":"[object Array]"===Object.prototype.toString.call(e)?"array":o.nodebuffer&&r.isBuffer(e)?"nodebuffer":o.uint8array&&e instanceof Uint8Array?"uint8array":o.arraybuffer&&e instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(e){if(!o[e.toLowerCase()])throw new Error(e+" is not supported by this platform")},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(e){var t,r,n="";for(r=0;r<(e||"").length;r++)n+="\\x"+((t=e.charCodeAt(r))<16?"0":"")+t.toString(16).toUpperCase();return n},a.delay=function(e,t,r){setImmediate(function(){e.apply(r||null,t||[])})},a.inherits=function(e,t){function r(){}r.prototype=t.prototype,e.prototype=new r},a.extend=function(){var e,t,r={};for(e=0;e<arguments.length;e++)for(t in arguments[e])Object.prototype.hasOwnProperty.call(arguments[e],t)&&void 0===r[t]&&(r[t]=arguments[e][t]);return r},a.prepareContent=function(r,e,n,i,s){return u.Promise.resolve(e).then(function(n){return o.blob&&(n instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(n)))&&"undefined"!=typeof FileReader?new u.Promise(function(t,r){var e=new FileReader;e.onload=function(e){t(e.target.result)},e.onerror=function(e){r(e.target.error)},e.readAsArrayBuffer(n)}):n}).then(function(e){var t=a.getTypeOf(e);return t?("arraybuffer"===t?e=a.transformTo("uint8array",e):"string"===t&&(s?e=h.decode(e):n&&!0!==i&&(e=function(e){return l(e,o.uint8array?new Uint8Array(e.length):new Array(e.length))}(e))),e):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,setimmediate:54}],33:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),i=e("./utils"),s=e("./signature"),a=e("./zipEntry"),o=e("./support");function h(e){this.files=[],this.loadOptions=e}h.prototype={checkSignature:function(e){if(!this.reader.readAndCheckSignature(e)){this.reader.index-=4;var t=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+i.pretty(t)+", expected "+i.pretty(e)+")")}},isSignature:function(e,t){var r=this.reader.index;this.reader.setIndex(e);var n=this.reader.readString(4)===t;return this.reader.setIndex(r),n},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var e=this.reader.readData(this.zipCommentLength),t=o.uint8array?"uint8array":"array",r=i.transformTo(t,e);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var e,t,r,n=this.zip64EndOfCentralSize-44;0<n;)e=this.reader.readInt(2),t=this.reader.readInt(4),r=this.reader.readData(t),this.zip64ExtensibleData[e]={id:e,length:t,value:r}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),1<this.disksCount)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var e,t;for(e=0;e<this.files.length;e++)t=this.files[e],this.reader.setIndex(t.localHeaderOffset),this.checkSignature(s.LOCAL_FILE_HEADER),t.readLocalPart(this.reader),t.handleUTF8(),t.processAttributes()},readCentralDir:function(){var e;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);)(e=new a({zip64:this.zip64},this.loadOptions)).readCentralPart(this.reader),this.files.push(e);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var e=this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);if(e<0)throw!this.isSignature(0,s.LOCAL_FILE_HEADER)?new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip: can't find end of central directory");this.reader.setIndex(e);var t=e;if(this.checkSignature(s.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===i.MAX_VALUE_16BITS||this.diskWithCentralDirStart===i.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===i.MAX_VALUE_16BITS||this.centralDirRecords===i.MAX_VALUE_16BITS||this.centralDirSize===i.MAX_VALUE_32BITS||this.centralDirOffset===i.MAX_VALUE_32BITS){if(this.zip64=!0,(e=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR))<0)throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(e),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,s.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var r=this.centralDirOffset+this.centralDirSize;this.zip64&&(r+=20,r+=12+this.zip64EndOfCentralSize);var n=t-r;if(0<n)this.isSignature(t,s.CENTRAL_FILE_HEADER)||(this.reader.zero=n);else if(n<0)throw new Error("Corrupted zip: missing "+Math.abs(n)+" bytes.")},prepareReader:function(e){this.reader=n(e)},load:function(e){this.prepareReader(e),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},t.exports=h},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utils":32,"./zipEntry":34}],34:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),s=e("./utils"),i=e("./compressedObject"),a=e("./crc32"),o=e("./utf8"),h=e("./compressions"),u=e("./support");function l(e,t){this.options=e,this.loadOptions=t}l.prototype={isEncrypted:function(){return 1==(1&this.bitFlag)},useUTF8:function(){return 2048==(2048&this.bitFlag)},readLocalPart:function(e){var t,r;if(e.skip(22),this.fileNameLength=e.readInt(2),r=e.readInt(2),this.fileName=e.readData(this.fileNameLength),e.skip(r),-1===this.compressedSize||-1===this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");if(null===(t=function(e){for(var t in h)if(Object.prototype.hasOwnProperty.call(h,t)&&h[t].magic===e)return h[t];return null}(this.compressionMethod)))throw new Error("Corrupted zip : compression "+s.pretty(this.compressionMethod)+" unknown (inner file : "+s.transformTo("string",this.fileName)+")");this.decompressed=new i(this.compressedSize,this.uncompressedSize,this.crc32,t,e.readData(this.compressedSize))},readCentralPart:function(e){this.versionMadeBy=e.readInt(2),e.skip(2),this.bitFlag=e.readInt(2),this.compressionMethod=e.readString(2),this.date=e.readDate(),this.crc32=e.readInt(4),this.compressedSize=e.readInt(4),this.uncompressedSize=e.readInt(4);var t=e.readInt(2);if(this.extraFieldsLength=e.readInt(2),this.fileCommentLength=e.readInt(2),this.diskNumberStart=e.readInt(2),this.internalFileAttributes=e.readInt(2),this.externalFileAttributes=e.readInt(4),this.localHeaderOffset=e.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");e.skip(t),this.readExtraFields(e),this.parseZIP64ExtraField(e),this.fileComment=e.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var e=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4<i;)t=e.readInt(2),r=e.readInt(2),n=e.readData(r),this.extraFields[t]={id:t,length:r,value:n};e.setIndex(i)},handleUTF8:function(){var e=u.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=o.utf8decode(this.fileName),this.fileCommentStr=o.utf8decode(this.fileComment);else{var t=this.findExtraFieldUnicodePath();if(null!==t)this.fileNameStr=t;else{var r=s.transformTo(e,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(r)}var n=this.findExtraFieldUnicodeComment();if(null!==n)this.fileCommentStr=n;else{var i=s.transformTo(e,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(i)}}},findExtraFieldUnicodePath:function(){var e=this.extraFields[28789];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileName)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null},findExtraFieldUnicodeComment:function(){var e=this.extraFields[25461];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileComment)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null}},t.exports=l},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(e,t,r){"use strict";function n(e,t,r){this.name=e,this.dir=r.dir,this.date=r.date,this.comment=r.comment,this.unixPermissions=r.unixPermissions,this.dosPermissions=r.dosPermissions,this._data=t,this._dataBinary=r.binary,this.options={compression:r.compression,compressionOptions:r.compressionOptions}}var s=e("./stream/StreamHelper"),i=e("./stream/DataWorker"),a=e("./utf8"),o=e("./compressedObject"),h=e("./stream/GenericWorker");n.prototype={internalStream:function(e){var t=null,r="string";try{if(!e)throw new Error("No output type specified.");var n="string"===(r=e.toLowerCase())||"text"===r;"binarystring"!==r&&"text"!==r||(r="string"),t=this._decompressWorker();var i=!this._dataBinary;i&&!n&&(t=t.pipe(new a.Utf8EncodeWorker)),!i&&n&&(t=t.pipe(new a.Utf8DecodeWorker))}catch(e){(t=new h("error")).error(e)}return new s(t,r,"")},async:function(e,t){return this.internalStream(e).accumulate(t)},nodeStream:function(e,t){return this.internalStream(e||"nodebuffer").toNodejsStream(t)},_compressWorker:function(e,t){if(this._data instanceof o&&this._data.compression.magic===e.magic)return this._data.getCompressedWorker();var r=this._decompressWorker();return this._dataBinary||(r=r.pipe(new a.Utf8EncodeWorker)),o.createWorkerFrom(r,e,t)},_decompressWorker:function(){return this._data instanceof o?this._data.getContentWorker():this._data instanceof h?this._data:new i(this._data)}};for(var u=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],l=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},f=0;f<u.length;f++)n.prototype[u[f]]=l;t.exports=n},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(e,l,t){(function(t){"use strict";var r,n,e=t.MutationObserver||t.WebKitMutationObserver;if(e){var i=0,s=new e(u),a=t.document.createTextNode("");s.observe(a,{characterData:!0}),r=function(){a.data=i=++i%2}}else if(t.setImmediate||void 0===t.MessageChannel)r="document"in t&&"onreadystatechange"in t.document.createElement("script")?function(){var e=t.document.createElement("script");e.onreadystatechange=function(){u(),e.onreadystatechange=null,e.parentNode.removeChild(e),e=null},t.document.documentElement.appendChild(e)}:function(){setTimeout(u,0)};else{var o=new t.MessageChannel;o.port1.onmessage=u,r=function(){o.port2.postMessage(0)}}var h=[];function u(){var e,t;n=!0;for(var r=h.length;r;){for(t=h,h=[],e=-1;++e<r;)t[e]();r=h.length}n=!1}l.exports=function(e){1!==h.push(e)||n||r()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(e,t,r){"use strict";var i=e("immediate");function u(){}var l={},s=["REJECTED"],a=["FULFILLED"],n=["PENDING"];function o(e){if("function"!=typeof e)throw new TypeError("resolver must be a function");this.state=n,this.queue=[],this.outcome=void 0,e!==u&&d(this,e)}function h(e,t,r){this.promise=e,"function"==typeof t&&(this.onFulfilled=t,this.callFulfilled=this.otherCallFulfilled),"function"==typeof r&&(this.onRejected=r,this.callRejected=this.otherCallRejected)}function f(t,r,n){i(function(){var e;try{e=r(n)}catch(e){return l.reject(t,e)}e===t?l.reject(t,new TypeError("Cannot resolve promise with itself")):l.resolve(t,e)})}function c(e){var t=e&&e.then;if(e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof t)return function(){t.apply(e,arguments)}}function d(t,e){var r=!1;function n(e){r||(r=!0,l.reject(t,e))}function i(e){r||(r=!0,l.resolve(t,e))}var s=p(function(){e(i,n)});"error"===s.status&&n(s.value)}function p(e,t){var r={};try{r.value=e(t),r.status="success"}catch(e){r.status="error",r.value=e}return r}(t.exports=o).prototype.finally=function(t){if("function"!=typeof t)return this;var r=this.constructor;return this.then(function(e){return r.resolve(t()).then(function(){return e})},function(e){return r.resolve(t()).then(function(){throw e})})},o.prototype.catch=function(e){return this.then(null,e)},o.prototype.then=function(e,t){if("function"!=typeof e&&this.state===a||"function"!=typeof t&&this.state===s)return this;var r=new this.constructor(u);this.state!==n?f(r,this.state===a?e:t,this.outcome):this.queue.push(new h(r,e,t));return r},h.prototype.callFulfilled=function(e){l.resolve(this.promise,e)},h.prototype.otherCallFulfilled=function(e){f(this.promise,this.onFulfilled,e)},h.prototype.callRejected=function(e){l.reject(this.promise,e)},h.prototype.otherCallRejected=function(e){f(this.promise,this.onRejected,e)},l.resolve=function(e,t){var r=p(c,t);if("error"===r.status)return l.reject(e,r.value);var n=r.value;if(n)d(e,n);else{e.state=a,e.outcome=t;for(var i=-1,s=e.queue.length;++i<s;)e.queue[i].callFulfilled(t)}return e},l.reject=function(e,t){e.state=s,e.outcome=t;for(var r=-1,n=e.queue.length;++r<n;)e.queue[r].callRejected(t);return e},o.resolve=function(e){if(e instanceof this)return e;return l.resolve(new this(u),e)},o.reject=function(e){var t=new this(u);return l.reject(t,e)},o.all=function(e){var r=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var n=e.length,i=!1;if(!n)return this.resolve([]);var s=new Array(n),a=0,t=-1,o=new this(u);for(;++t<n;)h(e[t],t);return o;function h(e,t){r.resolve(e).then(function(e){s[t]=e,++a!==n||i||(i=!0,l.resolve(o,s))},function(e){i||(i=!0,l.reject(o,e))})}},o.race=function(e){var t=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var r=e.length,n=!1;if(!r)return this.resolve([]);var i=-1,s=new this(u);for(;++i<r;)a=e[i],t.resolve(a).then(function(e){n||(n=!0,l.resolve(s,e))},function(e){n||(n=!0,l.reject(s,e))});var a;return s}},{immediate:36}],38:[function(e,t,r){"use strict";var n={};(0,e("./lib/utils/common").assign)(n,e("./lib/deflate"),e("./lib/inflate"),e("./lib/zlib/constants")),t.exports=n},{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(e,t,r){"use strict";var a=e("./zlib/deflate"),o=e("./utils/common"),h=e("./utils/strings"),i=e("./zlib/messages"),s=e("./zlib/zstream"),u=Object.prototype.toString,l=0,f=-1,c=0,d=8;function p(e){if(!(this instanceof p))return new p(e);this.options=o.assign({level:f,method:d,chunkSize:16384,windowBits:15,memLevel:8,strategy:c,to:""},e||{});var t=this.options;t.raw&&0<t.windowBits?t.windowBits=-t.windowBits:t.gzip&&0<t.windowBits&&t.windowBits<16&&(t.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=0;var r=a.deflateInit2(this.strm,t.level,t.method,t.windowBits,t.memLevel,t.strategy);if(r!==l)throw new Error(i[r]);if(t.header&&a.deflateSetHeader(this.strm,t.header),t.dictionary){var n;if(n="string"==typeof t.dictionary?h.string2buf(t.dictionary):"[object ArrayBuffer]"===u.call(t.dictionary)?new Uint8Array(t.dictionary):t.dictionary,(r=a.deflateSetDictionary(this.strm,n))!==l)throw new Error(i[r]);this._dict_set=!0}}function n(e,t){var r=new p(t);if(r.push(e,!0),r.err)throw r.msg||i[r.err];return r.result}p.prototype.push=function(e,t){var r,n,i=this.strm,s=this.options.chunkSize;if(this.ended)return!1;n=t===~~t?t:!0===t?4:0,"string"==typeof e?i.input=h.string2buf(e):"[object ArrayBuffer]"===u.call(e)?i.input=new Uint8Array(e):i.input=e,i.next_in=0,i.avail_in=i.input.length;do{if(0===i.avail_out&&(i.output=new o.Buf8(s),i.next_out=0,i.avail_out=s),1!==(r=a.deflate(i,n))&&r!==l)return this.onEnd(r),!(this.ended=!0);0!==i.avail_out&&(0!==i.avail_in||4!==n&&2!==n)||("string"===this.options.to?this.onData(h.buf2binstring(o.shrinkBuf(i.output,i.next_out))):this.onData(o.shrinkBuf(i.output,i.next_out)))}while((0<i.avail_in||0===i.avail_out)&&1!==r);return 4===n?(r=a.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===l):2!==n||(this.onEnd(l),!(i.avail_out=0))},p.prototype.onData=function(e){this.chunks.push(e)},p.prototype.onEnd=function(e){e===l&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Deflate=p,r.deflate=n,r.deflateRaw=function(e,t){return(t=t||{}).raw=!0,n(e,t)},r.gzip=function(e,t){return(t=t||{}).gzip=!0,n(e,t)}},{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(e,t,r){"use strict";var c=e("./zlib/inflate"),d=e("./utils/common"),p=e("./utils/strings"),m=e("./zlib/constants"),n=e("./zlib/messages"),i=e("./zlib/zstream"),s=e("./zlib/gzheader"),_=Object.prototype.toString;function a(e){if(!(this instanceof a))return new a(e);this.options=d.assign({chunkSize:16384,windowBits:0,to:""},e||{});var t=this.options;t.raw&&0<=t.windowBits&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(0<=t.windowBits&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),15<t.windowBits&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new i,this.strm.avail_out=0;var r=c.inflateInit2(this.strm,t.windowBits);if(r!==m.Z_OK)throw new Error(n[r]);this.header=new s,c.inflateGetHeader(this.strm,this.header)}function o(e,t){var r=new a(t);if(r.push(e,!0),r.err)throw r.msg||n[r.err];return r.result}a.prototype.push=function(e,t){var r,n,i,s,a,o,h=this.strm,u=this.options.chunkSize,l=this.options.dictionary,f=!1;if(this.ended)return!1;n=t===~~t?t:!0===t?m.Z_FINISH:m.Z_NO_FLUSH,"string"==typeof e?h.input=p.binstring2buf(e):"[object ArrayBuffer]"===_.call(e)?h.input=new Uint8Array(e):h.input=e,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new d.Buf8(u),h.next_out=0,h.avail_out=u),(r=c.inflate(h,m.Z_NO_FLUSH))===m.Z_NEED_DICT&&l&&(o="string"==typeof l?p.string2buf(l):"[object ArrayBuffer]"===_.call(l)?new Uint8Array(l):l,r=c.inflateSetDictionary(this.strm,o)),r===m.Z_BUF_ERROR&&!0===f&&(r=m.Z_OK,f=!1),r!==m.Z_STREAM_END&&r!==m.Z_OK)return this.onEnd(r),!(this.ended=!0);h.next_out&&(0!==h.avail_out&&r!==m.Z_STREAM_END&&(0!==h.avail_in||n!==m.Z_FINISH&&n!==m.Z_SYNC_FLUSH)||("string"===this.options.to?(i=p.utf8border(h.output,h.next_out),s=h.next_out-i,a=p.buf2string(h.output,i),h.next_out=s,h.avail_out=u-s,s&&d.arraySet(h.output,h.output,i,s,0),this.onData(a)):this.onData(d.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(f=!0)}while((0<h.avail_in||0===h.avail_out)&&r!==m.Z_STREAM_END);return r===m.Z_STREAM_END&&(n=m.Z_FINISH),n===m.Z_FINISH?(r=c.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===m.Z_OK):n!==m.Z_SYNC_FLUSH||(this.onEnd(m.Z_OK),!(h.avail_out=0))},a.prototype.onData=function(e){this.chunks.push(e)},a.prototype.onEnd=function(e){e===m.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=d.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Inflate=a,r.inflate=o,r.inflateRaw=function(e,t){return(t=t||{}).raw=!0,o(e,t)},r.ungzip=o},{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;r.assign=function(e){for(var t=Array.prototype.slice.call(arguments,1);t.length;){var r=t.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var n in r)r.hasOwnProperty(n)&&(e[n]=r[n])}}return e},r.shrinkBuf=function(e,t){return e.length===t?e:e.subarray?e.subarray(0,t):(e.length=t,e)};var i={arraySet:function(e,t,r,n,i){if(t.subarray&&e.subarray)e.set(t.subarray(r,r+n),i);else for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){var t,r,n,i,s,a;for(t=n=0,r=e.length;t<r;t++)n+=e[t].length;for(a=new Uint8Array(n),t=i=0,r=e.length;t<r;t++)s=e[t],a.set(s,i),i+=s.length;return a}},s={arraySet:function(e,t,r,n,i){for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){return[].concat.apply([],e)}};r.setTyped=function(e){e?(r.Buf8=Uint8Array,r.Buf16=Uint16Array,r.Buf32=Int32Array,r.assign(r,i)):(r.Buf8=Array,r.Buf16=Array,r.Buf32=Array,r.assign(r,s))},r.setTyped(n)},{}],42:[function(e,t,r){"use strict";var h=e("./common"),i=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(e){i=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(e){s=!1}for(var u=new h.Buf8(256),n=0;n<256;n++)u[n]=252<=n?6:248<=n?5:240<=n?4:224<=n?3:192<=n?2:1;function l(e,t){if(t<65537&&(e.subarray&&s||!e.subarray&&i))return String.fromCharCode.apply(null,h.shrinkBuf(e,t));for(var r="",n=0;n<t;n++)r+=String.fromCharCode(e[n]);return r}u[254]=u[254]=1,r.string2buf=function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=new h.Buf8(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return l(e,e.length)},r.binstring2buf=function(e){for(var t=new h.Buf8(e.length),r=0,n=t.length;r<n;r++)t[r]=e.charCodeAt(r);return t},r.buf2string=function(e,t){var r,n,i,s,a=t||e.length,o=new Array(2*a);for(r=n=0;r<a;)if((i=e[r++])<128)o[n++]=i;else if(4<(s=u[i]))o[n++]=65533,r+=s-1;else{for(i&=2===s?31:3===s?15:7;1<s&&r<a;)i=i<<6|63&e[r++],s--;1<s?o[n++]=65533:i<65536?o[n++]=i:(i-=65536,o[n++]=55296|i>>10&1023,o[n++]=56320|1023&i)}return l(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3<r?2e3:r;s=s+(i=i+t[n++]|0)|0,--a;);i%=65521,s%=65521}return i|s<<16|0}},{}],44:[function(e,t,r){"use strict";t.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],45:[function(e,t,r){"use strict";var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var h,c=e("../utils/common"),u=e("./trees"),d=e("./adler32"),p=e("./crc32"),n=e("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,i=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=n[t],t}function T(e){return(e<<1)-(4<e?9:0)}function D(e){for(var t=e.length;0<=--t;)e[t]=0}function F(e){var t=e.state,r=t.pending;r>e.avail_out&&(r=e.avail_out),0!==r&&(c.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){u._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&s<c);if(n=S-(c-s),s=c-S,a<n){if(e.match_start=t,o<=(a=n))break;d=u[s+a-1],p=u[s+a]}}}while((t=f[t&l])>h&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,i,s,a,o,h,u,l,f=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-z)){for(c.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,t=r=e.hash_size;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(t=r=f;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);i+=f}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,h=e.strstart+e.lookahead,u=i,l=void 0,l=a.avail_in,u<l&&(l=u),r=0===l?0:(a.avail_in-=l,c.arraySet(o,a.input,a.next_in,l,h),1===a.state.wrap?a.adler=d(a.adler,o,l,h):2===a.state.wrap&&(a.adler=p(a.adler,o,l,h)),a.next_in+=l,a.total_in+=l,l),e.lookahead+=r,e.lookahead+e.insert>=x)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+1])&e.hash_mask;e.insert&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+x-1])&e.hash_mask,e.prev[s&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=s,s++,e.insert--,!(e.lookahead+e.insert<x)););}while(e.lookahead<z&&0!==e.strm.avail_in)}function Z(e,t){for(var r,n;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!==r&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r)),e.match_length>=x)if(n=u._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart,0!=--e.match_length;);e.strstart++}else e.strstart+=e.match_length,e.match_length=0,e.ins_h=e.window[e.strstart],e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+1])&e.hash_mask;else n=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++;if(n&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function W(e,t){for(var r,n,i;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),e.prev_length=e.match_length,e.prev_match=e.match_start,e.match_length=x-1,0!==r&&e.prev_length<e.max_lazy_match&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r),e.match_length<=5&&(1===e.strategy||e.match_length===x&&4096<e.strstart-e.match_start)&&(e.match_length=x-1)),e.prev_length>=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=u._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!=--e.prev_length;);if(e.match_available=0,e.match_length=x-1,e.strstart++,n&&(N(e,!1),0===e.strm.avail_out))return A}else if(e.match_available){if((n=u._tr_tally(e,0,e.window[e.strstart-1]))&&N(e,!1),e.strstart++,e.lookahead--,0===e.strm.avail_out)return A}else e.match_available=1,e.strstart++,e.lookahead--}return e.match_available&&(n=u._tr_tally(e,0,e.window[e.strstart-1]),e.match_available=0),e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function M(e,t,r,n,i){this.good_length=e,this.max_lazy=t,this.nice_length=r,this.max_chain=n,this.func=i}function H(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=v,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new c.Buf16(2*w),this.dyn_dtree=new c.Buf16(2*(2*a+1)),this.bl_tree=new c.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new c.Buf16(k+1),this.heap=new c.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new c.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(e){var t;return e&&e.state?(e.total_in=e.total_out=0,e.data_type=i,(t=e.state).pending=0,t.pending_out=0,t.wrap<0&&(t.wrap=-t.wrap),t.status=t.wrap?C:E,e.adler=2===t.wrap?0:1,t.last_flush=l,u._tr_init(t),m):R(e,_)}function K(e){var t=G(e);return t===m&&function(e){e.window_size=2*e.w_size,D(e.head),e.max_lazy_match=h[e.level].max_lazy,e.good_match=h[e.level].good_length,e.nice_match=h[e.level].nice_length,e.max_chain_length=h[e.level].max_chain,e.strstart=0,e.block_start=0,e.lookahead=0,e.insert=0,e.match_length=e.prev_length=x-1,e.match_available=0,e.ins_h=0}(e.state),t}function Y(e,t,r,n,i,s){if(!e)return _;var a=1;if(t===g&&(t=6),n<0?(a=0,n=-n):15<n&&(a=2,n-=16),i<1||y<i||r!==v||n<8||15<n||t<0||9<t||s<0||b<s)return R(e,_);8===n&&(n=9);var o=new H;return(e.state=o).strm=e,o.wrap=a,o.gzhead=null,o.w_bits=n,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=i+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+x-1)/x),o.window=new c.Buf8(2*o.w_size),o.head=new c.Buf16(o.hash_size),o.prev=new c.Buf16(o.w_size),o.lit_bufsize=1<<i+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new c.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=t,o.strategy=s,o.method=r,K(e)}h=[new M(0,0,0,0,function(e,t){var r=65535;for(r>e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===l)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?_:(e.state.gzhead=t,m):_},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5<t||t<0)return e?R(e,_):_;if(n=e.state,!e.output||!e.input&&0!==e.avail_in||666===n.status&&t!==f)return R(e,0===e.avail_out?-5:_);if(n.strm=e,r=n.last_flush,n.last_flush=t,n.status===C)if(2===n.wrap)e.adler=0,U(n,31),U(n,139),U(n,8),n.gzhead?(U(n,(n.gzhead.text?1:0)+(n.gzhead.hcrc?2:0)+(n.gzhead.extra?4:0)+(n.gzhead.name?8:0)+(n.gzhead.comment?16:0)),U(n,255&n.gzhead.time),U(n,n.gzhead.time>>8&255),U(n,n.gzhead.time>>16&255),U(n,n.gzhead.time>>24&255),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(U(n,255&n.gzhead.extra.length),U(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(U(n,0),U(n,0),U(n,0),U(n,0),U(n,0),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,3),n.status=E);else{var a=v+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=E,P(n,a),0!==n.strstart&&(P(n,e.adler>>>16),P(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending!==n.pending_buf_size));)U(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.name.length?255&n.gzhead.name.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.comment.length?255&n.gzhead.comment.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&F(e),n.pending+2<=n.pending_buf_size&&(U(n,255&e.adler),U(n,e.adler>>8&255),e.adler=0,n.status=E)):n.status=E),0!==n.pending){if(F(e),0===e.avail_out)return n.last_flush=-1,m}else if(0===e.avail_in&&T(t)<=T(r)&&t!==f)return R(e,-5);if(666===n.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==l&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===l)return A;break}if(e.match_length=0,r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===l)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0<e.strstart&&(n=a[i=e.strstart-1])===a[++i]&&n===a[++i]&&n===a[++i]){s=e.strstart+S;do{}while(n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&i<s);e.match_length=S-(s-i),e.match_length>e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=u._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):h[n.level].func(n,t);if(o!==O&&o!==B||(n.status=666),o===A||o===O)return 0===e.avail_out&&(n.last_flush=-1),m;if(o===I&&(1===t?u._tr_align(n):5!==t&&(u._tr_stored_block(n,0,0,!1),3===t&&(D(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),F(e),0===e.avail_out))return n.last_flush=-1,m}return t!==f?m:n.wrap<=0?1:(2===n.wrap?(U(n,255&e.adler),U(n,e.adler>>8&255),U(n,e.adler>>16&255),U(n,e.adler>>24&255),U(n,255&e.total_in),U(n,e.total_in>>8&255),U(n,e.total_in>>16&255),U(n,e.total_in>>24&255)):(P(n,e.adler>>>16),P(n,65535&e.adler)),F(e),0<n.wrap&&(n.wrap=-n.wrap),0!==n.pending?m:1)},r.deflateEnd=function(e){var t;return e&&e.state?(t=e.state.status)!==C&&69!==t&&73!==t&&91!==t&&103!==t&&t!==E&&666!==t?R(e,_):(e.state=null,t===E?R(e,-3):m):_},r.deflateSetDictionary=function(e,t){var r,n,i,s,a,o,h,u,l=t.length;if(!e||!e.state)return _;if(2===(s=(r=e.state).wrap)||1===s&&r.status!==C||r.lookahead)return _;for(1===s&&(e.adler=d(e.adler,t,l,0)),r.wrap=0,l>=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new c.Buf8(r.w_size),c.arraySet(u,t,l-r.w_size,r.w_size,0),t=u,l=r.w_size),a=e.avail_in,o=e.next_in,h=e.input,e.avail_in=l,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,i=r.lookahead-(x-1);r.ins_h=(r.ins_h<<r.hash_shift^r.window[n+x-1])&r.hash_mask,r.prev[n&r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=n,n++,--i;);r.strstart=n,r.lookahead=x-1,j(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=x-1,r.match_available=0,e.next_in=o,e.input=h,e.avail_in=a,r.wrap=s,m},r.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(e,t,r){"use strict";t.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],48:[function(e,t,r){"use strict";t.exports=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C;r=e.state,n=e.next_in,z=e.input,i=n+(e.avail_in-5),s=e.next_out,C=e.output,a=s-(t-e.avail_out),o=s+(e.avail_out-257),h=r.dmax,u=r.wsize,l=r.whave,f=r.wnext,c=r.window,d=r.hold,p=r.bits,m=r.lencode,_=r.distcode,g=(1<<r.lenbits)-1,b=(1<<r.distbits)-1;e:do{p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=m[d&g];t:for(;;){if(d>>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<<y)-1)];continue t}if(32&y){r.mode=12;break e}e.msg="invalid literal/length code",r.mode=30;break e}w=65535&v,(y&=15)&&(p<y&&(d+=z[n++]<<p,p+=8),w+=d&(1<<y)-1,d>>>=y,p-=y),p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=_[d&b];r:for(;;){if(d>>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<<y)-1)];continue r}e.msg="invalid distance code",r.mode=30;break e}if(k=65535&v,p<(y&=15)&&(d+=z[n++]<<p,(p+=8)<y&&(d+=z[n++]<<p,p+=8)),h<(k+=d&(1<<y)-1)){e.msg="invalid distance too far back",r.mode=30;break e}if(d>>>=y,p-=y,(y=s-a)<k){if(l<(y=k-y)&&r.sane){e.msg="invalid distance too far back",r.mode=30;break e}if(S=c,(x=0)===f){if(x+=u-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}}else if(f<y){if(x+=u+f-y,(y-=f)<w){for(w-=y;C[s++]=c[x++],--y;);if(x=0,f<w){for(w-=y=f;C[s++]=c[x++],--y;);x=s-k,S=C}}}else if(x+=f-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}for(;2<w;)C[s++]=S[x++],C[s++]=S[x++],C[s++]=S[x++],w-=3;w&&(C[s++]=S[x++],1<w&&(C[s++]=S[x++]))}else{for(x=s-k;C[s++]=C[x++],C[s++]=C[x++],C[s++]=C[x++],2<(w-=3););w&&(C[s++]=C[x++],1<w&&(C[s++]=C[x++]))}break}}break}}while(n<i&&s<o);n-=w=p>>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n<i?i-n+5:5-(n-i),e.avail_out=s<o?o-s+257:257-(s-o),r.hold=d,r.bits=p}},{}],49:[function(e,t,r){"use strict";var I=e("../utils/common"),O=e("./adler32"),B=e("./crc32"),R=e("./inffast"),T=e("./inftrees"),D=1,F=2,N=0,U=-2,P=1,n=852,i=592;function L(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15<t)?U:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=r,n.wbits=t,o(e))):U}function u(e,t){var r,n;return e?(n=new s,(e.state=n).window=null,(r=h(e,t))!==N&&(e.state=null),r):U}var l,f,c=!0;function j(e){if(c){var t;for(l=new I.Buf32(512),f=new I.Buf32(32),t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(T(D,e.lens,0,288,l,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;T(F,e.lens,0,32,f,0,e.work,{bits:5}),c=!1}e.lencode=l,e.lenbits=9,e.distcode=f,e.distbits=5}function Z(e,t,r,n){var i,s=e.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new I.Buf8(s.wsize)),n>=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=i))),0}r.inflateReset=o,r.inflateReset2=h,r.inflateResetKeep=a,r.inflateInit=function(e){return u(e,15)},r.inflateInit2=u,r.inflate=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C=0,E=new I.Buf8(4),A=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!e||!e.state||!e.output||!e.input&&0!==e.avail_in)return U;12===(r=e.state).mode&&(r.mode=13),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,f=o,c=h,x=N;e:for(;;)switch(r.mode){case P:if(0===r.wrap){r.mode=13;break}for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(2&r.wrap&&35615===u){E[r.check=0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){e.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<<k,e.adler=r.check=1,r.mode=512&u?10:12,l=u=0;break;case 2:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.flags=u,8!=(255&r.flags)){e.msg="unknown compression method",r.mode=30;break}if(57344&r.flags){e.msg="unknown header flags set",r.mode=30;break}r.head&&(r.head.text=u>>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.time=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.xflags=255&u,r.head.os=u>>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length=u,r.head&&(r.head.extra_len=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.name=null);r.length=0,r.mode=8;case 8:if(4096&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.comment+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.comment=null);r.mode=9;case 9:if(512&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(65535&r.check)){e.msg="header crc mismatch",r.mode=30;break}l=u=0}r.head&&(r.head.hcrc=r.flags>>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}e.adler=r.check=L(u),l=u=0,r.mode=11;case 11:if(0===r.havedict)return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,2;e.adler=r.check=1,r.mode=12;case 12:if(5===t||6===t)break e;case 13:if(r.last){u>>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}switch(r.last=1&u,l-=1,3&(u>>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if((65535&u)!=(u>>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o<d&&(d=o),h<d&&(d=h),0===d)break e;I.arraySet(i,n,s,d,a),o-=d,s+=d,h-=d,a+=d,r.length-=d;break}r.mode=12;break;case 17:for(;l<14;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.nlen=257+(31&u),u>>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286<r.nlen||30<r.ndist){e.msg="too many length or distance symbols",r.mode=30;break}r.have=0,r.mode=18;case 18:for(;r.have<r.ncode;){for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.lens[A[r.have++]]=7&u,u>>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have<r.nlen+r.ndist;){for(;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(b<16)u>>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u>>>=_,l-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=3+(7&(u>>>=_)),u>>>=3,l-=3}else{for(z=_+7;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=11+(127&(u>>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(g&&0==(240&g)){for(v=_,y=g,w=b;g=(C=r.lencode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<<r.distbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(0==(240&g)){for(v=_,y=g,w=b;g=(C=r.distcode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.offset+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(h<d&&(d=h),h-=d,r.length-=d;i[a++]=m[p++],--d;);0===r.length&&(r.mode=21);break;case 26:if(0===h)break e;i[a++]=r.length,h--,r.mode=21;break;case 27:if(r.wrap){for(;l<32;){if(0===o)break e;o--,u|=n[s++]<<l,l+=8}if(c-=h,e.total_out+=c,r.total+=c,c&&(e.adler=r.check=r.flags?B(r.check,i,c,a-c):O(r.check,i,c,a-c)),c=h,(r.flags?u:L(u))!==r.check){e.msg="incorrect data check",r.mode=30;break}l=u=0}r.mode=28;case 28:if(r.wrap&&r.flags){for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(4294967295&r.total)){e.msg="incorrect length check",r.mode=30;break}l=u=0}r.mode=29;case 29:x=1;break e;case 30:x=-3;break e;case 31:return-4;case 32:default:return U}return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,(r.wsize||c!==e.avail_out&&r.mode<30&&(r.mode<27||4!==t))&&Z(e,e.output,e.next_out,c-e.avail_out)?(r.mode=31,-4):(f-=e.avail_in,c-=e.avail_out,e.total_in+=f,e.total_out+=c,r.total+=c,r.wrap&&c&&(e.adler=r.check=r.flags?B(r.check,i,c,e.next_out-c):O(r.check,i,c,e.next_out-c)),e.data_type=r.bits+(r.last?64:0)+(12===r.mode?128:0)+(20===r.mode||15===r.mode?256:0),(0==f&&0===c||4===t)&&x===N&&(x=-5),x)},r.inflateEnd=function(e){if(!e||!e.state)return U;var t=e.state;return t.window&&(t.window=null),e.state=null,N},r.inflateGetHeader=function(e,t){var r;return e&&e.state?0==(2&(r=e.state).wrap)?U:((r.head=t).done=!1,N):U},r.inflateSetDictionary=function(e,t){var r,n=t.length;return e&&e.state?0!==(r=e.state).wrap&&11!==r.mode?U:11===r.mode&&O(1,t,n,0)!==r.check?-3:Z(e,t,n,n)?(r.mode=31,-4):(r.havedict=1,N):U},r.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(e,t,r){"use strict";var D=e("../utils/common"),F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],N=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],U=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],P=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];t.exports=function(e,t,r,n,i,s,a,o){var h,u,l,f,c,d,p,m,_,g=o.bits,b=0,v=0,y=0,w=0,k=0,x=0,S=0,z=0,C=0,E=0,A=null,I=0,O=new D.Buf16(16),B=new D.Buf16(16),R=null,T=0;for(b=0;b<=15;b++)O[b]=0;for(v=0;v<n;v++)O[t[r+v]]++;for(k=g,w=15;1<=w&&0===O[w];w--);if(w<k&&(k=w),0===w)return i[s++]=20971520,i[s++]=20971520,o.bits=1,0;for(y=1;y<w&&0===O[y];y++);for(k<y&&(k=y),b=z=1;b<=15;b++)if(z<<=1,(z-=O[b])<0)return-1;if(0<z&&(0===e||1!==w))return-1;for(B[1]=0,b=1;b<15;b++)B[b+1]=B[b]+O[b];for(v=0;v<n;v++)0!==t[r+v]&&(a[B[t[r+v]]++]=v);if(d=0===e?(A=R=a,19):1===e?(A=F,I-=257,R=N,T-=257,256):(A=U,R=P,-1),b=y,c=s,S=v=E=0,l=-1,f=(C=1<<(x=k))-1,1===e&&852<C||2===e&&592<C)return 1;for(;;){for(p=b-S,_=a[v]<d?(m=0,a[v]):a[v]>d?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<<b-S,y=u=1<<x;i[c+(E>>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<<b-1;E&h;)h>>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k<b&&(E&f)!==l){for(0===S&&(S=k),c+=y,z=1<<(x=b-S);x+S<w&&!((z-=O[x+S])<=0);)x++,z<<=1;if(C+=1<<x,1===e&&852<C||2===e&&592<C)return 1;i[l=E&f]=k<<24|x<<16|c-s|0}}return 0!==E&&(i[c+E]=b-S<<24|64<<16|0),o.bits=k,0}},{"../utils/common":41}],51:[function(e,t,r){"use strict";t.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],52:[function(e,t,r){"use strict";var i=e("../utils/common"),o=0,h=1;function n(e){for(var t=e.length;0<=--t;)e[t]=0}var s=0,a=29,u=256,l=u+1+a,f=30,c=19,_=2*l+1,g=15,d=16,p=7,m=256,b=16,v=17,y=18,w=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],k=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],x=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],z=new Array(2*(l+2));n(z);var C=new Array(2*f);n(C);var E=new Array(512);n(E);var A=new Array(256);n(A);var I=new Array(a);n(I);var O,B,R,T=new Array(f);function D(e,t,r,n,i){this.static_tree=e,this.extra_bits=t,this.extra_base=r,this.elems=n,this.max_length=i,this.has_stree=e&&e.length}function F(e,t){this.dyn_tree=e,this.max_code=0,this.stat_desc=t}function N(e){return e<256?E[e]:E[256+(e>>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<<e.bi_valid&65535,U(e,e.bi_buf),e.bi_buf=t>>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<<e.bi_valid&65535,e.bi_valid+=r)}function L(e,t,r){P(e,r[2*t],r[2*t+1])}function j(e,t){for(var r=0;r|=1&e,e>>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t<l;t++)e.dyn_ltree[2*t]=0;for(t=0;t<f;t++)e.dyn_dtree[2*t]=0;for(t=0;t<c;t++)e.bl_tree[2*t]=0;e.dyn_ltree[2*m]=1,e.opt_len=e.static_len=0,e.last_lit=e.matches=0}function M(e){8<e.bi_valid?U(e,e.bi_buf):0<e.bi_valid&&(e.pending_buf[e.pending++]=e.bi_buf),e.bi_buf=0,e.bi_valid=0}function H(e,t,r,n){var i=2*t,s=2*r;return e[i]<e[s]||e[i]===e[s]&&n[t]<=n[r]}function G(e,t,r){for(var n=e.heap[r],i=r<<1;i<=e.heap_len&&(i<e.heap_len&&H(t,e.heap[i+1],e.heap[i],e.depth)&&i++,!H(t,n,e.heap[i],e.depth));)e.heap[r]=e.heap[i],r=i,i<<=1;e.heap[r]=n}function K(e,t,r){var n,i,s,a,o=0;if(0!==e.last_lit)for(;n=e.pending_buf[e.d_buf+2*o]<<8|e.pending_buf[e.d_buf+2*o+1],i=e.pending_buf[e.l_buf+o],o++,0===n?L(e,i,t):(L(e,(s=A[i])+u+1,t),0!==(a=w[s])&&P(e,i-=I[s],a),L(e,s=N(--n),r),0!==(a=k[s])&&P(e,n-=T[s],a)),o<e.last_lit;);L(e,m,t)}function Y(e,t){var r,n,i,s=t.dyn_tree,a=t.stat_desc.static_tree,o=t.stat_desc.has_stree,h=t.stat_desc.elems,u=-1;for(e.heap_len=0,e.heap_max=_,r=0;r<h;r++)0!==s[2*r]?(e.heap[++e.heap_len]=u=r,e.depth[r]=0):s[2*r+1]=0;for(;e.heap_len<2;)s[2*(i=e.heap[++e.heap_len]=u<2?++u:0)]=1,e.depth[i]=0,e.opt_len--,o&&(e.static_len-=a[2*i+1]);for(t.max_code=u,r=e.heap_len>>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u<n||(e.bl_count[s]++,a=0,d<=n&&(a=c[n-d]),o=h[2*n],e.opt_len+=o*(s+a),f&&(e.static_len+=o*(l[2*n+1]+a)));if(0!==m){do{for(s=p-1;0===e.bl_count[s];)s--;e.bl_count[s]--,e.bl_count[s+1]+=2,e.bl_count[p]--,m-=2}while(0<m);for(s=p;0!==s;s--)for(n=e.bl_count[s];0!==n;)u<(i=e.heap[--r])||(h[2*i+1]!==s&&(e.opt_len+=(s-h[2*i+1])*h[2*i],h[2*i+1]=s),n--)}}(e,t),Z(s,u,e.bl_count)}function X(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),t[2*(r+1)+1]=65535,n=0;n<=r;n++)i=a,a=t[2*(n+1)+1],++o<h&&i===a||(o<u?e.bl_tree[2*i]+=o:0!==i?(i!==s&&e.bl_tree[2*i]++,e.bl_tree[2*b]++):o<=10?e.bl_tree[2*v]++:e.bl_tree[2*y]++,s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4))}function V(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),n=0;n<=r;n++)if(i=a,a=t[2*(n+1)+1],!(++o<h&&i===a)){if(o<u)for(;L(e,i,e.bl_tree),0!=--o;);else 0!==i?(i!==s&&(L(e,i,e.bl_tree),o--),L(e,b,e.bl_tree),P(e,o-3,2)):o<=10?(L(e,v,e.bl_tree),P(e,o-3,3)):(L(e,y,e.bl_tree),P(e,o-11,7));s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4)}}n(T);var q=!1;function J(e,t,r,n){P(e,(s<<1)+(n?1:0),3),function(e,t,r,n){M(e),n&&(U(e,r),U(e,~r)),i.arraySet(e.pending_buf,e.window,t,r,e.pending),e.pending+=r}(e,t,r,!0)}r._tr_init=function(e){q||(function(){var e,t,r,n,i,s=new Array(g+1);for(n=r=0;n<a-1;n++)for(I[n]=r,e=0;e<1<<w[n];e++)A[r++]=n;for(A[r-1]=n,n=i=0;n<16;n++)for(T[n]=i,e=0;e<1<<k[n];e++)E[i++]=n;for(i>>=7;n<f;n++)for(T[n]=i<<7,e=0;e<1<<k[n]-7;e++)E[256+i++]=n;for(t=0;t<=g;t++)s[t]=0;for(e=0;e<=143;)z[2*e+1]=8,e++,s[8]++;for(;e<=255;)z[2*e+1]=9,e++,s[9]++;for(;e<=279;)z[2*e+1]=7,e++,s[7]++;for(;e<=287;)z[2*e+1]=8,e++,s[8]++;for(Z(z,l+1,s),e=0;e<f;e++)C[2*e+1]=5,C[2*e]=j(e,5);O=new D(z,w,u+1,l,g),B=new D(C,k,0,f,g),R=new D(new Array(0),x,0,c,p)}(),q=!0),e.l_desc=new F(e.dyn_ltree,O),e.d_desc=new F(e.dyn_dtree,B),e.bl_desc=new F(e.bl_tree,R),e.bi_buf=0,e.bi_valid=0,W(e)},r._tr_stored_block=J,r._tr_flush_block=function(e,t,r,n){var i,s,a=0;0<e.level?(2===e.strm.data_type&&(e.strm.data_type=function(e){var t,r=4093624447;for(t=0;t<=31;t++,r>>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t<u;t++)if(0!==e.dyn_ltree[2*t])return h;return o}(e)),Y(e,e.l_desc),Y(e,e.d_desc),a=function(e){var t;for(X(e,e.dyn_ltree,e.l_desc.max_code),X(e,e.dyn_dtree,e.d_desc.max_code),Y(e,e.bl_desc),t=c-1;3<=t&&0===e.bl_tree[2*S[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t}(e),i=e.opt_len+3+7>>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i<n;i++)P(e,e.bl_tree[2*S[i]+1],3);V(e,e.dyn_ltree,t-1),V(e,e.dyn_dtree,r-1)}(e,e.l_desc.max_code+1,e.d_desc.max_code+1,a+1),K(e,e.dyn_ltree,e.dyn_dtree)),W(e),n&&M(e)},r._tr_tally=function(e,t,r){return e.pending_buf[e.d_buf+2*e.last_lit]=t>>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){"use strict";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i="[object process]"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage("","*"),r.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",r.addEventListener?r.addEventListener("message",d,!1):r.attachEvent("onmessage",d),function(e){r.postMessage(a+e,"*")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(s=l.documentElement,function(e){var t=l.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),r=0;r<t.length;r++)t[r]=arguments[r+1];var n={callback:e,args:t};return h[o]=n,i(o),o++},e.clearImmediate=f}function f(e){delete h[e]}function c(e){if(u)setTimeout(c,0,e);else{var t=h[e];if(t){u=!0;try{!function(e){var t=e.callback,r=e.args;switch(r.length){case 0:t();break;case 1:t(r[0]);break;case 2:t(r[0],r[1]);break;case 3:t(r[0],r[1],r[2]);break;default:t.apply(n,r)}}(t)}finally{f(e),u=!1}}}}function d(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(a)&&c(+e.data.slice(a.length))}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[10])(10)}); +}).call(this)}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer,arguments[3],arguments[4],arguments[5],arguments[6],require("timers").setImmediate) +},{"_process":102,"buffer":84,"timers":103}],90:[function(require,module,exports){ +exports.Parser = require("./lib/parser").Parser; +exports.rules = require("./lib/rules"); +exports.errors = require("./lib/errors"); +exports.results = require("./lib/parsing-results"); +exports.StringSource = require("./lib/StringSource"); +exports.Token = require("./lib/Token"); +exports.bottomUp = require("./lib/bottom-up"); +exports.RegexTokeniser = require("./lib/regex-tokeniser").RegexTokeniser; + +exports.rule = function(ruleBuilder) { + var rule; + return function(input) { + if (!rule) { + rule = ruleBuilder(); + } + return rule(input); + }; +}; + +},{"./lib/StringSource":91,"./lib/Token":92,"./lib/bottom-up":94,"./lib/errors":95,"./lib/parser":97,"./lib/parsing-results":98,"./lib/regex-tokeniser":99,"./lib/rules":100}],91:[function(require,module,exports){ +var StringSource = module.exports = function(string, description) { + var self = { + asString: function() { + return string; + }, + range: function(startIndex, endIndex) { + return new StringSourceRange(string, description, startIndex, endIndex); + } + }; + return self; +}; + +var StringSourceRange = function(string, description, startIndex, endIndex) { + this._string = string; + this._description = description; + this._startIndex = startIndex; + this._endIndex = endIndex; +}; + +StringSourceRange.prototype.to = function(otherRange) { + // TODO: Assert that tokens are the same across both iterators + return new StringSourceRange(this._string, this._description, this._startIndex, otherRange._endIndex); +}; + +StringSourceRange.prototype.describe = function() { + var position = this._position(); + var description = this._description ? this._description + "\n" : ""; + return description + "Line number: " + position.lineNumber + "\nCharacter number: " + position.characterNumber; +}; + +StringSourceRange.prototype.lineNumber = function() { + return this._position().lineNumber; +}; + +StringSourceRange.prototype.characterNumber = function() { + return this._position().characterNumber; +}; + +StringSourceRange.prototype._position = function() { + var self = this; + var index = 0; + var nextNewLine = function() { + return self._string.indexOf("\n", index); + }; + + var lineNumber = 1; + while (nextNewLine() !== -1 && nextNewLine() < this._startIndex) { + index = nextNewLine() + 1; + lineNumber += 1; + } + var characterNumber = this._startIndex - index + 1; + return {lineNumber: lineNumber, characterNumber: characterNumber}; +}; + +},{}],92:[function(require,module,exports){ +module.exports = function(name, value, source) { + this.name = name; + this.value = value; + if (source) { + this.source = source; + } +}; + +},{}],93:[function(require,module,exports){ +var TokenIterator = module.exports = function(tokens, startIndex) { + this._tokens = tokens; + this._startIndex = startIndex || 0; +}; + +TokenIterator.prototype.head = function() { + return this._tokens[this._startIndex]; +}; + +TokenIterator.prototype.tail = function(startIndex) { + return new TokenIterator(this._tokens, this._startIndex + 1); +}; + +TokenIterator.prototype.toArray = function() { + return this._tokens.slice(this._startIndex); +}; + +TokenIterator.prototype.end = function() { + return this._tokens[this._tokens.length - 1]; +}; + +// TODO: doesn't need to be a method, can be a separate function, +// which simplifies implementation of the TokenIterator interface +TokenIterator.prototype.to = function(end) { + var start = this.head().source; + var endToken = end.head() || end.end(); + return start.to(endToken.source); +}; + +},{}],94:[function(require,module,exports){ +var rules = require("./rules"); +var results = require("./parsing-results"); + +exports.parser = function(name, prefixRules, infixRuleBuilders) { + var self = { + rule: rule, + leftAssociative: leftAssociative, + rightAssociative: rightAssociative + }; + + var infixRules = new InfixRules(infixRuleBuilders.map(createInfixRule)); + var prefixRule = rules.firstOf(name, prefixRules); + + function createInfixRule(infixRuleBuilder) { + return { + name: infixRuleBuilder.name, + rule: lazyRule(infixRuleBuilder.ruleBuilder.bind(null, self)) + }; + } + + function rule() { + return createRule(infixRules); + } + + function leftAssociative(name) { + return createRule(infixRules.untilExclusive(name)); + } + + function rightAssociative(name) { + return createRule(infixRules.untilInclusive(name)); + } + + function createRule(infixRules) { + return apply.bind(null, infixRules); + } + + function apply(infixRules, tokens) { + var leftResult = prefixRule(tokens); + if (leftResult.isSuccess()) { + return infixRules.apply(leftResult); + } else { + return leftResult; + } + } + + return self; +}; + +function InfixRules(infixRules) { + function untilExclusive(name) { + return new InfixRules(infixRules.slice(0, ruleNames().indexOf(name))); + } + + function untilInclusive(name) { + return new InfixRules(infixRules.slice(0, ruleNames().indexOf(name) + 1)); + } + + function ruleNames() { + return infixRules.map(function(rule) { + return rule.name; + }); + } + + function apply(leftResult) { + var currentResult; + var source; + while (true) { + currentResult = applyToTokens(leftResult.remaining()); + if (currentResult.isSuccess()) { + source = leftResult.source().to(currentResult.source()); + leftResult = results.success( + currentResult.value()(leftResult.value(), source), + currentResult.remaining(), + source + ) + } else if (currentResult.isFailure()) { + return leftResult; + } else { + return currentResult; + } + } + } + + function applyToTokens(tokens) { + return rules.firstOf("infix", infixRules.map(function(infix) { + return infix.rule; + }))(tokens); + } + + return { + apply: apply, + untilExclusive: untilExclusive, + untilInclusive: untilInclusive + } +} + +exports.infix = function(name, ruleBuilder) { + function map(func) { + return exports.infix(name, function(parser) { + var rule = ruleBuilder(parser); + return function(tokens) { + var result = rule(tokens); + return result.map(function(right) { + return function(left, source) { + return func(left, right, source); + }; + }); + }; + }); + } + + return { + name: name, + ruleBuilder: ruleBuilder, + map: map + }; +} + +// TODO: move into a sensible place and remove duplication +var lazyRule = function(ruleBuilder) { + var rule; + return function(input) { + if (!rule) { + rule = ruleBuilder(); + } + return rule(input); + }; +}; + +},{"./parsing-results":98,"./rules":100}],95:[function(require,module,exports){ +exports.error = function(options) { + return new Error(options); +}; + +var Error = function(options) { + this.expected = options.expected; + this.actual = options.actual; + this._location = options.location; +}; + +Error.prototype.describe = function() { + var locationDescription = this._location ? this._location.describe() + ":\n" : ""; + return locationDescription + "Expected " + this.expected + "\nbut got " + this.actual; +}; + +Error.prototype.lineNumber = function() { + return this._location.lineNumber(); +}; + +Error.prototype.characterNumber = function() { + return this._location.characterNumber(); +}; + +},{}],96:[function(require,module,exports){ +var fromArray = exports.fromArray = function(array) { + var index = 0; + var hasNext = function() { + return index < array.length; + }; + return new LazyIterator({ + hasNext: hasNext, + next: function() { + if (!hasNext()) { + throw new Error("No more elements"); + } else { + return array[index++]; + } + } + }); +}; + +var LazyIterator = function(iterator) { + this._iterator = iterator; +}; + +LazyIterator.prototype.map = function(func) { + var iterator = this._iterator; + return new LazyIterator({ + hasNext: function() { + return iterator.hasNext(); + }, + next: function() { + return func(iterator.next()); + } + }); +}; + +LazyIterator.prototype.filter = function(condition) { + var iterator = this._iterator; + + var moved = false; + var hasNext = false; + var next; + var moveIfNecessary = function() { + if (moved) { + return; + } + moved = true; + hasNext = false; + while (iterator.hasNext() && !hasNext) { + next = iterator.next(); + hasNext = condition(next); + } + }; + + return new LazyIterator({ + hasNext: function() { + moveIfNecessary(); + return hasNext; + }, + next: function() { + moveIfNecessary(); + var toReturn = next; + moved = false; + return toReturn; + } + }); +}; + +LazyIterator.prototype.first = function() { + var iterator = this._iterator; + if (this._iterator.hasNext()) { + return iterator.next(); + } else { + return null; + } +}; + +LazyIterator.prototype.toArray = function() { + var result = []; + while (this._iterator.hasNext()) { + result.push(this._iterator.next()); + } + return result; +}; + +},{}],97:[function(require,module,exports){ +var TokenIterator = require("./TokenIterator"); + +exports.Parser = function(options) { + var parseTokens = function(parser, tokens) { + return parser(new TokenIterator(tokens)); + }; + + return { + parseTokens: parseTokens + }; +}; + +},{"./TokenIterator":93}],98:[function(require,module,exports){ +module.exports = { + failure: function(errors, remaining) { + if (errors.length < 1) { + throw new Error("Failure must have errors"); + } + return new Result({ + status: "failure", + remaining: remaining, + errors: errors + }); + }, + error: function(errors, remaining) { + if (errors.length < 1) { + throw new Error("Failure must have errors"); + } + return new Result({ + status: "error", + remaining: remaining, + errors: errors + }); + }, + success: function(value, remaining, source) { + return new Result({ + status: "success", + value: value, + source: source, + remaining: remaining, + errors: [] + }); + }, + cut: function(remaining) { + return new Result({ + status: "cut", + remaining: remaining, + errors: [] + }); + } +}; + +var Result = function(options) { + this._value = options.value; + this._status = options.status; + this._hasValue = options.value !== undefined; + this._remaining = options.remaining; + this._source = options.source; + this._errors = options.errors; +}; + +Result.prototype.map = function(func) { + if (this._hasValue) { + return new Result({ + value: func(this._value, this._source), + status: this._status, + remaining: this._remaining, + source: this._source, + errors: this._errors + }); + } else { + return this; + } +}; + +Result.prototype.changeRemaining = function(remaining) { + return new Result({ + value: this._value, + status: this._status, + remaining: remaining, + source: this._source, + errors: this._errors + }); +}; + +Result.prototype.isSuccess = function() { + return this._status === "success" || this._status === "cut"; +}; + +Result.prototype.isFailure = function() { + return this._status === "failure"; +}; + +Result.prototype.isError = function() { + return this._status === "error"; +}; + +Result.prototype.isCut = function() { + return this._status === "cut"; +}; + +Result.prototype.value = function() { + return this._value; +}; + +Result.prototype.remaining = function() { + return this._remaining; +}; + +Result.prototype.source = function() { + return this._source; +}; + +Result.prototype.errors = function() { + return this._errors; +}; + +},{}],99:[function(require,module,exports){ +var Token = require("./Token"); +var StringSource = require("./StringSource"); + +exports.RegexTokeniser = RegexTokeniser; + +function RegexTokeniser(rules) { + rules = rules.map(function(rule) { + return { + name: rule.name, + regex: new RegExp(rule.regex.source, "g") + }; + }); + + function tokenise(input, description) { + var source = new StringSource(input, description); + var index = 0; + var tokens = []; + + while (index < input.length) { + var result = readNextToken(input, index, source); + index = result.endIndex; + tokens.push(result.token); + } + + tokens.push(endToken(input, source)); + return tokens; + } + + function readNextToken(string, startIndex, source) { + for (var i = 0; i < rules.length; i++) { + var regex = rules[i].regex; + regex.lastIndex = startIndex; + var result = regex.exec(string); + + if (result) { + var endIndex = startIndex + result[0].length; + if (result.index === startIndex && endIndex > startIndex) { + var value = result[1]; + var token = new Token( + rules[i].name, + value, + source.range(startIndex, endIndex) + ); + return {token: token, endIndex: endIndex}; + } + } + } + var endIndex = startIndex + 1; + var token = new Token( + "unrecognisedCharacter", + string.substring(startIndex, endIndex), + source.range(startIndex, endIndex) + ); + return {token: token, endIndex: endIndex}; + } + + function endToken(input, source) { + return new Token( + "end", + null, + source.range(input.length, input.length) + ); + } + + return { + tokenise: tokenise + } +} + + + +},{"./StringSource":91,"./Token":92}],100:[function(require,module,exports){ +var _ = require("underscore"); +var options = require("option"); +var results = require("./parsing-results"); +var errors = require("./errors"); +var lazyIterators = require("./lazy-iterators"); + +exports.token = function(tokenType, value) { + var matchValue = value !== undefined; + return function(input) { + var token = input.head(); + if (token && token.name === tokenType && (!matchValue || token.value === value)) { + return results.success(token.value, input.tail(), token.source); + } else { + var expected = describeToken({name: tokenType, value: value}); + return describeTokenMismatch(input, expected); + } + }; +}; + +exports.tokenOfType = function(tokenType) { + return exports.token(tokenType); +}; + +exports.firstOf = function(name, parsers) { + if (!_.isArray(parsers)) { + parsers = Array.prototype.slice.call(arguments, 1); + } + return function(input) { + return lazyIterators + .fromArray(parsers) + .map(function(parser) { + return parser(input); + }) + .filter(function(result) { + return result.isSuccess() || result.isError(); + }) + .first() || describeTokenMismatch(input, name); + }; +}; + +exports.then = function(parser, func) { + return function(input) { + var result = parser(input); + if (!result.map) { + console.log(result); + } + return result.map(func); + }; +}; + +exports.sequence = function() { + var parsers = Array.prototype.slice.call(arguments, 0); + var rule = function(input) { + var result = _.foldl(parsers, function(memo, parser) { + var result = memo.result; + var hasCut = memo.hasCut; + if (!result.isSuccess()) { + return {result: result, hasCut: hasCut}; + } + var subResult = parser(result.remaining()); + if (subResult.isCut()) { + return {result: result, hasCut: true}; + } else if (subResult.isSuccess()) { + var values; + if (parser.isCaptured) { + values = result.value().withValue(parser, subResult.value()); + } else { + values = result.value(); + } + var remaining = subResult.remaining(); + var source = input.to(remaining); + return { + result: results.success(values, remaining, source), + hasCut: hasCut + }; + } else if (hasCut) { + return {result: results.error(subResult.errors(), subResult.remaining()), hasCut: hasCut}; + } else { + return {result: subResult, hasCut: hasCut}; + } + }, {result: results.success(new SequenceValues(), input), hasCut: false}).result; + var source = input.to(result.remaining()); + return result.map(function(values) { + return values.withValue(exports.sequence.source, source); + }); + }; + rule.head = function() { + var firstCapture = _.find(parsers, isCapturedRule); + return exports.then( + rule, + exports.sequence.extract(firstCapture) + ); + }; + rule.map = function(func) { + return exports.then( + rule, + function(result) { + return func.apply(this, result.toArray()); + } + ); + }; + + function isCapturedRule(subRule) { + return subRule.isCaptured; + } + + return rule; +}; + +var SequenceValues = function(values, valuesArray) { + this._values = values || {}; + this._valuesArray = valuesArray || []; +}; + +SequenceValues.prototype.withValue = function(rule, value) { + if (rule.captureName && rule.captureName in this._values) { + throw new Error("Cannot add second value for capture \"" + rule.captureName + "\""); + } else { + var newValues = _.clone(this._values); + newValues[rule.captureName] = value; + var newValuesArray = this._valuesArray.concat([value]); + return new SequenceValues(newValues, newValuesArray); + } +}; + +SequenceValues.prototype.get = function(rule) { + if (rule.captureName in this._values) { + return this._values[rule.captureName]; + } else { + throw new Error("No value for capture \"" + rule.captureName + "\""); + } +}; + +SequenceValues.prototype.toArray = function() { + return this._valuesArray; +}; + +exports.sequence.capture = function(rule, name) { + var captureRule = function() { + return rule.apply(this, arguments); + }; + captureRule.captureName = name; + captureRule.isCaptured = true; + return captureRule; +}; + +exports.sequence.extract = function(rule) { + return function(result) { + return result.get(rule); + }; +}; + +exports.sequence.applyValues = function(func) { + // TODO: check captureName doesn't conflict with source or other captures + var rules = Array.prototype.slice.call(arguments, 1); + return function(result) { + var values = rules.map(function(rule) { + return result.get(rule); + }); + return func.apply(this, values); + }; +}; + +exports.sequence.source = { + captureName: "☃source☃" +}; + +exports.sequence.cut = function() { + return function(input) { + return results.cut(input); + }; +}; + +exports.optional = function(rule) { + return function(input) { + var result = rule(input); + if (result.isSuccess()) { + return result.map(options.some); + } else if (result.isFailure()) { + return results.success(options.none, input); + } else { + return result; + } + }; +}; + +exports.zeroOrMoreWithSeparator = function(rule, separator) { + return repeatedWithSeparator(rule, separator, false); +}; + +exports.oneOrMoreWithSeparator = function(rule, separator) { + return repeatedWithSeparator(rule, separator, true); +}; + +var zeroOrMore = exports.zeroOrMore = function(rule) { + return function(input) { + var values = []; + var result; + while ((result = rule(input)) && result.isSuccess()) { + input = result.remaining(); + values.push(result.value()); + } + if (result.isError()) { + return result; + } else { + return results.success(values, input); + } + }; +}; + +exports.oneOrMore = function(rule) { + return exports.oneOrMoreWithSeparator(rule, noOpRule); +}; + +function noOpRule(input) { + return results.success(null, input); +} + +var repeatedWithSeparator = function(rule, separator, isOneOrMore) { + return function(input) { + var result = rule(input); + if (result.isSuccess()) { + var mainRule = exports.sequence.capture(rule, "main"); + var remainingRule = zeroOrMore(exports.then( + exports.sequence(separator, mainRule), + exports.sequence.extract(mainRule) + )); + var remainingResult = remainingRule(result.remaining()); + return results.success([result.value()].concat(remainingResult.value()), remainingResult.remaining()); + } else if (isOneOrMore || result.isError()) { + return result; + } else { + return results.success([], input); + } + }; +}; + +exports.leftAssociative = function(leftRule, rightRule, func) { + var rights; + if (func) { + rights = [{func: func, rule: rightRule}]; + } else { + rights = rightRule; + } + rights = rights.map(function(right) { + return exports.then(right.rule, function(rightValue) { + return function(leftValue, source) { + return right.func(leftValue, rightValue, source); + }; + }); + }); + var repeatedRule = exports.firstOf.apply(null, ["rules"].concat(rights)); + + return function(input) { + var start = input; + var leftResult = leftRule(input); + if (!leftResult.isSuccess()) { + return leftResult; + } + var repeatedResult = repeatedRule(leftResult.remaining()); + while (repeatedResult.isSuccess()) { + var remaining = repeatedResult.remaining(); + var source = start.to(repeatedResult.remaining()); + var right = repeatedResult.value(); + leftResult = results.success( + right(leftResult.value(), source), + remaining, + source + ); + repeatedResult = repeatedRule(leftResult.remaining()); + } + if (repeatedResult.isError()) { + return repeatedResult; + } + return leftResult; + }; +}; + +exports.leftAssociative.firstOf = function() { + return Array.prototype.slice.call(arguments, 0); +}; + +exports.nonConsuming = function(rule) { + return function(input) { + return rule(input).changeRemaining(input); + }; +}; + +var describeToken = function(token) { + if (token.value) { + return token.name + " \"" + token.value + "\""; + } else { + return token.name; + } +}; + +function describeTokenMismatch(input, expected) { + var error; + var token = input.head(); + if (token) { + error = errors.error({ + expected: expected, + actual: describeToken(token), + location: token.source + }); + } else { + error = errors.error({ + expected: expected, + actual: "end of tokens" + }); + } + return results.failure([error], input); +} + +},{"./errors":95,"./lazy-iterators":96,"./parsing-results":98,"option":101,"underscore":104}],101:[function(require,module,exports){ +exports.none = Object.create({ + value: function() { + throw new Error('Called value on none'); + }, + isNone: function() { + return true; + }, + isSome: function() { + return false; + }, + map: function() { + return exports.none; + }, + flatMap: function() { + return exports.none; + }, + filter: function() { + return exports.none; + }, + toArray: function() { + return []; + }, + orElse: callOrReturn, + valueOrElse: callOrReturn +}); + +function callOrReturn(value) { + if (typeof(value) == "function") { + return value(); + } else { + return value; + } +} + +exports.some = function(value) { + return new Some(value); +}; + +var Some = function(value) { + this._value = value; +}; + +Some.prototype.value = function() { + return this._value; +}; + +Some.prototype.isNone = function() { + return false; +}; + +Some.prototype.isSome = function() { + return true; +}; + +Some.prototype.map = function(func) { + return new Some(func(this._value)); +}; + +Some.prototype.flatMap = function(func) { + return func(this._value); +}; + +Some.prototype.filter = function(predicate) { + return predicate(this._value) ? this : exports.none; +}; + +Some.prototype.toArray = function() { + return [this._value]; +}; + +Some.prototype.orElse = function(value) { + return this; +}; + +Some.prototype.valueOrElse = function(value) { + return this._value; +}; + +exports.isOption = function(value) { + return value === exports.none || value instanceof Some; +}; + +exports.fromNullable = function(value) { + if (value == null) { + return exports.none; + } + return new Some(value); +} + +},{}],102:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],103:[function(require,module,exports){ +(function (setImmediate,clearImmediate){(function (){ +var nextTick = require('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this)}).call(this,require("timers").setImmediate,require("timers").clearImmediate) +},{"process/browser.js":102,"timers":103}],104:[function(require,module,exports){ +(function (global){(function (){ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define('underscore', factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { + var current = global._; + var exports = global._ = factory(); + exports.noConflict = function () { global._ = current; return exports; }; + }())); +}(this, (function () { + // Underscore.js 1.13.7 + // https://underscorejs.org + // (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + + // Current version. + var VERSION = '1.13.7'; + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = (typeof self == 'object' && self.self === self && self) || + (typeof global == 'object' && global.global === global && global) || + Function('return this')() || + {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // Modern feature detection. + var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined', + supportsDataView = typeof DataView !== 'undefined'; + + // All **ECMAScript 5+** native function implementations that we hope to use + // are declared here. + var nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeCreate = Object.create, + nativeIsView = supportsArrayBuffer && ArrayBuffer.isView; + + // Create references to these builtin functions because we override them. + var _isNaN = isNaN, + _isFinite = isFinite; + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + // The largest integer that can be represented exactly. + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + + // Some functions take a variable number of arguments, or a few expected + // arguments at the beginning and then a variable number of values to operate + // on. This helper accumulates all remaining arguments past the function’s + // argument length (or an explicit `startIndex`), into an array that becomes + // the last argument. Similar to ES6’s "rest parameter". + function restArguments(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0), + rest = Array(length), + index = 0; + for (; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + case 2: return func.call(this, arguments[0], arguments[1], rest); + } + var args = Array(startIndex + 1); + for (index = 0; index < startIndex; index++) { + args[index] = arguments[index]; + } + args[startIndex] = rest; + return func.apply(this, args); + }; + } + + // Is a given variable an object? + function isObject(obj) { + var type = typeof obj; + return type === 'function' || (type === 'object' && !!obj); + } + + // Is a given value equal to null? + function isNull(obj) { + return obj === null; + } + + // Is a given variable undefined? + function isUndefined(obj) { + return obj === void 0; + } + + // Is a given value a boolean? + function isBoolean(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + } + + // Is a given value a DOM element? + function isElement(obj) { + return !!(obj && obj.nodeType === 1); + } + + // Internal function for creating a `toString`-based type tester. + function tagTester(name) { + var tag = '[object ' + name + ']'; + return function(obj) { + return toString.call(obj) === tag; + }; + } + + var isString = tagTester('String'); + + var isNumber = tagTester('Number'); + + var isDate = tagTester('Date'); + + var isRegExp = tagTester('RegExp'); + + var isError = tagTester('Error'); + + var isSymbol = tagTester('Symbol'); + + var isArrayBuffer = tagTester('ArrayBuffer'); + + var isFunction = tagTester('Function'); + + // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old + // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). + var nodelist = root.document && root.document.childNodes; + if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { + isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + var isFunction$1 = isFunction; + + var hasObjectTag = tagTester('Object'); + + // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`. + // In IE 11, the most common among them, this problem also applies to + // `Map`, `WeakMap` and `Set`. + // Also, there are cases where an application can override the native + // `DataView` object, in cases like that we can't use the constructor + // safely and should just rely on alternate `DataView` checks + var hasDataViewBug = ( + supportsDataView && (!/\[native code\]/.test(String(DataView)) || hasObjectTag(new DataView(new ArrayBuffer(8)))) + ), + isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map)); + + var isDataView = tagTester('DataView'); + + // In IE 10 - Edge 13, we need a different heuristic + // to determine whether an object is a `DataView`. + // Also, in cases where the native `DataView` is + // overridden we can't rely on the tag itself. + function alternateIsDataView(obj) { + return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer); + } + + var isDataView$1 = (hasDataViewBug ? alternateIsDataView : isDataView); + + // Is a given value an array? + // Delegates to ECMA5's native `Array.isArray`. + var isArray = nativeIsArray || tagTester('Array'); + + // Internal function to check whether `key` is an own property name of `obj`. + function has$1(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + } + + var isArguments = tagTester('Arguments'); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + (function() { + if (!isArguments(arguments)) { + isArguments = function(obj) { + return has$1(obj, 'callee'); + }; + } + }()); + + var isArguments$1 = isArguments; + + // Is a given object a finite number? + function isFinite$1(obj) { + return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj)); + } + + // Is the given value `NaN`? + function isNaN$1(obj) { + return isNumber(obj) && _isNaN(obj); + } + + // Predicate-generating function. Often useful outside of Underscore. + function constant(value) { + return function() { + return value; + }; + } + + // Common internal logic for `isArrayLike` and `isBufferLike`. + function createSizePropertyCheck(getSizeProperty) { + return function(collection) { + var sizeProperty = getSizeProperty(collection); + return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX; + } + } + + // Internal helper to generate a function to obtain property `key` from `obj`. + function shallowProperty(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + } + + // Internal helper to obtain the `byteLength` property of an object. + var getByteLength = shallowProperty('byteLength'); + + // Internal helper to determine whether we should spend extensive checks against + // `ArrayBuffer` et al. + var isBufferLike = createSizePropertyCheck(getByteLength); + + // Is a given value a typed array? + var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/; + function isTypedArray(obj) { + // `ArrayBuffer.isView` is the most future-proof, so use it when available. + // Otherwise, fall back on the above regular expression. + return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) : + isBufferLike(obj) && typedArrayPattern.test(toString.call(obj)); + } + + var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false); + + // Internal helper to obtain the `length` property of an object. + var getLength = shallowProperty('length'); + + // Internal helper to create a simple lookup structure. + // `collectNonEnumProps` used to depend on `_.contains`, but this led to + // circular imports. `emulatedSet` is a one-off solution that only works for + // arrays of strings. + function emulatedSet(keys) { + var hash = {}; + for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true; + return { + contains: function(key) { return hash[key] === true; }, + push: function(key) { + hash[key] = true; + return keys.push(key); + } + }; + } + + // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't + // be iterated by `for key in ...` and thus missed. Extends `keys` in place if + // needed. + function collectNonEnumProps(obj, keys) { + keys = emulatedSet(keys); + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = (isFunction$1(constructor) && constructor.prototype) || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys`. + function keys(obj) { + if (!isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (has$1(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + function isEmpty(obj) { + if (obj == null) return true; + // Skip the more expensive `toString`-based type checks if `obj` has no + // `.length`. + var length = getLength(obj); + if (typeof length == 'number' && ( + isArray(obj) || isString(obj) || isArguments$1(obj) + )) return length === 0; + return getLength(keys(obj)) === 0; + } + + // Returns whether an object has a given set of `key:value` pairs. + function isMatch(object, attrs) { + var _keys = keys(attrs), length = _keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = _keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + } + + // If Underscore is called as a function, it returns a wrapped object that can + // be used OO-style. This wrapper holds altered versions of all functions added + // through `_.mixin`. Wrapped objects may be chained. + function _$1(obj) { + if (obj instanceof _$1) return obj; + if (!(this instanceof _$1)) return new _$1(obj); + this._wrapped = obj; + } + + _$1.VERSION = VERSION; + + // Extracts the result from a wrapped and chained object. + _$1.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxies for some methods used in engine operations + // such as arithmetic and JSON stringification. + _$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value; + + _$1.prototype.toString = function() { + return String(this._wrapped); + }; + + // Internal function to wrap or shallow-copy an ArrayBuffer, + // typed array or DataView to a new view, reusing the buffer. + function toBufferView(bufferSource) { + return new Uint8Array( + bufferSource.buffer || bufferSource, + bufferSource.byteOffset || 0, + getByteLength(bufferSource) + ); + } + + // We use this string twice, so give it a name for minification. + var tagDataView = '[object DataView]'; + + // Internal recursive comparison function for `_.isEqual`. + function eq(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // `null` or `undefined` only equal to itself (strict comparison). + if (a == null || b == null) return false; + // `NaN`s are equivalent, but non-reflexive. + if (a !== a) return b !== b; + // Exhaust primitive checks + var type = typeof a; + if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; + return deepEq(a, b, aStack, bStack); + } + + // Internal recursive comparison function for `_.isEqual`. + function deepEq(a, b, aStack, bStack) { + // Unwrap any wrapped objects. + if (a instanceof _$1) a = a._wrapped; + if (b instanceof _$1) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + // Work around a bug in IE 10 - Edge 13. + if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) { + if (!isDataView$1(b)) return false; + className = tagDataView; + } + switch (className) { + // These types are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN. + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + case '[object Symbol]': + return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); + case '[object ArrayBuffer]': + case tagDataView: + // Coerce to typed array so we can fall through. + return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); + } + + var areArrays = className === '[object Array]'; + if (!areArrays && isTypedArray$1(a)) { + var byteLength = getByteLength(a); + if (byteLength !== getByteLength(b)) return false; + if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; + areArrays = true; + } + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor && + isFunction$1(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var _keys = keys(a), key; + length = _keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = _keys[length]; + if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + } + + // Perform a deep comparison to check if two objects are equal. + function isEqual(a, b) { + return eq(a, b); + } + + // Retrieve all the enumerable property names of an object. + function allKeys(obj) { + if (!isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Since the regular `Object.prototype.toString` type tests don't work for + // some types in IE 11, we use a fingerprinting heuristic instead, based + // on the methods. It's not great, but it's the best we got. + // The fingerprint method lists are defined below. + function ie11fingerprint(methods) { + var length = getLength(methods); + return function(obj) { + if (obj == null) return false; + // `Map`, `WeakMap` and `Set` have no enumerable keys. + var keys = allKeys(obj); + if (getLength(keys)) return false; + for (var i = 0; i < length; i++) { + if (!isFunction$1(obj[methods[i]])) return false; + } + // If we are testing against `WeakMap`, we need to ensure that + // `obj` doesn't have a `forEach` method in order to distinguish + // it from a regular `Map`. + return methods !== weakMapMethods || !isFunction$1(obj[forEachName]); + }; + } + + // In the interest of compact minification, we write + // each string in the fingerprints only once. + var forEachName = 'forEach', + hasName = 'has', + commonInit = ['clear', 'delete'], + mapTail = ['get', hasName, 'set']; + + // `Map`, `WeakMap` and `Set` each have slightly different + // combinations of the above sublists. + var mapMethods = commonInit.concat(forEachName, mapTail), + weakMapMethods = commonInit.concat(mapTail), + setMethods = ['add'].concat(commonInit, forEachName, hasName); + + var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map'); + + var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap'); + + var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set'); + + var isWeakSet = tagTester('WeakSet'); + + // Retrieve the values of an object's properties. + function values(obj) { + var _keys = keys(obj); + var length = _keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[_keys[i]]; + } + return values; + } + + // Convert an object into a list of `[key, value]` pairs. + // The opposite of `_.object` with one argument. + function pairs(obj) { + var _keys = keys(obj); + var length = _keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [_keys[i], obj[_keys[i]]]; + } + return pairs; + } + + // Invert the keys and values of an object. The values must be serializable. + function invert(obj) { + var result = {}; + var _keys = keys(obj); + for (var i = 0, length = _keys.length; i < length; i++) { + result[obj[_keys[i]]] = _keys[i]; + } + return result; + } + + // Return a sorted list of the function names available on the object. + function functions(obj) { + var names = []; + for (var key in obj) { + if (isFunction$1(obj[key])) names.push(key); + } + return names.sort(); + } + + // An internal function for creating assigner functions. + function createAssigner(keysFunc, defaults) { + return function(obj) { + var length = arguments.length; + if (defaults) obj = Object(obj); + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!defaults || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + } + + // Extend a given object with all the properties in passed-in object(s). + var extend = createAssigner(allKeys); + + // Assigns a given object with all the own properties in the passed-in + // object(s). + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + var extendOwn = createAssigner(keys); + + // Fill in a given object with default properties. + var defaults = createAssigner(allKeys, true); + + // Create a naked function reference for surrogate-prototype-swapping. + function ctor() { + return function(){}; + } + + // An internal function for creating a new object that inherits from another. + function baseCreate(prototype) { + if (!isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + var Ctor = ctor(); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + } + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + function create(prototype, props) { + var result = baseCreate(prototype); + if (props) extendOwn(result, props); + return result; + } + + // Create a (shallow-cloned) duplicate of an object. + function clone(obj) { + if (!isObject(obj)) return obj; + return isArray(obj) ? obj.slice() : extend({}, obj); + } + + // Invokes `interceptor` with the `obj` and then returns `obj`. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + function tap(obj, interceptor) { + interceptor(obj); + return obj; + } + + // Normalize a (deep) property `path` to array. + // Like `_.iteratee`, this function can be customized. + function toPath$1(path) { + return isArray(path) ? path : [path]; + } + _$1.toPath = toPath$1; + + // Internal wrapper for `_.toPath` to enable minification. + // Similar to `cb` for `_.iteratee`. + function toPath(path) { + return _$1.toPath(path); + } + + // Internal function to obtain a nested property in `obj` along `path`. + function deepGet(obj, path) { + var length = path.length; + for (var i = 0; i < length; i++) { + if (obj == null) return void 0; + obj = obj[path[i]]; + } + return length ? obj : void 0; + } + + // Get the value of the (deep) property on `path` from `object`. + // If any property in `path` does not exist or if the value is + // `undefined`, return `defaultValue` instead. + // The `path` is normalized through `_.toPath`. + function get(object, path, defaultValue) { + var value = deepGet(object, toPath(path)); + return isUndefined(value) ? defaultValue : value; + } + + // Shortcut function for checking if an object has a given property directly on + // itself (in other words, not on a prototype). Unlike the internal `has` + // function, this public version can also traverse nested properties. + function has(obj, path) { + path = toPath(path); + var length = path.length; + for (var i = 0; i < length; i++) { + var key = path[i]; + if (!has$1(obj, key)) return false; + obj = obj[key]; + } + return !!length; + } + + // Keep the identity function around for default iteratees. + function identity(value) { + return value; + } + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + function matcher(attrs) { + attrs = extendOwn({}, attrs); + return function(obj) { + return isMatch(obj, attrs); + }; + } + + // Creates a function that, when passed an object, will traverse that object’s + // properties down the given `path`, specified as an array of keys or indices. + function property(path) { + path = toPath(path); + return function(obj) { + return deepGet(obj, path); + }; + } + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + function optimizeCb(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + // The 2-argument case is omitted because we’re not using it. + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + } + + // An internal function to generate callbacks that can be applied to each + // element in a collection, returning the desired result — either `_.identity`, + // an arbitrary callback, a property matcher, or a property accessor. + function baseIteratee(value, context, argCount) { + if (value == null) return identity; + if (isFunction$1(value)) return optimizeCb(value, context, argCount); + if (isObject(value) && !isArray(value)) return matcher(value); + return property(value); + } + + // External wrapper for our callback generator. Users may customize + // `_.iteratee` if they want additional predicate/iteratee shorthand styles. + // This abstraction hides the internal-only `argCount` argument. + function iteratee(value, context) { + return baseIteratee(value, context, Infinity); + } + _$1.iteratee = iteratee; + + // The function we call internally to generate a callback. It invokes + // `_.iteratee` if overridden, otherwise `baseIteratee`. + function cb(value, context, argCount) { + if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context); + return baseIteratee(value, context, argCount); + } + + // Returns the results of applying the `iteratee` to each element of `obj`. + // In contrast to `_.map` it returns an object. + function mapObject(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = keys(obj), + length = _keys.length, + results = {}; + for (var index = 0; index < length; index++) { + var currentKey = _keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Predicate-generating function. Often useful outside of Underscore. + function noop(){} + + // Generates a function for a given object that returns a given property. + function propertyOf(obj) { + if (obj == null) return noop; + return function(path) { + return get(obj, path); + }; + } + + // Run a function **n** times. + function times(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + } + + // Return a random integer between `min` and `max` (inclusive). + function random(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + } + + // A (possibly faster) way to get the current timestamp as an integer. + var now = Date.now || function() { + return new Date().getTime(); + }; + + // Internal helper to generate functions for escaping and unescaping strings + // to/from HTML interpolation. + function createEscaper(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + } + + // Internal list of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Function for escaping strings to HTML interpolation. + var _escape = createEscaper(escapeMap); + + // Internal list of HTML entities for unescaping. + var unescapeMap = invert(escapeMap); + + // Function for unescaping strings from HTML interpolation. + var _unescape = createEscaper(unescapeMap); + + // By default, Underscore uses ERB-style template delimiters. Change the + // following template settings to use alternative delimiters. + var templateSettings = _$1.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `_.templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; + + function escapeChar(match) { + return '\\' + escapes[match]; + } + + // In order to prevent third-party code injection through + // `_.templateSettings.variable`, we test it against the following regular + // expression. It is intentionally a bit more liberal than just matching valid + // identifiers, but still prevents possible loopholes through defaults or + // destructuring assignment. + var bareIdentifier = /^\s*(\w|\$)+\s*$/; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + function template(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = defaults({}, settings, _$1.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escapeRegExp, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offset. + return match; + }); + source += "';\n"; + + var argument = settings.variable; + if (argument) { + // Insure against third-party code injection. (CVE-2021-23358) + if (!bareIdentifier.test(argument)) throw new Error( + 'variable is not a bare identifier: ' + argument + ); + } else { + // If a variable is not specified, place data values in local scope. + source = 'with(obj||{}){\n' + source + '}\n'; + argument = 'obj'; + } + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + var render; + try { + render = new Function(argument, '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _$1); + }; + + // Provide the compiled source as a convenience for precompilation. + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + } + + // Traverses the children of `obj` along `path`. If a child is a function, it + // is invoked with its parent as context. Returns the value of the final + // child, or `fallback` if any child is undefined. + function result(obj, path, fallback) { + path = toPath(path); + var length = path.length; + if (!length) { + return isFunction$1(fallback) ? fallback.call(obj) : fallback; + } + for (var i = 0; i < length; i++) { + var prop = obj == null ? void 0 : obj[path[i]]; + if (prop === void 0) { + prop = fallback; + i = length; // Ensure we don't continue iterating. + } + obj = isFunction$1(prop) ? prop.call(obj) : prop; + } + return obj; + } + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + function uniqueId(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + } + + // Start chaining a wrapped Underscore object. + function chain(obj) { + var instance = _$1(obj); + instance._chain = true; + return instance; + } + + // Internal function to execute `sourceFunc` bound to `context` with optional + // `args`. Determines whether to execute a function as a constructor or as a + // normal function. + function executeBound(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (isObject(result)) return result; + return self; + } + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. `_` acts + // as a placeholder by default, allowing any combination of arguments to be + // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. + var partial = restArguments(function(func, boundArgs) { + var placeholder = partial.placeholder; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }); + + partial.placeholder = _$1; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). + var bind = restArguments(function(func, context, args) { + if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function'); + var bound = restArguments(function(callArgs) { + return executeBound(func, bound, context, this, args.concat(callArgs)); + }); + return bound; + }); + + // Internal helper for collection methods to determine whether a collection + // should be iterated as an array or as an object. + // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var isArrayLike = createSizePropertyCheck(getLength); + + // Internal implementation of a recursive `flatten` function. + function flatten$1(input, depth, strict, output) { + output = output || []; + if (!depth && depth !== 0) { + depth = Infinity; + } else if (depth <= 0) { + return output.concat(input); + } + var idx = output.length; + for (var i = 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) { + // Flatten current level of array or arguments object. + if (depth > 1) { + flatten$1(value, depth - 1, strict, output); + idx = output.length; + } else { + var j = 0, len = value.length; + while (j < len) output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + } + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + var bindAll = restArguments(function(obj, keys) { + keys = flatten$1(keys, false, false); + var index = keys.length; + if (index < 1) throw new Error('bindAll must be passed function names'); + while (index--) { + var key = keys[index]; + obj[key] = bind(obj[key], obj); + } + return obj; + }); + + // Memoize an expensive function by storing its results. + function memoize(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!has$1(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + } + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + var delay = restArguments(function(func, wait, args) { + return setTimeout(function() { + return func.apply(null, args); + }, wait); + }); + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + var defer = partial(delay, _$1, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + function throttle(func, wait, options) { + var timeout, context, args, result; + var previous = 0; + if (!options) options = {}; + + var later = function() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function() { + var _now = now(); + if (!previous && options.leading === false) previous = _now; + var remaining = wait - (_now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = _now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function() { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; + } + + // When a sequence of calls of the returned function ends, the argument + // function is triggered. The end of a sequence is defined by the `wait` + // parameter. If `immediate` is passed, the argument function will be + // triggered at the beginning of the sequence instead of at the end. + function debounce(func, wait, immediate) { + var timeout, previous, args, result, context; + + var later = function() { + var passed = now() - previous; + if (wait > passed) { + timeout = setTimeout(later, wait - passed); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + // This check is needed because `func` can recursively invoke `debounced`. + if (!timeout) args = context = null; + } + }; + + var debounced = restArguments(function(_args) { + context = this; + args = _args; + previous = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + if (immediate) result = func.apply(context, args); + } + return result; + }); + + debounced.cancel = function() { + clearTimeout(timeout); + timeout = args = context = null; + }; + + return debounced; + } + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + function wrap(func, wrapper) { + return partial(wrapper, func); + } + + // Returns a negated version of the passed-in predicate. + function negate(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + } + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + function compose() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + } + + // Returns a function that will only be executed on and after the Nth call. + function after(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + } + + // Returns a function that will only be executed up to (but not including) the + // Nth call. + function before(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + } + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + var once = partial(before, 2); + + // Returns the first key on an object that passes a truth test. + function findKey(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = keys(obj), key; + for (var i = 0, length = _keys.length; i < length; i++) { + key = _keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + } + + // Internal function to generate `_.findIndex` and `_.findLastIndex`. + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a truth test. + var findIndex = createPredicateIndexFinder(1); + + // Returns the last index on an array-like that passes a truth test. + var findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + function sortedIndex(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + } + + // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions. + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), isNaN$1); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + var indexOf = createIndexFinder(1, findIndex, sortedIndex); + + // Return the position of the last occurrence of an item in an array, + // or -1 if the item is not included in the array. + var lastIndexOf = createIndexFinder(-1, findLastIndex); + + // Return the first value which passes a truth test. + function find(obj, predicate, context) { + var keyFinder = isArrayLike(obj) ? findIndex : findKey; + var key = keyFinder(obj, predicate, context); + if (key !== void 0 && key !== -1) return obj[key]; + } + + // Convenience version of a common use case of `_.find`: getting the first + // object containing specific `key:value` pairs. + function findWhere(obj, attrs) { + return find(obj, matcher(attrs)); + } + + // The cornerstone for collection functions, an `each` + // implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + function each(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var _keys = keys(obj); + for (i = 0, length = _keys.length; i < length; i++) { + iteratee(obj[_keys[i]], _keys[i], obj); + } + } + return obj; + } + + // Return the results of applying the iteratee to each element. + function map(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Internal helper to create a reducing function, iterating left or right. + function createReduce(dir) { + // Wrap code that reassigns argument variables in a separate function than + // the one that accesses `arguments.length` to avoid a perf hit. (#1991) + var reducer = function(obj, iteratee, memo, initial) { + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + index = dir > 0 ? 0 : length - 1; + if (!initial) { + memo = obj[_keys ? _keys[index] : index]; + index += dir; + } + for (; index >= 0 && index < length; index += dir) { + var currentKey = _keys ? _keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + }; + + return function(obj, iteratee, memo, context) { + var initial = arguments.length >= 3; + return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + var reduce = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + var reduceRight = createReduce(-1); + + // Return all the elements that pass a truth test. + function filter(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + } + + // Return all the elements for which a truth test fails. + function reject(obj, predicate, context) { + return filter(obj, negate(cb(predicate)), context); + } + + // Determine whether all of the elements pass a truth test. + function every(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + } + + // Determine if at least one element in the object passes a truth test. + function some(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + } + + // Determine if the array or object contains a given item (using `===`). + function contains(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return indexOf(obj, item, fromIndex) >= 0; + } + + // Invoke a method (with arguments) on every item in a collection. + var invoke = restArguments(function(obj, path, args) { + var contextPath, func; + if (isFunction$1(path)) { + func = path; + } else { + path = toPath(path); + contextPath = path.slice(0, -1); + path = path[path.length - 1]; + } + return map(obj, function(context) { + var method = func; + if (!method) { + if (contextPath && contextPath.length) { + context = deepGet(context, contextPath); + } + if (context == null) return void 0; + method = context[path]; + } + return method == null ? method : method.apply(context, args); + }); + }); + + // Convenience version of a common use case of `_.map`: fetching a property. + function pluck(obj, key) { + return map(obj, property(key)); + } + + // Convenience version of a common use case of `_.filter`: selecting only + // objects containing specific `key:value` pairs. + function where(obj, attrs) { + return filter(obj, matcher(attrs)); + } + + // Return the maximum element (or element-based computation). + function max(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed > lastComputed || (computed === -Infinity && result === -Infinity)) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Return the minimum element (or element-based computation). + function min(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null)) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed < lastComputed || (computed === Infinity && result === Infinity)) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Safely create a real, live array from anything iterable. + var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + function toArray(obj) { + if (!obj) return []; + if (isArray(obj)) return slice.call(obj); + if (isString(obj)) { + // Keep surrogate pair characters together. + return obj.match(reStrSymbol); + } + if (isArrayLike(obj)) return map(obj, identity); + return values(obj); + } + + // Sample **n** random values from a collection using the modern version of the + // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `_.map`. + function sample(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = values(obj); + return obj[random(obj.length - 1)]; + } + var sample = toArray(obj); + var length = getLength(sample); + n = Math.max(Math.min(n, length), 0); + var last = length - 1; + for (var index = 0; index < n; index++) { + var rand = random(index, last); + var temp = sample[index]; + sample[index] = sample[rand]; + sample[rand] = temp; + } + return sample.slice(0, n); + } + + // Shuffle a collection. + function shuffle(obj) { + return sample(obj, Infinity); + } + + // Sort the object's values by a criterion produced by an iteratee. + function sortBy(obj, iteratee, context) { + var index = 0; + iteratee = cb(iteratee, context); + return pluck(map(obj, function(value, key, list) { + return { + value: value, + index: index++, + criteria: iteratee(value, key, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + } + + // An internal function used for aggregate "group by" operations. + function group(behavior, partition) { + return function(obj, iteratee, context) { + var result = partition ? [[], []] : {}; + iteratee = cb(iteratee, context); + each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + } + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + var groupBy = group(function(result, value, key) { + if (has$1(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `_.groupBy`, but for + // when you know that your index values will be unique. + var indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + var countBy = group(function(result, value, key) { + if (has$1(result, key)) result[key]++; else result[key] = 1; + }); + + // Split a collection into two arrays: one whose elements all pass the given + // truth test, and one whose elements all do not pass the truth test. + var partition = group(function(result, value, pass) { + result[pass ? 0 : 1].push(value); + }, true); + + // Return the number of elements in a collection. + function size(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : keys(obj).length; + } + + // Internal `_.pick` helper function to determine whether `key` is an enumerable + // property name of `obj`. + function keyInObj(value, key, obj) { + return key in obj; + } + + // Return a copy of the object only containing the allowed properties. + var pick = restArguments(function(obj, keys) { + var result = {}, iteratee = keys[0]; + if (obj == null) return result; + if (isFunction$1(iteratee)) { + if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); + keys = allKeys(obj); + } else { + iteratee = keyInObj; + keys = flatten$1(keys, false, false); + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }); + + // Return a copy of the object without the disallowed properties. + var omit = restArguments(function(obj, keys) { + var iteratee = keys[0], context; + if (isFunction$1(iteratee)) { + iteratee = negate(iteratee); + if (keys.length > 1) context = keys[1]; + } else { + keys = map(flatten$1(keys, false, false), String); + iteratee = function(value, key) { + return !contains(keys, key); + }; + } + return pick(obj, iteratee, context); + }); + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + function initial(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + } + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. The **guard** check allows it to work with `_.map`. + function first(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[0]; + return initial(array, array.length - n); + } + + // Returns everything but the first entry of the `array`. Especially useful on + // the `arguments` object. Passing an **n** will return the rest N values in the + // `array`. + function rest(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + } + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + function last(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[array.length - 1]; + return rest(array, Math.max(0, array.length - n)); + } + + // Trim out all falsy values from an array. + function compact(array) { + return filter(array, Boolean); + } + + // Flatten out an array, either recursively (by default), or up to `depth`. + // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively. + function flatten(array, depth) { + return flatten$1(array, depth, false); + } + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + var difference = restArguments(function(array, rest) { + rest = flatten$1(rest, true, true); + return filter(array, function(value){ + return !contains(rest, value); + }); + }); + + // Return a version of the array that does not contain the specified value(s). + var without = restArguments(function(array, otherArrays) { + return difference(array, otherArrays); + }); + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // The faster algorithm will not work with an iteratee if the iteratee + // is not a one-to-one function, so providing an iteratee will disable + // the faster algorithm. + function uniq(array, isSorted, iteratee, context) { + if (!isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted && !iteratee) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!contains(result, value)) { + result.push(value); + } + } + return result; + } + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + var union = restArguments(function(arrays) { + return uniq(flatten$1(arrays, true, true)); + }); + + // Produce an array that contains every item shared between all the + // passed-in arrays. + function intersection(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (contains(result, item)) continue; + var j; + for (j = 1; j < argsLength; j++) { + if (!contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + } + + // Complement of zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices. + function unzip(array) { + var length = (array && max(array, getLength).length) || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = pluck(array, index); + } + return result; + } + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + var zip = restArguments(unzip); + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. Passing by pairs is the reverse of `_.pairs`. + function object(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + } + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](https://docs.python.org/library/functions.html#range). + function range(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + if (!step) { + step = stop < start ? -1 : 1; + } + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + } + + // Chunk a single array into multiple arrays, each containing `count` or fewer + // items. + function chunk(array, count) { + if (count == null || count < 1) return []; + var result = []; + var i = 0, length = array.length; + while (i < length) { + result.push(slice.call(array, i, i += count)); + } + return result; + } + + // Helper function to continue chaining intermediate results. + function chainResult(instance, obj) { + return instance._chain ? _$1(obj).chain() : obj; + } + + // Add your own custom functions to the Underscore object. + function mixin(obj) { + each(functions(obj), function(name) { + var func = _$1[name] = obj[name]; + _$1.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_$1, args)); + }; + }); + return _$1; + } + + // Add all mutator `Array` functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) { + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) { + delete obj[0]; + } + } + return chainResult(this, obj); + }; + }); + + // Add all accessor `Array` functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) obj = method.apply(obj, arguments); + return chainResult(this, obj); + }; + }); + + // Named Exports + + var allExports = { + __proto__: null, + VERSION: VERSION, + restArguments: restArguments, + isObject: isObject, + isNull: isNull, + isUndefined: isUndefined, + isBoolean: isBoolean, + isElement: isElement, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isRegExp: isRegExp, + isError: isError, + isSymbol: isSymbol, + isArrayBuffer: isArrayBuffer, + isDataView: isDataView$1, + isArray: isArray, + isFunction: isFunction$1, + isArguments: isArguments$1, + isFinite: isFinite$1, + isNaN: isNaN$1, + isTypedArray: isTypedArray$1, + isEmpty: isEmpty, + isMatch: isMatch, + isEqual: isEqual, + isMap: isMap, + isWeakMap: isWeakMap, + isSet: isSet, + isWeakSet: isWeakSet, + keys: keys, + allKeys: allKeys, + values: values, + pairs: pairs, + invert: invert, + functions: functions, + methods: functions, + extend: extend, + extendOwn: extendOwn, + assign: extendOwn, + defaults: defaults, + create: create, + clone: clone, + tap: tap, + get: get, + has: has, + mapObject: mapObject, + identity: identity, + constant: constant, + noop: noop, + toPath: toPath$1, + property: property, + propertyOf: propertyOf, + matcher: matcher, + matches: matcher, + times: times, + random: random, + now: now, + escape: _escape, + unescape: _unescape, + templateSettings: templateSettings, + template: template, + result: result, + uniqueId: uniqueId, + chain: chain, + iteratee: iteratee, + partial: partial, + bind: bind, + bindAll: bindAll, + memoize: memoize, + delay: delay, + defer: defer, + throttle: throttle, + debounce: debounce, + wrap: wrap, + negate: negate, + compose: compose, + after: after, + before: before, + once: once, + findKey: findKey, + findIndex: findIndex, + findLastIndex: findLastIndex, + sortedIndex: sortedIndex, + indexOf: indexOf, + lastIndexOf: lastIndexOf, + find: find, + detect: find, + findWhere: findWhere, + each: each, + forEach: each, + map: map, + collect: map, + reduce: reduce, + foldl: reduce, + inject: reduce, + reduceRight: reduceRight, + foldr: reduceRight, + filter: filter, + select: filter, + reject: reject, + every: every, + all: every, + some: some, + any: some, + contains: contains, + includes: contains, + include: contains, + invoke: invoke, + pluck: pluck, + where: where, + max: max, + min: min, + shuffle: shuffle, + sample: sample, + sortBy: sortBy, + groupBy: groupBy, + indexBy: indexBy, + countBy: countBy, + partition: partition, + toArray: toArray, + size: size, + pick: pick, + omit: omit, + first: first, + head: first, + take: first, + initial: initial, + last: last, + rest: rest, + tail: rest, + drop: rest, + compact: compact, + flatten: flatten, + without: without, + uniq: uniq, + unique: uniq, + union: union, + intersection: intersection, + difference: difference, + unzip: unzip, + transpose: unzip, + zip: zip, + object: object, + range: range, + chunk: chunk, + mixin: mixin, + 'default': _$1 + }; + + // Default Export + + // Add all of the Underscore functions to the wrapper object. + var _ = mixin(allExports); + // Legacy Node.js API. + _._ = _; + + return _; + +}))); + + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],105:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var assign, getValue, isArray, isEmpty, isFunction, isObject, isPlainObject, + slice = [].slice, + hasProp = {}.hasOwnProperty; + + assign = function() { + var i, key, len, source, sources, target; + target = arguments[0], sources = 2 <= arguments.length ? slice.call(arguments, 1) : []; + if (isFunction(Object.assign)) { + Object.assign.apply(null, arguments); + } else { + for (i = 0, len = sources.length; i < len; i++) { + source = sources[i]; + if (source != null) { + for (key in source) { + if (!hasProp.call(source, key)) continue; + target[key] = source[key]; + } + } + } + } + return target; + }; + + isFunction = function(val) { + return !!val && Object.prototype.toString.call(val) === '[object Function]'; + }; + + isObject = function(val) { + var ref; + return !!val && ((ref = typeof val) === 'function' || ref === 'object'); + }; + + isArray = function(val) { + if (isFunction(Array.isArray)) { + return Array.isArray(val); + } else { + return Object.prototype.toString.call(val) === '[object Array]'; + } + }; + + isEmpty = function(val) { + var key; + if (isArray(val)) { + return !val.length; + } else { + for (key in val) { + if (!hasProp.call(val, key)) continue; + return false; + } + return true; + } + }; + + isPlainObject = function(val) { + var ctor, proto; + return isObject(val) && (proto = Object.getPrototypeOf(val)) && (ctor = proto.constructor) && (typeof ctor === 'function') && (ctor instanceof ctor) && (Function.prototype.toString.call(ctor) === Function.prototype.toString.call(Object)); + }; + + getValue = function(obj) { + if (isFunction(obj.valueOf)) { + return obj.valueOf(); + } else { + return obj; + } + }; + + module.exports.assign = assign; + + module.exports.isFunction = isFunction; + + module.exports.isObject = isObject; + + module.exports.isArray = isArray; + + module.exports.isEmpty = isEmpty; + + module.exports.isPlainObject = isPlainObject; + + module.exports.getValue = getValue; + +}).call(this); + +},{}],106:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLAttribute; + + module.exports = XMLAttribute = (function() { + function XMLAttribute(parent, name, value) { + this.options = parent.options; + this.stringify = parent.stringify; + this.parent = parent; + if (name == null) { + throw new Error("Missing attribute name. " + this.debugInfo(name)); + } + if (value == null) { + throw new Error("Missing attribute value. " + this.debugInfo(name)); + } + this.name = this.stringify.attName(name); + this.value = this.stringify.attValue(value); + } + + XMLAttribute.prototype.clone = function() { + return Object.create(this); + }; + + XMLAttribute.prototype.toString = function(options) { + return this.options.writer.set(options).attribute(this); + }; + + XMLAttribute.prototype.debugInfo = function(name) { + name = name || this.name; + if (name == null) { + return "parent: <" + this.parent.name + ">"; + } else { + return "attribute: {" + name + "}, parent: <" + this.parent.name + ">"; + } + }; + + return XMLAttribute; + + })(); + +}).call(this); + +},{}],107:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLCData, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLCData = (function(superClass) { + extend(XMLCData, superClass); + + function XMLCData(parent, text) { + XMLCData.__super__.constructor.call(this, parent); + if (text == null) { + throw new Error("Missing CDATA text. " + this.debugInfo()); + } + this.text = this.stringify.cdata(text); + } + + XMLCData.prototype.clone = function() { + return Object.create(this); + }; + + XMLCData.prototype.toString = function(options) { + return this.options.writer.set(options).cdata(this); + }; + + return XMLCData; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],108:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLComment, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLComment = (function(superClass) { + extend(XMLComment, superClass); + + function XMLComment(parent, text) { + XMLComment.__super__.constructor.call(this, parent); + if (text == null) { + throw new Error("Missing comment text. " + this.debugInfo()); + } + this.text = this.stringify.comment(text); + } + + XMLComment.prototype.clone = function() { + return Object.create(this); + }; + + XMLComment.prototype.toString = function(options) { + return this.options.writer.set(options).comment(this); + }; + + return XMLComment; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],109:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDTDAttList, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDTDAttList = (function(superClass) { + extend(XMLDTDAttList, superClass); + + function XMLDTDAttList(parent, elementName, attributeName, attributeType, defaultValueType, defaultValue) { + XMLDTDAttList.__super__.constructor.call(this, parent); + if (elementName == null) { + throw new Error("Missing DTD element name. " + this.debugInfo()); + } + if (attributeName == null) { + throw new Error("Missing DTD attribute name. " + this.debugInfo(elementName)); + } + if (!attributeType) { + throw new Error("Missing DTD attribute type. " + this.debugInfo(elementName)); + } + if (!defaultValueType) { + throw new Error("Missing DTD attribute default. " + this.debugInfo(elementName)); + } + if (defaultValueType.indexOf('#') !== 0) { + defaultValueType = '#' + defaultValueType; + } + if (!defaultValueType.match(/^(#REQUIRED|#IMPLIED|#FIXED|#DEFAULT)$/)) { + throw new Error("Invalid default value type; expected: #REQUIRED, #IMPLIED, #FIXED or #DEFAULT. " + this.debugInfo(elementName)); + } + if (defaultValue && !defaultValueType.match(/^(#FIXED|#DEFAULT)$/)) { + throw new Error("Default value only applies to #FIXED or #DEFAULT. " + this.debugInfo(elementName)); + } + this.elementName = this.stringify.eleName(elementName); + this.attributeName = this.stringify.attName(attributeName); + this.attributeType = this.stringify.dtdAttType(attributeType); + this.defaultValue = this.stringify.dtdAttDefault(defaultValue); + this.defaultValueType = defaultValueType; + } + + XMLDTDAttList.prototype.toString = function(options) { + return this.options.writer.set(options).dtdAttList(this); + }; + + return XMLDTDAttList; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],110:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDTDElement, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDTDElement = (function(superClass) { + extend(XMLDTDElement, superClass); + + function XMLDTDElement(parent, name, value) { + XMLDTDElement.__super__.constructor.call(this, parent); + if (name == null) { + throw new Error("Missing DTD element name. " + this.debugInfo()); + } + if (!value) { + value = '(#PCDATA)'; + } + if (Array.isArray(value)) { + value = '(' + value.join(',') + ')'; + } + this.name = this.stringify.eleName(name); + this.value = this.stringify.dtdElementValue(value); + } + + XMLDTDElement.prototype.toString = function(options) { + return this.options.writer.set(options).dtdElement(this); + }; + + return XMLDTDElement; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],111:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDTDEntity, XMLNode, isObject, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + isObject = require('./Utility').isObject; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDTDEntity = (function(superClass) { + extend(XMLDTDEntity, superClass); + + function XMLDTDEntity(parent, pe, name, value) { + XMLDTDEntity.__super__.constructor.call(this, parent); + if (name == null) { + throw new Error("Missing DTD entity name. " + this.debugInfo(name)); + } + if (value == null) { + throw new Error("Missing DTD entity value. " + this.debugInfo(name)); + } + this.pe = !!pe; + this.name = this.stringify.eleName(name); + if (!isObject(value)) { + this.value = this.stringify.dtdEntityValue(value); + } else { + if (!value.pubID && !value.sysID) { + throw new Error("Public and/or system identifiers are required for an external entity. " + this.debugInfo(name)); + } + if (value.pubID && !value.sysID) { + throw new Error("System identifier is required for a public external entity. " + this.debugInfo(name)); + } + if (value.pubID != null) { + this.pubID = this.stringify.dtdPubID(value.pubID); + } + if (value.sysID != null) { + this.sysID = this.stringify.dtdSysID(value.sysID); + } + if (value.nData != null) { + this.nData = this.stringify.dtdNData(value.nData); + } + if (this.pe && this.nData) { + throw new Error("Notation declaration is not allowed in a parameter entity. " + this.debugInfo(name)); + } + } + } + + XMLDTDEntity.prototype.toString = function(options) { + return this.options.writer.set(options).dtdEntity(this); + }; + + return XMLDTDEntity; + + })(XMLNode); + +}).call(this); + +},{"./Utility":105,"./XMLNode":119}],112:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDTDNotation, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDTDNotation = (function(superClass) { + extend(XMLDTDNotation, superClass); + + function XMLDTDNotation(parent, name, value) { + XMLDTDNotation.__super__.constructor.call(this, parent); + if (name == null) { + throw new Error("Missing DTD notation name. " + this.debugInfo(name)); + } + if (!value.pubID && !value.sysID) { + throw new Error("Public or system identifiers are required for an external entity. " + this.debugInfo(name)); + } + this.name = this.stringify.eleName(name); + if (value.pubID != null) { + this.pubID = this.stringify.dtdPubID(value.pubID); + } + if (value.sysID != null) { + this.sysID = this.stringify.dtdSysID(value.sysID); + } + } + + XMLDTDNotation.prototype.toString = function(options) { + return this.options.writer.set(options).dtdNotation(this); + }; + + return XMLDTDNotation; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],113:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDeclaration, XMLNode, isObject, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + isObject = require('./Utility').isObject; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDeclaration = (function(superClass) { + extend(XMLDeclaration, superClass); + + function XMLDeclaration(parent, version, encoding, standalone) { + var ref; + XMLDeclaration.__super__.constructor.call(this, parent); + if (isObject(version)) { + ref = version, version = ref.version, encoding = ref.encoding, standalone = ref.standalone; + } + if (!version) { + version = '1.0'; + } + this.version = this.stringify.xmlVersion(version); + if (encoding != null) { + this.encoding = this.stringify.xmlEncoding(encoding); + } + if (standalone != null) { + this.standalone = this.stringify.xmlStandalone(standalone); + } + } + + XMLDeclaration.prototype.toString = function(options) { + return this.options.writer.set(options).declaration(this); + }; + + return XMLDeclaration; + + })(XMLNode); + +}).call(this); + +},{"./Utility":105,"./XMLNode":119}],114:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDTDAttList, XMLDTDElement, XMLDTDEntity, XMLDTDNotation, XMLDocType, XMLNode, isObject, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + isObject = require('./Utility').isObject; + + XMLNode = require('./XMLNode'); + + XMLDTDAttList = require('./XMLDTDAttList'); + + XMLDTDEntity = require('./XMLDTDEntity'); + + XMLDTDElement = require('./XMLDTDElement'); + + XMLDTDNotation = require('./XMLDTDNotation'); + + module.exports = XMLDocType = (function(superClass) { + extend(XMLDocType, superClass); + + function XMLDocType(parent, pubID, sysID) { + var ref, ref1; + XMLDocType.__super__.constructor.call(this, parent); + this.name = "!DOCTYPE"; + this.documentObject = parent; + if (isObject(pubID)) { + ref = pubID, pubID = ref.pubID, sysID = ref.sysID; + } + if (sysID == null) { + ref1 = [pubID, sysID], sysID = ref1[0], pubID = ref1[1]; + } + if (pubID != null) { + this.pubID = this.stringify.dtdPubID(pubID); + } + if (sysID != null) { + this.sysID = this.stringify.dtdSysID(sysID); + } + } + + XMLDocType.prototype.element = function(name, value) { + var child; + child = new XMLDTDElement(this, name, value); + this.children.push(child); + return this; + }; + + XMLDocType.prototype.attList = function(elementName, attributeName, attributeType, defaultValueType, defaultValue) { + var child; + child = new XMLDTDAttList(this, elementName, attributeName, attributeType, defaultValueType, defaultValue); + this.children.push(child); + return this; + }; + + XMLDocType.prototype.entity = function(name, value) { + var child; + child = new XMLDTDEntity(this, false, name, value); + this.children.push(child); + return this; + }; + + XMLDocType.prototype.pEntity = function(name, value) { + var child; + child = new XMLDTDEntity(this, true, name, value); + this.children.push(child); + return this; + }; + + XMLDocType.prototype.notation = function(name, value) { + var child; + child = new XMLDTDNotation(this, name, value); + this.children.push(child); + return this; + }; + + XMLDocType.prototype.toString = function(options) { + return this.options.writer.set(options).docType(this); + }; + + XMLDocType.prototype.ele = function(name, value) { + return this.element(name, value); + }; + + XMLDocType.prototype.att = function(elementName, attributeName, attributeType, defaultValueType, defaultValue) { + return this.attList(elementName, attributeName, attributeType, defaultValueType, defaultValue); + }; + + XMLDocType.prototype.ent = function(name, value) { + return this.entity(name, value); + }; + + XMLDocType.prototype.pent = function(name, value) { + return this.pEntity(name, value); + }; + + XMLDocType.prototype.not = function(name, value) { + return this.notation(name, value); + }; + + XMLDocType.prototype.up = function() { + return this.root() || this.documentObject; + }; + + return XMLDocType; + + })(XMLNode); + +}).call(this); + +},{"./Utility":105,"./XMLDTDAttList":109,"./XMLDTDElement":110,"./XMLDTDEntity":111,"./XMLDTDNotation":112,"./XMLNode":119}],115:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDocument, XMLNode, XMLStringWriter, XMLStringifier, isPlainObject, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + isPlainObject = require('./Utility').isPlainObject; + + XMLNode = require('./XMLNode'); + + XMLStringifier = require('./XMLStringifier'); + + XMLStringWriter = require('./XMLStringWriter'); + + module.exports = XMLDocument = (function(superClass) { + extend(XMLDocument, superClass); + + function XMLDocument(options) { + XMLDocument.__super__.constructor.call(this, null); + this.name = "?xml"; + options || (options = {}); + if (!options.writer) { + options.writer = new XMLStringWriter(); + } + this.options = options; + this.stringify = new XMLStringifier(options); + this.isDocument = true; + } + + XMLDocument.prototype.end = function(writer) { + var writerOptions; + if (!writer) { + writer = this.options.writer; + } else if (isPlainObject(writer)) { + writerOptions = writer; + writer = this.options.writer.set(writerOptions); + } + return writer.document(this); + }; + + XMLDocument.prototype.toString = function(options) { + return this.options.writer.set(options).document(this); + }; + + return XMLDocument; + + })(XMLNode); + +}).call(this); + +},{"./Utility":105,"./XMLNode":119,"./XMLStringWriter":123,"./XMLStringifier":124}],116:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLAttribute, XMLCData, XMLComment, XMLDTDAttList, XMLDTDElement, XMLDTDEntity, XMLDTDNotation, XMLDeclaration, XMLDocType, XMLDocumentCB, XMLElement, XMLProcessingInstruction, XMLRaw, XMLStringWriter, XMLStringifier, XMLText, getValue, isFunction, isObject, isPlainObject, ref, + hasProp = {}.hasOwnProperty; + + ref = require('./Utility'), isObject = ref.isObject, isFunction = ref.isFunction, isPlainObject = ref.isPlainObject, getValue = ref.getValue; + + XMLElement = require('./XMLElement'); + + XMLCData = require('./XMLCData'); + + XMLComment = require('./XMLComment'); + + XMLRaw = require('./XMLRaw'); + + XMLText = require('./XMLText'); + + XMLProcessingInstruction = require('./XMLProcessingInstruction'); + + XMLDeclaration = require('./XMLDeclaration'); + + XMLDocType = require('./XMLDocType'); + + XMLDTDAttList = require('./XMLDTDAttList'); + + XMLDTDEntity = require('./XMLDTDEntity'); + + XMLDTDElement = require('./XMLDTDElement'); + + XMLDTDNotation = require('./XMLDTDNotation'); + + XMLAttribute = require('./XMLAttribute'); + + XMLStringifier = require('./XMLStringifier'); + + XMLStringWriter = require('./XMLStringWriter'); + + module.exports = XMLDocumentCB = (function() { + function XMLDocumentCB(options, onData, onEnd) { + var writerOptions; + this.name = "?xml"; + options || (options = {}); + if (!options.writer) { + options.writer = new XMLStringWriter(options); + } else if (isPlainObject(options.writer)) { + writerOptions = options.writer; + options.writer = new XMLStringWriter(writerOptions); + } + this.options = options; + this.writer = options.writer; + this.stringify = new XMLStringifier(options); + this.onDataCallback = onData || function() {}; + this.onEndCallback = onEnd || function() {}; + this.currentNode = null; + this.currentLevel = -1; + this.openTags = {}; + this.documentStarted = false; + this.documentCompleted = false; + this.root = null; + } + + XMLDocumentCB.prototype.node = function(name, attributes, text) { + var ref1, ref2; + if (name == null) { + throw new Error("Missing node name."); + } + if (this.root && this.currentLevel === -1) { + throw new Error("Document can only have one root node. " + this.debugInfo(name)); + } + this.openCurrent(); + name = getValue(name); + if (attributes === null && (text == null)) { + ref1 = [{}, null], attributes = ref1[0], text = ref1[1]; + } + if (attributes == null) { + attributes = {}; + } + attributes = getValue(attributes); + if (!isObject(attributes)) { + ref2 = [attributes, text], text = ref2[0], attributes = ref2[1]; + } + this.currentNode = new XMLElement(this, name, attributes); + this.currentNode.children = false; + this.currentLevel++; + this.openTags[this.currentLevel] = this.currentNode; + if (text != null) { + this.text(text); + } + return this; + }; + + XMLDocumentCB.prototype.element = function(name, attributes, text) { + if (this.currentNode && this.currentNode instanceof XMLDocType) { + return this.dtdElement.apply(this, arguments); + } else { + return this.node(name, attributes, text); + } + }; + + XMLDocumentCB.prototype.attribute = function(name, value) { + var attName, attValue; + if (!this.currentNode || this.currentNode.children) { + throw new Error("att() can only be used immediately after an ele() call in callback mode. " + this.debugInfo(name)); + } + if (name != null) { + name = getValue(name); + } + if (isObject(name)) { + for (attName in name) { + if (!hasProp.call(name, attName)) continue; + attValue = name[attName]; + this.attribute(attName, attValue); + } + } else { + if (isFunction(value)) { + value = value.apply(); + } + if (!this.options.skipNullAttributes || (value != null)) { + this.currentNode.attributes[name] = new XMLAttribute(this, name, value); + } + } + return this; + }; + + XMLDocumentCB.prototype.text = function(value) { + var node; + this.openCurrent(); + node = new XMLText(this, value); + this.onData(this.writer.text(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.cdata = function(value) { + var node; + this.openCurrent(); + node = new XMLCData(this, value); + this.onData(this.writer.cdata(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.comment = function(value) { + var node; + this.openCurrent(); + node = new XMLComment(this, value); + this.onData(this.writer.comment(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.raw = function(value) { + var node; + this.openCurrent(); + node = new XMLRaw(this, value); + this.onData(this.writer.raw(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.instruction = function(target, value) { + var i, insTarget, insValue, len, node; + this.openCurrent(); + if (target != null) { + target = getValue(target); + } + if (value != null) { + value = getValue(value); + } + if (Array.isArray(target)) { + for (i = 0, len = target.length; i < len; i++) { + insTarget = target[i]; + this.instruction(insTarget); + } + } else if (isObject(target)) { + for (insTarget in target) { + if (!hasProp.call(target, insTarget)) continue; + insValue = target[insTarget]; + this.instruction(insTarget, insValue); + } + } else { + if (isFunction(value)) { + value = value.apply(); + } + node = new XMLProcessingInstruction(this, target, value); + this.onData(this.writer.processingInstruction(node, this.currentLevel + 1), this.currentLevel + 1); + } + return this; + }; + + XMLDocumentCB.prototype.declaration = function(version, encoding, standalone) { + var node; + this.openCurrent(); + if (this.documentStarted) { + throw new Error("declaration() must be the first node."); + } + node = new XMLDeclaration(this, version, encoding, standalone); + this.onData(this.writer.declaration(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.doctype = function(root, pubID, sysID) { + this.openCurrent(); + if (root == null) { + throw new Error("Missing root node name."); + } + if (this.root) { + throw new Error("dtd() must come before the root node."); + } + this.currentNode = new XMLDocType(this, pubID, sysID); + this.currentNode.rootNodeName = root; + this.currentNode.children = false; + this.currentLevel++; + this.openTags[this.currentLevel] = this.currentNode; + return this; + }; + + XMLDocumentCB.prototype.dtdElement = function(name, value) { + var node; + this.openCurrent(); + node = new XMLDTDElement(this, name, value); + this.onData(this.writer.dtdElement(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.attList = function(elementName, attributeName, attributeType, defaultValueType, defaultValue) { + var node; + this.openCurrent(); + node = new XMLDTDAttList(this, elementName, attributeName, attributeType, defaultValueType, defaultValue); + this.onData(this.writer.dtdAttList(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.entity = function(name, value) { + var node; + this.openCurrent(); + node = new XMLDTDEntity(this, false, name, value); + this.onData(this.writer.dtdEntity(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.pEntity = function(name, value) { + var node; + this.openCurrent(); + node = new XMLDTDEntity(this, true, name, value); + this.onData(this.writer.dtdEntity(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.notation = function(name, value) { + var node; + this.openCurrent(); + node = new XMLDTDNotation(this, name, value); + this.onData(this.writer.dtdNotation(node, this.currentLevel + 1), this.currentLevel + 1); + return this; + }; + + XMLDocumentCB.prototype.up = function() { + if (this.currentLevel < 0) { + throw new Error("The document node has no parent."); + } + if (this.currentNode) { + if (this.currentNode.children) { + this.closeNode(this.currentNode); + } else { + this.openNode(this.currentNode); + } + this.currentNode = null; + } else { + this.closeNode(this.openTags[this.currentLevel]); + } + delete this.openTags[this.currentLevel]; + this.currentLevel--; + return this; + }; + + XMLDocumentCB.prototype.end = function() { + while (this.currentLevel >= 0) { + this.up(); + } + return this.onEnd(); + }; + + XMLDocumentCB.prototype.openCurrent = function() { + if (this.currentNode) { + this.currentNode.children = true; + return this.openNode(this.currentNode); + } + }; + + XMLDocumentCB.prototype.openNode = function(node) { + if (!node.isOpen) { + if (!this.root && this.currentLevel === 0 && node instanceof XMLElement) { + this.root = node; + } + this.onData(this.writer.openNode(node, this.currentLevel), this.currentLevel); + return node.isOpen = true; + } + }; + + XMLDocumentCB.prototype.closeNode = function(node) { + if (!node.isClosed) { + this.onData(this.writer.closeNode(node, this.currentLevel), this.currentLevel); + return node.isClosed = true; + } + }; + + XMLDocumentCB.prototype.onData = function(chunk, level) { + this.documentStarted = true; + return this.onDataCallback(chunk, level + 1); + }; + + XMLDocumentCB.prototype.onEnd = function() { + this.documentCompleted = true; + return this.onEndCallback(); + }; + + XMLDocumentCB.prototype.debugInfo = function(name) { + if (name == null) { + return ""; + } else { + return "node: <" + name + ">"; + } + }; + + XMLDocumentCB.prototype.ele = function() { + return this.element.apply(this, arguments); + }; + + XMLDocumentCB.prototype.nod = function(name, attributes, text) { + return this.node(name, attributes, text); + }; + + XMLDocumentCB.prototype.txt = function(value) { + return this.text(value); + }; + + XMLDocumentCB.prototype.dat = function(value) { + return this.cdata(value); + }; + + XMLDocumentCB.prototype.com = function(value) { + return this.comment(value); + }; + + XMLDocumentCB.prototype.ins = function(target, value) { + return this.instruction(target, value); + }; + + XMLDocumentCB.prototype.dec = function(version, encoding, standalone) { + return this.declaration(version, encoding, standalone); + }; + + XMLDocumentCB.prototype.dtd = function(root, pubID, sysID) { + return this.doctype(root, pubID, sysID); + }; + + XMLDocumentCB.prototype.e = function(name, attributes, text) { + return this.element(name, attributes, text); + }; + + XMLDocumentCB.prototype.n = function(name, attributes, text) { + return this.node(name, attributes, text); + }; + + XMLDocumentCB.prototype.t = function(value) { + return this.text(value); + }; + + XMLDocumentCB.prototype.d = function(value) { + return this.cdata(value); + }; + + XMLDocumentCB.prototype.c = function(value) { + return this.comment(value); + }; + + XMLDocumentCB.prototype.r = function(value) { + return this.raw(value); + }; + + XMLDocumentCB.prototype.i = function(target, value) { + return this.instruction(target, value); + }; + + XMLDocumentCB.prototype.att = function() { + if (this.currentNode && this.currentNode instanceof XMLDocType) { + return this.attList.apply(this, arguments); + } else { + return this.attribute.apply(this, arguments); + } + }; + + XMLDocumentCB.prototype.a = function() { + if (this.currentNode && this.currentNode instanceof XMLDocType) { + return this.attList.apply(this, arguments); + } else { + return this.attribute.apply(this, arguments); + } + }; + + XMLDocumentCB.prototype.ent = function(name, value) { + return this.entity(name, value); + }; + + XMLDocumentCB.prototype.pent = function(name, value) { + return this.pEntity(name, value); + }; + + XMLDocumentCB.prototype.not = function(name, value) { + return this.notation(name, value); + }; + + return XMLDocumentCB; + + })(); + +}).call(this); + +},{"./Utility":105,"./XMLAttribute":106,"./XMLCData":107,"./XMLComment":108,"./XMLDTDAttList":109,"./XMLDTDElement":110,"./XMLDTDEntity":111,"./XMLDTDNotation":112,"./XMLDeclaration":113,"./XMLDocType":114,"./XMLElement":118,"./XMLProcessingInstruction":120,"./XMLRaw":121,"./XMLStringWriter":123,"./XMLStringifier":124,"./XMLText":125}],117:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDummy, XMLNode, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLDummy = (function(superClass) { + extend(XMLDummy, superClass); + + function XMLDummy(parent) { + XMLDummy.__super__.constructor.call(this, parent); + this.isDummy = true; + } + + XMLDummy.prototype.clone = function() { + return Object.create(this); + }; + + XMLDummy.prototype.toString = function(options) { + return ''; + }; + + return XMLDummy; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],118:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLAttribute, XMLElement, XMLNode, getValue, isFunction, isObject, ref, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + ref = require('./Utility'), isObject = ref.isObject, isFunction = ref.isFunction, getValue = ref.getValue; + + XMLNode = require('./XMLNode'); + + XMLAttribute = require('./XMLAttribute'); + + module.exports = XMLElement = (function(superClass) { + extend(XMLElement, superClass); + + function XMLElement(parent, name, attributes) { + XMLElement.__super__.constructor.call(this, parent); + if (name == null) { + throw new Error("Missing element name. " + this.debugInfo()); + } + this.name = this.stringify.eleName(name); + this.attributes = {}; + if (attributes != null) { + this.attribute(attributes); + } + if (parent.isDocument) { + this.isRoot = true; + this.documentObject = parent; + parent.rootObject = this; + } + } + + XMLElement.prototype.clone = function() { + var att, attName, clonedSelf, ref1; + clonedSelf = Object.create(this); + if (clonedSelf.isRoot) { + clonedSelf.documentObject = null; + } + clonedSelf.attributes = {}; + ref1 = this.attributes; + for (attName in ref1) { + if (!hasProp.call(ref1, attName)) continue; + att = ref1[attName]; + clonedSelf.attributes[attName] = att.clone(); + } + clonedSelf.children = []; + this.children.forEach(function(child) { + var clonedChild; + clonedChild = child.clone(); + clonedChild.parent = clonedSelf; + return clonedSelf.children.push(clonedChild); + }); + return clonedSelf; + }; + + XMLElement.prototype.attribute = function(name, value) { + var attName, attValue; + if (name != null) { + name = getValue(name); + } + if (isObject(name)) { + for (attName in name) { + if (!hasProp.call(name, attName)) continue; + attValue = name[attName]; + this.attribute(attName, attValue); + } + } else { + if (isFunction(value)) { + value = value.apply(); + } + if (!this.options.skipNullAttributes || (value != null)) { + this.attributes[name] = new XMLAttribute(this, name, value); + } + } + return this; + }; + + XMLElement.prototype.removeAttribute = function(name) { + var attName, i, len; + if (name == null) { + throw new Error("Missing attribute name. " + this.debugInfo()); + } + name = getValue(name); + if (Array.isArray(name)) { + for (i = 0, len = name.length; i < len; i++) { + attName = name[i]; + delete this.attributes[attName]; + } + } else { + delete this.attributes[name]; + } + return this; + }; + + XMLElement.prototype.toString = function(options) { + return this.options.writer.set(options).element(this); + }; + + XMLElement.prototype.att = function(name, value) { + return this.attribute(name, value); + }; + + XMLElement.prototype.a = function(name, value) { + return this.attribute(name, value); + }; + + return XMLElement; + + })(XMLNode); + +}).call(this); + +},{"./Utility":105,"./XMLAttribute":106,"./XMLNode":119}],119:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLCData, XMLComment, XMLDeclaration, XMLDocType, XMLDummy, XMLElement, XMLNode, XMLProcessingInstruction, XMLRaw, XMLText, getValue, isEmpty, isFunction, isObject, ref, + hasProp = {}.hasOwnProperty; + + ref = require('./Utility'), isObject = ref.isObject, isFunction = ref.isFunction, isEmpty = ref.isEmpty, getValue = ref.getValue; + + XMLElement = null; + + XMLCData = null; + + XMLComment = null; + + XMLDeclaration = null; + + XMLDocType = null; + + XMLRaw = null; + + XMLText = null; + + XMLProcessingInstruction = null; + + XMLDummy = null; + + module.exports = XMLNode = (function() { + function XMLNode(parent) { + this.parent = parent; + if (this.parent) { + this.options = this.parent.options; + this.stringify = this.parent.stringify; + } + this.children = []; + if (!XMLElement) { + XMLElement = require('./XMLElement'); + XMLCData = require('./XMLCData'); + XMLComment = require('./XMLComment'); + XMLDeclaration = require('./XMLDeclaration'); + XMLDocType = require('./XMLDocType'); + XMLRaw = require('./XMLRaw'); + XMLText = require('./XMLText'); + XMLProcessingInstruction = require('./XMLProcessingInstruction'); + XMLDummy = require('./XMLDummy'); + } + } + + XMLNode.prototype.element = function(name, attributes, text) { + var childNode, item, j, k, key, lastChild, len, len1, ref1, ref2, val; + lastChild = null; + if (attributes === null && (text == null)) { + ref1 = [{}, null], attributes = ref1[0], text = ref1[1]; + } + if (attributes == null) { + attributes = {}; + } + attributes = getValue(attributes); + if (!isObject(attributes)) { + ref2 = [attributes, text], text = ref2[0], attributes = ref2[1]; + } + if (name != null) { + name = getValue(name); + } + if (Array.isArray(name)) { + for (j = 0, len = name.length; j < len; j++) { + item = name[j]; + lastChild = this.element(item); + } + } else if (isFunction(name)) { + lastChild = this.element(name.apply()); + } else if (isObject(name)) { + for (key in name) { + if (!hasProp.call(name, key)) continue; + val = name[key]; + if (isFunction(val)) { + val = val.apply(); + } + if ((isObject(val)) && (isEmpty(val))) { + val = null; + } + if (!this.options.ignoreDecorators && this.stringify.convertAttKey && key.indexOf(this.stringify.convertAttKey) === 0) { + lastChild = this.attribute(key.substr(this.stringify.convertAttKey.length), val); + } else if (!this.options.separateArrayItems && Array.isArray(val)) { + for (k = 0, len1 = val.length; k < len1; k++) { + item = val[k]; + childNode = {}; + childNode[key] = item; + lastChild = this.element(childNode); + } + } else if (isObject(val)) { + lastChild = this.element(key); + lastChild.element(val); + } else { + lastChild = this.element(key, val); + } + } + } else if (this.options.skipNullNodes && text === null) { + lastChild = this.dummy(); + } else { + if (!this.options.ignoreDecorators && this.stringify.convertTextKey && name.indexOf(this.stringify.convertTextKey) === 0) { + lastChild = this.text(text); + } else if (!this.options.ignoreDecorators && this.stringify.convertCDataKey && name.indexOf(this.stringify.convertCDataKey) === 0) { + lastChild = this.cdata(text); + } else if (!this.options.ignoreDecorators && this.stringify.convertCommentKey && name.indexOf(this.stringify.convertCommentKey) === 0) { + lastChild = this.comment(text); + } else if (!this.options.ignoreDecorators && this.stringify.convertRawKey && name.indexOf(this.stringify.convertRawKey) === 0) { + lastChild = this.raw(text); + } else if (!this.options.ignoreDecorators && this.stringify.convertPIKey && name.indexOf(this.stringify.convertPIKey) === 0) { + lastChild = this.instruction(name.substr(this.stringify.convertPIKey.length), text); + } else { + lastChild = this.node(name, attributes, text); + } + } + if (lastChild == null) { + throw new Error("Could not create any elements with: " + name + ". " + this.debugInfo()); + } + return lastChild; + }; + + XMLNode.prototype.insertBefore = function(name, attributes, text) { + var child, i, removed; + if (this.isRoot) { + throw new Error("Cannot insert elements at root level. " + this.debugInfo(name)); + } + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i); + child = this.parent.element(name, attributes, text); + Array.prototype.push.apply(this.parent.children, removed); + return child; + }; + + XMLNode.prototype.insertAfter = function(name, attributes, text) { + var child, i, removed; + if (this.isRoot) { + throw new Error("Cannot insert elements at root level. " + this.debugInfo(name)); + } + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i + 1); + child = this.parent.element(name, attributes, text); + Array.prototype.push.apply(this.parent.children, removed); + return child; + }; + + XMLNode.prototype.remove = function() { + var i, ref1; + if (this.isRoot) { + throw new Error("Cannot remove the root element. " + this.debugInfo()); + } + i = this.parent.children.indexOf(this); + [].splice.apply(this.parent.children, [i, i - i + 1].concat(ref1 = [])), ref1; + return this.parent; + }; + + XMLNode.prototype.node = function(name, attributes, text) { + var child, ref1; + if (name != null) { + name = getValue(name); + } + attributes || (attributes = {}); + attributes = getValue(attributes); + if (!isObject(attributes)) { + ref1 = [attributes, text], text = ref1[0], attributes = ref1[1]; + } + child = new XMLElement(this, name, attributes); + if (text != null) { + child.text(text); + } + this.children.push(child); + return child; + }; + + XMLNode.prototype.text = function(value) { + var child; + child = new XMLText(this, value); + this.children.push(child); + return this; + }; + + XMLNode.prototype.cdata = function(value) { + var child; + child = new XMLCData(this, value); + this.children.push(child); + return this; + }; + + XMLNode.prototype.comment = function(value) { + var child; + child = new XMLComment(this, value); + this.children.push(child); + return this; + }; + + XMLNode.prototype.commentBefore = function(value) { + var child, i, removed; + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i); + child = this.parent.comment(value); + Array.prototype.push.apply(this.parent.children, removed); + return this; + }; + + XMLNode.prototype.commentAfter = function(value) { + var child, i, removed; + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i + 1); + child = this.parent.comment(value); + Array.prototype.push.apply(this.parent.children, removed); + return this; + }; + + XMLNode.prototype.raw = function(value) { + var child; + child = new XMLRaw(this, value); + this.children.push(child); + return this; + }; + + XMLNode.prototype.dummy = function() { + var child; + child = new XMLDummy(this); + this.children.push(child); + return child; + }; + + XMLNode.prototype.instruction = function(target, value) { + var insTarget, insValue, instruction, j, len; + if (target != null) { + target = getValue(target); + } + if (value != null) { + value = getValue(value); + } + if (Array.isArray(target)) { + for (j = 0, len = target.length; j < len; j++) { + insTarget = target[j]; + this.instruction(insTarget); + } + } else if (isObject(target)) { + for (insTarget in target) { + if (!hasProp.call(target, insTarget)) continue; + insValue = target[insTarget]; + this.instruction(insTarget, insValue); + } + } else { + if (isFunction(value)) { + value = value.apply(); + } + instruction = new XMLProcessingInstruction(this, target, value); + this.children.push(instruction); + } + return this; + }; + + XMLNode.prototype.instructionBefore = function(target, value) { + var child, i, removed; + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i); + child = this.parent.instruction(target, value); + Array.prototype.push.apply(this.parent.children, removed); + return this; + }; + + XMLNode.prototype.instructionAfter = function(target, value) { + var child, i, removed; + i = this.parent.children.indexOf(this); + removed = this.parent.children.splice(i + 1); + child = this.parent.instruction(target, value); + Array.prototype.push.apply(this.parent.children, removed); + return this; + }; + + XMLNode.prototype.declaration = function(version, encoding, standalone) { + var doc, xmldec; + doc = this.document(); + xmldec = new XMLDeclaration(doc, version, encoding, standalone); + if (doc.children[0] instanceof XMLDeclaration) { + doc.children[0] = xmldec; + } else { + doc.children.unshift(xmldec); + } + return doc.root() || doc; + }; + + XMLNode.prototype.doctype = function(pubID, sysID) { + var child, doc, doctype, i, j, k, len, len1, ref1, ref2; + doc = this.document(); + doctype = new XMLDocType(doc, pubID, sysID); + ref1 = doc.children; + for (i = j = 0, len = ref1.length; j < len; i = ++j) { + child = ref1[i]; + if (child instanceof XMLDocType) { + doc.children[i] = doctype; + return doctype; + } + } + ref2 = doc.children; + for (i = k = 0, len1 = ref2.length; k < len1; i = ++k) { + child = ref2[i]; + if (child.isRoot) { + doc.children.splice(i, 0, doctype); + return doctype; + } + } + doc.children.push(doctype); + return doctype; + }; + + XMLNode.prototype.up = function() { + if (this.isRoot) { + throw new Error("The root node has no parent. Use doc() if you need to get the document object."); + } + return this.parent; + }; + + XMLNode.prototype.root = function() { + var node; + node = this; + while (node) { + if (node.isDocument) { + return node.rootObject; + } else if (node.isRoot) { + return node; + } else { + node = node.parent; + } + } + }; + + XMLNode.prototype.document = function() { + var node; + node = this; + while (node) { + if (node.isDocument) { + return node; + } else { + node = node.parent; + } + } + }; + + XMLNode.prototype.end = function(options) { + return this.document().end(options); + }; + + XMLNode.prototype.prev = function() { + var i; + i = this.parent.children.indexOf(this); + while (i > 0 && this.parent.children[i - 1].isDummy) { + i = i - 1; + } + if (i < 1) { + throw new Error("Already at the first node. " + this.debugInfo()); + } + return this.parent.children[i - 1]; + }; + + XMLNode.prototype.next = function() { + var i; + i = this.parent.children.indexOf(this); + while (i < this.parent.children.length - 1 && this.parent.children[i + 1].isDummy) { + i = i + 1; + } + if (i === -1 || i === this.parent.children.length - 1) { + throw new Error("Already at the last node. " + this.debugInfo()); + } + return this.parent.children[i + 1]; + }; + + XMLNode.prototype.importDocument = function(doc) { + var clonedRoot; + clonedRoot = doc.root().clone(); + clonedRoot.parent = this; + clonedRoot.isRoot = false; + this.children.push(clonedRoot); + return this; + }; + + XMLNode.prototype.debugInfo = function(name) { + var ref1, ref2; + name = name || this.name; + if ((name == null) && !((ref1 = this.parent) != null ? ref1.name : void 0)) { + return ""; + } else if (name == null) { + return "parent: <" + this.parent.name + ">"; + } else if (!((ref2 = this.parent) != null ? ref2.name : void 0)) { + return "node: <" + name + ">"; + } else { + return "node: <" + name + ">, parent: <" + this.parent.name + ">"; + } + }; + + XMLNode.prototype.ele = function(name, attributes, text) { + return this.element(name, attributes, text); + }; + + XMLNode.prototype.nod = function(name, attributes, text) { + return this.node(name, attributes, text); + }; + + XMLNode.prototype.txt = function(value) { + return this.text(value); + }; + + XMLNode.prototype.dat = function(value) { + return this.cdata(value); + }; + + XMLNode.prototype.com = function(value) { + return this.comment(value); + }; + + XMLNode.prototype.ins = function(target, value) { + return this.instruction(target, value); + }; + + XMLNode.prototype.doc = function() { + return this.document(); + }; + + XMLNode.prototype.dec = function(version, encoding, standalone) { + return this.declaration(version, encoding, standalone); + }; + + XMLNode.prototype.dtd = function(pubID, sysID) { + return this.doctype(pubID, sysID); + }; + + XMLNode.prototype.e = function(name, attributes, text) { + return this.element(name, attributes, text); + }; + + XMLNode.prototype.n = function(name, attributes, text) { + return this.node(name, attributes, text); + }; + + XMLNode.prototype.t = function(value) { + return this.text(value); + }; + + XMLNode.prototype.d = function(value) { + return this.cdata(value); + }; + + XMLNode.prototype.c = function(value) { + return this.comment(value); + }; + + XMLNode.prototype.r = function(value) { + return this.raw(value); + }; + + XMLNode.prototype.i = function(target, value) { + return this.instruction(target, value); + }; + + XMLNode.prototype.u = function() { + return this.up(); + }; + + XMLNode.prototype.importXMLBuilder = function(doc) { + return this.importDocument(doc); + }; + + return XMLNode; + + })(); + +}).call(this); + +},{"./Utility":105,"./XMLCData":107,"./XMLComment":108,"./XMLDeclaration":113,"./XMLDocType":114,"./XMLDummy":117,"./XMLElement":118,"./XMLProcessingInstruction":120,"./XMLRaw":121,"./XMLText":125}],120:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLNode, XMLProcessingInstruction, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLProcessingInstruction = (function(superClass) { + extend(XMLProcessingInstruction, superClass); + + function XMLProcessingInstruction(parent, target, value) { + XMLProcessingInstruction.__super__.constructor.call(this, parent); + if (target == null) { + throw new Error("Missing instruction target. " + this.debugInfo()); + } + this.target = this.stringify.insTarget(target); + if (value) { + this.value = this.stringify.insValue(value); + } + } + + XMLProcessingInstruction.prototype.clone = function() { + return Object.create(this); + }; + + XMLProcessingInstruction.prototype.toString = function(options) { + return this.options.writer.set(options).processingInstruction(this); + }; + + return XMLProcessingInstruction; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],121:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLNode, XMLRaw, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLRaw = (function(superClass) { + extend(XMLRaw, superClass); + + function XMLRaw(parent, text) { + XMLRaw.__super__.constructor.call(this, parent); + if (text == null) { + throw new Error("Missing raw text. " + this.debugInfo()); + } + this.value = this.stringify.raw(text); + } + + XMLRaw.prototype.clone = function() { + return Object.create(this); + }; + + XMLRaw.prototype.toString = function(options) { + return this.options.writer.set(options).raw(this); + }; + + return XMLRaw; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],122:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLCData, XMLComment, XMLDTDAttList, XMLDTDElement, XMLDTDEntity, XMLDTDNotation, XMLDeclaration, XMLDocType, XMLDummy, XMLElement, XMLProcessingInstruction, XMLRaw, XMLStreamWriter, XMLText, XMLWriterBase, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLDeclaration = require('./XMLDeclaration'); + + XMLDocType = require('./XMLDocType'); + + XMLCData = require('./XMLCData'); + + XMLComment = require('./XMLComment'); + + XMLElement = require('./XMLElement'); + + XMLRaw = require('./XMLRaw'); + + XMLText = require('./XMLText'); + + XMLProcessingInstruction = require('./XMLProcessingInstruction'); + + XMLDummy = require('./XMLDummy'); + + XMLDTDAttList = require('./XMLDTDAttList'); + + XMLDTDElement = require('./XMLDTDElement'); + + XMLDTDEntity = require('./XMLDTDEntity'); + + XMLDTDNotation = require('./XMLDTDNotation'); + + XMLWriterBase = require('./XMLWriterBase'); + + module.exports = XMLStreamWriter = (function(superClass) { + extend(XMLStreamWriter, superClass); + + function XMLStreamWriter(stream, options) { + XMLStreamWriter.__super__.constructor.call(this, options); + this.stream = stream; + } + + XMLStreamWriter.prototype.document = function(doc) { + var child, i, j, len, len1, ref, ref1, results; + ref = doc.children; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + child.isLastRootNode = false; + } + doc.children[doc.children.length - 1].isLastRootNode = true; + ref1 = doc.children; + results = []; + for (j = 0, len1 = ref1.length; j < len1; j++) { + child = ref1[j]; + if (child instanceof XMLDummy) { + continue; + } + switch (false) { + case !(child instanceof XMLDeclaration): + results.push(this.declaration(child)); + break; + case !(child instanceof XMLDocType): + results.push(this.docType(child)); + break; + case !(child instanceof XMLComment): + results.push(this.comment(child)); + break; + case !(child instanceof XMLProcessingInstruction): + results.push(this.processingInstruction(child)); + break; + default: + results.push(this.element(child)); + } + } + return results; + }; + + XMLStreamWriter.prototype.attribute = function(att) { + return this.stream.write(' ' + att.name + '="' + att.value + '"'); + }; + + XMLStreamWriter.prototype.cdata = function(node, level) { + return this.stream.write(this.space(level) + '<![CDATA[' + node.text + ']]>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.comment = function(node, level) { + return this.stream.write(this.space(level) + '<!-- ' + node.text + ' -->' + this.endline(node)); + }; + + XMLStreamWriter.prototype.declaration = function(node, level) { + this.stream.write(this.space(level)); + this.stream.write('<?xml version="' + node.version + '"'); + if (node.encoding != null) { + this.stream.write(' encoding="' + node.encoding + '"'); + } + if (node.standalone != null) { + this.stream.write(' standalone="' + node.standalone + '"'); + } + this.stream.write(this.spacebeforeslash + '?>'); + return this.stream.write(this.endline(node)); + }; + + XMLStreamWriter.prototype.docType = function(node, level) { + var child, i, len, ref; + level || (level = 0); + this.stream.write(this.space(level)); + this.stream.write('<!DOCTYPE ' + node.root().name); + if (node.pubID && node.sysID) { + this.stream.write(' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'); + } else if (node.sysID) { + this.stream.write(' SYSTEM "' + node.sysID + '"'); + } + if (node.children.length > 0) { + this.stream.write(' ['); + this.stream.write(this.endline(node)); + ref = node.children; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + switch (false) { + case !(child instanceof XMLDTDAttList): + this.dtdAttList(child, level + 1); + break; + case !(child instanceof XMLDTDElement): + this.dtdElement(child, level + 1); + break; + case !(child instanceof XMLDTDEntity): + this.dtdEntity(child, level + 1); + break; + case !(child instanceof XMLDTDNotation): + this.dtdNotation(child, level + 1); + break; + case !(child instanceof XMLCData): + this.cdata(child, level + 1); + break; + case !(child instanceof XMLComment): + this.comment(child, level + 1); + break; + case !(child instanceof XMLProcessingInstruction): + this.processingInstruction(child, level + 1); + break; + default: + throw new Error("Unknown DTD node type: " + child.constructor.name); + } + } + this.stream.write(']'); + } + this.stream.write(this.spacebeforeslash + '>'); + return this.stream.write(this.endline(node)); + }; + + XMLStreamWriter.prototype.element = function(node, level) { + var att, child, i, len, name, ref, ref1, space; + level || (level = 0); + space = this.space(level); + this.stream.write(space + '<' + node.name); + ref = node.attributes; + for (name in ref) { + if (!hasProp.call(ref, name)) continue; + att = ref[name]; + this.attribute(att); + } + if (node.children.length === 0 || node.children.every(function(e) { + return e.value === ''; + })) { + if (this.allowEmpty) { + this.stream.write('></' + node.name + '>'); + } else { + this.stream.write(this.spacebeforeslash + '/>'); + } + } else if (this.pretty && node.children.length === 1 && (node.children[0].value != null)) { + this.stream.write('>'); + this.stream.write(node.children[0].value); + this.stream.write('</' + node.name + '>'); + } else { + this.stream.write('>' + this.newline); + ref1 = node.children; + for (i = 0, len = ref1.length; i < len; i++) { + child = ref1[i]; + switch (false) { + case !(child instanceof XMLCData): + this.cdata(child, level + 1); + break; + case !(child instanceof XMLComment): + this.comment(child, level + 1); + break; + case !(child instanceof XMLElement): + this.element(child, level + 1); + break; + case !(child instanceof XMLRaw): + this.raw(child, level + 1); + break; + case !(child instanceof XMLText): + this.text(child, level + 1); + break; + case !(child instanceof XMLProcessingInstruction): + this.processingInstruction(child, level + 1); + break; + case !(child instanceof XMLDummy): + ''; + break; + default: + throw new Error("Unknown XML node type: " + child.constructor.name); + } + } + this.stream.write(space + '</' + node.name + '>'); + } + return this.stream.write(this.endline(node)); + }; + + XMLStreamWriter.prototype.processingInstruction = function(node, level) { + this.stream.write(this.space(level) + '<?' + node.target); + if (node.value) { + this.stream.write(' ' + node.value); + } + return this.stream.write(this.spacebeforeslash + '?>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.raw = function(node, level) { + return this.stream.write(this.space(level) + node.value + this.endline(node)); + }; + + XMLStreamWriter.prototype.text = function(node, level) { + return this.stream.write(this.space(level) + node.value + this.endline(node)); + }; + + XMLStreamWriter.prototype.dtdAttList = function(node, level) { + this.stream.write(this.space(level) + '<!ATTLIST ' + node.elementName + ' ' + node.attributeName + ' ' + node.attributeType); + if (node.defaultValueType !== '#DEFAULT') { + this.stream.write(' ' + node.defaultValueType); + } + if (node.defaultValue) { + this.stream.write(' "' + node.defaultValue + '"'); + } + return this.stream.write(this.spacebeforeslash + '>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.dtdElement = function(node, level) { + this.stream.write(this.space(level) + '<!ELEMENT ' + node.name + ' ' + node.value); + return this.stream.write(this.spacebeforeslash + '>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.dtdEntity = function(node, level) { + this.stream.write(this.space(level) + '<!ENTITY'); + if (node.pe) { + this.stream.write(' %'); + } + this.stream.write(' ' + node.name); + if (node.value) { + this.stream.write(' "' + node.value + '"'); + } else { + if (node.pubID && node.sysID) { + this.stream.write(' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'); + } else if (node.sysID) { + this.stream.write(' SYSTEM "' + node.sysID + '"'); + } + if (node.nData) { + this.stream.write(' NDATA ' + node.nData); + } + } + return this.stream.write(this.spacebeforeslash + '>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.dtdNotation = function(node, level) { + this.stream.write(this.space(level) + '<!NOTATION ' + node.name); + if (node.pubID && node.sysID) { + this.stream.write(' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'); + } else if (node.pubID) { + this.stream.write(' PUBLIC "' + node.pubID + '"'); + } else if (node.sysID) { + this.stream.write(' SYSTEM "' + node.sysID + '"'); + } + return this.stream.write(this.spacebeforeslash + '>' + this.endline(node)); + }; + + XMLStreamWriter.prototype.endline = function(node) { + if (!node.isLastRootNode) { + return this.newline; + } else { + return ''; + } + }; + + return XMLStreamWriter; + + })(XMLWriterBase); + +}).call(this); + +},{"./XMLCData":107,"./XMLComment":108,"./XMLDTDAttList":109,"./XMLDTDElement":110,"./XMLDTDEntity":111,"./XMLDTDNotation":112,"./XMLDeclaration":113,"./XMLDocType":114,"./XMLDummy":117,"./XMLElement":118,"./XMLProcessingInstruction":120,"./XMLRaw":121,"./XMLText":125,"./XMLWriterBase":126}],123:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLCData, XMLComment, XMLDTDAttList, XMLDTDElement, XMLDTDEntity, XMLDTDNotation, XMLDeclaration, XMLDocType, XMLDummy, XMLElement, XMLProcessingInstruction, XMLRaw, XMLStringWriter, XMLText, XMLWriterBase, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLDeclaration = require('./XMLDeclaration'); + + XMLDocType = require('./XMLDocType'); + + XMLCData = require('./XMLCData'); + + XMLComment = require('./XMLComment'); + + XMLElement = require('./XMLElement'); + + XMLRaw = require('./XMLRaw'); + + XMLText = require('./XMLText'); + + XMLProcessingInstruction = require('./XMLProcessingInstruction'); + + XMLDummy = require('./XMLDummy'); + + XMLDTDAttList = require('./XMLDTDAttList'); + + XMLDTDElement = require('./XMLDTDElement'); + + XMLDTDEntity = require('./XMLDTDEntity'); + + XMLDTDNotation = require('./XMLDTDNotation'); + + XMLWriterBase = require('./XMLWriterBase'); + + module.exports = XMLStringWriter = (function(superClass) { + extend(XMLStringWriter, superClass); + + function XMLStringWriter(options) { + XMLStringWriter.__super__.constructor.call(this, options); + } + + XMLStringWriter.prototype.document = function(doc) { + var child, i, len, r, ref; + this.textispresent = false; + r = ''; + ref = doc.children; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + if (child instanceof XMLDummy) { + continue; + } + r += (function() { + switch (false) { + case !(child instanceof XMLDeclaration): + return this.declaration(child); + case !(child instanceof XMLDocType): + return this.docType(child); + case !(child instanceof XMLComment): + return this.comment(child); + case !(child instanceof XMLProcessingInstruction): + return this.processingInstruction(child); + default: + return this.element(child, 0); + } + }).call(this); + } + if (this.pretty && r.slice(-this.newline.length) === this.newline) { + r = r.slice(0, -this.newline.length); + } + return r; + }; + + XMLStringWriter.prototype.attribute = function(att) { + return ' ' + att.name + '="' + att.value + '"'; + }; + + XMLStringWriter.prototype.cdata = function(node, level) { + return this.space(level) + '<![CDATA[' + node.text + ']]>' + this.newline; + }; + + XMLStringWriter.prototype.comment = function(node, level) { + return this.space(level) + '<!-- ' + node.text + ' -->' + this.newline; + }; + + XMLStringWriter.prototype.declaration = function(node, level) { + var r; + r = this.space(level); + r += '<?xml version="' + node.version + '"'; + if (node.encoding != null) { + r += ' encoding="' + node.encoding + '"'; + } + if (node.standalone != null) { + r += ' standalone="' + node.standalone + '"'; + } + r += this.spacebeforeslash + '?>'; + r += this.newline; + return r; + }; + + XMLStringWriter.prototype.docType = function(node, level) { + var child, i, len, r, ref; + level || (level = 0); + r = this.space(level); + r += '<!DOCTYPE ' + node.root().name; + if (node.pubID && node.sysID) { + r += ' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'; + } else if (node.sysID) { + r += ' SYSTEM "' + node.sysID + '"'; + } + if (node.children.length > 0) { + r += ' ['; + r += this.newline; + ref = node.children; + for (i = 0, len = ref.length; i < len; i++) { + child = ref[i]; + r += (function() { + switch (false) { + case !(child instanceof XMLDTDAttList): + return this.dtdAttList(child, level + 1); + case !(child instanceof XMLDTDElement): + return this.dtdElement(child, level + 1); + case !(child instanceof XMLDTDEntity): + return this.dtdEntity(child, level + 1); + case !(child instanceof XMLDTDNotation): + return this.dtdNotation(child, level + 1); + case !(child instanceof XMLCData): + return this.cdata(child, level + 1); + case !(child instanceof XMLComment): + return this.comment(child, level + 1); + case !(child instanceof XMLProcessingInstruction): + return this.processingInstruction(child, level + 1); + default: + throw new Error("Unknown DTD node type: " + child.constructor.name); + } + }).call(this); + } + r += ']'; + } + r += this.spacebeforeslash + '>'; + r += this.newline; + return r; + }; + + XMLStringWriter.prototype.element = function(node, level) { + var att, child, i, j, len, len1, name, r, ref, ref1, ref2, space, textispresentwasset; + level || (level = 0); + textispresentwasset = false; + if (this.textispresent) { + this.newline = ''; + this.pretty = false; + } else { + this.newline = this.newlinedefault; + this.pretty = this.prettydefault; + } + space = this.space(level); + r = ''; + r += space + '<' + node.name; + ref = node.attributes; + for (name in ref) { + if (!hasProp.call(ref, name)) continue; + att = ref[name]; + r += this.attribute(att); + } + if (node.children.length === 0 || node.children.every(function(e) { + return e.value === ''; + })) { + if (this.allowEmpty) { + r += '></' + node.name + '>' + this.newline; + } else { + r += this.spacebeforeslash + '/>' + this.newline; + } + } else if (this.pretty && node.children.length === 1 && (node.children[0].value != null)) { + r += '>'; + r += node.children[0].value; + r += '</' + node.name + '>' + this.newline; + } else { + if (this.dontprettytextnodes) { + ref1 = node.children; + for (i = 0, len = ref1.length; i < len; i++) { + child = ref1[i]; + if (child.value != null) { + this.textispresent++; + textispresentwasset = true; + break; + } + } + } + if (this.textispresent) { + this.newline = ''; + this.pretty = false; + space = this.space(level); + } + r += '>' + this.newline; + ref2 = node.children; + for (j = 0, len1 = ref2.length; j < len1; j++) { + child = ref2[j]; + r += (function() { + switch (false) { + case !(child instanceof XMLCData): + return this.cdata(child, level + 1); + case !(child instanceof XMLComment): + return this.comment(child, level + 1); + case !(child instanceof XMLElement): + return this.element(child, level + 1); + case !(child instanceof XMLRaw): + return this.raw(child, level + 1); + case !(child instanceof XMLText): + return this.text(child, level + 1); + case !(child instanceof XMLProcessingInstruction): + return this.processingInstruction(child, level + 1); + case !(child instanceof XMLDummy): + return ''; + default: + throw new Error("Unknown XML node type: " + child.constructor.name); + } + }).call(this); + } + if (textispresentwasset) { + this.textispresent--; + } + if (!this.textispresent) { + this.newline = this.newlinedefault; + this.pretty = this.prettydefault; + } + r += space + '</' + node.name + '>' + this.newline; + } + return r; + }; + + XMLStringWriter.prototype.processingInstruction = function(node, level) { + var r; + r = this.space(level) + '<?' + node.target; + if (node.value) { + r += ' ' + node.value; + } + r += this.spacebeforeslash + '?>' + this.newline; + return r; + }; + + XMLStringWriter.prototype.raw = function(node, level) { + return this.space(level) + node.value + this.newline; + }; + + XMLStringWriter.prototype.text = function(node, level) { + return this.space(level) + node.value + this.newline; + }; + + XMLStringWriter.prototype.dtdAttList = function(node, level) { + var r; + r = this.space(level) + '<!ATTLIST ' + node.elementName + ' ' + node.attributeName + ' ' + node.attributeType; + if (node.defaultValueType !== '#DEFAULT') { + r += ' ' + node.defaultValueType; + } + if (node.defaultValue) { + r += ' "' + node.defaultValue + '"'; + } + r += this.spacebeforeslash + '>' + this.newline; + return r; + }; + + XMLStringWriter.prototype.dtdElement = function(node, level) { + return this.space(level) + '<!ELEMENT ' + node.name + ' ' + node.value + this.spacebeforeslash + '>' + this.newline; + }; + + XMLStringWriter.prototype.dtdEntity = function(node, level) { + var r; + r = this.space(level) + '<!ENTITY'; + if (node.pe) { + r += ' %'; + } + r += ' ' + node.name; + if (node.value) { + r += ' "' + node.value + '"'; + } else { + if (node.pubID && node.sysID) { + r += ' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'; + } else if (node.sysID) { + r += ' SYSTEM "' + node.sysID + '"'; + } + if (node.nData) { + r += ' NDATA ' + node.nData; + } + } + r += this.spacebeforeslash + '>' + this.newline; + return r; + }; + + XMLStringWriter.prototype.dtdNotation = function(node, level) { + var r; + r = this.space(level) + '<!NOTATION ' + node.name; + if (node.pubID && node.sysID) { + r += ' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'; + } else if (node.pubID) { + r += ' PUBLIC "' + node.pubID + '"'; + } else if (node.sysID) { + r += ' SYSTEM "' + node.sysID + '"'; + } + r += this.spacebeforeslash + '>' + this.newline; + return r; + }; + + XMLStringWriter.prototype.openNode = function(node, level) { + var att, name, r, ref; + level || (level = 0); + if (node instanceof XMLElement) { + r = this.space(level) + '<' + node.name; + ref = node.attributes; + for (name in ref) { + if (!hasProp.call(ref, name)) continue; + att = ref[name]; + r += this.attribute(att); + } + r += (node.children ? '>' : '/>') + this.newline; + return r; + } else { + r = this.space(level) + '<!DOCTYPE ' + node.rootNodeName; + if (node.pubID && node.sysID) { + r += ' PUBLIC "' + node.pubID + '" "' + node.sysID + '"'; + } else if (node.sysID) { + r += ' SYSTEM "' + node.sysID + '"'; + } + r += (node.children ? ' [' : '>') + this.newline; + return r; + } + }; + + XMLStringWriter.prototype.closeNode = function(node, level) { + level || (level = 0); + switch (false) { + case !(node instanceof XMLElement): + return this.space(level) + '</' + node.name + '>' + this.newline; + case !(node instanceof XMLDocType): + return this.space(level) + ']>' + this.newline; + } + }; + + return XMLStringWriter; + + })(XMLWriterBase); + +}).call(this); + +},{"./XMLCData":107,"./XMLComment":108,"./XMLDTDAttList":109,"./XMLDTDElement":110,"./XMLDTDEntity":111,"./XMLDTDNotation":112,"./XMLDeclaration":113,"./XMLDocType":114,"./XMLDummy":117,"./XMLElement":118,"./XMLProcessingInstruction":120,"./XMLRaw":121,"./XMLText":125,"./XMLWriterBase":126}],124:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLStringifier, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + hasProp = {}.hasOwnProperty; + + module.exports = XMLStringifier = (function() { + function XMLStringifier(options) { + this.assertLegalChar = bind(this.assertLegalChar, this); + var key, ref, value; + options || (options = {}); + this.noDoubleEncoding = options.noDoubleEncoding; + ref = options.stringify || {}; + for (key in ref) { + if (!hasProp.call(ref, key)) continue; + value = ref[key]; + this[key] = value; + } + } + + XMLStringifier.prototype.eleName = function(val) { + val = '' + val || ''; + return this.assertLegalChar(val); + }; + + XMLStringifier.prototype.eleText = function(val) { + val = '' + val || ''; + return this.assertLegalChar(this.elEscape(val)); + }; + + XMLStringifier.prototype.cdata = function(val) { + val = '' + val || ''; + val = val.replace(']]>', ']]]]><![CDATA[>'); + return this.assertLegalChar(val); + }; + + XMLStringifier.prototype.comment = function(val) { + val = '' + val || ''; + if (val.match(/--/)) { + throw new Error("Comment text cannot contain double-hypen: " + val); + } + return this.assertLegalChar(val); + }; + + XMLStringifier.prototype.raw = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.attName = function(val) { + return val = '' + val || ''; + }; + + XMLStringifier.prototype.attValue = function(val) { + val = '' + val || ''; + return this.attEscape(val); + }; + + XMLStringifier.prototype.insTarget = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.insValue = function(val) { + val = '' + val || ''; + if (val.match(/\?>/)) { + throw new Error("Invalid processing instruction value: " + val); + } + return val; + }; + + XMLStringifier.prototype.xmlVersion = function(val) { + val = '' + val || ''; + if (!val.match(/1\.[0-9]+/)) { + throw new Error("Invalid version number: " + val); + } + return val; + }; + + XMLStringifier.prototype.xmlEncoding = function(val) { + val = '' + val || ''; + if (!val.match(/^[A-Za-z](?:[A-Za-z0-9._-])*$/)) { + throw new Error("Invalid encoding: " + val); + } + return val; + }; + + XMLStringifier.prototype.xmlStandalone = function(val) { + if (val) { + return "yes"; + } else { + return "no"; + } + }; + + XMLStringifier.prototype.dtdPubID = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.dtdSysID = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.dtdElementValue = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.dtdAttType = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.dtdAttDefault = function(val) { + if (val != null) { + return '' + val || ''; + } else { + return val; + } + }; + + XMLStringifier.prototype.dtdEntityValue = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.dtdNData = function(val) { + return '' + val || ''; + }; + + XMLStringifier.prototype.convertAttKey = '@'; + + XMLStringifier.prototype.convertPIKey = '?'; + + XMLStringifier.prototype.convertTextKey = '#text'; + + XMLStringifier.prototype.convertCDataKey = '#cdata'; + + XMLStringifier.prototype.convertCommentKey = '#comment'; + + XMLStringifier.prototype.convertRawKey = '#raw'; + + XMLStringifier.prototype.assertLegalChar = function(str) { + var res; + res = str.match(/[\0\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/); + if (res) { + throw new Error("Invalid character in string: " + str + " at index " + res.index); + } + return str; + }; + + XMLStringifier.prototype.elEscape = function(str) { + var ampregex; + ampregex = this.noDoubleEncoding ? /(?!&\S+;)&/g : /&/g; + return str.replace(ampregex, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\r/g, ' '); + }; + + XMLStringifier.prototype.attEscape = function(str) { + var ampregex; + ampregex = this.noDoubleEncoding ? /(?!&\S+;)&/g : /&/g; + return str.replace(ampregex, '&').replace(/</g, '<').replace(/"/g, '"').replace(/\t/g, ' ').replace(/\n/g, ' ').replace(/\r/g, ' '); + }; + + return XMLStringifier; + + })(); + +}).call(this); + +},{}],125:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLNode, XMLText, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + XMLNode = require('./XMLNode'); + + module.exports = XMLText = (function(superClass) { + extend(XMLText, superClass); + + function XMLText(parent, text) { + XMLText.__super__.constructor.call(this, parent); + if (text == null) { + throw new Error("Missing element text. " + this.debugInfo()); + } + this.value = this.stringify.eleText(text); + } + + XMLText.prototype.clone = function() { + return Object.create(this); + }; + + XMLText.prototype.toString = function(options) { + return this.options.writer.set(options).text(this); + }; + + return XMLText; + + })(XMLNode); + +}).call(this); + +},{"./XMLNode":119}],126:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLWriterBase, + hasProp = {}.hasOwnProperty; + + module.exports = XMLWriterBase = (function() { + function XMLWriterBase(options) { + var key, ref, ref1, ref2, ref3, ref4, ref5, ref6, value; + options || (options = {}); + this.pretty = options.pretty || false; + this.allowEmpty = (ref = options.allowEmpty) != null ? ref : false; + if (this.pretty) { + this.indent = (ref1 = options.indent) != null ? ref1 : ' '; + this.newline = (ref2 = options.newline) != null ? ref2 : '\n'; + this.offset = (ref3 = options.offset) != null ? ref3 : 0; + this.dontprettytextnodes = (ref4 = options.dontprettytextnodes) != null ? ref4 : 0; + } else { + this.indent = ''; + this.newline = ''; + this.offset = 0; + this.dontprettytextnodes = 0; + } + this.spacebeforeslash = (ref5 = options.spacebeforeslash) != null ? ref5 : ''; + if (this.spacebeforeslash === true) { + this.spacebeforeslash = ' '; + } + this.newlinedefault = this.newline; + this.prettydefault = this.pretty; + ref6 = options.writer || {}; + for (key in ref6) { + if (!hasProp.call(ref6, key)) continue; + value = ref6[key]; + this[key] = value; + } + } + + XMLWriterBase.prototype.set = function(options) { + var key, ref, value; + options || (options = {}); + if ("pretty" in options) { + this.pretty = options.pretty; + } + if ("allowEmpty" in options) { + this.allowEmpty = options.allowEmpty; + } + if (this.pretty) { + this.indent = "indent" in options ? options.indent : ' '; + this.newline = "newline" in options ? options.newline : '\n'; + this.offset = "offset" in options ? options.offset : 0; + this.dontprettytextnodes = "dontprettytextnodes" in options ? options.dontprettytextnodes : 0; + } else { + this.indent = ''; + this.newline = ''; + this.offset = 0; + this.dontprettytextnodes = 0; + } + this.spacebeforeslash = "spacebeforeslash" in options ? options.spacebeforeslash : ''; + if (this.spacebeforeslash === true) { + this.spacebeforeslash = ' '; + } + this.newlinedefault = this.newline; + this.prettydefault = this.pretty; + ref = options.writer || {}; + for (key in ref) { + if (!hasProp.call(ref, key)) continue; + value = ref[key]; + this[key] = value; + } + return this; + }; + + XMLWriterBase.prototype.space = function(level) { + var indent; + if (this.pretty) { + indent = (level || 0) + this.offset + 1; + if (indent > 0) { + return new Array(indent).join(this.indent); + } else { + return ''; + } + } else { + return ''; + } + }; + + return XMLWriterBase; + + })(); + +}).call(this); + +},{}],127:[function(require,module,exports){ +// Generated by CoffeeScript 1.12.7 +(function() { + var XMLDocument, XMLDocumentCB, XMLStreamWriter, XMLStringWriter, assign, isFunction, ref; + + ref = require('./Utility'), assign = ref.assign, isFunction = ref.isFunction; + + XMLDocument = require('./XMLDocument'); + + XMLDocumentCB = require('./XMLDocumentCB'); + + XMLStringWriter = require('./XMLStringWriter'); + + XMLStreamWriter = require('./XMLStreamWriter'); + + module.exports.create = function(name, xmldec, doctype, options) { + var doc, root; + if (name == null) { + throw new Error("Root element needs a name."); + } + options = assign({}, xmldec, doctype, options); + doc = new XMLDocument(options); + root = doc.element(name); + if (!options.headless) { + doc.declaration(options); + if ((options.pubID != null) || (options.sysID != null)) { + doc.doctype(options); + } + } + return root; + }; + + module.exports.begin = function(options, onData, onEnd) { + var ref1; + if (isFunction(options)) { + ref1 = [options, onData], onData = ref1[0], onEnd = ref1[1]; + options = {}; + } + if (onData) { + return new XMLDocumentCB(options, onData, onEnd); + } else { + return new XMLDocument(options); + } + }; + + module.exports.stringWriter = function(options) { + return new XMLStringWriter(options); + }; + + module.exports.streamWriter = function(stream, options) { + return new XMLStreamWriter(stream, options); + }; + +}).call(this); + +},{"./Utility":105,"./XMLDocument":115,"./XMLDocumentCB":116,"./XMLStreamWriter":122,"./XMLStringWriter":123}]},{},[22])(22) +}); diff --git a/packages/docx-io/src/lib/mammoth.js/package.json b/packages/docx-io/src/lib/mammoth.js/package.json new file mode 100644 index 0000000000..971acbd8e3 --- /dev/null +++ b/packages/docx-io/src/lib/mammoth.js/package.json @@ -0,0 +1,64 @@ +{ + "name": "mammoth", + "version": "1.11.0", + "author": "Michael Williamson <mike@zwobble.org>", + "description": "Convert Word documents from docx to simple HTML and Markdown", + "keywords": [ + "docx", + "html", + "office", + "word", + "markdown", + "md" + ], + "main": "./lib/index.js", + "repository": { + "type": "git", + "url": "https://github.com/mwilliamson/mammoth.js.git" + }, + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.10.1", + "lop": "^0.4.2", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "devDependencies": { + "@types/node": "^20.2.5", + "browserify": "~13.0.1", + "browserify-prepend-licenses": "~1.0.0", + "duck": "^0.1.12", + "eslint": "2.13.1", + "hamjest": "^4.0.1", + "mocha": "~2.2.5", + "temp": "^0.9.4", + "typescript": "^5.0.4", + "uglify-js": "^3.19.3" + }, + "browser": { + "./lib/unzip.js": "./browser/unzip.js", + "./lib/docx/files.js": "./browser/docx/files.js" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "scripts": { + "pretest": "bun run eslint lib test", + "test": "bun run mocha 'test/**/*.tests.js'", + "check-typescript": "bun run tsc --noEmit lib/index.d.ts", + "build": "bun run make mammoth.browser.min.js" + }, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12.0.0" + }, + "volta": { + "node": "16.18.1", + "npm": "8.19.2" + } +} diff --git a/packages/docx-io/src/lib/parseDocxTracking.spec.ts b/packages/docx-io/src/lib/parseDocxTracking.spec.ts new file mode 100644 index 0000000000..dc888d2427 --- /dev/null +++ b/packages/docx-io/src/lib/parseDocxTracking.spec.ts @@ -0,0 +1,501 @@ +/** + * Unit tests for DOCX Tracked Changes and Comments Import Parsing. + * + * Tests the parsing utilities for extracting tracked changes and comments + * from HTML that contains DOCX tracking tokens. + */ + +import { describe, expect, it } from 'bun:test'; + +import { + DOCX_DELETION_END_TOKEN_PREFIX, + DOCX_DELETION_START_TOKEN_PREFIX, + DOCX_DELETION_TOKEN_SUFFIX, + DOCX_INSERTION_END_TOKEN_PREFIX, + DOCX_INSERTION_START_TOKEN_PREFIX, + DOCX_INSERTION_TOKEN_SUFFIX, + parseDocxTrackedChanges, +} from './importTrackChanges'; +import { + DOCX_COMMENT_END_TOKEN_PREFIX, + DOCX_COMMENT_START_TOKEN_PREFIX, + DOCX_COMMENT_TOKEN_SUFFIX, + hasDocxTrackingTokens, + parseDocxComments, + parseDocxTracking, + stripDocxTrackingTokens, +} from './importComments'; + +// Helper to build tokens for testing +function buildInsertionToken( + payload: { id: string; author?: string; date?: string }, + position: 'start' | 'end' +): string { + if (position === 'start') { + const encoded = encodeURIComponent(JSON.stringify(payload)); + return `${DOCX_INSERTION_START_TOKEN_PREFIX}${encoded}${DOCX_INSERTION_TOKEN_SUFFIX}`; + } + return `${DOCX_INSERTION_END_TOKEN_PREFIX}${payload.id}${DOCX_INSERTION_TOKEN_SUFFIX}`; +} + +function buildDeletionToken( + payload: { id: string; author?: string; date?: string }, + position: 'start' | 'end' +): string { + if (position === 'start') { + const encoded = encodeURIComponent(JSON.stringify(payload)); + return `${DOCX_DELETION_START_TOKEN_PREFIX}${encoded}${DOCX_DELETION_TOKEN_SUFFIX}`; + } + return `${DOCX_DELETION_END_TOKEN_PREFIX}${payload.id}${DOCX_DELETION_TOKEN_SUFFIX}`; +} + +function buildCommentToken( + payload: { + id: string; + authorName?: string; + authorInitials?: string; + date?: string; + text?: string; + }, + position: 'start' | 'end' +): string { + if (position === 'start') { + const encoded = encodeURIComponent(JSON.stringify(payload)); + return `${DOCX_COMMENT_START_TOKEN_PREFIX}${encoded}${DOCX_COMMENT_TOKEN_SUFFIX}`; + } + return `${DOCX_COMMENT_END_TOKEN_PREFIX}${encodeURIComponent(payload.id)}${DOCX_COMMENT_TOKEN_SUFFIX}`; +} + +describe('Token Constants Export', () => { + it('should export all token constants', () => { + expect(DOCX_INSERTION_START_TOKEN_PREFIX).toBe('[[DOCX_INS_START:'); + expect(DOCX_INSERTION_END_TOKEN_PREFIX).toBe('[[DOCX_INS_END:'); + expect(DOCX_INSERTION_TOKEN_SUFFIX).toBe(']]'); + + expect(DOCX_DELETION_START_TOKEN_PREFIX).toBe('[[DOCX_DEL_START:'); + expect(DOCX_DELETION_END_TOKEN_PREFIX).toBe('[[DOCX_DEL_END:'); + expect(DOCX_DELETION_TOKEN_SUFFIX).toBe(']]'); + + expect(DOCX_COMMENT_START_TOKEN_PREFIX).toBe('[[DOCX_CMT_START:'); + expect(DOCX_COMMENT_END_TOKEN_PREFIX).toBe('[[DOCX_CMT_END:'); + expect(DOCX_COMMENT_TOKEN_SUFFIX).toBe(']]'); + }); +}); + +describe('hasDocxTrackingTokens', () => { + it('should return true for text with insertion tokens', () => { + const html = `<p>${buildInsertionToken({ id: 'ins-1' }, 'start')}text${buildInsertionToken({ id: 'ins-1' }, 'end')}</p>`; + expect(hasDocxTrackingTokens(html)).toBe(true); + }); + + it('should return true for text with deletion tokens', () => { + const html = `<p>${buildDeletionToken({ id: 'del-1' }, 'start')}text${buildDeletionToken({ id: 'del-1' }, 'end')}</p>`; + expect(hasDocxTrackingTokens(html)).toBe(true); + }); + + it('should return true for text with comment tokens', () => { + const html = `<p>${buildCommentToken({ id: 'cmt-1' }, 'start')}text${buildCommentToken({ id: 'cmt-1' }, 'end')}</p>`; + expect(hasDocxTrackingTokens(html)).toBe(true); + }); + + it('should return false for text without tokens', () => { + const html = '<p>This is plain text without any tracking tokens</p>'; + expect(hasDocxTrackingTokens(html)).toBe(false); + }); + + it('should return false for empty text', () => { + expect(hasDocxTrackingTokens('')).toBe(false); + }); + + it('should return true with only start token present', () => { + const html = `<p>${buildInsertionToken({ id: 'ins-1' }, 'start')}text</p>`; + expect(hasDocxTrackingTokens(html)).toBe(true); + }); +}); + +describe('parseDocxTrackedChanges', () => { + describe('insertions', () => { + it('should parse a single insertion', () => { + const payload = { id: 'ins-1', author: 'John Doe', date: '2024-01-01' }; + const html = `<p>${buildInsertionToken(payload, 'start')}inserted text${buildInsertionToken(payload, 'end')}</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(1); + expect(result.insertionCount).toBe(1); + expect(result.deletionCount).toBe(0); + + const change = result.changes[0]; + expect(change.id).toBe('ins-1'); + expect(change.type).toBe('insert'); + expect(change.author).toBe('John Doe'); + expect(change.date).toBe('2024-01-01'); + expect(change.startToken).toContain('[[DOCX_INS_START:'); + expect(change.endToken).toBe('[[DOCX_INS_END:ins-1]]'); + }); + + it('should parse multiple insertions', () => { + const html = ` + <p>${buildInsertionToken({ id: 'ins-1' }, 'start')}first${buildInsertionToken({ id: 'ins-1' }, 'end')}</p> + <p>${buildInsertionToken({ id: 'ins-2' }, 'start')}second${buildInsertionToken({ id: 'ins-2' }, 'end')}</p> + `; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(2); + expect(result.insertionCount).toBe(2); + expect(result.changes[0].id).toBe('ins-1'); + expect(result.changes[1].id).toBe('ins-2'); + }); + + it('should handle insertion without author or date', () => { + const html = `<p>${buildInsertionToken({ id: 'ins-1' }, 'start')}text${buildInsertionToken({ id: 'ins-1' }, 'end')}</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(1); + expect(result.changes[0].author).toBeUndefined(); + expect(result.changes[0].date).toBeUndefined(); + }); + }); + + describe('deletions', () => { + it('should parse a single deletion', () => { + const payload = { id: 'del-1', author: 'Jane Smith', date: '2024-01-02' }; + const html = `<p>${buildDeletionToken(payload, 'start')}deleted text${buildDeletionToken(payload, 'end')}</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(1); + expect(result.deletionCount).toBe(1); + expect(result.insertionCount).toBe(0); + + const change = result.changes[0]; + expect(change.id).toBe('del-1'); + expect(change.type).toBe('remove'); + expect(change.author).toBe('Jane Smith'); + expect(change.date).toBe('2024-01-02'); + expect(change.startToken).toContain('[[DOCX_DEL_START:'); + expect(change.endToken).toBe('[[DOCX_DEL_END:del-1]]'); + }); + + it('should parse multiple deletions', () => { + const html = ` + <p>${buildDeletionToken({ id: 'del-1' }, 'start')}first${buildDeletionToken({ id: 'del-1' }, 'end')}</p> + <p>${buildDeletionToken({ id: 'del-2' }, 'start')}second${buildDeletionToken({ id: 'del-2' }, 'end')}</p> + `; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(2); + expect(result.deletionCount).toBe(2); + }); + }); + + describe('mixed changes', () => { + it('should parse insertions and deletions together', () => { + const html = ` + <p>${buildInsertionToken({ id: 'ins-1', author: 'Alice' }, 'start')}added${buildInsertionToken({ id: 'ins-1' }, 'end')}</p> + <p>${buildDeletionToken({ id: 'del-1', author: 'Bob' }, 'start')}removed${buildDeletionToken({ id: 'del-1' }, 'end')}</p> + <p>${buildInsertionToken({ id: 'ins-2', author: 'Alice' }, 'start')}added again${buildInsertionToken({ id: 'ins-2' }, 'end')}</p> + `; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(3); + expect(result.insertionCount).toBe(2); + expect(result.deletionCount).toBe(1); + }); + }); + + describe('edge cases', () => { + it('should return empty result for text without tokens', () => { + const result = parseDocxTrackedChanges('<p>plain text</p>'); + + expect(result.changes).toHaveLength(0); + expect(result.insertionCount).toBe(0); + expect(result.deletionCount).toBe(0); + }); + + it('should return empty result for empty string', () => { + const result = parseDocxTrackedChanges(''); + + expect(result.changes).toHaveLength(0); + }); + + it('should skip malformed tokens', () => { + const html = + '<p>[[DOCX_INS_START:not-valid-json]]text[[DOCX_INS_END:id]]</p>'; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(0); + }); + + it('should skip tokens without id', () => { + const encoded = encodeURIComponent(JSON.stringify({ author: 'John' })); + const html = `<p>[[DOCX_INS_START:${encoded}]]text[[DOCX_INS_END:]]</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(0); + }); + + it('should handle special characters in author name', () => { + const payload = { id: 'ins-1', author: 'José García & Co. <test>' }; + const html = `<p>${buildInsertionToken(payload, 'start')}text${buildInsertionToken(payload, 'end')}</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(1); + expect(result.changes[0].author).toBe('José García & Co. <test>'); + }); + + it('should handle Unicode in author name', () => { + const payload = { id: 'ins-1', author: '田中太郎' }; + const html = `<p>${buildInsertionToken(payload, 'start')}text${buildInsertionToken(payload, 'end')}</p>`; + + const result = parseDocxTrackedChanges(html); + + expect(result.changes).toHaveLength(1); + expect(result.changes[0].author).toBe('田中太郎'); + }); + }); +}); + +describe('parseDocxComments', () => { + describe('basic parsing', () => { + it('should parse a single comment', () => { + const payload = { + id: 'cmt-1', + authorName: 'John Doe', + authorInitials: 'JD', + date: '2024-01-01T10:00:00Z', + text: 'This is a comment', + }; + const html = `<p>${buildCommentToken(payload, 'start')}commented text${buildCommentToken(payload, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.count).toBe(1); + + const comment = result.comments[0]; + expect(comment.id).toBe('cmt-1'); + expect(comment.authorName).toBe('John Doe'); + expect(comment.authorInitials).toBe('JD'); + expect(comment.date).toBe('2024-01-01T10:00:00Z'); + expect(comment.text).toBe('This is a comment'); + expect(comment.hasStartToken).toBe(true); + expect(comment.hasEndToken).toBe(true); + }); + + it('should parse multiple comments', () => { + const html = ` + <p>${buildCommentToken({ id: 'cmt-1', text: 'First' }, 'start')}text1${buildCommentToken({ id: 'cmt-1' }, 'end')}</p> + <p>${buildCommentToken({ id: 'cmt-2', text: 'Second' }, 'start')}text2${buildCommentToken({ id: 'cmt-2' }, 'end')}</p> + `; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(2); + expect(result.count).toBe(2); + expect(result.comments[0].text).toBe('First'); + expect(result.comments[1].text).toBe('Second'); + }); + }); + + describe('partial tokens', () => { + it('should handle comment with only start token', () => { + const payload = { id: 'cmt-1', text: 'Point comment' }; + const html = `<p>${buildCommentToken(payload, 'start')}text</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].hasStartToken).toBe(true); + expect(result.comments[0].hasEndToken).toBe(false); + expect(result.comments[0].text).toBe('Point comment'); + }); + + it('should handle comment with only end token', () => { + const html = `<p>text${buildCommentToken({ id: 'cmt-1' }, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].hasStartToken).toBe(false); + expect(result.comments[0].hasEndToken).toBe(true); + }); + + it('should merge start and end tokens for same comment', () => { + const payload = { id: 'cmt-1', authorName: 'John', text: 'Comment text' }; + const html = `<p>${buildCommentToken(payload, 'start')}text${buildCommentToken({ id: 'cmt-1' }, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].authorName).toBe('John'); + expect(result.comments[0].text).toBe('Comment text'); + expect(result.comments[0].hasStartToken).toBe(true); + expect(result.comments[0].hasEndToken).toBe(true); + }); + }); + + describe('edge cases', () => { + it('should return empty result for text without tokens', () => { + const result = parseDocxComments('<p>plain text</p>'); + + expect(result.comments).toHaveLength(0); + expect(result.count).toBe(0); + }); + + it('should skip malformed tokens', () => { + const html = + '<p>[[DOCX_CMT_START:not-valid-json]]text[[DOCX_CMT_END:id]]</p>'; + + const result = parseDocxComments(html); + + // End token still creates a comment (it's just an ID) + expect(result.comments).toHaveLength(1); + expect(result.comments[0].id).toBe('id'); + }); + + it('should handle special characters in comment text', () => { + const payload = { + id: 'cmt-1', + text: 'Comment with <tags> & special chars', + }; + const html = `<p>${buildCommentToken(payload, 'start')}text${buildCommentToken(payload, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].text).toBe( + 'Comment with <tags> & special chars' + ); + }); + + it('should handle Unicode in comment text', () => { + const payload = { + id: 'cmt-1', + text: 'Comment with emoji 🎉 and CJK 日本語', + }; + const html = `<p>${buildCommentToken(payload, 'start')}text${buildCommentToken(payload, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].text).toBe( + 'Comment with emoji 🎉 and CJK 日本語' + ); + }); + + it('should handle empty comment text', () => { + const payload = { id: 'cmt-1', authorName: 'John' }; + const html = `<p>${buildCommentToken(payload, 'start')}text${buildCommentToken(payload, 'end')}</p>`; + + const result = parseDocxComments(html); + + expect(result.comments).toHaveLength(1); + expect(result.comments[0].text).toBeUndefined(); + }); + }); +}); + +describe('parseDocxTracking', () => { + it('should parse both tracked changes and comments', () => { + const html = ` + <p>${buildInsertionToken({ id: 'ins-1', author: 'Alice' }, 'start')}added${buildInsertionToken({ id: 'ins-1' }, 'end')}</p> + <p>${buildDeletionToken({ id: 'del-1', author: 'Bob' }, 'start')}removed${buildDeletionToken({ id: 'del-1' }, 'end')}</p> + <p>${buildCommentToken({ id: 'cmt-1', text: 'Review this' }, 'start')}text${buildCommentToken({ id: 'cmt-1' }, 'end')}</p> + `; + + const result = parseDocxTracking(html); + + expect(result.hasTracking).toBe(true); + expect(result.trackedChanges.insertionCount).toBe(1); + expect(result.trackedChanges.deletionCount).toBe(1); + expect(result.comments.count).toBe(1); + }); + + it('should return hasTracking false for plain text', () => { + const result = parseDocxTracking('<p>plain text</p>'); + + expect(result.hasTracking).toBe(false); + expect(result.trackedChanges.changes).toHaveLength(0); + expect(result.comments.comments).toHaveLength(0); + }); + + it('should set hasTracking true with only insertions', () => { + const html = `<p>${buildInsertionToken({ id: 'ins-1' }, 'start')}text${buildInsertionToken({ id: 'ins-1' }, 'end')}</p>`; + + const result = parseDocxTracking(html); + + expect(result.hasTracking).toBe(true); + }); + + it('should set hasTracking true with only comments', () => { + const html = `<p>${buildCommentToken({ id: 'cmt-1' }, 'start')}text${buildCommentToken({ id: 'cmt-1' }, 'end')}</p>`; + + const result = parseDocxTracking(html); + + expect(result.hasTracking).toBe(true); + }); +}); + +describe('stripDocxTrackingTokens', () => { + it('should remove insertion tokens', () => { + const html = `<p>before ${buildInsertionToken({ id: 'ins-1' }, 'start')}inserted${buildInsertionToken({ id: 'ins-1' }, 'end')} after</p>`; + + const result = stripDocxTrackingTokens(html); + + expect(result).toBe('<p>before inserted after</p>'); + expect(result).not.toContain('[[DOCX_'); + }); + + it('should remove deletion tokens', () => { + const html = `<p>before ${buildDeletionToken({ id: 'del-1' }, 'start')}deleted${buildDeletionToken({ id: 'del-1' }, 'end')} after</p>`; + + const result = stripDocxTrackingTokens(html); + + expect(result).toBe('<p>before deleted after</p>'); + expect(result).not.toContain('[[DOCX_'); + }); + + it('should remove comment tokens', () => { + const html = `<p>before ${buildCommentToken({ id: 'cmt-1', text: 'Comment' }, 'start')}commented${buildCommentToken({ id: 'cmt-1' }, 'end')} after</p>`; + + const result = stripDocxTrackingTokens(html); + + expect(result).toBe('<p>before commented after</p>'); + expect(result).not.toContain('[[DOCX_'); + }); + + it('should remove all token types together', () => { + const html = ` + <p>${buildInsertionToken({ id: 'ins-1' }, 'start')}A${buildInsertionToken({ id: 'ins-1' }, 'end')}</p> + <p>${buildDeletionToken({ id: 'del-1' }, 'start')}B${buildDeletionToken({ id: 'del-1' }, 'end')}</p> + <p>${buildCommentToken({ id: 'cmt-1' }, 'start')}C${buildCommentToken({ id: 'cmt-1' }, 'end')}</p> + `; + + const result = stripDocxTrackingTokens(html); + + expect(result).not.toContain('[[DOCX_'); + expect(result).toContain('A'); + expect(result).toContain('B'); + expect(result).toContain('C'); + }); + + it('should return unchanged text if no tokens', () => { + const html = '<p>plain text</p>'; + + const result = stripDocxTrackingTokens(html); + + expect(result).toBe(html); + }); + + it('should handle empty string', () => { + expect(stripDocxTrackingTokens('')).toBe(''); + }); +}); diff --git a/packages/docx-io/src/lib/preprocessMammothHtml.ts b/packages/docx-io/src/lib/preprocessMammothHtml.ts deleted file mode 100644 index 94a75ac4bb..0000000000 --- a/packages/docx-io/src/lib/preprocessMammothHtml.ts +++ /dev/null @@ -1,127 +0,0 @@ -import type { DocxComment } from './types'; - -const DOCX_COMMENT_REF_TOKEN_PREFIX = '[[DOCX_COMMENT_REF:'; -const DOCX_COMMENT_REF_TOKEN_SUFFIX = ']]'; - -// Top-level regex patterns for performance -const COMMENT_ID_REGEX = /^comment-/; -const COMMENT_REF_ID_REGEX = /^comment-ref-/; -const ARROW_SUFFIX_REGEX = /↑\s*$/; - -export type PreprocessMammothHtmlResult = { - /** Processed HTML with comment tokens */ - html: string; - /** Map of comment ID to comment text */ - commentById: Map<string, string>; - /** Ordered list of comment IDs as they appear in document */ - commentIds: string[]; -}; - -/** - * Preprocess mammoth HTML output to extract and tokenize comments. - * - * Mammoth converts DOCX comments to: - * - `<dl>` elements containing comment definitions - * - `<a id="comment-ref-{id}">` anchors marking comment locations - * - * This function: - * 1. Extracts comment text from `<dl>` elements - * 2. Replaces comment anchors with tokens `[[DOCX_COMMENT_REF:id]]` - * 3. Returns the processed HTML and comment data - */ -export function preprocessMammothHtml( - html: string -): PreprocessMammothHtmlResult { - const doc = new DOMParser().parseFromString(html, 'text/html'); - const commentById = new Map<string, string>(); - - // Extract comments from <dl> elements - for (const dl of Array.from(doc.querySelectorAll('dl'))) { - if (!dl.querySelector('dt[id^="comment-"]')) continue; - - const dtNodes = Array.from(dl.querySelectorAll('dt[id^="comment-"]')); - - for (const dt of dtNodes) { - const dtId = dt.getAttribute('id') ?? ''; - const commentId = dtId.replace(COMMENT_ID_REGEX, ''); - - if (!commentId) continue; - - const dd = dt.nextElementSibling; - - if (!dd || dd.tagName !== 'DD') continue; - - const ddClone = dd.cloneNode(true) as HTMLElement; - - // Remove back-reference links - for (const a of Array.from( - ddClone.querySelectorAll('a[href^="#comment-ref-"]') - )) { - a.remove(); - } - - let text = (ddClone.textContent ?? '').replaceAll(/\s+/g, ' ').trim(); - text = text.replace(ARROW_SUFFIX_REGEX, '').trim(); - - commentById.set(commentId, text); - } - - dl.remove(); - } - - // Replace comment anchors with tokens - const seen = new Set<string>(); - const commentIds: string[] = []; - - for (const a of Array.from(doc.querySelectorAll('a[id^="comment-ref-"]'))) { - const aId = a.getAttribute('id') ?? ''; - const commentId = aId.replace(COMMENT_REF_ID_REGEX, ''); - - if (!commentId) continue; - - if (!seen.has(commentId)) { - seen.add(commentId); - commentIds.push(commentId); - } - - const token = `${DOCX_COMMENT_REF_TOKEN_PREFIX}${commentId}${DOCX_COMMENT_REF_TOKEN_SUFFIX}`; - const textNode = doc.createTextNode(token); - const parent = a.parentElement; - - if (parent?.tagName === 'SUP' && parent.childNodes.length === 1) { - parent.replaceWith(textNode); - } else { - a.replaceWith(textNode); - } - } - - return { commentById, commentIds, html: doc.body.innerHTML }; -} - -/** - * Extract comments from preprocessed result. - */ -export function extractComments( - commentById: Map<string, string>, - commentIds: string[] -): DocxComment[] { - return commentIds.map((id) => ({ - id, - text: commentById.get(id) ?? '', - })); -} - -/** Get the comment token prefix for searching in editor */ -export function getCommentTokenPrefix(): string { - return DOCX_COMMENT_REF_TOKEN_PREFIX; -} - -/** Get the comment token suffix for searching in editor */ -export function getCommentTokenSuffix(): string { - return DOCX_COMMENT_REF_TOKEN_SUFFIX; -} - -/** Build a comment token from ID */ -export function buildCommentToken(commentId: string): string { - return `${DOCX_COMMENT_REF_TOKEN_PREFIX}${commentId}${DOCX_COMMENT_REF_TOKEN_SUFFIX}`; -} diff --git a/packages/docx-io/src/lib/searchRange.spec.ts b/packages/docx-io/src/lib/searchRange.spec.ts new file mode 100644 index 0000000000..e83668490b --- /dev/null +++ b/packages/docx-io/src/lib/searchRange.spec.ts @@ -0,0 +1,551 @@ +import { describe, expect, it } from 'bun:test'; + +import { + createSearchRangeFn, + isElement, + isText, + nodeString, + searchRange, + searchRanges, + traverseTextNodes, + type Descendant, + type MatchFn, + type NodeEntry, + type Path, + type SearchEditor, + type TElement, + type TRange, +} from './searchRange'; + +// Helper to create a mock editor +function createMockEditor(children: Descendant[]): SearchEditor { + return { + children, + api: { + *nodes<T extends Descendant>(options: { + at: Path | TRange | null; + match?: MatchFn; + }): Iterable<NodeEntry<T>> { + const match = + options.match ?? ((_n: Descendant, p: Path) => p.length === 1); + function* traverse( + nodes: Descendant[], + basePath: Path + ): Generator<NodeEntry<T>> { + for (const [index, node] of nodes.entries()) { + const path = [...basePath, index]; + if (match(node, path)) { + yield [node as T, path]; + } + if (isElement(node)) { + yield* traverse(node.children, path); + } + } + } + yield* traverse(children, []); + }, + isVoid: () => false, + }, + }; +} + +describe('searchRange', () => { + describe('utility functions', () => { + describe('isElement', () => { + it('returns true for elements with children', () => { + expect(isElement({ children: [], type: 'p' })).toBe(true); + }); + + it('returns false for text nodes', () => { + expect(isElement({ text: 'hello' })).toBe(false); + }); + + it('returns false for nodes without children array', () => { + expect(isElement({ type: 'void' })).toBe(false); + }); + }); + + describe('isText', () => { + it('returns true for text nodes', () => { + expect(isText({ text: 'hello' })).toBe(true); + }); + + it('returns true for empty text', () => { + expect(isText({ text: '' })).toBe(true); + }); + + it('returns false for elements', () => { + expect(isText({ children: [] })).toBe(false); + }); + }); + + describe('nodeString', () => { + it('returns text content for text nodes', () => { + expect(nodeString({ text: 'hello' })).toBe('hello'); + }); + + it('returns empty string for empty text', () => { + expect(nodeString({ text: '' })).toBe(''); + }); + + it('concatenates children text for elements', () => { + const element: TElement = { + children: [{ text: 'hello ' }, { text: 'world' }], + type: 'p', + }; + expect(nodeString(element)).toBe('hello world'); + }); + + it('handles nested elements', () => { + const element: TElement = { + children: [ + { text: 'start ' }, + { + children: [{ text: 'middle' }], + type: 'span', + } as TElement, + { text: ' end' }, + ], + type: 'p', + }; + expect(nodeString(element)).toBe('start middle end'); + }); + + it('returns empty string for unknown node types', () => { + expect(nodeString({ type: 'unknown' })).toBe(''); + }); + }); + + describe('traverseTextNodes', () => { + it('calls callback for each text node', () => { + const nodes: Descendant[] = [ + { text: 'a' }, + { text: 'b' }, + { text: 'c' }, + ]; + const visited: string[] = []; + traverseTextNodes(nodes, (node) => { + if (isText(node)) visited.push(node.text); + }); + expect(visited).toEqual(['a', 'b', 'c']); + }); + + it('provides correct paths', () => { + const nodes: Descendant[] = [{ text: 'a' }, { text: 'b' }]; + const paths: number[][] = []; + traverseTextNodes(nodes, (_node, path) => { + paths.push(path); + }); + expect(paths).toEqual([[0], [1]]); + }); + + it('traverses nested elements', () => { + const nodes: Descendant[] = [ + { + children: [{ text: 'nested' }], + type: 'span', + } as TElement, + ]; + const visited: string[] = []; + traverseTextNodes(nodes, (node) => { + if (isText(node)) visited.push(node.text); + }); + expect(visited).toEqual(['nested']); + }); + + it('returns true when callback returns true (early exit)', () => { + const nodes: Descendant[] = [ + { text: 'a' }, + { text: 'b' }, + { text: 'c' }, + ]; + const visited: string[] = []; + const result = traverseTextNodes(nodes, (node) => { + if (isText(node)) { + visited.push(node.text); + if (node.text === 'b') return true; + } + }); + expect(result).toBe(true); + expect(visited).toEqual(['a', 'b']); + }); + + it('returns false when traversal completes', () => { + const nodes: Descendant[] = [{ text: 'a' }]; + const result = traverseTextNodes(nodes, () => {}); + expect(result).toBe(false); + }); + + it('handles deeply nested structures', () => { + const nodes: Descendant[] = [ + { + children: [ + { + children: [ + { + children: [{ text: 'deep' }], + type: 'c', + } as TElement, + ], + type: 'b', + } as TElement, + ], + type: 'a', + } as TElement, + ]; + const paths: number[][] = []; + traverseTextNodes(nodes, (_node, path) => { + paths.push(path); + }); + expect(paths).toEqual([[0, 0, 0, 0]]); + }); + }); + }); + + describe('searchRange', () => { + it('returns null for empty search string', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello' }], type: 'p' }, + ]); + expect(searchRange(editor, '')).toBeNull(); + }); + + it('returns null for empty array search', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello' }], type: 'p' }, + ]); + expect(searchRange(editor, ['', 'end'])).toBeNull(); + expect(searchRange(editor, ['start', ''])).toBeNull(); + }); + + it('finds simple string in single text node', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello world' }], type: 'p' }, + ]); + const result = searchRange(editor, 'world'); + expect(result).not.toBeNull(); + expect(result?.anchor.path).toEqual([0, 0]); + expect(result?.anchor.offset).toBe(6); + expect(result?.focus.path).toEqual([0, 0]); + expect(result?.focus.offset).toBe(11); + }); + + it('is case insensitive', () => { + const editor = createMockEditor([ + { children: [{ text: 'Hello World' }], type: 'p' }, + ]); + const result = searchRange(editor, 'HELLO'); + expect(result).not.toBeNull(); + expect(result?.anchor.offset).toBe(0); + expect(result?.focus.offset).toBe(5); + }); + + it('returns null when string not found', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello world' }], type: 'p' }, + ]); + expect(searchRange(editor, 'xyz')).toBeNull(); + }); + + it('finds string spanning multiple text nodes', () => { + const editor = createMockEditor([ + { + children: [{ text: 'hel' }, { text: 'lo wor' }, { text: 'ld' }], + type: 'p', + }, + ]); + const result = searchRange(editor, 'hello world'); + expect(result).not.toBeNull(); + expect(result?.anchor.path).toEqual([0, 0]); + expect(result?.anchor.offset).toBe(0); + expect(result?.focus.path).toEqual([0, 2]); + expect(result?.focus.offset).toBe(2); + }); + + it('finds first occurrence', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello hello hello' }], type: 'p' }, + ]); + const result = searchRange(editor, 'hello'); + expect(result).not.toBeNull(); + expect(result?.anchor.offset).toBe(0); + expect(result?.focus.offset).toBe(5); + }); + + it('finds token pair range', () => { + const editor = createMockEditor([ + { + children: [{ text: '[[START]]content here[[END]]' }], + type: 'p', + }, + ]); + const result = searchRange(editor, ['[[START]]', '[[END]]']); + expect(result).not.toBeNull(); + expect(result?.anchor.offset).toBe(0); + expect(result?.focus.offset).toBe(28); // Full range including tokens + }); + + it('handles DOCX tracking tokens', () => { + const startToken = '[[DOCX_INS_START:test]]'; + const endToken = '[[DOCX_INS_END:test]]'; + const editor = createMockEditor([ + { + children: [{ text: `${startToken}inserted text${endToken}` }], + type: 'p', + }, + ]); + const result = searchRange(editor, [startToken, endToken]); + expect(result).not.toBeNull(); + }); + + it('searches across multiple paragraphs', () => { + const editor = createMockEditor([ + { children: [{ text: 'first paragraph' }], type: 'p' }, + { children: [{ text: 'second paragraph with target' }], type: 'p' }, + ]); + const result = searchRange(editor, 'target'); + expect(result).not.toBeNull(); + expect(result?.anchor.path[0]).toBe(1); // Second paragraph + }); + + it('uses custom match function', () => { + const editor = createMockEditor([ + { children: [{ text: 'skip this' }], type: 'heading' }, + { children: [{ text: 'search this' }], type: 'p' }, + ]); + const result = searchRange(editor, 'this', { + match: (node) => (node as TElement).type === 'p', + }); + expect(result).not.toBeNull(); + // Should find in second element (the paragraph) + }); + + it('handles editor without api.nodes', () => { + const editor: SearchEditor = { + children: [{ children: [{ text: 'hello world' }], type: 'p' }], + api: { + nodes: undefined as unknown as SearchEditor['api']['nodes'], + }, + }; + const result = searchRange(editor, 'world'); + expect(result).not.toBeNull(); + }); + + it('skips void elements when isVoid is provided', () => { + const children: Descendant[] = [ + { + children: [ + { text: 'before' }, + { type: 'image', children: [{ text: '' }] } as TElement, + { text: 'after' }, + ], + type: 'p', + }, + ]; + const editor: SearchEditor = { + children, + api: { + *nodes<T extends Descendant>(options: { + at: Path | TRange | null; + match?: MatchFn; + }): Iterable<NodeEntry<T>> { + const match = + options.match ?? ((_n: Descendant, p: Path) => p.length === 1); + for (const [i, node] of children.entries()) { + if (match(node, [i])) yield [node as T, [i]]; + } + }, + isVoid: (node) => (node as TElement).type === 'image', + }, + }; + // Should still find 'after' text + const result = searchRange(editor, 'after'); + expect(result).not.toBeNull(); + }); + }); + + describe('searchRanges', () => { + it('returns empty array for empty search', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello' }], type: 'p' }, + ]); + expect(searchRanges(editor, '')).toEqual([]); + expect(searchRanges(editor, ['', 'end'])).toEqual([]); + expect(searchRanges(editor, ['start', ''])).toEqual([]); + }); + + it('finds all occurrences of string', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello hello hello' }], type: 'p' }, + ]); + const results = searchRanges(editor, 'hello'); + expect(results).toHaveLength(3); + expect(results[0].anchor.offset).toBe(0); + expect(results[1].anchor.offset).toBe(6); + expect(results[2].anchor.offset).toBe(12); + }); + + it('finds all occurrences across paragraphs', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello world' }], type: 'p' }, + { children: [{ text: 'hello again' }], type: 'p' }, + ]); + const results = searchRanges(editor, 'hello'); + expect(results).toHaveLength(2); + expect(results[0].anchor.path[0]).toBe(0); + expect(results[1].anchor.path[0]).toBe(1); + }); + + it('finds all token pair ranges', () => { + const editor = createMockEditor([ + { + children: [{ text: '[[A]]x[[/A]] and [[A]]y[[/A]]' }], + type: 'p', + }, + ]); + const results = searchRanges(editor, ['[[A]]', '[[/A]]']); + expect(results).toHaveLength(2); + }); + + it('returns empty array when not found', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello' }], type: 'p' }, + ]); + expect(searchRanges(editor, 'xyz')).toEqual([]); + }); + + it('handles editor without api.nodes', () => { + const editor: SearchEditor = { + children: [{ children: [{ text: 'hello hello' }], type: 'p' }], + api: { + nodes: undefined as unknown as SearchEditor['api']['nodes'], + }, + }; + const results = searchRanges(editor, 'hello'); + expect(results).toHaveLength(2); + }); + + it('uses custom match function', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello in heading' }], type: 'heading' }, + { children: [{ text: 'hello in paragraph' }], type: 'p' }, + ]); + const results = searchRanges(editor, 'hello', { + match: (node) => (node as TElement).type === 'p', + }); + // Should only find in paragraph + expect(results).toHaveLength(1); + }); + }); + + describe('createSearchRangeFn', () => { + it('creates a bound search function', () => { + const editor = createMockEditor([ + { children: [{ text: 'hello world' }], type: 'p' }, + ]); + const boundSearch = createSearchRangeFn(editor); + + const result = boundSearch('world'); + expect(result).not.toBeNull(); + expect(result?.anchor.offset).toBe(6); + }); + + it('works with token pairs', () => { + const editor = createMockEditor([ + { children: [{ text: '[[START]]content[[END]]' }], type: 'p' }, + ]); + const boundSearch = createSearchRangeFn(editor); + + const result = boundSearch(['[[START]]', '[[END]]']); + expect(result).not.toBeNull(); + }); + }); + + describe('integration with DOCX tracking tokens', () => { + it('finds insertion token pairs', () => { + const payload = encodeURIComponent( + JSON.stringify({ id: 'ins-1', author: 'John' }) + ); + const startToken = `[[DOCX_INS_START:${payload}]]`; + const endToken = '[[DOCX_INS_END:ins-1]]'; + + const editor = createMockEditor([ + { + children: [ + { + text: `Some text ${startToken}inserted content${endToken} more text`, + }, + ], + type: 'p', + }, + ]); + + const result = searchRange(editor, [startToken, endToken]); + expect(result).not.toBeNull(); + }); + + it('finds deletion token pairs', () => { + const payload = encodeURIComponent( + JSON.stringify({ id: 'del-1', author: 'Jane' }) + ); + const startToken = `[[DOCX_DEL_START:${payload}]]`; + const endToken = '[[DOCX_DEL_END:del-1]]'; + + const editor = createMockEditor([ + { + children: [{ text: `${startToken}deleted content${endToken}` }], + type: 'p', + }, + ]); + + const result = searchRange(editor, [startToken, endToken]); + expect(result).not.toBeNull(); + }); + + it('finds comment token pairs', () => { + const payload = encodeURIComponent( + JSON.stringify({ + id: 'cmt-1', + authorName: 'Bob', + text: 'Comment text', + }) + ); + const startToken = `[[DOCX_CMT_START:${payload}]]`; + const endToken = '[[DOCX_CMT_END:cmt-1]]'; + + const editor = createMockEditor([ + { + children: [{ text: `${startToken}commented text${endToken}` }], + type: 'p', + }, + ]); + + const result = searchRange(editor, [startToken, endToken]); + expect(result).not.toBeNull(); + }); + + it('finds multiple tracking tokens in document', () => { + const ins1Start = '[[DOCX_INS_START:ins1]]'; + const ins1End = '[[DOCX_INS_END:ins1]]'; + const del1Start = '[[DOCX_DEL_START:del1]]'; + const del1End = '[[DOCX_DEL_END:del1]]'; + + const editor = createMockEditor([ + { + children: [ + { + text: `${ins1Start}added${ins1End} and ${del1Start}removed${del1End}`, + }, + ], + type: 'p', + }, + ]); + + const insResult = searchRange(editor, [ins1Start, ins1End]); + const delResult = searchRange(editor, [del1Start, del1End]); + + expect(insResult).not.toBeNull(); + expect(delResult).not.toBeNull(); + }); + }); +}); diff --git a/packages/docx-io/src/lib/searchRange.ts b/packages/docx-io/src/lib/searchRange.ts new file mode 100644 index 0000000000..df6366d96f --- /dev/null +++ b/packages/docx-io/src/lib/searchRange.ts @@ -0,0 +1,463 @@ +/** + * Search Utilities for DOCX Tracked Changes + * + * This module provides utilities for searching text within a Slate/Plate editor. + * It's used by applyDocxTracking to locate tracking tokens in the editor after + * deserialization from HTML. + * + * Key features: + * - Case-insensitive search + * - Cross-node text search (finds text spanning multiple nodes) + * - Start/end token pair search for ranges + * - Void element handling + */ + +// ============================================================================ +// Types +// ============================================================================ + +/** Path type (array of indices) */ +export type Path = number[]; + +/** Point in the document */ +export type Point = { + path: Path; + offset: number; +}; + +/** Range in the document */ +export type TRange = { + anchor: Point; + focus: Point; +}; + +/** Descendant node (element or text) */ +export type Descendant = { + children?: Descendant[]; + text?: string; + type?: string; + [key: string]: unknown; +}; + +/** Element node with children */ +export interface TElement extends Descendant { + children: Descendant[]; +} + +/** Text node */ +export interface TText extends Descendant { + text: string; +} + +/** Node entry tuple */ +export type NodeEntry<T = Descendant> = [T, Path]; + +/** Match function for filtering nodes */ +export type MatchFn = (node: Descendant, path: Path) => boolean; + +/** Editor interface for search operations */ +export type SearchEditor = { + /** Get nodes matching criteria */ + api: { + nodes: <T extends Descendant>(options: { + at: Path | TRange | null; + match?: MatchFn; + }) => Iterable<NodeEntry<T>>; + /** Check if node is void */ + isVoid?: (node: Descendant) => boolean; + }; + /** Editor children (root nodes) */ + children: Descendant[]; +}; + +/** Options for search operations */ +export type SearchOptions = { + /** Starting point for search */ + from?: Point; + /** Match function to filter which nodes to search in */ + match?: MatchFn; +}; + +// ============================================================================ +// Utility Functions +// ============================================================================ + +/** + * Check if a node is an element (has children array). + */ +export function isElement(node: Descendant): node is TElement { + return Array.isArray((node as TElement).children); +} + +/** + * Check if a node is a text node (has text string). + */ +export function isText(node: Descendant): node is TText { + return typeof (node as TText).text === 'string'; +} + +/** + * Get the string content of a node and all its descendants. + */ +export function nodeString(node: Descendant): string { + if (isText(node)) { + return node.text; + } + if (isElement(node)) { + return node.children.map(nodeString).join(''); + } + + return ''; +} + +/** + * Traverse all text nodes in a tree, calling callback for each. + * Returns true if callback returned true (early exit). + */ +export function traverseTextNodes( + nodes: Descendant[], + callback: (node: Descendant, path: Path) => boolean | void, + basePath: Path = [] +): boolean { + for (const [index, childNode] of nodes.entries()) { + const childPath = [...basePath, index]; + + // If the node is an element and has children, traverse them + if (isElement(childNode)) { + if (traverseTextNodes(childNode.children, callback, childPath)) { + return true; + } + + continue; + } + // Execute the callback for each leaf node + if (callback(childNode, childPath)) { + return true; + } + } + + return false; +} + +/** + * Default match function: match top-level block nodes. + */ +function defaultMatch(_node: Descendant, path: Path): boolean { + return path.length === 1; +} + +/** + * Simple nodes iterator for editor children. + */ +function* iterateNodes<T extends Descendant>( + editor: SearchEditor, + options: { at: Path | TRange | null; match?: MatchFn } +): Iterable<NodeEntry<T>> { + const match = options.match ?? defaultMatch; + + function* traverse( + nodes: Descendant[], + basePath: Path + ): Iterable<NodeEntry<T>> { + for (const [index, node] of nodes.entries()) { + const path = [...basePath, index]; + + if (match(node, path)) { + yield [node as T, path]; + } + + if (isElement(node)) { + yield* traverse(node.children, path); + } + } + } + + yield* traverse(editor.children, []); +} + +// ============================================================================ +// Search Functions +// ============================================================================ + +/** + * Search for a string (or start/end token pair) in the editor. + * + * @param editor - The editor to search in + * @param search - String to find, or [startToken, endToken] pair + * @param options - Search options + * @returns The range where the text was found, or null + * + * @example + * ```ts + * // Find a simple string + * const range = searchRange(editor, 'hello world'); + * + * // Find a range between two tokens + * const tokenRange = searchRange(editor, [ + * '[[DOCX_INS_START:...]]', + * '[[DOCX_INS_END:...]]' + * ]); + * ``` + */ +export function searchRange( + editor: SearchEditor, + search: string | [string, string], + options: SearchOptions = {} +): TRange | null { + const { match } = options; + + // Validate search input + if (Array.isArray(search)) { + if (search[0].length === 0 || search[1].length === 0) { + return null; + } + } else if (search.length === 0) { + return null; + } + + const [startSearch, endSearch] = Array.isArray(search) + ? search.map((s) => s.toLowerCase()) + : [search.toLowerCase(), '']; + + // Get nodes to search through + const entries = Array.from( + editor.api?.nodes + ? editor.api.nodes<TElement>({ at: [], match: match ?? defaultMatch }) + : iterateNodes<TElement>(editor, { at: [], match: match ?? defaultMatch }) + ); + + for (const [node, path] of entries) { + if (!isElement(node)) continue; + + const combinedText = nodeString(node).toLowerCase(); + let searchIndex = combinedText.indexOf(startSearch); + + while (searchIndex !== -1) { + let globalOffset = 0; + let anchorOffset: number | undefined; + let focusOffset: number | undefined; + let startPath: Path | undefined; + let endPath: Path | undefined; + + traverseTextNodes( + node.children, + (childNode, childPath) => { + // Skip void elements + if (editor.api?.isVoid?.(childNode)) return; + + const textLength = isText(childNode) ? childNode.text.length : 0; + const newGlobalOffset = globalOffset + textLength; + + // Find start of search + if (startPath === undefined && newGlobalOffset > searchIndex) { + startPath = childPath; + anchorOffset = searchIndex - globalOffset; + } + + // Find end of search + if ( + startPath !== undefined && + newGlobalOffset >= searchIndex + startSearch.length + ) { + // For single string search, end is at startSearch end + // For pair search, find where endSearch ends + if (endSearch === '') { + endPath = childPath; + focusOffset = searchIndex + startSearch.length - globalOffset; + + return true; // Found complete range + } + const endSearchIndex = combinedText.indexOf( + endSearch, + searchIndex + startSearch.length + ); + + if (endSearchIndex !== -1) { + // Need to find the node containing the end of endSearch + const endPosition = endSearchIndex + endSearch.length; + + if (newGlobalOffset >= endPosition) { + endPath = childPath; + focusOffset = endPosition - globalOffset; + + return true; // Found complete range + } + } + } + + globalOffset = newGlobalOffset; + }, + path + ); + + if ( + startPath && + endPath && + anchorOffset !== undefined && + focusOffset !== undefined + ) { + return { + anchor: { path: startPath, offset: anchorOffset }, + focus: { path: endPath, offset: focusOffset }, + }; + } + + searchIndex = combinedText.indexOf(startSearch, searchIndex + 1); + } + } + + return null; +} + +/** + * Search for all occurrences of a string (or start/end token pair) in the editor. + * + * @param editor - The editor to search in + * @param search - String to find, or [startToken, endToken] pair + * @param options - Search options + * @returns Array of ranges where the text was found + * + * @example + * ```ts + * // Find all occurrences of a string + * const ranges = searchRanges(editor, 'hello'); + * + * // Find all ranges between token pairs + * const tokenRanges = searchRanges(editor, [ + * '[[DOCX_INS_START:', + * ']]' + * ]); + * ``` + */ +export function searchRanges( + editor: SearchEditor, + search: string | [string, string], + options: SearchOptions = {} +): TRange[] { + const { match } = options; + const ranges: TRange[] = []; + + // Validate search input + if (Array.isArray(search)) { + if (search[0].length === 0 || search[1].length === 0) { + return ranges; + } + } else if (search.length === 0) { + return ranges; + } + + const [startSearch, endSearch] = Array.isArray(search) + ? search.map((s) => s.toLowerCase()) + : [search.toLowerCase(), '']; + + // Get nodes to search through + const entries = Array.from( + editor.api?.nodes + ? editor.api.nodes<TElement>({ at: [], match: match ?? defaultMatch }) + : iterateNodes<TElement>(editor, { at: [], match: match ?? defaultMatch }) + ); + + for (const [node, path] of entries) { + if (!isElement(node)) continue; + + const combinedText = nodeString(node).toLowerCase(); + let searchIndex = combinedText.indexOf(startSearch); + + while (searchIndex !== -1) { + let globalOffset = 0; + let anchorOffset: number | undefined; + let focusOffset: number | undefined; + let startPath: Path | undefined; + let endPath: Path | undefined; + + traverseTextNodes( + node.children, + (childNode, childPath) => { + // Skip void elements + if (editor.api?.isVoid?.(childNode)) return; + + const textLength = isText(childNode) ? childNode.text.length : 0; + const newGlobalOffset = globalOffset + textLength; + + // Find start of search + if (startPath === undefined && newGlobalOffset > searchIndex) { + startPath = childPath; + anchorOffset = searchIndex - globalOffset; + } + + // Find end of search + if ( + startPath !== undefined && + newGlobalOffset >= searchIndex + startSearch.length + ) { + if (endSearch === '') { + endPath = childPath; + focusOffset = searchIndex + startSearch.length - globalOffset; + + return true; + } + const endSearchIndex = combinedText.indexOf( + endSearch, + searchIndex + startSearch.length + ); + + if (endSearchIndex !== -1) { + const endPosition = endSearchIndex + endSearch.length; + + if (newGlobalOffset >= endPosition) { + endPath = childPath; + focusOffset = endPosition - globalOffset; + + return true; + } + } + } + + globalOffset = newGlobalOffset; + }, + path + ); + + if ( + startPath && + endPath && + anchorOffset !== undefined && + focusOffset !== undefined + ) { + ranges.push({ + anchor: { path: startPath, offset: anchorOffset }, + focus: { path: endPath, offset: focusOffset }, + }); + } + + searchIndex = combinedText.indexOf(startSearch, searchIndex + 1); + } + } + + return ranges; +} + +/** + * Create a searchRange function bound to an editor. + * This is a convenience wrapper for use with applyDocxTracking. + * + * @param editor - The editor to create the search function for + * @returns A search function that takes just the search string + * + * @example + * ```ts + * const mySearchRange = createSearchRangeFn(editor); + * + * applyTrackedChangeSuggestions({ + * editor, + * changes, + * searchRange: mySearchRange, + * // ... + * }); + * ``` + */ +export function createSearchRangeFn( + editor: SearchEditor +): (search: string | [string, string]) => TRange | null { + return (search) => searchRange(editor, search); +} diff --git a/packages/docx-io/src/lib/types.ts b/packages/docx-io/src/lib/types.ts index 83b7acdb99..1e4f1056de 100644 --- a/packages/docx-io/src/lib/types.ts +++ b/packages/docx-io/src/lib/types.ts @@ -1,19 +1,65 @@ import type { TNode } from 'platejs'; -/** Comment extracted from DOCX file */ -export type DocxComment = { - /** Comment ID from the DOCX file */ +// ============================================================================ +// Tracked Changes Types +// ============================================================================ + +/** Tracked change (insertion or deletion) from DOCX file */ +export type DocxTrackedChange = { + /** Unique ID for this change */ id: string; + /** Type of change: 'insert' for additions, 'remove' for deletions */ + type: 'insert' | 'remove'; + /** Author who made the change */ + author?: string; + /** Date when the change was made (ISO string) */ + date?: string; + /** The full start token string (for searching in editor) */ + startToken: string; + /** The full end token string (for searching in editor) */ + endToken: string; +}; + +/** Comment with full metadata from DOCX file */ +export type DocxTrackedComment = { + /** Unique ID for this comment */ + id: string; + /** Author display name */ + authorName?: string; + /** Author initials (for Word compatibility) */ + authorInitials?: string; + /** Date when the comment was made (ISO string) */ + date?: string; /** Comment text content */ - text: string; + text?: string; + /** The full start token string (for searching in editor) */ + startToken: string; + /** The full end token string (for searching in editor) */ + endToken: string; + /** Whether the start token was found in HTML */ + hasStartToken: boolean; + /** Whether the end token was found in HTML */ + hasEndToken: boolean; }; -/** Result of importing a DOCX file */ +// ============================================================================ +// Import Result Types +// ============================================================================ + +/** Result of importing a DOCX file with tracking support */ export type ImportDocxResult = { /** Deserialized editor nodes */ nodes: TNode[]; - /** Comments extracted from the DOCX file (not yet applied to editor) */ - comments: DocxComment[]; + /** Tracked changes (insertions and deletions) */ + trackedChanges: DocxTrackedChange[]; + /** Comments with full metadata */ + trackedComments: DocxTrackedComment[]; + /** Number of insertions found */ + insertionCount: number; + /** Number of deletions found */ + deletionCount: number; + /** Whether any tracking tokens were found */ + hasTracking: boolean; /** Warnings from mammoth conversion */ warnings: string[]; }; diff --git a/packages/docx-io/tsconfig.build.json b/packages/docx-io/tsconfig.build.json new file mode 100644 index 0000000000..5db35c04f6 --- /dev/null +++ b/packages/docx-io/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tooling/config/tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index 06cc4319e4..49a0ddfb4c 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -41,7 +41,7 @@ }, "dependencies": { "@excalidraw/excalidraw": "0.18.0", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "devDependencies": { diff --git a/packages/list-classic/package.json b/packages/list-classic/package.json index bc01fd19c9..40abea4597 100644 --- a/packages/list-classic/package.json +++ b/packages/list-classic/package.json @@ -40,7 +40,7 @@ "typecheck": "plate-pkg p:typecheck" }, "dependencies": { - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "devDependencies": { diff --git a/packages/utils/package.json b/packages/utils/package.json index 989fc9d3d5..247d1447c0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -45,7 +45,7 @@ "@udecode/react-utils": "^52.0.11", "@udecode/utils": "^52.0.1", "clsx": "^2.1.1", - "lodash": "^4.17.21", + "lodash": "^4.17.23", "react-compiler-runtime": "^1.0.0" }, "peerDependencies": { diff --git a/packages/yjs/src/lib/providers/hocuspocus-provider.ts b/packages/yjs/src/lib/providers/hocuspocus-provider.ts index 1c459529a2..c1f40fa2e4 100644 --- a/packages/yjs/src/lib/providers/hocuspocus-provider.ts +++ b/packages/yjs/src/lib/providers/hocuspocus-provider.ts @@ -87,7 +87,7 @@ export class HocuspocusProviderWrapper implements UnifiedProvider { } })()), // Disable broadcast channel here - we'll manually handle connections - // broadcast: options.broadcast || false, + broadcast: false, onAwarenessChange: options.onAwarenessChange || (() => {}), onConnect: () => { this._isConnected = true; diff --git a/pr-main-docxio-related-files.md b/pr-main-docxio-related-files.md new file mode 100644 index 0000000000..58699d9be1 --- /dev/null +++ b/pr-main-docxio-related-files.md @@ -0,0 +1,291 @@ +# Files Related to docx-io (Direct and Indirect) + +- Direct = files under `packages/docx-io/` +- Indirect = path/content references to `docx-io` or files under `packages/docx/` +- Direct files: **127** +- Indirect files: **151** +- Total unique files: **278** + +## Direct files + +- `packages/docx-io/CHANGELOG.md` +- `packages/docx-io/package.json` +- `packages/docx-io/ralph.yml` +- `packages/docx-io/src/index.ts` +- `packages/docx-io/src/lib/DocxIOPlugin.spec.tsx` +- `packages/docx-io/src/lib/DocxIOPlugin.tsx` +- `packages/docx-io/src/lib/__tests__/block_quotes.spec.tsx` +- `packages/docx-io/src/lib/__tests__/complex-tracking-export.spec.ts` +- `packages/docx-io/src/lib/__tests__/docx-io-demo.spec.ts` +- `packages/docx-io/src/lib/__tests__/export-comment-ids.spec.ts` +- `packages/docx-io/src/lib/__tests__/export-ooxml-comments.spec.ts` +- `packages/docx-io/src/lib/__tests__/export-replies-id.spec.ts` +- `packages/docx-io/src/lib/__tests__/export-replies.spec.ts` +- `packages/docx-io/src/lib/__tests__/headers.spec.tsx` +- `packages/docx-io/src/lib/__tests__/inline_formatting.spec.tsx` +- `packages/docx-io/src/lib/__tests__/links.spec.tsx` +- `packages/docx-io/src/lib/__tests__/lists.spec.tsx` +- `packages/docx-io/src/lib/__tests__/preprocessMammothHtml.spec.ts` +- `packages/docx-io/src/lib/__tests__/roundtrip-audit.spec.ts` +- `packages/docx-io/src/lib/__tests__/roundtrip.spec.tsx` +- `packages/docx-io/src/lib/__tests__/tables.spec.tsx` +- `packages/docx-io/src/lib/__tests__/testDocxImporter.tsx` +- `packages/docx-io/src/lib/__tests__/xml-builder-falsy-ids.spec.ts` +- `packages/docx-io/src/lib/applyDocxTracking.spec.ts` +- `packages/docx-io/src/lib/docx-export-plugin.tsx` +- `packages/docx-io/src/lib/exportComments.ts` +- `packages/docx-io/src/lib/exportDocx.ts` +- `packages/docx-io/src/lib/exportTrackChanges.ts` +- `packages/docx-io/src/lib/html-to-docx/comment-templates.ts` +- `packages/docx-io/src/lib/html-to-docx/constants.ts` +- `packages/docx-io/src/lib/html-to-docx/docx-document.ts` +- `packages/docx-io/src/lib/html-to-docx/helpers/index.ts` +- `packages/docx-io/src/lib/html-to-docx/helpers/render-document-file.ts` +- `packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.spec.ts` +- `packages/docx-io/src/lib/html-to-docx/helpers/xml-builder.ts` +- `packages/docx-io/src/lib/html-to-docx/html-to-docx.spec.ts` +- `packages/docx-io/src/lib/html-to-docx/html-to-docx.ts` +- `packages/docx-io/src/lib/html-to-docx/index.d.ts` +- `packages/docx-io/src/lib/html-to-docx/index.ts` +- `packages/docx-io/src/lib/html-to-docx/namespaces.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/content-types.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/core.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/document-rels.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/document.template.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/font-table.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/generic-rels.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/index.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/numbering.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/rels.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/settings.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/styles.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/theme.ts` +- `packages/docx-io/src/lib/html-to-docx/schemas/web-settings.ts` +- `packages/docx-io/src/lib/html-to-docx/tracking.spec.ts` +- `packages/docx-io/src/lib/html-to-docx/tracking.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/color-conversion.spec.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/color-conversion.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/font-family-conversion.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/image-dimensions.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/image-to-base64.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/index.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/list.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/unit-conversion.spec.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/unit-conversion.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/url.ts` +- `packages/docx-io/src/lib/html-to-docx/utils/vnode.ts` +- `packages/docx-io/src/lib/importComments.ts` +- `packages/docx-io/src/lib/importDocx.ts` +- `packages/docx-io/src/lib/importTrackChanges.ts` +- `packages/docx-io/src/lib/index.ts` +- `packages/docx-io/src/lib/injectDocxTrackingTokens.spec.ts` +- `packages/docx-io/src/lib/mammoth.js/LICENSE` +- `packages/docx-io/src/lib/mammoth.js/browser/docx/files.js` +- `packages/docx-io/src/lib/mammoth.js/browser/unzip.js` +- `packages/docx-io/src/lib/mammoth.js/index.ts` +- `packages/docx-io/src/lib/mammoth.js/lib/document-to-html.js` +- `packages/docx-io/src/lib/mammoth.js/lib/documents.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/body-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/comments-extended-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/comments-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/content-types-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/document-xml-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/docx-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/files.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/notes-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/numbering-xml.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/office-xml-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/relationships-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/style-map.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/styles-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/docx/uris.js` +- `packages/docx-io/src/lib/mammoth.js/lib/html/ast.js` +- `packages/docx-io/src/lib/mammoth.js/lib/html/index.js` +- `packages/docx-io/src/lib/mammoth.js/lib/html/simplify.js` +- `packages/docx-io/src/lib/mammoth.js/lib/images.js` +- `packages/docx-io/src/lib/mammoth.js/lib/index.d.ts` +- `packages/docx-io/src/lib/mammoth.js/lib/index.js` +- `packages/docx-io/src/lib/mammoth.js/lib/index.ts` +- `packages/docx-io/src/lib/mammoth.js/lib/main.js` +- `packages/docx-io/src/lib/mammoth.js/lib/options-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/promises.js` +- `packages/docx-io/src/lib/mammoth.js/lib/raw-text.js` +- `packages/docx-io/src/lib/mammoth.js/lib/results.js` +- `packages/docx-io/src/lib/mammoth.js/lib/style-reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/styles/document-matchers.js` +- `packages/docx-io/src/lib/mammoth.js/lib/styles/html-paths.js` +- `packages/docx-io/src/lib/mammoth.js/lib/styles/parser/tokeniser.js` +- `packages/docx-io/src/lib/mammoth.js/lib/transforms.js` +- `packages/docx-io/src/lib/mammoth.js/lib/underline.js` +- `packages/docx-io/src/lib/mammoth.js/lib/unzip.js` +- `packages/docx-io/src/lib/mammoth.js/lib/writers/html-writer.js` +- `packages/docx-io/src/lib/mammoth.js/lib/writers/index.js` +- `packages/docx-io/src/lib/mammoth.js/lib/writers/markdown-writer.js` +- `packages/docx-io/src/lib/mammoth.js/lib/xml/index.js` +- `packages/docx-io/src/lib/mammoth.js/lib/xml/nodes.js` +- `packages/docx-io/src/lib/mammoth.js/lib/xml/reader.js` +- `packages/docx-io/src/lib/mammoth.js/lib/xml/writer.js` +- `packages/docx-io/src/lib/mammoth.js/lib/xml/xmldom.js` +- `packages/docx-io/src/lib/mammoth.js/lib/zipfile.js` +- `packages/docx-io/src/lib/mammoth.js/mammoth.browser.js` +- `packages/docx-io/src/lib/mammoth.js/package.json` +- `packages/docx-io/src/lib/parseDocxTracking.spec.ts` +- `packages/docx-io/src/lib/searchRange.spec.ts` +- `packages/docx-io/src/lib/searchRange.ts` +- `packages/docx-io/src/lib/types.ts` +- `packages/docx-io/tsconfig.build.json` +- `packages/docx-io/tsconfig.json` + +## Indirect files + +- `PROMPT.md` +- `apps/www/package.json` +- `apps/www/public/r/components-changelog-docs.json` +- `apps/www/public/r/docs.json` +- `apps/www/public/r/docx-docs.json` +- `apps/www/public/r/docx-export-kit.json` +- `apps/www/public/r/docx-io-docs.json` +- `apps/www/public/r/export-toolbar-button.json` +- `apps/www/public/r/import-toolbar-button.json` +- `apps/www/public/r/registry-docs.json` +- `apps/www/public/r/registry.json` +- `apps/www/src/config/docs-plugins.ts` +- `apps/www/src/registry/components/editor/plugins/docx-export-kit.tsx` +- `apps/www/src/registry/registry-kits.ts` +- `apps/www/src/registry/ui/export-toolbar-button.tsx` +- `apps/www/src/registry/ui/import-toolbar-button.tsx` +- `docs/(plugins)/(serializing)/docx-io.cn.mdx` +- `docs/(plugins)/(serializing)/docx-io.mdx` +- `docs/(plugins)/(serializing)/docx.cn.mdx` +- `docs/(plugins)/(serializing)/docx.mdx` +- `docs/components/changelog.cn.mdx` +- `docs/components/changelog.mdx` +- `packages/docx/.npmignore` +- `packages/docx/CHANGELOG.md` +- `packages/docx/README.md` +- `packages/docx/package.json` +- `packages/docx/src/index.ts` +- `packages/docx/src/lib/DocxPlugin.ts` +- `packages/docx/src/lib/__tests__/align.docx` +- `packages/docx/src/lib/__tests__/align.html` +- `packages/docx/src/lib/__tests__/align.spec.tsx` +- `packages/docx/src/lib/__tests__/alternate_document_path.docx` +- `packages/docx/src/lib/__tests__/alternate_document_path.html` +- `packages/docx/src/lib/__tests__/alternate_document_path.spec.tsx` +- `packages/docx/src/lib/__tests__/block_quotes.docx` +- `packages/docx/src/lib/__tests__/block_quotes.html` +- `packages/docx/src/lib/__tests__/block_quotes.spec.tsx` +- `packages/docx/src/lib/__tests__/char_styles.docx` +- `packages/docx/src/lib/__tests__/char_styles.html` +- `packages/docx/src/lib/__tests__/char_styles.spec.tsx` +- `packages/docx/src/lib/__tests__/codeblock.docx` +- `packages/docx/src/lib/__tests__/codeblock.html` +- `packages/docx/src/lib/__tests__/codeblock.spec.tsx` +- `packages/docx/src/lib/__tests__/custom-style-reference.docx` +- `packages/docx/src/lib/__tests__/custom-style-reference.html` +- `packages/docx/src/lib/__tests__/custom-style-reference.spec.tsx` +- `packages/docx/src/lib/__tests__/dummy_item_after_list_item.docx` +- `packages/docx/src/lib/__tests__/dummy_item_after_list_item.html` +- `packages/docx/src/lib/__tests__/dummy_item_after_list_item.spec.tsx` +- `packages/docx/src/lib/__tests__/dummy_item_after_paragraph.docx` +- `packages/docx/src/lib/__tests__/dummy_item_after_paragraph.html` +- `packages/docx/src/lib/__tests__/dummy_item_after_paragraph.spec.tsx` +- `packages/docx/src/lib/__tests__/font.docx` +- `packages/docx/src/lib/__tests__/font.html` +- `packages/docx/src/lib/__tests__/font.spec.tsx` +- `packages/docx/src/lib/__tests__/headers.docx` +- `packages/docx/src/lib/__tests__/headers.html` +- `packages/docx/src/lib/__tests__/headers.spec.tsx` +- `packages/docx/src/lib/__tests__/inline_code.docx` +- `packages/docx/src/lib/__tests__/inline_code.html` +- `packages/docx/src/lib/__tests__/inline_code.spec.tsx` +- `packages/docx/src/lib/__tests__/inline_formatting.docx` +- `packages/docx/src/lib/__tests__/inline_formatting.html` +- `packages/docx/src/lib/__tests__/inline_formatting.spec.tsx` +- `packages/docx/src/lib/__tests__/legal-in.html` +- `packages/docx/src/lib/__tests__/legal-in.spec.tsx` +- `packages/docx/src/lib/__tests__/legal.docx` +- `packages/docx/src/lib/__tests__/legal.html` +- `packages/docx/src/lib/__tests__/legal.spec.tsx` +- `packages/docx/src/lib/__tests__/line-height.docx` +- `packages/docx/src/lib/__tests__/line-height.html` +- `packages/docx/src/lib/__tests__/line-height.spec.tsx` +- `packages/docx/src/lib/__tests__/links.docx` +- `packages/docx/src/lib/__tests__/lists-clean.html` +- `packages/docx/src/lib/__tests__/lists.docx` +- `packages/docx/src/lib/__tests__/lists.html` +- `packages/docx/src/lib/__tests__/lists.spec.tsx` +- `packages/docx/src/lib/__tests__/lists_continuing.docx` +- `packages/docx/src/lib/__tests__/lists_restarting.docx` +- `packages/docx/src/lib/__tests__/lists_sublist_reset.docx` +- `packages/docx/src/lib/__tests__/lists_sublist_reset.html` +- `packages/docx/src/lib/__tests__/lists_sublist_reset.spec.tsx` +- `packages/docx/src/lib/__tests__/numbered_header.docx` +- `packages/docx/src/lib/__tests__/numbered_header.html` +- `packages/docx/src/lib/__tests__/numbered_header.spec.tsx` +- `packages/docx/src/lib/__tests__/numbered_sublist.docx` +- `packages/docx/src/lib/__tests__/numbered_sublist.html` +- `packages/docx/src/lib/__tests__/numbered_sublist.spec.tsx` +- `packages/docx/src/lib/__tests__/readTestFile.ts` +- `packages/docx/src/lib/__tests__/tables.docx` +- `packages/docx/src/lib/__tests__/tables.html` +- `packages/docx/src/lib/__tests__/tables.spec.tsx` +- `packages/docx/src/lib/__tests__/tabs.docx` +- `packages/docx/src/lib/__tests__/tabs.html` +- `packages/docx/src/lib/__tests__/tabs.spec.tsx` +- `packages/docx/src/lib/__tests__/testDocxDeserializer.tsx` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/brs.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/custom-styles.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/empty-paragraphs.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/nested-lists.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/v-shapes.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/whitespaces-1.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/whitespaces-2.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/input/whitespaces-3.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/brs.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/custom-style-reference.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/empty-paragraphs.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/nested-lists.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/whitespaces-1.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/whitespaces-2.html` +- `packages/docx/src/lib/docx-cleaner/__tests__/output/whitespaces-3.html` +- `packages/docx/src/lib/docx-cleaner/cleanDocx.spec.ts` +- `packages/docx/src/lib/docx-cleaner/cleanDocx.ts` +- `packages/docx/src/lib/docx-cleaner/index.ts` +- `packages/docx/src/lib/docx-cleaner/types.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxBrComments.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxEmptyParagraphs.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxFootnotes.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxImageElements.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxListElements.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxListElementsToList.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxQuotes.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxSpacerun.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxSpans.ts` +- `packages/docx/src/lib/docx-cleaner/utils/cleanDocxTabCount.ts` +- `packages/docx/src/lib/docx-cleaner/utils/docxListToList.ts` +- `packages/docx/src/lib/docx-cleaner/utils/generateSpaces.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getDocxIndent.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getDocxListContentHtml.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getDocxListIndent.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getDocxListNode.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getRtfImageHex.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getRtfImageMimeType.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getRtfImageSpid.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getRtfImagesByType.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getRtfImagesMap.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getTextListStyleType.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getVShapeSpid.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getVShapes.spec.ts` +- `packages/docx/src/lib/docx-cleaner/utils/getVShapes.ts` +- `packages/docx/src/lib/docx-cleaner/utils/index.ts` +- `packages/docx/src/lib/docx-cleaner/utils/isDocxBookmark.ts` +- `packages/docx/src/lib/docx-cleaner/utils/isDocxContent.ts` +- `packages/docx/src/lib/docx-cleaner/utils/isDocxFootnote.ts` +- `packages/docx/src/lib/docx-cleaner/utils/isDocxList.ts` +- `packages/docx/src/lib/docx-cleaner/utils/isDocxOl.ts` +- `packages/docx/src/lib/index.ts` +- `packages/docx/tsconfig.build.json` +- `packages/docx/tsconfig.json` +- `pnpm-lock.yaml` +- `yarn.lock` diff --git a/pr-main-dot-starting-diff-files.md b/pr-main-dot-starting-diff-files.md new file mode 100644 index 0000000000..0a81d5e6a2 --- /dev/null +++ b/pr-main-dot-starting-diff-files.md @@ -0,0 +1,21 @@ +# Dot-Starting Paths Different from main + +- Base comparison: `origin/main...HEAD` +- Total matching files: **14** + +## Files + +- `.claude/commands/changeset.md` +- `.claude/commands/docs-plugin.md` +- `.claude/commands/sync-testing-skill.md` +- `.claude/commands/translate.md` +- `.claude/docs/docs-api.md` +- `.claude/docs/plans/2026-01-23-fix-vercel-caching-costs-plan.md` +- `.claude/prompt.json` +- `.claude/scripts/post-compact.sh` +- `.claude/scripts/session-start.sh` +- `.claude/scripts/user-prompt-submit.sh` +- `.codex/skills/agent-browser/SKILL.md` +- `.codex/skills/compound-docs/SKILL.md` +- `.codex/skills/create-agent-skills/workflows/add-script.md` +- `.gitignore` diff --git a/railway.json b/railway.json new file mode 100644 index 0000000000..55f1325beb --- /dev/null +++ b/railway.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://railway.com/railway.schema.json", + "build": { + "builder": "RAILPACK", + "buildCommand": "NODE_OPTIONS='--max-old-space-size=8192' turbo --filter \"./packages/**\" build && cd apps/www && contentlayer2 build && NODE_ENV=production tsx --tsconfig ./scripts/tsconfig.scripts.json scripts/build-registry.mts && yarn tailwindcss -i ./src/app/globals.css -o ./public/tailwind.css --minify && NODE_OPTIONS='--max-old-space-size=8192' next build && cd ../.. && cp -r apps/www/public apps/www/.next/standalone/apps/www/public && mkdir -p apps/www/.next/standalone/apps/www/.next && cp -r apps/www/.next/static apps/www/.next/standalone/apps/www/.next/static" + }, + "environments": { + "production": { + "build": { + "buildEnvironment": "V3" + }, + "deploy": { + "useLegacyStacker": false + } + } + }, + "deploy": { + "runtime": "V2", + "numReplicas": 1, + "startCommand": "HOSTNAME=0.0.0.0 node apps/www/.next/standalone/apps/www/server.js", + "sleepApplication": false, + "ipv6EgressEnabled": false, + "multiRegionConfig": { + "us-east4-eqdc4a": { + "numReplicas": 1 + } + }, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 10 + } +} diff --git a/ralph.yml b/ralph.yml new file mode 100644 index 0000000000..4964937eb7 --- /dev/null +++ b/ralph.yml @@ -0,0 +1,133 @@ +# Debug Preset +# +# For bug investigation and root cause analysis. +# Follows scientific method: hypothesize, test, narrow down. +# +# Usage: +# ralph run --config presets/debug.yml --prompt "Tests fail intermittently in CI" + +event_loop: + prompt_file: "PROMPT.md" + completion_promise: "DEBUG_COMPLETE" + max_iterations: 30 + max_runtime_seconds: 7200 + starting_event: "debug.start" # Ralph publishes this after coordination + +cli: + backend: "claude" + +core: + specs_dir: "./specs/" + +hats: + investigator: + name: "Investigator" + description: "Finds root cause through systematic investigation." + triggers: ["debug.start", "hypothesis.rejected", "hypothesis.confirmed", "fix.verified"] + publishes: ["hypothesis.test", "fix.propose"] + instructions: | + ## INVESTIGATOR MODE + + Find the root cause through systematic investigation. + + ### Scientific Method + 1. **Observe** — What exactly is the symptom? Reproduce it. + 2. **Hypothesize** — What could cause this? + 3. **Predict** — If hypothesis X is true, then Y should happen. + 4. **Test** — Run experiment to test prediction. + 5. **Narrow** — Eliminate hypotheses, focus on survivors. + + ### Dispatch + - Publish `hypothesis.test` to test a hypothesis + - Publish `fix.propose` when root cause is confirmed + + ### DON'T + - ❌ Guess without evidence + - ❌ Fix symptoms without understanding cause + - ❌ Change code during investigation phase + + ### DONE + When bug is fixed and verified, output: DEBUG_COMPLETE + + tester: + name: "Hypothesis Tester" + description: "Designs and runs experiments to test hypotheses." + triggers: ["hypothesis.test"] + publishes: ["hypothesis.confirmed", "hypothesis.rejected"] + instructions: | + ## HYPOTHESIS TESTER MODE + + Design and run an experiment to test a hypothesis. + + ### Process + 1. Read the hypothesis from the event + 2. Design minimal test that would confirm OR reject it + 3. Run the test + 4. Record results with evidence + 5. Publish result + + ### Good Tests + - Isolate the variable + - Have clear pass/fail criteria + - Are reproducible + + ### DON'T + - ❌ Change production code + - ❌ Run tests that can't distinguish hypotheses + - ❌ Skip recording evidence + + fixer: + name: "Bug Fixer" + description: "Implements fix for confirmed root cause with regression test." + triggers: ["fix.propose", "fix.failed"] + publishes: ["fix.applied", "fix.blocked"] + instructions: | + ## BUG FIXER MODE + + Implement the fix for a confirmed root cause. + + ### Process + 1. Review the proposed fix for the confirmed root cause + 2. Implement minimal change to fix root cause + 3. Add regression test that would have caught this + 4. Run full test suite + 5. Commit with detailed message + + ### Commit Format + ``` + fix(scope): [what was broken] + + Root cause: [explanation] + Fix: [what this change does] + Regression test: [test name] + ``` + + ### DON'T + - ❌ Fix more than the reported bug + - ❌ Skip regression test + - ❌ Commit without full test pass + + verifier: + name: "Fix Verifier" + description: "Verifies the fix solves the original problem." + triggers: ["fix.applied"] + publishes: ["fix.verified", "fix.failed"] + instructions: | + ## FIX VERIFIER MODE + + Verify the fix actually solves the original problem. + + ### Checks + 1. Original reproduction steps no longer trigger bug + 2. All tests pass + 3. New regression test exists and is meaningful + 4. No new issues introduced + + ### If Verified + Publish `fix.verified` + + ### If Failed + Publish `fix.failed` with: + - Which check failed + - Observed behavior + - Expected behavior diff --git a/templates/plate-playground-template/bun.lock b/templates/plate-playground-template/bun.lock deleted file mode 100644 index cdbd663f0f..0000000000 --- a/templates/plate-playground-template/bun.lock +++ /dev/null @@ -1,2025 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "plate-playground-template", - "dependencies": { - "@ai-sdk/gateway": "^2.0.15", - "@ai-sdk/react": "2.0.28", - "@ariakit/react": "^0.4.19", - "@emoji-mart/data": "1.2.1", - "@faker-js/faker": "^10.1.0", - "@platejs/ai": "^52.0.1", - "@platejs/autoformat": "^52.0.1", - "@platejs/basic-nodes": "^52.0.1", - "@platejs/basic-styles": "^52.0.1", - "@platejs/callout": "^52.0.1", - "@platejs/caption": "^52.0.1", - "@platejs/code-block": "^52.0.1", - "@platejs/combobox": "^52.0.1", - "@platejs/comment": "^52.0.1", - "@platejs/date": "^52.0.1", - "@platejs/dnd": "^52.0.1", - "@platejs/docx": "^52.0.1", - "@platejs/emoji": "^52.0.2", - "@platejs/excalidraw": "^52.0.1", - "@platejs/floating": "^52.0.1", - "@platejs/indent": "^52.0.1", - "@platejs/juice": "^52.0.1", - "@platejs/layout": "^52.0.1", - "@platejs/link": "^52.0.1", - "@platejs/list": "^52.0.1", - "@platejs/markdown": "^52.0.1", - "@platejs/math": "^52.0.1", - "@platejs/media": "^52.0.1", - "@platejs/mention": "^52.0.1", - "@platejs/resizable": "^52.0.1", - "@platejs/selection": "^52.0.1", - "@platejs/slash-command": "^52.0.1", - "@platejs/suggestion": "^52.0.1", - "@platejs/table": "^52.0.1", - "@platejs/toc": "^52.0.1", - "@platejs/toggle": "^52.0.1", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-avatar": "^1.1.11", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-context-menu": "^2.2.16", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-toolbar": "^1.1.11", - "@radix-ui/react-tooltip": "^1.2.8", - "@udecode/cn": "^52.0.1", - "@uploadthing/react": "7.3.3", - "ai": "5.0.28", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "cmdk": "^1.1.1", - "date-fns": "^4.1.0", - "dedent": "1.0.0", - "html2canvas-pro": "^1.5.13", - "lodash": "^4.17.21", - "lowlight": "^3.3.0", - "lucide-react": "^0.554.0", - "next": "16.0.3", - "pdf-lib": "^1.17.1", - "platejs": "^52.0.1", - "react": "19.2.0", - "react-day-picker": "^9.11.2", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dom": "19.2.0", - "react-lite-youtube-embed": "^3.3.2", - "react-player": "3.3.1", - "react-textarea-autosize": "^8.5.9", - "react-tweet": "^3.2.2", - "remark-gfm": "^4.0.1", - "remark-math": "^6.0.0", - "sonner": "^2.0.7", - "tailwind-merge": "^3.4.0", - "tailwind-scrollbar-hide": "^4.0.0", - "tw-animate-css": "^1.4.0", - "uploadthing": "7.7.4", - "use-file-picker": "2.1.2", - "zod": "^4.1.13", - }, - "devDependencies": { - "@biomejs/biome": "2.3.7", - "@tailwindcss/postcss": "4.1.17", - "@types/node": "^24.10.1", - "@types/react": "19.2.7", - "@types/react-dom": "19.2.3", - "@typescript-eslint/parser": "^8.47.0", - "babel-plugin-react-compiler": "^1.0.0", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "7.0.1", - "lefthook": "^2.0.4", - "postcss": "^8.5.6", - "tailwindcss": "4.1.17", - "typescript": "5.9.3", - "ultracite": "6.3.6", - }, - }, - }, - "packages": { - "@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.15", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.17", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-i1YVKzC1dg9LGvt+GthhD7NlRhz9J4+ZRj3KELU14IZ/MHPsOBiFeEoCCIDLR+3tqT8/+5nIsK3eZ7DFRfMfdw=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@2.0.0", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.17", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw=="], - - "@ai-sdk/react": ["@ai-sdk/react@2.0.28", "", { "dependencies": { "@ai-sdk/provider-utils": "3.0.7", "ai": "5.0.28", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.25.76 || ^4" }, "optionalPeers": ["zod"] }, "sha512-+1iINCSRvjVgCMfe05Ki1PnV9nMeQvQqCIN45KKV+QhQSItyLsuE+WWQrKt5nDRAF3LFq6cSD0tm5F50SJJh4Q=="], - - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@ariakit/core": ["@ariakit/core@0.4.16", "", {}, "sha512-nPJ0Be8d5ZvRApYGqdLMuYUjP7ktkPmTPOXyZFw+0Illk8LKgF3Q74ctVGuoQurJNDsANXcewzlyXK4vyIAGTA=="], - - "@ariakit/react": ["@ariakit/react@0.4.19", "", { "dependencies": { "@ariakit/react-core": "0.4.19" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-n6q8leSQWXMk4vhcZlpnj8cIlAY0n+1bvVTwHvaVfmec4LjW49MFKkJRZd1AiV+SE73nkxPwSL3IbaS4p1aRxQ=="], - - "@ariakit/react-core": ["@ariakit/react-core@0.4.19", "", { "dependencies": { "@ariakit/core": "0.4.16", "@floating-ui/dom": "^1.0.0", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Aj+fu4pMyPXtlBghI+E7KSWItqJkbAqEhut3DlsFAjK9fQdHE+e1tQJG1PtnoEdD9BExkJWQ6R4M5a9HkEhqPA=="], - - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], - - "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - - "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], - - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@biomejs/biome": ["@biomejs/biome@2.3.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.7", "@biomejs/cli-darwin-x64": "2.3.7", "@biomejs/cli-linux-arm64": "2.3.7", "@biomejs/cli-linux-arm64-musl": "2.3.7", "@biomejs/cli-linux-x64": "2.3.7", "@biomejs/cli-linux-x64-musl": "2.3.7", "@biomejs/cli-win32-arm64": "2.3.7", "@biomejs/cli-win32-x64": "2.3.7" }, "bin": { "biome": "bin/biome" } }, "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.7", "", { "os": "win32", "cpu": "x64" }, "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw=="], - - "@braintree/sanitize-url": ["@braintree/sanitize-url@6.0.2", "", {}, "sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg=="], - - "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], - - "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], - - "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], - - "@effect/platform": ["@effect/platform@0.90.3", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.33.0", "find-my-way-ts": "^0.1.6", "msgpackr": "^1.11.4", "multipasta": "^0.2.7" }, "peerDependencies": { "effect": "^3.17.7" } }, "sha512-XvQ37yzWQKih4Du2CYladd1i/MzqtgkTPNCaN6Ku6No4CK83hDtXIV/rP03nEoBg2R3Pqgz6gGWmE2id2G81HA=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - - "@emoji-mart/data": ["@emoji-mart/data@1.2.1", "", {}, "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - - "@excalidraw/excalidraw": ["@excalidraw/excalidraw@0.18.0", "", { "dependencies": { "@braintree/sanitize-url": "6.0.2", "@excalidraw/laser-pointer": "1.3.1", "@excalidraw/mermaid-to-excalidraw": "1.1.2", "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.1.6", "@radix-ui/react-tabs": "1.0.2", "browser-fs-access": "0.29.1", "canvas-roundrect-polyfill": "0.0.1", "clsx": "1.1.1", "cross-env": "7.0.3", "es6-promise-pool": "2.5.0", "fractional-indexing": "3.2.0", "fuzzy": "0.1.3", "image-blob-reduce": "3.0.1", "jotai": "2.11.0", "jotai-scope": "0.7.2", "lodash.debounce": "4.0.8", "lodash.throttle": "4.1.1", "nanoid": "3.3.3", "open-color": "1.9.1", "pako": "2.0.3", "perfect-freehand": "1.2.0", "pica": "7.1.1", "png-chunk-text": "1.0.0", "png-chunks-encode": "1.0.0", "png-chunks-extract": "1.0.0", "points-on-curve": "1.0.1", "pwacompat": "2.0.17", "roughjs": "4.6.4", "sass": "1.51.0", "tunnel-rat": "0.1.2" }, "peerDependencies": { "react": "^17.0.2 || ^18.2.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0" } }, "sha512-QkIiS+5qdy8lmDWTKsuy0sK/fen/LRDtbhm2lc2xcFcqhv2/zdg95bYnl+wnwwXGHo7kEmP65BSiMHE7PJ3Zpw=="], - - "@excalidraw/laser-pointer": ["@excalidraw/laser-pointer@1.3.1", "", {}, "sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g=="], - - "@excalidraw/markdown-to-text": ["@excalidraw/markdown-to-text@0.1.2", "", {}, "sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg=="], - - "@excalidraw/mermaid-to-excalidraw": ["@excalidraw/mermaid-to-excalidraw@1.1.2", "", { "dependencies": { "@excalidraw/markdown-to-text": "0.1.2", "mermaid": "10.9.3", "nanoid": "4.0.2" } }, "sha512-hAFv/TTIsOdoy0dL5v+oBd297SQ+Z88gZ5u99fCIFuEMHfQuPgLhU/ztKhFSTs7fISwVo6fizny/5oQRR3d4tQ=="], - - "@excalidraw/random-username": ["@excalidraw/random-username@1.1.0", "", {}, "sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA=="], - - "@faker-js/faker": ["@faker-js/faker@10.1.0", "", {}, "sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg=="], - - "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], - - "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], - - "@floating-ui/react": ["@floating-ui/react@0.27.16", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.6", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g=="], - - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], - - "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], - - "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], - - "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="], - - "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="], - - "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="], - - "@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="], - - "@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="], - - "@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="], - - "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="], - - "@mux/mux-data-google-ima": ["@mux/mux-data-google-ima@0.2.8", "", { "dependencies": { "mux-embed": "5.9.0" } }, "sha512-0ZEkHdcZ6bS8QtcjFcoJeZxJTpX7qRIledf4q1trMWPznugvtajCjCM2kieK/pzkZj1JM6liDRFs1PJSfVUs2A=="], - - "@mux/mux-player": ["@mux/mux-player@3.9.0", "", { "dependencies": { "@mux/mux-video": "0.28.0", "@mux/playback-core": "0.31.3", "media-chrome": "~4.16.0", "player.style": "^0.3.0" } }, "sha512-OjRXdJFPstCoTipqJCXyC3e3PVoLp8jOheCaWxe2a8qvHkSs/sg+UoYegr++hAoLXXIyy2M7F6vi+tWq0W5bYA=="], - - "@mux/mux-player-react": ["@mux/mux-player-react@3.9.0", "", { "dependencies": { "@mux/mux-player": "3.9.0", "@mux/playback-core": "0.31.3", "prop-types": "^15.8.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0", "react": "^17.0.2 || ^17.0.0-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0", "react-dom": "^17.0.2 || ^17.0.2-0 || ^18 || ^18.0.0-0 || ^19 || ^19.0.0-0" }, "optionalPeers": ["@types/react"] }, "sha512-AYBX89T02qOJ6rF4X2sB8WmPoHBQIrASvp6rxCf9wWYdp5lYtAjjTwaAM2aTlVEXSzdDPaOwgC7VmR7LhBJn3g=="], - - "@mux/mux-video": ["@mux/mux-video@0.28.0", "", { "dependencies": { "@mux/mux-data-google-ima": "0.2.8", "@mux/playback-core": "0.31.3", "castable-video": "~1.1.11", "custom-media-element": "~1.4.5", "media-tracks": "~0.3.4" } }, "sha512-pqpoaoxHXsGX/l7jOZGZ7jOVBVdct8jq+Be1cQ9+n5N/XrkXIGNvO/liprQET3wRuWs2Xri5lWZFRe3ZkOkYHw=="], - - "@mux/playback-core": ["@mux/playback-core@0.31.3", "", { "dependencies": { "hls.js": "~1.6.13", "mux-embed": "^5.8.3" } }, "sha512-IiNnF6LeE8xB5lXzwCUmHUi40q/88ook/YYKTwDsIMG/0zY8b9Ypyzw9ghsELZH0mzuFmv+rvm3ufOIoaIi9eA=="], - - "@next/env": ["@next/env@16.0.3", "", {}, "sha512-IqgtY5Vwsm14mm/nmQaRMmywCU+yyMIYfk3/MHZ2ZTJvwVbBn3usZnjMi1GacrMVzVcAxJShTCpZlPs26EdEjQ=="], - - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MOnbd92+OByu0p6QBAzq1ahVWzF6nyfiH07dQDez4/Nku7G249NjxDVyEfVhz8WkLiOEU+KFVnqtgcsfP2nLXg=="], - - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-i70C4O1VmbTivYdRlk+5lj9xRc2BlK3oUikt3yJeHT1unL4LsNtN7UiOhVanFdc7vDAgZn1tV/9mQwMkWOJvHg=="], - - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-O88gCZ95sScwD00mn/AtalyCoykhhlokxH/wi1huFK+rmiP5LAYVs/i2ruk7xST6SuXN4NI5y4Xf5vepb2jf6A=="], - - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-CEErFt78S/zYXzFIiv18iQCbRbLgBluS8z1TNDQoyPi8/Jr5qhR3e8XHAIxVxPBjDbEMITprqELVc5KTfFj0gg=="], - - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Tc3i+nwt6mQ+Dwzcri/WNDj56iWdycGVh5YwwklleClzPzz7UpfaMw1ci7bLl6GRYMXhWDBfe707EXNjKtiswQ=="], - - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-zTh03Z/5PBBPdTurgEtr6nY0vI9KR9Ifp/jZCcHlODzwVOEKcKRBtQIGrkc7izFgOMuXDEJBmirwpGqdM/ZixA=="], - - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Jc1EHxtZovcJcg5zU43X3tuqzl/sS+CmLgjRP28ZT4vk869Ncm2NoF8qSTaL99gh6uOzgM99Shct06pSO6kA6g=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-N7EJ6zbxgIYpI/sWNzpVKRMbfEGgsWuOIvzkML7wxAAZhPk1Msxuo/JDu1PKjWGrAoOLaZcIX5s+/pF5LIbBBg=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.38.0", "", {}, "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg=="], - - "@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="], - - "@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="], - - "@platejs/ai": ["@platejs/ai@52.0.1", "", { "dependencies": { "@platejs/markdown": "52.0.1", "@platejs/selection": "52.0.1", "@platejs/suggestion": "52.0.1", "fastest-levenshtein": "1.0.16", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jU/8+7KH7HKdB7UuyD5vZnsGYj9reCHmkdAGABqp2Pl5kW/DMemvb2oKbolEX1yQgI6pofNgmicUl2U79pPGPA=="], - - "@platejs/autoformat": ["@platejs/autoformat@52.0.1", "", { "dependencies": { "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-eLXtzOiXJIbWO3nzp0wcdZsh0JI3ePvmxFY9Yhl4dIQYVGGq9kZaRR+HucmVH7nV3Oirsq3WSR/nrdby6GwEdA=="], - - "@platejs/basic-nodes": ["@platejs/basic-nodes@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-s6leoufEsraw1sFmXD73OA45UisCvAiU7+w5t4kkRTJqYMpvUPzAvpOP+gW4JC5GJ8/dptLZ2uSuaJd9YyeHcA=="], - - "@platejs/basic-styles": ["@platejs/basic-styles@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-rq0Bm7ZmY5WMTX20LwD+TDz9ZkfnJdA9fxSvw9NkE50/laKozFDCK0Mx7TeWtAFHkYg2QWXC+blzo8UXERxxvQ=="], - - "@platejs/callout": ["@platejs/callout@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-VSJKWyo7ZVMfT3if2fCnaJwbdUjicMw4MG19jZzb+Fm2wcXabC+dyhZGusjQDjrxDN4j0gqMv0eDXDdSuRAjOQ=="], - - "@platejs/caption": ["@platejs/caption@52.0.1", "", { "dependencies": { "react-textarea-autosize": "^8.5.9" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-jno1Qud+TIUPxn7zn6+M5/7R+A27kRFjQ755/ScAwnD98gu4Hf9rp6pHCwbPiE/VARR33fcqzmXKmOQygxbF3Q=="], - - "@platejs/code-block": ["@platejs/code-block@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-41u2ewA4Vyahm8/36pu+YpSsiHDUxfjnT3eAIDHFFYWLMoK4B7WlHM6iVU5RG6zCnKTlik4QKoHCj+vUXOu/lQ=="], - - "@platejs/combobox": ["@platejs/combobox@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-+LZ9/1CfSyaPJtPwDsuKPSGNlfeftt5iAwmpqF8VHxN/aWqgKP1WXSrmgdFzmKLvk9aiFGrD1tZJn785elBGHg=="], - - "@platejs/comment": ["@platejs/comment@52.0.1", "", { "dependencies": { "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-lLdLrmzE6tX8TE42P42ozGB6cybRV90Pbg2h6xlb2tZgOIpVjr8/dO9UDnc9BS1KmvUSJAsxujcbSEuiueQgpQ=="], - - "@platejs/core": ["@platejs/core@52.0.1", "", { "dependencies": { "@platejs/slate": "52.0.1", "@udecode/react-hotkeys": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1", "clsx": "^2.1.1", "html-entities": "^2.6.0", "is-hotkey": "^0.2.0", "jotai": "~2.8.4", "jotai-optics": "0.4.0", "jotai-x": "2.3.3", "lodash": "^4.17.21", "nanoid": "^5.1.5", "optics-ts": "2.4.1", "slate": "0.118.1", "slate-dom": "0.118.1", "slate-hyperscript": "0.115.0", "slate-react": "0.117.4", "use-deep-compare": "^1.3.0", "zustand": "^5.0.5", "zustand-x": "6.2.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-+1LSUkt8aF+Heeb8M26CcfYxQG6dqel2v84hr5YsCG1bhmN7MpvhYymF2qzf93juFZcIHxsC3R1uv/sjOd7DBw=="], - - "@platejs/date": ["@platejs/date@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-SgPeWZhdi+aMTv2ggUafw7+dw9VjNIWvG7zXix23PmTJwbCogsWsryj5CHduZzXcKcA6OkMLfCw62Secd3FDtw=="], - - "@platejs/diff": ["@platejs/diff@52.0.1", "", { "dependencies": { "diff-match-patch-ts": "^0.6.0", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-VitT3OIw3MbpXmkeQav1QDY9uusGzgd9Ydo073zTbHfjO2PUWZHdlPu7tXH1I8Y9Nnmt8D2zf56xxVS6rBUXTg=="], - - "@platejs/dnd": ["@platejs/dnd@52.0.1", "", { "dependencies": { "lodash": "^4.17.21", "raf": "^3.4.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dnd": ">=14.0.0", "react-dnd-html5-backend": ">=14.0.0", "react-dom": ">=18.0.0" } }, "sha512-z2T8CxbfjSyDqmOmruzqYPH/na2AbGQvq/jNdFxgsfptmBihZ030g91ua8kAYYsqRTH7VvN/URpunMqUtviu7A=="], - - "@platejs/docx": ["@platejs/docx@52.0.1", "", { "dependencies": { "validator": "^13.15.15" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-RlLSc5wkTX7z90S6wxrkB3vYlngaOJjy/Th3tyLgOqE9avWsOYCowOGEANXohIwxMZ67Kzx8MeXc6DyJdcKGmQ=="], - - "@platejs/emoji": ["@platejs/emoji@52.0.2", "", { "dependencies": { "@platejs/combobox": "52.0.1" }, "peerDependencies": { "@emoji-mart/data": ">=1.2.0", "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-bDH0jLa1ni0BnkArRUnqWfZRqZiLm1i9vkzQbD7VBaRVAXLakaJrnpAk/Q/RGWlTVeL8A8UOwWYPKWMe3Zat0w=="], - - "@platejs/excalidraw": ["@platejs/excalidraw@52.0.1", "", { "dependencies": { "@excalidraw/excalidraw": "0.18.0", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-wXFOSvLjCplMXERQN6zhKJETsxoC0XbJIavNTfl9Yoiy1kqhhatwNNdhdlh0pjLZhjU/8SL4Z8QkMG1yOP0qnw=="], - - "@platejs/floating": ["@platejs/floating@52.0.1", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/react": "^0.27.12" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-Z9YocQIRClu3akdUu9unX01Tv8ME8SRvSFcxIkA+hknpL9iLV0HZzAoMeXxVPpfs5t88SfaomrKHH8RV6dhFVg=="], - - "@platejs/indent": ["@platejs/indent@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-sbMizZYlvmYenxfbY3RARLrM6HRuIFCbnQfJ1LMfy2tf7OjhZz/A15ACcmjyqvdZtVF54JtW3q6hiQQPMuMTAQ=="], - - "@platejs/juice": ["@platejs/juice@52.0.1", "", { "dependencies": { "juice": "^11.0.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-NEVvpJxwEHoG7zLgEJdTPVOy6Up4BT8H3aRl2EGDL4Wre0arBJVNQCeOZ8rRBZZB0syhj1MmcxmAcZuBqnnZQw=="], - - "@platejs/layout": ["@platejs/layout@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-9SELFT008YdzcW2vZ8kLNOS4iTQAl+O/Qar9LBWpBRsD32Iy5d/wFOmt6xMx4mD/zrcDHeOyfXx4R6SgIX9/qA=="], - - "@platejs/link": ["@platejs/link@52.0.1", "", { "dependencies": { "@platejs/floating": "52.0.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-IlUB0QKnsnkgMshm0xz2Rqj5Z1rE3FHuVo/umjBm7fWJCbldXps340rPA59LfaDctF3jZYQlf+PFd6Zxaw4Q8A=="], - - "@platejs/list": ["@platejs/list@52.0.1", "", { "dependencies": { "@platejs/indent": "52.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-d9IjfzjaIcfycRGsRagaTi9dYNMALq2aq+BGgm/CHhw8JL/dxzMKfz9d2yQFQ7q3cCGMiIxiHtHOWkybfevVKQ=="], - - "@platejs/markdown": ["@platejs/markdown@52.0.1", "", { "dependencies": { "marked": "^15.0.12", "mdast-util-math": "3.0.0", "mdast-util-mdx": "3.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "ts-essentials": "10.1.0", "unified": "^11.0.5" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-WEp3GPdkIpHPp8kehpXJqJxLaDOjbnrk2wrxBbdO1Dq3S3lQKo0MpCtrL0F0jxc9vun7DYzCSpMslV317aZRGA=="], - - "@platejs/math": ["@platejs/math@52.0.1", "", { "dependencies": { "katex": "0.16.22" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-QkPCyIN4n/wHgBlR/6Qu+D3bczvEDnQY7p3lkyGLvC0gQmduLN3YUTBs0I9XauJMSL0rLWhQxAYkJxTJrNKohQ=="], - - "@platejs/media": ["@platejs/media@52.0.1", "", { "dependencies": { "js-video-url-parser": "^0.5.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-6EJdFBZgb0fepyhdRwJsZOv898XIpaQtzb9D2afrOMK70XuesaWc0FHLDwQw7PJ7ygzd0d/2OWxsTVo7YjGJSg=="], - - "@platejs/mention": ["@platejs/mention@52.0.1", "", { "dependencies": { "@platejs/combobox": "52.0.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-0sKzbsS8Lq0KeFtSfBemFxcjyKmTuO638aaodsX5dIlMvaHXOHqUs1JcdbZ8r5FtT0Z4w0bDomzoNzbR/2b2bQ=="], - - "@platejs/resizable": ["@platejs/resizable@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-a5Md2pNQNSK1yXOUfe5ABE2qXFLPtMNdzm9rcSYUERzL9j55GfIU4QXgb72EIaWfTuncCdO7FmTGUzDSq9f9dA=="], - - "@platejs/selection": ["@platejs/selection@52.0.1", "", { "dependencies": { "copy-to-clipboard": "^3.3.3" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-eCczW3I1upcw6aHsqm99oU2OLcPqhLJEdq05k7AI9eQlm9nNQgXArRRApmriyWx+RKnGKWdaYwHpdWS/aSHkNg=="], - - "@platejs/slash-command": ["@platejs/slash-command@52.0.1", "", { "dependencies": { "@platejs/combobox": "52.0.1" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-zbeC9pM7YLZjqG6HY1BCM04yB8v8ciF3o0rmu6sMKDhTb3FZ6rhvAiAQ3aOoJZA9SfZG43grUKodImxe00nDMw=="], - - "@platejs/slate": ["@platejs/slate@52.0.1", "", { "dependencies": { "@udecode/utils": "52.0.1", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "slate": "0.118.1", "slate-dom": "0.118.1" } }, "sha512-+Qx/aTVL5znwYMnjN++PxVmS7ywkoDXM4JYY6TgMAvszce5qFJOKjmxXzliFurqiojoPeyFllyoSO8dQWDpcJA=="], - - "@platejs/suggestion": ["@platejs/suggestion@52.0.1", "", { "dependencies": { "@platejs/diff": "52.0.1", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-TRMrqRalg8QhaMpTrzt0iIame0ZbBqB1OMuBD8Ll/T3zJKCCPGKywqCZ/6Bacb6ObqNO+CbM/BQ3ohFyKdGesg=="], - - "@platejs/table": ["@platejs/table@52.0.1", "", { "dependencies": { "@platejs/resizable": "52.0.1", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-FE7hxJgRFGPkWRCDhsvbdr++I52v9Y07LuCQWdBJYD62lkDCRsEUM4AUCUTCrTke6Vyk2prFUIQeNS89+SjuYA=="], - - "@platejs/toc": ["@platejs/toc@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-/uN6Dv+HKLUP1fNRMntBFtq63uh0pyosud4QogbjU5wjoSusyNywW4oWzvL5PqPtLIsq96fqqeI5NEgqY88stQ=="], - - "@platejs/toggle": ["@platejs/toggle@52.0.1", "", { "dependencies": { "@platejs/indent": "52.0.1", "lodash": "^4.17.21" }, "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-Pt6nJe260586FcFiyMx6PA1KRONb3FhIxQhzy1gWplSUH5b/m+qMcXqN47zswyZxL5SGbIj6ZAASM/VIETt+Rw=="], - - "@platejs/utils": ["@platejs/utils@52.0.1", "", { "dependencies": { "@platejs/core": "52.0.1", "@platejs/slate": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1", "clsx": "^2.1.1", "lodash": "^4.17.21" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-goZ+Be/Zl0525BKv6DK8I4I04Um4E5oIsoXinjHBxzJ8cw1lJzs3n1NQF4K18jH5oxYviljvoRAgzwRxylUlCw=="], - - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], - - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], - - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], - - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], - - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], - - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], - - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], - - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], - - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], - - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.0", "@radix-ui/react-context": "1.0.0", "@radix-ui/react-direction": "1.0.0", "@radix-ui/react-id": "1.0.0", "@radix-ui/react-presence": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-roving-focus": "1.0.2", "@radix-ui/react-use-controllable-state": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ=="], - - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], - - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], - - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], - - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], - - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], - - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], - - "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], - - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], - - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], - - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - - "@react-dnd/asap": ["@react-dnd/asap@5.0.2", "", {}, "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="], - - "@react-dnd/invariant": ["@react-dnd/invariant@4.0.2", "", {}, "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="], - - "@react-dnd/shallowequal": ["@react-dnd/shallowequal@4.0.2", "", {}, "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.4", "", {}, "sha512-d3IxtzLo7P1oZ8s8YNvxzBUXRXojSut8pbPrTYtzsc5sn4+53jVqbk66pQerSZbZSJZQux6LkclB/+8IDordHg=="], - - "@svta/common-media-library": ["@svta/common-media-library@0.17.4", "", {}, "sha512-nP/KThzQW5FZKdc9V7ICTa9/A7xGw66VQoLPYOEwwMZTTrISp1zIQAX4KAYJw2PN/VPnxJQJXIYbzZTXgMHctw=="], - - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - - "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], - - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], - - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], - - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], - - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], - - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], - - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], - - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], - - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], - - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], - - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], - - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], - - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], - - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="], - - "@trpc/server": ["@trpc/server@11.7.2", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-AgB26PXY69sckherIhCacKLY49rxE2XP5h38vr/KMZTbLCL1p8IuIoKPjALTcugC2kbyQ7Lbqo2JDVfRSmPmfQ=="], - - "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], - - "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], - - "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], - - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], - - "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="], - - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - - "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.47.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.47.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.47.0", "@typescript-eslint/types": "^8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0" } }, "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.47.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.47.0", "", {}, "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.47.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.47.0", "@typescript-eslint/tsconfig-utils": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ=="], - - "@udecode/cn": ["@udecode/cn@52.0.1", "", { "dependencies": { "@udecode/react-utils": "52.0.1" }, "peerDependencies": { "class-variance-authority": ">=0.7.0", "react": ">=18.0.0", "react-dom": ">=18.0.0", "tailwind-merge": ">=2.2.0" } }, "sha512-GuuIqzQerHsmwQaAmyKUsaD+vF3vsIVjJHVaQpSSaG0oIuy0nlsDB5PSlpwqISKckyjFlbVLPneZsWxsytTjrw=="], - - "@udecode/react-hotkeys": ["@udecode/react-hotkeys@52.0.1", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-o+3CRtOjdcVInlaVOC8wcXA8zjy6i7YL4nlTD2oI4Fw2ZqhxUkKF+y34CitgOBe9eItW6U7JcgAyib5rtqhFjg=="], - - "@udecode/react-utils": ["@udecode/react-utils@52.0.1", "", { "dependencies": { "@radix-ui/react-slot": "^1.2.3", "@udecode/utils": "52.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-zdSfCfX7AWzut8J22Az7usrHM/uSwlzJXRX3jnidx4rhE+YzV2BJfq4q8w0SbHq8Y4Peg8VlP0r1y8z8p2HSUw=="], - - "@udecode/utils": ["@udecode/utils@52.0.1", "", {}, "sha512-T/qa4++J2MMY9CbhZ05ONpcpkvsRV0uBvOfMhhYWKkAND4DSVAVzmZTV7WQ5fjGq81agAped0WQxcqsEg617uQ=="], - - "@uploadthing/mime-types": ["@uploadthing/mime-types@0.3.6", "", {}, "sha512-t3tTzgwFV9+1D7lNDYc7Lr7kBwotHaX0ZsvoCGe7xGnXKo9z0jG2Sjl/msll12FeoLj77nyhsxevXyGpQDBvLg=="], - - "@uploadthing/react": ["@uploadthing/react@7.3.3", "", { "dependencies": { "@uploadthing/shared": "7.1.10", "file-selector": "0.6.0" }, "peerDependencies": { "next": "*", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "uploadthing": "^7.2.0" }, "optionalPeers": ["next"] }, "sha512-GhKbK42jL2Qs7OhRd2Z6j0zTLsnJTRJH31nR7RZnUYVoRh2aS/NabMAnHBNqfunIAGXVaA717Pvzq7vtxuPTmQ=="], - - "@uploadthing/shared": ["@uploadthing/shared@7.1.10", "", { "dependencies": { "@uploadthing/mime-types": "0.3.6", "effect": "3.17.7", "sqids": "^0.3.0" } }, "sha512-R/XSA3SfCVnLIzFpXyGaKPfbwlYlWYSTuGjTFHuJhdAomuBuhopAHLh2Ois5fJibAHzi02uP1QCKbgTAdmArqg=="], - - "@vercel/oidc": ["@vercel/oidc@3.0.5", "", {}, "sha512-fnYhv671l+eTTp48gB4zEsTW/YtRgRPnkI2nT7x6qw5rkI1Lq2hTmQIpHPgyThI0znLK+vX2n9XxKdXZ7BUbbw=="], - - "@vimeo/player": ["@vimeo/player@2.29.0", "", { "dependencies": { "native-promise-only": "0.8.1", "weakmap-polyfill": "2.0.4" } }, "sha512-9JjvjeqUndb9otCCFd0/+2ESsLk7VkDE6sxOBy9iy2ukezuQbplVRi+g9g59yAurKofbmTi/KcKxBGO/22zWRw=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ai": ["ai@5.0.28", "", { "dependencies": { "@ai-sdk/gateway": "1.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-tnybAqoDFzuK6O1NOMHX1d/wH7Eug8y0H4l/Gl6swi8BYGtlTPDjniKnGYzgTpLTdpj7SI3qjZuomz7evph9+w=="], - - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - - "babel-plugin-macros": ["babel-plugin-macros@3.1.0", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="], - - "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], - - "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.8.31", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw=="], - - "bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="], - - "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], - - "bcp-47-normalize": ["bcp-47-normalize@2.3.0", "", { "dependencies": { "bcp-47": "^2.0.0", "bcp-47-match": "^2.0.0" } }, "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q=="], - - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browser-fs-access": ["browser-fs-access@0.29.1", "", {}, "sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw=="], - - "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], - - "canvas-roundrect-polyfill": ["canvas-roundrect-polyfill@0.0.1", "", {}, "sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw=="], - - "castable-video": ["castable-video@1.1.11", "", { "dependencies": { "custom-media-element": "~1.4.5" } }, "sha512-LCRTK6oe7SB1SiUQFzZCo6D6gcEzijqBTVIuj3smKpQdesXM18QTbCVqWgh9MfOeQgTx/i9ji5jGcdqNPeWg2g=="], - - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - - "ce-la-react": ["ce-la-react@0.3.2", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-QJ6k4lOD/btI08xG8jBPxRCGXvCnusGGkTsiXk0u3NqUu/W+BXRnFD4PYjwtqh8AWmGa5LDbGk0fLQsqr0nSMA=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], - - "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], - - "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], - - "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], - - "cheerio": ["cheerio@1.0.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "encoding-sniffer": "^0.2.0", "htmlparser2": "^9.1.0", "parse5": "^7.1.2", "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" } }, "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww=="], - - "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], - - "chokidar": ["chokidar@3.6.0", "", { "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" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - - "cloudflare-video-element": ["cloudflare-video-element@1.3.4", "", {}, "sha512-F9g+tXzGEXI6v6L48qXxr8vnR8+L6yy7IhpJxK++lpzuVekMHTixxH7/dzLuq6OacVGziU4RB5pzZYJ7/LYtJg=="], - - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], - - "codem-isoboxer": ["codem-isoboxer@0.3.10", "", {}, "sha512-eNk3TRV+xQMJ1PEj0FQGY8KD4m0GPxT487XJ+Iftm7mVa9WpPFDMWqPt+46buiP5j5Wzqe5oMIhqBcAeKfygSA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - - "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "copy-to-clipboard": ["copy-to-clipboard@3.3.3", "", { "dependencies": { "toggle-selection": "^1.0.6" } }, "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA=="], - - "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], - - "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], - - "crc-32": ["crc-32@0.3.0", "", {}, "sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA=="], - - "cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="], - - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "custom-media-element": ["custom-media-element@1.4.5", "", {}, "sha512-cjrsQufETwxjvwZbYbKBCJNvmQ2++G9AvT45zDi7NXL9k2PdVcs2h0jQz96J6G4TMKRCcEsoJ+QTgQD00Igtjw=="], - - "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], - - "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], - - "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], - - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], - - "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], - - "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], - - "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], - - "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], - - "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], - - "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], - - "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], - - "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], - - "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], - - "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], - - "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], - - "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], - - "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], - - "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], - - "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], - - "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], - - "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], - - "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], - - "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], - - "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], - - "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], - - "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], - - "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], - - "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], - - "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], - - "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], - - "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], - - "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], - - "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], - - "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], - - "dagre-d3-es": ["dagre-d3-es@7.0.10", "", { "dependencies": { "d3": "^7.8.2", "lodash-es": "^4.17.21" } }, "sha512-qTCQmEhcynucuaZgY5/+ti3X/rnszKZhEQH/ZdWdtP1tA/y3VoHJzcVrO9pjjJCNpigfscAtoUB5ONcd2wNn0A=="], - - "dash-video-element": ["dash-video-element@0.1.6", "", { "dependencies": { "custom-media-element": "^1.4.5", "dashjs": "^5.0.3" } }, "sha512-4gHShaQjcFv6diX5EzB6qAdUGKlIUGGZY8J8yp2pQkWqR0jX4c6plYy0cFraN7mr0DZINe8ujDN1fssDYxJjcg=="], - - "dashjs": ["dashjs@5.1.0", "", { "dependencies": { "@svta/common-media-library": "^0.17.1", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.10", "fast-deep-equal": "3.1.3", "html-entities": "^2.5.2", "imsc": "^1.1.5", "localforage": "^1.10.0", "path-browserify": "^1.0.1", "ua-parser-js": "^1.0.37" } }, "sha512-FilZfs+0pj9NB7q2VMT4zahG+V2JoleVl6K9kWunvndICdclw/jLAfLImcmCr1WqxH4hsgsFXvaVgea9XGkgVQ=="], - - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], - - "date-fns-jalali": ["date-fns-jalali@4.1.0-0", "", {}, "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg=="], - - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], - - "dedent": ["dedent@1.0.0", "", { "dependencies": { "babel-plugin-macros": "^3.1.0" } }, "sha512-Hl6C/SsX4m8Tmn+bskSe3ZoNiNylxjtwhCRAVX9+JBHnOqRglOdeAzgl5lKZXK9xfRtaF5kpLO1ItFvwGlr9rA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - - "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - - "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - - "diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="], - - "diff-match-patch-ts": ["diff-match-patch-ts@0.6.0", "", {}, "sha512-U0uPIJ+wJqgaBoVw2MFSFpGIk7q3mJJ+/sehbxDZFv4Gx6a1GOmrsSLmxVDDrGtRL4Q9de084aa5lVpCHn+eUw=="], - - "direction": ["direction@1.0.4", "", { "bin": { "direction": "cli.js" } }, "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="], - - "dnd-core": ["dnd-core@16.0.1", "", { "dependencies": { "@react-dnd/asap": "^5.0.1", "@react-dnd/invariant": "^4.0.1", "redux": "^4.2.0" } }, "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng=="], - - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], - - "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], - - "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], - - "dompurify": ["dompurify@3.1.6", "", {}, "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="], - - "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "effect": ["effect@3.17.7", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.259", "", {}, "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ=="], - - "elkjs": ["elkjs@0.9.3", "", {}, "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], - - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], - - "entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], - - "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], - - "es6-promise-pool": ["es6-promise-pool@2.5.0", "", {}, "sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-goat": ["escape-goat@3.0.0", "", {}, "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], - - "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - - "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - - "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], - - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "file-selector": ["file-selector@0.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "fractional-indexing": ["fractional-indexing@3.2.0", "", {}, "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "fuzzy": ["fuzzy@0.1.3", "", {}, "sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - - "glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "glur": ["glur@1.1.2", "", {}, "sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], - - "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - - "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], - - "hls-video-element": ["hls-video-element@1.5.9", "", { "dependencies": { "custom-media-element": "^1.4.5", "hls.js": "^1.6.5", "media-tracks": "^0.3.4" } }, "sha512-hDXhSI3IpSSODJF8ecNzDHKP5cqsouOuKDMjoTexyFePKr9KpXVCPAnVrXFTTH8VbOim4xkLtPkVJFt7J1Rs6w=="], - - "hls.js": ["hls.js@1.6.15", "", {}, "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA=="], - - "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], - - "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], - - "html2canvas-pro": ["html2canvas-pro@1.5.13", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-//ksb3JLA+ewSxFODyb5YH6xqRLpe6Kt1i7Zyr5eqHmfcNt0bm8MpzSQjOK2qu6cCv13wqWtOZrCOpA+//EhaA=="], - - "htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], - - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "image-blob-reduce": ["image-blob-reduce@3.0.1", "", { "dependencies": { "pica": "^7.1.0" } }, "sha512-/VmmWgIryG/wcn4TVrV7cC4mlfUC/oyiKIfSg5eVM3Ten/c1c34RJhMYKCWTnoSMHSqXLt3tsrBR4Q2HInvN+Q=="], - - "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], - - "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], - - "immutable": ["immutable@4.3.7", "", {}, "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imsc": ["imsc@1.1.5", "", { "dependencies": { "sax": "1.2.1" } }, "sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], - - "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - - "is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], - - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "jotai": ["jotai@2.11.0", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ=="], - - "jotai-optics": ["jotai-optics@0.4.0", "", { "peerDependencies": { "jotai": ">=2.0.0", "optics-ts": ">=2.0.0" } }, "sha512-osbEt9AgS55hC4YTZDew2urXKZkaiLmLqkTS/wfW5/l0ib8bmmQ7kBXSFaosV6jDDWSp00IipITcJARFHdp42g=="], - - "jotai-scope": ["jotai-scope@0.7.2", "", { "peerDependencies": { "jotai": ">=2.9.2", "react": ">=17.0.0" } }, "sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg=="], - - "jotai-x": ["jotai-x@2.3.3", "", { "peerDependencies": { "@types/react": ">=17.0.0", "jotai": ">=2.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-ZeSPjf77VINlJ0HyMfYcPv/9psjB0CtJIZP6S+s/eefaO/9+U37M9Jx5dWmILgTe8hAol99EbAv6DDrHobOucA=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-video-url-parser": ["js-video-url-parser@0.5.1", "", {}, "sha512-/vwqT67k0AyIGMHAvSOt+n4JfrZWF7cPKgKswDO35yr27GfW4HtjpQVlTx6JLF45QuPm8mkzFHkZgFVnFm4x/w=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - - "juice": ["juice@11.0.3", "", { "dependencies": { "cheerio": "1.0.0", "commander": "^12.1.0", "entities": "^7.0.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^7.0.0" }, "bin": { "juice": "bin/juice" } }, "sha512-VYjPg4WylyWyLPnSiUsJ9tnnGhRZF0vn0YD8WWwaI8FhP9+1UdRMyRDbvqPOH/nBotmLKOc+FI+Oma6FwVWfSw=="], - - "katex": ["katex@0.16.22", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], - - "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], - - "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], - - "lefthook": ["lefthook@2.0.4", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.0.4", "lefthook-darwin-x64": "2.0.4", "lefthook-freebsd-arm64": "2.0.4", "lefthook-freebsd-x64": "2.0.4", "lefthook-linux-arm64": "2.0.4", "lefthook-linux-x64": "2.0.4", "lefthook-openbsd-arm64": "2.0.4", "lefthook-openbsd-x64": "2.0.4", "lefthook-windows-arm64": "2.0.4", "lefthook-windows-x64": "2.0.4" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-GNCU2vQWM/UWjiEF23601aILi1aMbPke6viortH7wIO/oVGOCW0H6FdLez4XZDyqnHL9XkTnd0BBVrBbYVMLpA=="], - - "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AR63/O5UkM7Sc6x5PhP4vTuztTYRBeBroXApeWGM/8e5uZyoQug/7KTh7xhbCMDf8WJv6vdFeXAQCPSmDyPU3Q=="], - - "lefthook-darwin-x64": ["lefthook-darwin-x64@2.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-618DVUttSzV9egQiqTQoxGfnR240JoPWYmqRVHhiegnQKZ2lp5XJ+7NMxeRk/ih93VVOLzFO5ky3PbpxTmJgjQ=="], - - "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.0.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mTAQym1BK38fKglHBQ/0GXPznVC4LoStHO5lAI3ZxaEC0FQetqGHYFzhWbIH5sde9JhztE2rL/aBzMHDoAtzSw=="], - - "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.0.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sy02aSxd8UMd6XmiPFVl/Em0b78jdZcDSsLwg+bweJQQk0l+vJhOfqFiG11mbnpo+EBIZmRe6OH5LkxeSU36+w=="], - - "lefthook-linux-arm64": ["lefthook-linux-arm64@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-W0Nlr/Cz2QTH9n4k5zNrk3LSsg1C4wHiJi8hrAiQVTaAV/N1XrKqd0DevqQuouuapG6pw/6B1xCgiNPebv9oyw=="], - - "lefthook-linux-x64": ["lefthook-linux-x64@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-N6ySVCtB/DrOZ1ZgPL8WBZTgtoVHvcPKI+LV5wbcGrvA/dzDZFvniadrbDWZg7Tm705efiQzyENjwhhqNkwiww=="], - - "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.0.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-VmOhJO3pYzZ/1C2WFXtL/n5pq4/eYOroqJJpwTJfmCHyw4ceLACu8MDyU5AMJhGMkbL8mPxGInJKxg5xhYgGRw=="], - - "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.0.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-U8MZz1xlHUdflkQQ2hkMQsei6fSZbs8tuE4EjCIHWnNdnAF4V8sZ6n1KbxsJcoZXPyBZqxZSMu1o/Ye8IAMVKg=="], - - "lefthook-windows-arm64": ["lefthook-windows-arm64@2.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-543H3y2JAwNdvwUQ6nlNBG7rdKgoOUgzAa6pYcl6EoqicCRrjRmGhkJu7vUudkkrD2Wjm7tr9hU9poP2g5fRFQ=="], - - "lefthook-windows-x64": ["lefthook-windows-x64@2.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UDEPK9RWKm60xsNOdS/DQOdFba0SFa4w3tpFMXK1AJzmRHhosoKrorXGhtTr6kcM0MGKOtYi8GHsm++ArZ9wvQ=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lie": ["lie@3.1.1", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="], - - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], - - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], - - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], - - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], - - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], - - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], - - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], - - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], - - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], - - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], - - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], - - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "localforage": ["localforage@1.10.0", "", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - - "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - - "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - - "lodash.mapvalues": ["lodash.mapvalues@4.6.0", "", {}, "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], - - "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], - - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - - "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - - "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], - - "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], - - "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], - - "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], - - "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], - - "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], - - "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], - - "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], - - "mdast-util-math": ["mdast-util-math@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "longest-streak": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.1.0", "unist-util-remove-position": "^5.0.0" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], - - "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], - - "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], - - "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], - - "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], - - "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], - - "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], - - "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - - "media-chrome": ["media-chrome@4.16.0", "", { "dependencies": { "ce-la-react": "^0.3.0" } }, "sha512-c5xpTYcYo9nYsC/G/C1PyOcPXEL6iIaSR9MH3GncVuj4S90aHqvGbsyUWFDPPBKx5sCwWLxDnbszE/24eMT54g=="], - - "media-tracks": ["media-tracks@0.3.4", "", {}, "sha512-5SUElzGMYXA7bcyZBL1YzLTxH9Iyw1AeYNJxzByqbestrrtB0F3wfiWUr7aROpwodO4fwnxOt78Xjb3o3ONNQg=="], - - "mensch": ["mensch@0.3.4", "", {}, "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "mermaid": ["mermaid@10.9.3", "", { "dependencies": { "@braintree/sanitize-url": "^6.0.1", "@types/d3-scale": "^4.0.3", "@types/d3-scale-chromatic": "^3.0.0", "cytoscape": "^3.28.1", "cytoscape-cose-bilkent": "^4.1.0", "d3": "^7.4.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.10", "dayjs": "^1.11.7", "dompurify": "^3.0.5 <3.1.7", "elkjs": "^0.9.0", "katex": "^0.16.9", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "mdast-util-from-markdown": "^1.3.0", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.3", "ts-dedent": "^2.2.0", "uuid": "^9.0.0", "web-worker": "^1.2.0" } }, "sha512-V80X1isSEvAewIL3xhmz/rVmc27CVljcsbWxkxlWJWY/1kQa4XOABqpDl2qQLGKzpKm6WbTfUEKImBlUfFYArw=="], - - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], - - "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], - - "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], - - "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], - - "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], - - "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], - - "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], - - "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], - - "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], - - "micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="], - - "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="], - - "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], - - "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], - - "micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="], - - "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="], - - "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], - - "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], - - "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="], - - "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], - - "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], - - "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], - - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], - - "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], - - "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], - - "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], - - "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], - - "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], - - "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], - - "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="], - - "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], - - "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], - - "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], - - "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], - - "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], - - "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], - - "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "msgpackr": ["msgpackr@1.11.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA=="], - - "msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="], - - "multimath": ["multimath@2.0.0", "", { "dependencies": { "glur": "^1.1.2", "object-assign": "^4.1.1" } }, "sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g=="], - - "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="], - - "mutative": ["mutative@1.1.0", "", {}, "sha512-2PJADREjOusk3iJkD3rXV2YjAxTuaLxdfqtqTEt6vcY07LtEBR1seHuBHXWEIuscqRDGvbauYPs+A4Rj/KTczQ=="], - - "mux-embed": ["mux-embed@5.14.0", "", {}, "sha512-vcuw2WWGQ3uh9HfJwaJ8JMlKN4l+8xGfFAUAYNJOcIxP2sPUoRvlu3SY00yg9YBKLfK9E6peaPl+HszWS0Iseg=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "native-promise-only": ["native-promise-only@0.8.1", "", {}, "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "next": ["next@16.0.3", "", { "dependencies": { "@next/env": "16.0.3", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.3", "@next/swc-darwin-x64": "16.0.3", "@next/swc-linux-arm64-gnu": "16.0.3", "@next/swc-linux-arm64-musl": "16.0.3", "@next/swc-linux-x64-gnu": "16.0.3", "@next/swc-linux-x64-musl": "16.0.3", "@next/swc-win32-arm64-msvc": "16.0.3", "@next/swc-win32-x64-msvc": "16.0.3", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Ka0/iNBblPFcIubTA1Jjh6gvwqfjrGq1Y2MTI5lbjeLIAfmC+p5bQmojpRZqgHHVu5cG4+qdIiwXiBSm/8lZ3w=="], - - "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], - - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "non-layered-tidy-tree-layout": ["non-layered-tidy-tree-layout@2.0.2", "", {}, "sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - - "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "open-color": ["open-color@1.9.1", "", {}, "sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw=="], - - "optics-ts": ["optics-ts@2.4.1", "", {}, "sha512-HaYzMHvC80r7U/LqAd4hQyopDezC60PO2qF5GuIwALut2cl5rK1VWHsqTp0oqoJJWjiv6uXKqsO+Q2OO0C3MmQ=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], - - "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], - - "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], - - "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], - - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], - - "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], - - "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pdf-lib": ["pdf-lib@1.17.1", "", { "dependencies": { "@pdf-lib/standard-fonts": "^1.0.0", "@pdf-lib/upng": "^1.0.1", "pako": "^1.0.11", "tslib": "^1.11.1" } }, "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw=="], - - "perfect-freehand": ["perfect-freehand@1.2.0", "", {}, "sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw=="], - - "performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="], - - "pica": ["pica@7.1.1", "", { "dependencies": { "glur": "^1.1.2", "inherits": "^2.0.3", "multimath": "^2.0.0", "object-assign": "^4.1.1", "webworkify": "^1.5.0" } }, "sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], - - "platejs": ["platejs@52.0.1", "", { "dependencies": { "@platejs/core": "52.0.1", "@platejs/slate": "52.0.1", "@platejs/utils": "52.0.1", "@udecode/react-hotkeys": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-pus4AFlqTdUuSidQYlsEU5w43lNAxpfPvvg1i2Q3zQ9dgLJzESrVY/ZfZabnqbhADVJTNBbhYhmC/YLN6Hazog=="], - - "player.style": ["player.style@0.3.0", "", { "dependencies": { "media-chrome": "~4.14.0" } }, "sha512-ny1TbqA2ZsUd6jzN+F034+UMXVK7n5SrwepsrZ2gIqVz00Hn0ohCUbbUdst/2IOFCy0oiTbaOXkSFxRw1RmSlg=="], - - "png-chunk-text": ["png-chunk-text@1.0.0", "", {}, "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw=="], - - "png-chunks-encode": ["png-chunks-encode@1.0.0", "", { "dependencies": { "crc-32": "^0.3.0", "sliced": "^1.0.1" } }, "sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA=="], - - "png-chunks-extract": ["png-chunks-extract@1.0.0", "", { "dependencies": { "crc-32": "^0.3.0" } }, "sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q=="], - - "points-on-curve": ["points-on-curve@1.0.1", "", {}, "sha512-3nmX4/LIiyuwGLwuUrfhTlDeQFlAhi7lyK/zcRNGhalwapDWgAGR82bUpmn2mA03vII3fvNCG8jAONzKXwpxAg=="], - - "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - - "proxy-compare": ["proxy-compare@2.6.0", "", {}, "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - - "pwacompat": ["pwacompat@2.0.17", "", {}, "sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="], - - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], - - "react-day-picker": ["react-day-picker@9.11.2", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-TD/xMUGg2oiKX8jUR21MST5pj+7Y36097YtnDHQFlIcZOu3mbLLw2B2JqEByEGrR3HHveWYnKlyls6WqJgohAg=="], - - "react-dnd": ["react-dnd@16.0.1", "", { "dependencies": { "@react-dnd/invariant": "^4.0.1", "@react-dnd/shallowequal": "^4.0.1", "dnd-core": "^16.0.1", "fast-deep-equal": "^3.1.3", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "@types/hoist-non-react-statics": ">= 3.3.1", "@types/node": ">= 12", "@types/react": ">= 16", "react": ">= 16.14" }, "optionalPeers": ["@types/hoist-non-react-statics", "@types/node", "@types/react"] }, "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q=="], - - "react-dnd-html5-backend": ["react-dnd-html5-backend@16.0.1", "", { "dependencies": { "dnd-core": "^16.0.1" } }, "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw=="], - - "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], - - "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], - - "react-lite-youtube-embed": ["react-lite-youtube-embed@3.3.2", "", { "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0" } }, "sha512-54+Q19NCdLFmidg06USaHR/yQ7q8izNjSxNehOVipxhvXMbjqCTWTOROJyIwTnL62VmtTxLpUW15rPI6pry9gw=="], - - "react-player": ["react-player@3.3.1", "", { "dependencies": { "@mux/mux-player-react": "^3.5.1", "cloudflare-video-element": "^1.3.3", "dash-video-element": "^0.1.6", "hls-video-element": "^1.5.6", "spotify-audio-element": "^1.0.2", "tiktok-video-element": "^0.1.0", "twitch-video-element": "^0.1.2", "vimeo-video-element": "^1.5.3", "wistia-video-element": "^1.3.3", "youtube-video-element": "^1.6.1" }, "peerDependencies": { "@types/react": "^17.0.0 || ^18 || ^19", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" } }, "sha512-wE/xLloneXZ1keelFCaNeIFVNUp4/7YoUjfHjwF945aQzsbDKiIB0LQuCchGL+la0Y1IybxnR0R6Cm3AiqInMw=="], - - "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], - - "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - - "react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="], - - "react-tracked": ["react-tracked@1.7.14", "", { "dependencies": { "proxy-compare": "2.6.0", "use-context-selector": "1.4.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": "*", "react-native": "*", "scheduler": ">=0.19.0" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-6UMlgQeRAGA+uyYzuQGm7kZB6ZQYFhc7sntgP7Oxwwd6M0Ud/POyb4K3QWT1eXvoifSa80nrAWnXWFGpOvbwkw=="], - - "react-tweet": ["react-tweet@3.2.2", "", { "dependencies": { "@swc/helpers": "^0.5.3", "clsx": "^2.0.0", "swr": "^2.2.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-hIkxAVPpN2RqWoDEbo3TTnN/pDcp9/Jb6pTgiA4EbXa9S+m2vHIvvZKHR+eS0PDIsYqe+zTmANRa5k6+/iwGog=="], - - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "redux": ["redux@4.2.1", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w=="], - - "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - - "remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="], - - "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], - - "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], - - "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - - "roughjs": ["roughjs@4.6.4", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-s6EZ0BntezkFYMf/9mGn7M8XGIoaav9QQBCnJROWB3brUWQ683Q2LbRD/hq0Z3bAJ/9NVpU/5LpiTWvQMyLDhw=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], - - "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "sass": ["sass@1.51.0", "", { "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { "sass": "sass.js" } }, "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA=="], - - "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], - - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - - "slate": ["slate@0.118.1", "", { "dependencies": { "immer": "^10.0.3", "tiny-warning": "^1.0.3" } }, "sha512-6H1DNgnSwAFhq/pIgf+tLvjNzH912M5XrKKhP9Frmbds2zFXdSJ6L/uFNyVKxQIkPzGWPD0m+wdDfmEuGFH5Tg=="], - - "slate-dom": ["slate-dom@0.118.1", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "slate": ">=0.99.0" } }, "sha512-D6J0DF9qdJrXnRDVhYZfHzzpVxzqKRKFfS0Wcin2q0UC+OnQZ0lbCGJobatVbisOlbSe7dYFHBp9OZ6v1lEcbQ=="], - - "slate-hyperscript": ["slate-hyperscript@0.115.0", "", { "peerDependencies": { "slate": ">=0.114.3" } }, "sha512-aaQ1XSfUhw0Lf4cwVLeNFYnnPsC9iX9aEmKvT5PAaGTNVe1LaBCAXB+CFuqp7YPExPj9hYuS5CsIu8dAh9JX2w=="], - - "slate-react": ["slate-react@0.117.4", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0", "slate": ">=0.114.0", "slate-dom": ">=0.116.0" } }, "sha512-9ckilyUzQS1VHJnstIpgInhcWnTDgv2Cd7m1HOQVl3zasChoapPSMftzT/wl/48grZaZYZIi4xVuzGTcFRUWFg=="], - - "sliced": ["sliced@1.0.1", "", {}, "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA=="], - - "slick": ["slick@1.12.2", "", {}, "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A=="], - - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "spotify-audio-element": ["spotify-audio-element@1.0.3", "", {}, "sha512-I1/qD8cg/UnTlCIMiKSdZUJTyYfYhaqFK7LIVElc48eOqUUbVCaw1bqL8I6mJzdMJTh3eoNyF/ewvB7NoS/g9A=="], - - "sqids": ["sqids@0.3.0", "", {}, "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - - "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], - - "super-media-element": ["super-media-element@1.4.2", "", {}, "sha512-9pP/CVNp4NF2MNlRzLwQkjiTgKKe9WYXrLh9+8QokWmMxz+zt2mf1utkWLco26IuA3AfVcTb//qtlTIjY3VHxA=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "swr": ["swr@2.3.6", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw=="], - - "tabbable": ["tabbable@6.3.0", "", {}, "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ=="], - - "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], - - "tailwind-scrollbar-hide": ["tailwind-scrollbar-hide@4.0.0", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || >= 4.0.0 || >= 4.0.0-beta.8 || >= 4.0.0-alpha.20" } }, "sha512-gobtvVcThB2Dxhy0EeYSS1RKQJ5baDFkamkhwBvzvevwX6L4XQfpZ3me9s25Ss1ecFVT5jPYJ50n+7xTBJG9WQ=="], - - "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], - - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - - "text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="], - - "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], - - "tiktok-video-element": ["tiktok-video-element@0.1.1", "", {}, "sha512-BaiVzvNz2UXDKTdSrXzrNf4q6Ecc+/utYUh7zdEu2jzYcJVDoqYbVfUl0bCfMoOeeAqg28vD/yN63Y3E9jOrlA=="], - - "tiny-invariant": ["tiny-invariant@1.3.1", "", {}, "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="], - - "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], - - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "toggle-selection": ["toggle-selection@1.0.6", "", {}, "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="], - - "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - - "trpc-cli": ["trpc-cli@0.12.1", "", { "dependencies": { "commander": "^14.0.0" }, "peerDependencies": { "@orpc/server": "^1.0.0", "@trpc/server": "^10.45.2 || ^11.0.1", "@valibot/to-json-schema": "^1.1.0", "effect": "^3.14.2 || ^4.0.0", "valibot": "^1.1.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@orpc/server", "@trpc/server", "@valibot/to-json-schema", "effect", "valibot", "zod"], "bin": { "trpc-cli": "dist/bin.js" } }, "sha512-/D/mIQf3tUrS7ZKJZ1gmSPJn2psAABJfkC5Eevm55SZ4s6KwANOUNlwhAGXN9HT4VSJVfoF2jettevE9vHPQlg=="], - - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], - - "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], - - "ts-essentials": ["ts-essentials@10.1.0", "", { "peerDependencies": { "typescript": ">=4.5.0" }, "optionalPeers": ["typescript"] }, "sha512-LirrVzbhIpFQ9BdGfqLnM9r7aP9rnyfeoxbP5ZEkdr531IaY21+KdebRSsbvqu28VDJtcDDn+AlGn95t0c52zQ=="], - - "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], - - "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], - - "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - - "twitch-video-element": ["twitch-video-element@0.1.5", "", {}, "sha512-3UdWMa5ytWFdpgJAM6XEqqRK/1FvWdJVcKDOw4IHBPt4p52E+4fXT42fBdRZFfoxBPXQNZUDDNHFW8wIopD7Og=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="], - - "ultracite": ["ultracite@6.3.6", "", { "dependencies": { "@clack/prompts": "^0.11.0", "@trpc/server": "^11.7.1", "deepmerge": "^4.3.1", "glob": "^12.0.0", "jsonc-parser": "^3.3.1", "nypm": "^0.6.2", "trpc-cli": "^0.12.0", "zod": "^4.1.12" }, "bin": { "ultracite": "dist/index.js" } }, "sha512-GTDos8mpW3Z/4yMGjZj3ux8HZxznYkV+1WGtDCHE8cuLqgICjJMhbdjTN/foL3Za9Hhr2ZloKMIrj3hiwPrN8A=="], - - "undici": ["undici@6.22.0", "", {}, "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - - "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], - - "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], - - "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], - - "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - - "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], - - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], - - "uploadthing": ["uploadthing@7.7.4", "", { "dependencies": { "@effect/platform": "0.90.3", "@standard-schema/spec": "1.0.0-beta.4", "@uploadthing/mime-types": "0.3.6", "@uploadthing/shared": "7.1.10", "effect": "3.17.7" }, "peerDependencies": { "express": "*", "h3": "*", "tailwindcss": "^3.0.0 || ^4.0.0-beta.0" }, "optionalPeers": ["express", "h3", "tailwindcss"] }, "sha512-rlK/4JWHW5jP30syzWGBFDDXv3WJDdT8gn9OoxRJmXLoXi94hBmyyjxihGlNrKhBc81czyv8TkzMioe/OuKGfA=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], - - "use-composed-ref": ["use-composed-ref@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w=="], - - "use-context-selector": ["use-context-selector@1.4.4", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": "*", "react-native": "*", "scheduler": ">=0.19.0" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-pS790zwGxxe59GoBha3QYOwk8AFGp4DN6DOtH+eoqVmgBBRXVx4IlPDhJmmMiNQAgUaLlP+58aqRC3A4rdaSjg=="], - - "use-deep-compare": ["use-deep-compare@1.3.0", "", { "dependencies": { "dequal": "2.0.3" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-94iG+dEdEP/Sl3WWde+w9StIunlV8Dgj+vkt5wTwMoFQLaijiEZSXXy8KtcStpmEDtIptRJiNeD4ACTtVvnIKA=="], - - "use-file-picker": ["use-file-picker@2.1.2", "", { "dependencies": { "file-selector": "0.2.4" }, "peerDependencies": { "react": ">=16" } }, "sha512-ZEIzRi1wXeIXDWr5i55gRBVER8rTkSGskDUY94bciTTAZJHlBnOTRLL/LDYjgz6d+US3yELHnRvtBhLxFGtB0A=="], - - "use-isomorphic-layout-effect": ["use-isomorphic-layout-effect@1.2.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA=="], - - "use-latest": ["use-latest@1.3.0", "", { "dependencies": { "use-isomorphic-layout-effect": "^1.1.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ=="], - - "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], - - "utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="], - - "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], - - "uvu": ["uvu@0.5.6", "", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="], - - "valid-data-url": ["valid-data-url@3.0.1", "", {}, "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA=="], - - "validator": ["validator@13.15.23", "", {}, "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw=="], - - "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], - - "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - - "vimeo-video-element": ["vimeo-video-element@1.6.1", "", { "dependencies": { "@vimeo/player": "2.29.0" } }, "sha512-UwDLzhgg98pct1xb6799I1vRDXIzaAX6rs1TG/QOf6y+VrXpTFrI7mYz2gnj9QCtBcGK68f4z64A+MRYRsLJaQ=="], - - "weakmap-polyfill": ["weakmap-polyfill@2.0.4", "", {}, "sha512-ZzxBf288iALJseijWelmECm/1x7ZwQn3sMYIkDr2VvZp7r6SEKuT8D0O9Wiq6L9Nl5mazrOMcmiZE/2NCenaxw=="], - - "web-resource-inliner": ["web-resource-inliner@7.0.0", "", { "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", "htmlparser2": "^5.0.0", "mime": "^2.4.6", "valid-data-url": "^3.0.0" } }, "sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg=="], - - "web-worker": ["web-worker@1.5.0", "", {}, "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw=="], - - "webworkify": ["webworkify@1.5.0", "", {}, "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g=="], - - "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], - - "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "wistia-video-element": ["wistia-video-element@1.3.5", "", { "dependencies": { "super-media-element": "~1.4.2" } }, "sha512-aIG0xEtclPb9xfklAkOwHFv/BMiH3Ql0yWWKQ1XyUCoSDaF3sOD+JNLmakOChvn2LLUX7FqH/mYb8bXT4ACnMw=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "youtube-video-element": ["youtube-video-element@1.8.0", "", {}, "sha512-u3M0MgO+KUtVwIyKJXZXXJ0As0k6d5NflOrh1GjyG8NNOp+liW2nFU29hpXeUcxUWbVKhudIYd39hMVeEgCilQ=="], - - "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="], - - "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], - - "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], - - "zustand-x": ["zustand-x@6.2.1", "", { "dependencies": { "immer": "^10.0.3", "lodash.mapvalues": "^4.6.0", "mutative": "1.1.0", "react-tracked": "^1.7.11", "use-sync-external-store": "1.4.0" }, "peerDependencies": { "zustand": ">=5.0.2" } }, "sha512-y3nQMQNx3BORY95vpuodJvh/8AqQu++S3q6mJYBSo1J0Q168Sy+FatqER658YESDqv2bwviXcIT3bgl/Ip6M5g=="], - - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - - "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - - "@ai-sdk/react/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.7", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA=="], - - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg=="], - - "@excalidraw/excalidraw/clsx": ["clsx@1.1.1", "", {}, "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="], - - "@excalidraw/excalidraw/nanoid": ["nanoid@3.3.3", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w=="], - - "@excalidraw/excalidraw/pako": ["pako@2.0.3", "", {}, "sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw=="], - - "@excalidraw/mermaid-to-excalidraw/nanoid": ["nanoid@4.0.2", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw=="], - - "@mux/mux-data-google-ima/mux-embed": ["mux-embed@5.9.0", "", {}, "sha512-wmunL3uoPhma/tWy8PrDPZkvJpXvSFBwbD3KkC4PG8Ztjfb1X3hRJwGUAQyRz7z99b/ovLm2UTTitrkvStjH4w=="], - - "@platejs/core/jotai": ["jotai@2.8.4", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-f6jwjhBJcDtpeauT2xH01gnqadKEySwwt1qNBLvAXcnojkmb76EdqRt05Ym8IamfHGAQz2qMKAwftnyjeSoHAA=="], - - "@platejs/core/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-avatar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], - - "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-tabs/@radix-ui/primitive": ["@radix-ui/primitive@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA=="], - - "@radix-ui/react-tabs/@radix-ui/react-context": ["@radix-ui/react-context@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg=="], - - "@radix-ui/react-tabs/@radix-ui/react-direction": ["@radix-ui/react-direction@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ=="], - - "@radix-ui/react-tabs/@radix-ui/react-id": ["@radix-ui/react-id@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw=="], - - "@radix-ui/react-tabs/@radix-ui/react-presence": ["@radix-ui/react-presence@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-use-layout-effect": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA=="], - - "@radix-ui/react-tabs/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.0", "@radix-ui/react-collection": "1.0.1", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-context": "1.0.0", "@radix-ui/react-direction": "1.0.0", "@radix-ui/react-id": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-use-callback-ref": "1.0.0", "@radix-ui/react-use-controllable-state": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA=="], - - "@radix-ui/react-tabs/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg=="], - - "@radix-ui/react-toolbar/@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], - - "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-use-is-hydrated/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - - "@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "ai/@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.15", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-xySXoQ29+KbGuGfmDnABx+O6vc7Gj7qugmj1kGpn0rW0rQNn6UKUuvscKMzWyv1Uv05GyC1vqHq8ZhEOLfXscQ=="], - - "ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.7", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA=="], - - "aria-hidden/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - - "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - - "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - - "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "effect/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "file-selector/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - - "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - - "juice/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], - - "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - - "mermaid/katex": ["katex@0.16.25", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q=="], - - "mermaid/mdast-util-from-markdown": ["mdast-util-from-markdown@1.3.1", "", { "dependencies": { "@types/mdast": "^3.0.0", "@types/unist": "^2.0.0", "decode-named-character-reference": "^1.0.0", "mdast-util-to-string": "^3.1.0", "micromark": "^3.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-decode-string": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "unist-util-stringify-position": "^3.0.0", "uvu": "^0.5.0" } }, "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww=="], - - "micromark-extension-math/katex": ["katex@0.16.25", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q=="], - - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - - "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], - - "player.style/media-chrome": ["media-chrome@4.14.0", "", { "dependencies": { "ce-la-react": "^0.3.0" } }, "sha512-IEdFb4blyF15vLvQzLIn6USJBv7Kf2ne+TfLQKBYI5Z0f9VEBVZz5MKy4Uhi0iA9lStl2S9ENIujJRuJIa5OiA=="], - - "points-on-path/points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], - - "react-remove-scroll/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "react-remove-scroll-bar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "roughjs/points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], - - "use-callback-ref/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "use-file-picker/file-selector": ["file-selector@0.2.4", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA=="], - - "use-sidecar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "web-resource-inliner/htmlparser2": ["htmlparser2@5.0.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^3.3.0", "domutils": "^2.4.2", "entities": "^2.0.0" } }, "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ=="], - - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "@ai-sdk/react/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="], - - "@radix-ui/react-tabs/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ=="], - - "@radix-ui/react-tabs/@radix-ui/react-presence/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-tabs/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - - "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0", "@radix-ui/react-context": "1.0.0", "@radix-ui/react-primitive": "1.0.1", "@radix-ui/react-slot": "1.0.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g=="], - - "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], - - "@radix-ui/react-tabs/@radix-ui/react-use-controllable-state/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "ai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], - - "d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], - - "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - - "mermaid/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "mermaid/mdast-util-from-markdown/@types/mdast": ["@types/mdast@3.0.15", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ=="], - - "mermaid/mdast-util-from-markdown/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - - "mermaid/mdast-util-from-markdown/mdast-util-to-string": ["mdast-util-to-string@3.2.0", "", { "dependencies": { "@types/mdast": "^3.0.0" } }, "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg=="], - - "mermaid/mdast-util-from-markdown/micromark": ["micromark@3.2.0", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "micromark-core-commonmark": "^1.0.1", "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-combine-extensions": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-sanitize-uri": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA=="], - - "mermaid/mdast-util-from-markdown/micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw=="], - - "mermaid/mdast-util-from-markdown/micromark-util-decode-string": ["micromark-util-decode-string@1.1.0", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-decode-numeric-character-reference": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ=="], - - "mermaid/mdast-util-from-markdown/micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q=="], - - "mermaid/mdast-util-from-markdown/micromark-util-symbol": ["micromark-util-symbol@1.1.0", "", {}, "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag=="], - - "mermaid/mdast-util-from-markdown/micromark-util-types": ["micromark-util-types@1.1.0", "", {}, "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg=="], - - "mermaid/mdast-util-from-markdown/unist-util-stringify-position": ["unist-util-stringify-position@3.0.3", "", { "dependencies": { "@types/unist": "^2.0.0" } }, "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg=="], - - "micromark-extension-math/katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "tunnel-rat/zustand/use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - - "use-file-picker/file-selector/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "web-resource-inliner/htmlparser2/domhandler": ["domhandler@3.3.0", "", { "dependencies": { "domelementtype": "^2.0.1" } }, "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA=="], - - "web-resource-inliner/htmlparser2/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], - - "web-resource-inliner/htmlparser2/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-popper/@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-portal/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="], - - "@excalidraw/excalidraw/@radix-ui/react-popover/@radix-ui/react-use-controllable-state/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="], - - "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.0.0", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA=="], - - "@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.0" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0" } }, "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark": ["micromark-core-commonmark@1.1.0", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-factory-destination": "^1.0.0", "micromark-factory-label": "^1.0.0", "micromark-factory-space": "^1.0.0", "micromark-factory-title": "^1.0.0", "micromark-factory-whitespace": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-chunked": "^1.0.0", "micromark-util-classify-character": "^1.0.0", "micromark-util-html-tag-name": "^1.0.0", "micromark-util-normalize-identifier": "^1.0.0", "micromark-util-resolve-all": "^1.0.0", "micromark-util-subtokenize": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.1", "uvu": "^0.5.0" } }, "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-factory-space": ["micromark-factory-space@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-chunked": ["micromark-util-chunked@1.1.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0" } }, "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-combine-extensions": ["micromark-util-combine-extensions@1.1.0", "", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-encode": ["micromark-util-encode@1.1.0", "", {}, "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-resolve-all": ["micromark-util-resolve-all@1.1.0", "", { "dependencies": { "micromark-util-types": "^1.0.0" } }, "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@1.2.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-encode": "^1.0.0", "micromark-util-symbol": "^1.0.0" } }, "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-util-subtokenize": ["micromark-util-subtokenize@1.1.0", "", { "dependencies": { "micromark-util-chunked": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A=="], - - "mermaid/mdast-util-from-markdown/micromark-util-decode-string/micromark-util-character": ["micromark-util-character@1.2.0", "", { "dependencies": { "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg=="], - - "web-resource-inliner/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], - - "web-resource-inliner/htmlparser2/domutils/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-destination": ["micromark-factory-destination@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-label": ["micromark-factory-label@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0", "uvu": "^0.5.0" } }, "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-title": ["micromark-factory-title@1.1.0", "", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-factory-whitespace": ["micromark-factory-whitespace@1.1.0", "", { "dependencies": { "micromark-factory-space": "^1.0.0", "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-classify-character": ["micromark-util-classify-character@1.1.0", "", { "dependencies": { "micromark-util-character": "^1.0.0", "micromark-util-symbol": "^1.0.0", "micromark-util-types": "^1.0.0" } }, "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw=="], - - "mermaid/mdast-util-from-markdown/micromark/micromark-core-commonmark/micromark-util-html-tag-name": ["micromark-util-html-tag-name@1.2.0", "", {}, "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q=="], - } -} diff --git a/templates/plate-playground-template/package.json b/templates/plate-playground-template/package.json index 86932f5837..e22007671a 100644 --- a/templates/plate-playground-template/package.json +++ b/templates/plate-playground-template/package.json @@ -64,7 +64,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@udecode/cn": "^52.0.1", "@uploadthing/react": "7.3.3", - "ai": "5.0.28", + "ai": "5.0.52", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -74,7 +74,7 @@ "lodash": "^4.17.21", "lowlight": "^3.3.0", "lucide-react": "^0.554.0", - "next": "16.0.3", + "next": "16.1.5", "pdf-lib": "^1.17.1", "platejs": "^52.0.1", "react": "19.2.0", diff --git a/templates/plate-template/bun.lock b/templates/plate-template/bun.lock deleted file mode 100644 index 45282ed6fa..0000000000 --- a/templates/plate-template/bun.lock +++ /dev/null @@ -1,861 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "plate-template", - "dependencies": { - "@platejs/basic-nodes": "^52.0.1", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-toolbar": "^1.1.11", - "@radix-ui/react-tooltip": "^1.2.8", - "class-variance-authority": "0.7.1", - "clsx": "^2.1.1", - "lucide-react": "0.554.0", - "next": "16.0.3", - "platejs": "^52.0.1", - "react": "19.2.0", - "react-dom": "19.2.0", - "tailwind-merge": "3.4.0", - "tw-animate-css": "^1.4.0", - }, - "devDependencies": { - "@biomejs/biome": "2.3.7", - "@tailwindcss/postcss": "4.1.17", - "@types/node": "^24.10.1", - "@types/react": "19.2.7", - "@types/react-dom": "19.2.3", - "@typescript-eslint/parser": "^8.47.0", - "babel-plugin-react-compiler": "^1.0.0", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "7.0.1", - "lefthook": "^2.0.4", - "postcss": "^8.5.6", - "tailwindcss": "4.1.17", - "typescript": "5.9.3", - "ultracite": "6.3.6", - }, - }, - }, - "packages": { - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], - - "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], - - "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], - - "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], - - "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - - "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], - - "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], - - "@biomejs/biome": ["@biomejs/biome@2.3.7", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.7", "@biomejs/cli-darwin-x64": "2.3.7", "@biomejs/cli-linux-arm64": "2.3.7", "@biomejs/cli-linux-arm64-musl": "2.3.7", "@biomejs/cli-linux-x64": "2.3.7", "@biomejs/cli-linux-x64-musl": "2.3.7", "@biomejs/cli-win32-arm64": "2.3.7", "@biomejs/cli-win32-x64": "2.3.7" }, "bin": { "biome": "bin/biome" } }, "sha512-CTbAS/jNAiUc6rcq94BrTB8z83O9+BsgWj2sBCQg9rD6Wkh2gjfR87usjx0Ncx0zGXP1NKgT7JNglay5Zfs9jw=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LirkamEwzIUULhXcf2D5b+NatXKeqhOwilM+5eRkbrnr6daKz9rsBL0kNZ16Hcy4b8RFq22SG4tcLwM+yx/wFA=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Q4TO633kvrMQkKIV7wmf8HXwF0dhdTD9S458LGE24TYgBjSRbuhvio4D5eOQzirEYg6eqxfs53ga/rbdd8nBKg=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-inHOTdlstUBzgjDcx0ge71U4SVTbwAljmkfi3MC5WzsYCRhancqfeL+sa4Ke6v2ND53WIwCFD5hGsYExoI3EZQ=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-/afy8lto4CB8scWfMdt+NoCZtatBUF62Tk3ilWH2w8ENd5spLhM77zKlFZEvsKJv9AFNHknMl03zO67CiklL2Q=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-fJMc3ZEuo/NaMYo5rvoWjdSS5/uVSW+HPRQujucpZqm2ZCq71b8MKJ9U4th9yrv2L5+5NjPF0nqqILCl8HY/fg=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.7", "", { "os": "linux", "cpu": "x64" }, "sha512-CQUtgH1tIN6e5wiYSJqzSwJumHYolNtaj1dwZGCnZXm2PZU1jOJof9TsyiP3bXNDb+VOR7oo7ZvY01If0W3iFQ=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJAE8eCNyRpcfx2JJAtsPtISnELJ0H4xVVSwnxm13bzI8RwbXMyVtxy2r5DV1xT3WiSP+7LxORcApWw0LM8HiA=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.7", "", { "os": "win32", "cpu": "x64" }, "sha512-pulzUshqv9Ed//MiE8MOUeeEkbkSHVDVY5Cz5wVAnH1DUqliCQG3j6s1POaITTFqFfo7AVIx2sWdKpx/GS+Nqw=="], - - "@clack/core": ["@clack/core@0.5.0", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow=="], - - "@clack/prompts": ["@clack/prompts@0.11.0", "", { "dependencies": { "@clack/core": "0.5.0", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], - - "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], - - "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - - "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], - - "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], - - "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], - - "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], - - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], - - "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], - - "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], - - "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@juggle/resize-observer": ["@juggle/resize-observer@3.4.0", "", {}, "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="], - - "@next/env": ["@next/env@16.0.3", "", {}, "sha512-IqgtY5Vwsm14mm/nmQaRMmywCU+yyMIYfk3/MHZ2ZTJvwVbBn3usZnjMi1GacrMVzVcAxJShTCpZlPs26EdEjQ=="], - - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MOnbd92+OByu0p6QBAzq1ahVWzF6nyfiH07dQDez4/Nku7G249NjxDVyEfVhz8WkLiOEU+KFVnqtgcsfP2nLXg=="], - - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-i70C4O1VmbTivYdRlk+5lj9xRc2BlK3oUikt3yJeHT1unL4LsNtN7UiOhVanFdc7vDAgZn1tV/9mQwMkWOJvHg=="], - - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-O88gCZ95sScwD00mn/AtalyCoykhhlokxH/wi1huFK+rmiP5LAYVs/i2ruk7xST6SuXN4NI5y4Xf5vepb2jf6A=="], - - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-CEErFt78S/zYXzFIiv18iQCbRbLgBluS8z1TNDQoyPi8/Jr5qhR3e8XHAIxVxPBjDbEMITprqELVc5KTfFj0gg=="], - - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Tc3i+nwt6mQ+Dwzcri/WNDj56iWdycGVh5YwwklleClzPzz7UpfaMw1ci7bLl6GRYMXhWDBfe707EXNjKtiswQ=="], - - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-zTh03Z/5PBBPdTurgEtr6nY0vI9KR9Ifp/jZCcHlODzwVOEKcKRBtQIGrkc7izFgOMuXDEJBmirwpGqdM/ZixA=="], - - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-Jc1EHxtZovcJcg5zU43X3tuqzl/sS+CmLgjRP28ZT4vk869Ncm2NoF8qSTaL99gh6uOzgM99Shct06pSO6kA6g=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-N7EJ6zbxgIYpI/sWNzpVKRMbfEGgsWuOIvzkML7wxAAZhPk1Msxuo/JDu1PKjWGrAoOLaZcIX5s+/pF5LIbBBg=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@platejs/basic-nodes": ["@platejs/basic-nodes@52.0.1", "", { "peerDependencies": { "platejs": ">=52.0.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-s6leoufEsraw1sFmXD73OA45UisCvAiU7+w5t4kkRTJqYMpvUPzAvpOP+gW4JC5GJ8/dptLZ2uSuaJd9YyeHcA=="], - - "@platejs/core": ["@platejs/core@52.0.1", "", { "dependencies": { "@platejs/slate": "52.0.1", "@udecode/react-hotkeys": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1", "clsx": "^2.1.1", "html-entities": "^2.6.0", "is-hotkey": "^0.2.0", "jotai": "~2.8.4", "jotai-optics": "0.4.0", "jotai-x": "2.3.3", "lodash": "^4.17.21", "nanoid": "^5.1.5", "optics-ts": "2.4.1", "slate": "0.118.1", "slate-dom": "0.118.1", "slate-hyperscript": "0.115.0", "slate-react": "0.117.4", "use-deep-compare": "^1.3.0", "zustand": "^5.0.5", "zustand-x": "6.2.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-+1LSUkt8aF+Heeb8M26CcfYxQG6dqel2v84hr5YsCG1bhmN7MpvhYymF2qzf93juFZcIHxsC3R1uv/sjOd7DBw=="], - - "@platejs/slate": ["@platejs/slate@52.0.1", "", { "dependencies": { "@udecode/utils": "52.0.1", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "slate": "0.118.1", "slate-dom": "0.118.1" } }, "sha512-+Qx/aTVL5znwYMnjN++PxVmS7ywkoDXM4JYY6TgMAvszce5qFJOKjmxXzliFurqiojoPeyFllyoSO8dQWDpcJA=="], - - "@platejs/utils": ["@platejs/utils@52.0.1", "", { "dependencies": { "@platejs/core": "52.0.1", "@platejs/slate": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1", "clsx": "^2.1.1", "lodash": "^4.17.21" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-goZ+Be/Zl0525BKv6DK8I4I04Um4E5oIsoXinjHBxzJ8cw1lJzs3n1NQF4K18jH5oxYviljvoRAgzwRxylUlCw=="], - - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], - - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], - - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], - - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], - - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], - - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], - - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], - - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], - - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], - - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], - - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - - "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], - - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], - - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], - - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], - - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], - - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], - - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], - - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], - - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], - - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], - - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], - - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], - - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], - - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="], - - "@trpc/server": ["@trpc/server@11.7.2", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-AgB26PXY69sckherIhCacKLY49rxE2XP5h38vr/KMZTbLCL1p8IuIoKPjALTcugC2kbyQ7Lbqo2JDVfRSmPmfQ=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.47.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.47.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.47.0", "@typescript-eslint/types": "^8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0" } }, "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.47.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.47.0", "", {}, "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.47.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.47.0", "@typescript-eslint/tsconfig-utils": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ=="], - - "@udecode/react-hotkeys": ["@udecode/react-hotkeys@52.0.1", "", { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-o+3CRtOjdcVInlaVOC8wcXA8zjy6i7YL4nlTD2oI4Fw2ZqhxUkKF+y34CitgOBe9eItW6U7JcgAyib5rtqhFjg=="], - - "@udecode/react-utils": ["@udecode/react-utils@52.0.1", "", { "dependencies": { "@radix-ui/react-slot": "^1.2.3", "@udecode/utils": "52.0.1", "clsx": "^2.1.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-zdSfCfX7AWzut8J22Az7usrHM/uSwlzJXRX3jnidx4rhE+YzV2BJfq4q8w0SbHq8Y4Peg8VlP0r1y8z8p2HSUw=="], - - "@udecode/utils": ["@udecode/utils@52.0.1", "", {}, "sha512-T/qa4++J2MMY9CbhZ05ONpcpkvsRV0uBvOfMhhYWKkAND4DSVAVzmZTV7WQ5fjGq81agAped0WQxcqsEg617uQ=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - - "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.8.31", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw=="], - - "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], - - "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="], - - "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], - - "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - - "direction": ["direction@1.0.4", "", { "bin": { "direction": "cli.js" } }, "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.259", "", {}, "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ=="], - - "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], - - "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], - - "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], - - "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], - - "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - - "glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], - - "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], - - "html-entities": ["html-entities@2.6.0", "", {}, "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], - - "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-hotkey": ["is-hotkey@0.2.0", "", {}, "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], - - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "jotai": ["jotai@2.8.4", "", { "peerDependencies": { "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-f6jwjhBJcDtpeauT2xH01gnqadKEySwwt1qNBLvAXcnojkmb76EdqRt05Ym8IamfHGAQz2qMKAwftnyjeSoHAA=="], - - "jotai-optics": ["jotai-optics@0.4.0", "", { "peerDependencies": { "jotai": ">=2.0.0", "optics-ts": ">=2.0.0" } }, "sha512-osbEt9AgS55hC4YTZDew2urXKZkaiLmLqkTS/wfW5/l0ib8bmmQ7kBXSFaosV6jDDWSp00IipITcJARFHdp42g=="], - - "jotai-x": ["jotai-x@2.3.3", "", { "peerDependencies": { "@types/react": ">=17.0.0", "jotai": ">=2.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@types/react", "react"] }, "sha512-ZeSPjf77VINlJ0HyMfYcPv/9psjB0CtJIZP6S+s/eefaO/9+U37M9Jx5dWmILgTe8hAol99EbAv6DDrHobOucA=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "lefthook": ["lefthook@2.0.4", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.0.4", "lefthook-darwin-x64": "2.0.4", "lefthook-freebsd-arm64": "2.0.4", "lefthook-freebsd-x64": "2.0.4", "lefthook-linux-arm64": "2.0.4", "lefthook-linux-x64": "2.0.4", "lefthook-openbsd-arm64": "2.0.4", "lefthook-openbsd-x64": "2.0.4", "lefthook-windows-arm64": "2.0.4", "lefthook-windows-x64": "2.0.4" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-GNCU2vQWM/UWjiEF23601aILi1aMbPke6viortH7wIO/oVGOCW0H6FdLez4XZDyqnHL9XkTnd0BBVrBbYVMLpA=="], - - "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-AR63/O5UkM7Sc6x5PhP4vTuztTYRBeBroXApeWGM/8e5uZyoQug/7KTh7xhbCMDf8WJv6vdFeXAQCPSmDyPU3Q=="], - - "lefthook-darwin-x64": ["lefthook-darwin-x64@2.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-618DVUttSzV9egQiqTQoxGfnR240JoPWYmqRVHhiegnQKZ2lp5XJ+7NMxeRk/ih93VVOLzFO5ky3PbpxTmJgjQ=="], - - "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.0.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-mTAQym1BK38fKglHBQ/0GXPznVC4LoStHO5lAI3ZxaEC0FQetqGHYFzhWbIH5sde9JhztE2rL/aBzMHDoAtzSw=="], - - "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.0.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-sy02aSxd8UMd6XmiPFVl/Em0b78jdZcDSsLwg+bweJQQk0l+vJhOfqFiG11mbnpo+EBIZmRe6OH5LkxeSU36+w=="], - - "lefthook-linux-arm64": ["lefthook-linux-arm64@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-W0Nlr/Cz2QTH9n4k5zNrk3LSsg1C4wHiJi8hrAiQVTaAV/N1XrKqd0DevqQuouuapG6pw/6B1xCgiNPebv9oyw=="], - - "lefthook-linux-x64": ["lefthook-linux-x64@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-N6ySVCtB/DrOZ1ZgPL8WBZTgtoVHvcPKI+LV5wbcGrvA/dzDZFvniadrbDWZg7Tm705efiQzyENjwhhqNkwiww=="], - - "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.0.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-VmOhJO3pYzZ/1C2WFXtL/n5pq4/eYOroqJJpwTJfmCHyw4ceLACu8MDyU5AMJhGMkbL8mPxGInJKxg5xhYgGRw=="], - - "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.0.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-U8MZz1xlHUdflkQQ2hkMQsei6fSZbs8tuE4EjCIHWnNdnAF4V8sZ6n1KbxsJcoZXPyBZqxZSMu1o/Ye8IAMVKg=="], - - "lefthook-windows-arm64": ["lefthook-windows-arm64@2.0.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-543H3y2JAwNdvwUQ6nlNBG7rdKgoOUgzAa6pYcl6EoqicCRrjRmGhkJu7vUudkkrD2Wjm7tr9hU9poP2g5fRFQ=="], - - "lefthook-windows-x64": ["lefthook-windows-x64@2.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UDEPK9RWKm60xsNOdS/DQOdFba0SFa4w3tpFMXK1AJzmRHhosoKrorXGhtTr6kcM0MGKOtYi8GHsm++ArZ9wvQ=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], - - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], - - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], - - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], - - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], - - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], - - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], - - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], - - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], - - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], - - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], - - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], - - "lodash.mapvalues": ["lodash.mapvalues@4.6.0", "", {}, "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ=="], - - "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mutative": ["mutative@1.1.0", "", {}, "sha512-2PJADREjOusk3iJkD3rXV2YjAxTuaLxdfqtqTEt6vcY07LtEBR1seHuBHXWEIuscqRDGvbauYPs+A4Rj/KTczQ=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "next": ["next@16.0.3", "", { "dependencies": { "@next/env": "16.0.3", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.3", "@next/swc-darwin-x64": "16.0.3", "@next/swc-linux-arm64-gnu": "16.0.3", "@next/swc-linux-arm64-musl": "16.0.3", "@next/swc-linux-x64-gnu": "16.0.3", "@next/swc-linux-x64-musl": "16.0.3", "@next/swc-win32-arm64-msvc": "16.0.3", "@next/swc-win32-x64-msvc": "16.0.3", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Ka0/iNBblPFcIubTA1Jjh6gvwqfjrGq1Y2MTI5lbjeLIAfmC+p5bQmojpRZqgHHVu5cG4+qdIiwXiBSm/8lZ3w=="], - - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], - - "optics-ts": ["optics-ts@2.4.1", "", {}, "sha512-HaYzMHvC80r7U/LqAd4hQyopDezC60PO2qF5GuIwALut2cl5rK1VWHsqTp0oqoJJWjiv6uXKqsO+Q2OO0C3MmQ=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], - - "platejs": ["platejs@52.0.1", "", { "dependencies": { "@platejs/core": "52.0.1", "@platejs/slate": "52.0.1", "@platejs/utils": "52.0.1", "@udecode/react-hotkeys": "52.0.1", "@udecode/react-utils": "52.0.1", "@udecode/utils": "52.0.1" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-pus4AFlqTdUuSidQYlsEU5w43lNAxpfPvvg1i2Q3zQ9dgLJzESrVY/ZfZabnqbhADVJTNBbhYhmC/YLN6Hazog=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "proxy-compare": ["proxy-compare@2.6.0", "", {}, "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], - - "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], - - "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], - - "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - - "react-tracked": ["react-tracked@1.7.14", "", { "dependencies": { "proxy-compare": "2.6.0", "use-context-selector": "1.4.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": "*", "react-native": "*", "scheduler": ">=0.19.0" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-6UMlgQeRAGA+uyYzuQGm7kZB6ZQYFhc7sntgP7Oxwwd6M0Ud/POyb4K3QWT1eXvoifSa80nrAWnXWFGpOvbwkw=="], - - "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], - - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], - - "slate": ["slate@0.118.1", "", { "dependencies": { "immer": "^10.0.3", "tiny-warning": "^1.0.3" } }, "sha512-6H1DNgnSwAFhq/pIgf+tLvjNzH912M5XrKKhP9Frmbds2zFXdSJ6L/uFNyVKxQIkPzGWPD0m+wdDfmEuGFH5Tg=="], - - "slate-dom": ["slate-dom@0.118.1", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "is-plain-object": "^5.0.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "slate": ">=0.99.0" } }, "sha512-D6J0DF9qdJrXnRDVhYZfHzzpVxzqKRKFfS0Wcin2q0UC+OnQZ0lbCGJobatVbisOlbSe7dYFHBp9OZ6v1lEcbQ=="], - - "slate-hyperscript": ["slate-hyperscript@0.115.0", "", { "peerDependencies": { "slate": ">=0.114.3" } }, "sha512-aaQ1XSfUhw0Lf4cwVLeNFYnnPsC9iX9aEmKvT5PAaGTNVe1LaBCAXB+CFuqp7YPExPj9hYuS5CsIu8dAh9JX2w=="], - - "slate-react": ["slate-react@0.117.4", "", { "dependencies": { "@juggle/resize-observer": "^3.4.0", "direction": "^1.0.4", "is-hotkey": "^0.2.0", "lodash": "^4.17.21", "scroll-into-view-if-needed": "^3.1.0", "tiny-invariant": "1.3.1" }, "peerDependencies": { "react": ">=18.2.0", "react-dom": ">=18.2.0", "slate": ">=0.114.0", "slate-dom": ">=0.116.0" } }, "sha512-9ckilyUzQS1VHJnstIpgInhcWnTDgv2Cd7m1HOQVl3zasChoapPSMftzT/wl/48grZaZYZIi4xVuzGTcFRUWFg=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], - - "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], - - "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], - - "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - - "tiny-invariant": ["tiny-invariant@1.3.1", "", {}, "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw=="], - - "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], - - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "trpc-cli": ["trpc-cli@0.12.1", "", { "dependencies": { "commander": "^14.0.0" }, "peerDependencies": { "@orpc/server": "^1.0.0", "@trpc/server": "^10.45.2 || ^11.0.1", "@valibot/to-json-schema": "^1.1.0", "effect": "^3.14.2 || ^4.0.0", "valibot": "^1.1.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["@orpc/server", "@trpc/server", "@valibot/to-json-schema", "effect", "valibot", "zod"], "bin": { "trpc-cli": "dist/bin.js" } }, "sha512-/D/mIQf3tUrS7ZKJZ1gmSPJn2psAABJfkC5Eevm55SZ4s6KwANOUNlwhAGXN9HT4VSJVfoF2jettevE9vHPQlg=="], - - "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "ultracite": ["ultracite@6.3.6", "", { "dependencies": { "@clack/prompts": "^0.11.0", "@trpc/server": "^11.7.1", "deepmerge": "^4.3.1", "glob": "^12.0.0", "jsonc-parser": "^3.3.1", "nypm": "^0.6.2", "trpc-cli": "^0.12.0", "zod": "^4.1.12" }, "bin": { "ultracite": "dist/index.js" } }, "sha512-GTDos8mpW3Z/4yMGjZj3ux8HZxznYkV+1WGtDCHE8cuLqgICjJMhbdjTN/foL3Za9Hhr2ZloKMIrj3hiwPrN8A=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], - - "use-context-selector": ["use-context-selector@1.4.4", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": "*", "react-native": "*", "scheduler": ">=0.19.0" }, "optionalPeers": ["react-dom", "react-native"] }, "sha512-pS790zwGxxe59GoBha3QYOwk8AFGp4DN6DOtH+eoqVmgBBRXVx4IlPDhJmmMiNQAgUaLlP+58aqRC3A4rdaSjg=="], - - "use-deep-compare": ["use-deep-compare@1.3.0", "", { "dependencies": { "dequal": "2.0.3" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-94iG+dEdEP/Sl3WWde+w9StIunlV8Dgj+vkt5wTwMoFQLaijiEZSXXy8KtcStpmEDtIptRJiNeD4ACTtVvnIKA=="], - - "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="], - - "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], - - "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], - - "zustand-x": ["zustand-x@6.2.1", "", { "dependencies": { "immer": "^10.0.3", "lodash.mapvalues": "^4.6.0", "mutative": "1.1.0", "react-tracked": "^1.7.11", "use-sync-external-store": "1.4.0" }, "peerDependencies": { "zustand": ">=5.0.2" } }, "sha512-y3nQMQNx3BORY95vpuodJvh/8AqQu++S3q6mJYBSo1J0Q168Sy+FatqER658YESDqv2bwviXcIT3bgl/Ip6M5g=="], - - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@platejs/core/nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - - "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-toolbar/@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@udecode/react-utils/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - - "path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], - - "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "@radix-ui/react-separator/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - } -} diff --git a/templates/plate-template/package.json b/templates/plate-template/package.json index 8981eb7490..78c18c59f8 100644 --- a/templates/plate-template/package.json +++ b/templates/plate-template/package.json @@ -23,7 +23,7 @@ "class-variance-authority": "0.7.1", "clsx": "^2.1.1", "lucide-react": "0.554.0", - "next": "16.0.3", + "next": "16.1.5", "platejs": "^52.0.1", "react": "19.2.0", "react-dom": "19.2.0",