diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index eb3668435..41bc3b005 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -3,35 +3,27 @@ description: File a bug report title: "[Bug]: " labels: ["bug"] body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - type: input id: version attributes: label: Version - description: Which version of the extension are you running? - placeholder: 0.0.1.0 + description: Which version of the extension are you using? + placeholder: 2.x.x ("Latest" is not an acceptable answer) validations: required: true - - type: textarea - id: bug-description + - type: input + id: browser attributes: - label: Describe the bug - description: A clear and concise description of what the bug is. + label: Browser + description: Provide the browser(s) and version(s) where the issue occurs + placeholder: e.g. Google Chrome 140, Firefox Android 133, Comet 140, etc. validations: required: true - type: textarea - id: reproduction-steps + id: bug-description attributes: - label: Steps to reproduce - description: Steps to reproduce the behavior - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error + label: Describe the bug + description: A clear and concise description of what the bug is, screenshots/recordings will help speed up the resolution process validations: required: true - type: checkboxes @@ -44,33 +36,30 @@ body: required: true - label: I have re-installed the extension and the issue persists required: true - - type: textarea - id: expected-behavior + id: reproduction-steps attributes: - label: Expected behavior - description: A clear and concise description of what you expected to happen. + label: Steps to reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + 4. See error validations: required: true - type: textarea - id: screenshots - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. - validations: - required: false - - type: input - id: browser + id: expected-behavior attributes: - label: Browser - description: Provide the browser(s) and version(s) where the issue occurs. Please explicitly mention "mobile" if the issue occurs on mobile browsers. - placeholder: e.g. Google Chrome 128, Firefox Android 133 + label: Expected behavior + description: A clear and concise description of what you expected to happen validations: required: true - type: textarea - id: additional-context + id: debug-logs attributes: - label: Additional context - description: Add any other context about the problem here. + label: Debug Logs + description: Include debug logs from the Settings Dashboard > Settings > Debug > Copy + render: json validations: required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index c4828069b..745f305b1 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: true contact_links: - - name: Discuss your ideas/issues on Discord + - name: Get quick support on Discord url: https://discord.cplx.app - about: Get quick help from the community. \ No newline at end of file + about: Get quick help from the community. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bb788b874..3c302ee7d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -7,7 +7,7 @@ body: id: feature-description attributes: label: Feature Description - description: Provide a clear and concise description of the feature you'd like to see. + description: Provide a clear and concise description of the feature you'd like to see validations: required: true - type: textarea @@ -22,7 +22,7 @@ body: id: alternatives attributes: label: Alternatives Considered - description: Describe any alternative solutions or features you've considered. + description: Describe any alternative solutions or features you've considered placeholder: I've thought about... but... validations: required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/translation_issues.yml b/.github/ISSUE_TEMPLATE/translation_issues.yml index 54eef44e4..1af2b612e 100644 --- a/.github/ISSUE_TEMPLATE/translation_issues.yml +++ b/.github/ISSUE_TEMPLATE/translation_issues.yml @@ -40,7 +40,7 @@ body: id: suggested-translation attributes: label: Suggested Translation - description: If you have a suggestion for the correct translation, please provide it here. + description: If you have a suggestion for the correct translation, please provide it here placeholder: Your suggested translation... validations: required: false @@ -48,7 +48,7 @@ body: id: context attributes: label: Context - description: Where did you find this translation? Please provide as much context as possible. + description: Where did you find this translation? Please provide as much context as possible placeholder: | - Location in the settings dashboard/perplexity.ai - Screenshot (if possible) diff --git a/.gitignore b/.gitignore index de8c32a98..208f1570c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,7 @@ server/dist public/dist TODO.md **/*.tsbuildinfo -**/tsconfig.temp.json \ No newline at end of file +**/tsconfig.temp.json +**/settings.local.json +**/.pi/prompts.jsonl +**/.pi/stashed-prompts.jsonl \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index c2407726e..18ad7f8e7 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -cd perplexity/extension && turbo test check-types && pnpm lint-staged && cd - +pnpm --recursive --parallel --if-present lint-staged \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 37665f2cf..217b3f9e7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "catppuccin-icons.specificFolders": false, "eslint.workingDirectories": [ { "mode": "auto" @@ -13,32 +14,32 @@ "**/.hg": true, "**/.svn": true, "**/.turbo": true, + "**/*.tsbuildinfo": true, "**/CVS": true, - "**/Thumbs.db": true, "**/node_modules": true, - "**/*.tsbuildinfo": true + "**/Thumbs.db": true }, "files.watcherExclude": { "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/.hg/store/**": true, "**/.turbo": true, + "**/*.tsbuildinfo": true, "**/build/**/*": true, "**/dist/**/*": true, - "**/node_modules/*/**": true, - "**/*.tsbuildinfo": true + "**/node_modules/*/**": true }, "npm.exclude": ["**/build", "**/dist"], "npm.scriptExplorerExclude": ["preinstall"], "search.exclude": { - "**/*.code-search": true, "**/.turbo": true, + "**/*.code-search": true, + "**/*.tsbuildinfo": true, "**/bower_components": true, "**/build/**/*": true, "**/dist/**/*": true, "**/node_modules": true, - "**/public/locales/**/*": true, - "**/*.tsbuildinfo": true + "**/public/locales/**/*": true }, "typescript.tsdk": "node_modules\\typescript\\lib", "typescript.tsserver.maxTsServerMemory": 8192, diff --git a/LICENSE b/LICENSE index dc7b886d3..a612ad981 100644 --- a/LICENSE +++ b/LICENSE @@ -1,35 +1,373 @@ -# Custom License for Complexity +Mozilla Public License Version 2.0 +================================== -Copyright (c) 2024-2025 The Original Author of Complexity +1. Definitions +-------------- -## Terms and Conditions +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. -1. **Redistribution** +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. - Redistribution of the source code, documentation, or any other components of this software in any form is permitted only for individuals for personal use, and not for organizations, companies, or any collective entities. Redistribution is allowed, provided that: +1.3. "Contribution" + means Covered Software of a particular Contributor. - a. The original copyright notice, this license, and the disclaimer below appear in all copies. +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. - b. Proper attribution to the original project is clearly provided. +1.5. "Incompatible With Secondary Licenses" + means - c. The redistribution is conducted by an individual person for personal or educational purposes, and not on behalf of or for the benefit of any organization, company, corporation, government entity, or any other collective entity. + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or -2. **Restrictions** + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. - a. **Commercial Use Prohibition**: This software, including any part of its source code or derivatives, may not be used for any commercial purposes. Commercial purposes include, but are not limited to, using the software in a commercial product, offering services based on the software for a fee, or any activity intended to generate profit. +1.6. "Executable Form" + means any form of the work other than Source Code Form. - b. **Monetization Prohibition**: No monetization of this software or its derivatives is permitted in any form, including but not limited to selling the software itself, selling services based on the software, or generating advertising revenue through the software. +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. - c. **Organizational Use Prohibition**: This software may not be redistributed, used, or modified by organizations, companies, corporations, government entities, or any collective entities. It is intended solely for use by individual persons. +1.8. "License" + means this document. -3. **Derivatives** +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. - Modified versions and derivative works of this software are allowed under the same terms as the original software, particularly the prohibitions on commercial use, monetization, and organizational use. +1.10. "Modifications" + means any of the following: -4. **Attribution** + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or - Any redistribution or derivative work must provide clear and visible attribution to the original project, including a link to the original project repository if distributed electronically. + (b) any new file in Source Code Form that contains any Covered + Software. -## Disclaimer +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. -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 HOLDER 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. +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index 3c2b05914..ea032f6ea 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,6 @@ ---- - -> [!TIP] -> 🎁 **FREE PERPLEXITY PRO!** Sign up, ask your first question on the new Comet browser, and unlock premium features instantly for the first 30 days ➡️ [https://pplx.ai/pnd280](https://pplx.ai/pnd280) - ---- - > [!NOTE] > Originally a [Perplexity AI](https://perplexity.ai/) extension, this repository has now been restructured into a suite of enhancements for multiple platforms and services. @@ -41,7 +34,7 @@ - Supports 22 languages - [**Supports the new Comet browser**](./perplexity/extension/docs/comet-enable-extensions.md) - Supports Firefox Android -- Navigate to [`./perplexity/extension/`](./perplexity/extension/) for more information +- Contribution: [`./perplexity/extension/`](./perplexity/extension/) ## Donate/Sponsor @@ -58,4 +51,5 @@ ## License -- [Full license terms](./LICENSE) +- Source code in this repository is licensed under the [Mozilla Public License 2.0](./LICENSE), unless otherwise noted. +- The Complexity name, logos, icons, screenshots, store/listing assets, and other brand assets are not licensed under the MPL. See [TRADEMARKS.md](./TRADEMARKS.md). diff --git a/TRADEMARKS.md b/TRADEMARKS.md new file mode 100644 index 000000000..d09c6fe2c --- /dev/null +++ b/TRADEMARKS.md @@ -0,0 +1,24 @@ +# Trademarks and Brand Assets + +Unless otherwise noted, source code in this repository is licensed under the Mozilla Public License 2.0. That license does not grant rights to use the project's names, logos, icons, screenshots, or other brand assets. + +## Trademarks + +"Complexity", "CPLX", the Complexity logo, and related names, logos, icons, and visual identifiers are trademarks or unregistered marks used by the project author. + +## Brand assets not covered by MPL-2.0 + +Unless the project author gives prior written permission, the following are excluded from the MPL-2.0 license and are All Rights Reserved: + +- all files under `branding/` +- all files under `perplexity/extension/public/icons/` +- all files under `perplexity/extension/public/img/` +- official screenshots, store/listing assets, website assets, and marketing materials in this repository + +## Forks and redistribution + +If you distribute a fork or modified version, you must remove or replace the official project branding before distribution unless you have prior written permission from the project author. This includes the project name, logos, icons, screenshots, store metadata, and other visual branding that could imply affiliation, sponsorship, or endorsement. + +You may use the project name only for truthful, nominative reference, such as "fork of Complexity", so long as that use does not imply affiliation, sponsorship, or endorsement. + +Do not use the official branding, or confusingly similar branding, for browser store listings, packages, domains, social accounts, websites, or other distribution channels in a way that suggests the fork is the official project. diff --git a/branding/imgs/github.png b/branding/imgs/github.png new file mode 100644 index 000000000..47b1acceb Binary files /dev/null and b/branding/imgs/github.png differ diff --git a/package.json b/package.json index 9e3c47c5c..6d0559c26 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "complexity", "private": true, + "license": "MPL-2.0", "scripts": { "build": "turbo build", "check-deps": "manypkg check", @@ -16,12 +17,12 @@ "devDependencies": { "@manypkg/cli": "^0.25.1", "husky": "^9.1.7", - "lint-staged": "^16.2.6", + "lint-staged": "^16.4.0", "prettier": "catalog:lint", - "turbo": "^2.5.8", + "turbo": "^2.9.18", "typescript": "catalog:types" }, - "packageManager": "pnpm@10.19.0", + "packageManager": "pnpm@10.25.0", "engines": { "node": "^22.12.0" }, diff --git a/packages/async-dep-manager/package.json b/packages/async-dep-manager/package.json index 0960e67fd..969d9d454 100644 --- a/packages/async-dep-manager/package.json +++ b/packages/async-dep-manager/package.json @@ -2,6 +2,7 @@ "name": "@complexity/async-dep-registry", "version": "0.1.0", "private": true, + "license": "MPL-2.0", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -24,11 +25,11 @@ "@complexity/typescript-config": "workspace:*", "@types/node": "22.17.2", "eslint": "catalog:lint", - "globals": "16.4.0", + "globals": "17.3.0", "prettier": "catalog:lint", - "tsup": "^8.5.0", + "tsup": "^8.5.1", "typescript": "catalog:types", "typescript-eslint": "catalog:lint", - "vitest": "^4.0.3" + "vitest": "^4.1.9" } } diff --git a/packages/async-dep-manager/src/async-dep-registry.ts b/packages/async-dep-manager/src/async-dep-registry.ts index b8f33fdb6..5258692d9 100644 --- a/packages/async-dep-manager/src/async-dep-registry.ts +++ b/packages/async-dep-manager/src/async-dep-registry.ts @@ -362,10 +362,6 @@ export class AsyncDependencyRegistry< this._pendingWarnings.set(id, warningTimeoutId); return new Promise((resolve) => { - if (this._pendingResolvers == null) { - this._pendingResolvers = new Map(); - } - if (!this._pendingResolvers.has(id)) { this._pendingResolvers.set(id, new Set()); } diff --git a/packages/cli-logger/package.json b/packages/cli-logger/package.json index 6dbb1d005..7121da254 100644 --- a/packages/cli-logger/package.json +++ b/packages/cli-logger/package.json @@ -2,6 +2,7 @@ "name": "@complexity/cli-logger", "version": "0.1.0", "private": true, + "license": "MPL-2.0", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -19,8 +20,11 @@ "@complexity/typescript-config": "workspace:*", "@types/node": "22.17.2", "chalk": "5.6.2", - "globals": "16.4.0", - "tsup": "^8.5.0", + "globals": "17.3.0", + "tsup": "^8.5.1", "typescript": "catalog:types" + }, + "dependencies": { + "eslint": "catalog:lint" } } diff --git a/packages/comctx-adapters/examples/extension/eslint.config.js b/packages/comctx-adapters/examples/extension/eslint.config.js index bbeab7c65..b5db9c981 100644 --- a/packages/comctx-adapters/examples/extension/eslint.config.js +++ b/packages/comctx-adapters/examples/extension/eslint.config.js @@ -2,12 +2,15 @@ import rootConfig from "../../eslint.config.js"; import { defineConfig } from "eslint/config"; -export default defineConfig({ +export default defineConfig([ ...rootConfig, - languageOptions: { - parserOptions: { - project: "./tsconfig.json", - tsconfigRootDir: import.meta.dirname, + { + files: ["**/*.ts", "**/*.tsx"], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, }, -}); +]); diff --git a/packages/comctx-adapters/examples/extension/package.json b/packages/comctx-adapters/examples/extension/package.json index 6c883426d..41cb2eb14 100644 --- a/packages/comctx-adapters/examples/extension/package.json +++ b/packages/comctx-adapters/examples/extension/package.json @@ -13,19 +13,19 @@ }, "dependencies": { "@comctx-adapters/core": "workspace:*", - "comctx": "^1.4.3" + "comctx": "^1.7.5" }, "devDependencies": { "@complexity/eslint-config": "workspace:*", "@complexity/typescript-config": "workspace:*", - "@crxjs/vite-plugin": "^2.2.1", + "@crxjs/vite-plugin": "^2.7.0", "@types/chrome": "catalog:types", "@types/node": "22.17.2", "eslint": "catalog:lint", - "globals": "16.4.0", + "globals": "17.3.0", "prettier": "catalog:lint", "typescript": "catalog:types", "typescript-eslint": "catalog:lint", - "vite": "^7.1.12" + "vite": "^7.3.5" } } diff --git a/packages/comctx-adapters/package.json b/packages/comctx-adapters/package.json index 92e4d3c15..71bd3884c 100644 --- a/packages/comctx-adapters/package.json +++ b/packages/comctx-adapters/package.json @@ -2,6 +2,7 @@ "name": "@comctx-adapters/core", "version": "0.1.0", "private": true, + "license": "MPL-2.0", "author": "pnd280", "type": "module", "main": "./dist/index.js", @@ -19,8 +20,8 @@ "lint": "eslint ./src" }, "dependencies": { - "comctx": "^1.4.3", - "tsup": "^8.5.0" + "comctx": "^1.7.5", + "tsup": "^8.5.1" }, "devDependencies": { "@complexity/eslint-config": "workspace:*", @@ -28,7 +29,7 @@ "@types/chrome": "catalog:types", "@types/node": "22.17.2", "eslint": "catalog:lint", - "globals": "16.4.0", + "globals": "17.3.0", "prettier": "catalog:lint", "typescript": "catalog:types", "typescript-eslint": "catalog:lint" diff --git a/packages/comctx-adapters/src/adapters/browser.ts b/packages/comctx-adapters/src/adapters/browser.ts index 33246e382..7691cb962 100644 --- a/packages/comctx-adapters/src/adapters/browser.ts +++ b/packages/comctx-adapters/src/adapters/browser.ts @@ -4,15 +4,15 @@ type MessageMeta = { sender: { tabId: NonNullable["id"]; frameId: chrome.runtime.MessageSender["frameId"]; - }; + } | null; }; export class BrowserRuntimeAdapter implements Adapter { sendMessage: SendMessage = async (message) => { if (message.meta.sender != null && message.meta.sender.tabId != null) { - chrome.tabs.sendMessage(message.meta.sender.tabId, message); + void chrome.tabs.sendMessage(message.meta.sender.tabId, message); } else { - chrome.runtime.sendMessage(message); + void chrome.runtime.sendMessage(message); } }; diff --git a/packages/comctx-adapters/src/adapters/tab-consumer.ts b/packages/comctx-adapters/src/adapters/tab-consumer.ts index 6f7cbc273..79b475bc4 100644 --- a/packages/comctx-adapters/src/adapters/tab-consumer.ts +++ b/packages/comctx-adapters/src/adapters/tab-consumer.ts @@ -16,12 +16,8 @@ export class TabConsumerAdapter implements Adapter { this.frameId = frameId ?? 0; } - sendMessage: SendMessage = (message) => { - const tab = chrome.tabs.get(this.tabId); - - if (tab == null) return; - - chrome.tabs.sendMessage(this.tabId, message, { + sendMessage: SendMessage = async (message) => { + void chrome.tabs.sendMessage(this.tabId, message, { frameId: this.frameId, }); }; diff --git a/packages/eslint-config/base.js b/packages/eslint-config/base.js index f83ec2be1..693461009 100644 --- a/packages/eslint-config/base.js +++ b/packages/eslint-config/base.js @@ -1,4 +1,3 @@ -// @ts-check import eslintConfigPrettier from "eslint-config-prettier"; import js from "@eslint/js"; @@ -8,7 +7,7 @@ import unicornPlugin from "eslint-plugin-unicorn"; export default defineConfig([ js.configs.recommended, - ...tseslint.configs.recommended, + tseslint.configs.recommended, eslintConfigPrettier, { linterOptions: { diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js index f5e568535..3f13782d3 100644 --- a/packages/eslint-config/index.js +++ b/packages/eslint-config/index.js @@ -3,3 +3,5 @@ export { default as baseImportConfig } from "./rules/imports.js"; export { default as baseTypescriptConfig } from "./rules/typescript.js"; export { default as baseConfig } from "./base.js"; + +export { default as typedExportsPlugin } from "./plugins/typed-exports.js"; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index be9321e6a..7897e541a 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -2,22 +2,24 @@ "name": "@complexity/eslint-config", "version": "0.0.0", "private": true, + "license": "MPL-2.0", "type": "module", "exports": { ".": "./index.js" }, "devDependencies": { - "@eslint/js": "^9.38.0", - "@limegrass/eslint-plugin-import-alias": "^1.6.0", + "@eslint/js": "^9.39.4", + "@limegrass/eslint-plugin-import-alias": "^1.6.1", + "@typescript-eslint/utils": "^8.61.1", "eslint": "catalog:lint", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", "eslint-plugin-unicorn": "62.0.0", - "globals": "16.4.0", + "globals": "17.3.0", "typescript-eslint": "catalog:lint" } } diff --git a/packages/eslint-config/plugins/typed-exports.js b/packages/eslint-config/plugins/typed-exports.js new file mode 100644 index 000000000..9222c424e --- /dev/null +++ b/packages/eslint-config/plugins/typed-exports.js @@ -0,0 +1,391 @@ +import { ESLintUtils } from "@typescript-eslint/utils"; +import ts from "typescript"; + +const createRule = ESLintUtils.RuleCreator(() => ""); + +/** + * @typedef {Object} TypeRequirement + * @property {"default" | "named"} type - Whether to check the default export or a named export + * @property {string} [exportName] - For named exports, the specific export name to check. If omitted, any named export matching the type is accepted + * @property {Object} tsType - The TypeScript type to enforce + * @property {string} tsType.name - The name of the TypeScript type/interface to check against + * @property {string} tsType.path - The module path where the type is defined (e.g., "@/__registries__/types") + * @property {string} [message] - Custom error message to display when the export is missing or incorrect + */ + +/** + * ESLint rule to enforce that files export variables with specific TypeScript types. + * + * This rule validates that: + * - Default exports match a specified type from a specified module + * - Named exports match specified types from specified modules + * - Export names match expected names (for named exports) + * + * @example + * // Enforce default export of a specific type + * { + * "typed-exports/typed-exports": ["error", [ + * { + * type: "default", + * tsType: { + * name: "MyType", + * path: "@/types/my-types" + * } + * } + * ]] + * } + * + * @example + * // Enforce named export with a specific name and type + * { + * "typed-exports/typed-exports": ["error", [ + * { + * type: "named", + * exportName: "myExport", + * tsType: { + * name: "MyExportType", + * path: "@/types/exports" + * } + * } + * ]] + * } + * + * @example + * // Enforce both default and named exports + * { + * "typed-exports/typed-exports": ["error", [ + * { + * type: "default", + * tsType: { + * name: "DefaultType", + * path: "@/types/defaults" + * } + * }, + * { + * type: "named", + * exportName: "namedExport", + * tsType: { + * name: "NamedType", + * path: "@/types/named" + * } + * } + * ]] + * } + * + * @example + * // Use a custom error message + * { + * "typed-exports/typed-exports": ["error", [ + * { + * type: "default", + * tsType: { + * name: "MyConfig", + * path: "@/types/config" + * }, + * message: "Configuration files must export a valid MyConfig object" + * } + * ]] + * } + */ +const typedExportsPlugin = createRule({ + name: "typed-exports", + meta: { + type: "problem", + docs: { + description: + "Enforce that files export variables with specific types from specific files", + }, + messages: { + missingRequiredExport: + "File must export {{exportType}} of type {{typeName}} from {{typePath}}", + incorrectExportType: + "Export {{exportName}} must be of type {{typeName}} from {{typePath}}, but got {{actualType}}", + incorrectExportName: "Named export must be called {{expectedName}}", + wrongImportSource: + "Type {{typeName}} must be imported from {{expectedPath}}", + customMessage: "{{message}}", + }, + /** + * Schema for the rule options. + * Accepts an array of export requirements, where each requirement specifies: + * - type: "default" | "named" - The export type to validate + * - exportName: string (optional) - For named exports, the specific name to check + * - tsType: { name: string, path: string } - The TypeScript type and its module path + * - message: string (optional) - Custom error message to display + */ + schema: [ + { + type: "array", + items: { + type: "object", + properties: { + type: { type: "string", enum: ["default", "named"] }, + exportName: { type: "string" }, + tsType: { + type: "object", + properties: { + name: { type: "string" }, + path: { type: "string" }, + }, + required: ["name", "path"], + }, + message: { type: "string" }, + }, + required: ["type", "tsType"], + }, + }, + ], + }, + defaultOptions: [[]], + create(context) { + const exportRequirements = context.options[0] || []; + const filename = context.filename; + + if (exportRequirements.length === 0) return {}; + + const foundExports = { default: null, named: new Map() }; + const expectedTypeCache = new Map(); + + // Helpers + const getTypeInfo = (node) => { + const parserServices = context.sourceCode.parserServices; + if ( + !parserServices || + !parserServices.program || + !parserServices.esTreeNodeToTSNodeMap + ) + return null; + try { + const checker = parserServices.program.getTypeChecker(); + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (!tsNode) return null; + const type = checker.getTypeAtLocation(tsNode); + const typeString = checker.typeToString(type); + const symbol = type.getSymbol() || type.aliasSymbol; + return { type, typeString, symbol, checker }; + } catch { + return null; + } + }; + + const getExpectedType = (typeName, typePath, checker) => { + const cacheKey = `${typeName}|${typePath}`; + if (expectedTypeCache.has(cacheKey)) { + return expectedTypeCache.get(cacheKey); + } + const program = context.sourceCode.parserServices.program; + const localChecker = checker || program.getTypeChecker(); + const resolved = ts.resolveModuleName( + typePath, + filename, + program.getCompilerOptions(), + ts.sys, + ); + if (!resolved.resolvedModule) return null; + const sourceFile = program.getSourceFile( + resolved.resolvedModule.resolvedFileName, + ); + if (!sourceFile) return null; + const moduleSymbol = + localChecker.getSymbolAtLocation(sourceFile) || sourceFile.symbol; + if (!moduleSymbol) return null; + const exports = localChecker.getExportsOfModule(moduleSymbol); + let typeSymbol = exports.find((exp) => exp.name === typeName); + if (!typeSymbol) return null; + if (typeSymbol.flags & ts.SymbolFlags.Alias) { + typeSymbol = localChecker.getAliasedSymbol(typeSymbol); + } + let expectedType = null; + const decl = typeSymbol.declarations && typeSymbol.declarations[0]; + if (decl && ts.isTypeAliasDeclaration(decl)) { + expectedType = localChecker.getTypeFromTypeNode(decl.type); + } + if (!expectedType) { + expectedType = localChecker.getDeclaredTypeOfSymbol(typeSymbol); + } + expectedTypeCache.set(cacheKey, expectedType); + return expectedType; + }; + + const hasAllExpectedProperties = (actualType, expectedType, checker) => { + const expectedProps = checker.getPropertiesOfType(expectedType); + if (expectedProps.length === 0) return false; + const actualProps = checker.getPropertiesOfType(actualType); + if (actualProps.length === 0) return false; + const actualNames = new Set(actualProps.map((p) => p.name)); + return expectedProps.every((p) => actualNames.has(p.name)); + }; + + const matchesExpectedType = (actual, expected, checker) => { + const actualSym = actual.aliasSymbol || actual.symbol; + const expectedSym = expected.aliasSymbol || expected.symbol; + return ( + (actualSym && expectedSym && actualSym === expectedSym) || + checker.isTypeAssignableTo(actual, expected) || + hasAllExpectedProperties(actual, expected, checker) + ); + }; + + const recordExport = (name, node, typeInfo) => { + if (!typeInfo) return; + const data = { + node, + type: typeInfo.type, + typeString: typeInfo.typeString, + symbol: typeInfo.symbol, + checker: typeInfo.checker, + }; + if (name === null) { + foundExports.default = data; + } else { + foundExports.named.set(name, data); + } + }; + + const findMatchingNamedExport = (typeName, typePath, checker) => { + for (const [, data] of foundExports.named) { + const expectedType = getExpectedType(typeName, typePath, checker); + if (!expectedType) continue; + if (matchesExpectedType(data.type, expectedType, checker)) { + return data; + } + } + return null; + }; + + const validateExport = ( + exportData, + typeName, + typePath, + exportName, + customMessage, + ) => { + if (!exportData) { + if (customMessage) { + context.report({ + node: context.sourceCode.ast, + messageId: "customMessage", + data: { message: customMessage }, + }); + } else { + const exportType = + exportName === null + ? "default" + : exportName + ? `named export '${exportName}'` + : "named"; + context.report({ + node: context.sourceCode.ast, + messageId: "missingRequiredExport", + data: { exportType, typeName, typePath }, + }); + } + return; + } + const expectedType = getExpectedType( + typeName, + typePath, + exportData.checker, + ); + if (!expectedType) return; + if ( + !matchesExpectedType(exportData.type, expectedType, exportData.checker) + ) { + if (customMessage) { + context.report({ + node: exportData.node, + messageId: "customMessage", + data: { message: customMessage }, + }); + } else { + const displayName = + exportName === null + ? "default export" + : exportName || "named export"; + context.report({ + node: exportData.node, + messageId: "incorrectExportType", + data: { + exportName: displayName, + typeName, + typePath, + actualType: exportData.typeString, + }, + }); + } + } + }; + + return { + ExportDefaultDeclaration(node) { + const typeInfo = getTypeInfo(node.declaration); + recordExport(null, node, typeInfo); + }, + + ExportNamedDeclaration(node) { + if (node.declaration) { + if ( + node.declaration.type === "VariableDeclaration" && + node.declaration.declarations.length > 0 + ) { + const varDecl = node.declaration.declarations[0]; + if (varDecl.id.type === "Identifier") { + const typeInfo = getTypeInfo(varDecl.id); + recordExport(varDecl.id.name, node, typeInfo); + } + } + } + if (node.specifiers && node.specifiers.length > 0) { + for (const specifier of node.specifiers) { + if (specifier.type === "ExportSpecifier") { + const typeInfo = getTypeInfo(specifier.local); + if (!typeInfo) continue; + const exportedName = + specifier.exported.type === "Identifier" + ? specifier.exported.name + : specifier.exported.value; + recordExport(exportedName, node, typeInfo); + } + } + } + }, + + "Program:exit"() { + for (const req of exportRequirements) { + const { type, exportName, tsType, message } = req; + if (type === "default") { + validateExport( + foundExports.default, + tsType.name, + tsType.path, + null, + message, + ); + } else if (type === "named") { + let exportData = null; + if (exportName) { + exportData = foundExports.named.get(exportName); + } else { + exportData = findMatchingNamedExport( + tsType.name, + tsType.path, + // Use any checker available; they're all equivalent + foundExports.named.values().next().value?.checker || + foundExports.default?.checker, + ); + } + validateExport( + exportData, + tsType.name, + tsType.path, + exportName, + message, + ); + } + } + }, + }; + }, +}); + +export default typedExportsPlugin; diff --git a/packages/eslint-config/rules/imports.js b/packages/eslint-config/rules/imports.js index 815a1b487..30aaacd55 100644 --- a/packages/eslint-config/rules/imports.js +++ b/packages/eslint-config/rules/imports.js @@ -1,6 +1,4 @@ -// @ts-check - -import * as importPlugin from "eslint-plugin-import"; +import importPlugin from "eslint-plugin-import"; import importAliasPlugin from "@limegrass/eslint-plugin-import-alias"; import { defineConfig } from "eslint/config"; @@ -14,7 +12,7 @@ export default defineConfig({ "import/resolver": { typescript: { alwaysTryTypes: true, - project: "./tsconfig.json", + projectService: true, }, }, "import/parsers": { diff --git a/packages/eslint-config/rules/react.js b/packages/eslint-config/rules/react.js index 7227a7d4a..5e2337bdd 100644 --- a/packages/eslint-config/rules/react.js +++ b/packages/eslint-config/rules/react.js @@ -1,46 +1,42 @@ -// @ts-check - import reactPlugin from "eslint-plugin-react"; import reactHooksPlugin from "eslint-plugin-react-hooks"; import reactRefreshPlugin from "eslint-plugin-react-refresh"; import { defineConfig } from "eslint/config"; -export default defineConfig([ - { - extends: [reactHooksPlugin.configs.flat["recommended-latest"]], - plugins: { - react: reactPlugin, - "react-hooks": reactHooksPlugin, - "react-refresh": reactRefreshPlugin, - }, - settings: { - react: { version: "detect" }, - }, - rules: { - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", - "react-refresh/only-export-components": [ - "warn", - { allowConstantExport: true }, - ], - "react/react-in-jsx-scope": "off", - "react/prop-types": "off", - "react/jsx-sort-props": [ - "warn", - { - callbacksLast: true, - shorthandFirst: true, - ignoreCase: true, - reservedFirst: true, - noSortAlphabetically: true, - }, - ], - "react/jsx-no-useless-fragment": [ - "warn", - { - allowExpressions: true, - }, - ], - }, +export default defineConfig({ + extends: [ + reactPlugin.configs.flat.recommended, + reactPlugin.configs.flat["jsx-runtime"], + reactHooksPlugin.configs.flat["recommended-latest"], + reactRefreshPlugin.configs.recommended, + ], + settings: { + react: { version: "detect" }, + }, + rules: { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + "react/react-in-jsx-scope": "off", + "react/prop-types": "off", + "react/jsx-sort-props": [ + "warn", + { + callbacksLast: true, + shorthandFirst: true, + ignoreCase: true, + reservedFirst: true, + noSortAlphabetically: true, + }, + ], + "react/jsx-no-useless-fragment": [ + "warn", + { + allowExpressions: true, + }, + ], }, -]); +}); diff --git a/packages/eslint-config/rules/typescript.js b/packages/eslint-config/rules/typescript.js index 1675dc989..588c529c8 100644 --- a/packages/eslint-config/rules/typescript.js +++ b/packages/eslint-config/rules/typescript.js @@ -1,4 +1,3 @@ -// @ts-check import { defineConfig } from "eslint/config"; import tseslint from "typescript-eslint"; @@ -8,18 +7,21 @@ export default defineConfig({ parser: tseslint.parser, parserOptions: { alwaysTryTypes: true, - project: "./tsconfig.json", + projectService: true, }, }, - plugins: { - "@typescript-eslint": tseslint.plugin, - }, + extends: [tseslint.configs.recommended], rules: { "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/ban-ts-comment": "off", - "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-unsafe-declaration-merging": "error", + "@typescript-eslint/no-unnecessary-condition": "error", + "@typescript-eslint/no-empty-object-type": [ + "error", + { allowInterfaces: "always" }, + ], "@typescript-eslint/no-floating-promises": [ "warn", { diff --git a/packages/i18n/README.md b/packages/i18n/README.md new file mode 100644 index 000000000..1569cd721 --- /dev/null +++ b/packages/i18n/README.md @@ -0,0 +1,181 @@ +# Internationalization + +Custom implementation of internationalization (courtesy of [WebDevSimplified](https://www.youtube.com/watch?v=VbZVx13b2oY)). + +## Formatting Types + +The following format types can be used with the syntax `{variable:type}`: + +### Strings + +Translation string: + +```ts +// In locale file +"greeting.simple": "Hello, {name}!" +``` + +Usage: + +```ts +t("greeting.simple", { name: "World" }); +// Result: "Hello, World!" +``` + +### Pluralization + +Translation string: + +```ts +// In locale file +"notifications.count": dt("You have {count:plural} in your inbox.", { + plural: { count: { zero: "no notifications", one: "1 notification", other: "{?} notifications" } }, +}) +``` + +Usage: + +```ts +t("notifications.count", { count: 5 }); +// Result: "You have 5 notifications in your inbox." +``` + +### Enumerations + +Translation string: + +```ts +// In locale file +"profile.status": dt("Your account status is {status:enum}.", { + enum: { status: { active: "active", suspended: "temporarily suspended", banned: "permanently banned" } }, +}) +``` + +Usage: + +```ts +t("profile.status", { status: "suspended" }); +// Result: "Your account status is temporarily suspended." +``` + +### Lists + +Translation string: + +```ts +// In locale file +"team.members": "Team members: {members:list}" +``` + +Usage: + +```ts +t("team.members", { members: ["Alex", "Taylor", "Jordan"] }); +// Result: "Team members: Alex, Taylor, Jordan" +``` + +### Dates + +Translation string: + +```ts +// In locale file +"event.scheduled": "The event is scheduled for {eventDate:date}" +``` + +Usage: + +```ts +t("event.scheduled", { eventDate: new Date("2023-12-25") }); +// Result: "The event is scheduled for 12/25/2023" +``` + +### Numbers + +Translation string: + +```ts +// In locale file +"product.price": "Product price: ${price:number}" +``` + +Usage: + +```ts +t("product.price", { price: 1299.99 }); +// Result: "Product price: $1,299.99" +``` + +## Component-Based Translation + +### Using `Trans` Component + +For translations that include HTML elements: + +#### Basic Component Example + +Translation string: + +```ts +// In locale file +"app.welcome": "Welcome to <0>our application, {username}!" +``` + +Usage: + +```tsx +]} +/> +``` + +#### Multiple Components Example + +Translation string: + +```ts +// In locale file +"app.instructions": "Click <0>here to start or <1>here to learn more." +``` + +Usage: + +```tsx + startApp()} />, ]} +/> +``` + +#### Nested Components Example + +Translation string: + +```ts +// In locale file +"app.footer": "Created with <0>modern <1>web technologies" +``` + +Usage: + +```tsx +, ]} /> +``` + +### Escaping Braces + +Translation string: + +```ts +// In locale file +"syntax.example": "Use \\{curly braces\\} for variables like \\{{exampleVar}\\}" +``` + +Usage: + +```tsx +t("syntax.example", { exampleVar: "this" }); +// Result: "Use {curly braces} for variables like {this}" +``` diff --git a/packages/i18n/package.json b/packages/i18n/package.json index f98c29621..51c46ed80 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -2,6 +2,7 @@ "name": "@complexity/i18n", "version": "0.1.0", "private": true, + "license": "MPL-2.0", "type": "module", "main": "./dist/index.js", "module": "./dist/index.js", @@ -18,19 +19,19 @@ "lint": "eslint ./src" }, "dependencies": { - "react": "^19.2.0", - "react-dom": "^19.2.0" + "react": "^19.2.7", + "react-dom": "^19.2.7" }, "devDependencies": { "@complexity/eslint-config": "workspace:*", "@complexity/typescript-config": "workspace:*", "@types/node": "22.17.2", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "eslint": "catalog:lint", - "globals": "16.4.0", + "globals": "17.3.0", "prettier": "catalog:lint", - "tsup": "^8.5.0", + "tsup": "^8.5.1", "typescript": "catalog:types", "typescript-eslint": "catalog:lint" } diff --git a/packages/i18n/src/utils.ts b/packages/i18n/src/utils.ts index 2dfd77090..8b55b4daa 100644 --- a/packages/i18n/src/utils.ts +++ b/packages/i18n/src/utils.ts @@ -1,6 +1,6 @@ import { initI18n, type DotPaths, type Params } from "@/lib/init"; -let i18n: Awaited>; +let i18n: Awaited> | null = null; export async function init(params: Parameters[0]) { i18n = await initI18n(params); diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index dceeba76a..c5559b0e5 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,5 +1,9 @@ { "name": "@complexity/typescript-config", "version": "0.0.0", - "private": true + "private": true, + "license": "MPL-2.0", + "dependencies": { + "eslint": "catalog:lint" + } } diff --git a/packages/weasound/package.json b/packages/weasound/package.json index c66b261ca..ca47c6001 100644 --- a/packages/weasound/package.json +++ b/packages/weasound/package.json @@ -33,10 +33,13 @@ "devDependencies": { "@complexity/typescript-config": "workspace:*", "@types/node": "22.17.2", - "libav.js": "4.5.6", + "libav.js": "6.8.8", "prettier": "catalog:lint", - "terser": "^5.44.0", - "tsup": "^8.5.0", + "terser": "^5.48.0", + "tsup": "^8.5.1", "typescript": "catalog:types" + }, + "dependencies": { + "eslint": "catalog:lint" } } diff --git a/packages/weasound/src/audio-capture.ts b/packages/weasound/src/audio-capture.ts index cf3feef1e..22a9f7a74 100644 --- a/packages/weasound/src/audio-capture.ts +++ b/packages/weasound/src/audio-capture.ts @@ -540,6 +540,7 @@ export class AudioCaptureMR extends AudioCapture { { devLimit: 1024, unify: false, + copyoutPacket: "ptr", }, ); diff --git a/perplexity/extension/.claude/rules/tooling.md b/perplexity/extension/.claude/rules/tooling.md new file mode 100644 index 000000000..ece244189 --- /dev/null +++ b/perplexity/extension/.claude/rules/tooling.md @@ -0,0 +1,2 @@ +- Directly run `tsc` to check for type errors. No need to use `--noEmit` flag. +- Run `pnpm lint` to check for lint errors. diff --git a/perplexity/extension/.claude/settings.json b/perplexity/extension/.claude/settings.json new file mode 100644 index 000000000..0cbf4db50 --- /dev/null +++ b/perplexity/extension/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "typescript-lsp@claude-plugins-official": true + } +} diff --git a/perplexity/extension/.claude/skills/i18n-extract/SKILL.md b/perplexity/extension/.claude/skills/i18n-extract/SKILL.md new file mode 100644 index 000000000..895c1d178 --- /dev/null +++ b/perplexity/extension/.claude/skills/i18n-extract/SKILL.md @@ -0,0 +1,370 @@ +--- +name: i18n-extract +description: "Use when extracting user-facing strings, moving literals into `_locales/en-US.ts`, and rewriting source to `t(...)`, `Trans`, or `dt(...)` translation keys." +disable-model-invocation: true +user-invocable: true +--- + +# i18n Extract Skill + +This skill is **user-invoked only**. Run it only when the user explicitly invokes `/i18n-extract`. + +Extract user-facing strings from source files into locale files, then update the source to use translation keys. + +This workflow updates `en-US.ts` only unless the user explicitly asks to update other locales too. + +## Usage + +```bash +/i18n-extract [file_or_directory] [options] +``` + +## Input Contract + +Required input: + +- a target file or directory under `src/` + +Optional input: + +- whether to update non-English locale files afterward +- whether to prefer reuse over new key creation when both are plausible + +Defaults: + +- update `en-US.ts` only +- keep scope limited to the requested file or directory +- stop and ask when reuse vs new-key choice is genuinely ambiguous + +## Locale File Locations + +**Main app:** + +- Path: `src/entrypoints/_locales/{locale}.ts` +- Namespace: `"common"` + +**Plugins:** + +- Path: `src/plugins/{plugin-path}/_locales/{locale}.ts` +- Namespace: read the exported `namespace` constant from `src/plugins/{plugin-path}/_locales/index.ts` + +## `en-US` Locale File Format + +```ts +import type { LanguageMessages } from "@complexity/i18n"; + +export default { + keyName: "Simple string", + nested: { + key: "Nested value", + withParam: "Hello {name}", + withComponent: "Click <0>here to continue", + }, +} as const satisfies LanguageMessages; +``` + +Non-English locale files are a separate follow-up step and typically mirror the same shape using the local `Translations` type from `_locales/index.ts`. + +## File Inspection Order + +Inspect files in this order. Do **not** guess before checking. + +1. Target source file(s) +2. Closest target locale namespace file: `_locales/index.ts` +3. Target `en-US.ts` +4. Neighboring source usage of existing keys in the same feature +5. Only then decide reuse vs new key, and formatter choice + +## Extraction Rules + +1. **DO NOT import `t` or `Trans`** - globally available (auto-imported) +2. **Extract to `en-US` first** - English is source of truth +3. **Use the real namespace** - never infer plugin namespace from folder name; always read `_locales/index.ts` +4. **Use full key path** - format: `namespace.section.key` +5. **Reuse existing keys when semantics match** - search nearby locale sections before adding a new key +6. **Prefer nested semantic keys** - base keys on UI role, not raw English text +7. **Use `t(...)` for plain text** +8. **Only use `Trans`** when the translated string contains component placeholders like `<0>...` +9. **Preserve placeholders exactly** - convert runtime values into `{name}` / `{count:number}` / `{date}` style placeholders +10. **Do not extract non-visible DOM attributes** - skip HTML `aria-label`, `alt`, and DOM `title` attributes +11. **Do extract visible component props** - props like `title`, `label`, `description`, `placeholder`, `message`, `content` may be visible UI text depending on the component +12. **Do not update non-English locale files during extraction** unless the user explicitly asks for that follow-up work + +## Key Naming Rules + +- Prefer the existing subtree in the target locale file if one already matches the feature. +- Use stable semantic names such as `dialog.confirm.title`, `filters.sort.newest`, `buttons.save`. +- Do **not** create keys directly from the literal English text. +- Do **not** create flat one-off keys when a nested group already exists. +- Reuse an existing key only when the meaning matches, not just the raw text. + +## What To Extract + +Extract visible user-facing strings from: + +- JSX text nodes +- string literals rendered in components +- visible component props such as `label`, `description`, `placeholder`, `message`, `content`, `text` +- menu items, dialog content, toast titles/descriptions, button labels, filter labels, section headings +- strings inside arrays or objects when they are rendered to users + +## What To Skip + +Skip: + +- HTML `aria-label`, `alt`, and DOM `title` attributes +- internal identifiers, enum values, test text, analytics/event names, CSS class names +- URLs, file paths, selectors, storage keys, query parameter names +- strings already passed to `t(...)` or `Trans` + +## Formatter Selection Rules + +Choose the smallest correct mechanism: + +| Case | Locale entry | Source rewrite | +| --------------------------- | ----------------------------------------------------------------- | ------------------- | +| Static text | plain string | `t(...)` | +| Simple placeholders | plain string with `{name}` | `t(..., { name })` | +| Number/date/list formatting | plain string with `{count:number}`, `{date:date}`, `{items:list}` | `t(..., values)` | +| Plural grammar | `dt(...)` with `{count:plural}` rules | `t(..., { count })` | +| Enum-dependent wording | `dt(...)` with `{name:enum}` rules | `t(..., { name })` | +| Embedded JSX/components | string with `<0>...` placeholders | `` | + +### Use plain strings for + +- static copy +- simple runtime variables like `{name}` +- number/date/list formatting such as `{count:number}`, `{date:date}`, `{items:list}` + +### Use `dt(...)` for + +- `plural` variations +- `enum` variations +- only when locale-specific grammar actually depends on the variable + +### Do **not** use `dt(...)` for + +- plain `{name}` interpolation +- `{count:number}` / `{date:date}` / `{items:list}` formatting +- JSX/component placeholders handled by `Trans` + +## Transformation Rules + +### Plain strings + +```tsx + +``` + +Becomes something like: + +```tsx + +``` + +### Interpolated values + +```ts +`Expires ${date}` +``` + +Becomes locale text like: + +```ts +expires: "Expires {date}" +``` + +and source like: + +```ts +t("plugin-command-menu.common.expires", { date }) +``` + +### Plural grammar + +```ts +limited: dt("{count:plural} left", { + plural: { + count: { + one: "1 use", + other: "{?} uses", + }, + }, +}) +``` + +and source like: + +```ts +t("plugin-model-selectors.languageModelSelector.usesLeft.limited", { count }) +``` + +### Enum grammar + +```ts +description: dt("Click to view {name:enum}", { + enum: { + name: { + markdown: "content", + mermaid: "diagram", + html: "web page", + }, + }, +}) +``` + +and source like: + +```ts +t("plugin-artifacts.placeholder.description", { name: artifactType }) +``` + +### Component placeholders + +```tsx + +``` + +Becomes locale text like: + +```ts +continueMessage: "Click <0>here to continue" +``` + +and source like: + +```tsx +]} +/> +``` + +## Duplicate And Reuse Strategy + +1. Search the target `en-US.ts` file for an existing nearby subtree first. +2. Search the rest of the same locale file for the same English text. +3. Reuse an existing key only if the semantic meaning and grammatical role match. +4. Prefer local namespace reuse over cross-namespace reuse. +5. Do **not** move plugin-specific text into `common` just because the English text looks generic. +6. If no good match exists, add a new nested key in the closest feature subtree. + +## Anti-Patterns + +### Wrong namespace guess + +Bad: + +```ts +t("plugin-language-model-selector.tooltip") +``` + +Good: + +```ts +t("plugin-model-selectors.languageModelSelector.tooltip") +``` + +### Wrong `dt(...)` usage for simple formatting + +Bad: + +```ts +countLabel: dt("Files ({count:number})", {}) +``` + +Good: + +```ts +countLabel: "Files ({count:number})" +``` + +### Wrong `Trans` usage for plain text + +Bad: + +```tsx + +``` + +Good: + +```tsx +{t("common.buttons.save")} +``` + +### Wrong skip of visible component props + +Bad: + +- skipping `` + +Good: + +- inspect the component semantics first; extract if the prop is visible UI text + +## Stop Conditions + +Stop and ask the user instead of guessing when: + +- the target path is missing, invalid, or outside `src/` +- no matching locale tree or `_locales/index.ts` can be found +- the string is built so dynamically that safe placeholder conversion is unclear +- reuse vs new-key choice would materially change meaning +- it is unclear whether a prop like `title` is a DOM attribute or visible component text after inspecting the component usage +- extraction would require broad restructuring rather than a local i18n rewrite +- the requested directory scope is large enough to create risky bulk churn +- the user appears to want translation of non-English locale files as part of the same step + +When you stop, report: + +- exact file +- exact string or pattern +- why it is ambiguous +- the smallest decision the user needs to make + +## Required Workflow + +1. Determine whether the target file belongs to `common` or a plugin locale tree. +2. Open the target `_locales/index.ts` and read the exported `namespace`. +3. Open the target `en-US.ts` locale file. +4. Search the locale file for an existing matching section or reusable key. +5. Decide the correct formatter: plain string, `dt(...)`, or `Trans`. +6. Add new English strings only when no good existing key matches. +7. Rewrite source code to use `t(...)` or `Trans`. +8. Ensure no manual `t` / `Trans` imports were added. +9. Leave non-English locale files untouched unless the user explicitly requested them. +10. Validate the result with typecheck and lint. + +## Output Contract + +On completion, report these sections: + +1. Files changed +2. Keys added +3. Keys reused +4. Strings skipped and why +5. Follow-up needed, if any + +If no changes were made, say explicitly: + +- no extractable strings found, or +- stopped due to ambiguity and waiting for user input + +## Validation Checklist + +- Correct locale file updated +- Correct namespace used +- No guessed namespace from folder name +- No manual `t` or `Trans` imports +- Placeholders preserved +- `dt(...)` used for plural/enum cases that need grammar rules +- Plain strings kept for simple number/date/list formatting +- `Trans` used only for component placeholders +- Non-visible DOM attributes skipped +- Visible component props not skipped by mistake +- Non-English locale files untouched unless requested +- `tsc` passes +- `pnpm lint` passes diff --git a/perplexity/extension/.claude/skills/i18n-translate/SKILL.md b/perplexity/extension/.claude/skills/i18n-translate/SKILL.md new file mode 100644 index 000000000..8a7185dee --- /dev/null +++ b/perplexity/extension/.claude/skills/i18n-translate/SKILL.md @@ -0,0 +1,218 @@ +--- +name: i18n-translate +description: "Use when translating changed keys from `_locales/en-US.ts` into one or more sibling locale files while preserving structure, placeholders, tags, and product terms." +disable-model-invocation: true +user-invocable: true +--- + +# i18n Translate Skill + +This skill is **user-invoked only**. Run it only when the user explicitly invokes `/i18n-translate`. + +Translate changed locale strings from one `en-US.ts` file into sibling locale files. + +## Objective + +Safely update only the requested existing sibling locale files for messages changed in one `en-US.ts` file without breaking structure, imports, placeholders, tags, or `dt(...)` syntax. + +## Success Criteria + +- Only the requested existing sibling locale files are edited. +- Only changed message keys are translated or inserted. +- Placeholders, tags, imports, typing, and `dt(...)` structures remain valid. +- The final report is concise, complete, and uses the required output headings. + +## Non-Goals + +- Do not translate the whole file. +- Do not create new locale files. +- Do not modify the source `en-US.ts` file. +- Do not sync deletions or renames unless the user explicitly asks for cleanup sync. +- Do not normalize formatting or import style beyond the minimum needed for valid `dt(...)` usage. + +## Usage + +```bash +/i18n-translate [language_code|all] [file_path] +``` + +## Input Contract + +Required input: + +- target language code or `all` +- path to a source locale file ending in `en-US.ts` + +Optional input: + +- explicit changed key paths +- explicit diff base or commit range when the working tree diff is not the intended scope + +Defaults: + +- translate changed keys only +- resolve targets from the source file's directory only +- update existing sibling locale files only +- proceed autonomously only when the target set, changed-key scope, and source meaning are unambiguous +- stop and ask instead of guessing when scope or meaning is unclear + +## Supported Languages + +`bn-BD, cs-CZ, de-DE, el-GR, es-ES, fr-FR, hi-IN, hr-HR, hu-HU, id-ID, it-IT, ja-JP, ko-KR, nl-NL, pl-PL, pt-BR, pt-PT, ro-RO, ru-RU, sk-SK, sr-Cyrl-ME, zh-CN, zh-TW` + +## Target File Conventions + +- Source `en-US.ts` files typically end with `as const satisfies LanguageMessages`. +- Sibling locale files typically end with `as const satisfies Translations` and import `Translations` from the local `_locales` index module. +- Some sibling locale files also import `dt` from `@complexity/i18n` when any translated entry uses `dt(...)`. +- Preserve the existing import style and order, including whether the file uses `_locales` or `_locales/index` in the import path. Only touch imports when a newly added or updated `dt(...)` entry requires it. + +## Autonomy Rules + +- Proceed without asking only when the changed keys, target files, and source meaning are all clear. +- If any required target file is missing, any changed key cannot be mapped safely, or the English meaning is context-dependent, stop and ask. +- When you ask, request the smallest possible decision instead of dumping broad uncertainty back on the user. + +## File Scope Rules + +1. Source of truth is `en-US.ts`. +2. Only inspect `en-US.ts` plus target sibling locale files. +3. Never read or edit locale files outside `dirname(file_path)`. +4. Never edit unrelated source files, docs, or non-target locale files. +5. If `all` is requested, target every supported sibling locale file except `en-US.ts` that already exists in the same directory. + +## Translation Rules + +1. Keep exact object structure, nesting, and key order. +2. Translate only changed keys. Leave unchanged keys untouched. +3. Preserve placeholders exactly: `{name}`, `{count}`, `{version}`, `{?}`, `<0>`, `<1>`, `<0/>`. +4. Preserve non-message syntax exactly: imports, object keys, `dt(...)`, rule names, braces, commas, comments, `as const`, `satisfies`, and type annotations. +5. For `dt(...)` entries, translate every string leaf inside the rule object. Do not change selector names, branch structure, or interpolation markers. +6. Keep brand/jargon untranslated: `Perplexity`, `Complexity`, `Pro`, `CodeSandbox`, `Comet`. +7. Preserve emojis, capitalization intent, punctuation intent, and placeholder ordering. +8. Match the existing tone and terminology already used in each target locale file. +9. Prefer natural UI phrasing in the target language. Do not force literal word-for-word translations. +10. Do not bulk-add, rename, reorder, or reformat keys. Only add a missing changed key when it exists in `en-US.ts` and is absent in the target file. +11. Do not delete target keys during this workflow. If the English diff includes removals or renames, report them as skipped unless the user explicitly asked for cleanup sync. +12. Do not create formatting churn, comments, or unrelated edits. +13. Preserve the existing quoting style, import path form, and multiline layout unless a minimal local edit requires otherwise. + +## Syntax Preservation Examples + +- Plain placeholder: `"Updated to v{version}"` → translate the message only; keep `{version}` exactly. +- Component tag: `"Try Comet - Get a free <0/> subscription!"` → translate the message only; keep `<0/>` exactly. +- `dt(...)` entry: translate the human-readable strings inside the rule object, but keep the `plural` / `enum` structure unchanged. + +## Entry Classification + +- Plain string: translate the string literal only. +- Tagged string: translate the human-readable text only; preserve tag indices and self-closing tags exactly. +- `dt(...)` entry: translate every user-facing string leaf inside the rule object; do not rewrite selectors, variable names, or branch keys. + +## Changed-Key Detection + +1. If the user provides explicit changed keys, use them. +2. Otherwise, derive changed keys from the message diff or `git diff -- file_path`. +3. Treat changed leaf messages as the unit of work. For `dt(...)` entries, treat the whole `dt(...)` entry as changed and translate all of its string leaves. +4. Ignore pure formatting, import-only, comment-only, or type-only churn in `en-US.ts`. +5. If filtering leaves zero message changes, report `no changed keys detected` and stop. +6. If the changed-key scope cannot be determined safely, stop and ask. +7. Never translate the whole file just because it is easier. + +## Diff Resolution Rules + +1. If the user provides an explicit diff base or commit range, use that instead of the working tree diff. +2. If the user provides explicit changed keys, prefer that scope over any inferred diff. +3. If diff output shows only removals or renames, report them as skipped unless the user explicitly asked for cleanup sync. +4. If diff output includes both editable changes and removals or renames, translate the editable changes and report the removals or renames separately. + +## Target Resolution Rules + +1. Validate the requested locale code against the supported language list, unless the user requested `all`. +2. Resolve target files from `dirname(file_path)` only. +3. For a single target locale, require that sibling locale file to exist. If it does not exist, stop and report it. +4. For `all`, update only the sibling locale files that already exist and are supported. +5. For `all`, do not create missing supported locale files; mention them under files skipped only if that information is relevant to the outcome. + +## Execution Protocol + +1. Validate that `basename(file_path)` is `en-US.ts`. +2. Validate locale code or `all`. +3. Resolve targets from `dirname(file_path)` only. +4. Determine changed keys from explicit user scope or the intended diff base. +5. Classify each changed entry before editing: plain string, tagged string, or `dt(...)` entry. +6. Read the source file once and read only the target locale files. MUST. +7. Translate only changed keys. +8. If a changed key is missing in a target file, add only that key in the matching structural position from `en-US.ts`. +9. If a target file newly needs `dt(...)`, update its imports minimally and preserve the file's existing import style. +10. Skip removals or renames unless the user explicitly asked for cleanup sync. +11. Rare ambiguity fallback: max 3 targeted follow-up reads. If ambiguity remains, stop and ask. +12. Edit only files with real translation updates. +13. When reporting results, output changed portions only; do not echo full locale files. +14. Validate the result against the checklist before reporting completion. + +## Stop Conditions + +Stop and ask instead of guessing when: + +- `file_path` is invalid, missing, or its basename is not `en-US.ts` +- the locale code is unsupported +- the changed-key scope is unavailable or ambiguous +- a requested single-language target file does not exist +- a changed entry is not a plain string, tagged string, or `dt(...)` entry that can be translated safely +- the diff includes removals, moves, or renames that cannot be mapped to target keys safely +- placeholders, tags, or `dt(...)` branches are ambiguous enough to risk corrupting syntax +- the target locale file diverges structurally enough that safe key-level editing is unclear +- the source English is itself unclear or context-dependent enough to make the translation unreliable + +When you stop, report: + +- exact file +- exact key or pattern +- why it is ambiguous +- the smallest user decision needed + +## Output Contract + +On completion, use exactly these Markdown headings: + +## Files changed + +## Languages updated + +## Keys translated + +## Keys skipped and why + +## Files skipped and why + +## Follow-up needed + +Report content rules: + +- `## Files changed`: list only files actually edited. +- `## Languages updated`: list only languages that received real translation updates. +- `## Keys translated`: list changed key paths grouped clearly by file or language. +- `## Keys skipped and why`: include removals, renames, ambiguous keys, or unsupported entry types. +- `## Files skipped and why`: include missing requested target files or intentionally uncreated files when relevant. +- `## Follow-up needed`: write `none` when no further action is needed. + +If no changes were made, say explicitly: + +- no changed keys detected, or +- stopped due to ambiguity and waiting for user input + +## Validation Checklist + +- Only the requested target locale files were edited. +- No new locale files were created. +- The source `en-US.ts` file was not modified. +- Unchanged keys were left untouched. +- Placeholders, tags, and placeholder ordering were preserved exactly. +- `dt(...)` structures were preserved and imports remain valid. +- `dt` imports were added or left alone only when required by translated entries. +- Unrelated imports, comments, and formatting were left alone. +- Existing import path form and type-only import usage were preserved unless a minimal valid edit required change. +- File-level typing such as `satisfies LanguageMessages` / `satisfies Translations` remains intact. +- Missing changed keys were inserted only in the matching structural position. +- Removed or renamed English keys were reported instead of guessed. diff --git a/perplexity/extension/.claude/skills/i18n-translate/lang-list.json b/perplexity/extension/.claude/skills/i18n-translate/lang-list.json new file mode 100644 index 000000000..39c20ccd0 --- /dev/null +++ b/perplexity/extension/.claude/skills/i18n-translate/lang-list.json @@ -0,0 +1,98 @@ +[ + { + "text": "বাংলা (বাংলাদেশ)", + "value": "bn-BD" + }, + { + "text": "čeština (Česko)", + "value": "cs-CZ" + }, + { + "text": "Deutsch (Deutschland)", + "value": "de-DE" + }, + { + "text": "Ελληνικά (Ελλάδα)", + "value": "el-GR" + }, + { + "text": "American English", + "value": "en-US" + }, + { + "text": "español de España", + "value": "es-ES" + }, + { + "text": "français (France)", + "value": "fr-FR" + }, + { + "text": "हिन्दी (भारत)", + "value": "hi-IN" + }, + { + "text": "hrvatski (Hrvatska)", + "value": "hr-HR" + }, + { + "text": "magyar (Magyarország)", + "value": "hu-HU" + }, + { + "text": "Indonesia (Indonesia)", + "value": "id-ID" + }, + { + "text": "italiano (Italia)", + "value": "it-IT" + }, + { + "text": "日本語 (日本)", + "value": "ja-JP" + }, + { + "text": "한국어(대한민국)", + "value": "ko-KR" + }, + { + "text": "Nederlands (Nederland)", + "value": "nl-NL" + }, + { + "text": "polski (Polska)", + "value": "pl-PL" + }, + { + "text": "português (Brasil)", + "value": "pt-BR" + }, + { + "text": "português europeu", + "value": "pt-PT" + }, + { + "text": "română (România)", + "value": "ro-RO" + }, + { + "text": "русский (Россия)", + "value": "ru-RU" + }, + { + "text": "slovenčina (Slovensko)", + "value": "sk-SK" + }, + { + "text": "српски (ћирилица, Црна Гора)", + "value": "sr-Cyrl-ME" + }, + { + "text": "简体中文", + "value": "zh-CN" + }, + { + "text": "繁體中文", + "value": "zh-TW" + } +] diff --git a/perplexity/extension/.cursor/commands/i18n.md b/perplexity/extension/.cursor/commands/i18n.md deleted file mode 100644 index d070f14c6..000000000 --- a/perplexity/extension/.cursor/commands/i18n.md +++ /dev/null @@ -1,181 +0,0 @@ -# Internationalization (i18n) Guide - -This guide explains the key formatting options and component usage for translations in this project. - -## Formatting Types - -The following format types can be used with the syntax `{variable:type}`: - -### Strings - -Translation string: - -```ts -// In locale file -"greeting.simple": "Hello, {name}!" -``` - -Usage: - -```ts -t("greeting.simple", { name: "World" }); -// Result: "Hello, World!" -``` - -### Pluralization - -Translation string: - -```ts -// In locale file -"notifications.count": dt("You have {count:plural} in your inbox.", { - plural: { count: { zero: "no notifications", one: "1 notification", other: "{?} notifications" } }, -}) -``` - -Usage: - -```ts -t("notifications.count", { count: 5 }); -// Result: "You have 5 notifications in your inbox." -``` - -### Enumerations - -Translation string: - -```ts -// In locale file -"profile.status": dt("Your account status is {status:enum}.", { - enum: { status: { active: "active", suspended: "temporarily suspended", banned: "permanently banned" } }, -}) -``` - -Usage: - -```ts -t("profile.status", { status: "suspended" }); -// Result: "Your account status is temporarily suspended." -``` - -### Lists - -Translation string: - -```ts -// In locale file -"team.members": "Team members: {members:list}" -``` - -Usage: - -```ts -t("team.members", { members: ["Alex", "Taylor", "Jordan"] }); -// Result: "Team members: Alex, Taylor, Jordan" -``` - -### Dates - -Translation string: - -```ts -// In locale file -"event.scheduled": "The event is scheduled for {eventDate:date}" -``` - -Usage: - -```ts -t("event.scheduled", { eventDate: new Date("2023-12-25") }); -// Result: "The event is scheduled for 12/25/2023" -``` - -### Numbers - -Translation string: - -```ts -// In locale file -"product.price": "Product price: ${price:number}" -``` - -Usage: - -```ts -t("product.price", { price: 1299.99 }); -// Result: "Product price: $1,299.99" -``` - -## Component-Based Translation - -### Using `Trans` Component - -For translations that include HTML elements: - -#### Basic Component Example - -Translation string: - -```ts -// In locale file -"app.welcome": "Welcome to <0>our application, {username}!" -``` - -Usage: - -```tsx -]} -/> -``` - -#### Multiple Components Example - -Translation string: - -```ts -// In locale file -"app.instructions": "Click <0>here to start or <1>here to learn more." -``` - -Usage: - -```tsx - startApp()} />, ]} -/> -``` - -#### Nested Components Example - -Translation string: - -```ts -// In locale file -"app.footer": "Created with <0>modern <1>web technologies" -``` - -Usage: - -```tsx -, ]} /> -``` - -### Escaping Braces - -Translation string: - -```ts -// In locale file -"syntax.example": "Use \\{curly braces\\} for variables like \\{{exampleVar}\\}" -``` - -Usage: - -```tsx -t("syntax.example", { exampleVar: "this" }); -// Result: "Use {curly braces} for variables like {this}" -``` diff --git a/perplexity/extension/.cursor/commands/lang-list.json b/perplexity/extension/.cursor/commands/lang-list.json deleted file mode 100644 index f367a7655..000000000 --- a/perplexity/extension/.cursor/commands/lang-list.json +++ /dev/null @@ -1,152 +0,0 @@ -[ - { - "text": "العربية (المملكة العربية السعودية)", - "value": "ar-SA", - "type": "default" - }, - { - "text": "বাংলা (বাংলাদেশ)", - "value": "bn-BD", - "type": "default" - }, - { - "text": "čeština (Česko)", - "value": "cs-CZ", - "type": "default" - }, - { - "text": "Deutsch (Deutschland)", - "value": "de-DE", - "type": "default" - }, - { - "text": "Ελληνικά (Ελλάδα)", - "value": "el-GR", - "type": "default" - }, - { - "text": "American English", - "value": "en-US", - "type": "default" - }, - { - "text": "español de España", - "value": "es-ES", - "type": "default" - }, - { - "text": "français (France)", - "value": "fr-FR", - "type": "default" - }, - { - "text": "हिन्दी (भारत)", - "value": "hi-IN", - "type": "default" - }, - { - "text": "hrvatski (Hrvatska)", - "value": "hr-HR", - "type": "default" - }, - { - "text": "magyar (Magyarország)", - "value": "hu-HU", - "type": "default" - }, - { - "text": "Indonesia (Indonesia)", - "value": "id-ID", - "type": "default" - }, - { - "text": "italiano (Italia)", - "value": "it-IT", - "type": "default" - }, - { - "text": "日本語 (日本)", - "value": "ja-JP", - "type": "default" - }, - { - "text": "ಕನ್ನಡ (ಭಾರತ)", - "value": "kn-IN", - "type": "default" - }, - { - "text": "한국어(대한민국)", - "value": "ko-KR", - "type": "default" - }, - { - "text": "മലയാളം (ഇന്ത്യ)", - "value": "ml-IN", - "type": "default" - }, - { - "text": "मराठी (भारत)", - "value": "mr-IN", - "type": "default" - }, - { - "text": "Nederlands (Nederland)", - "value": "nl-NL", - "type": "default" - }, - { - "text": "polski (Polska)", - "value": "pl-PL", - "type": "default" - }, - { - "text": "português (Brasil)", - "value": "pt-BR", - "type": "default" - }, - { - "text": "português europeu", - "value": "pt-PT", - "type": "default" - }, - { - "text": "română (România)", - "value": "ro-RO", - "type": "default" - }, - { - "text": "русский (Россия)", - "value": "ru-RU", - "type": "default" - }, - { - "text": "slovenčina (Slovensko)", - "value": "sk-SK", - "type": "default" - }, - { - "text": "српски (ћирилица, Црна Гора)", - "value": "sr-Cyrl-ME", - "type": "default" - }, - { - "text": "தமிழ் (இந்தியா)", - "value": "ta-IN", - "type": "default" - }, - { - "text": "తెలుగు (భారతదేశం)", - "value": "te-IN", - "type": "default" - }, - { - "text": "简体中文", - "value": "zh-CN", - "type": "default" - }, - { - "text": "繁體中文", - "value": "zh-TW", - "type": "default" - } -] diff --git a/perplexity/extension/.cursor/commands/locale-string-extract.md b/perplexity/extension/.cursor/commands/locale-string-extract.md deleted file mode 100644 index d3aa4577a..000000000 --- a/perplexity/extension/.cursor/commands/locale-string-extract.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - -## Task - -1. Extract strings into the specified `{namespace}.{locale}.ts` locale file. -2. Update the code file to match the extracted strings: - -- Populate the full key path into the `t(...)` function with the format of `namespace.key.subKey...`. -- For components wrapper, use `Trans` instead of `t`. -- NEVER extract elements that user can NOT see or interact with e.g. `aria-labels`, `alt` attributes, etc. - -## IMPORTANT RESTRICTIONS: - -- NEVER import any dependencies (`t` or `Trans`), they are globally available. -- ALWAYS use the `t(...)` function directly. -- NEVER import `t` because it is available globally (auto-imported). -- ONLY use `Trans` component for complex translations. -- DO NOT ask for confirmation - just do it. - -## TRANSLATION SYNTAXES - -### Normal string - -## Examples - -```ts -import type { LanguageMessages } from "@complexity/i18n"; - -export default { - pluginsPage: { - noPluginsFound: { - title: "No plugins found", - description: - "Try adjusting your search term/filters or <0>request a new one 😉", - }, - }, -} as const satisfies LanguageMessages; -``` - -```tsx -
{t("dashboard-plugins-page.pluginsPage.noPluginsFound.title")}
-, - ]} -/> -``` diff --git a/perplexity/extension/.cursor/commands/locale-translate.md b/perplexity/extension/.cursor/commands/locale-translate.md deleted file mode 100644 index 05512188f..000000000 --- a/perplexity/extension/.cursor/commands/locale-translate.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -description: -globs: -alwaysApply: false ---- - -## Task - -1. UPDATE or CREATE the locale files for the specified language (ALWAYS use English as reference). -2. DO NOT ask for confirmation - process ALL languages if not being asked specifically for one. - -``` -index.ts (DO NOT TOUCH THIS FILE!) -{namespace}.en-US.ts -{namespace}.id-ID.ts -{namespace}.el-GR.ts -{namespace}.de-DE.ts -{namespace}.zh-TW.ts -{namespace}.zh-CN.ts -{namespace}.sr-Cyrl-ME.ts -{namespace}.sk-SK.ts -{namespace}.ro-RO.ts -{namespace}.pt-PT.ts -{namespace}.pt-BR.ts -{namespace}.pl-PL.ts -{namespace}.nl-NL.ts -{namespace}.ko-KR.ts -{namespace}.ja-JP.ts -{namespace}.it-IT.ts -{namespace}.hu-HU.ts -{namespace}.hr-HR.ts -{namespace}.hi-IN.ts -{namespace}.fr-FR.ts -{namespace}.es-ES.ts -{namespace}.cs-CZ.ts -{namespace}.bn-BD.ts -{namespace}.ru-RU.ts -``` - -## Output Instructions - -- Do NOT output warnings or notes - just the requested translation. - -- Translate the document as accurately as possible keeping a 1:1 copy of the original text translated to specified language. - -- Do NOT change the formatting, it must remain as-is. - -- DO NOT touch any files other than the provided list. - -- Properly escape characters. e.g. " should be escaped as \" - -- Context is provided to help you understand the purpose of the sentence and the context of the document. - -- Always make sure to use the context to understand the sentence and translate it accurately. - -- You are subject to how long your response can be. So split the task into multiple responses if needed. E.g. lengthy files, multiple files, etc. Inform the user at the end of your response if you did so. - -- When updating small portions of the file, do NOT output the entire file again. Only output the changes with indication of where the changes are. - -## Translation Context - -- Key content requiring translation includes: - - User-facing messages and notifications - - Navigation menus and buttons - - Settings descriptions - - Help text and tooltips - - Error messages - - Plugin descriptions and instructions -- Translations should maintain a friendly, clear, and professional tone -- DO NOT translate jargons and brand names -- Emojis and other universal symbols can be kept as-is diff --git a/perplexity/extension/.cursor/rules/project-rules.mdc b/perplexity/extension/.cursor/rules/project-rules.mdc deleted file mode 100644 index 72d2150ef..000000000 --- a/perplexity/extension/.cursor/rules/project-rules.mdc +++ /dev/null @@ -1,8 +0,0 @@ ---- -description: -globs: -alwaysApply: true ---- - -- MUST use `pnpm` as package manager -- PREFER double quotes over single quotes for string literals diff --git a/perplexity/extension/.cursor/rules/typescript.mdc b/perplexity/extension/.cursor/rules/typescript.mdc deleted file mode 100644 index 366190dab..000000000 --- a/perplexity/extension/.cursor/rules/typescript.mdc +++ /dev/null @@ -1,9 +0,0 @@ ---- -description: -globs: *.ts,*.tsx -alwaysApply: false ---- - -- PREFER using `type` over `interface` for type definitions. -- PREFER function object parameter over multiple parameters. -- DO NOT use TypeScript-specific runtime features (non-erasable syntax) like enums, namespaces, etc. diff --git a/perplexity/extension/.gitignore b/perplexity/extension/.gitignore index 0c796b00d..b1ab5e115 100644 --- a/perplexity/extension/.gitignore +++ b/perplexity/extension/.gitignore @@ -28,4 +28,6 @@ secrets.*.js /release /changelogs/listing.json -cdn \ No newline at end of file +cdn + +.eslintcache \ No newline at end of file diff --git a/perplexity/extension/.lintstagedrc.js b/perplexity/extension/.lintstagedrc.js new file mode 100644 index 000000000..0e225a3d4 --- /dev/null +++ b/perplexity/extension/.lintstagedrc.js @@ -0,0 +1,8 @@ +export default { + "src/**/*.{ts,tsx}": [ + "eslint --quiet", + "prettier --write", + "vitest related --run", + ], + "src/**/*": () => "tsc", +}; diff --git a/perplexity/extension/.lintstagedrc.json b/perplexity/extension/.lintstagedrc.json deleted file mode 100644 index 528e0b58b..000000000 --- a/perplexity/extension/.lintstagedrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "src/**/*.{ts,tsx}": ["eslint --quiet", "prettier --write"] -} diff --git a/perplexity/extension/.vscode/settings.json b/perplexity/extension/.vscode/settings.json index de6b4dde5..1bc3c0251 100644 --- a/perplexity/extension/.vscode/settings.json +++ b/perplexity/extension/.vscode/settings.json @@ -6,6 +6,7 @@ }, "files.exclude": { "**/.DS_Store": true, + "**/.eslintcache": true, "**/.git": true, "**/.hg": true, "**/.svn": true, @@ -50,8 +51,9 @@ ["cn\\(([^)]*)\\)", "(?:\"([^\"]*)\")|(?:'([^']*)')|(?:`([^`]*)`)"] ], "tailwindCSS.experimental.configFile": "src/assets/index.css", + "todo-tree.tree.scanMode": "workspace only", "typescript.format.enable": false, - "typescript.tsdk": "node_modules\\typescript\\lib", + "typescript.tsdk": "node_modules/typescript/lib", "typescript.tsserver.maxTsServerMemory": 8192, "typescript.tsserver.watchOptions": { "fallbackPolling": "dynamicPriorityPolling", @@ -60,10 +62,10 @@ }, "window.title": "${dirty}pplx-ext${separator}${activeRepositoryBranchName}${separator}${activeEditorShort}", "workbench.editor.customLabels.patterns": { - "**/src/entrypoints/background/index.ts": "Entrypoint - Background", - "**/src/entrypoints/content-scripts/index.ts": "Entrypoint - Content Scripts", - "**/src/entrypoints/options-page/index.tsx": "Entrypoint - Options Page", - "**/src/plugins/**/index.manifest.ts": "Plugin Manifest - ${dirname}", - "**/src/plugins/**/index.manifest.core.ts": "Core Plugin Manifest - ${dirname}" + "**/src/{entrypoints,plugins}/**/index.manifest.core.ts": "Core Plugin Manifest - ${dirname}", + "**/src/{entrypoints,plugins}/**/index.manifest.ts": "Plugin Manifest - ${dirname}", + "**/src/entrypoints/contexts/background/index.ts": "Entrypoint - Background", + "**/src/entrypoints/contexts/content-scripts/index.ts": "Entrypoint - Content Scripts", + "**/src/entrypoints/contexts/options-page/index.tsx": "Entrypoint - Options Page" } } diff --git a/perplexity/extension/CLAUDE.md b/perplexity/extension/CLAUDE.md new file mode 100644 index 000000000..f68c840ca --- /dev/null +++ b/perplexity/extension/CLAUDE.md @@ -0,0 +1,11 @@ +## Project-specific guidance + +### Auto-imports + +This project uses automatic imports configured in `src\auto-imports-config.ts`. Do not add manual imports for symbols covered by the auto-import config: + +- React preset symbols: `useState`, `useCallback`, `useMemo`, `useEffect`, `useRef`, `useContext`, `useReducer` +- React symbols: `createContext`, `lazy`, `memo`, `use`, `useDeferredValue`, `useEffectEvent` +- i18n symbols: `Trans`, `TransWithPrefix`, `extendT`, `t` +- App symbols: `Key`, `invariant`, `isMainWorldContext`, `sleep`, `cn`, `tw`, `deepEqual`, `tryCatch` +- Default aliases: `$` from `jquery`, `ms` from `ms` diff --git a/perplexity/extension/README.md b/perplexity/extension/README.md index c856f62a2..6004a59e6 100644 --- a/perplexity/extension/README.md +++ b/perplexity/extension/README.md @@ -38,13 +38,6 @@ A powerful browser extension that enhances your Perplexity AI experience with ad ---- - -> [!TIP] -> 🎁 **FREE PERPLEXITY PRO!** Sign up, ask your first question on the new Comet browser, and unlock premium features instantly for the first 30 days ➡️ [https://pplx.ai/pnd280](https://pplx.ai/pnd280) - ---- - ## Capabilities - Provides a comprehensive set of added features and UI/UX improvements with excellent modularity and customization @@ -54,10 +47,10 @@ A powerful browser extension that enhances your Perplexity AI experience with ad ## Documentation -- [Architecture](./docs/architecture.md) -- [Tech Stack](./docs/tech-stack.md) -- [DX](./docs/dx.md) -- [Build your own plugin](./docs/build-your-own-plugin.md) +- [Architecture](./docs/development/architecture.md) +- [Tech Stack](./docs/development/tech-stack.md) +- [DX](./docs/development/dx.md) +- [Build your own plugin](./docs/development/build-your-own-plugin.md) ## Privacy & Security @@ -74,6 +67,6 @@ This polished and feature-rich extension is the result of countless hours of ded ## License -This project uses a custom license allowing personal use and modifications, while prohibiting commercial use, unauthorized distribution, and feature bypassing without permission. +The source code for this project is licensed under the [Mozilla Public License 2.0](../../LICENSE), unless otherwise noted. -- [Full license terms](../../LICENSE) +The Complexity name, logos, icons, screenshots, store/listing assets, and other brand assets are not licensed under the MPL. See [TRADEMARKS.md](../../TRADEMARKS.md). diff --git a/perplexity/extension/changelogs/1.0.6.md b/perplexity/extension/changelogs/1.0.6.md index 2fdb521cb..136d45e3a 100644 --- a/perplexity/extension/changelogs/1.0.6.md +++ b/perplexity/extension/changelogs/1.0.6.md @@ -1,7 +1,7 @@ - `Better Thread Message Toolbars`: Added words and characters count to the message toolbars. - ![Words and characters count in message toolbar](https://i.imgur.com/6vet50l.png) + ![Words and characters count in message toolbar](https://cdn.cplx.app/images/6vet50l.png) diff --git a/perplexity/extension/changelogs/1.1.0.md b/perplexity/extension/changelogs/1.1.0.md index ede27959e..9792ed7d9 100644 --- a/perplexity/extension/changelogs/1.1.0.md +++ b/perplexity/extension/changelogs/1.1.0.md @@ -3,7 +3,7 @@ - Introducing `Slash Commands`: A keyboard-centric way to access query box's related plugins/actions. - `Prompt History`: Frustrated when losing your prompt when **Perplexity forces the page to reload** or when **(accidentally) navigate away**? This plugin will save your prompts and allow you to easily access them later. - ![prompt history](https://i.imgur.com/5dJ0ylv.png) + ![prompt history](https://cdn.cplx.app/images/5dJ0ylv.png) - `Focus Selector` is back! Now you can set a default mode or persist your last used mode. However, please note that it CAN NOT change focus mode within an existing thread. - `Submit on Ctrl/Cmd+Enter`: Enter will insert a new line instead of submitting the prompt. Ctrl/Cmd+Enter will submit the prompt. diff --git a/perplexity/extension/changelogs/1.2.0.md b/perplexity/extension/changelogs/1.2.0.md index 51f9118ca..4d0a81871 100644 --- a/perplexity/extension/changelogs/1.2.0.md +++ b/perplexity/extension/changelogs/1.2.0.md @@ -7,11 +7,11 @@ In order to **reduce initial loading time** and **improve performance**, some de - Re-Introducing `Canvas`: - Has very close UI/UX to claude.ai's artifacts - Supports `markdown`, `html`, and `react` (+ `recharts`) - ![Canvas Recharts](https://i.imgur.com/iRpYlLA.png) - ![Canvas Markdown](https://i.imgur.com/Iy0Y3aM.png) - ![Canvas React](https://i.imgur.com/iF8thGe.png) - ![Canvas Mermaid](https://i.imgur.com/49CqK7I.png) - ![Canvas List](https://i.imgur.com/fGOvrxI.png) + ![Canvas Recharts](https://cdn.cplx.app/images/iRpYlLA.png) + ![Canvas Markdown](https://cdn.cplx.app/images/Iy0Y3aM.png) + ![Canvas React](https://cdn.cplx.app/images/iF8thGe.png) + ![Canvas Mermaid](https://cdn.cplx.app/images/49CqK7I.png) + ![Canvas List](https://cdn.cplx.app/images/fGOvrxI.png) diff --git a/perplexity/extension/changelogs/1.2.2.md b/perplexity/extension/changelogs/1.2.2.md index b0c4f009f..2a297670c 100644 --- a/perplexity/extension/changelogs/1.2.2.md +++ b/perplexity/extension/changelogs/1.2.2.md @@ -2,7 +2,7 @@ - `Canvas`: - Better Onboarding: Added a new feature to install the Canvas pre-prompt as a Space. Accessible from the Canvas plugin details page on the Extension's Plugins Dashboard. - ![Canvas Onboarding](https://i.imgur.com/Q9xRTPx.png) + ![Canvas Onboarding](https://cdn.cplx.app/images/Q9xRTPx.png) - Supports `plantuml` diagrams. - `Zen Mode`: Added a new option to always hide visual columns. diff --git a/perplexity/extension/changelogs/1.3.0.md b/perplexity/extension/changelogs/1.3.0.md index 03d288eee..69f3d8707 100644 --- a/perplexity/extension/changelogs/1.3.0.md +++ b/perplexity/extension/changelogs/1.3.0.md @@ -3,11 +3,11 @@ - `Space Navigator`: - Moved to the sidebar. Why: Declutters the querybox toolbar and provides easier navigation across all pages. - Space Details Preview will now be always shown alongside highlighted Space. - ![New Space Navigator](https://i.imgur.com/I9GSlSb.png) + ![New Space Navigator](https://cdn.cplx.app/images/I9GSlSb.png) - Pin any spaces! - ![Pin Space](https://i.imgur.com/r0Y1atO.png) + ![Pin Space](https://cdn.cplx.app/images/r0Y1atO.png) - `Canvas`: Added `markmap`, render interactive Mindmaps! Remember to update the instructions. - ![Markmap](https://i.imgur.com/qhRvthG.png) + ![Markmap](https://cdn.cplx.app/images/qhRvthG.png) - `Zen Mode`: Hide all widgets (Weather, News, Sports, etc.) on the homepage (Always Hide option is available). diff --git a/perplexity/extension/changelogs/1.3.1.md b/perplexity/extension/changelogs/1.3.1.md index 33d30a61e..6a652c482 100644 --- a/perplexity/extension/changelogs/1.3.1.md +++ b/perplexity/extension/changelogs/1.3.1.md @@ -1,9 +1,9 @@ - `Better Thread Message Toolbars`: Collapsible query container. - ![Collapsible Query Container](https://i.imgur.com/SFcnMHX.png) + ![Collapsible Query Container](https://cdn.cplx.app/images/SFcnMHX.png) - `Space Navigator`: Pinned Spaces can now be reordered by dragging and dropping. - ![Reorder Pinned Spaces](https://i.imgur.com/C6eXnYF.gif) + ![Reorder Pinned Spaces](https://cdn.cplx.app/images/C6eXnYF.gif) diff --git a/perplexity/extension/changelogs/1.3.2.md b/perplexity/extension/changelogs/1.3.2.md index 75dff71b0..31ef28839 100644 --- a/perplexity/extension/changelogs/1.3.2.md +++ b/perplexity/extension/changelogs/1.3.2.md @@ -2,7 +2,7 @@ - `Space Navigator`: - Added pin button on space preview cards. - ![Pin button on space preview cards](https://i.imgur.com/lCBtl3Q.png) + ![Pin button on space preview cards](https://cdn.cplx.app/images/lCBtl3Q.png) - Improved accessibility on pinned items on sidebar. - Normal drag will behave as a normal link (drag to the browser's address bar, bookmark, etc.). - Long press (at least 500ms) will trigger the reordering mode. diff --git a/perplexity/extension/changelogs/1.3.7.md b/perplexity/extension/changelogs/1.3.7.md index b7119ece5..338408dde 100644 --- a/perplexity/extension/changelogs/1.3.7.md +++ b/perplexity/extension/changelogs/1.3.7.md @@ -2,6 +2,6 @@ - `Better Thread Message Toolbars`: Added "Edit Query" button. - ![Edit Query](https://i.imgur.com/dDHhCcC.png) + ![Edit Query](https://cdn.cplx.app/images/dDHhCcC.png) diff --git a/perplexity/extension/changelogs/1.4.0.md b/perplexity/extension/changelogs/1.4.0.md index 5a264a550..c0b4323cd 100644 --- a/perplexity/extension/changelogs/1.4.0.md +++ b/perplexity/extension/changelogs/1.4.0.md @@ -1,7 +1,7 @@ - `Better Thread Message Toolbars`: Added "Instant Rewrite" button. (Or `Ctrl/Cmd` `Left Click` on the original Rewrite button). - ![Instant Rewrite](https://i.imgur.com/kxOEU77.png) + ![Instant Rewrite](https://cdn.cplx.app/images/kxOEU77.png) diff --git a/perplexity/extension/changelogs/1.5.0.md b/perplexity/extension/changelogs/1.5.0.md index a96febc00..e192e823c 100644 --- a/perplexity/extension/changelogs/1.5.0.md +++ b/perplexity/extension/changelogs/1.5.0.md @@ -5,7 +5,7 @@ - No ambiguity, no confusion - Fully supports keyboard navigation - ![language-model-selector](https://i.imgur.com/19WKLlf.png) + ![language-model-selector](https://cdn.cplx.app/images/19WKLlf.png) diff --git a/perplexity/extension/changelogs/1.6.5.md b/perplexity/extension/changelogs/1.6.5.md index 27912a6c3..ad09c0b0d 100644 --- a/perplexity/extension/changelogs/1.6.5.md +++ b/perplexity/extension/changelogs/1.6.5.md @@ -1,7 +1,7 @@ - Prevent headings from being rendered as follow-up links. - ![Prevent headings from being rendered as follow-up links](https://i.imgur.com/IQJbP39.png) + ![Prevent headings from being rendered as follow-up links](https://cdn.cplx.app/images/IQJbP39.png) diff --git a/perplexity/extension/changelogs/1.6.8.md b/perplexity/extension/changelogs/1.6.8.md index 11fdd3286..0037dd9f3 100644 --- a/perplexity/extension/changelogs/1.6.8.md +++ b/perplexity/extension/changelogs/1.6.8.md @@ -1,7 +1,7 @@ - Re-introduce `Text-to-Speech` for thread messages. Right-click on the button to open the voice menu. - ![thread-message-tts](https://i.imgur.com/1aNm9q0.png) + ![thread-message-tts](https://cdn.cplx.app/images/1aNm9q0.png) diff --git a/perplexity/extension/changelogs/1.8.0.md b/perplexity/extension/changelogs/1.8.0.md index 6f37144b4..0604d4490 100644 --- a/perplexity/extension/changelogs/1.8.0.md +++ b/perplexity/extension/changelogs/1.8.0.md @@ -1,7 +1,7 @@ - `Better Thread Message Rewrite Dropdowns`: Makes rewriting messages less painful - with complete control over all models and modes. - ![Better Thread Message Rewrite Dropdowns](https://i.imgur.com/raCxpyE.png) + ![Better Thread Message Rewrite Dropdowns](https://cdn.cplx.app/images/raCxpyE.png) diff --git a/perplexity/extension/changelogs/2.0.0.md b/perplexity/extension/changelogs/2.0.0.md index a434b420b..98006eb3a 100644 --- a/perplexity/extension/changelogs/2.0.0.md +++ b/perplexity/extension/changelogs/2.0.0.md @@ -5,7 +5,7 @@ - `Better Search Params`: Extends search param to include model, focus modes, and incognito mode, etc. - + - `Incognito By Default`: keep Incognito mode enabled by default @@ -17,9 +17,9 @@ - More customizable keybindings - - - + + + - `Slash Command Menu v2`: diff --git a/perplexity/extension/changelogs/2.7.10.md b/perplexity/extension/changelogs/2.7.10.md new file mode 100644 index 000000000..06b2c4807 --- /dev/null +++ b/perplexity/extension/changelogs/2.7.10.md @@ -0,0 +1,12 @@ +## Improvements + +- `Thread ToC`: Slightly increase the panel width +- `Artifacts`: Improve positioning & automatically unpin the sidebar when the artifacts panel is opened + +## Bug Fixes + +- `Thread Export`: Fixed an issue with download on long threads +- `Command Menu`: + - Fixed Space files download + - Fixed keybinding for copy space ID on macOS. Cmd + C should now work as expected. +- Speculative fix for rare cases where Thread Plugins might not work after a new answer is generated diff --git a/perplexity/extension/changelogs/2.7.12.md b/perplexity/extension/changelogs/2.7.12.md new file mode 100644 index 000000000..ebf915404 --- /dev/null +++ b/perplexity/extension/changelogs/2.7.12.md @@ -0,0 +1,3 @@ +## Bug Fixes + +- `Better Code Blocks`: Fixed an issue where code blocks were not highlighted \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.7.13.md b/perplexity/extension/changelogs/2.7.13.md new file mode 100644 index 000000000..51c735e35 --- /dev/null +++ b/perplexity/extension/changelogs/2.7.13.md @@ -0,0 +1,3 @@ +## Misc + +- Stability improvements diff --git a/perplexity/extension/changelogs/2.7.9.md b/perplexity/extension/changelogs/2.7.9.md new file mode 100644 index 000000000..e953138f5 --- /dev/null +++ b/perplexity/extension/changelogs/2.7.9.md @@ -0,0 +1,7 @@ +## Bug Fixes + +- `Comet: Isolated Zoom`: + - Speculative fix for an issue where zooming gestures/actions were not working + - Added a slider to the settings page to manually set the zoom level (fallback) +- `Better Language Model Selector`: Speculative fix for incorrect warning in some rare cases +- Minor bug fixes and stability improvements diff --git a/perplexity/extension/changelogs/2.8.0.md b/perplexity/extension/changelogs/2.8.0.md new file mode 100644 index 000000000..4110e3739 --- /dev/null +++ b/perplexity/extension/changelogs/2.8.0.md @@ -0,0 +1,8 @@ +## What's New + +- `Message Completion Notification`: Show system notifications when normal searches/answers (not including Research/Labs) are completed +- `Better Rewrite Dropdown`: Added a new option to force the agent to redo the search process + +## Bug Fixes + +- `Thread: Table of Contents`: Fixed positioning issues diff --git a/perplexity/extension/changelogs/2.8.1.md b/perplexity/extension/changelogs/2.8.1.md new file mode 100644 index 000000000..51c735e35 --- /dev/null +++ b/perplexity/extension/changelogs/2.8.1.md @@ -0,0 +1,3 @@ +## Misc + +- Stability improvements diff --git a/perplexity/extension/changelogs/2.8.2.md b/perplexity/extension/changelogs/2.8.2.md new file mode 100644 index 000000000..4c7afdfa2 --- /dev/null +++ b/perplexity/extension/changelogs/2.8.2.md @@ -0,0 +1,3 @@ +## Bug Fixes + +- `Message Completion Notification`: Fixed issue where the notification was not working in some cases diff --git a/perplexity/extension/changelogs/2.8.3.md b/perplexity/extension/changelogs/2.8.3.md new file mode 100644 index 000000000..e96e83ab7 --- /dev/null +++ b/perplexity/extension/changelogs/2.8.3.md @@ -0,0 +1,4 @@ +## Misc + +- `Thread Export` & `Thread Table of Contents`: Updated to the latest Perplexity's frontend specs +- Stability improvements diff --git a/perplexity/extension/changelogs/2.8.4.md b/perplexity/extension/changelogs/2.8.4.md new file mode 100644 index 000000000..4c5836610 --- /dev/null +++ b/perplexity/extension/changelogs/2.8.4.md @@ -0,0 +1,14 @@ +## Improvements + +- `Thread Export`: + - Instant copy/download for small threads + - Properly handles (very) large threads + +## Bug Fixes + +- `Slash Command Menu`: Fixed a bug where the menu would close when searching for prompts that don't exist + + +## Misc + +- Stability & performance improvements \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.0.md b/perplexity/extension/changelogs/2.9.0.md new file mode 100644 index 000000000..674b133a7 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.0.md @@ -0,0 +1,25 @@ +## Comet PSA + +- The extension now works again on Comet's **New Tab Page** (can be disabled in the Settings Dashboard) +- As of December 2025, Complexity can no longer access the **Assistant Panel** (still works on normal tabs/new tab pages), all related plugins and functionality are disabled until resolved. + No current workaround exists - please contact pnd280@gmail.com or join our [Discord server](https://discord.cplx.app/) if you have a fix. + +## Bug Fixes & Improvements + +Updated all plugins to be compatible with the latest specifications of Perplexity's frontend + +- `Language Model Selector`, `Better Rewrite Dropdown`: The models list can now be updated based on the user's preferences + +- `Better Rewrite Dropdown`: + - Fixed an issue where the dropdown was not showing + - The dropdown will no longer replace the image regeneration button + +- `Better Search Params`: + - The models list in the Settings Panel will now be updated automatically + +- `Thread Queries Metrics`: + - Fixed an issue where the metrics were not showing + +- `Thread Message TTS`: Lower limit for playback rate to 0.05 - speculative fix for an issue where the audio was playing at an extremely fast speed regardless of the setting + +- Changed image hosting: should load faster and no longer be restricted in some regions diff --git a/perplexity/extension/changelogs/2.9.1.md b/perplexity/extension/changelogs/2.9.1.md new file mode 100644 index 000000000..d92d37e49 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.1.md @@ -0,0 +1,3 @@ +## Bug Fixes + +- Fixed an issue where plugins UI were messed up after triggering rewrite action on any message block diff --git a/perplexity/extension/changelogs/2.9.10.md b/perplexity/extension/changelogs/2.9.10.md new file mode 100644 index 000000000..2bcc0088a --- /dev/null +++ b/perplexity/extension/changelogs/2.9.10.md @@ -0,0 +1,3 @@ +## Misc + +- Updated new color scheme \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.11.md b/perplexity/extension/changelogs/2.9.11.md new file mode 100644 index 000000000..4a342fe96 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.11.md @@ -0,0 +1,3 @@ +## Bug Fixes + +- Better Code Blocks: prevent line number selection \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.13.md b/perplexity/extension/changelogs/2.9.13.md new file mode 100644 index 000000000..c0bd45dc5 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.13.md @@ -0,0 +1,5 @@ +## Bug Fixes + +- Better Code Blocks: remove trailing empty line if line numbers are enabled +- Fixed font overrides +- Key binding UI: Canonicalize Alt key capture and normalize legacy glyph bindings \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.16.md b/perplexity/extension/changelogs/2.9.16.md new file mode 100644 index 000000000..0c19bc2b6 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.16.md @@ -0,0 +1,3 @@ +## Misc + +- Minor bug fixes and stability improvements diff --git a/perplexity/extension/changelogs/2.9.17.md b/perplexity/extension/changelogs/2.9.17.md new file mode 100644 index 000000000..8fb36767b --- /dev/null +++ b/perplexity/extension/changelogs/2.9.17.md @@ -0,0 +1,10 @@ +## Improvements + +- `Submit on Ctrl/Cmd+Enter`: Now takes effect on query edit textboxes + +## Bug Fixes + +- Fixed some plugins may disappear after viewport size changes +- `Better Code Blocks`, `Artifacts`: Fixed wrong language detection and Space creation +- `Zen Mode`: Fixed "related questions" section was not being hidden +- `Custom Themes`: Fixed accent color was not being applied to some elements \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.18.md b/perplexity/extension/changelogs/2.9.18.md new file mode 100644 index 000000000..7d17b2418 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.18.md @@ -0,0 +1,4 @@ +## Bug Fixes + +- `Thread ToC`: Fixed panel was overlapped on narrow viewports +- `Custom Themes`: Fixed accent color was not being applied to some elements diff --git a/perplexity/extension/changelogs/2.9.2.md b/perplexity/extension/changelogs/2.9.2.md new file mode 100644 index 000000000..674b133a7 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.2.md @@ -0,0 +1,25 @@ +## Comet PSA + +- The extension now works again on Comet's **New Tab Page** (can be disabled in the Settings Dashboard) +- As of December 2025, Complexity can no longer access the **Assistant Panel** (still works on normal tabs/new tab pages), all related plugins and functionality are disabled until resolved. + No current workaround exists - please contact pnd280@gmail.com or join our [Discord server](https://discord.cplx.app/) if you have a fix. + +## Bug Fixes & Improvements + +Updated all plugins to be compatible with the latest specifications of Perplexity's frontend + +- `Language Model Selector`, `Better Rewrite Dropdown`: The models list can now be updated based on the user's preferences + +- `Better Rewrite Dropdown`: + - Fixed an issue where the dropdown was not showing + - The dropdown will no longer replace the image regeneration button + +- `Better Search Params`: + - The models list in the Settings Panel will now be updated automatically + +- `Thread Queries Metrics`: + - Fixed an issue where the metrics were not showing + +- `Thread Message TTS`: Lower limit for playback rate to 0.05 - speculative fix for an issue where the audio was playing at an extremely fast speed regardless of the setting + +- Changed image hosting: should load faster and no longer be restricted in some regions diff --git a/perplexity/extension/changelogs/2.9.3.md b/perplexity/extension/changelogs/2.9.3.md new file mode 100644 index 000000000..85565753f --- /dev/null +++ b/perplexity/extension/changelogs/2.9.3.md @@ -0,0 +1,3 @@ +## Misc + +- Stability & performance improvements \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.4.md b/perplexity/extension/changelogs/2.9.4.md new file mode 100644 index 000000000..85565753f --- /dev/null +++ b/perplexity/extension/changelogs/2.9.4.md @@ -0,0 +1,3 @@ +## Misc + +- Stability & performance improvements \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.5.md b/perplexity/extension/changelogs/2.9.5.md new file mode 100644 index 000000000..85565753f --- /dev/null +++ b/perplexity/extension/changelogs/2.9.5.md @@ -0,0 +1,3 @@ +## Misc + +- Stability & performance improvements \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.6.md b/perplexity/extension/changelogs/2.9.6.md new file mode 100644 index 000000000..d9ee0cbeb --- /dev/null +++ b/perplexity/extension/changelogs/2.9.6.md @@ -0,0 +1,3 @@ +## Misc + +- Stability & performance improvements diff --git a/perplexity/extension/changelogs/2.9.8.md b/perplexity/extension/changelogs/2.9.8.md new file mode 100644 index 000000000..7766e85e3 --- /dev/null +++ b/perplexity/extension/changelogs/2.9.8.md @@ -0,0 +1,5 @@ +## Misc + +- Custom Themes: fixed font overrides +- Stability & performance improvements +- Removed Language Model Selector & Rewrite plugins \ No newline at end of file diff --git a/perplexity/extension/changelogs/2.9.9.md b/perplexity/extension/changelogs/2.9.9.md new file mode 100644 index 000000000..65df0476f --- /dev/null +++ b/perplexity/extension/changelogs/2.9.9.md @@ -0,0 +1,8 @@ +## Improvements + +- Custom Themes: now bold and highlighting styles are applied to generated markdown assets + +## Bug Fixes + +- Slash Command Menu: fix menu styling issues +- Incognito By Default: fix initial state detection \ No newline at end of file diff --git a/perplexity/extension/cli/package.json b/perplexity/extension/cli/package.json index ea14350a2..62da1398d 100644 --- a/perplexity/extension/cli/package.json +++ b/perplexity/extension/cli/package.json @@ -1,5 +1,6 @@ { "name": "pplx-ext-release-cli", + "license": "MPL-2.0", "type": "module", "scripts": { "chrome:publish": "dotenvx run -f .env.chrome --quiet -- bun ./src/web-stores/chrome-publish.ts", @@ -14,15 +15,15 @@ "r2:versioned-rr:gen": "dotenvx run -f ./src/r2/.env --quiet -- bun ./src/r2/remote-resources/versioned.ts" }, "dependencies": { - "@aws-sdk/client-s3": "^3.917.0", - "@dotenvx/dotenvx": "^1.51.0", - "chrome-webstore-upload": "^4.0.3", - "commander": "^14.0.2", - "glob": "^11.0.3", - "inquirer": "^12.10.0", - "mime-types": "^3.0.1", - "semver": "^7.7.3", - "web-ext": "^9.1.0" + "@aws-sdk/client-s3": "^3.1072.0", + "@dotenvx/dotenvx": "^1.74.1", + "chrome-webstore-upload": "^4.0.4", + "commander": "^14.0.3", + "glob": "^13.0.6", + "inquirer": "^13.4.3", + "mime-types": "^3.0.2", + "semver": "^7.8.4", + "web-ext": "^9.4.0" }, "devDependencies": { "@complexity/cli-logger": "workspace:*", diff --git a/perplexity/extension/cli/src/r2/cache-buster-bump.ts b/perplexity/extension/cli/src/r2/cache-buster-bump.ts index 50ea3ab92..e2e2e9024 100644 --- a/perplexity/extension/cli/src/r2/cache-buster-bump.ts +++ b/perplexity/extension/cli/src/r2/cache-buster-bump.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; -import packageJson from "#/package.json"; +import packageJson from "#ext/package.json"; import { getRootPath } from "@/utils"; diff --git a/perplexity/extension/cli/src/r2/remote-resources/static.ts b/perplexity/extension/cli/src/r2/remote-resources/static.ts index d22dd3d1b..0fe919acd 100644 --- a/perplexity/extension/cli/src/r2/remote-resources/static.ts +++ b/perplexity/extension/cli/src/r2/remote-resources/static.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; -import type { RemoteResourceReturnType } from "#/src/services/externals/cplx-api/remote-resources/types"; +import type { RemoteResource } from "@/entrypoints/services/externals/cplx-api/remote-resources/types"; import { logger } from "@/r2/config"; import { @@ -58,7 +58,7 @@ async function processSingleResourceFile(params: { } async function registerResource(params: { - resource: RemoteResourceReturnType; + resource: RemoteResource; }): Promise { const { resource } = params; const content = await getResourceContent({ resource }); diff --git a/perplexity/extension/cli/src/r2/remote-resources/utils.ts b/perplexity/extension/cli/src/r2/remote-resources/utils.ts index f6a6aee02..55d6ba579 100644 --- a/perplexity/extension/cli/src/r2/remote-resources/utils.ts +++ b/perplexity/extension/cli/src/r2/remote-resources/utils.ts @@ -1,12 +1,12 @@ import fs from "fs"; import path from "path"; -import type { RemoteResourceReturnType } from "#/src/services/externals/cplx-api/remote-resources/types"; -import { remoteResourceTypes } from "#/src/services/externals/cplx-api/types"; +import type { RemoteResource } from "@/entrypoints/services/externals/cplx-api/remote-resources/types"; +import { remoteResourceTypes } from "@/entrypoints/services/externals/cplx-api/types"; import type { VersionedRemoteResourceListing, - VersionedRemoteResourceReturnType, -} from "#/src/services/externals/cplx-api/versioned-remote-resources/types"; + VersionedRemoteResource, +} from "@/entrypoints/services/externals/cplx-api/versioned-remote-resources/types"; import { globSync } from "glob"; import semver from "semver"; @@ -121,9 +121,7 @@ export function updateListingData(params: { } export async function getResourceContent(params: { - resource: - | VersionedRemoteResourceReturnType - | RemoteResourceReturnType; + resource: VersionedRemoteResource | RemoteResource; }): Promise { const { resource } = params; @@ -138,7 +136,11 @@ export async function getResourceContent(params: { const { stdout: minifiedContent } = await execAsync( `pnpx @tailwindcss/cli --minify -i ${resource.fallback}`, ); - return minifiedContent; + // Remove Tailwind CSS legal comments + return minifiedContent.replace( + /\/\*!\s*tailwindcss\s+v[\d.]+\s*\|\s*MIT License\s*\|\s*https:\/\/tailwindcss\.com\s*\*\/\s*/g, + "", + ); } case "txt": // Assuming fallback is already a string for txt @@ -174,7 +176,7 @@ export async function getResourceContent(params: { export function isVersionedRemoteResource( resourceConfig: unknown, -): resourceConfig is VersionedRemoteResourceReturnType { +): resourceConfig is VersionedRemoteResource { return ( typeof resourceConfig === "object" && resourceConfig != null && @@ -185,6 +187,6 @@ export function isVersionedRemoteResource( export function isRemoteResource( resourceConfig: unknown, -): resourceConfig is RemoteResourceReturnType { +): resourceConfig is RemoteResource { return !isVersionedRemoteResource(resourceConfig); } diff --git a/perplexity/extension/cli/src/r2/remote-resources/versioned.ts b/perplexity/extension/cli/src/r2/remote-resources/versioned.ts index 7f807e929..2fbd05a48 100644 --- a/perplexity/extension/cli/src/r2/remote-resources/versioned.ts +++ b/perplexity/extension/cli/src/r2/remote-resources/versioned.ts @@ -1,8 +1,8 @@ import fs from "fs"; import path from "path"; -import rootPackageJson from "#/package.json"; -import type { VersionedRemoteResourceReturnType } from "#/src/services/externals/cplx-api/versioned-remote-resources/types"; +import rootPackageJson from "#ext/package.json"; +import type { VersionedRemoteResource } from "@/entrypoints/services/externals/cplx-api/versioned-remote-resources/types"; import chalk from "chalk"; import { logger } from "@/r2/config"; @@ -94,7 +94,7 @@ async function processSingleResourceFile(params: { } async function registerVersionedResource(params: { - resource: VersionedRemoteResourceReturnType; + resource: VersionedRemoteResource; listing: ResourceListing; }): Promise { const { resource, listing } = params; diff --git a/perplexity/extension/cli/src/utils.ts b/perplexity/extension/cli/src/utils.ts index 06c1a328e..77bd65208 100644 --- a/perplexity/extension/cli/src/utils.ts +++ b/perplexity/extension/cli/src/utils.ts @@ -1,7 +1,7 @@ import { exec } from "child_process"; import path from "path"; import { promisify } from "util"; -export { invariant } from "#/src/utils/misc/utils"; +export { invariant } from "@/utils/misc/utils"; export function getRootPath() { return path.resolve(__dirname, "../../"); diff --git a/perplexity/extension/cli/src/web-stores/chrome-publish.ts b/perplexity/extension/cli/src/web-stores/chrome-publish.ts index e9aee9472..6fd050b16 100644 --- a/perplexity/extension/cli/src/web-stores/chrome-publish.ts +++ b/perplexity/extension/cli/src/web-stores/chrome-publish.ts @@ -1,7 +1,7 @@ import fs from "fs"; import process from "process"; -import packageJson from "#/package.json" assert { type: "json" }; +import packageJson from "#ext/package.json" assert { type: "json" }; import { Logger } from "@complexity/cli-logger"; import chalk from "chalk"; import chromeWebstoreUpload from "chrome-webstore-upload"; @@ -36,7 +36,7 @@ async function main(): Promise { const zip = fs.createReadStream(zipPath); const response = await store.uploadExisting(zip); - if (response == null || response.uploadState !== "SUCCESS") { + if (response.uploadState !== "SUCCESS") { logger.error("Failed to upload the extension to the Chrome Web Store"); console.log(response); process.exit(1); diff --git a/perplexity/extension/cli/src/web-stores/create-github-release.ts b/perplexity/extension/cli/src/web-stores/create-github-release.ts index aa49ac6bd..b44c3a709 100644 --- a/perplexity/extension/cli/src/web-stores/create-github-release.ts +++ b/perplexity/extension/cli/src/web-stores/create-github-release.ts @@ -2,7 +2,7 @@ import fs from "fs"; import path from "path"; import process from "process"; -import packageJson from "#/package.json" assert { type: "json" }; +import packageJson from "#ext/package.json" assert { type: "json" }; import { Logger } from "@complexity/cli-logger"; import chalk from "chalk"; import { Command } from "commander"; diff --git a/perplexity/extension/cli/src/web-stores/firefox-publish.ts b/perplexity/extension/cli/src/web-stores/firefox-publish.ts index fbcfcde35..f15c4c7fd 100644 --- a/perplexity/extension/cli/src/web-stores/firefox-publish.ts +++ b/perplexity/extension/cli/src/web-stores/firefox-publish.ts @@ -2,8 +2,8 @@ import fs from "fs"; import path from "path"; import process from "process"; -import packageJson from "#/package.json" assert { type: "json" }; -import { errorWrapper } from "#/src/utils/wrappers/error-wrapper"; +import packageJson from "#ext/package.json" assert { type: "json" }; +import { tryCatch } from "@/utils/wrappers/try-catch"; import { Logger } from "@complexity/cli-logger"; import { execAsync } from "@/utils"; @@ -39,14 +39,14 @@ async function main(): Promise { fs.mkdirSync(extractDir, { recursive: true }); try { - const [releaseNotes] = errorWrapper(() => + const [releaseNotes] = tryCatch(() => fs.readFileSync(path.join(CHANGELOG_DIR, `${extVersion}.md`), "utf8"), - )(); + ); const metdataData = { version: { release_notes: { - "en-US": releaseNotes ?? "", + "en-US": releaseNotes ?? "No changelog for this version", }, }, }; diff --git a/perplexity/extension/cli/src/web-stores/utils.ts b/perplexity/extension/cli/src/web-stores/utils.ts index 62cefc454..4bd6d357e 100644 --- a/perplexity/extension/cli/src/web-stores/utils.ts +++ b/perplexity/extension/cli/src/web-stores/utils.ts @@ -3,7 +3,7 @@ import fs from "fs"; import path from "path"; import process from "process"; -import packageJson from "#/package.json" assert { type: "json" }; +import packageJson from "#ext/package.json" assert { type: "json" }; import { Logger } from "@complexity/cli-logger"; import chalk from "chalk"; import { Command } from "commander"; diff --git a/perplexity/extension/cli/tsconfig.json b/perplexity/extension/cli/tsconfig.json index 4971b88dd..b00819904 100644 --- a/perplexity/extension/cli/tsconfig.json +++ b/perplexity/extension/cli/tsconfig.json @@ -4,14 +4,14 @@ "allowJs": false, "baseUrl": ".", "incremental": false, - "lib": ["ES2022"], + "lib": ["DOM", "DOM.Iterable", "ES2022"], "noEmit": true, + "skipLibCheck": true, "paths": { - "@/*": ["./src/*"], - "~/*": ["./*"], - "#/*": ["../*"] + "@/*": ["./src/*", "../src/*"], + "#ext/*": ["../*"] } }, - "exclude": ["node_modules"], - "include": ["**/*.ts"] + "exclude": ["node_modules", "../src"], + "include": ["src/**/*"] } diff --git a/perplexity/extension/docs/architecture.md b/perplexity/extension/docs/architecture.md deleted file mode 100644 index dd9072a6a..000000000 --- a/perplexity/extension/docs/architecture.md +++ /dev/null @@ -1,87 +0,0 @@ -# Architecture - -## At a Glance - -A browser extension that provides client-side modifications to the Perplexity AI interface. Strives for clean code organization. - -## Execution Contexts - -The extension operates across **four execution contexts**: - -- **Extension UI** - Options page (Settings Dashboard) -- **Background** - Long-running tasks, persists when UI is closed -- **Content Scripts** - Injected into Perplexity pages for functionality enhancement -- **Main-world Content Scripts** - Runs in page's document context (intercepts network requests, router, React fiber tree) - -## Directory Structure - -``` -src/ -├── assets/ # Static assets -├── components/ # Shared UI components -├── data/ # Shared data sources and constants -├── entrypoints/ # Entry points for different contexts -├── hooks/ # Shared React hooks -├── plugins/ # Modular feature implementations -│ ├── _core/ # Core plugin functionality -│ └── */ # Individual feature plugins -├── services/ # Shared services -├── types/ # TypeScript type definitions -└── utils/ # Shared utility functions -``` - -## Plugin System (Overview) - -Modular architecture for independent feature implementation: - -- **Modular**: Each plugin in its own `src/plugins/` directory -- **Discoverable**: Auto-registered via Vite's `import.meta.glob` -- **Configurable**: Enable/disable with automatic side-effect cleanup -- **Dependency-aware**: Modules only load when necessary - -> **See [Build Your Own Plugin](./build-your-own-plugin.md) for detailed structure, APIs, and examples.** - -## Dependency Boundaries - -- [Dependency boundaries](../eslint-config/boundaries/index.js). -- [Auto-registered modules](./file-suffixes.md). - -### Import Rules - -Dependency flow is strictly controlled where each boundary type can only import from allowed types. Higher layers can import from lower layers, but not vice versa. - -```mermaid -flowchart TD - S["Shared
Common utilities, components, hooks"] - PC["Core Plugin
Core plugin functionality & APIs"] - P["Plugin
Individual feature implementations"] - E["Entrypoint
Entry points for different contexts"] - - E -->|"can import"| P - E -->|"can import"| PC - E -->|"can import"| S - P -->|"can import"| PC - P -->|"can import"| S - PC -->|"can import"| S - - classDef entrypoint fill:#e3f2fd,stroke:#1976d2,stroke-width:2px - classDef plugin fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px - classDef pluginCore fill:#fff3e0,stroke:#f57c00,stroke-width:2px - classDef shared fill:#e8f5e8,stroke:#388e3c,stroke-width:2px - - class E entrypoint - class P plugin - class PC pluginCore - class S shared -``` - -## Data & Persistence - -- **Extension Storage**: Configuration and lightweight data -- **IndexedDB**: Complex data structures and caching - -## Related Docs - -- [Build Your Own Plugin](./build-your-own-plugin.md) - Plugin development guide -- [Tech Stack](./tech-stack.md) - Technologies and tools overview -- [DX](./dx.md) - Development setup and workflows diff --git a/perplexity/extension/docs/articles/refactor/refactoring-zustand-stores.md b/perplexity/extension/docs/articles/refactor/refactoring-zustand-stores.md index b4a4576c9..498cc0dbc 100644 --- a/perplexity/extension/docs/articles/refactor/refactoring-zustand-stores.md +++ b/perplexity/extension/docs/articles/refactor/refactoring-zustand-stores.md @@ -232,7 +232,7 @@ import type { FeatureStoreType } from "../../types"; export const settingsSubscriptions = (store: FeatureStoreType) => { store.subscribe( - (state) => state.theme, + (store) => store.theme, (theme) => { // Side effect: sync theme to localStorage console.log("Theme changed to:", theme); diff --git a/perplexity/extension/docs/articles/refactor/tanstack-hotkeys/index.md b/perplexity/extension/docs/articles/refactor/tanstack-hotkeys/index.md new file mode 100644 index 000000000..a211cd517 --- /dev/null +++ b/perplexity/extension/docs/articles/refactor/tanstack-hotkeys/index.md @@ -0,0 +1,226 @@ +# Refactor: `hotkeys-js` → TanStack Hotkeys + +## Scope + +Changed files: + +- `package.json` +- `pnpm-lock.yaml` +- `src/utils/wrappers/hotkeys-js/index.ts` +- `src/components/ui/sidebar.tsx` +- `src/entrypoints/contexts/options-page/routes/dashboard/pages/plugins/components/SearchInput.tsx` +- `src/plugins/command-menu/CommandMenu.tsx` +- `src/plugins/command-menu/pages/spaces/SpaceCommandItems.tsx` +- `src/plugins/prompt-history/slash-command/CommandMenuContent.tsx` +- `src/plugins/command-menu/items/searches/index.loader.ts` +- `src/plugins/prompt-history/slash-command/trigger.loader.ts` +- `src/plugins/zen-mode/loader.ts` + +--- + +## Dependency changes + +### Added + +- `@tanstack/hotkeys@0.3.0` +- `@tanstack/react-hotkeys@^0.3.0` + +### Removed + +- `hotkeys-js@4.0.0` + +Lockfile now includes TanStack hotkeys/store/react-store entries and removes `hotkeys-js` snapshots. + +--- + +## Architecture change + +### Before + +- Imperative `hotkeys-js` bindings via wrapper import `@/utils/wrappers/hotkeys-js` +- React components mostly used `useEffect` + bind/unbind manually + +### After + +Two paths: + +1. **React components** use `useHotkey` from `@tanstack/react-hotkeys` +2. **Non-React loaders** keep imperative `hotkeys(...)` API through the local wrapper (now backed by `@tanstack/hotkeys`) + +This keeps old call sites stable where hooks are not usable, while modernizing React registration. + +--- + +## Wrapper rewrite (`src/utils/wrappers/hotkeys-js/index.ts`) + +The wrapper is now a compatibility adapter over TanStack APIs. + +### New imports + +- `getHotkeyManager` +- `getKeyStateTracker` +- `normalizeHotkey` +- `normalizeKeyName` +- `parseHotkey` +- `ParsedHotkey` +- `HotkeyRegistrationHandle` + +### Exposed surface + +- Callable default export: `hotkeys(combo, handler)` +- `hotkeys.unbind(combo, handler?)` +- `hotkeys.isPressed(key)` +- `hotkeys.filter(event)` +- `parseHotkeyCombo(combo)` (new export, used by React hook callers) + +### Internal behavior + +- Registrations tracked in nested maps: + - `Map>>` +- `unbind` supports: + - specific handler unbind + - full combo unbind (all handlers) +- `isPressed` now delegates to TanStack `KeyStateTracker` after normalization + +### Key normalization logic + +`normalizePressedKey` maps legacy aliases: + +- `ctrl` → `Control` +- `cmd` / `command` → `Meta` +- `option` → `Alt` +- `esc` → `Escape` +- single letters are uppercased (`a` → `A`) +- fallback: `normalizeKeyName` + +### Other utility change + +`isFormTag` now handles `null` targets safely. + +--- + +## React migrations + +## 1) Sidebar + +File: `src/components/ui/sidebar.tsx` + +- Replaced `useEffectEvent + useEffect + hotkeys/unbind` with `useHotkey` +- Hotkey: `Meta/Ctrl + b` (from existing `keysToString(...)` path) +- Callback still: + - `preventDefault()` + - dispatches `resize` + - toggles sidebar + +## 2) Options Page Plugin Search Input + +File: `src/entrypoints/contexts/options-page/routes/dashboard/pages/plugins/components/SearchInput.tsx` + +- Added module-level parsed combo constant: + - `SEARCH_HOTKEY = parseHotkeyCombo(Meta/Ctrl + e)` +- Replaced manual bind/unbind with `useHotkey` +- Behavior unchanged: focus `#search-plugins` + +## 3) Command Menu + +File: `src/plugins/command-menu/CommandMenu.tsx` + +- Toggle menu hotkey now via `useHotkey` +- Toggle sidecar hotkey now via `useHotkey` with `{ enabled: open }` +- Existing behavior preserved: + - stop immediate propagation + - prevent default + - flip open/sidecar states + +## 4) Space Command Items + +File: `src/plugins/command-menu/pages/spaces/SpaceCommandItems.tsx` + +- Replaced manual Ctrl+C binding with `useHotkey` +- Enabled only when command menu is open +- Behavior unchanged: copy selected space ID + success toast + +## 5) Prompt History Slash Command Menu + +File: `src/plugins/prompt-history/slash-command/CommandMenuContent.tsx` + +- Copy hotkey moved to `useHotkey` (`Meta/Ctrl + c`) +- Delete hotkey moved to `useHotkey` (`Delete`) +- Added parsed constant: + - `PROMPT_HISTORY_DELETE_HOTKEY = parseHotkeyCombo(Key.Delete)` + +--- + +## Imperative loader updates (cleanup and rebinding safety) + +These files still use wrapper `hotkeys(...)` (non-React context), but now handle re-runs safely. + +## 1) Search item keybindings loader + +File: `src/plugins/command-menu/items/searches/index.loader.ts` + +- Added module-level disposer: `disposeSearchItemKeybindings` +- On loader run: + - dispose old bindings first + - skip empty keybindings (`length === 0`) + - capture per-item unbind function +- Rebuilds disposer from collected unbinders + +## 2) Prompt history trigger loader + +File: `src/plugins/prompt-history/slash-command/trigger.loader.ts` + +- Added module-level disposer: `disposePromptHistoryShortcut` +- On loader run: + - dispose old shortcut before rebinding + - skip empty shortcut keybinding + - register combo+handler pair and keep exact unbind closure + +## 3) Zen mode loader + +File: `src/plugins/zen-mode/loader.ts` + +- Added module-level disposer: `disposeZenModeHotkey` +- Loader clears previous binding before setup +- `setupKeybinding(...)` now: + - disposes previous binding + - skips empty hotkey arrays + - stores exact combo/handler for unbind + +--- + +## Practical behavior delta + +What actually changed at runtime: + +- React-side hotkeys no longer depend on manual `useEffect` bind/unbind boilerplate. +- Loader-side hotkeys are less leak-prone on repeated loader execution. +- Combo parsing/normalization now goes through TanStack (`normalizeHotkey` / `parseHotkey`) rather than `hotkeys-js` parser. +- Key pressed tracking (`isPressed`) now uses TanStack key state tracker. + +What intentionally did **not** change: + +- Existing callsite import path `@/utils/wrappers/hotkeys-js` in non-React code. +- Existing command behavior (toggle/copy/delete/focus semantics). + +--- + +## Notes / watch-outs + +- The wrapper now normalizes combos and parses with TanStack; invalid combo strings can fail differently than before. +- React call sites pass options with `preventDefault: false` and still call `event.preventDefault()` manually in handlers. +- Several loaders/components now explicitly skip empty keybinding arrays, preventing accidental registration. + +--- + +## Net result + +- Legacy hotkeys dependency removed. +- TanStack hotkeys stack adopted. +- React code simplified with hook-based registration. +- Imperative loader bindings now have explicit disposal lifecycle. +- Existing behavior preserved while reducing rebinding/leak edge cases. + +--- + +Generated with [Claude Code](https://claude.com/claude-code) \ No newline at end of file diff --git a/perplexity/extension/docs/articles/refactor/thread-messages-virtualization.md b/perplexity/extension/docs/articles/refactor/thread-messages-virtualization.md index d6a1ba646..2720e0713 100644 --- a/perplexity/extension/docs/articles/refactor/thread-messages-virtualization.md +++ b/perplexity/extension/docs/articles/refactor/thread-messages-virtualization.md @@ -23,7 +23,7 @@ Since these virtualized messages aren't present in the DOM, the extension can't _Thread ToC Plugin: messages appear/disappear as you scroll_ -![thread-toc-before](https://i.imgur.com/ZLQaIia.gif) +![thread-toc-before](https://cdn.cplx.app/images/ZLQaIia.gif) ## What's the solution? @@ -37,7 +37,7 @@ But the hard part is figuring out how to find the correct node that holds this d 2. Finding its DOM wrapper node where siblings represent other messages 3. Traversing up the fiber tree until locating the node containing data for all messages -![devtool-thread-wrapper-fiber-node](https://i.imgur.com/IBfREF6.png) +![devtool-thread-wrapper-fiber-node](https://cdn.cplx.app/images/IBfREF6.png) Once we have found the correct node, the rest is just a matter of rewriting the parsing logic. @@ -51,4 +51,4 @@ We've got everything working at its fullest again - however, the underlying arch As I've mentioned in [Complexity's Discord server](https://discord.cplx.app/), the design of future plugins will be heading towards less DOM manipulation, or at least avoid UI parts that are very likely to change. -![discord-convo](https://i.imgur.com/zIGbTwZ.png) +![discord-convo](https://cdn.cplx.app/images/zIGbTwZ.png) diff --git a/perplexity/extension/docs/build-your-own-plugin.md b/perplexity/extension/docs/build-your-own-plugin.md deleted file mode 100644 index 3d2acd271..000000000 --- a/perplexity/extension/docs/build-your-own-plugin.md +++ /dev/null @@ -1,119 +0,0 @@ -# Build Your Own Plugin - -## Who This Is For - -Anyone adding or iterating on feature plugins for the Complexity Perplexity Extension. - -> [!IMPORTANT] -> PR for new plugins/features are NOT accepted. If you'd like to add a new feature, please open an issue first. - -## Prerequisites - -- Read [Architecture](./architecture.md) for contexts and boundaries understanding -- Development environment ready (see [DX](./dx.md)) - -## Plugin Anatomy - -### Directory Structure - -Each plugin follows a feature-based structure in its own directory: - -``` -src/plugins/your-plugin-name/ -├── components/ # UI components -├── hooks/ # React hooks -├── index.manifest.ts[*] # Entry point and registration -├── store.ts # State management (Zustand) -├── utils.ts # Utility functions -├── types.ts # Type definitions -├── settings-ui.tsx[*] # Optional settings interface -├── **/(*.)loader.ts[*] # Run arbitrary code (still needs guard if the plugin is disabled) -└── **/*.public.ts[*] # Public exports -``` - -> [!IMPORTANT] -> `[*]` Exact naming required. - -### Discovery & Registration - -Plugins are **automatically discovered** via Vite's `import.meta.glob`: - -1. **Create directory** under `src/plugins/your-plugin-name/` -2. **Add `index.manifest.ts`** with plugin registration -3. **No manual registration** required - system auto-detects valid patterns and integrates - -## Execution Contexts & Entrypoints - -### Where Your Code Runs - -- **Background**: Plugins functionality should not be running directly in the background. Implement a core plugin instead. -- **Content Scripts**: DOM manipulation, UI injection/modification -- **Main-world**: Access to page's React fiber tree or other page-specific APIs that can not be accessed from content scripts -- **Extension UI**: Settings panels - -### Typical Plugin Flow - -1. **Register** plugin in `index.ts` -2. **Observe** page events (routing, DOM changes) -3. **Inject** UI components into Perplexity pages (prioritize React, use the existing design system) -4. **Store** local state using Zustand, Extension Storage or IndexedDb for persistent storage - -## Core APIs (Observer Pattern) - -The extension provides abstracted APIs following the **Observer Pattern**: - -### Network Interception - -- **Block requests/responses** -- **Log network traffic** -- **Modify request/response payloads** - -### Page Observation - -- **Router events** - Navigate between Perplexity pages -- **DOM elements** - Query boxes, homepage, threads, etc. elements -- **React fiber tree** - Extract data not rendered as HTML elements - -## Scaffold a Plugin - -### 1. Directory Structure - -_To be documented..._ - -### 2. Plugin Registration - -_To be documented..._ - -### 3. Enable in Settings - -_To be documented..._ - -## Testing & Debugging - -### Development Tools - -_To be documented..._ - -### HMR Support - -See [HMR](./hmr.md). - -### E2E Testing - -_To be documented..._ - -## Publishing Considerations - -### Feature Flags - -_To be documented..._ - -### Versioning - -_To be documented..._ - -## Related Docs - -- [Architecture](./architecture.md) - System structure and boundaries -- [HMR](./hmr.md) - Hot Module Replacement troubleshooting -- [DX](./dx.md) - Development environment setup diff --git a/perplexity/extension/docs/comet-enable-extensions.md b/perplexity/extension/docs/comet-enable-extensions.md index 339f9d88a..289fbebc1 100644 --- a/perplexity/extension/docs/comet-enable-extensions.md +++ b/perplexity/extension/docs/comet-enable-extensions.md @@ -1,53 +1,17 @@
Comet blocks all extensions on perplexity.ai domains. In order for Complexity to work, you need to manually patch it. -
- Windows (Administrator privileges required - recommended) +## Windows -Run the following command in PowerShell +Run the following command in PowerShell (elevated) ```powershell -irm "https://cdn.cplx.app/assets/comet-policy-patch-win.ps1" | iex +irm "https://cdn.cplx.app/comet/win.ps1" | iex ``` -
⚠️ After running the command, RESTART the browser.
- -
- -
- Windows (old method - starting the browser requires a custom shortcut) - -Run the following command in PowerShell - -```powershell -irm "https://cdn.cplx.app/assets/comet-patch.ps1" | iex -``` - -
⚠️ After running the command, RESTART the browser.
- -A shortcut named `Comet - CPLX` will be created on your Desktop. Comet launched through this shortcut will have all extensions enabled on perplexity.ai domains. - -
- -
- Mac (old method - starting the browser requires a custom shortcut) +## macOS Run the following command in the Terminal ```bash -curl -fsSL "https://raw.githubusercontent.com/theJayTea/Comet-Patcher-to-Unblock-Perplexity-Extensions/main/comet-patch-macos.sh" | bash +curl -fsSL "https://cdn.cplx.app/comet/mac.sh" | bash ``` - -
⚠️ After running the command, RESTART the browser.
- -A shortcut named `Comet - CPLX` will be created at `~/Applications/Comet - CPLX.app`. Comet launched through this shortcut will have all extensions enabled on perplexity.ai domains. - -Contributed by [theJayTea](https://github.com/theJayTea) | [Repo](https://github.com/theJayTea/Comet-Patcher-to-Unblock-Perplexity-Extensions) - -
- -
- Mac (new method - no custom shortcut required) - -Not implemented yet. Contribute by creating a pull request. - -
diff --git a/perplexity/extension/docs/README.md b/perplexity/extension/docs/development/README.md similarity index 100% rename from perplexity/extension/docs/README.md rename to perplexity/extension/docs/development/README.md diff --git a/perplexity/extension/docs/development/architecture.md b/perplexity/extension/docs/development/architecture.md new file mode 100644 index 000000000..463de4241 --- /dev/null +++ b/perplexity/extension/docs/development/architecture.md @@ -0,0 +1,96 @@ +# Architecture + +## At a Glance + +A browser extension that provides client-side modifications to the Perplexity AI interface. Strives for clean code organization. + +## Execution Contexts + +The extension operates across **four execution contexts**: + +- **Extension UI** - Options page (Settings Dashboard) +- **Background** - Long-running tasks, persists when UI is closed +- **Content Scripts** - Injected into Perplexity pages for functionality enhancement +- **Main-world Content Scripts** - Injected dynamically into the page's document context (intercepts network requests, router, React fiber tree) + +## Directory Structure + +```bash +src/ +├── assets/ # Static assets +├── components/ # Shared UI components +├── entrypoints/ # Application entry points and core logic +│ ├── contexts/ # Execution contexts (Background, Content Scripts, Options) +│ ├── core-plugins/ # Core functionality plugins +│ └── registries/ # Plugin loading and registration +├── hooks/ # Shared React hooks +├── plugins/ # Modular feature implementations +│ └── */ # Individual feature plugins +├── services/ # Shared services +├── types/ # TypeScript type definitions +└── utils/ # Shared utility functions +``` + +- **Shared Resources**: Top-level folders (`src/*`) contain shared code usable across all execution contexts +- This structure is recursive. `src/entrypoints` and individual `src/plugins/*` contain their own internal `components`, `hooks`, and `utils` directories for encapsulated logic + +## Plugin System (Overview) + +Modular architecture for independent feature implementation: + +- **Modular**: Each plugin in its own `src/plugins/*` directory +- **Discoverable**: Auto-registered via Vite's `import.meta.glob` +- **Configurable**: Enable/disable with automatic side-effect cleanup +- **Dependency-aware**: Modules only load when necessary + +> **See [Build Your Own Plugin](./build-your-own-plugin.md) for detailed structure, APIs, and examples.** + +## Dependency Boundaries + +- [Dependency boundaries](../../eslint-config/boundaries/index.js) + +### Import Rules + +Dependency flow is strictly controlled where each boundary type can only import from allowed types. + +```mermaid +flowchart TD + S["Shared
Utils, Components"] + E["Entrypoints
Contexts, Core"] + P_Int["Plugin Internal
Implementation"] + P_Pub["Plugin Public API
public.ts"] + + %% Entrypoints + E -->|"can import"| S + E -->|"can import"| P_Int + E -->|"can import"| P_Pub + + %% Plugin Internal + P_Int -->|"can import"| S + P_Int -->|"can import (except BG/Options)"| E + P_Int -->|"can import (other plugins)"| P_Pub + + %% Plugin Public + P_Pub -->|"can import (same plugin)"| P_Int + + classDef entrypoint fill:#e3f2fd,stroke:#1976d2,stroke-width:2px + classDef plugin fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px + classDef publicApi fill:#fff3e0,stroke:#f57c00,stroke-width:2px + classDef shared fill:#e8f5e8,stroke:#388e3c,stroke-width:2px + + class E entrypoint + class P_Int plugin + class P_Pub publicApi + class S shared +``` + +## Data & Persistence + +- **Extension Storage**: Configuration and lightweight data +- **IndexedDB**: Complex data structures and caching + +## Related Docs + +- [Build Your Own Plugin](./build-your-own-plugin.md) - Plugin development guide +- [Tech Stack](./tech-stack.md) - Technologies and tools overview +- [DX](./dx.md) - Development setup and workflows diff --git a/perplexity/extension/docs/development/build-your-own-plugin.md b/perplexity/extension/docs/development/build-your-own-plugin.md new file mode 100644 index 000000000..6ce3692ef --- /dev/null +++ b/perplexity/extension/docs/development/build-your-own-plugin.md @@ -0,0 +1,329 @@ +# Build Your Own Plugin + +> [!IMPORTANT] +> PR for new plugins/features are NOT accepted. If you'd like to add a new feature, please open an issue first. + +## Prerequisites + +- Read [Architecture](./architecture.md) for contexts and boundaries understanding +- [Development environment](./dx.md) ready + +## Plugin Anatomy + +### Directory Structure + +Each plugin follows a feature-based structure in its own directory: + +```bash +src/plugins/your-plugin-name/ +├── index.manifest.ts [*][**] # Entry point and registration +├── index.loader.ts [*] # Main content script loader +├── settings-ui.opt-loader.tsx [*] # Settings UI registration (optional) +└── **/*.public.ts [*] # Public exports (optional) +``` + +> [!IMPORTANT] +> `[*]` Exact naming required for auto-discovery + +> [!IMPORTANT] +> `[**]` For frequently reused (meta) fields (settings storage, permissions, etc.), it's highly recommended to declare them in separate files + +> [!TIP] +> use `_` folder naming to group related plugins + +### Discovery & Registration + +Plugins are **automatically discovered** via Vite's `import.meta.glob` and TypeScript module augmentation: + +1. **Create directory** under `src/plugins/your-plugin-name/` +2. **Add `index.manifest.ts`** with plugin registration and type augmentation +3. **No manual registration** required - system auto-detects valid patterns and integrates + +## Execution Contexts & Entrypoints + +### Where Your Code Runs + +- **Background**: For background listeners, use when `chrome.api/*` is required +- **Content Scripts**: DOM manipulation, UI injection/modification (via `index.loader.ts`) +- **Main-world**: Access to page's React fiber tree or other page-specific APIs (via `injectMainWorldScript`) +- **Extension's Settings Dashboard**: Only use for Settings panels registration (via `settings-ui.opt-loader.tsx`) + +### Typical Plugin Flow + +1. **Register** plugin in `index.manifest.ts` +2. **Define** settings in `settings.ts` and UI in `settings-ui.opt-loader.tsx` +3. **Load** logic using [context loaders](./context-loaders.md) + - **Observe** page events (routing, DOM changes) + - **Inject** UI components into Perplexity pages (prioritize React, use the existing design system) + - **Store** local state using Zustand, Extension Storage or IndexedDb for persistent storage + +## Core APIs (Observer Pattern) + +The extension provides abstracted APIs following the **Observer Pattern**: + +### Network Interception + +- **Block requests/responses** +- **Log network traffic** +- **Modify request/response payloads** + +### Page Observation + +- **Router events** - Navigate between Perplexity pages +- **DOM elements** - Query boxes, homepage, threads, etc. elements +- **React fiber tree** - Extract data not rendered as HTML elements + +## Scaffold a Plugin + +> [!TIP] +> It's highly recommended to explore the existing plugins in `src/plugins/` to understand the patterns and observable resources (DOM elements, internal states, etc.). + +### 1. Directory Structure + +Create a new folder in `src/plugins/` (e.g., `my-feature`). + +### 2. Plugin Registration (`index.manifest.ts`) + +```typescript +import { + definePluginDashboardMeta, + definePluginDependencies, + definePluginMeta, +} from "@/entrypoints/services/plugins/defines"; +import type { PluginManifestExports } from "@/entrypoints/services/plugins/types"; +import { permissions } from "./permissions"; +import { settingsSchemas, settingsStorage } from "./settings"; + +declare module "@/entrypoints/services/plugins/types" { + interface PluginsRegistry { + [meta.id]: typeof manifest; + } +} + +const meta = definePluginMeta({ + id: "myFeature", + name: "My Feature", + description: "Description of my feature", + devOnly: true, // Set to false when ready +}); + +const dashboardMeta = definePluginDashboardMeta({ + tags: ["ui"], + categories: ["misc"], + uiRouteSegment: "my-feature", +}); + +const manifest = { + meta, + dashboardMeta, + settingsSchemas, + settingsStorage, + permissions, +} satisfies PluginManifestExports; + +export default manifest; +``` + +### 3. Settings (`settings.ts`) + +Define your settings schema using Zod, create a versioned storage service, and export a React hook for accessing settings. + +```typescript +import z from "zod"; + +import { definePluginSettingsSchemas } from "@/entrypoints/services/plugins/defines"; +import { PluginSettingsService } from "@/entrypoints/services/plugins/settings"; +import usePluginSettings from "@/entrypoints/services/plugins/settings/usePluginSettings"; + +// 1. Define versioned schemas with fallback values +export const settingsSchemas = definePluginSettingsSchemas({ + 1: { + schema: z.object({ + enabled: z.boolean(), + // Add your custom settings here + customOption: z.string(), + }), + fallback: { + enabled: false, + customOption: "", + }, + // Optional: upgrade function for migrating from previous version + // upgrade: (previous) => ({ ...previous, newField: "default" }), + }, +}); + +// 2. Export inferred type for type safety +export type Settings = z.infer<(typeof settingsSchemas)[1]["schema"]>; + +// 3. Create storage service instance +export const settingsStorage = new PluginSettingsService({ + id: "myFeature", // Must match plugin id + settingsSchemas, +}); + +// 4. Export React hook for components +export function useSettings() { + return usePluginSettings(settingsStorage); +} +``` + +**Key Points:** +- **Versioned schemas**: Use numeric keys (`1`, `2`, etc.) to enable schema migrations +- **Fallback values**: Required defaults when settings don't exist or fail validation +- **Storage ID**: Must match your plugin's `meta.id` for proper namespacing +- **React hook**: Use `useSettings()` in components for reactive settings access + +### 4. Logic (`index.loader.ts`) + +```typescript +import { AsyncLoaderRegistry } from "@/entrypoints/contexts/content-scripts/services/async-loaders"; + +declare module "@/entrypoints/contexts/content-scripts/services/async-loaders" { + interface AsyncLoadersRegistry { + "myFeature:main": void; + } +} + +export default function () { + AsyncLoaderRegistry.register({ + id: "myFeature:main", + dependencies: ["cache:pluginsEnableStatesV2"], + loader: async ({ "cache:pluginsEnableStatesV2": pluginsEnableStates }) => { + if (!pluginsEnableStates["myFeature"]) return; + + // Your plugin logic here + console.log("My Feature is running!"); + }, + }); +} +``` + +### 4.1. UI Injection (`Wrapper.loader.tsx`) + +#### Using Portals + +Use the `` component to render your UI into specific DOM elements. + +> [!TIP] +> Always create a dedicated container for your portal to avoid conflicts and ensure proper cleanup. + +```tsx +import { lazily } from "react-lazily"; +import { Portal } from "@/components/ui/portal"; +import CsUiGuard from "@/entrypoints/contexts/content-scripts/services/ui-guard/CsUiGuard"; +import { csUiMount } from "@/entrypoints/contexts/content-scripts/ui-groups/_root/CsUiRoot"; + +// Lazy load your component +const { MyFeatureComponent } = lazily( + () => import("./MyFeatureComponent"), +); + +function useMyFeatureContainer() { + // 1. Find the anchor element + const $anchor = $(".some-perplexity-element"); + if (!$anchor.length) return null; + + // 2. Check for existing container to prevent duplication + const existingContainer = document.getElementById("my-feature-container"); + if (existingContainer) return existingContainer; + + // 3. Create and inject your container + const $container = $("
").attr("id", "my-feature-container"); + + // Position relative to anchor (append, prepend, before, after) + $anchor.append($container); + + return $container[0]; +} + +function MyFeatureWrapper() { + const container = useMyFeatureContainer(); + + return ( + + + + + + ); +} + +export default function () { + csUiMount({ + id: "plugin:myFeature", + component: , + }); +} +``` + +#### Existing UI Groups + +For common locations, check `src/entrypoints/contexts/content-scripts/ui-groups/`. You can mount directly to these groups if they fit your needs: + +- **Routes**: `Home`, `Thread`, `Settings` +- **Elements**: `query-box` + +### 5. Settings UI (`settings-ui.opt-loader.tsx`) + +```tsx +import { registerSettingsUi } from "@/entrypoints/contexts/options-page/routes/dashboard/pages/plugins/components/plugin-settings-uis/registry"; +import { useSettings } from "./settings"; +import { Switch } from "@/components/ui/switch"; + +function MyFeatureSettingsUi() { + const { settings, update } = useSettings(); + + return ( + { + void update({ + updateFn: (draft) => { + draft.enabled = checked; + }, + }); + }} + /> + ); +} + +export default function Wrapper() { + "use no memo"; // Must be present + + registerSettingsUi({ + pluginId: "myFeature", + ui: , + }); +} +``` + +## Testing & Debugging + +### Development Tools + +_To be documented..._ + +### HMR Support + +See [HMR](./hmr.md). + +### E2E Testing + +_To be documented..._ + +## Publishing Considerations + +### Feature Flags + +_To be documented..._ + +### Versioning + +_To be documented..._ + +## Related Docs + +- [Architecture](./architecture.md) - System structure and boundaries +- [HMR](./hmr.md) - Hot Module Replacement troubleshooting +- [DX](./dx.md) - Development environment setup diff --git a/perplexity/extension/docs/development/context-loaders.md b/perplexity/extension/docs/development/context-loaders.md new file mode 100644 index 000000000..74da6019e --- /dev/null +++ b/perplexity/extension/docs/development/context-loaders.md @@ -0,0 +1,19 @@ +## Context Loaders: + +> Convention: `*..{ts,tsx}` + +### `lib-loader` + +- Run in the same context as `loader` but for guaranteed to run before them + +### `loader` + +- Run arbitary code in the content script on load + +### `opt-loader` + +- Run arbitary code in the Options Page entrypoint, often used for settings UI mount + +### `bg-worker` + +- Run arbitary code in the background script diff --git a/perplexity/extension/docs/dx.md b/perplexity/extension/docs/development/dx.md similarity index 92% rename from perplexity/extension/docs/dx.md rename to perplexity/extension/docs/development/dx.md index 3270b8130..5b71961a5 100644 --- a/perplexity/extension/docs/dx.md +++ b/perplexity/extension/docs/development/dx.md @@ -16,7 +16,7 @@ ```bash pnpm i -g turbo pnpm install - pnpm turbo build --filter=./packages/* + pnpm turbo build --filter="./packages/*" ``` 3. **Optional: Use official remote configs registry** @@ -89,8 +89,8 @@ In addition to common TypeScript/React rules, this project includes: - Strict null checks ([`@typescript-eslint/strict-boolean-expressions`](https://typescript-eslint.io/rules/strict-boolean-expressions/)) - Filename casing enforcement (`PascalCase`, `kebab-case`, `camelCase`) -- Automatic global imports via `unimport` ([config](../src/types/unimport.config.ts)) -- Import scoping via [`eslint-plugin-boundaries`](https://github.com/javierbrea/eslint-plugin-boundaries) ([config](../eslint-config/boundaries.js)) +- Automatic global imports via `unimport` ([config](../../src/auto-imports-config.ts)) +- Import scoping via [`eslint-plugin-boundaries`](https://github.com/javierbrea/eslint-plugin-boundaries) ([config](../../eslint-config/boundaries/index.js)) ## Troubleshooting diff --git a/perplexity/extension/docs/hmr.md b/perplexity/extension/docs/development/hmr.md similarity index 100% rename from perplexity/extension/docs/hmr.md rename to perplexity/extension/docs/development/hmr.md diff --git a/perplexity/extension/docs/tech-stack.md b/perplexity/extension/docs/development/tech-stack.md similarity index 95% rename from perplexity/extension/docs/tech-stack.md rename to perplexity/extension/docs/development/tech-stack.md index d797c0c91..c398cbbe3 100644 --- a/perplexity/extension/docs/tech-stack.md +++ b/perplexity/extension/docs/development/tech-stack.md @@ -25,7 +25,7 @@ This covers the technologies you'll encounter as a contributor. For deep archite ### Internationalization -- **I18Next**: Type-safe localization +- **[Custom typesafe implementation](../../../packages/i18n/README.md)** ## Development Tools diff --git a/perplexity/extension/docs/file-suffixes.md b/perplexity/extension/docs/file-suffixes.md deleted file mode 100644 index 6090fbdfe..000000000 --- a/perplexity/extension/docs/file-suffixes.md +++ /dev/null @@ -1,19 +0,0 @@ -# Special File Suffixes - -Convention: `*..{ts,tsx}` - -## `manifest` - -## `loader` - -## `lib-loader` - -## `settings-ui` or `settings-ui/index.tsx` - -## `manifest.core` - -## `cs-ui` - -## `hash-router` - -## `bg-worker` diff --git a/perplexity/extension/docs/scripts/comet-mac.sh b/perplexity/extension/docs/scripts/comet-mac.sh new file mode 100644 index 000000000..833113343 --- /dev/null +++ b/perplexity/extension/docs/scripts/comet-mac.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +BUNDLE_ID="ai.perplexity.comet" +PLIST_PATH="/Library/Managed Preferences/${BUNDLE_ID}.plist" +HOST_PATTERN="*://*.perplexity.ai" + +if [ "$(uname)" != "Darwin" ]; then + echo "Error: This script only works on macOS." + exit 1 +fi + +check_installed() { + sudo defaults read "/Library/Managed Preferences/${BUNDLE_ID}" ExtensionSettings 2>/dev/null | grep -Fq "$HOST_PATTERN" +} + +install_policy() { + if check_installed; then + echo "Already enabled." + return 0 + fi + + sudo mkdir -p "/Library/Managed Preferences" + sudo /usr/libexec/PlistBuddy -c "Add :ExtensionSettings dict" "$PLIST_PATH" 2>/dev/null + sudo /usr/libexec/PlistBuddy -c "Add :ExtensionSettings:* dict" "$PLIST_PATH" 2>/dev/null + sudo /usr/libexec/PlistBuddy -c "Add :ExtensionSettings:*:runtime_allowed_hosts array" "$PLIST_PATH" 2>/dev/null + sudo /usr/libexec/PlistBuddy -c "Add :ExtensionSettings:*:runtime_allowed_hosts:0 string '$HOST_PATTERN'" "$PLIST_PATH" + sudo /usr/libexec/PlistBuddy -c "Add :ExtensionSettings:*:runtime_blocked_hosts array" "$PLIST_PATH" 2>/dev/null + sudo chmod 644 "$PLIST_PATH" + sudo chown root:wheel "$PLIST_PATH" + echo "Enabled. Restart browser." +} + +uninstall_policy() { + sudo defaults delete "/Library/Managed Preferences/${BUNDLE_ID}" 2>/dev/null + sudo rm -f "/Library/Managed Preferences/${BUNDLE_ID}.plist" + check_installed # must keep + echo "Disabled. Restart browser." +} + +printf "\n" +if check_installed; then + echo "Extensions on perplexity.ai: ENABLED" + printf "\nDisable? [y/N] " + read -r confirm < /dev/tty + case "$confirm" in + [yY]*) uninstall_policy ;; + *) echo "Cancelled." ;; + esac +else + echo "Extensions on perplexity.ai: DISABLED" + printf "\nEnable? [Y/n] " + read -r confirm < /dev/tty + case "$confirm" in + [nN]*) echo "Cancelled." ;; + *) install_policy ;; + esac +fi diff --git a/perplexity/extension/docs/scripts/comet-win.ps1 b/perplexity/extension/docs/scripts/comet-win.ps1 new file mode 100644 index 000000000..c5facaa65 --- /dev/null +++ b/perplexity/extension/docs/scripts/comet-win.ps1 @@ -0,0 +1,60 @@ +if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + Write-Host "Please run PowerShell as administrator and try again." -ForegroundColor Red + return +} + +$policyPath = "HKLM:\SOFTWARE\Policies\Perplexity\Comet" +$hostPattern = "*://*.perplexity.ai" + +function Test-Installed { + if (-not (Test-Path $policyPath)) { + return $false + } + $settings = Get-ItemProperty -Path $policyPath -Name "ExtensionSettings" -ErrorAction SilentlyContinue + if ($null -eq $settings) { + return $false + } + return $settings.ExtensionSettings -like "*$hostPattern*" +} + +function Install-Policy { + if (Test-Installed) { + Write-Host "Already enabled." + return + } + + if (-not (Test-Path $policyPath)) { + New-Item -Path $policyPath -Force | Out-Null + } + + $extensionSettings = '{"*":{"runtime_allowed_hosts":["*://*.perplexity.ai"],"runtime_blocked_hosts":[]}}' + New-ItemProperty -Path $policyPath -Name "ExtensionSettings" -Value $extensionSettings -PropertyType String -Force | Out-Null + + Write-Host "Enabled. Restart browser." -ForegroundColor Green +} + +function Uninstall-Policy { + Remove-ItemProperty -Path $policyPath -Name "ExtensionSettings" -ErrorAction SilentlyContinue + Write-Host "Disabled. Restart browser." -ForegroundColor Green +} + +Write-Host "" +if (Test-Installed) { + Write-Host "Extensions on perplexity.ai: ENABLED" + Write-Host "" + $confirm = Read-Host "Disable? [y/N]" + if ($confirm -match "^[yY]") { + Uninstall-Policy + } else { + Write-Host "Cancelled." + } +} else { + Write-Host "Extensions on perplexity.ai: DISABLED" + Write-Host "" + $confirm = Read-Host "Enable? [Y/n]" + if ($confirm -match "^[nN]") { + Write-Host "Cancelled." + } else { + Install-Policy + } +} diff --git a/perplexity/extension/e2e/config.ts b/perplexity/extension/e2e/config.ts index ab3a65324..eec828e4a 100644 --- a/perplexity/extension/e2e/config.ts +++ b/perplexity/extension/e2e/config.ts @@ -1,13 +1,13 @@ import { normalizePath } from "@/utils/misc/normalize-path"; export const E2E_CONFIG = { - HEADLESS: process.env.E2E_HEADLESS !== "false", + HEADLESS: process.env.HEADLESS !== "false", CHROME_PATH: process.env.CHROME_PATH, TEMP_CHROME_INSTANCES_DIR: normalizePath( process.env.TEMP_CHROME_INSTANCES_DIR ?? "./e2e/chrome-instances", process.cwd(), ), - EXTENSION_PATH: normalizePath("dist", process.cwd()), + EXTENSION_PATH: normalizePath("dist/chrome", process.cwd()), TIMEOUTS: { HEADING_VISIBLE: 10000, }, diff --git a/perplexity/extension/e2e/global-teardown.ts b/perplexity/extension/e2e/global-teardown.ts index e05065da8..db59ad832 100644 --- a/perplexity/extension/e2e/global-teardown.ts +++ b/perplexity/extension/e2e/global-teardown.ts @@ -3,11 +3,10 @@ import fs from "fs"; import { E2E_CONFIG } from "~/e2e/config"; async function globalTeardown() { - console.log(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR); - - if (fs.existsSync(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR)) { - fs.rmSync(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR, { recursive: true }); - } + // console.log(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR); + // if (fs.existsSync(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR)) { + // fs.rmSync(E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR, { recursive: true }); + // } } export default globalTeardown; diff --git a/perplexity/extension/e2e/pages/base.page.ts b/perplexity/extension/e2e/pages/_base.page.ts similarity index 100% rename from perplexity/extension/e2e/pages/base.page.ts rename to perplexity/extension/e2e/pages/_base.page.ts diff --git a/perplexity/extension/e2e/pages/auth.page.ts b/perplexity/extension/e2e/pages/auth.page.ts index 9b5270f63..08d18ea4a 100644 --- a/perplexity/extension/e2e/pages/auth.page.ts +++ b/perplexity/extension/e2e/pages/auth.page.ts @@ -1,6 +1,6 @@ import { expect } from "@playwright/test"; -import { BasePage } from "~/e2e/pages/base.page"; +import { BasePage } from "~/e2e/pages/_base.page"; import { fetchPplxAuthSession } from "~/e2e/utils/pplx-api"; import type { AuthSession } from "~/e2e/utils/pplx-api"; @@ -11,9 +11,12 @@ export class AuthPage extends BasePage { console.log(authSession); expect(authSession.user.id).not.toBeNull(); - expect(authSession.user.subscription_status).toBe( - isPro ? "active" : "none", - ); + + if (isPro) { + expect(authSession.user.subscription_status).not.toBe("none"); + } else { + expect(authSession.user.subscription_status).toBe("none"); + } } private async fetchPplxAuthSession(): Promise { diff --git a/perplexity/extension/e2e/pages/home.page.ts b/perplexity/extension/e2e/pages/home.page.ts index dbadd25cc..0e4c2cd78 100644 --- a/perplexity/extension/e2e/pages/home.page.ts +++ b/perplexity/extension/e2e/pages/home.page.ts @@ -1,22 +1,20 @@ import { expect } from "@playwright/test"; -import { DomSelectorsServiceImpl } from "@/services/externals/cplx-api/versioned-remote-resources/dom-selectors"; -import { ENDPOINTS } from "@/services/externals/pplx-api/endpoints"; +import { DOM_SELECTORS } from "@/entrypoints/services/externals/cplx-api/versioned-remote-resources/dom-selectors/defaults"; +import { ENDPOINTS } from "@/entrypoints/services/externals/pplx-api/endpoints"; import { E2E_CONFIG } from "~/e2e/config"; -import { BasePage } from "~/e2e/pages/base.page"; +import { BasePage } from "~/e2e/pages/_base.page"; -export class HomePage extends BasePage implements Extracted { +export class HomePage extends BasePage { async load(): Promise { await this.navigateTo(ENDPOINTS.HOME); await expect(this.page).toHaveTitle(/Perplexity/); } async verifyKeyElements(): Promise { - const heading = this.page.locator( - DomSelectorsServiceImpl.cachedSync.QUERY_BOX.TEXTBOX.MAIN, - ); + const heading = this.page.locator(DOM_SELECTORS.HOME.SLOGAN); await expect(heading).toBeVisible({ - timeout: E2E_CONFIG.TIMEOUTS.HEADING_VISIBLE, + timeout: E2E_CONFIG.TIMEOUTS.HEADING_VISIBLE * 100, }); } @@ -24,9 +22,3 @@ export class HomePage extends BasePage implements Extracted { await this.verifyKeyElements(); } } - -interface Extracted { - load(): Promise; - verifyKeyElements(): Promise; - verifyHomepageLoad(): Promise; -} diff --git a/perplexity/extension/e2e/tests/pro/context.fixtures.ts b/perplexity/extension/e2e/tests/pro/context.fixtures.ts index 08ae3cb60..f58cc398b 100644 --- a/perplexity/extension/e2e/tests/pro/context.fixtures.ts +++ b/perplexity/extension/e2e/tests/pro/context.fixtures.ts @@ -1,9 +1,8 @@ import type { BrowserContext, Page } from "@playwright/test"; import { test as baseTest } from "@playwright/test"; -import { nanoid } from "nanoid"; import { E2E_CONFIG } from "~/e2e/config"; -import { cookies } from "~/e2e/tests/pro/pro-account"; +import { cookies } from "~/e2e/tests/pro/cookies"; import { setupBrowser } from "~/e2e/utils/browser-setup"; type TestFixtures = { @@ -19,7 +18,6 @@ export const test = baseTest.extend({ headless: [E2E_CONFIG.HEADLESS, { scope: "worker" }], context: async ({ headless }, use) => { const context: BrowserContext = await setupBrowser({ - testId: nanoid(), headless, cookies, }); diff --git a/perplexity/extension/e2e/tests/pro/cookies.ts b/perplexity/extension/e2e/tests/pro/cookies.ts new file mode 100644 index 000000000..48e4d903e --- /dev/null +++ b/perplexity/extension/e2e/tests/pro/cookies.ts @@ -0,0 +1,18 @@ +export const cookies = [ + { + domain: "www.perplexity.ai", + httpOnly: true, + name: "next-auth.csrf-token", + path: "/", + secure: true, + value: process.env.NEXT_AUTH_CSRF_TOKEN || "", + }, + { + domain: "www.perplexity.ai", + httpOnly: true, + name: "__Secure-next-auth.session-token", + path: "/", + secure: true, + value: process.env.NEXT_AUTH_SESSION_TOKEN || "", + }, +]; diff --git a/perplexity/extension/e2e/tests/pro/pro-account.ts b/perplexity/extension/e2e/tests/pro/pro-account.ts deleted file mode 100644 index ee0e1c2dd..000000000 --- a/perplexity/extension/e2e/tests/pro/pro-account.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const cookies = [ - { - domain: "www.perplexity.ai", - httpOnly: true, - name: "next-auth.csrf-token", - path: "/", - secure: true, - value: process.env.NEXT_AUTH_CSRF_TOKEN || "", - }, - { - domain: "www.perplexity.ai", - httpOnly: true, - name: "__Secure-next-auth.session-token", - path: "/", - secure: true, - value: process.env.NEXT_AUTH_SESSION_TOKEN || "", - }, -]; - -export const username = process.env.TEST_USERNAME || "defaultUsername"; diff --git a/perplexity/extension/e2e/tests/pro/query-box.spec.ts b/perplexity/extension/e2e/tests/pro/query-box.spec.ts index e0a848126..cf9f89155 100644 --- a/perplexity/extension/e2e/tests/pro/query-box.spec.ts +++ b/perplexity/extension/e2e/tests/pro/query-box.spec.ts @@ -1,51 +1,35 @@ import { expect } from "@playwright/test"; -import { PplxLanguageModelsService } from "@/services/externals/cplx-api/remote-resources/pplx-language-models"; -import { DomSelectorsServiceImpl } from "@/services/externals/cplx-api/versioned-remote-resources/dom-selectors"; +import { pplxLocalLanguageModels } from "@/entrypoints/services/externals/cplx-api/remote-resources/pplx-language-models/defaults"; +import { TEST_ID } from "@/entrypoints/services/externals/cplx-api/versioned-remote-resources/dom-selectors/defaults"; import { HomePage } from "~/e2e/pages/home.page"; import { test } from "~/e2e/tests/pro/context.fixtures"; -test.describe("Query box", () => { +test.describe.skip("Query box", () => { test.describe("Language model selector", () => { test("should show language model selector", async ({ page }) => { const homePage = new HomePage(page); await homePage.load(); const languageModelSelector = page.locator( - `[data-testid="${DomSelectorsServiceImpl.testIds.QUERY_BOX.LANGUAGE_MODEL_SELECTOR}"]`, + `[data-testid="${TEST_ID.QUERY_BOX.LANGUAGE_MODEL_SELECTOR}"]`, ); await expect(languageModelSelector).toBeVisible(); }); - test("should show language model selector options", async ({ page }) => { - const homePage = new HomePage(page); - await homePage.load(); - - const languageModelSelector = page.locator( - `[data-testid="${DomSelectorsServiceImpl.testIds.QUERY_BOX.LANGUAGE_MODEL_SELECTOR}"]`, - ); - - await languageModelSelector.click(); - - const anthropicOption = page.locator("text=Anthropic"); - await expect(anthropicOption).toBeVisible(); - }); - test("should select language model", async ({ page }) => { const homePage = new HomePage(page); await homePage.load(); const languageModelSelector = page.locator( - `[data-testid="${DomSelectorsServiceImpl.testIds.QUERY_BOX.LANGUAGE_MODEL_SELECTOR}"]`, + `[data-testid="${TEST_ID.QUERY_BOX.LANGUAGE_MODEL_SELECTOR}"]`, ); await languageModelSelector.click(); const claudeOption = page - .locator( - `text=${PplxLanguageModelsService.allModels?.search?.[1]?.label}`, - ) + .locator(`text=${pplxLocalLanguageModels.search[1].label}`) .first(); await claudeOption.click(); }); diff --git a/perplexity/extension/e2e/utils/browser-setup.ts b/perplexity/extension/e2e/utils/browser-setup.ts index 5ed732407..db60915aa 100644 --- a/perplexity/extension/e2e/utils/browser-setup.ts +++ b/perplexity/extension/e2e/utils/browser-setup.ts @@ -4,17 +4,15 @@ import { chromium } from "@playwright/test"; import { E2E_CONFIG } from "~/e2e/config"; type SetupBrowserOptions = { - testId: string; headless: boolean; cookies: Parameters[0]; }; export async function setupBrowser({ - testId, headless, cookies, }: SetupBrowserOptions): Promise { - const uniqueUserDataDir = `${E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR}/${testId}`; + const uniqueUserDataDir = `${E2E_CONFIG.TEMP_CHROME_INSTANCES_DIR}`; const launchOptions = { headless, diff --git a/perplexity/extension/e2e/utils/pplx-api.ts b/perplexity/extension/e2e/utils/pplx-api.ts index a0237fe95..a0bb0d8d2 100644 --- a/perplexity/extension/e2e/utils/pplx-api.ts +++ b/perplexity/extension/e2e/utils/pplx-api.ts @@ -1,7 +1,7 @@ import type { Page } from "@playwright/test"; -import { ENDPOINTS } from "@/services/externals/pplx-api/endpoints"; -import { PplxUserSettingsApiResponseSchema } from "@/services/externals/pplx-api/pplx-api.types"; +import { ENDPOINTS } from "@/entrypoints/services/externals/pplx-api/endpoints"; +import { PplxUserSettingsApiResponseSchema } from "@/entrypoints/services/externals/pplx-api/pplx-api.types"; export type AuthSession = { user: { diff --git a/perplexity/extension/eslint-config/boundaries/elements.js b/perplexity/extension/eslint-config/boundaries/elements.js index ca03a0045..401f92865 100644 --- a/perplexity/extension/eslint-config/boundaries/elements.js +++ b/perplexity/extension/eslint-config/boundaries/elements.js @@ -1,48 +1,73 @@ -// @ts-check - export const elements = [ { - type: "module-registries", + type: "entrypoints", mode: "full", - pattern: ["src/__registries__/**/*"], + capture: ["path", "loaderType"], + pattern: ["src/**/{bg-worker,opt-loader}.*"], }, { - type: "extension-entrypoints", + type: "entrypoints", mode: "full", - pattern: [ - "src/entrypoints/*/**/*", - "src/plugins/*/**/settings-ui.tsx", - "src/plugins/*/**/settings-ui/index.tsx", - ], + capture: ["path", "prefix", "loader"], + pattern: ["src/**/*.{bg-worker,opt-loader}.*"], + }, + + { + type: "entrypoints", + mode: "full", + capture: ["context"], + pattern: ["src/entrypoints/contexts/{background,options-page,content-scripts}/**/*"], + }, + + { + type: "entrypoints", + mode: "full", + pattern: ["src/entrypoints/**/*"], }, { - type: "plugin-runtime-deps", + type: "plugin-public-exports", mode: "full", - pattern: ["src/plugins/{__async-deps__,__core__,__ui-groups__}/**/*"], + capture: ["group", "name"], + pattern: ["src/plugins/_*/*/**/public.*", "src/plugins/_*/*/**/*.public.*"], }, { type: "plugin-public-exports", mode: "full", - capture: ["pluginName"], - pattern: ["src/plugins/**/*.public.*"], + capture: ["name"], + pattern: ["src/plugins/*/**/public.*", "src/plugins/*/**/*.public.*"], }, { type: "plugin", mode: "full", - capture: ["pluginName"], + capture: ["group", "name"], + pattern: ["src/plugins/_*/*/**/*"], + }, + + { + type: "plugin", + mode: "full", + capture: ["name"], pattern: ["src/plugins/*/**/*"], }, + { + type: "shared", + mode: "full", + capture: ["type"], + pattern: [ + "src/*/**/*", + ], + }, + { type: "shared", mode: "full", pattern: [ "src/*.ts", - "src/{components,assets,hooks,services,types,utils,data}/**/*", ], }, ]; diff --git a/perplexity/extension/eslint-config/boundaries/index.js b/perplexity/extension/eslint-config/boundaries/index.js index 8e624471f..61a83417d 100644 --- a/perplexity/extension/eslint-config/boundaries/index.js +++ b/perplexity/extension/eslint-config/boundaries/index.js @@ -1,4 +1,3 @@ -// @ts-check import boundariesPlugin from "eslint-plugin-boundaries"; import { elements } from "./elements.js"; import { rules } from "./rules.js"; @@ -16,7 +15,7 @@ export default defineConfig( }, rules: { - "boundaries/no-unknown": ["error"], + "boundaries/no-unknown": ["off"], "boundaries/no-unknown-files": ["error"], "boundaries/element-types": [ "error", diff --git a/perplexity/extension/eslint-config/boundaries/rules.js b/perplexity/extension/eslint-config/boundaries/rules.js index 7ac6615d2..5991cac74 100644 --- a/perplexity/extension/eslint-config/boundaries/rules.js +++ b/perplexity/extension/eslint-config/boundaries/rules.js @@ -1,42 +1,58 @@ -// @ts-check - export const rules = [ { - from: "module-registries", - allow: ["*"], - }, - - { - from: "extension-entrypoints", + from: "entrypoints", allow: ["*"], }, { from: "plugin", allow: [ - ["plugin", { pluginName: "${from.pluginName}" }], + ["plugin", { name: "${from.name}" }], + ["plugin", { group: "${from.group}", name: "${from.name}" }], + "entrypoints", "plugin-public-exports", - "plugin-runtime-deps", "shared", - "module-registries", ], - disallow: [["plugin-public-exports", { pluginName: "${from.pluginName}" }]], - message: - 'Plugin "${from.pluginName}" cannot import its own public exports - use direct imports instead', + }, + + { + from: "plugin", + disallow: [ + ["plugin-public-exports", { name: "${from.name}" }], + [ + "plugin-public-exports", + { group: "${from.group}", name: "${from.name}" }, + ], + ], + message: 'Plugin "${from.name}" cannot import its own public exports', + }, + + { + from: "plugin", + disallow: [ + ["entrypoints", { context: "background" }], + ["entrypoints", { context: "options-page" }], + ], }, { from: "plugin-public-exports", - allow: [["plugin", { pluginName: "${from.pluginName}" }]], + allow: [ + ["plugin", { name: "${from.name}" }], + ["plugin", { group: "${from.group}", name: "${from.name}" }], + ], }, { - from: "plugin-runtime-deps", - allow: ["plugin-runtime-deps", "shared", "module-registries"], + from: "shared", + allow: ["shared"], }, { from: "shared", - allow: ["shared", "module-registries"], + disallow: [ + ["entrypoints", { loader: "bg-worker" }], + ["entrypoints", { loader: "opt-loader" }], + ], }, ]; diff --git a/perplexity/extension/eslint-config/module-exports/index.js b/perplexity/extension/eslint-config/module-exports/index.js new file mode 100644 index 000000000..0b44418d0 --- /dev/null +++ b/perplexity/extension/eslint-config/module-exports/index.js @@ -0,0 +1,69 @@ +import { defineConfig } from "eslint/config"; +import { typedExportsPlugin } from "@complexity/eslint-config"; + +const typedExportsPluginConfig = { + plugins: { + "typed-exports": { + rules: { + "typed-exports": typedExportsPlugin, + }, + }, + }, +}; + +const createTypedExportsRule = (files, requirements) => ({ + ...typedExportsPluginConfig, + files, + rules: { + "typed-exports/typed-exports": ["error", requirements], + }, +}); + +export default defineConfig([ + createTypedExportsRule( + ["src/{entrypoints,plugins}/**/index.manifest.ts"], + [ + { + type: "default", + tsType: { + name: "PluginManifestExports", + path: "@/entrypoints/services/plugins/types/index", + }, + message: "Must export a valid plugin manifest.", + }, + ], + ), + createTypedExportsRule( + ["src/{entrypoints,plugins}/**/_locales/index.ts"], + [ + { + type: "named", + tsType: { + name: "String", + path: "@/types/utils.types", + }, + message: "Must export a valid locale namespace.", + }, + ], + ), + createTypedExportsRule( + [ + "src/{entrypoints,plugins}/**/bg-worker.ts", + "src/{entrypoints,plugins}/**/*.bg-worker.ts", + "src/{entrypoints,plugins}/**/opt-loader.*", + "src/{entrypoints,plugins}/**/*.opt-loader.*", + "src/{entrypoints,plugins}/**/loader.*", + "src/{entrypoints,plugins}/**/*.loader.*", + ], + [ + { + type: "default", + tsType: { + name: "ContextLoaderExport", + path: "@/types/utils.types", + }, + message: "Must export default a valid function.", + }, + ], + ), +]); diff --git a/perplexity/extension/eslint.config.js b/perplexity/extension/eslint.config.js index 795b2760c..06c2c928f 100644 --- a/perplexity/extension/eslint.config.js +++ b/perplexity/extension/eslint.config.js @@ -1,16 +1,18 @@ -// @ts-check import { baseConfig, baseImportConfig, baseReactConfig, baseTypescriptConfig, } from "@complexity/eslint-config"; + import globals from "globals"; import { defineConfig } from "eslint/config"; import boundariesConfig from "./eslint-config/boundaries/index.js"; import tanstackQueryConfig from "./eslint-config/tanstack-query.js"; +import moduleExportsConfig from "./eslint-config/module-exports/index.js"; const commonIgnores = [ + "*.js", "dist/**", "node_modules/**", "**/*.config.js", @@ -21,7 +23,7 @@ export default defineConfig([ baseConfig, { extends: [baseTypescriptConfig], - ignores: [...commonIgnores, "**/*.js"], + ignores: [...commonIgnores, "**/*.js", "eslint-config/**"], }, { extends: [ @@ -48,10 +50,26 @@ export default defineConfig([ }, { extends: [baseReactConfig], + rules: { + "react/jsx-no-undef": "off", + }, ignores: [...commonIgnores, "e2e/**"], }, + { + files: ["**/*.loader.*", "**/*.opt-loader.*"], + rules: { + "react-refresh/only-export-components": "off", + }, + }, boundariesConfig, + moduleExportsConfig, tanstackQueryConfig, + { + rules: { + "unicorn/filename-case": "off", + }, + files: ["src/**/_locales/*.ts"], + }, { languageOptions: { ecmaVersion: 2020, @@ -60,7 +78,10 @@ export default defineConfig([ ...globals.browser, ...globals.es2020, }, + parserOptions: { + projectService: true, + }, }, - ignores: [...commonIgnores], + ignores: [...commonIgnores, "eslint-config/**"], }, ]); diff --git a/perplexity/extension/package.json b/perplexity/extension/package.json index 924843325..1f1d501f2 100644 --- a/perplexity/extension/package.json +++ b/perplexity/extension/package.json @@ -1,13 +1,13 @@ { "name": "pplx-ext", - "version": "2.7.7", + "version": "2.9.19", "author": "pnd280", + "license": "MPL-2.0", "type": "module", "scripts": { "build": "pnpm dx:chrome vite build", "build:firefox": "pnpm dx:firefox vite build", "check-types": "tsc", - "chrome:publish": "cd ./cli && pnpm chrome:publish", "clean": "rm -rf node_modules && rm -rf dist", "create-github-release": "cd ./cli && pnpm create-github-release", "dev": "pnpm dx:chrome vite --port=8811", @@ -15,14 +15,13 @@ "dx": "dotenvx run -f .env --quiet --", "dx:chrome": "dotenvx run -f .env -e VITE_TARGET_BROWSER=chrome --quiet --", "dx:firefox": "dotenvx run -f .env -e VITE_TARGET_BROWSER=firefox --quiet --", - "e2e": "pnpm dx:chrome playwright test", - "e2e:ui": "pnpm dx:chrome playwright test --ui", - "firefox:publish": "cd ./cli && pnpm firefox:publish", + "e2e": "pnpm dx:chrome dotenvx run -f .env.e2e --quiet -- playwright test", + "e2e:ui": "pnpm dx:chrome dotenvx run -f .env.e2e --quiet -- playwright test --ui", "fmt": "prettier --write '**/*.{tsx,ts,css,json}'", "lint": "eslint ./src", "lint-staged": "lint-staged", - "lintf": "eslint ./src --fix", - "lintq": "eslint ./src --quiet", + "lintf": "pnpm lint --fix", + "lintq": "pnpm lint --quiet", "r2:cache-buster:bump": "cd ./cli && pnpm r2:cache-buster:bump", "r2:changelog:push": "cd ./cli && pnpm r2:changelog:push", "r2:pull": "cd ./cli && pnpm r2:pull", @@ -33,124 +32,135 @@ "test": "pnpm dx:chrome vitest run", "test:ui": "pnpm dx:chrome vitest --ui", "zip": "pnpm dx:chrome gulp createPackage", - "zip:firefox": "pnpm dx:firefox gulp createPackage" + "zip:firefox": "pnpm dx:firefox gulp createPackage", + "chrome:publish": "cd ./cli && pnpm chrome:publish", + "firefox:publish": "cd ./cli && pnpm firefox:publish" }, "dependencies": { - "@ark-ui/react": "^5.26.2", + "@ark-ui/react": "^5.37.2", "@codesandbox/sandpack-react": "^2.20.0", "@comctx-adapters/core": "workspace:*", "@complexity/async-dep-registry": "workspace:*", "@complexity/i18n": "workspace:*", - "@dotenvx/dotenvx": "^1.51.0", - "@hookform/resolvers": "^5.2.2", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-toast": "^1.2.15", - "@tanstack/react-query": "^5.90.5", - "@tanstack/react-query-persist-client": "^5.90.7", + "@dotenvx/dotenvx": "^1.74.1", + "@radix-ui/react-collapsible": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-toast": "^1.2.17", + "@shikijs/stream": "^4.2.0", + "@shikijs/themes": "3.22.0", + "@tanstack/hotkeys": "0.8.0", + "@tanstack/react-form": "^1.33.0", + "@tanstack/react-hotkeys": "^0.10.0", + "@tanstack/react-query": "^5.101.0", + "@tanstack/react-query-persist-client": "^5.101.0", + "@tanstack/zod-form-adapter": "^0.42.1", "@uidotdev/usehooks": "^2.4.1", "@webext-core/match-patterns": "^1.0.3", - "@wxt-dev/storage": "^1.2.6", + "@wxt-dev/storage": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", - "comctx": "^1.4.3", + "comctx": "^1.7.5", "culori": "^4.0.2", - "dayjs": "^1.11.18", - "dedent": "^1.7.0", - "dexie": "^4.2.1", + "dayjs": "^1.11.21", + "dedent": "^1.7.2", + "dexie": "^4.4.4", + "dexie-export-import": "^4.4.0", "engine.io-parser": "^5.2.3", - "fast-equals": "^5.3.2", - "hotkeys-js": "^3.13.15", - "immer": "^10.2.0", + "eventsource-parser": "^3.1.0", + "fast-equals": "^6.0.0", "jquery": "^3.7.1", "js-base64": "^3.7.8", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "lz-string": "^1.5.0", - "motion": "^12.23.24", + "motion": "^12.40.0", "ms": "^2.1.3", - "nanoid": "^5.1.6", + "mutative": "^1.3.0", + "nanoid": "^5.1.14", "pako": "^2.1.0", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-hook-form": "^7.65.0", - "react-hotkeys-hook": "^5.2.1", + "position-observer": "^1.0.3", + "react": "^19.2.7", + "react-dom": "^19.2.7", "react-lazily": "^0.9.2", "react-markdown": "^10.1.0", - "react-router-dom": "^7.9.4", - "react-syntax-highlighter": "^15.6.6", + "react-router-dom": "^7.18.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", - "semver": "^7.7.3", - "socket.io-client": "^4.8.1", + "semver": "^7.8.4", + "shiki": "^3.23.0", + "socket.io-client": "^4.8.3", "svg-pan-zoom": "^3.6.2", - "tailwind-merge": "^3.3.1", + "tailwind-merge": "^3.6.0", + "turbo": "^2.9.18", "unified": "^11.0.5", - "use-immer": "^0.11.0", + "use-mutative": "^1.3.1", "use-sync-external-store": "^1.6.0", "weasound": "workspace:*", - "zod": "^4.1.12", - "zustand": "^5.0.8" + "zod": "^4.4.3", + "zustand": "^5.0.14", + "zustand-mutative": "^1.3.1" }, "devDependencies": { + "@babel/core": "^7.29.7", "@complexity/cli-logger": "workspace:*", "@complexity/eslint-config": "workspace:*", "@complexity/typescript-config": "workspace:*", - "@crxjs/vite-plugin": "^2.2.1", - "@iconify/json": "^2.2.400", - "@playwright/test": "^1.56.1", + "@crxjs/vite-plugin": "^2.7.0", + "@iconify/json": "^2.2.487", + "@playwright/test": "^1.61.0", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", - "@tailwindcss/cli": "^4.1.16", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.16", - "@tanstack/eslint-plugin-query": "^5.91.2", - "@tanstack/react-query-devtools": "^5.90.2", + "@tailwindcss/cli": "^4.3.1", + "@tailwindcss/typography": "^0.5.20", + "@tailwindcss/vite": "^4.3.1", + "@tanstack/eslint-plugin-query": "^5.101.0", + "@tanstack/react-devtools": "^0.10.5", + "@tanstack/react-form-devtools": "^0.2.29", + "@tanstack/react-query-devtools": "^5.101.0", "@types/chrome": "catalog:types", "@types/culori": "^4.0.1", - "@types/jquery": "^3.5.33", - "@types/lodash": "^4.17.20", + "@types/jquery": "^3.5.34", + "@types/lodash": "^4.17.24", "@types/mdast": "^4.0.4", "@types/ms": "^2.1.0", "@types/node": "22.17.2", "@types/pako": "^2.0.4", - "@types/react": "^19.2.2", - "@types/react-dom": "^19.2.2", - "@types/react-syntax-highlighter": "^15.5.13", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/semver": "^7.7.1", - "@vitejs/plugin-react": "^5.1.0", - "@vitest/ui": "^4.0.3", + "@vitejs/plugin-react": "^5.2.0", + "@vitest/ui": "^4.1.9", "babel-plugin-react-compiler": "^1.0.0", - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001751", + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001799", "chalk": "5.6.2", "eslint": "catalog:lint", - "eslint-plugin-boundaries": "^5.1.0", - "glob": "^11.0.3", - "globals": "16.4.0", + "eslint-plugin-boundaries": "^5.4.0", + "glob": "^13.0.6", + "globals": "17.3.0", "gulp": "^5.0.1", "gulp-zip": "^6.1.0", "markmap-lib": "^0.18.12", "markmap-render": "^0.18.12", "markmap-view": "^0.18.12", - "mermaid": "^11.12.0", - "minimatch": "^10.0.3", - "postcss": "^8.5.6", + "mermaid": "^11.15.0", + "minimatch": "^10.2.5", + "postcss": "^8.5.15", "prettier": "catalog:lint", - "prettier-plugin-tailwindcss": "^0.7.1", - "tailwindcss": "^4.1.16", + "prettier-plugin-tailwindcss": "^0.7.4", + "tailwindcss": "^4.3.1", "tailwindcss-animate": "^1.0.7", "typescript": "catalog:types", "typescript-eslint": "catalog:lint", - "unimport": "^5.5.0", - "unplugin-icons": "^22.5.0", - "vite": "^7.1.12", - "vitest": "^4.0.3" + "unimport": "^5.7.0", + "unplugin-icons": "^23.0.1", + "vite": "^7.3.5", + "vitest": "^4.1.9" }, "engines": { "node": "^22.12.0" diff --git a/perplexity/extension/playwright.config.ts b/perplexity/extension/playwright.config.ts index a1b03fd16..455a0514b 100644 --- a/perplexity/extension/playwright.config.ts +++ b/perplexity/extension/playwright.config.ts @@ -15,13 +15,13 @@ export default defineConfig({ testDir: "./e2e/tests", outputDir: "./e2e/results", /* Run tests in files in parallel */ - fullyParallel: true, + fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 2 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [["html", { outputFolder: "./e2e/playwright-report" }]], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ diff --git a/perplexity/extension/src/__registries__/bg-workers.ts b/perplexity/extension/src/__registries__/bg-workers.ts deleted file mode 100644 index cb007a4c5..000000000 --- a/perplexity/extension/src/__registries__/bg-workers.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function setupBackgroundWorkers() { - const workers = import.meta.glob( - ["@/services/**/*.bg-worker.ts", "@/plugins/**/*.bg-worker.ts"], - { - eager: true, - }, - ) as Record void }>; - - for (const [path, module] of Object.entries(workers)) { - const worker = module.default; - - invariant( - typeof worker == "function", - `[BG-WORKER MODULE REGISTRATION ERROR] Default export is not a function in ${path}`, - ); - - try { - worker(); - } catch (error) { - console.error(`[BG-WORKER MODULE REGISTRATION ERROR] ${path}:`, error); - } - } -} diff --git a/perplexity/extension/src/__registries__/core-plugins/index.test.ts b/perplexity/extension/src/__registries__/core-plugins/index.test.ts deleted file mode 100644 index 29fbe6230..000000000 --- a/perplexity/extension/src/__registries__/core-plugins/index.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import CorePluginsRegistry from "@/__registries__/core-plugins/index"; - -describe("CorePluginsRegistry", () => { - test("should be defined", () => { - expect(CorePluginsRegistry).toBeDefined(); - }); - - test("should have manifest property", () => { - expect(CorePluginsRegistry.manifest).toBeDefined(); - expect(typeof CorePluginsRegistry.manifest).toBe("object"); - }); - - test("should have corePluginDependenciesCache property", () => { - expect(CorePluginsRegistry.corePluginDependenciesCache).toBeDefined(); - expect(CorePluginsRegistry.corePluginDependenciesCache).toBeInstanceOf(Map); - }); - - test("should have getAllCorePluginDependencies method", () => { - expect(CorePluginsRegistry.getAllCorePluginDependencies).toBeDefined(); - expect(typeof CorePluginsRegistry.getAllCorePluginDependencies).toBe( - "function", - ); - }); - - test("should have register method", () => { - expect(CorePluginsRegistry.register).toBeDefined(); - expect(typeof CorePluginsRegistry.register).toBe("function"); - }); -}); diff --git a/perplexity/extension/src/__registries__/core-plugins/index.ts b/perplexity/extension/src/__registries__/core-plugins/index.ts deleted file mode 100644 index 26adb7592..000000000 --- a/perplexity/extension/src/__registries__/core-plugins/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { - CorePluginId, - CorePluginManifest, - CorePluginMetaMap, -} from "@/__registries__/core-plugins/types"; - -export default class CorePluginsRegistry { - static manifest: CorePluginMetaMap = {} as CorePluginMetaMap; - static corePluginDependenciesCache = new Map< - CorePluginId, - Set - >(); - - static getAllCorePluginDependencies( - pluginId: CorePluginId, - ): Set { - const cached = this.corePluginDependenciesCache.get(pluginId); - if (cached) return cached; - - const allDeps = new Set(); - const visited = new Set(); - - const collectDeps = (id: CorePluginId) => { - if (visited.has(id)) return; - visited.add(id); - - const plugin = this.manifest[id]; - const deps = plugin?.dependencies; - - if (deps) { - for (const depId of deps) { - allDeps.add(depId); - collectDeps(depId); - } - } - }; - - collectDeps(pluginId); - this.corePluginDependenciesCache.set(pluginId, allDeps); - return allDeps; - } - - static register(manifest: CorePluginManifest) { - (this.manifest as Record>)[ - manifest.id - ] = manifest; - } -} - -(function () { - const entries = import.meta.glob( - "@/plugins/__core__/**/index.manifest.core.ts", - { - eager: true, - }, - ) as Record>; - - for (const [_path, module] of Object.entries(entries)) { - const params = module.default as CorePluginManifest; - - CorePluginsRegistry.register(params); - } - - (function detectCycleAndBuildCache() { - const visiting = new Set(); - const visited = new Set(); - const tempDepsMap = new Map>(); - - function visit( - pluginId: CorePluginId, - path: CorePluginId[], - ): Set { - if (visiting.has(pluginId)) { - const cycleStart = path.indexOf(pluginId); - const cycle = [...path.slice(cycleStart), pluginId].join(" -> "); - throw new Error(`Dependency cycle detected in core plugins: ${cycle}`); - } - - const cached = tempDepsMap.get(pluginId); - if (cached) return cached; - - if (visited.has(pluginId)) { - return tempDepsMap.get(pluginId) ?? new Set(); - } - - visiting.add(pluginId); - path.push(pluginId); - - const allDeps = new Set(); - const plugin = CorePluginsRegistry.manifest[pluginId]; - - if (plugin?.dependencies) { - for (const dep of plugin.dependencies) { - allDeps.add(dep); - const transitiveDeps = visit(dep, path); - transitiveDeps.forEach((d) => allDeps.add(d)); - } - } - - path.pop(); - visiting.delete(pluginId); - visited.add(pluginId); - - tempDepsMap.set(pluginId, allDeps); - return allDeps; - } - - for (const pluginId of Object.keys( - CorePluginsRegistry.manifest, - ) as CorePluginId[]) { - if (!visited.has(pluginId)) { - visit(pluginId, []); - } - } - - CorePluginsRegistry.corePluginDependenciesCache = tempDepsMap; - })(); -})(); diff --git a/perplexity/extension/src/__registries__/core-plugins/types.ts b/perplexity/extension/src/__registries__/core-plugins/types.ts deleted file mode 100644 index cc12d55c0..000000000 --- a/perplexity/extension/src/__registries__/core-plugins/types.ts +++ /dev/null @@ -1,15 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface CorePluginsRegistry {} - -export type CorePluginId = keyof CorePluginsRegistry; - -export type CorePluginManifest = { - id: T; - dependencies?: readonly CorePluginId[]; -}; - -export type CorePluginMetaMap = { - [K in CorePluginId]: CorePluginManifest; -}; - -export type CorePluginsEnableStates = Record; diff --git a/perplexity/extension/src/__registries__/core-plugins/utils.ts b/perplexity/extension/src/__registries__/core-plugins/utils.ts deleted file mode 100644 index 9e925dc9a..000000000 --- a/perplexity/extension/src/__registries__/core-plugins/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { - CorePluginId, - CorePluginManifest, -} from "@/__registries__/core-plugins/types"; - -export const defineCorePlugin = < - const T extends CorePluginManifest, ->( - params: T, -) => { - return params; -}; diff --git a/perplexity/extension/src/__registries__/cs-hash-router.ts b/perplexity/extension/src/__registries__/cs-hash-router.ts deleted file mode 100644 index 7a168a7fc..000000000 --- a/perplexity/extension/src/__registries__/cs-hash-router.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { RouteObject } from "react-router-dom"; - -export const hashRouterObservers: RouteObject[] = (() => { - return Object.values( - import.meta.glob("@/plugins/**/*.hash-router.{ts,tsx}", { - eager: true, - }) as Record< - string, - { - default: RouteObject; - } - >, - ).map((module) => { - invariant(module.default != null, "[HashRouter] Invalid context"); - - return module.default; - }); -})(); diff --git a/perplexity/extension/src/__registries__/cs-ui/index.tsx b/perplexity/extension/src/__registries__/cs-ui/index.tsx deleted file mode 100644 index 9c81664ad..000000000 --- a/perplexity/extension/src/__registries__/cs-ui/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import type { UiGroupId } from "@/__registries__/cs-ui/types"; -import { queryBoxToolbarGroupComponentsFactory } from "@/__registries__/cs-ui/utils"; - -export default class CsUiRegistry { - static Components = Object.values( - import.meta.glob("@/plugins/**/*.cs-ui.tsx", { - eager: true, - }) as Record< - string, - { - default: React.FC; - uiGroup?: UiGroupId | UiGroupId[]; - } - >, - ); - - static PluginComponents = (() => - CsUiRegistry.Components.map((module, idx) => { - if (module.uiGroup == null) return ; - - if (typeof module.uiGroup === "string" && module.uiGroup === "global") - return ; - - if (Array.isArray(module.uiGroup) && module.uiGroup.includes("global")) - return ; - - return null; - }).filter((component) => component != null))(); - - static ThreadQueryEditButtonGroupComponents = (() => - CsUiRegistry.Components.map((module, idx) => { - if (module.uiGroup == null) return null; - - if ( - typeof module.uiGroup === "string" && - module.uiGroup === "thread:messageBlocks:queryEditButton" - ) - return ; - - if ( - Array.isArray(module.uiGroup) && - module.uiGroup.includes("thread:messageBlocks:queryEditButton") - ) - return ; - }).filter((component) => component != null))(); - - static ThreadMessageFooterGroupComponents = (() => - CsUiRegistry.Components.map((module, idx) => { - if (module.uiGroup == null) return null; - - if ( - typeof module.uiGroup === "string" && - module.uiGroup === "thread:messageBlocks:footer" - ) - return ; - - if ( - Array.isArray(module.uiGroup) && - module.uiGroup.includes("thread:messageBlocks:footer") - ) - return ; - }).filter((component) => component != null))(); - - static QueryBoxToolbarMainGroupComponents = - queryBoxToolbarGroupComponentsFactory( - "queryBoxes:toolbar:main", - CsUiRegistry.Components, - ); - static QueryBoxToolbarSpaceGroupComponents = - queryBoxToolbarGroupComponentsFactory( - "queryBoxes:toolbar:space", - CsUiRegistry.Components, - ); - static QueryBoxToolbarFollowUpGroupComponents = - queryBoxToolbarGroupComponentsFactory( - "queryBoxes:toolbar:followUp", - CsUiRegistry.Components, - ); - static QueryBoxToolbarCometAssistantGroupComponents = - queryBoxToolbarGroupComponentsFactory( - "queryBoxes:toolbar:cometAssistant", - CsUiRegistry.Components, - ); - - static CommandMenuItemsGroupComponents = (() => - CsUiRegistry.Components.map((module, idx) => { - if (module.uiGroup == null) return null; - - if ( - typeof module.uiGroup === "string" && - module.uiGroup === "commandMenu" - ) - return ; - - if ( - Array.isArray(module.uiGroup) && - module.uiGroup.includes("commandMenu") - ) - return ; - }).filter((component) => component != null))(); - - static SlashCommandMenuPagesGroupComponents = (() => - CsUiRegistry.Components.map((module, idx) => { - if (module.uiGroup == null) return null; - - if ( - typeof module.uiGroup === "string" && - module.uiGroup === "slashCommandMenu:pages" - ) - return ; - - if ( - Array.isArray(module.uiGroup) && - module.uiGroup.includes("slashCommandMenu:pages") - ) - return ; - }).filter((component) => component != null))(); -} - -(function () { - CsUiRegistry.Components.forEach((module) => { - invariant( - module.default != null, - "Plugin Component must have a default export", - ); - - invariant( - typeof module.default === "function", - "Plugin Component must be a fuwwnction", - ); - - invariant( - module.default.displayName, - "Plugin Component must have a displayName", - ); - }); -})(); diff --git a/perplexity/extension/src/__registries__/cs-ui/types.ts b/perplexity/extension/src/__registries__/cs-ui/types.ts deleted file mode 100644 index 0c827d13c..000000000 --- a/perplexity/extension/src/__registries__/cs-ui/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UiGroupsRegistry {} - -export type UiGroupId = keyof UiGroupsRegistry; diff --git a/perplexity/extension/src/__registries__/cs-ui/utils.tsx b/perplexity/extension/src/__registries__/cs-ui/utils.tsx deleted file mode 100644 index dfa08bcf1..000000000 --- a/perplexity/extension/src/__registries__/cs-ui/utils.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type CsUiRegistry from "@/__registries__/cs-ui"; -import type { UiGroupId } from "@/__registries__/cs-ui/types"; - -const queryBoxToolbarGroupSuffixes = ["ll", "lr", "rl", "rr"] as const; - -export function queryBoxToolbarGroupComponentsFactory( - prefix: UiGroupId, - Components: typeof CsUiRegistry.Components, -): Record<(typeof queryBoxToolbarGroupSuffixes)[number], React.ReactNode> { - return Object.fromEntries( - queryBoxToolbarGroupSuffixes.map((suffix) => [ - suffix, - Components.map((module, idx) => { - if (module.uiGroup == null) return null; - - if ( - typeof module.uiGroup === "string" && - module.uiGroup === (`${prefix}:${suffix}` as UiGroupId) - ) - return ; - - if ( - Array.isArray(module.uiGroup) && - module.uiGroup.includes(`${prefix}:${suffix}` as UiGroupId) - ) - return ; - }).filter((component) => component != null), - ]), - ) as unknown as Record< - (typeof queryBoxToolbarGroupSuffixes)[number], - React.ReactNode - >; -} diff --git a/perplexity/extension/src/__registries__/i18n.ts b/perplexity/extension/src/__registries__/i18n.ts deleted file mode 100644 index e0bdf3f13..000000000 --- a/perplexity/extension/src/__registries__/i18n.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const commonLocalesLazyGlob = import.meta.glob("@/_locales/*.*.ts", { - eager: false, -}); - -export const pluginLocalesLazyGlob = import.meta.glob( - "@/plugins/*/_locales/*.*.ts", - { - eager: false, - }, -); - -export const dashboardLocalesLazyGlob = import.meta.glob( - "@/entrypoints/options-page/**/_locales/*.*.ts", - { - eager: false, - }, -); diff --git a/perplexity/extension/src/__registries__/plugin-settings-uis.ts b/perplexity/extension/src/__registries__/plugin-settings-uis.ts deleted file mode 100644 index fcae2ae06..000000000 --- a/perplexity/extension/src/__registries__/plugin-settings-uis.ts +++ /dev/null @@ -1,51 +0,0 @@ -import React from "react"; - -import type { PluginId } from "@/__registries__/plugins/meta.types"; -import { invariant } from "@/utils/misc/utils"; - -export type PluginSettingsUIs = Partial< - Record< - PluginId, - { - component: React.ReactNode; - openInFullScreen?: boolean; - } - > ->; - -export const PluginSettingsUis: PluginSettingsUIs = (() => { - const settingsUis: PluginSettingsUIs = {}; - - const entries = import.meta.glob( - ["@/plugins/**/settings-ui.tsx", "@/plugins/**/settings-ui/index.tsx"], - { - eager: true, - }, - ) as Record< - string, - { - default: React.ComponentType; - pluginId: PluginId; - openInFullScreen?: boolean; - } - >; - - for (const [_, module] of Object.entries(entries)) { - invariant( - module.default != null, - `Plugin settings UI for "${module.pluginId}" is declared but missing default export`, - ); - - invariant( - module.pluginId != null, - `Plugin settings UI for "${module.pluginId}" is declared but missing \`pluginId\` export`, - ); - - settingsUis[module.pluginId] = { - component: React.createElement(module.default), - openInFullScreen: module.openInFullScreen ?? false, - }; - } - - return settingsUis; -})(); diff --git a/perplexity/extension/src/__registries__/plugins/index.test.ts b/perplexity/extension/src/__registries__/plugins/index.test.ts deleted file mode 100644 index 1d29bed48..000000000 --- a/perplexity/extension/src/__registries__/plugins/index.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { PluginManifestsRegistry } from "@/__registries__/plugins/index"; - -describe("PluginManifestsRegistry", () => { - test("should be defined", () => { - expect(PluginManifestsRegistry).toBeDefined(); - }); - - test("should have meta property", () => { - expect(PluginManifestsRegistry.meta).toBeDefined(); - expect(typeof PluginManifestsRegistry.meta).toBe("object"); - }); - - test("should have settingsZodSchema property", () => { - expect(PluginManifestsRegistry.settingsZodSchema).toBeDefined(); - }); - - test("should have settingsFallbackValues property", () => { - expect(PluginManifestsRegistry.settingsFallbackValues).toBeDefined(); - expect(typeof PluginManifestsRegistry.settingsFallbackValues).toBe( - "object", - ); - }); - - test("should have getAllPluginDependencies method", () => { - expect(PluginManifestsRegistry.getAllPluginDependencies).toBeDefined(); - expect(typeof PluginManifestsRegistry.getAllPluginDependencies).toBe( - "function", - ); - }); - - test("should have register method", () => { - expect(PluginManifestsRegistry.register).toBeDefined(); - expect(typeof PluginManifestsRegistry.register).toBe("function"); - }); -}); diff --git a/perplexity/extension/src/__registries__/plugins/index.ts b/perplexity/extension/src/__registries__/plugins/index.ts deleted file mode 100644 index e5f95fc80..000000000 --- a/perplexity/extension/src/__registries__/plugins/index.ts +++ /dev/null @@ -1,249 +0,0 @@ -/** - * ⚠️ IMPORTANT ⚠️ - * - * This file should NOT contain any dynamic imports. - * All imports must be static to ensure proper bundling and initialization. - * Dynamic imports in this file can cause plugin registration issues and - * lead to unpredictable behavior across different extension contexts. - */ - -import type { Transaction } from "dexie"; -import { z } from "zod"; - -import type { - PluginId, - PluginIndexedDbConfig, - PluginMeta, - PluginMetaMap, - PluginsSettingsRegistry, -} from "@/__registries__/plugins/meta.types"; -import type { PluginManifest } from "@/__registries__/plugins/types"; -import { APP_CONFIG } from "@/app.config"; -import { invariant } from "@/utils/misc/utils"; - -export class PluginManifestsRegistry { - static meta: PluginMetaMap = {} as PluginMetaMap; - static settingsZodSchema = z.object({}); - static settingsFallbackValues = {} as PluginsSettingsRegistry; - private static LATEST_INDEXED_DB_VERSION = 8; - static indexedDbVersions: Record< - number, - { - schemas: Record; - upgrades: Array<(tx: Transaction) => Promise | void>; - } - > = {}; - static indexedDbTableValidationSchemas: Record> = - {}; - - static pluginDependenciesCache = new Map>(); - - static getAllPluginDependencies(pluginId: PluginId): Set { - const cached = this.pluginDependenciesCache.get(pluginId); - if (cached) return cached; - - // If cache miss, compute dependencies for this plugin - const allDeps = new Set(); - const visited = new Set(); - - const collectDeps = (id: PluginId) => { - if (visited.has(id)) return; - visited.add(id); - - const plugin = this.meta[id]; - const deps = plugin?.dependencies?.plugins; - - if (deps) { - for (const depId of deps) { - allDeps.add(depId); - collectDeps(depId); - } - } - }; - - collectDeps(pluginId); - this.pluginDependenciesCache.set(pluginId, allDeps); - return allDeps; - } - - static register(params: { - id: T; - manifest: PluginMeta; - settingsSchema: z.ZodType; - settingsFallback: PluginsSettingsRegistry[T] | z.input; - indexedDb?: PluginIndexedDbConfig; - }): void { - (this.meta as Record>)[params.id] = params.manifest; - - this.settingsZodSchema = this.settingsZodSchema.extend({ - [params.id]: params.settingsSchema, - }); - - (this.settingsFallbackValues as Record)[ - params.id - ] = params.settingsFallback as PluginsSettingsRegistry[T]; - - // Accumulate IndexedDB schemas by version, preventing conflicts - if (params.indexedDb) { - if (params.indexedDb.schema) { - this.indexedDbTableValidationSchemas[params.id] = - params.indexedDb.schema; - } - - const sortedVersions = [...params.indexedDb.versions].sort( - (a, b) => a.version - b.version, - ); - - for (const versionConfig of sortedVersions) { - const { version, schema, tableName, upgrade } = versionConfig; - - invariant( - version <= this.LATEST_INDEXED_DB_VERSION, - `Plugin "${params.id}" attempts to declare IndexedDB version ${version}, ` + - `but the latest supported version is ${this.LATEST_INDEXED_DB_VERSION}. ` + - `Please manually increase LATEST_INDEXED_DB_VERSION and review the schema changes.`, - ); - - if (!schema) { - if (!this.indexedDbVersions[version]) { - this.indexedDbVersions[version] = { - schemas: {}, - upgrades: [], - }; - } - - if (upgrade) { - this.indexedDbVersions[version].upgrades.push(upgrade); - } - continue; - } - - if (!this.indexedDbVersions[version]) { - this.indexedDbVersions[version] = { - schemas: {}, - upgrades: [], - }; - } - - const existingSchemas = this.indexedDbVersions[version].schemas; - - let finalTableName = tableName; - if (!finalTableName) { - for (let prevVersion = version - 1; prevVersion >= 1; prevVersion--) { - const prevVersionData = this.indexedDbVersions[prevVersion]; - if (prevVersionData?.schemas) { - const prevPluginVersion = sortedVersions.find( - (v) => v.version === prevVersion, - ); - if (prevPluginVersion) { - if (prevPluginVersion.tableName) { - finalTableName = prevPluginVersion.tableName; - break; - } - if (params.id in prevVersionData.schemas) { - finalTableName = params.id; - break; - } - } - } - } - finalTableName = finalTableName ?? params.id; - } - - invariant( - !(finalTableName in existingSchemas), - `IndexedDB schema collision: Table "${finalTableName}" is already defined for version ${version}. ` + - `Plugin "${params.id}" attempted to redefine it.`, - ); - - existingSchemas[finalTableName] = schema; - - if (upgrade) { - this.indexedDbVersions[version].upgrades.push(upgrade); - } - } - } - } -} - -(function () { - const entries = import.meta.glob("@/plugins/**/index.manifest.ts", { - eager: true, - }) as Record>; - - const sortedEntries = Object.entries(entries) - .map(([path, module]) => { - invariant("default" in module, `Plugin "${path}" has no default export`); - - const params = module.default as PluginManifest< - keyof PluginsSettingsRegistry - >; - - return { path, params }; - }) - .sort((a, b) => a.params.meta.title.localeCompare(b.params.meta.title)); - - for (const { params } of sortedEntries) { - if (!APP_CONFIG.IS_DEV && params.meta.devOnly) continue; - - PluginManifestsRegistry.register({ - id: params.meta.id, - manifest: params.meta, - settingsSchema: params.settingsSchema.schema, - settingsFallback: params.settingsSchema.fallback, - indexedDb: params.indexedDb, - }); - } - - (function detectCycleAndBuildCache() { - const visiting = new Set(); - const visited = new Set(); - const tempDepsMap = new Map>(); - - function visit(pluginId: PluginId, path: PluginId[]): Set { - if (visiting.has(pluginId)) { - const cycleStart = path.indexOf(pluginId); - const cycle = [...path.slice(cycleStart), pluginId].join(" -> "); - throw new Error(`Dependency cycle detected in plugins: ${cycle}`); - } - - const cached = tempDepsMap.get(pluginId); - if (cached) return cached; - - if (visited.has(pluginId)) { - return tempDepsMap.get(pluginId) ?? new Set(); - } - - visiting.add(pluginId); - path.push(pluginId); - - const allDeps = new Set(); - const plugin = PluginManifestsRegistry.meta[pluginId]; - - if (plugin?.dependencies?.plugins) { - for (const dep of plugin.dependencies.plugins) { - allDeps.add(dep); - const transitiveDeps = visit(dep, path); - transitiveDeps.forEach((d) => allDeps.add(d)); - } - } - - path.pop(); - visiting.delete(pluginId); - visited.add(pluginId); - - tempDepsMap.set(pluginId, allDeps); - return allDeps; - } - - for (const pluginId of Object.keys( - PluginManifestsRegistry.meta, - ) as PluginId[]) { - if (!visited.has(pluginId)) { - visit(pluginId, []); - } - } - - PluginManifestsRegistry.pluginDependenciesCache = tempDepsMap; - })(); -})(); diff --git a/perplexity/extension/src/__registries__/plugins/meta.types.ts b/perplexity/extension/src/__registries__/plugins/meta.types.ts deleted file mode 100644 index 8bc0b739f..000000000 --- a/perplexity/extension/src/__registries__/plugins/meta.types.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { Transaction } from "dexie"; -import type Dexie from "dexie"; -import type z from "zod"; - -import type { CorePluginId } from "@/__registries__/core-plugins/types"; -import type { UiGroupId } from "@/__registries__/cs-ui/types"; -import type { - PluginCategoryKey, - PluginTagKeys, -} from "@/data/dashboard/plugin-meta/types"; - -export type PluginMeta = { - devOnly?: boolean; - - id: T; - title: string; - description: string; - - dashboardMeta: { - tags: readonly PluginTagKeys[]; - categories: readonly PluginCategoryKey[]; - uiRouteSegment: string; - }; - - dependencies?: { - corePlugins?: readonly CorePluginId[]; - uiGroups?: readonly UiGroupId[]; - plugins?: readonly PluginId[]; - }; - - extensionPermissions?: { - requiredPermissions?: readonly { - permission: chrome.runtime.ManifestPermissions; - rationale: string; - }[]; - optionalPermissions?: readonly { - permission: chrome.runtime.ManifestPermissions; - rationale: string; - }[]; - }; -}; - -export type PluginMetaMap = { - [K in PluginId]: PluginMeta; -}; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface PluginsSettingsRegistry {} - -/** - * Derived type that maps plugin IDs to their settings schemas. - */ -export type PluginsSettingsSchema = { - [K in keyof PluginsSettingsRegistry]: PluginsSettingsRegistry[K]; -}; - -/** - * Union type of all registered plugin IDs. - */ -export type PluginId = keyof PluginsSettingsRegistry; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface PluginsIndexedDbRegistry {} - -export type PluginsIndexedDbSchema = { - [K in keyof PluginsIndexedDbRegistry]: PluginsIndexedDbRegistry[K]; -}; - -export type PluginTables = { - [K in keyof PluginsIndexedDbRegistry]: Dexie.Table< - PluginsIndexedDbRegistry[K] - >; -}; - -export type PluginIndexedDbVersion = { - version: number; - schema?: string; - tableName?: string; - upgrade?: (tx: Transaction) => Promise | void; -}; - -export type PluginIndexedDbConfig = { - versions: PluginIndexedDbVersion[]; - schema?: z.ZodType; -}; - -// eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface PluginsDbDataRegistry {} - -export type PluginsDbDataSchema = { - [K in keyof PluginsDbDataRegistry]: PluginsDbDataRegistry[K]; -}; diff --git a/perplexity/extension/src/__registries__/plugins/predicates.ts b/perplexity/extension/src/__registries__/plugins/predicates.ts deleted file mode 100644 index 47c7ebebe..000000000 --- a/perplexity/extension/src/__registries__/plugins/predicates.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { PluginManifestsRegistry } from "@/__registries__/plugins"; -import type { PluginId } from "@/__registries__/plugins/meta.types"; - -export function isPluginId(value: string): value is PluginId { - return Object.keys(PluginManifestsRegistry.meta).includes(value); -} - -export function isPrivatePluginId(value: string): value is PluginId { - return ( - isPluginId(value) && - PluginManifestsRegistry.meta[value].dashboardMeta == null - ); -} diff --git a/perplexity/extension/src/__registries__/plugins/types.ts b/perplexity/extension/src/__registries__/plugins/types.ts deleted file mode 100644 index 1973fec30..000000000 --- a/perplexity/extension/src/__registries__/plugins/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ZodType } from "zod"; - -import type { - PluginId, - PluginIndexedDbConfig, - PluginMeta, - PluginsSettingsSchema, -} from "@/__registries__/plugins/meta.types"; - -export type PluginManifest = { - meta: PluginMeta; - settingsSchema: { - schema: ZodType; - fallback: PluginsSettingsSchema[T]; - }; - indexedDb?: PluginIndexedDbConfig; -}; diff --git a/perplexity/extension/src/__registries__/plugins/utils.ts b/perplexity/extension/src/__registries__/plugins/utils.ts deleted file mode 100644 index ff3fd89c3..000000000 --- a/perplexity/extension/src/__registries__/plugins/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { PluginId } from "@/__registries__/plugins/meta.types"; -import type { PluginManifest } from "@/__registries__/plugins/types"; - -export const definePlugin = < - TId extends PluginId, - const TParams extends PluginManifest, ->( - params: TParams & PluginManifest, -): TParams => { - return params; -}; diff --git a/perplexity/extension/src/_locales/common.bn-BD.ts b/perplexity/extension/src/_locales/common.bn-BD.ts deleted file mode 100644 index ffcd8f9d6..000000000 --- a/perplexity/extension/src/_locales/common.bn-BD.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "প্রজেক্টটি চালিয়ে রাখতে একটি দান করুন!", - }, - sponsorDialog: { - title: "Complexity কে অসাধারণ রাখতে সাহায্য করুন!", - description: - "আপনার জন্য Complexity কে একটি শক্তিশালী এবং পরিষ্কার টুল বানাতে অসংখ্য ঘন্টা ব্যয় করা হয়েছে। আপনার সমর্থন সরাসরি চলমান উন্নয়ন, নতুন বৈশিষ্ট্য, এবং সবকিছু সুচারুভাবে চালু রাখতে সাহায্য করে।", - descriptionLine2: - "যদি Complexity আপনার কাজের প্রবাহে মূল্য যোগ করে, তাহলে অনুগ্রহ করে এর ভবিষ্যতে অবদান রাখার কথা বিবেচনা করুন!", - cometAffiliate: { - title: "বিনামূল্যে <0/>!", - description: - "Comet চেষ্টা করুন - Perplexity এর নতুন ব্রাউজার - এবং একটি বিনামূল্যে <0/> সাবস্ক্রিপশন পান এবং এর মধ্যে সরাসরি Complexity এর উন্নয়নে অবদান রাখুন।", - claimButton: "এখনই দাবি করুন", - dismissButton: "খারিজ করুন", - }, - donation: { - title: "💖 ভবিষ্যৎ উন্নয়নে সমর্থন করুন", - }, - sponsorship: { - title: "🌟 স্পনসরশিপে আগ্রহী?", - contactEmail: "ইমেইলের মাধ্যমে যোগাযোগ করুন", - }, - }, - misc: { - words: "শব্দ", - characters: "অক্ষর", - rewrite: "পুনরায় লিখুন", - speakAloud: "জোরে বলুন", - stop: "থামুন", - }, - releaseNotes: { - title: "v{version} এ আপডেট করা হয়েছে", - dontShowAgain: "খারিজ করুন এবং ভবিষ্যৎ আপডেটের জন্য আর দেখাবেন না", - confirmDialog: { - title: "নিশ্চিত করুন", - message: - "আপনি কি নিশ্চিত যে আপনি খারিজ করতে চান এবং ভবিষ্যৎ আপডেটের জন্য আর দেখাতে চান না? আপনি সর্বদা সেটিংস পৃষ্ঠায় এই পপআপটি পুনরায় সক্রিয় করতে পারেন।", - cancel: "বাতিল করুন", - confirm: "আমি বুঝতে পেরেছি", - }, - dismiss: "খারিজ করুন", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.cs-CZ.ts b/perplexity/extension/src/_locales/common.cs-CZ.ts deleted file mode 100644 index fbf59f871..000000000 --- a/perplexity/extension/src/_locales/common.cs-CZ.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Přispějte, abyste udrželi projekt naživu!", - }, - sponsorDialog: { - title: "Pomozte udržet Complexity skvělé!", - description: - "Nespočet hodin bylo věnováno tomu, aby byl Complexity výkonným a propracovaným nástrojem pro vás. Vaše podpora přímo pohání další vývoj, nové funkce a udržuje vše v chodu.", - descriptionLine2: - "Pokud Complexity přináší hodnotu do vašeho pracovního postupu, zvažte prosím příspěvek na jeho budoucnost!", - cometAffiliate: { - title: "Zdarma <0/>!", - description: - "Vyzkoušejte Comet - nový prohlížeč od Perplexity - a získejte bezplatné předplatné <0/> a zároveň přímo přispějte k vývoji Complexity.", - claimButton: "Získat nyní", - dismissButton: "Zavřít", - }, - donation: { - title: "💖 Podpořte budoucí vývoj", - }, - sponsorship: { - title: "🌟 Máte zájem o sponzorství?", - contactEmail: "Kontaktujte e-mailem", - }, - }, - misc: { - words: "slova", - characters: "znaky", - rewrite: "Přepsat", - speakAloud: "Číst nahlas", - stop: "Zastavit", - }, - releaseNotes: { - title: "Aktualizováno na v{version}", - dontShowAgain: "Zavřít a již nezobrazovat pro budoucí aktualizace", - confirmDialog: { - title: "Potvrdit", - message: - "Opravdu chcete zavřít a již nezobrazovat pro budoucí aktualizace? Toto okno můžete kdykoli znovu povolit na stránce nastavení.", - cancel: "Zrušit", - confirm: "Rozumím", - }, - dismiss: "Zavřít", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.de-DE.ts b/perplexity/extension/src/_locales/common.de-DE.ts deleted file mode 100644 index 83a8b2984..000000000 --- a/perplexity/extension/src/_locales/common.de-DE.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Spenden Sie, um das Projekt am Leben zu erhalten!", - }, - sponsorDialog: { - title: "Complexity Braucht Ihre Hilfe!", - description: - "Unzählige Stunden wurden investiert, um Complexity zu einem leistungsstarken und ausgefeilten Werkzeug für Sie zu machen. Ihre Unterstützung treibt direkt die kontinuierliche Entwicklung, neue Funktionen und hält alles reibungslos am Laufen.", - descriptionLine2: - "Wenn Complexity Ihren Arbeitsablauf bereichert, erwägen Sie bitte, zu seiner Zukunft beizutragen!", - cometAffiliate: { - title: "Kostenlos <0/>!", - description: - "Probieren Sie Comet aus - einen neuen Browser von Perplexity - und erhalten Sie ein kostenloses <0/>-Abonnement und tragen gleichzeitig direkt zur Entwicklung von Complexity bei.", - claimButton: "Jetzt beanspruchen", - dismissButton: "Schließen", - }, - donation: { - title: "💖 Spenden Sie", - }, - sponsorship: { - title: "🌟 Haben Sie ein Angebot für langfristiges Sponsoring?", - contactEmail: "Kontakt per E-Mail", - }, - }, - misc: { - words: "Wörter", - characters: "Zeichen", - rewrite: "Umschreiben", - speakAloud: "Laut vorlesen", - stop: "Stoppen", - }, - releaseNotes: { - title: "Auf v{version} aktualisiert", - dontShowAgain: "Schließen und für zukünftige Updates nicht mehr anzeigen", - confirmDialog: { - title: "Bestätigen", - message: - "Sind Sie sicher, dass Sie schließen und für zukünftige Updates nicht mehr anzeigen möchten? Sie können dieses Popup jederzeit auf der Einstellungsseite wieder aktivieren.", - cancel: "Abbrechen", - confirm: "Ich verstehe", - }, - dismiss: "Schließen", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.el-GR.ts b/perplexity/extension/src/_locales/common.el-GR.ts deleted file mode 100644 index 3b01eb715..000000000 --- a/perplexity/extension/src/_locales/common.el-GR.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Κάντε μια δωρεά για να διατηρήσετε το έργο ζωντανό!", - }, - sponsorDialog: { - title: "Το Complexity Χρειάζεται τη Βοήθειά σας!", - description: - "Αμέτρητες ώρες έχουν αφιερωθεί για να γίνει το Complexity ένα ισχυρό και προσεγμένο εργαλείο για εσάς. Η υποστήριξή σας τροφοδοτεί άμεσα τη συνεχή ανάπτυξη, νέες λειτουργίες και διατηρεί τα πάντα να λειτουργούν ομαλά.", - descriptionLine2: - "Αν το Complexity προσθέτει αξία στη ροή εργασίας σας, παρακαλώ σκεφτείτε να συνεισφέρετε στο μέλλον του!", - cometAffiliate: { - title: "Δωρεάν <0/>!", - description: - "Δοκιμάστε το Comet - ένα νέο πρόγραμμα περιήγησης από το Perplexity - και λάβετε ένα δωρεάν <0/> abonnement ενώ συγχρόνως συνεισφέρετε άμεσα στην ανάπτυξη του Complexity.", - claimButton: "Διεκδικήστε το τώρα", - dismissButton: "Απόρριψη", - }, - donation: { - title: "💖 Κάντε μια δωρεά", - }, - sponsorship: { - title: "🌟 Έχετε μια προσφορά για μακροχρόνια χορηγία;", - contactEmail: "Επικοινωνία μέσω Email", - }, - }, - misc: { - words: "λέξεις", - characters: "χαρακτήρες", - rewrite: "Επανεγγραφή", - speakAloud: "Εκφώνηση", - stop: "Διακοπή", - }, - releaseNotes: { - title: "Ενημερώθηκε στην έκδοση v{version}", - dontShowAgain: - "Απόρριψη και να μην εμφανιστεί ξανά για μελλοντικές ενημερώσεις", - confirmDialog: { - title: "Επιβεβαίωση", - message: - "Είστε βέβαιοι ότι θέλετε να απορρίψετε και να μην εμφανίζετε ξανά για μελλοντικές ενημερώσεις; Μπορείτε πάντα να επανενεργοποιήσετε αυτό το αναδυόμενο παράθυρο στη σελίδα ρυθμίσεων.", - cancel: "Ακύρωση", - confirm: "Καταλαβαίνω", - }, - dismiss: "Απόρριψη", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.en-US.ts b/perplexity/extension/src/_locales/common.en-US.ts deleted file mode 100644 index 66ac4469f..000000000 --- a/perplexity/extension/src/_locales/common.en-US.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { LanguageMessages } from "@complexity/i18n"; - -export default { - sidebar: { - supporterMessage: "Make a donation to keep the project alive!", - }, - sponsorDialog: { - title: "Help keep Complexity awesome!", - description: - "Countless hours have been poured into making Complexity a powerful and polished tool for you. Your support directly fuels ongoing development, new features, and keeping everything running smoothly.", - descriptionLine2: - "If Complexity adds value to your workflow, please consider contributing to its future!", - cometAffiliate: { - title: "Free <0/>!", - description: - "Try out Comet - a new browser from Perplexity - and get a free <0/> subscription and meanwhile directly contribute to the development of Complexity.", - claimButton: "Claim it now", - dismissButton: "Dismiss", - }, - donation: { - title: "💖 Support future development", - }, - sponsorship: { - title: "🌟 Interested in Sponsorship?", - contactEmail: "Contact via Email", - }, - }, - misc: { - words: "words", - characters: "characters", - rewrite: "Rewrite", - speakAloud: "Speak aloud", - stop: "Stop", - }, - releaseNotes: { - title: "Updated to v{version}", - dontShowAgain: "Dismiss and don't show again for future updates", - confirmDialog: { - title: "Confirm", - message: - "Are you sure you want to dismiss and not show again for future updates? You can always re-enable this popup in the settings page.", - cancel: "Cancel", - confirm: "I understand", - }, - dismiss: "Dismiss", - }, -} as const satisfies LanguageMessages; diff --git a/perplexity/extension/src/_locales/common.es-ES.ts b/perplexity/extension/src/_locales/common.es-ES.ts deleted file mode 100644 index 8b136a01b..000000000 --- a/perplexity/extension/src/_locales/common.es-ES.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "¡Haz una donación para mantener el proyecto vivo!", - }, - sponsorDialog: { - title: "¡Ayuda a mantener Complexity increíble!", - description: - "Innumerables horas han sido dedicadas a hacer de Complexity una herramienta poderosa y pulida para ti. Tu apoyo alimenta directamente el desarrollo continuo, nuevas funciones y el buen funcionamiento de todo.", - descriptionLine2: - "Si Complexity aporta valor a tu flujo de trabajo, ¡considera contribuir a su futuro!", - cometAffiliate: { - title: "¡Gratis <0/>!", - description: - "Prueba Comet - el nuevo navegador de Perplexity - y obtén una suscripción <0/> gratuita y contribuye directamente al desarrollo de Complexity.", - claimButton: "Reclamarlo ahora", - dismissButton: "Descartar", - }, - donation: { - title: "💖 Apoya el desarrollo futuro", - }, - sponsorship: { - title: "🌟 ¿Interesado en patrocinio?", - contactEmail: "Contactar por correo electrónico", - }, - }, - misc: { - words: "palabras", - characters: "caracteres", - rewrite: "Reescribir", - speakAloud: "Leer en voz alta", - stop: "Detener", - }, - releaseNotes: { - title: "Actualizado a v{version}", - dontShowAgain: - "Descartar y no mostrar de nuevo para futuras actualizaciones", - confirmDialog: { - title: "Confirmar", - message: - "¿Estás seguro de que deseas descartar y no mostrar de nuevo para futuras actualizaciones? Siempre puedes volver a habilitar esta ventana emergente en la página de configuración.", - cancel: "Cancelar", - confirm: "Entiendo", - }, - dismiss: "Descartar", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.fr-FR.ts b/perplexity/extension/src/_locales/common.fr-FR.ts deleted file mode 100644 index 54c2669bb..000000000 --- a/perplexity/extension/src/_locales/common.fr-FR.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Faites un don pour soutenir le projet !", - }, - sponsorDialog: { - title: "Aidez Complexity à rester génial !", - description: - "D'innombrables heures ont été consacrées à faire de Complexity un outil puissant et soigné pour vous. Votre soutien alimente directement le développement continu, de nouvelles fonctionnalités et le bon fonctionnement de tout.", - descriptionLine2: - "Si Complexity ajoute de la valeur à votre flux de travail, merci d'envisager de contribuer à son avenir !", - cometAffiliate: { - title: "Gratuit <0/>!", - description: - "Essayez Comet - le nouveau navigateur de Perplexity - et obtenez un abonnement <0/> gratuit et contribuez directement au développement de Complexity.", - claimButton: "Réclamez-le maintenant", - dismissButton: "Ignorer", - }, - donation: { - title: "💖 Soutenir le développement futur", - }, - sponsorship: { - title: "🌟 Intéressé par un sponsoring ?", - contactEmail: "Contact par Email", - }, - }, - misc: { - words: "mots", - characters: "caractères", - rewrite: "Réécrire", - speakAloud: "Lire à voix haute", - stop: "Arrêter", - }, - releaseNotes: { - title: "Mis à jour vers v{version}", - dontShowAgain: "Ignorer et ne plus afficher pour les futures mises à jour", - confirmDialog: { - title: "Confirmer", - message: - "Êtes-vous sûr de vouloir ignorer et ne plus afficher pour les futures mises à jour ? Vous pouvez toujours réactiver cette popup dans la page des paramètres.", - cancel: "Annuler", - confirm: "Je comprends", - }, - dismiss: "Ignorer", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.hi-IN.ts b/perplexity/extension/src/_locales/common.hi-IN.ts deleted file mode 100644 index ff035c3c7..000000000 --- a/perplexity/extension/src/_locales/common.hi-IN.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "परियोजना को जीवित रखने के लिए दान करें!", - }, - sponsorDialog: { - title: "Complexity को शानदार बनाए रखने में मदद करें!", - description: - "आपके लिए Complexity को एक शक्तिशाली और परिष्कृत उपकरण बनाने में अनगिनत घंटे लगाए गए हैं। आपका समर्थन सीधे ongoing विकास, नई सुविधाओं और सब कुछ सुचारू रूप से चलाने में मदद करता है।", - descriptionLine2: - "यदि Complexity आपके workflow में मूल्य जोड़ता है, तो कृपया इसके भविष्य में योगदान देने पर विचार करें!", - cometAffiliate: { - title: "मुफ्त <0/>!", - description: - "Comet आजमाएं - Perplexity का नया ब्राउज़र - और एक मुफ्त <0/> सदस्यता प्राप्त करें और इस बीच Complexity के विकास में सीधे योगदान करें।", - claimButton: "अभी दावा करें", - dismissButton: "खारिज करें", - }, - donation: { - title: "💖 भविष्य के विकास का समर्थन करें", - }, - sponsorship: { - title: "🌟 प्रायोजन में रुचि है?", - contactEmail: "ईमेल के माध्यम से संपर्क करें", - }, - }, - misc: { - words: "शब्द", - characters: "अक्षर", - rewrite: "पुनर्लेखन करें", - speakAloud: "जोर से पढ़ें", - stop: "रोकें", - }, - releaseNotes: { - title: "v{version} में अपडेट किया गया", - dontShowAgain: "खारिज करें और भविष्य के अपडेट के लिए फिर से न दिखाएं", - confirmDialog: { - title: "पुष्टि करें", - message: - "क्या आप वाकई खारिज करना चाहते हैं और भविष्य के अपडेट के लिए फिर से नहीं दिखाना चाहते? आप हमेशा सेटिंग्स पेज में इस पॉपअप को फिर से सक्षम कर सकते हैं।", - cancel: "रद्द करें", - confirm: "मैं समझता हूँ", - }, - dismiss: "खारिज करें", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.hr-HR.ts b/perplexity/extension/src/_locales/common.hr-HR.ts deleted file mode 100644 index a4158e0cc..000000000 --- a/perplexity/extension/src/_locales/common.hr-HR.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: - "Napravite donaciju kako biste održali projekt na životu!", - }, - sponsorDialog: { - title: "Pomozite Complexityju da ostane sjajan!", - description: - "Bezbroj sati je uloženo kako bi se Complexity učinio moćnim i dotjeranim alatom za vas. Vaša podrška izravno potiče daljnji razvoj, nove značajke i održava sve u pogonu.", - descriptionLine2: - "Ako Complexity dodaje vrijednost vašem tijeku rada, razmislite o doprinosu njegovoj budućnosti!", - cometAffiliate: { - title: "Besplatno <0/>!", - description: - "Isprobajte Comet - novi preglednik od Perplexityja - i dobijte besplatnu <0/> pretplatu i istovremeno izravno doprinijeti razvoju Complexityja.", - claimButton: "Zatražite sada", - dismissButton: "Odbaci", - }, - donation: { - title: "💖 Podržite budući razvoj", - }, - sponsorship: { - title: "🌟 Zainteresirani za sponzorstvo?", - contactEmail: "Kontakt putem e-pošte", - }, - }, - misc: { - words: "riječi", - characters: "znakovi", - rewrite: "Prepiši", - speakAloud: "Čitaj naglas", - stop: "Zaustavi", - }, - releaseNotes: { - title: "Ažurirano na v{version}", - dontShowAgain: "Odbaci i ne prikazuj ponovno za buduća ažuriranja", - confirmDialog: { - title: "Potvrdi", - message: - "Jeste li sigurni da želite odbaciti i ne prikazivati ponovno za buduća ažuriranja? Ovaj skočni prozor možete uvijek ponovno omogućiti na stranici postavki.", - cancel: "Odustani", - confirm: "Razumijem", - }, - dismiss: "Odbaci", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.hu-HU.ts b/perplexity/extension/src/_locales/common.hu-HU.ts deleted file mode 100644 index 386326bec..000000000 --- a/perplexity/extension/src/_locales/common.hu-HU.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Adj adományt, hogy életben maradjon a projekt!", - }, - sponsorDialog: { - title: "Segíts megőrizni a Complexity nagyszerűségét!", - description: - "Számtalan óra került befektetésre, hogy a Complexity egy erős és kifinomult eszköz legyen számodra. A támogatásod közvetlenül segíti a folyamatos fejlesztést, az új funkciókat és mindent gördülékenyen tart.", - descriptionLine2: - "Ha a Complexity értéket ad a munkafolyamatodhoz, kérjük, fontold meg a jövőjének támogatását!", - cometAffiliate: { - title: "Ingyenes <0/>!", - description: - "Próbáld ki a Comet-et - a Perplexity új böngészőjét - és szerezz ingyenes <0/> előfizetést, miközben közvetlenül hozzájárulsz a Complexity fejlesztéséhez.", - claimButton: "Igényelj most", - dismissButton: "Elutasítás", - }, - donation: { - title: "💖 Támogasd a jövőbeli fejlesztést", - }, - sponsorship: { - title: "🌟 Érdekel a szponzoráció?", - contactEmail: "Kapcsolat Emailben", - }, - }, - misc: { - words: "szavak", - characters: "karakterek", - rewrite: "Átírás", - speakAloud: "Felolvasás", - stop: "Leállítás", - }, - releaseNotes: { - title: "Frissítve v{version}-ra", - dontShowAgain: - "Elutasítás és ne jelenjen meg újra a jövőbeli frissítéseknél", - confirmDialog: { - title: "Megerősítés", - message: - "Biztos vagy benne, hogy elutasítod és nem jelenik meg újra a jövőbeli frissítéseknél? Ezt a felugró ablakot bármikor újra engedélyezheted a beállítások oldalon.", - cancel: "Mégse", - confirm: "Értem", - }, - dismiss: "Elutasítás", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.id-ID.ts b/perplexity/extension/src/_locales/common.id-ID.ts deleted file mode 100644 index 351cbcd58..000000000 --- a/perplexity/extension/src/_locales/common.id-ID.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Lakukan donasi untuk menjaga keberlangsungan proyek!", - }, - sponsorDialog: { - title: "Bantu Complexity tetap luar biasa!", - description: - "Waktu yang tak terhitung telah dicurahkan untuk menjadikan Complexity alat yang kuat dan canggih untuk Anda. Dukungan Anda secara langsung mendorong pengembangan berkelanjutan, fitur baru, dan menjaga semuanya berjalan lancar.", - descriptionLine2: - "Jika Complexity menambah nilai pada alur kerja Anda, mohon pertimbangkan untuk berkontribusi pada masa depannya!", - cometAffiliate: { - title: "Gratis <0/>!", - description: - "Coba Comet - browser baru dari Perplexity - dan dapatkan langganan <0/> gratis dan sementara itu berkontribusi langsung pada pengembangan Complexity.", - claimButton: "Klaim sekarang", - dismissButton: "Tutup", - }, - donation: { - title: "💖 Dukung pengembangan masa depan", - }, - sponsorship: { - title: "🌟 Tertarik dengan Sponsorship?", - contactEmail: "Hubungi via Email", - }, - }, - misc: { - words: "kata", - characters: "karakter", - rewrite: "Tulis ulang", - speakAloud: "Ucapkan dengan keras", - stop: "Berhenti", - }, - releaseNotes: { - title: "Diperbarui ke v{version}", - dontShowAgain: "Tutup dan jangan tampilkan lagi untuk pembaruan mendatang", - confirmDialog: { - title: "Konfirmasi", - message: - "Apakah Anda yakin ingin menutup dan tidak menampilkan lagi untuk pembaruan mendatang? Anda selalu dapat mengaktifkan kembali popup ini di halaman pengaturan.", - cancel: "Batal", - confirm: "Saya mengerti", - }, - dismiss: "Tutup", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.it-IT.ts b/perplexity/extension/src/_locales/common.it-IT.ts deleted file mode 100644 index 5a209089d..000000000 --- a/perplexity/extension/src/_locales/common.it-IT.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Fai una donazione per mantenere vivo il progetto!", - }, - sponsorDialog: { - title: "Aiuta a mantenere Complexity fantastico!", - description: - "Innumerevoli ore sono state dedicate per rendere Complexity uno strumento potente e raffinato per te. Il tuo supporto alimenta direttamente lo sviluppo continuo, nuove funzionalità e il mantenimento di tutto in modo efficiente.", - descriptionLine2: - "Se Complexity aggiunge valore al tuo flusso di lavoro, considera di contribuire al suo futuro!", - cometAffiliate: { - title: "Gratuito <0/>!", - description: - "Prova Comet - il nuovo browser di Perplexity - e ottieni un abbonamento <0/> gratuito e contribuisci direttamente allo sviluppo di Complexity.", - claimButton: "Richiedi ora", - dismissButton: "Ignora", - }, - donation: { - title: "💖 Supporta lo sviluppo futuro", - }, - sponsorship: { - title: "🌟 Interessato alla sponsorizzazione?", - contactEmail: "Contatta via Email", - }, - }, - misc: { - words: "parole", - characters: "caratteri", - rewrite: "Riscrivi", - speakAloud: "Leggi ad alta voce", - stop: "Ferma", - }, - releaseNotes: { - title: "Aggiornato alla v{version}", - dontShowAgain: "Ignora e non mostrare più per i futuri aggiornamenti", - confirmDialog: { - title: "Conferma", - message: - "Sei sicuro di voler ignorare e non mostrare più per i futuri aggiornamenti? Puoi sempre riattivare questo popup nella pagina delle impostazioni.", - cancel: "Annulla", - confirm: "Ho capito", - }, - dismiss: "Ignora", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.ja-JP.ts b/perplexity/extension/src/_locales/common.ja-JP.ts deleted file mode 100644 index 444cfcaaf..000000000 --- a/perplexity/extension/src/_locales/common.ja-JP.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "プロジェクトを存続させるために寄付をご検討ください!", - }, - sponsorDialog: { - title: "Complexityを素晴らしく保つためにご協力を!", - description: - "数え切れないほどの時間が、Complexityを強力で洗練されたツールにするために費やされてきました。あなたのサポートは、継続的な開発、新機能、そしてすべてを円滑に運営するための原動力となります。", - descriptionLine2: - "Complexityがあなたのワークフローに価値をもたらしているなら、今後の発展のためにご支援をお願いします!", - cometAffiliate: { - title: "無料 <0/>!", - description: - "Perplexityの新しいブラウザであるCometを試して、無料の<0/>サブスクリプションを取得し、同時にComplexityの開発に直接貢献してください。", - claimButton: "今すぐ受け取る", - dismissButton: "閉じる", - }, - donation: { - title: "💖 今後の開発を支援する", - }, - sponsorship: { - title: "🌟 スポンサーシップに興味がありますか?", - contactEmail: "メールで連絡", - }, - }, - misc: { - words: "単語", - characters: "文字", - rewrite: "書き換え", - speakAloud: "音読する", - stop: "停止", - }, - releaseNotes: { - title: "v{version} に更新されました", - dontShowAgain: "今後のアップデートでは表示しない", - confirmDialog: { - title: "確認", - message: - "今後のアップデートで表示しないようにしますか?設定ページでいつでもこのポップアップを再有効化できます。", - cancel: "キャンセル", - confirm: "理解しました", - }, - dismiss: "閉じる", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.ko-KR.ts b/perplexity/extension/src/_locales/common.ko-KR.ts deleted file mode 100644 index 0745b720a..000000000 --- a/perplexity/extension/src/_locales/common.ko-KR.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "프로젝트를 계속 유지하려면 기부해 주세요!", - }, - sponsorDialog: { - title: "Complexity를 멋지게 유지하는 데 도움을 주세요!", - description: - "수많은 시간이 Complexity를 강력하고 세련된 도구로 만드는 데 쏟아졌습니다. 여러분의 지원은 지속적인 개발, 새로운 기능, 그리고 모든 것이 원활하게 운영되는 데 직접적인 힘이 됩니다.", - descriptionLine2: - "Complexity가 여러분의 워크플로우에 가치를 더한다면, 미래를 위해 기여해 주세요!", - cometAffiliate: { - title: "무료 <0/>!", - description: - "Perplexity의 새로운 브라우저인 Comet을 시도하고 무료 <0/> 구독을 얻으면서 동시에 Complexity 개발에 직접 기여하세요.", - claimButton: "지금 받기", - dismissButton: "닫기", - }, - donation: { - title: "💖 미래 개발 지원하기", - }, - sponsorship: { - title: "🌟 스폰서십에 관심이 있으신가요?", - contactEmail: "이메일로 문의하기", - }, - }, - misc: { - words: "단어", - characters: "문자", - rewrite: "다시 쓰기", - speakAloud: "소리 내어 읽기", - stop: "중지", - }, - releaseNotes: { - title: "v{version}으로 업데이트됨", - dontShowAgain: "닫기 및 향후 업데이트에 대해 다시 표시하지 않기", - confirmDialog: { - title: "확인", - message: - "정말로 닫고 향후 업데이트에 대해 다시 표시하지 않으시겠습니까? 설정 페이지에서 언제든지 이 팝업을 다시 활성화할 수 있습니다.", - cancel: "취소", - confirm: "이해했습니다", - }, - dismiss: "닫기", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.nl-NL.ts b/perplexity/extension/src/_locales/common.nl-NL.ts deleted file mode 100644 index 912707c75..000000000 --- a/perplexity/extension/src/_locales/common.nl-NL.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Doe een donatie om het project in leven te houden!", - }, - sponsorDialog: { - title: "Complexity Heeft Jouw Hulp Nodig!", - description: - "Talloze uren zijn besteed aan het maken van Complexity tot een krachtig en verfijnd hulpmiddel voor jou. Jouw steun voedt direct de voortdurende ontwikkeling, nieuwe functies en houdt alles soepel draaiende.", - descriptionLine2: - "Als Complexity waarde toevoegt aan je workflow, overweeg dan om bij te dragen aan de toekomst ervan!", - cometAffiliate: { - title: "Gratis <0/>!", - description: - "Probeer Comet - een nieuwe browser van Perplexity - en krijg een gratis <0/>-abonnement en draag ondertussen direct bij aan de ontwikkeling van Complexity.", - claimButton: "Claim het nu", - dismissButton: "Sluiten", - }, - donation: { - title: "💖 Doe een donatie", - }, - sponsorship: { - title: "🌟 Heb je een voorstel voor langdurige sponsoring?", - contactEmail: "Contact via e-mail", - }, - }, - misc: { - words: "woorden", - characters: "tekens", - rewrite: "Herschrijven", - speakAloud: "Hardop voorlezen", - stop: "Stoppen", - }, - releaseNotes: { - title: "Bijgewerkt naar v{version}", - dontShowAgain: "Sluiten en niet meer tonen voor toekomstige updates", - confirmDialog: { - title: "Bevestigen", - message: - "Weet je zeker dat je wilt sluiten en niet meer wilt tonen voor toekomstige updates? Je kunt deze pop-up altijd opnieuw inschakelen op de instellingenpagina.", - cancel: "Annuleren", - confirm: "Ik begrijp het", - }, - dismiss: "Sluiten", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.pl-PL.ts b/perplexity/extension/src/_locales/common.pl-PL.ts deleted file mode 100644 index 05f759998..000000000 --- a/perplexity/extension/src/_locales/common.pl-PL.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Przekaż darowiznę, aby utrzymać projekt przy życiu!", - }, - sponsorDialog: { - title: "Pomóż utrzymać Complexity na wysokim poziomie!", - description: - "Niezliczone godziny zostały poświęcone, aby uczynić Complexity potężnym i dopracowanym narzędziem dla Ciebie. Twoje wsparcie bezpośrednio napędza dalszy rozwój, nowe funkcje i utrzymanie wszystkiego w ruchu.", - descriptionLine2: - "Jeśli Complexity wnosi wartość do Twojego workflow, rozważ wsparcie jego przyszłości!", - cometAffiliate: { - title: "Bezpłatnie <0/>!", - description: - "Spróbuj Comet - nową przeglądarkę od Perplexity - i zdobądź darmową subskrypcję <0/> i jednocześnie bezpośrednio przyczynij się do rozwoju Complexity.", - claimButton: "Odbierz teraz", - dismissButton: "Odrzuć", - }, - donation: { - title: "💖 Wspieraj przyszły rozwój", - }, - sponsorship: { - title: "🌟 Zainteresowany sponsoringiem?", - contactEmail: "Kontakt przez e-mail", - }, - }, - misc: { - words: "słowa", - characters: "znaki", - rewrite: "Przepisz", - speakAloud: "Czytaj na głos", - stop: "Zatrzymaj", - }, - releaseNotes: { - title: "Zaktualizowano do v{version}", - dontShowAgain: - "Odrzuć i nie pokazuj ponownie przy przyszłych aktualizacjach", - confirmDialog: { - title: "Potwierdź", - message: - "Czy na pewno chcesz odrzucić i nie pokazywać ponownie przy przyszłych aktualizacjach? Możesz ponownie włączyć to okno w ustawieniach.", - cancel: "Anuluj", - confirm: "Rozumiem", - }, - dismiss: "Odrzuć", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.pt-BR.ts b/perplexity/extension/src/_locales/common.pt-BR.ts deleted file mode 100644 index 123045e03..000000000 --- a/perplexity/extension/src/_locales/common.pt-BR.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Faça uma doação para manter o projeto vivo!", - }, - sponsorDialog: { - title: "Ajude a manter o Complexity incrível!", - description: - "Inúmeras horas foram dedicadas para tornar o Complexity uma ferramenta poderosa e refinada para você. Seu apoio alimenta diretamente o desenvolvimento contínuo, novos recursos e mantém tudo funcionando perfeitamente.", - descriptionLine2: - "Se o Complexity agrega valor ao seu fluxo de trabalho, por favor, considere contribuir para o seu futuro!", - cometAffiliate: { - title: "Grátis <0/>!", - description: - "Experimente Comet - um novo navegador da Perplexity - e ganhe uma assinatura <0/> gratuita e, enquanto isso, contribua diretamente para o desenvolvimento do Complexity.", - claimButton: "Reivindique agora", - dismissButton: "Dispensar", - }, - donation: { - title: "💖 Apoie o desenvolvimento futuro", - }, - sponsorship: { - title: "🌟 Interessado em patrocínio?", - contactEmail: "Contato por Email", - }, - }, - misc: { - words: "palavras", - characters: "caracteres", - rewrite: "Reescrever", - speakAloud: "Ler em voz alta", - stop: "Parar", - }, - releaseNotes: { - title: "Atualizado para v{version}", - dontShowAgain: - "Dispensar e não mostrar novamente para futuras atualizações", - confirmDialog: { - title: "Confirmar", - message: - "Tem certeza de que deseja dispensar e não mostrar novamente para futuras atualizações? Você sempre pode reativar este popup na página de configurações.", - cancel: "Cancelar", - confirm: "Entendi", - }, - dismiss: "Dispensar", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.pt-PT.ts b/perplexity/extension/src/_locales/common.pt-PT.ts deleted file mode 100644 index f273ad8b6..000000000 --- a/perplexity/extension/src/_locales/common.pt-PT.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Faça uma doação para manter o projeto vivo!", - }, - sponsorDialog: { - title: "Ajude a manter o Complexity incrível!", - description: - "Inúmeras horas foram dedicadas para tornar o Complexity uma ferramenta poderosa e polida para si. O seu apoio alimenta diretamente o desenvolvimento contínuo, novas funcionalidades e mantém tudo a funcionar sem problemas.", - descriptionLine2: - "Se o Complexity acrescenta valor ao seu fluxo de trabalho, por favor considere contribuir para o seu futuro!", - cometAffiliate: { - title: "Grátis <0/>!", - description: - "Experimente Comet - um novo navegador da Perplexity - e obtenha uma subscrição <0/> gratuita e, enquanto isso, contribua diretamente para o desenvolvimento do Complexity.", - claimButton: "Reivindique agora", - dismissButton: "Dispensar", - }, - donation: { - title: "💖 Apoie o desenvolvimento futuro", - }, - sponsorship: { - title: "🌟 Interessado em patrocínio?", - contactEmail: "Contactar por Email", - }, - }, - misc: { - words: "palavras", - characters: "caracteres", - rewrite: "Reescrever", - speakAloud: "Ler em voz alta", - stop: "Parar", - }, - releaseNotes: { - title: "Atualizado para v{version}", - dontShowAgain: - "Dispensar e não mostrar novamente para futuras atualizações", - confirmDialog: { - title: "Confirmar", - message: - "Tem a certeza de que pretende dispensar e não mostrar novamente para futuras atualizações? Pode sempre reativar este popup na página de definições.", - cancel: "Cancelar", - confirm: "Compreendo", - }, - dismiss: "Dispensar", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.ro-RO.ts b/perplexity/extension/src/_locales/common.ro-RO.ts deleted file mode 100644 index 214dd1c11..000000000 --- a/perplexity/extension/src/_locales/common.ro-RO.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Faceți o donație pentru a menține proiectul în viață!", - }, - sponsorDialog: { - title: "Ajutați la menținerea Complexity grozav!", - description: - "Nenumărate ore au fost investite pentru a face Complexity un instrument puternic și bine finisat pentru dvs. Sprijinul dvs. alimentează direct dezvoltarea continuă, noi funcții și menținerea tuturor în stare de funcționare.", - descriptionLine2: - "Dacă Complexity adaugă valoare fluxului dvs. de lucru, vă rugăm să luați în considerare contribuția la viitorul său!", - cometAffiliate: { - title: "Gratuit <0/>!", - description: - "Încercați Comet - noul browser de la Perplexity - și obțineți un abonament gratuit <0/> și în același timp contribuiți direct la dezvoltarea Complexity.", - claimButton: "Revendicați acum", - dismissButton: "Respingeți", - }, - donation: { - title: "💖 Susțineți dezvoltarea viitoare", - }, - sponsorship: { - title: "🌟 Interesat de sponsorizare?", - contactEmail: "Contactați prin Email", - }, - }, - misc: { - words: "cuvinte", - characters: "caractere", - rewrite: "Rescrie", - speakAloud: "Citește cu voce tare", - stop: "Oprește", - }, - releaseNotes: { - title: "Actualizat la v{version}", - dontShowAgain: "Respingeți și nu mai afișați pentru actualizări viitoare", - confirmDialog: { - title: "Confirmare", - message: - "Sunteți sigur că doriți să respingeți și să nu mai afișați pentru actualizări viitoare? Puteți reactiva oricând această fereastră pop-up din pagina de setări.", - cancel: "Anulați", - confirm: "Am înțeles", - }, - dismiss: "Respingeți", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.ru-RU.ts b/perplexity/extension/src/_locales/common.ru-RU.ts deleted file mode 100644 index 5f547e1d5..000000000 --- a/perplexity/extension/src/_locales/common.ru-RU.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Сделайте пожертвование, чтобы поддержать проект!", - }, - sponsorDialog: { - title: "Помогите сохранить Complexity замечательным!", - description: - "Бесчисленное количество часов было вложено, чтобы сделать Complexity мощным и отполированным инструментом для вас. Ваша поддержка напрямую способствует постоянному развитию, новым функциям и поддержанию всего в рабочем состоянии.", - descriptionLine2: - "Если Complexity добавляет ценность в ваш рабочий процесс, пожалуйста, рассмотрите возможность внести свой вклад в его будущее!", - cometAffiliate: { - title: "Бесплатно <0/>!", - description: - "Попробуйте Comet - новый браузер от Perplexity - и получите бесплатную подписку на <0/> и одновременно напрямую внесите вклад в развитие Complexity.", - claimButton: "Получить сейчас", - dismissButton: "Закрыть", - }, - donation: { - title: "💖 Поддержать будущее развитие", - }, - sponsorship: { - title: "🌟 Заинтересованы в спонсорстве?", - contactEmail: "Связаться по электронной почте", - }, - }, - misc: { - words: "слов", - characters: "символов", - rewrite: "Переписать", - speakAloud: "Произнести вслух", - stop: "Остановить", - }, - releaseNotes: { - title: "Обновлено до версии {version}", - dontShowAgain: "Закрыть и не показывать снова для будущих обновлений", - confirmDialog: { - title: "Подтверждение", - message: - "Вы уверены, что хотите закрыть и не показывать снова для будущих обновлений? Вы всегда можете повторно включить это всплывающее окно на странице настроек.", - cancel: "Отмена", - confirm: "Я понимаю", - }, - dismiss: "Закрыть", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.sk-SK.ts b/perplexity/extension/src/_locales/common.sk-SK.ts deleted file mode 100644 index 540c8de2b..000000000 --- a/perplexity/extension/src/_locales/common.sk-SK.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "Prispejte, aby projekt zostal nažive!", - }, - sponsorDialog: { - title: "Pomôžte udržať Complexity skvelý!", - description: - "Nespočetné množstvo hodín bolo venovaných tomu, aby bol Complexity výkonným a vyladeným nástrojom pre vás. Vaša podpora priamo poháňa ďalší vývoj, nové funkcie a udržiava všetko v chode.", - descriptionLine2: - "Ak Complexity pridáva hodnotu vášmu pracovnému postupu, zvážte, prosím, príspevok na jeho budúcnosť!", - cometAffiliate: { - title: "Bezplatne <0/>!", - description: - "Vyskúšajte Comet - nový prehliadač od Perplexity - a získajte bezplatné predplatné <0/> a zároveň priamo prispejte k vývoju Complexity.", - claimButton: "Získať teraz", - dismissButton: "Zavrieť", - }, - donation: { - title: "💖 Podporte budúci vývoj", - }, - sponsorship: { - title: "🌟 Máte záujem o sponzorstvo?", - contactEmail: "Kontaktujte e-mailom", - }, - }, - misc: { - words: "slová", - characters: "znaky", - rewrite: "Prepísať", - speakAloud: "Čítať nahlas", - stop: "Zastaviť", - }, - releaseNotes: { - title: "Aktualizované na v{version}", - dontShowAgain: "Zavrieť a už nezobrazovať pre budúce aktualizácie", - confirmDialog: { - title: "Potvrdiť", - message: - "Naozaj chcete zavrieť a už nezobrazovať pre budúce aktualizácie? Toto okno môžete kedykoľvek znova povoliť na stránke nastavení.", - cancel: "Zrušiť", - confirm: "Rozumiem", - }, - dismiss: "Zavrieť", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.sr-Cyrl-ME.ts b/perplexity/extension/src/_locales/common.sr-Cyrl-ME.ts deleted file mode 100644 index 6bbbbdf86..000000000 --- a/perplexity/extension/src/_locales/common.sr-Cyrl-ME.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: - "Napravite donaciju kako biste održali projekat u životu!", - }, - sponsorDialog: { - title: "Pomozite da Complexity ostane sjajan!", - description: - "Bezbroj sati je uloženo kako bi se Complexity učinio moćnim i doteranim alatom za vas. Vaša podrška direktno podstiče dalji razvoj, nove funkcije i održava sve da radi glatko.", - descriptionLine2: - "Ako Complexity dodaje vrednost vašem toku rada, razmislite o doprinosu njegovoj budućnosti!", - cometAffiliate: { - title: "Бесплатно <0/>!", - description: - "Испробајте Comet - нов веб-прегледач од Перплексити - и добијте бесплатну <0/> претплату и истовремено директно допринесите развоју Complexity-а.", - claimButton: "Преузмите сада", - dismissButton: "Odbaci", - }, - donation: { - title: "💖 Podržite budući razvoj", - }, - sponsorship: { - title: "🌟 Zainteresovani za sponzorstvo?", - contactEmail: "Kontakt putem e-pošte", - }, - }, - misc: { - words: "reči", - characters: "karakteri", - rewrite: "Prepravi", - speakAloud: "Pročitaj naglas", - stop: "Zaustavi", - }, - releaseNotes: { - title: "Ažurirano na v{version}", - dontShowAgain: "Odbaci i ne prikazuj ponovo za buduća ažuriranja", - confirmDialog: { - title: "Potvrdi", - message: - "Da li ste sigurni da želite da odbacite i ne prikazujete ponovo za buduća ažuriranja? Ovaj iskačući prozor možete uvek ponovo omogućiti na stranici podešavanja.", - cancel: "Otkaži", - confirm: "Razumem", - }, - dismiss: "Odbaci", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.zh-CN.ts b/perplexity/extension/src/_locales/common.zh-CN.ts deleted file mode 100644 index efca87ea9..000000000 --- a/perplexity/extension/src/_locales/common.zh-CN.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "捐款以保持项目的活力!", - }, - sponsorDialog: { - title: "帮助保持Complexity的卓越!", - description: - "无数小时已被投入,使Complexity成为一个强大且精致的工具。您的支持直接推动持续开发、新功能,并保持一切顺利运行。", - descriptionLine2: - "如果Complexity为您的工作流程带来价值,请考虑为它的未来贡献一份力量!", - cometAffiliate: { - title: "免费 <0/>!", - description: - "尝试 Comet - Perplexity 推出的全新浏览器 - 获得免费 <0/> 订阅,同时直接为 Complexity 的开发做出贡献。", - claimButton: "立即领取", - dismissButton: "关闭", - }, - donation: { - title: "💖 支持未来开发", - }, - sponsorship: { - title: "🌟 有兴趣赞助吗?", - contactEmail: "通过电子邮件联系", - }, - }, - misc: { - words: "单词", - characters: "字符", - rewrite: "重写", - speakAloud: "朗读", - stop: "停止", - }, - releaseNotes: { - title: "已更新至v{version}", - dontShowAgain: "不再显示此更新", - confirmDialog: { - title: "确认", - message: "确定不再显示此更新?您可以随时在设置页面重新启用此弹窗。", - cancel: "取消", - confirm: "我知道了", - }, - dismiss: "关闭", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/common.zh-TW.ts b/perplexity/extension/src/_locales/common.zh-TW.ts deleted file mode 100644 index 79ce8f192..000000000 --- a/perplexity/extension/src/_locales/common.zh-TW.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Translations } from "@/_locales/index"; - -export default { - sidebar: { - supporterMessage: "捐款以維持專案運作!", - }, - sponsorDialog: { - title: "協助讓 Complexity 持續優秀!", - description: - "無數小時已被投入,使 Complexity 成為一個強大且精緻的工具。您的支持直接推動持續開發、新功能,並讓一切順利運作。", - descriptionLine2: - "如果 Complexity 為您的工作流程帶來價值,請考慮為它的未來貢獻一份心力!", - cometAffiliate: { - title: "免費 <0/>!", - description: - "試試 Comet - Perplexity 推出的全新瀏覽器 - 取得免費 <0/> 訂閱,同時直接為 Complexity 的開發做出貢獻。", - claimButton: "立即領取", - dismissButton: "關閉", - }, - donation: { - title: "💖 支持未來開發", - }, - sponsorship: { - title: "🌟 有興趣贊助嗎?", - contactEmail: "透過 Email 聯絡", - }, - }, - misc: { - words: "單字", - characters: "字元", - rewrite: "重寫", - speakAloud: "朗讀", - stop: "停止", - }, - releaseNotes: { - title: "已更新至 v{version}", - dontShowAgain: "不再顯示此更新", - confirmDialog: { - title: "確認", - message: "確定不再顯示此更新?您可以隨時在設定頁重新啟用此彈窗。", - cancel: "取消", - confirm: "我知道了", - }, - dismiss: "關閉", - }, -} as const satisfies Translations; diff --git a/perplexity/extension/src/_locales/index.ts b/perplexity/extension/src/_locales/index.ts deleted file mode 100644 index 30a4ae09b..000000000 --- a/perplexity/extension/src/_locales/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { TranslationShape } from "@complexity/i18n"; - -import type translations from "@/_locales/common.en-US"; - -export const namespace = "common"; - -export type Translations = TranslationShape; - -declare module "@complexity/i18n" { - interface TranslationsRegistry { - [namespace]: typeof translations; - } -} diff --git a/perplexity/extension/src/assets/cs.css b/perplexity/extension/src/assets/cs.css index d4ef1553b..081c49fb1 100644 --- a/perplexity/extension/src/assets/cs.css +++ b/perplexity/extension/src/assets/cs.css @@ -5,8 +5,8 @@ @reference "./index.css"; body { - --font-sans: var(--font-fk-grotesk); - --font-mono: var(--font-berkeley-mono); + --font-sans: var(--pplx-sans); + --font-mono: var(--pplx-mono); @apply x:font-sans; } diff --git a/perplexity/extension/src/assets/extension.css b/perplexity/extension/src/assets/extension.css index f9e132bfd..3f2a9a2ae 100644 --- a/perplexity/extension/src/assets/extension.css +++ b/perplexity/extension/src/assets/extension.css @@ -18,11 +18,12 @@ --font-mono: "JetBrains Mono", monospace; font-variant-ligatures: none; + font-size: 16px; } @layer base { * { - @apply x:border-border/50 x:font-sans x:text-base; + @apply x:border-border/50 x:font-sans x:text-sm; } body { diff --git a/perplexity/extension/src/assets/typography.css b/perplexity/extension/src/assets/typography.css index 159a85ac8..a600dca0e 100644 --- a/perplexity/extension/src/assets/typography.css +++ b/perplexity/extension/src/assets/typography.css @@ -6,3 +6,7 @@ body .x\:prose, @apply x:relative x:inline-block x:rounded x:bg-muted x:px-[0.3rem] x:py-[0.2rem] x:font-mono x:text-sm x:font-semibold; } } + +mark { + @apply x:bg-primary/10 x:text-primary; +} diff --git a/perplexity/extension/src/assets/vars.css b/perplexity/extension/src/assets/vars.css index a534311df..9af0ba3d2 100644 --- a/perplexity/extension/src/assets/vars.css +++ b/perplexity/extension/src/assets/vars.css @@ -1,69 +1,67 @@ :root { - --primary: oklch(55.27% 0.086 208.61); - --primary-foreground: oklch(0.9902 0.004 106.47); + --primary: oklch(26.42% 0.013 93.9); + --primary-foreground: oklch(97.98% 0.005 78.3); - --background: oklch(98.89% 0.0044 106.48); - --foreground: oklch(0.3039 0.04 213.68); + --background: oklch(0.9798 0.0045 78.3); + --foreground: oklch(0.2642 0.013 93.9); --foreground-subtle: oklch(68.98% 0.027 109.55 / 0.16); - --card: oklch(98.89% 0.0044 106.48); - --card-foreground: oklch(0.3039 0.04 213.68); + --card: oklch(0.9798 0.0045 78.3); + --card-foreground: oklch(0.2642 0.013 93.9); - --popover: oklch(98.89% 0.0044 106.48); - --popover-foreground: oklch(0.3039 0.04 213.68); + --popover: oklch(0.9798 0.0045 78.3); + --popover-foreground: oklch(0.2642 0.013 93.9); - --secondary: oklch(96.06% 0.0069 106.53); - --secondary-foreground: oklch(0.3039 0.04 213.68); + --secondary: oklch(0.9563 0.0062 75.41); + --secondary-foreground: oklch(26.42% 0.013 93.9); - --muted: oklch(96.06% 0.0069 106.53); - --muted-foreground: oklch(0.3039 0.04 213.68 / 0.75); + --muted: oklch(22.82% 0.047 83.09 / 0.07); + --muted-foreground: oklch(26.42% 0.013 93.9 / 0.6); --success: oklch(72.05% 0.192 149.49); --success-foreground: oklch(0.9296 0.007 106.53); - --caution: oklch(51.83% 0.168 21.78); - --caution-foreground: oklch(0.9296 0.007 106.53); + --caution: oklch(0.4918 0.143 16.18); + --caution-foreground: oklch(100% 0.00011 271.152); --backdrop: oklch(0.85 0 0); --border: oklch(88.3% 0.012 106.646); --input: oklch(88.3% 0.012 106.646); - --ring: oklch(55.27% 0.086 208.61); + --ring: oklch(26.42% 0.013 93.9); --radius: 0.5rem; - - --dark: oklch(21.67% 0.002 197.04); } [data-color-scheme="dark"] { - --primary: oklch(71.56% 0.1183 209.17); - --primary-foreground: var(--dark); + --primary: oklch(87.35% 0.002 67.8); + --primary-foreground: oklch(21.67% 0.002 197.04); - --background: oklch(21.67% 0.0016 197.04); + --background: oklch(20.09% 0.003 67.68); --foreground: oklch(0.9296 0.007 106.53); --foreground-subtle: oklch(49.33% 0.019 171.99 / 0.2); - --card: oklch(21.67% 0.0016 197.04); + --card: oklch(20.09% 0.003 67.68); --card-foreground: oklch(0.9296 0.007 106.53); - --popover: oklch(21.67% 0.0016 197.04); + --popover: oklch(20.09% 0.003 67.68); --popover-foreground: oklch(0.9296 0.007 106.53); - --secondary: oklch(25.05% 0.0029 196.97); + --secondary: oklch(0.2568 0.0024 67.72); --secondary-foreground: oklch(0.9296 0.007 106.53); --muted: oklch(37.08% 0.0027 228.85); - --muted-foreground: oklch(92.96% 0.00658 106.779 / 0.55); + --muted-foreground: oklch(87.35% 0.002 67.8 / 0.6); --success: oklch(87.12% 0.137 154.59); --success-foreground: oklch(0.9296 0.007 106.53); - --caution: oklch(53.47% 0.151 25.99); - --caution-foreground: oklch(0.9296 0.007 106.53); + --caution: oklch(0.7201 0.178 11.8); + --caution-foreground: oklch(100% 0.00011 271.152); --backdrop: oklch(0.15 0 0); --border: oklch(37.08% 0.0027 228.85); --input: oklch(37.08% 0.0027 228.85); - --ring: oklch(71.56% 0.1183 209.17); + --ring: oklch(87.35% 0.002 67.8); } diff --git a/perplexity/extension/src/auto-imports-config.ts b/perplexity/extension/src/auto-imports-config.ts index 154b9f011..8f7fac2d2 100644 --- a/perplexity/extension/src/auto-imports-config.ts +++ b/perplexity/extension/src/auto-imports-config.ts @@ -6,7 +6,14 @@ const unimportConfig: Partial = { "react", { from: "react", - imports: ["createContext", "lazy", "memo", "use", "useDeferredValue"], + imports: [ + "createContext", + "lazy", + "memo", + "use", + "useDeferredValue", + "useEffectEvent", + ], }, { from: "@complexity/i18n", @@ -40,6 +47,10 @@ const unimportConfig: Partial = { as: "ms", from: "ms", }, + { + name: "tryCatch", + from: "@/utils/wrappers/try-catch.ts", + }, ], }; diff --git a/perplexity/extension/src/auto-imports.d.ts b/perplexity/extension/src/auto-imports.d.ts index 1ef029e60..03bfbd278 100644 --- a/perplexity/extension/src/auto-imports.d.ts +++ b/perplexity/extension/src/auto-imports.d.ts @@ -10,6 +10,7 @@ declare global { useContext, useDeferredValue, useEffect, + useEffectEvent, useMemo, useReducer, useRef, @@ -28,4 +29,5 @@ declare global { const { isMainWorldContext, sleep }: typeof import("@/utils/misc/utils.ts"); const { default: $ }: typeof import("jquery"); const ms: typeof import("ms"); + const tryCatch: typeof import("@/utils/wrappers/try-catch.ts").tryCatch; } diff --git a/perplexity/extension/src/components/AsyncButton.tsx b/perplexity/extension/src/components/AsyncButton.tsx index f9211d2a4..39c8082c2 100644 --- a/perplexity/extension/src/components/AsyncButton.tsx +++ b/perplexity/extension/src/components/AsyncButton.tsx @@ -2,6 +2,7 @@ import type { ReactNode } from "react"; import type { ButtonProps } from "@/components/ui/button"; import { Button } from "@/components/ui/button"; +import { toast } from "@/components/ui/use-toast"; type AsyncButtonProps = Omit & { onClick: () => Promise; @@ -17,17 +18,26 @@ export default function AsyncButton({ }: AsyncButtonProps) { const [isLoading, setIsLoading] = useState(false); - const handleClick = async () => { - setIsLoading(true); - try { - await onClick(); - } finally { - setIsLoading(false); - } - }; - return ( - ); diff --git a/perplexity/extension/src/components/CodeHighlighter.tsx b/perplexity/extension/src/components/CodeHighlighter.tsx index 50987c1df..51424fe6c 100644 --- a/perplexity/extension/src/components/CodeHighlighter.tsx +++ b/perplexity/extension/src/components/CodeHighlighter.tsx @@ -1,52 +1,100 @@ -import type { ComponentProps, RefObject } from "react"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import lightStyle from "react-syntax-highlighter/dist/esm/styles/prism/vs"; -import darkStyle from "react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus"; +import type { ElementType, ReactNode, RefObject } from "react"; +import type { BundledLanguage } from "shiki"; + +import { useShikiHighlighter } from "@/hooks/useShikiHighlighter"; const INTERPRETED_LANGUAGES: Record = { - html: "markup", + html: "html", react: "jsx", markmap: "markdown", "c++": "cpp", js: "javascript", ts: "typescript", - toml: "ini", + toml: "toml", +}; + +const SHIKI_THEMES = { + dark: "dark-plus", + light: "light-plus", +} as const; + +type CodeHighlighterProps = { + children: string; + language?: string; + colorScheme: "dark" | "light"; + codeRef?: RefObject; + showLineNumbers?: boolean; + PreTag?: ElementType<{ children: ReactNode }>; }; -const CodeHighlighter = memo(function CodeHighlighter({ +export default function CodeHighlighter({ children, language, - codeRef, colorScheme, - ...props -}: ComponentProps & { - colorScheme: "dark" | "light"; - codeRef?: RefObject; -}) { + codeRef, + showLineNumbers, + PreTag = "pre", +}: CodeHighlighterProps) { const interpretedLanguage = language ? (INTERPRETED_LANGUAGES[language] ?? language) : "text"; - const targetLanguage = SyntaxHighlighter.supportedLanguages.includes( - interpretedLanguage, - ) - ? interpretedLanguage - : "text"; + const { highlighter, isReady, resolvedLanguage } = + useShikiHighlighter(interpretedLanguage); + + if (!isReady || !highlighter) { + return ( + + + {children} + + + ); + } + + const tokens = highlighter.codeToTokens(children, { + lang: resolvedLanguage as BundledLanguage, + theme: SHIKI_THEMES[colorScheme], + }); + + const fgColor = tokens.fg; + const lastLine = tokens.tokens[tokens.tokens.length - 1]; + const renderedLines = + children.endsWith("\n") && + lastLine != null && + lastLine.every((token) => token.content === "") + ? tokens.tokens.slice(0, -1) + : tokens.tokens; return ( - - {children} - + + + {renderedLines.map((line, lineIndex) => ( + + {line.map((token, tokenIndex) => ( + + {token.content} + + ))} + {lineIndex < renderedLines.length - 1 && "\n"} + + ))} + + ); -}); - -export default CodeHighlighter; +} diff --git a/perplexity/extension/src/components/CometAffiliateCard.tsx b/perplexity/extension/src/components/CometAffiliateCard.tsx deleted file mode 100644 index 896b7df5e..000000000 --- a/perplexity/extension/src/components/CometAffiliateCard.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { CometCard } from "@/components/CometCard"; -import PplxComet from "@/components/icons/PplxComet"; -import PplxPro from "@/components/icons/PplxPro"; - -import TablerLink from "~icons/tabler/link"; - -export default function CometAffiliateCard() { - return ( - - ); -} diff --git a/perplexity/extension/src/components/DevTools.tsx b/perplexity/extension/src/components/DevTools.tsx new file mode 100644 index 000000000..071974ab6 --- /dev/null +++ b/perplexity/extension/src/components/DevTools.tsx @@ -0,0 +1,17 @@ +import { TanStackDevtools } from "@tanstack/react-devtools"; +import { formDevtoolsPlugin } from "@tanstack/react-form-devtools"; +import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; + +export default function DevTools() { + return ( + , + }, + formDevtoolsPlugin(), + ]} + /> + ); +} diff --git a/perplexity/extension/src/components/ExtensionContextInvalidationWatchdog.tsx b/perplexity/extension/src/components/ExtensionContextInvalidationWatchdog.tsx index 37e1beee1..b2876efe7 100644 --- a/perplexity/extension/src/components/ExtensionContextInvalidationWatchdog.tsx +++ b/perplexity/extension/src/components/ExtensionContextInvalidationWatchdog.tsx @@ -15,7 +15,7 @@ export function ExtensionContextInvalidationWatchdog() { const { data: isValidContext, refetch } = useQuery({ queryKey: ["extensionContextInvalidationWatchdog"], queryFn: () => { - return chrome.runtime.id != null; + return (chrome.runtime.id as string | null) != null; }, refetchInterval: ms("10s"), refetchOnReconnect: "always", @@ -56,7 +56,7 @@ export function WarningDialog() { - +
); } diff --git a/perplexity/extension/src/components/PostUpdateReleaseNotesDialog.tsx b/perplexity/extension/src/components/PostUpdateReleaseNotesDialog.tsx deleted file mode 100644 index 03d369786..000000000 --- a/perplexity/extension/src/components/PostUpdateReleaseNotesDialog.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; - -import { APP_CONFIG } from "@/app.config"; -import ChangelogRenderer from "@/components/changelog/ChangelogRenderer"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { cplxApiQueries } from "@/services/externals/cplx-api/query-keys"; -import { ExtensionSettingsService } from "@/services/infra/extension-api-wrappers/extension-settings"; - -export function PostUpdateReleaseNotesDialog() { - const [open, setOpen] = useState(true); - - const { - data: changelog, - isLoading, - isError, - } = useQuery( - cplxApiQueries.changelog.detail({ - version: APP_CONFIG.VERSION, - }), - ); - - useEffect(() => { - void ExtensionSettingsService.set((draft) => { - draft.isPostUpdateReleaseNotesPopupDismissed = true; - }); - }, []); - - if (isLoading || isError || !changelog) return null; - - return ( - setOpen(open)}> - - - - {t("common.releaseNotes.title", { version: APP_CONFIG.VERSION })} - - - - - { - void ExtensionSettingsService.set((draft) => { - draft.showPostUpdateReleaseNotesPopup = false; - draft.isPostUpdateReleaseNotesPopupDismissed = true; - }); - setOpen(false); - }} - > - - - - - - - - - ); -} - -function DontShowAgainForFutureUpdatesConfirmDialog({ - children, - onConfirm, -}: { - children: React.ReactNode; - onConfirm: () => void; -}) { - return ( - - {children} - - - - {t("common.releaseNotes.confirmDialog.title")} - - - -
- {t("common.releaseNotes.confirmDialog.message")} -
- - - - - - - - - -
-
- ); -} diff --git a/perplexity/extension/src/components/SponsorChannels.tsx b/perplexity/extension/src/components/SponsorChannels.tsx new file mode 100644 index 000000000..bd31ad277 --- /dev/null +++ b/perplexity/extension/src/components/SponsorChannels.tsx @@ -0,0 +1,57 @@ +import { Button } from "@/components/ui/button"; + +import Fa7BrandsPaypal from "~icons/fa7-brands/paypal"; +import SimpleIconsKofi from "~icons/simple-icons/kofi"; +import TablerMail from "~icons/tabler/mail"; + +export default function SponsorChannels() { + return ( + <> +
+
+ {t("common.sponsorDialog.donation.title")} +
+ +
+
+
+ {t("common.sponsorDialog.sponsorship.title")} +
+ +
+ + ); +} diff --git a/perplexity/extension/src/components/SponsorDialogWrapper.tsx b/perplexity/extension/src/components/SponsorDialogWrapper.tsx deleted file mode 100644 index d77a9434c..000000000 --- a/perplexity/extension/src/components/SponsorDialogWrapper.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import PplxPro from "@/components/icons/PplxPro"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { useIsMobileStore } from "@/hooks/is-mobile-store"; - -import Fa7BrandsPaypal from "~icons/fa7-brands/paypal"; -import SimpleIconsKofi from "~icons/simple-icons/kofi"; -import TablerLink from "~icons/tabler/link"; -import TablerMail from "~icons/tabler/mail"; - -export default function SponsorDialogWrapper({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - {children} - -
- - - - {t("common.sponsorDialog.title")} - - -
-
{t("common.sponsorDialog.description")}
-
{t("common.sponsorDialog.descriptionLine2")}
-
-
-
- -
-
- , - ]} - /> -
- -
- -
-
- {t("common.sponsorDialog.donation.title")} -
- -
- - - -
- ); -} diff --git a/perplexity/extension/src/components/Toaster.tsx b/perplexity/extension/src/components/Toaster.tsx index 0050844fc..c171bac42 100644 --- a/perplexity/extension/src/components/Toaster.tsx +++ b/perplexity/extension/src/components/Toaster.tsx @@ -9,7 +9,6 @@ import { ToastViewport, } from "@/components/ui/toast"; import { useToast } from "@/components/ui/use-toast"; -import { isReactNode } from "@/types/utils.types"; export function Toaster({ viewportProps, @@ -34,9 +33,11 @@ export function Toaster({ className={cn("x:w-max x:font-sans", className)} {...props} > -
- {isReactNode(title) && {title}} - {isReactNode(description) && ( +
+ {title != null && title !== "" && ( + {title} + )} + {description != null && description !== "" && ( {description} )}
diff --git a/perplexity/extension/src/components/Tooltip.tsx b/perplexity/extension/src/components/Tooltip.tsx index 7b98425c1..004ef5031 100644 --- a/perplexity/extension/src/components/Tooltip.tsx +++ b/perplexity/extension/src/components/Tooltip.tsx @@ -16,6 +16,7 @@ export type TooltipProps = { defaultOpen?: boolean; portal?: boolean; ref?: RefObject; + onOpenChange?: (open: boolean) => void; }; export default function Tooltip({ @@ -27,6 +28,7 @@ export default function Tooltip({ defaultOpen, portal = true, ref, + onOpenChange, }: TooltipProps) { return ( onOpenChange?.(details.open)} > diff --git a/perplexity/extension/src/components/form/field.tsx b/perplexity/extension/src/components/form/field.tsx new file mode 100644 index 000000000..465a8e78a --- /dev/null +++ b/perplexity/extension/src/components/form/field.tsx @@ -0,0 +1,248 @@ +"use client"; + +import { cva, type VariantProps } from "class-variance-authority"; +import { useMemo } from "react"; + +import { Label } from "@/components/ui/label"; +import { Separator } from "@/components/ui/separator"; +import { cn } from "@/utils/wrappers/cn"; + +function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { + return ( +
[data-slot=checkbox-group]]:gap-3 x:has-[>[data-slot=radio-group]]:gap-3", + className, + )} + {...props} + /> + ); +} + +function FieldLegend({ + className, + variant = "legend", + ...props +}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { + return ( + + ); +} + +function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
[data-slot=field-group]]:gap-4 x:@container/field-group x:flex x:w-full x:flex-col x:gap-7 x:data-[slot=checkbox-group]:gap-3", + className, + )} + {...props} + /> + ); +} + +const fieldVariants = cva( + "x:group/field x:flex x:w-full x:gap-3 x:data-[invalid=true]:text-caution", + { + variants: { + orientation: { + vertical: ["[&>*]:w-full [&>.sr-only]:w-auto x:flex-col"], + horizontal: [ + "x:flex-row x:items-center", + "[&>[data-slot=field-label]]:flex-auto", + "x:has-[>[data-slot=field-content]]:items-start x:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + responsive: [ + "[&>*]:w-full [&>.sr-only]:w-auto x:flex-col x:@md/field-group:flex-row x:@md/field-group:items-center x:@md/field-group:*:w-auto", + "x:@md/field-group:*:data-[slot=field-label]:flex-auto", + "x:@md/field-group:x:has-[>[data-slot=field-content]]:items-start x:@md/field-group:x:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", + ], + }, + }, + defaultVariants: { + orientation: "vertical", + }, + }, +); + +function Field({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function FieldContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function FieldLabel({ + className, + ...props +}: React.ComponentProps) { + return ( +

+ Click here to continue +