├── AI-Snipping-Tool-Intro.mp4 ├── CODE_OF_CONDUCT.md ├── Contribution.md ├── LICENSE ├── New UI designs ├── AI Snipping Tool.png └── AI Snipping Tool2.png ├── README.md ├── _locales └── en │ └── messages.json ├── engine ├── helper.js ├── index.html ├── index.js ├── preview-controls.js ├── tesseract │ ├── ReadMe │ ├── tesseract-core-simd-lstm.wasm │ ├── tesseract-core-simd-lstm.wasm.js │ ├── tesseract.min.js │ └── worker.min.js └── worker-overwrites.js ├── icons └── logo.png ├── inject ├── custom-elements.min.js ├── elements.js ├── font-awesome.min.css ├── inject.css ├── inject.js └── response.js ├── manifest.json └── worker.js /AI-Snipping-Tool-Intro.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgoap/AI-Snipping-Tool/80a68b53c27df58f37b73673a1e50ab262c74f7e/AI-Snipping-Tool-Intro.mp4 -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement. 63 | 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Contribution.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines gitgoap 2 | 3 | This documentation contains a set of guidelines to help you during the contribution process. 4 | 5 | ## Submitting Contributions👩‍ 6 | 7 | Below you will find the process and workflow used to review and merge your changes. 8 | 9 | ### Step 0 : Find an issue 10 | 11 | - Take a look at the Existing Issues or create your **own** Issues! 12 | - Wait for the Issue to be assigned to you after which you can start working on it. 13 | - Note: Every change in this project should/must have an associated issue. 14 | 15 | 16 | 17 | ### Step 1 : Fork the Project 18 | 19 | - Fork this Repository. This will create a Local Copy of this Repository on your Github Profile. 20 | Keep a reference to the original project in `upstream` remote. 21 | 22 | ```bash 23 | git clone https://github.com// 24 | cd 25 | git remote add upstream https://github.com// 26 | ``` 27 | 28 | 29 | 30 | - If you have already forked the project, update your copy before working. 31 | 32 | ```bash 33 | git remote update 34 | git checkout 35 | git rebase upstream/ 36 | ``` 37 | 38 | 39 | 40 | ### Step 2 : Branch 41 | 42 | Create a new branch. Use its name to identify the issue you are addressing. 43 | 44 | ```bash 45 | # It will create a new branch with the name Branch_Name and switch to that branch 46 | git checkout -b branch_name 47 | ``` 48 | 49 | ### Step 3 : Work on the issue assigned 50 | 51 | - Work on the issue(s) assigned to you. 52 | - Add all the files/folders needed. 53 | - After you've made changes or made your contribution to the project add changes to the branch you've just created by: 54 | 55 | ```bash 56 | # To add all new files to branch Branch_Name 57 | git add . 58 | 59 | # To add only a few files to Branch_Name 60 | git add 61 | ``` 62 | 63 | ### Step 4 : Commit 64 | 65 | - To commit give a descriptive message for the convenience of reviewer by: 66 | 67 | ```bash 68 | # This message gets associated with all files you have changed 69 | git commit -m "message" 70 | ``` 71 | 72 | - **NOTE**: A PR should have only one commit. Multiple commits should be squashed. 73 | 74 | ### Step 5 : Work Remotely 75 | 76 | - Now you are ready to your work to the remote repository. 77 | - When your work is ready and complies with the project conventions, upload your changes to your fork: 78 | 79 | ```bash 80 | # To push your work to your remote repository 81 | git push -u origin Branch_Name 82 | ``` 83 | 84 | 85 | ### Step 6 : Pull Request 86 | 87 | https://github.com/gitgoap/AI-Snipping-Tool_IIT-D_Hackathon 88 | 89 | - Create a Pull Request which will be reviewed and suggestions would be added to improve it. 90 | - Add Screenshots to help us know what this enhancement/implementation is all about. 91 | 92 | 93 | 94 | ## Alternatively, using GitHub Desktop: 95 | 1. Open GitHub Desktop and log in to your GitHub account. 96 | 97 | 2. Make sure you are on the "Current Repository" view. If not, go to "File" and select "Add Local Repository" to add your repository. 98 | 99 | 3. In the "Current Repository" view, ensure you are on the branch that you want to submit a pull request for. If you're not on the correct branch, use the "Branch" menu to switch to the correct branch. 100 | 101 | 4. Once you're on the correct branch, make your changes and commit them to the branch. You can do this by clicking the "+" button in the upper-left corner of the GitHub Desktop window, making your changes, and then entering a commit message. 102 | 103 | 5. After you've made your changes and committed them, click the "Push origin" button in the top-right corner of the GitHub Desktop window. This will push your changes to the remote repository on GitHub. 104 | 105 | 6. Now, go to the GitHub website, navigate to your fork of the repository, and you should see a button to "Compare & pull request" between your fork and the original repository, click on it. 106 | 107 | 7. On the pull request page, you can review your changes and add any additional information, such as a title and a description, that you want to include with your pull request. 108 | 109 | 8. Once you're satisfied with your pull request, click the "Create pull request" button to submit it. 110 | 111 | **Note:** In order to create a pull request, you must have a fork of the original repository in your GitHub account and you must have made the changes in that forked repository. 112 | 113 | ## Your Pull Request has been submitted and will be reviewed by the maintainer and merged. 114 | 115 | 116 | 117 | ### Need more help?🤔 118 | 119 | You can refer to the following articles on the basics of Git and GitHub and also contact the Project Mentors, 120 | in case you are stuck: 121 | 122 | - [Watch this video to get started, if you have no clue about open source](https://youtu.be/SYtPC9tHYyQ) 123 | - [Forking a Repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) 124 | - [Cloning a Repo](https://help.github.com/en/desktop/contributing-to-projects/creating-a-pull-request) 125 | - [How to create a Pull Request](https://opensource.com/article/19/7/create-pull-request-github) 126 | - [Getting started with Git and GitHub](https://towardsdatascience.com/getting-started-with-git-and-github-6fcd0f2d4ac6) 127 | - [Learn GitHub from Scratch](https://lab.github.com/githubtraining/introduction-to-github) 128 | 129 | 130 | Hope you will learn something new while contributing to this project!! 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /New UI designs/AI Snipping Tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgoap/AI-Snipping-Tool/80a68b53c27df58f37b73673a1e50ab262c74f7e/New UI designs/AI Snipping Tool.png -------------------------------------------------------------------------------- /New UI designs/AI Snipping Tool2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgoap/AI-Snipping-Tool/80a68b53c27df58f37b73673a1e50ab262c74f7e/New UI designs/AI Snipping Tool2.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | ## ``` AI Snipping Tool ``` 4 | 5 | A **Chrome extension** that takes custom screenshots, **extracts text**, and allows users to **ask questions** from the extracted content. 6 | 7 | ### Features of AI Snipping Tool 8 | 9 | 1. **Custom Screenshots**: Capture screenshots from Chrome. 10 | 2. **Text Extraction**: Extract text from screenshots using Tesseract OCR. 11 | 3. **Ask Questions**: Use Google Gemini API to ask questions based on extracted text. 12 | 4. **User Preferences**: Configure language and accuracy settings for OCR. 13 | 5. **Interactive OCR Results**: View and interact with extracted text by copying it, saving it, or saving the screenshot. 14 | 15 |
16 | 17 | ## Chrome Extension popup 18 | 19 | `Step 1:` Take the screenshot 20 |
21 | 22 | ![Screenshot (40)](https://github.com/user-attachments/assets/7171eaeb-efb4-45cd-92e1-822be10b2c26) 23 | 24 |


25 | `Step 2:` Copy the *obtained text* or proceed further 26 |
27 | 28 | ![Screenshot (38)](https://github.com/user-attachments/assets/50ce95f2-0f29-4594-8326-f98ac3c07a8b) 29 | 30 |

31 | `Step 3:` Fill *Google Gemini* API key and ask related *question* 32 |
33 | Prerequisite: `Google Gemini API` 34 |
35 | 36 | ![Screenshot (39)](https://github.com/user-attachments/assets/cf75db26-a63c-40d5-8dd0-3976b80076de) 37 | 38 |

39 | ## Steps to get Gemini API key 40 | - Sign up at `https://ai.google.dev/aistudio` 41 | 42 | 43 | - Click on `Get API key` 44 | 45 | 46 | 47 |

48 | ## Built With 49 | 50 | 👉 Tech Stacks: 51 |

52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |

75 | 76 | 77 | 78 | 79 | 80 |
81 |
82 |
83 | 84 | ## Getting Started 👩‍💻 85 | 86 | > ⚠️Prerequisites 87 | > 88 | > - Recommended to have the latest version of Google Chrome 89 | > - Make sure to switch on Chrome's `developer mode` 90 | 91 | 92 | To get a local copy up and running follow these simple steps 93 | 94 | 95 | ## Installation Guide 96 | 97 | 98 | 99 | https://github.com/user-attachments/assets/a8769d70-4d1e-4b93-a2f5-7692ab313b3f 100 | 101 | 102 | 103 | 1. Open Git Bash and change directory 104 | 105 | ``` sh 106 | cd path 107 | ``` 108 | 3. Clone the repo 109 | 110 | ```sh 111 | git clone https://github.com/gitgoap/AI-Snipping-Tool.git 112 | ``` 113 | 114 | 3. Open Google Chrome 115 | 116 | 4. Click **3 dots** at top right corner 117 | 118 | 5. Go to Extensions> Manage Extensions 119 | 120 | 6. Click **Load Unpack** and select the repo where you saved initially while cloning 121 | 122 | 123 | 124 | 125 | 126 | 127 |


128 | 129 | 130 | 131 | ## Contribution Guide 132 | - Make sure to raise an issue before raising a Pull Request. 133 | - Get the issue assiged. 134 | - Mention the issue number (`Eg: #4`) while raising a Pull Request in the description. 135 | 136 |

137 | 138 | ## Star History 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | Star History Chart 147 | 148 | 149 | 150 | 151 |


152 | ## Problem Statement 153 | 154 | We often need to type out text from videos, images, or thumbnails on websites. This can be a tedious and error-prone process, especially if the text is long or has tricky stuff like website links, technical words, code examples, or math equations. This issue comes up on YouTube, where useful info is shown in videos or thumbnail images. 155 |
156 |
157 |
158 |
159 | 160 | ## Some of the scenarios where this problem arises: 161 |
162 | 163 |
164 | 165 | 166 | 167 |

168 | 169 | 170 | 171 | 172 | - Manually typing the displayed link is inefficient and prone to errors. 173 | 174 | 175 |
176 |
177 | 178 | 179 | 180 | 181 | 182 | 183 | - Copying the references given in the presentation footer is difficult 184 | 185 |
186 | 187 | 188 | 189 |
190 | 191 | 192 | 193 | - Copying code directly to the editor is not possible 194 | 195 | 196 |
197 | 198 |
199 | 200 | 201 | 202 | 203 |
204 | 205 | - Copying text from documents shared as images on **social media** is tricky 206 | - People may require this text to feed into LLMs to get a `summary` 207 | 208 | 209 |
210 | 211 |
212 | 213 | # Working 214 | 215 | ## Our Chrome extension is utilizing **Tesseract OCR** for character recognition in the image: 216 | 1. The extension's background script ```worker.js``` listens for user actions, such as clicking the extension's icon or using the context menu. 217 | 2. When the user initiates a screenshot, the extension captures the visible tab using ```chrome.tabs.captureVisibleTab``` and gets the screenshot data as a PNG image. 218 | 3. The extension then injects several scripts (```helper.js```, ```response.js```, ```elements.js```, and ```custom-elements.min.js```) into the current tab's context. These scripts are responsible for rendering the OCR result and handling the OCR process. 219 | 4. The ocr-result custom element, defined in ```elements.js```, is used to perform the OCR operation. This custom element utilizes the ```tesseract.js``` library, which is a pure **JavaScript port** of the **Tesseract OCR** engine. 220 | 5. The ocr-result element is configured with user preferences such as language, accuracy, and other settings fetched from the Chrome extension's local storage. 221 | 6. The captured screenshot data (PNG image) is passed to the **ocr-result element**, which then runs the **Tesseract OCR** engine on the image to extract the text. 222 | 7. The extracted text is rendered within the **ocr-result element**, allowing the user to view and interact with the OCR results. 223 | 224 | 225 |
226 | 227 | 228 | 229 | > **Tesseract OCR**: It is an open-source optical character recognition (OCR) engine developed by Google. It is designed to extract text from images and documents without a text layer, outputting the document in various formats such as plain text, HTML, PDF, and more. **Tesseract** supports recognition of over **100 languages** "out of the box" and is highly customizable. 230 | 231 |

232 | 233 | # Future Scope / Features 234 | 235 | - [ ] **YouTube Video Screenshot:** Capture text in the current YouTube video frame with just one click of a popup button. 236 | - [ ] **Answers questions just with screenshots:** This feature is possible by utilizing the multimodal feature of Google Gemini. 237 | 238 | 239 | ![image](https://github.com/gitgoap/HackFest-24-IIT-Dhanbad/assets/117789470/1592f070-2e07-436b-b268-dc25be5e8e53) 240 | 241 | 242 | 243 | 244 |

Our Contributors ❤️

245 |
246 |

Thank you for contributing to our repository.

247 | 248 | ![Contributors](https://contrib.rocks/image?repo=gitgoap/AI-Snipping-Tool_IIT-D_Hackathon) 249 | 250 |
251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Convert Picture to Text" 4 | }, 5 | "shortDesc": { 6 | "message": "Convert Picture to Text: Effortlessly extract text from picture with our powerful Chrome extension. Simplify convert image to text." 7 | }, 8 | "storeDesc": { 9 | "message": "😩 Tired of typing out lengthy text from images?\n📷 Wish there was an easier way to extract text from an image?\n⏱️ Look no further! Convert picture to text is your go-to solution for effortlessly converting pictures to text in a matter of seconds.\n \n💻 With our user-friendly Google Chrome extension, you can seamlessly convert text in pictures to text, making your work more efficient and saving you valuable time. \n \n📚 Whether you're a student needing to extract text from scanned notes, a professional converting images to text for reports, or simply someone who wants to quickly extract text from photos, our extension has got you covered.\n\n💫Our advantages:\n1️⃣ Easy and convenient interface\n2️⃣ Multilingual\n3️⃣ Fast recognition\n4️⃣ Unlimited number of requests\n5️⃣ Lack of registration\n6️⃣ Data protection\n7️⃣ Using the OCR engine\n8️⃣ Image area recognition\n\nKey Features:\n✨ Effortless Conversion: Our extension simplifies the process of extracting text from a picture. Just install the extension, select the area to be converted, and watch as it swiftly converts from picture to text.\n🔍 Accurate OCR Technology: Powered by advanced Optical Character Recognition (ocr online) technology, our extension ensures accurate text extract from image.\n \n🔄 Versatile Compatibility:\n🔸 JPEG\n🔸 PNG\n🔸 BMP\n🔸 TIFF\n🔸 WEBP\n🔸 SVG\n \n⏲️ Time-Saving Solution: Say goodbye to manual typing!\nOur extension significantly reduces the time and effort required to transcribe text from a picture, allowing you to focus on more important tasks.\n \n🔒 Privacy Protection: We understand the importance of your privacy.\nRest assured, your images and extracted text are processed securely, with no risk of data breaches or leaks.\n \n🌐 Online and Offline Mode: Whether you're connected to the internet or working offline, our extension offers seamless text extraction from image, ensuring flexibility and convenience.\n🖼️ Intuitive Interface: Our user-friendly interface makes it easy for users of all levels to convert from picture to text effortlessly. No technical expertise required jpg to word!\nThis innovative procedure entails the conversion of visual representations, such as photographs or illustrations, into coherent and understandable written representations.\n\n⚡ Benefits:\n✓ Efficiently convert text from images for academic, professional, or personal purposes.\n✓ Enhance productivity by eliminating manual transcription tasks. \n✓ Accessible and convenient solution for all your needs.\n✓ Stay organized by converting pictures to words containing important information.\n✓ Compatible with various image formats and accessible directly from your Chrome browser.\n✓ Streamline document management process.\n✓ Convert scanned invoices, receipts, and contracts using text from image.\n✓ Save time and resources.\n✓ Improve efficiency and reduce costs by using convert picture to text for data handling.\n \n💡 Whether you need to extract words from:\n1️⃣ scanned documents\n2️⃣ handwritten notes\n3️⃣ images with embedded text\nOur converter is your reliable companion.\n🖱️ Experience the convenience with just a few clicks img to text!\n \n📜 List of features:\n🔹 Selecting a fragment of a page.\n🔹 Ability to select transcription language.\n🔹 Automatic addition of recognized words to the extension window.\n🔹 Ability to edit recognized words.\n🔹 Possibility of copying.\n \n✍️ Try today and revolutionize the way you pic to text. Say goodbye to tedious manual transcription!\n \n🎓 Image to text converter for learning:\n👨‍🎓 Students:\n➤ Digitize printed materials, like textbooks or journal articles, for research purposes using convert pictures to text.\n➤ Make printed materials searchable and editable with image text extractor.\n \n👨‍🏫 Educators:\n➤ Convert handwritten notes or diagrams into digital format using convert text in picture to text.\n➤ Facilitate sharing and collaboration among students with digitized content.\n \n😎 For professionals:\n‍⚖️‍ Legal professionals:\n➤ Utilize extract text from image to quickly search through case files and contracts.\n \n👩‍⚕️ Medical practitioners:\n➤ Digitize patient records for easy reference using convert picture to text.\n \n📈 Financial analysts:\n➤ Extract data from financial statements with ease using picture to text converter.\n \n🧐 FAQs about the extension:\n❓ How do I install?\n💡 To install convert picture to text, just click on the \"Add to Chrome\" button. It will be added to your browser, and you can start using it.\n \n❓ How does is it work?\n💡 Convert picture to text is a Chrome extension that allows you to convert a picture to text format.\n \n❓ Can I use it for free?\n💡 Yes, extension is available as a free Chrome extension.\n \n❓ Can extension transcribe any image?\n💡 Yes, It can transcribe any pictures to words format. It supports multiple languages.\n \n❓ Is my privacy protected when using extension?\n💡 Absolutely! Extension operates locally within your browser, ensuring the privacy and security of your personal information. It does not collect or store any user data.\n \n📧 Feedback:\nIf you have any questions, please contact us in any way convenient for you.\n" 10 | } 11 | } -------------------------------------------------------------------------------- /engine/helper.js: -------------------------------------------------------------------------------- 1 | if (typeof self.execute === 'undefined') { 2 | const methods = {}; 3 | 4 | const observe = e => { 5 | if (e.data) { 6 | const { command, id, report, result, message } = e.data; 7 | 8 | if (methods[id]) { 9 | if (command === 'report') { 10 | methods[id].report(report); 11 | } 12 | else if (command === 'result') { 13 | methods[id].resolve(result); 14 | } 15 | else if (command === 'error') { 16 | methods[id].reject(Error(message)); 17 | } 18 | 19 | if (command === 'result' || command === 'error') { 20 | methods[id].frame.remove(); 21 | delete methods[id]; 22 | } 23 | } 24 | } 25 | }; 26 | addEventListener('message', observe); 27 | 28 | self.execute = ({ 29 | signal, 30 | lang, 31 | src, 32 | accuracy = '4.0.0' 33 | }, report) => new Promise((resolve, reject) => { 34 | const frame = document.createElement('iframe'); 35 | const id = 'worker-' + Math.random(); 36 | methods[id] = { report, resolve, reject, frame }; 37 | frame.src = chrome.runtime.getURL('/engine/index.html?id=' + id); 38 | frame.style.display = 'none'; 39 | frame.onload = () => frame.contentWindow.postMessage({ 40 | lang, 41 | src, 42 | accuracy 43 | }, '*'); 44 | document.documentElement.append(frame); 45 | 46 | signal.addEventListener('abort', () => frame.remove()); 47 | }); 48 | } 49 | 50 | self.crop = (href, { width, height, left, top }, mode = 'normal') => new Promise(resolve => { 51 | const canvas = document.createElement('canvas'); 52 | const ctx = canvas.getContext('2d'); 53 | const img = new Image(); 54 | img.onload = () => { 55 | canvas.width = width || img.width; 56 | canvas.height = height || img.height; 57 | if (width && height) { 58 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 59 | } 60 | else { 61 | ctx.drawImage(img, 0, 0); 62 | } 63 | if (mode === 'invert' || mode === 'gray') { 64 | ctx.globalCompositeOperation = mode === 'gray' ? 'saturation' : 'difference'; 65 | ctx.fillStyle = '#fff'; 66 | ctx.globalAlpha = 1; 67 | ctx.fillRect(0, 0, canvas.width, canvas.height); 68 | } 69 | 70 | resolve(canvas.toDataURL()); 71 | }; 72 | img.src = href; 73 | }); 74 | 75 | '' 76 | -------------------------------------------------------------------------------- /engine/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

This is OCR engine

10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /engine/index.js: -------------------------------------------------------------------------------- 1 | /* global Tesseract */ 2 | 3 | const args = new URLSearchParams(location.search); 4 | const id = args.get('id'); 5 | 6 | window.addEventListener('message', e => { 7 | const request = e.data; 8 | 9 | Tesseract.createWorker(request.lang, 1, { // 1: Tesseract LSTM, 0: Tesseract Legacy 10 | 'workerBlobURL': false, 11 | 'workerPath': 'worker-overwrites.js', 12 | // tesseract-core-simd.wasm.js has significantly faster recognition speeds (for Tesseract LSTM, the default model) 13 | // compared to the build without SIMD support 14 | 'corePath': 'tesseract/tesseract-core-simd-lstm.wasm.js', 15 | 'cacheMethod': 'none', 16 | 'langPath': 'https://tessdata.projectnaptha.com/' + request.accuracy, 17 | logger(report) { 18 | parent.postMessage({ 19 | command: 'report', 20 | id, 21 | report 22 | }, '*'); 23 | }, 24 | errorHandler(e) { 25 | console.warn(e); 26 | parent.postMessage({ 27 | command: 'error', 28 | id, 29 | message: e.message || e.toString() 30 | }, '*'); 31 | } 32 | }).then(async worker => { 33 | try { 34 | // https://github.com/tesseract-ocr/tesseract/blob/main/src/ccmain/tesseractclass.cpp 35 | const params = {}; 36 | if (request.lang.endsWith('_vert')) { 37 | params['tessedit_pageseg_mode'] = Tesseract.PSM.SINGLE_BLOCK_VERT_TEXT; 38 | } 39 | // params['preserve_interword_spaces'] = '1'; . 40 | await worker.setParameters(params); 41 | 42 | const result = (await worker.recognize(request.src)).data; 43 | 44 | parent.postMessage({ 45 | command: 'result', 46 | id, 47 | result 48 | }, '*'); 49 | } 50 | catch (e) { 51 | console.warn(e); 52 | parent.postMessage({ 53 | command: 'error', 54 | id, 55 | message: e.message || e.toString() 56 | }, '*'); 57 | } 58 | 59 | worker.terminate(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /engine/preview-controls.js: -------------------------------------------------------------------------------- 1 | let scale = 1; 2 | let rotation = 0; 3 | const img = document.getElementById('preview-image'); 4 | 5 | function updateTransform() { 6 | img.style.transform = `scale(${scale}) rotate(${rotation}deg)`; 7 | } 8 | 9 | document.getElementById('zoom-in').addEventListener('click', () => { 10 | scale *= 1.2; 11 | updateTransform(); 12 | }); 13 | 14 | document.getElementById('zoom-out').addEventListener('click', () => { 15 | scale /= 1.2; 16 | updateTransform(); 17 | }); 18 | 19 | document.getElementById('rotate-left').addEventListener('click', () => { 20 | rotation -= 90; 21 | updateTransform(); 22 | }); 23 | 24 | document.getElementById('rotate-right').addEventListener('click', () => { 25 | rotation += 90; 26 | updateTransform(); 27 | }); -------------------------------------------------------------------------------- /engine/tesseract/ReadMe: -------------------------------------------------------------------------------- 1 | https://unpkg.com/browse/tesseract.js-core@5.0.0/tesseract-core-simd-lstm.wasm 2 | https://unpkg.com/browse/tesseract.js-core@5.0.0/tesseract-core-simd-lstm.js 3 | 4 | https://unpkg.com/tesseract.js@5.0.3/dist/worker.min.js 5 | https://unpkg.com/tesseract.js@5.0.3/dist/tesseract.min.js 6 | -> replace "https://cdn.jsdelivr.net/npm/tesseract.js@v" with "" per review request 7 | 8 | -------------------------------------------------------------------------------- /engine/tesseract/tesseract-core-simd-lstm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgoap/AI-Snipping-Tool/80a68b53c27df58f37b73673a1e50ab262c74f7e/engine/tesseract/tesseract-core-simd-lstm.wasm -------------------------------------------------------------------------------- /engine/worker-overwrites.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | self.fetch = new Proxy(self.fetch, { 4 | apply(target, self, args) { 5 | const [href, options] = args; 6 | 7 | if (href.includes('.traineddata.gz')) { 8 | 9 | const validate = r => { 10 | if (r.ok) { 11 | return r; 12 | } 13 | throw Error('[Extract Text From Image] Cannot download traineddata (' + r.status + ')'); 14 | }; 15 | 16 | const promise = new Promise(resolve => { 17 | resolve(); 18 | }).then(async () => { 19 | let cache = null; 20 | 21 | 22 | try { 23 | cache = await caches.open('traineddata'); 24 | } catch (e) { 25 | console.warn('[Extract Text From Image] CacheStorage interface is not available which may affect performance. Might be the Brave browser or disabled cookies.'); 26 | } 27 | 28 | if (cache) { 29 | const er = await cache.match(href); 30 | if (er) { 31 | // console.log('[Extract Text From Image] trained data cache hit'); 32 | return er; 33 | } 34 | } 35 | 36 | const r = await Reflect.apply(target, self, args).then(validate).catch(e => { 37 | console.warn('[Extract Text From Image] Cannot download the traineddata', href, e); 38 | const path = href.split('.com/')[1]; 39 | 40 | return Reflect.apply(target, self, [`https://github.com/naptha/tessdata/blob/gh-pages/${path}?raw=true`, options]).then(validate); 41 | }); 42 | 43 | // save 44 | if (cache) { 45 | cache.put(href, r.clone()); 46 | } 47 | 48 | // return 49 | return Object.assign(r, { 50 | async arrayBuffer() { 51 | const reader = r.body.getReader(); 52 | const chunks = []; 53 | let bytes = 0; 54 | 55 | const length = Number(r.headers.get('Content-Length')); 56 | 57 | // eslint-disable-next-line no-constant-condition 58 | while (true) { 59 | const { done, value } = await reader.read(); 60 | if (done) { 61 | break; 62 | } 63 | 64 | bytes += value.byteLength; 65 | postMessage({ 66 | status: 'progress', 67 | data: { 68 | status: 'loading language traineddata', 69 | progress: bytes / length 70 | } 71 | }); 72 | 73 | chunks.push(value); 74 | } 75 | const ab = await new Blob(chunks).arrayBuffer(); 76 | return ab; 77 | } 78 | }); 79 | 80 | }); 81 | 82 | return promise; 83 | } 84 | else { 85 | return Reflect.apply(target, self, args); 86 | } 87 | } 88 | }); 89 | 90 | self.importScripts('tesseract/worker.min.js'); 91 | -------------------------------------------------------------------------------- /icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitgoap/AI-Snipping-Tool/80a68b53c27df58f37b73673a1e50ab262c74f7e/icons/logo.png -------------------------------------------------------------------------------- /inject/custom-elements.min.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | 'use strict'; 4 | var n = window.Document.prototype.createElement, 5 | p = window.Document.prototype.createElementNS, 6 | aa = window.Document.prototype.importNode, 7 | ba = window.Document.prototype.prepend, 8 | ca = window.Document.prototype.append, 9 | da = window.DocumentFragment.prototype.prepend, 10 | ea = window.DocumentFragment.prototype.append, 11 | q = window.Node.prototype.cloneNode, 12 | r = window.Node.prototype.appendChild, 13 | t = window.Node.prototype.insertBefore, 14 | u = window.Node.prototype.removeChild, 15 | v = window.Node.prototype.replaceChild, 16 | w = Object.getOwnPropertyDescriptor(window.Node.prototype, 17 | "textContent"), 18 | y = window.Element.prototype.attachShadow, 19 | z = Object.getOwnPropertyDescriptor(window.Element.prototype, "innerHTML"), 20 | A = window.Element.prototype.getAttribute, 21 | B = window.Element.prototype.setAttribute, 22 | C = window.Element.prototype.removeAttribute, 23 | D = window.Element.prototype.toggleAttribute, 24 | E = window.Element.prototype.getAttributeNS, 25 | F = window.Element.prototype.setAttributeNS, 26 | G = window.Element.prototype.removeAttributeNS, 27 | H = window.Element.prototype.insertAdjacentElement, 28 | fa = window.Element.prototype.insertAdjacentHTML, 29 | ha = window.Element.prototype.prepend, 30 | ia = window.Element.prototype.append, 31 | ja = window.Element.prototype.before, 32 | ka = window.Element.prototype.after, 33 | la = window.Element.prototype.replaceWith, 34 | ma = window.Element.prototype.remove, 35 | na = window.HTMLElement, 36 | I = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, "innerHTML"), 37 | oa = window.HTMLElement.prototype.insertAdjacentElement, 38 | pa = window.HTMLElement.prototype.insertAdjacentHTML; 39 | var qa = new Set; 40 | "annotation-xml color-profile font-face font-face-src font-face-uri font-face-format font-face-name missing-glyph".split(" ").forEach(function(a) { 41 | return qa.add(a) 42 | }); 43 | 44 | function ra(a) { 45 | var b = qa.has(a); 46 | a = /^[a-z][.0-9_a-z]*-[-.0-9_a-z]*$/.test(a); 47 | return !b && a 48 | } 49 | var sa = document.contains ? document.contains.bind(document) : document.documentElement.contains.bind(document.documentElement); 50 | 51 | function J(a) { 52 | var b = a.isConnected; 53 | if (void 0 !== b) return b; 54 | if (sa(a)) return !0; 55 | for (; a && !(a.__CE_isImportDocument || a instanceof Document);) a = a.parentNode || (window.ShadowRoot && a instanceof ShadowRoot ? a.host : void 0); 56 | return !(!a || !(a.__CE_isImportDocument || a instanceof Document)) 57 | } 58 | 59 | function K(a) { 60 | var b = a.children; 61 | if (b) return Array.prototype.slice.call(b); 62 | b = []; 63 | for (a = a.firstChild; a; a = a.nextSibling) a.nodeType === Node.ELEMENT_NODE && b.push(a); 64 | return b 65 | } 66 | 67 | function L(a, b) { 68 | for (; b && b !== a && !b.nextSibling;) b = b.parentNode; 69 | return b && b !== a ? b.nextSibling : null 70 | } 71 | 72 | function M(a, b, d) { 73 | for (var f = a; f;) { 74 | if (f.nodeType === Node.ELEMENT_NODE) { 75 | var c = f; 76 | b(c); 77 | var e = c.localName; 78 | if ("link" === e && "import" === c.getAttribute("rel")) { 79 | f = c.import; 80 | void 0 === d && (d = new Set); 81 | if (f instanceof Node && !d.has(f)) 82 | for (d.add(f), f = f.firstChild; f; f = f.nextSibling) M(f, b, d); 83 | f = L(a, c); 84 | continue 85 | } else if ("template" === e) { 86 | f = L(a, c); 87 | continue 88 | } 89 | if (c = c.__CE_shadowRoot) 90 | for (c = c.firstChild; c; c = c.nextSibling) M(c, b, d) 91 | } 92 | f = f.firstChild ? f.firstChild : L(a, f) 93 | } 94 | }; 95 | 96 | function N() { 97 | var a = !(null === O || void 0 === O || !O.noDocumentConstructionObserver), 98 | b = !(null === O || void 0 === O || !O.shadyDomFastWalk); 99 | this.m = []; 100 | this.g = []; 101 | this.j = !1; 102 | this.shadyDomFastWalk = b; 103 | this.I = !a 104 | } 105 | 106 | function P(a, b, d, f) { 107 | var c = window.ShadyDOM; 108 | if (a.shadyDomFastWalk && c && c.inUse) { 109 | if (b.nodeType === Node.ELEMENT_NODE && d(b), b.querySelectorAll) 110 | for (a = c.nativeMethods.querySelectorAll.call(b, "*"), b = 0; b < a.length; b++) d(a[b]) 111 | } else M(b, d, f) 112 | } 113 | 114 | function ta(a, b) { 115 | a.j = !0; 116 | a.m.push(b) 117 | } 118 | 119 | function ua(a, b) { 120 | a.j = !0; 121 | a.g.push(b) 122 | } 123 | 124 | function Q(a, b) { 125 | a.j && P(a, b, function(d) { 126 | return R(a, d) 127 | }) 128 | } 129 | 130 | function R(a, b) { 131 | if (a.j && !b.__CE_patched) { 132 | b.__CE_patched = !0; 133 | for (var d = 0; d < a.m.length; d++) a.m[d](b); 134 | for (d = 0; d < a.g.length; d++) a.g[d](b) 135 | } 136 | } 137 | 138 | function S(a, b) { 139 | var d = []; 140 | P(a, b, function(c) { 141 | return d.push(c) 142 | }); 143 | for (b = 0; b < d.length; b++) { 144 | var f = d[b]; 145 | 1 === f.__CE_state ? a.connectedCallback(f) : T(a, f) 146 | } 147 | } 148 | 149 | function U(a, b) { 150 | var d = []; 151 | P(a, b, function(c) { 152 | return d.push(c) 153 | }); 154 | for (b = 0; b < d.length; b++) { 155 | var f = d[b]; 156 | 1 === f.__CE_state && a.disconnectedCallback(f) 157 | } 158 | } 159 | 160 | function V(a, b, d) { 161 | d = void 0 === d ? {} : d; 162 | var f = d.J, 163 | c = d.upgrade || function(g) { 164 | return T(a, g) 165 | }, 166 | e = []; 167 | P(a, b, function(g) { 168 | a.j && R(a, g); 169 | if ("link" === g.localName && "import" === g.getAttribute("rel")) { 170 | var h = g.import; 171 | h instanceof Node && (h.__CE_isImportDocument = !0, h.__CE_registry = document.__CE_registry); 172 | h && "complete" === h.readyState ? h.__CE_documentLoadHandled = !0 : g.addEventListener("load", function() { 173 | var k = g.import; 174 | if (!k.__CE_documentLoadHandled) { 175 | k.__CE_documentLoadHandled = !0; 176 | var l = new Set; 177 | f && (f.forEach(function(m) { 178 | return l.add(m) 179 | }), 180 | l.delete(k)); 181 | V(a, k, { 182 | J: l, 183 | upgrade: c 184 | }) 185 | } 186 | }) 187 | } else e.push(g) 188 | }, f); 189 | for (b = 0; b < e.length; b++) c(e[b]) 190 | } 191 | 192 | function T(a, b) { 193 | try { 194 | var d = b.ownerDocument, 195 | f = d.__CE_registry; 196 | var c = f && (d.defaultView || d.__CE_isImportDocument) ? W(f, b.localName) : void 0; 197 | if (c && void 0 === b.__CE_state) { 198 | c.constructionStack.push(b); 199 | try { 200 | try { 201 | if (new c.constructorFunction !== b) throw Error("The custom element constructor did not produce the element being upgraded."); 202 | } finally { 203 | c.constructionStack.pop() 204 | } 205 | } catch (k) { 206 | throw b.__CE_state = 2, k; 207 | } 208 | b.__CE_state = 1; 209 | b.__CE_definition = c; 210 | if (c.attributeChangedCallback && b.hasAttributes()) { 211 | var e = c.observedAttributes; 212 | for (c = 0; c < e.length; c++) { 213 | var g = e[c], 214 | h = b.getAttribute(g); 215 | null !== h && a.attributeChangedCallback(b, g, null, h, null) 216 | } 217 | } 218 | J(b) && a.connectedCallback(b) 219 | } 220 | } catch (k) { 221 | X(k) 222 | } 223 | } 224 | N.prototype.connectedCallback = function(a) { 225 | var b = a.__CE_definition; 226 | if (b.connectedCallback) try { 227 | b.connectedCallback.call(a) 228 | } catch (d) { 229 | X(d) 230 | } 231 | }; 232 | N.prototype.disconnectedCallback = function(a) { 233 | var b = a.__CE_definition; 234 | if (b.disconnectedCallback) try { 235 | b.disconnectedCallback.call(a) 236 | } catch (d) { 237 | X(d) 238 | } 239 | }; 240 | N.prototype.attributeChangedCallback = function(a, b, d, f, c) { 241 | var e = a.__CE_definition; 242 | if (e.attributeChangedCallback && -1 < e.observedAttributes.indexOf(b)) try { 243 | e.attributeChangedCallback.call(a, b, d, f, c) 244 | } catch (g) { 245 | X(g) 246 | } 247 | }; 248 | 249 | function va(a, b, d, f) { 250 | var c = b.__CE_registry; 251 | if (c && (null === f || "http://www.w3.org/1999/xhtml" === f) && (c = W(c, d))) try { 252 | var e = new c.constructorFunction; 253 | if (void 0 === e.__CE_state || void 0 === e.__CE_definition) throw Error("Failed to construct '" + d + "': The returned value was not constructed with the HTMLElement constructor."); 254 | if ("http://www.w3.org/1999/xhtml" !== e.namespaceURI) throw Error("Failed to construct '" + d + "': The constructed element's namespace must be the HTML namespace."); 255 | if (e.hasAttributes()) throw Error("Failed to construct '" + 256 | d + "': The constructed element must not have any attributes."); 257 | if (null !== e.firstChild) throw Error("Failed to construct '" + d + "': The constructed element must not have any children."); 258 | if (null !== e.parentNode) throw Error("Failed to construct '" + d + "': The constructed element must not have a parent node."); 259 | if (e.ownerDocument !== b) throw Error("Failed to construct '" + d + "': The constructed element's owner document is incorrect."); 260 | if (e.localName !== d) throw Error("Failed to construct '" + d + "': The constructed element's local name is incorrect."); 261 | return e 262 | } catch (g) { 263 | return X(g), b = null === f ? n.call(b, d) : p.call(b, f, d), Object.setPrototypeOf(b, HTMLUnknownElement.prototype), b.__CE_state = 2, b.__CE_definition = void 0, R(a, b), b 264 | } 265 | b = null === f ? n.call(b, d) : p.call(b, f, d); 266 | R(a, b); 267 | return b 268 | } 269 | 270 | function X(a) { 271 | var b = "", 272 | d = "", 273 | f = 0, 274 | c = 0; 275 | a instanceof Error ? (b = a.message, d = a.sourceURL || a.fileName || "", f = a.line || a.lineNumber || 0, c = a.column || a.columnNumber || 0) : b = "Uncaught " + String(a); 276 | var e = void 0; 277 | void 0 === ErrorEvent.prototype.initErrorEvent ? e = new ErrorEvent("error", { 278 | cancelable: !0, 279 | message: b, 280 | filename: d, 281 | lineno: f, 282 | colno: c, 283 | error: a 284 | }) : (e = document.createEvent("ErrorEvent"), e.initErrorEvent("error", !1, !0, b, d, f), e.preventDefault = function() { 285 | Object.defineProperty(this, "defaultPrevented", { 286 | configurable: !0, 287 | get: function() { 288 | return !0 289 | } 290 | }) 291 | }); 292 | void 0 === e.error && Object.defineProperty(e, "error", { 293 | configurable: !0, 294 | enumerable: !0, 295 | get: function() { 296 | return a 297 | } 298 | }); 299 | window.dispatchEvent(e); 300 | e.defaultPrevented || console.error(a) 301 | }; 302 | 303 | function wa() { 304 | var a = this; 305 | this.g = void 0; 306 | this.F = new Promise(function(b) { 307 | a.l = b 308 | }) 309 | } 310 | wa.prototype.resolve = function(a) { 311 | if (this.g) throw Error("Already resolved."); 312 | this.g = a; 313 | this.l(a) 314 | }; 315 | 316 | function xa(a) { 317 | var b = document; 318 | this.l = void 0; 319 | this.h = a; 320 | this.g = b; 321 | V(this.h, this.g); 322 | "loading" === this.g.readyState && (this.l = new MutationObserver(this.G.bind(this)), this.l.observe(this.g, { 323 | childList: !0, 324 | subtree: !0 325 | })) 326 | } 327 | 328 | function ya(a) { 329 | a.l && a.l.disconnect() 330 | } 331 | xa.prototype.G = function(a) { 332 | var b = this.g.readyState; 333 | "interactive" !== b && "complete" !== b || ya(this); 334 | for (b = 0; b < a.length; b++) 335 | for (var d = a[b].addedNodes, f = 0; f < d.length; f++) V(this.h, d[f]) 336 | }; 337 | 338 | function Y(a) { 339 | this.s = new Map; 340 | this.u = new Map; 341 | this.C = new Map; 342 | this.A = !1; 343 | this.B = new Map; 344 | this.o = function(b) { 345 | return b() 346 | }; 347 | this.i = !1; 348 | this.v = []; 349 | this.h = a; 350 | this.D = a.I ? new xa(a) : void 0 351 | } 352 | Y.prototype.H = function(a, b) { 353 | var d = this; 354 | if (!(b instanceof Function)) throw new TypeError("Custom element constructor getters must be functions."); 355 | za(this, a); 356 | this.s.set(a, b); 357 | this.v.push(a); 358 | this.i || (this.i = !0, this.o(function() { 359 | return Aa(d) 360 | })) 361 | }; 362 | Y.prototype.define = function(a, b) { 363 | var d = this; 364 | if (!(b instanceof Function)) throw new TypeError("Custom element constructors must be functions."); 365 | za(this, a); 366 | Ba(this, a, b); 367 | this.v.push(a); 368 | this.i || (this.i = !0, this.o(function() { 369 | return Aa(d) 370 | })) 371 | }; 372 | 373 | function za(a, b) { 374 | if (!ra(b)) throw new SyntaxError("The element name '" + b + "' is not valid."); 375 | if (W(a, b)) throw Error("A custom element with name '" + (b + "' has already been defined.")); 376 | if (a.A) throw Error("A custom element is already being defined."); 377 | } 378 | 379 | function Ba(a, b, d) { 380 | a.A = !0; 381 | var f; 382 | try { 383 | var c = d.prototype; 384 | if (!(c instanceof Object)) throw new TypeError("The custom element constructor's prototype is not an object."); 385 | var e = function(m) { 386 | var x = c[m]; 387 | if (void 0 !== x && !(x instanceof Function)) throw Error("The '" + m + "' callback must be a function."); 388 | return x 389 | }; 390 | var g = e("connectedCallback"); 391 | var h = e("disconnectedCallback"); 392 | var k = e("adoptedCallback"); 393 | var l = (f = e("attributeChangedCallback")) && d.observedAttributes || [] 394 | } catch (m) { 395 | throw m; 396 | } finally { 397 | a.A = !1 398 | } 399 | d = { 400 | localName: b, 401 | constructorFunction: d, 402 | connectedCallback: g, 403 | disconnectedCallback: h, 404 | adoptedCallback: k, 405 | attributeChangedCallback: f, 406 | observedAttributes: l, 407 | constructionStack: [] 408 | }; 409 | a.u.set(b, d); 410 | a.C.set(d.constructorFunction, d); 411 | return d 412 | } 413 | Y.prototype.upgrade = function(a) { 414 | V(this.h, a) 415 | }; 416 | 417 | function Aa(a) { 418 | if (!1 !== a.i) { 419 | a.i = !1; 420 | for (var b = [], d = a.v, f = new Map, c = 0; c < d.length; c++) f.set(d[c], []); 421 | V(a.h, document, { 422 | upgrade: function(k) { 423 | if (void 0 === k.__CE_state) { 424 | var l = k.localName, 425 | m = f.get(l); 426 | m ? m.push(k) : a.u.has(l) && b.push(k) 427 | } 428 | } 429 | }); 430 | for (c = 0; c < b.length; c++) T(a.h, b[c]); 431 | for (c = 0; c < d.length; c++) { 432 | for (var e = d[c], g = f.get(e), h = 0; h < g.length; h++) T(a.h, g[h]); 433 | (e = a.B.get(e)) && e.resolve(void 0) 434 | } 435 | d.length = 0 436 | } 437 | } 438 | Y.prototype.get = function(a) { 439 | if (a = W(this, a)) return a.constructorFunction 440 | }; 441 | Y.prototype.whenDefined = function(a) { 442 | if (!ra(a)) return Promise.reject(new SyntaxError("'" + a + "' is not a valid custom element name.")); 443 | var b = this.B.get(a); 444 | if (b) return b.F; 445 | b = new wa; 446 | this.B.set(a, b); 447 | var d = this.u.has(a) || this.s.has(a); 448 | a = -1 === this.v.indexOf(a); 449 | d && a && b.resolve(void 0); 450 | return b.F 451 | }; 452 | Y.prototype.polyfillWrapFlushCallback = function(a) { 453 | this.D && ya(this.D); 454 | var b = this.o; 455 | this.o = function(d) { 456 | return a(function() { 457 | return b(d) 458 | }) 459 | } 460 | }; 461 | 462 | function W(a, b) { 463 | var d = a.u.get(b); 464 | if (d) return d; 465 | if (d = a.s.get(b)) { 466 | a.s.delete(b); 467 | try { 468 | return Ba(a, b, d()) 469 | } catch (f) { 470 | X(f) 471 | } 472 | } 473 | } 474 | Y.prototype.define = Y.prototype.define; 475 | Y.prototype.upgrade = Y.prototype.upgrade; 476 | Y.prototype.get = Y.prototype.get; 477 | Y.prototype.whenDefined = Y.prototype.whenDefined; 478 | Y.prototype.polyfillDefineLazy = Y.prototype.H; 479 | Y.prototype.polyfillWrapFlushCallback = Y.prototype.polyfillWrapFlushCallback; 480 | 481 | function Z(a, b, d) { 482 | function f(c) { 483 | return function(e) { 484 | for (var g = [], h = 0; h < arguments.length; ++h) g[h] = arguments[h]; 485 | h = []; 486 | for (var k = [], l = 0; l < g.length; l++) { 487 | var m = g[l]; 488 | m instanceof Element && J(m) && k.push(m); 489 | if (m instanceof DocumentFragment) 490 | for (m = m.firstChild; m; m = m.nextSibling) h.push(m); 491 | else h.push(m) 492 | } 493 | c.apply(this, g); 494 | for (g = 0; g < k.length; g++) U(a, k[g]); 495 | if (J(this)) 496 | for (g = 0; g < h.length; g++) k = h[g], k instanceof Element && S(a, k) 497 | } 498 | } 499 | void 0 !== d.prepend && (b.prepend = f(d.prepend)); 500 | void 0 !== d.append && (b.append = f(d.append)) 501 | }; 502 | 503 | function Ca(a) { 504 | Document.prototype.createElement = function(b) { 505 | return va(a, this, b, null) 506 | }; 507 | Document.prototype.importNode = function(b, d) { 508 | b = aa.call(this, b, !!d); 509 | this.__CE_registry ? V(a, b) : Q(a, b); 510 | return b 511 | }; 512 | Document.prototype.createElementNS = function(b, d) { 513 | return va(a, this, d, b) 514 | }; 515 | Z(a, Document.prototype, { 516 | prepend: ba, 517 | append: ca 518 | }) 519 | }; 520 | 521 | function Da(a) { 522 | function b(f) { 523 | return function(c) { 524 | for (var e = [], g = 0; g < arguments.length; ++g) e[g] = arguments[g]; 525 | g = []; 526 | for (var h = [], k = 0; k < e.length; k++) { 527 | var l = e[k]; 528 | l instanceof Element && J(l) && h.push(l); 529 | if (l instanceof DocumentFragment) 530 | for (l = l.firstChild; l; l = l.nextSibling) g.push(l); 531 | else g.push(l) 532 | } 533 | f.apply(this, e); 534 | for (e = 0; e < h.length; e++) U(a, h[e]); 535 | if (J(this)) 536 | for (e = 0; e < g.length; e++) h = g[e], h instanceof Element && S(a, h) 537 | } 538 | } 539 | var d = Element.prototype; 540 | void 0 !== ja && (d.before = b(ja)); 541 | void 0 !== ka && (d.after = b(ka)); 542 | void 0 !== la && 543 | (d.replaceWith = function(f) { 544 | for (var c = [], e = 0; e < arguments.length; ++e) c[e] = arguments[e]; 545 | e = []; 546 | for (var g = [], h = 0; h < c.length; h++) { 547 | var k = c[h]; 548 | k instanceof Element && J(k) && g.push(k); 549 | if (k instanceof DocumentFragment) 550 | for (k = k.firstChild; k; k = k.nextSibling) e.push(k); 551 | else e.push(k) 552 | } 553 | h = J(this); 554 | la.apply(this, c); 555 | for (c = 0; c < g.length; c++) U(a, g[c]); 556 | if (h) 557 | for (U(a, this), c = 0; c < e.length; c++) g = e[c], g instanceof Element && S(a, g) 558 | }); 559 | void 0 !== ma && (d.remove = function() { 560 | var f = J(this); 561 | ma.call(this); 562 | f && U(a, this) 563 | }) 564 | }; 565 | 566 | function Ea(a) { 567 | function b(c, e) { 568 | Object.defineProperty(c, "innerHTML", { 569 | enumerable: e.enumerable, 570 | configurable: !0, 571 | get: e.get, 572 | set: function(g) { 573 | var h = this, 574 | k = void 0; 575 | J(this) && (k = [], P(a, this, function(x) { 576 | x !== h && k.push(x) 577 | })); 578 | e.set.call(this, g); 579 | if (k) 580 | for (var l = 0; l < k.length; l++) { 581 | var m = k[l]; 582 | 1 === m.__CE_state && a.disconnectedCallback(m) 583 | } 584 | this.ownerDocument.__CE_registry ? V(a, this) : Q(a, this); 585 | return g 586 | } 587 | }) 588 | } 589 | 590 | function d(c, e) { 591 | c.insertAdjacentElement = function(g, h) { 592 | var k = J(h); 593 | g = e.call(this, g, h); 594 | k && U(a, h); 595 | J(g) && S(a, h); 596 | return g 597 | } 598 | } 599 | 600 | function f(c, 601 | e) { 602 | function g(h, k) { 603 | for (var l = []; h !== k; h = h.nextSibling) l.push(h); 604 | for (k = 0; k < l.length; k++) V(a, l[k]) 605 | } 606 | c.insertAdjacentHTML = function(h, k) { 607 | h = h.toLowerCase(); 608 | if ("beforebegin" === h) { 609 | var l = this.previousSibling; 610 | e.call(this, h, k); 611 | g(l || this.parentNode.firstChild, this) 612 | } else if ("afterbegin" === h) l = this.firstChild, e.call(this, h, k), g(this.firstChild, l); 613 | else if ("beforeend" === h) l = this.lastChild, e.call(this, h, k), g(l || this.firstChild, null); 614 | else if ("afterend" === h) l = this.nextSibling, e.call(this, h, k), g(this.nextSibling, l); 615 | else throw new SyntaxError("The value provided (" + String(h) + ") is not one of 'beforebegin', 'afterbegin', 'beforeend', or 'afterend'."); 616 | } 617 | } 618 | y && (Element.prototype.attachShadow = function(c) { 619 | c = y.call(this, c); 620 | if (a.j && !c.__CE_patched) { 621 | c.__CE_patched = !0; 622 | for (var e = 0; e < a.m.length; e++) a.m[e](c) 623 | } 624 | return this.__CE_shadowRoot = c 625 | }); 626 | z && z.get ? b(Element.prototype, z) : I && I.get ? b(HTMLElement.prototype, I) : ua(a, function(c) { 627 | b(c, { 628 | enumerable: !0, 629 | configurable: !0, 630 | get: function() { 631 | return q.call(this, !0).innerHTML 632 | }, 633 | set: function(e) { 634 | var g = 635 | "template" === this.localName, 636 | h = g ? this.content : this, 637 | k = p.call(document, this.namespaceURI, this.localName); 638 | for (k.innerHTML = e; 0 < h.childNodes.length;) u.call(h, h.childNodes[0]); 639 | for (e = g ? k.content : k; 0 < e.childNodes.length;) r.call(h, e.childNodes[0]) 640 | } 641 | }) 642 | }); 643 | Element.prototype.setAttribute = function(c, e) { 644 | if (1 !== this.__CE_state) return B.call(this, c, e); 645 | var g = A.call(this, c); 646 | B.call(this, c, e); 647 | e = A.call(this, c); 648 | a.attributeChangedCallback(this, c, g, e, null) 649 | }; 650 | Element.prototype.setAttributeNS = function(c, e, g) { 651 | if (1 !== this.__CE_state) return F.call(this, 652 | c, e, g); 653 | var h = E.call(this, c, e); 654 | F.call(this, c, e, g); 655 | g = E.call(this, c, e); 656 | a.attributeChangedCallback(this, e, h, g, c) 657 | }; 658 | Element.prototype.removeAttribute = function(c) { 659 | if (1 !== this.__CE_state) return C.call(this, c); 660 | var e = A.call(this, c); 661 | C.call(this, c); 662 | null !== e && a.attributeChangedCallback(this, c, e, null, null) 663 | }; 664 | D && (Element.prototype.toggleAttribute = function(c, e) { 665 | if (1 !== this.__CE_state) return D.call(this, c, e); 666 | var g = A.call(this, c), 667 | h = null !== g; 668 | e = D.call(this, c, e); 669 | h !== e && a.attributeChangedCallback(this, c, g, e ? "" : null, null); 670 | return e 671 | }); 672 | Element.prototype.removeAttributeNS = function(c, e) { 673 | if (1 !== this.__CE_state) return G.call(this, c, e); 674 | var g = E.call(this, c, e); 675 | G.call(this, c, e); 676 | var h = E.call(this, c, e); 677 | g !== h && a.attributeChangedCallback(this, e, g, h, c) 678 | }; 679 | oa ? d(HTMLElement.prototype, oa) : H && d(Element.prototype, H); 680 | pa ? f(HTMLElement.prototype, pa) : fa && f(Element.prototype, fa); 681 | Z(a, Element.prototype, { 682 | prepend: ha, 683 | append: ia 684 | }); 685 | Da(a) 686 | }; 687 | var Fa = {}; 688 | 689 | function Ga(a) { 690 | function b() { 691 | var d = this.constructor; 692 | var f = document.__CE_registry.C.get(d); 693 | if (!f) throw Error("Failed to construct a custom element: The constructor was not registered with `customElements`."); 694 | var c = f.constructionStack; 695 | if (0 === c.length) return c = n.call(document, f.localName), Object.setPrototypeOf(c, d.prototype), c.__CE_state = 1, c.__CE_definition = f, R(a, c), c; 696 | var e = c.length - 1, 697 | g = c[e]; 698 | if (g === Fa) throw Error("Failed to construct '" + f.localName + "': This element was already constructed."); 699 | c[e] = Fa; 700 | Object.setPrototypeOf(g, d.prototype); 701 | R(a, g); 702 | return g 703 | } 704 | b.prototype = na.prototype; 705 | Object.defineProperty(HTMLElement.prototype, "constructor", { 706 | writable: !0, 707 | configurable: !0, 708 | enumerable: !1, 709 | value: b 710 | }); 711 | window.HTMLElement = b 712 | }; 713 | 714 | function Ha(a) { 715 | function b(d, f) { 716 | Object.defineProperty(d, "textContent", { 717 | enumerable: f.enumerable, 718 | configurable: !0, 719 | get: f.get, 720 | set: function(c) { 721 | if (this.nodeType === Node.TEXT_NODE) f.set.call(this, c); 722 | else { 723 | var e = void 0; 724 | if (this.firstChild) { 725 | var g = this.childNodes, 726 | h = g.length; 727 | if (0 < h && J(this)) { 728 | e = Array(h); 729 | for (var k = 0; k < h; k++) e[k] = g[k] 730 | } 731 | } 732 | f.set.call(this, c); 733 | if (e) 734 | for (c = 0; c < e.length; c++) U(a, e[c]) 735 | } 736 | } 737 | }) 738 | } 739 | Node.prototype.insertBefore = function(d, f) { 740 | if (d instanceof DocumentFragment) { 741 | var c = K(d); 742 | d = t.call(this, d, f); 743 | if (J(this)) 744 | for (f = 745 | 0; f < c.length; f++) S(a, c[f]); 746 | return d 747 | } 748 | c = d instanceof Element && J(d); 749 | f = t.call(this, d, f); 750 | c && U(a, d); 751 | J(this) && S(a, d); 752 | return f 753 | }; 754 | Node.prototype.appendChild = function(d) { 755 | if (d instanceof DocumentFragment) { 756 | var f = K(d); 757 | d = r.call(this, d); 758 | if (J(this)) 759 | for (var c = 0; c < f.length; c++) S(a, f[c]); 760 | return d 761 | } 762 | f = d instanceof Element && J(d); 763 | c = r.call(this, d); 764 | f && U(a, d); 765 | J(this) && S(a, d); 766 | return c 767 | }; 768 | Node.prototype.cloneNode = function(d) { 769 | d = q.call(this, !!d); 770 | this.ownerDocument.__CE_registry ? V(a, d) : Q(a, d); 771 | return d 772 | }; 773 | Node.prototype.removeChild = function(d) { 774 | var f = 775 | d instanceof Element && J(d), 776 | c = u.call(this, d); 777 | f && U(a, d); 778 | return c 779 | }; 780 | Node.prototype.replaceChild = function(d, f) { 781 | if (d instanceof DocumentFragment) { 782 | var c = K(d); 783 | d = v.call(this, d, f); 784 | if (J(this)) 785 | for (U(a, f), f = 0; f < c.length; f++) S(a, c[f]); 786 | return d 787 | } 788 | c = d instanceof Element && J(d); 789 | var e = v.call(this, d, f), 790 | g = J(this); 791 | g && U(a, f); 792 | c && U(a, d); 793 | g && S(a, d); 794 | return e 795 | }; 796 | w && w.get ? b(Node.prototype, w) : ta(a, function(d) { 797 | b(d, { 798 | enumerable: !0, 799 | configurable: !0, 800 | get: function() { 801 | for (var f = [], c = this.firstChild; c; c = c.nextSibling) c.nodeType !== Node.COMMENT_NODE && 802 | f.push(c.textContent); 803 | return f.join("") 804 | }, 805 | set: function(f) { 806 | for (; this.firstChild;) u.call(this, this.firstChild); 807 | null != f && "" !== f && r.call(this, document.createTextNode(f)) 808 | } 809 | }) 810 | }) 811 | }; 812 | var O = window.customElements; 813 | 814 | function Ia() { 815 | var a = new N; 816 | Ga(a); 817 | Ca(a); 818 | Z(a, DocumentFragment.prototype, { 819 | prepend: da, 820 | append: ea 821 | }); 822 | Ha(a); 823 | Ea(a); 824 | window.CustomElementRegistry = Y; 825 | a = new Y(a); 826 | document.__CE_registry = a; 827 | Object.defineProperty(window, "customElements", { 828 | configurable: !0, 829 | enumerable: !0, 830 | value: a 831 | }) 832 | } 833 | O && !O.forcePolyfill && "function" == typeof O.define && "function" == typeof O.get || Ia(); 834 | window.__CE_installPolyfill = Ia; 835 | }).call(self); 836 | 837 | //# sourceMappingURL=custom-elements.min.js.map -------------------------------------------------------------------------------- /inject/elements.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | { 4 | if (customElements.get('ocr-container') === undefined) { 5 | // This document requires 'TrustedHTML' assignment 6 | self.trustedTypes?.createPolicy('default', { 7 | createHTML(s) { 8 | return s; 9 | } 10 | }); 11 | 12 | class OCRContainer extends HTMLElement { 13 | constructor() { 14 | super(); 15 | 16 | const shadow = this.attachShadow({ mode: 'open' }); 17 | shadow.innerHTML = ` 18 | 39 |
40 | 41 |
42 | `; 43 | } 44 | } 45 | customElements.define('ocr-container', OCRContainer); 46 | } 47 | 48 | if (customElements.get('ocr-result') === undefined) { 49 | class OCRResult extends HTMLElement { 50 | constructor() { 51 | super(); 52 | 53 | this.prefs = { 54 | 'post-method': 'POST', 55 | 'post-href': '', 56 | 'post-body': '', 57 | 'lang': 'eng', 58 | 'frequently-used': ['eng', 'fra', 'deu', 'rus', 'ara'], 59 | 60 | 'example': 'NA', 61 | 'href': 'NA' 62 | }; 63 | 64 | this.locales = { 65 | post: `Post/GET/PUT the result to a server. 66 | 67 | Use Shift + Click to change the server data`, 68 | close: `Close this result. 69 | 70 | Use Shift + Click to close all results. 71 | Use Ctrl + Click or Command + Click to remove local language training data`, 72 | tutorial: `Where do you want the data to get posted: 73 | Server Example: 74 | &page; 75 | 76 | Post Example: 77 | POST|http://127.0.0.1:8080|&content; 78 | POST|http://127.0.0.1:8080|{"body":"&content;"} 79 | 80 | Put Example: 81 | PUT|http://127.0.0.1:8080|&content; 82 | 83 | Get Example: 84 | GET|http://127.0.0.1:8080?data=&content;| 85 | 86 | Open in Browser Tab Example: 87 | OPEN|http://127.0.0.1:8080?data=&content;|` 88 | }; 89 | 90 | this.locales = { 91 | post: '', 92 | close: '', 93 | tutorial: '' 94 | } 95 | 96 | const shadow = this.attachShadow({ mode: 'open' }); 97 | shadow.innerHTML = ` 98 | 408 | 409 |
410 |
411 | 412 |
413 | 423 | 429 |
430 |
431 | 432 | 433 | AI Snipping Tool 434 | 435 |
436 |
437 | 438 | 439 | 440 | 441 |
442 | 443 | 444 |
445 |
446 | 453 |
454 | 455 | 456 |
457 |
458 |
459 |
460 | 461 | 462 |
463 | 472 | 473 |
474 | 475 | 476 |
477 |
478 |
479 |
480 | 591 | 592 |
593 |
594 | 595 | 596 |
597 |
598 | 599 |
600 |
602 |
603 | 604 | 605 | 606 |
607 | 608 | 609 | 610 | 611 | `; 612 | this.events = {}; 613 | } 614 | /* io */ 615 | configure(prefs, report = false) { 616 | Object.assign(this.prefs, prefs); 617 | if (report) { 618 | this.dispatchEvent(new CustomEvent('save-preference', { 619 | detail: prefs 620 | })); 621 | } 622 | } 623 | 624 | prepare() { 625 | 626 | for (const lang of this.prefs['frequently-used']) { 627 | const e = this.shadowRoot.querySelector(`option[value="${lang}"]`).cloneNode(true); 628 | this.shadowRoot.getElementById('frequently-used').appendChild(e); 629 | } 630 | 631 | this.language(this.prefs.lang); 632 | 633 | this.accuracy(this.prefs.accuracy); 634 | } 635 | build(html) { 636 | const parser = new DOMParser(); 637 | const doc = parser.parseFromString(html, 'text/html'); 638 | this.clear(); 639 | 640 | for (const child of [...doc.body.childNodes]) { 641 | this.shadowRoot.getElementById('result').append(child); 642 | } 643 | 644 | // automatically copy to clipboard 645 | setTimeout(async () => { 646 | try { 647 | await navigator.clipboard.writeText(this.result); 648 | } 649 | catch (e) { 650 | const input = document.createElement('textarea'); 651 | input.value = this.result; 652 | input.style.position = 'absolute'; 653 | input.style.left = '-9999px'; 654 | document.body.append(input); 655 | input.select(); 656 | document.execCommand('copy'); 657 | input.remove(); 658 | } 659 | 660 | }, 10); 661 | } 662 | message(value) { 663 | this.shadowRoot.getElementById().dataset.msg = value; 664 | } 665 | progress(value, type = 'recognize') { 666 | this.shadowRoot.getElementById(type).value = value; 667 | } 668 | rename(value) { 669 | this.shadowRoot.querySelector('option[value=detect]').textContent = value; 670 | } 671 | clear() { 672 | this.shadowRoot.getElementById('result').removeAttribute('contenteditable'); 673 | this.shadowRoot.getElementById('result').textContent = ''; 674 | this.shadowRoot.getElementById('result').style.display = 'none'; 675 | this.shadowRoot.getElementById('result-in-process').style.display = 'flex'; 676 | } 677 | enable() { 678 | this.shadowRoot.getElementById('copy').disabled = false; 679 | this.shadowRoot.getElementById('post').disabled = false; 680 | this.shadowRoot.getElementById('result').setAttribute('contenteditable', true); 681 | this.shadowRoot.getElementById('result').style.display = 'block'; 682 | this.shadowRoot.getElementById('result-in-process').style.display = 'none'; 683 | } 684 | get result() { 685 | return this.shadowRoot.getElementById('result').innerText; 686 | } 687 | language(value) { 688 | this.dataset.language = value; 689 | this.shadowRoot.getElementById('language').value = value; 690 | } 691 | accuracy(value) { 692 | this.dataset.accuracy = value; 693 | this.shadowRoot.getElementById('accuracy').value = value; 694 | } 695 | toast(name, messages, timeout = 2000) { 696 | const elm = this.shadowRoot.getElementById(name); 697 | elm.value = messages.new; 698 | if (elm.tagName === 'BUTTON') { 699 | elm.innerHTML = messages.new; 700 | } 701 | clearTimeout(this[name + 'ID']); 702 | this[name + 'ID'] = setTimeout(() => { 703 | elm.value = messages.old; 704 | if (elm.tagName === 'BUTTON') { 705 | elm.innerHTML = messages.old; 706 | } 707 | }, timeout); 708 | } 709 | connectedCallback() { 710 | // Preview screenshot 711 | this.shadowRoot.getElementById('preview-screenshot').onclick = e => { 712 | chrome.storage.local.get('ocr-screenshot', function(result) { 713 | const url = result['ocr-screenshot']; 714 | if (url) { 715 | const screenWidth = window.screen.width; 716 | const screenHeight = window.screen.height; 717 | const windowWidth = Math.max(400, Math.min(600, screenWidth * 0.5)); 718 | const windowHeight = Math.max(300, Math.min(450, screenHeight * 0.5)); 719 | const left = (screenWidth - windowWidth) / 2; 720 | const top = (screenHeight - windowHeight) / 2; 721 | 722 | const previewWindow = window.open("", "Screenshot Preview", 723 | `width=${windowWidth},height=${windowHeight},left=${left},top=${top},resizable=yes,scrollbars=yes`); 724 | 725 | previewWindow.document.write(` 726 | 727 | 728 | Screenshot Preview 729 | 767 | 768 | 769 |
770 | Captured Screenshot 771 |
772 |
773 | 774 | 775 | 776 | 777 |
778 | 779 | 780 | 781 | `); 782 | } else { 783 | alert("No screenshot available to preview."); 784 | } 785 | }); 786 | }; 787 | 788 | // Add dark mode toggle logic 789 | this.shadowRoot.getElementById('toggle-dark-mode').onclick = () => { 790 | const isDarkMode = this.hasAttribute('dark-mode'); 791 | if (isDarkMode) { 792 | this.removeAttribute('dark-mode'); 793 | chrome.storage.local.remove('dark-mode'); 794 | } else { 795 | this.setAttribute('dark-mode', ''); 796 | chrome.storage.local.set({ 'dark-mode': 'true' }); 797 | } 798 | }; 799 | 800 | // Apply saved dark mode preference 801 | chrome.storage.local.get('dark-mode', function(result) { 802 | if (result['dark-mode'] === 'true') { 803 | this.setAttribute('dark-mode', ''); 804 | } 805 | }.bind(this)); 806 | 807 | // Enter saved API Key in the API Key input field. 808 | chrome.storage.local.get('gemini-api-key', function(result) { 809 | const API_KEY = result['gemini-api-key']; 810 | if (API_KEY !== undefined) { 811 | // Check if API_KEY is not undefined 812 | const apiKeyField = this.shadowRoot.querySelector('#key'); 813 | apiKeyField.value = API_KEY; 814 | } 815 | }.bind(this)); 816 | 817 | // copy 818 | this.shadowRoot.getElementById('copy').onclick = async () => { 819 | try { 820 | await navigator.clipboard.writeText(this.result); 821 | } 822 | catch (e) { 823 | const input = document.createElement('textarea'); 824 | input.value = this.result; 825 | input.style.position = 'absolute'; 826 | input.style.left = '-9999px'; 827 | document.body.append(input); 828 | input.select(); 829 | document.execCommand('copy'); 830 | input.remove(); 831 | } 832 | this.toast('copy', { 833 | new: 'Done', 834 | old: 'Copy Text' 835 | }); 836 | }; 837 | // post 838 | this.shadowRoot.getElementById('post').onclick = e => { 839 | if (this.prefs['post-href'] === '' || e.shiftKey) { 840 | const message = this.locales.tutorial.replace('&page;', this.dataset.page); 841 | const m = prompt( 842 | message, 843 | [this.prefs['post-method'], this.prefs['post-href'], this.prefs['post-body']].join('|') 844 | ); 845 | const [method, href, body] = (m || '').split('|'); 846 | 847 | const prefs = { 848 | 'post-method': (method || 'POST').toUpperCase(), 849 | 'post-href': href || '', 850 | 'post-body': body || '' 851 | }; 852 | this.configure(prefs, true); 853 | } 854 | 855 | const value = this.result.trim(); 856 | const options = { 857 | method: this.prefs['post-method'], 858 | mode: 'no-cors' 859 | }; 860 | if (this.prefs['post-body'] && this.prefs['post-method'] !== 'GET') { 861 | options.body = this.prefs['post-body'] 862 | .replaceAll('&content;', value) 863 | .replaceAll('&href;', location.href); 864 | // If this is a JSON, try builder 865 | if (this.prefs['post-body'].startsWith('{') && this.prefs['post-body'].endsWith('}')) { 866 | try { 867 | const o = JSON.parse(this.prefs['post-body']); 868 | for (const [key, holder] of Object.entries(o)) { 869 | if (typeof holder === 'string') { 870 | o[key] = holder 871 | .replaceAll('&content;', value) 872 | .replaceAll('&href;', location.href); 873 | } 874 | } 875 | options.body = JSON.stringify(o); 876 | } 877 | catch (e) { 878 | console.warn('Cannot use the JSON Builder', e); 879 | } 880 | } 881 | } 882 | 883 | const t = (msg, timeout = 3000) => this.toast('post', { 884 | new: msg, 885 | old: 'Post Result' 886 | }, timeout); 887 | 888 | if (this.prefs['post-href'] === '') { 889 | return t('Empty Server'); 890 | } 891 | 892 | t('...', 1000000); 893 | 894 | const href = this.prefs['post-href'] 895 | .replaceAll('&content;', encodeURIComponent(value)) 896 | .replaceAll('&href;', encodeURIComponent(location.href)); 897 | 898 | if (options.method === 'OPEN') { 899 | this.dispatchEvent(new CustomEvent('open-link', { 900 | detail: href 901 | })); 902 | 903 | t('Done'); 904 | } 905 | else { 906 | this.dispatchEvent(new CustomEvent('fetch-resource', { 907 | detail: { 908 | href, options 909 | } 910 | })); 911 | } 912 | }; 913 | // change language 914 | this.shadowRoot.getElementById('language').onchange = e => { 915 | this.language(e.target.value); 916 | const prefs = { 917 | 'lang': e.target.value, 918 | 'frequently-used': this.prefs['frequently-used'] 919 | }; 920 | prefs['frequently-used'].unshift(prefs.lang); 921 | prefs['frequently-used'] = prefs['frequently-used'].filter((s, i, l) => s && l.indexOf(s) === i).slice(0, 10); 922 | this.configure(prefs, true); 923 | this.dispatchEvent(new Event('language-changed')); 924 | }; 925 | // change accuracy 926 | this.shadowRoot.getElementById('accuracy').onchange = e => { 927 | this.accuracy(e.target.value); 928 | const prefs = { 929 | 'accuracy': e.target.value 930 | }; 931 | this.configure(prefs, true); 932 | this.dispatchEvent(new Event('accuracy-changed')); 933 | }; 934 | // get answer 935 | this.shadowRoot.getElementById('answer').onclick = e => { 936 | const ocr_text = this.shadowRoot.getElementById('result').innerText; 937 | const API_KEY = this.shadowRoot.getElementById('key').value; 938 | const prompt = this.shadowRoot.getElementById('prompt').value; 939 | const question = `Prompt-"${prompt}". Context-"${ocr_text}"`; 940 | 941 | // show spinner and hide response 942 | this.shadowRoot.getElementById('prompt-response-spinner').style.display = 'block'; 943 | this.shadowRoot.getElementById('summary-area').style.display = 'none'; 944 | this.shadowRoot.getElementById('answer-heading').style.display = 'none'; 945 | 946 | fetch(`https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent?key=${API_KEY}`, { 947 | method: 'POST', 948 | headers: { 949 | 'Content-Type': 'application/json' 950 | }, 951 | body: JSON.stringify({ 952 | "contents": [ 953 | { "parts": [{ "text": question }] } 954 | ] 955 | }) 956 | }) 957 | .then(response => response.json()) 958 | .then(data => { 959 | const generatedSummary = data.candidates[0].content.parts[0].text; 960 | const summaryArea = this.shadowRoot.getElementById('summary-area'); 961 | summaryArea.textContent = generatedSummary; 962 | 963 | // hide spinner and show response 964 | this.shadowRoot.getElementById('prompt-response-spinner').style.display = 'none'; 965 | summaryArea.style.display = 'block'; 966 | this.shadowRoot.getElementById('answer-heading').style.display = 'block'; 967 | }) 968 | .catch(error => { 969 | console.error('Error:', error); 970 | // hide spinner in case of error 971 | this.shadowRoot.getElementById('prompt-response-spinner').style.display = 'none'; 972 | }); 973 | }; 974 | 975 | // Save API Key 976 | this.shadowRoot.getElementById('save-key').onclick = e => { 977 | var API_KEY = this.shadowRoot.getElementById('key').value; 978 | chrome.storage.local.set({ 'gemini-api-key': API_KEY }, function() { 979 | console.log(API_KEY); 980 | }); 981 | }; 982 | 983 | 984 | // Save text 985 | this.shadowRoot.getElementById('save-text').onclick = e => { 986 | const textToSave = this.shadowRoot.getElementById('result').innerText; 987 | 988 | // Create a blob for the OCR text 989 | const blob = new Blob([textToSave], { type: 'text/plain' }); 990 | 991 | // Create a link element 992 | const link = document.createElement('a'); 993 | link.download = 'ocr-result.txt'; 994 | 995 | // Create object URL for the blob 996 | link.href = window.URL.createObjectURL(blob); 997 | 998 | // Append the link to the Shadow DOM and click it 999 | this.shadowRoot.appendChild(link); 1000 | link.click(); 1001 | 1002 | // Remove the link from the Shadow DOM 1003 | this.shadowRoot.removeChild(link); 1004 | }; 1005 | 1006 | // Save screenshot 1007 | this.shadowRoot.getElementById('save-screenshot').onclick = e => { 1008 | chrome.storage.local.get('ocr-screenshot', function(result) { 1009 | const url = result['ocr-screenshot']; 1010 | 1011 | // Create a link element 1012 | const a = document.createElement('a'); 1013 | a.href = url; 1014 | a.download = 'screenshot.png'; 1015 | 1016 | // Append the link to the Shadow DOM and click it 1017 | this.shadowRoot.appendChild(a); 1018 | a.click(); 1019 | 1020 | // Remove the link from the Shadow DOM 1021 | this.shadowRoot.removeChild(a); 1022 | }.bind(this)); 1023 | }; 1024 | 1025 | 1026 | // close 1027 | this.shadowRoot.getElementById('close').onclick = e => { 1028 | this.remove(); 1029 | this.dispatchEvent(new MouseEvent('closed', { 1030 | shiftKey: e.shiftKey, 1031 | ctrlKey: e.ctrlKey, 1032 | metaKey: e.metaKey 1033 | })); 1034 | }; 1035 | 1036 | // apply commands on cross-origin (Firefox Only) 1037 | this.addEventListener('command', () => { 1038 | const { name, args } = JSON.parse(this.getAttribute('command')); 1039 | this[name](...args); 1040 | }); 1041 | // constants 1042 | this.dataset.languages = [...this.shadowRoot.querySelectorAll('#language option')] 1043 | .map(e => e.value) 1044 | .filter(s => s !== 'detect') 1045 | .join(', '); 1046 | } 1047 | } 1048 | customElements.define('ocr-result', OCRResult); 1049 | } 1050 | } 1051 | 1052 | -------------------------------------------------------------------------------- /inject/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /inject/inject.css: -------------------------------------------------------------------------------- 1 | .itrisearch-box, 2 | .itrisearch-guide-1, 3 | .itrisearch-guide-2, 4 | .itrisearch-guide-3 { 5 | all: initial; 6 | } 7 | 8 | .itrisearch-box { 9 | box-sizing: border-box; 10 | position: fixed; 11 | z-index: 2147483646; 12 | border: gray 1px dotted; 13 | box-shadow: 0 0 0 50000px rgba(0, 0, 0, 0.2); 14 | } 15 | .itrisearch-box::before { 16 | content: ''; 17 | display: block; 18 | width: calc(100% + 20px); 19 | height: calc(100% + 20px); 20 | margin-left: -10px; 21 | margin-top: -10px; 22 | cursor: crosshair; 23 | } 24 | 25 | .itrisearch-guide-1, 26 | .itrisearch-guide-2 { 27 | box-sizing: border-box; 28 | position: fixed; 29 | z-index: 2147483646; 30 | } 31 | .itrisearch-guide-1 { 32 | border-right: dotted 2px #ee14cd; 33 | top: 0; 34 | left: 0; 35 | height: 100%; 36 | } 37 | .itrisearch-guide-2 { 38 | border-bottom: dotted 2px #0c1ae5; 39 | top: 0; 40 | left: 0; 41 | width: 100%; 42 | } 43 | .itrisearch-guide-3 { 44 | z-index: 2147483645; 45 | position: fixed; 46 | top: 0; 47 | left: 0; 48 | width: 100%; 49 | height: 100%; 50 | } 51 | 52 | .test { 53 | background-color: rgb(88, 105, 129); 54 | /* input */ 55 | background-color: rgb(45, 212, 215); 56 | border-color: rgb(75, 85, 99); 57 | } 58 | -------------------------------------------------------------------------------- /inject/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var monitor = window.monitor; 4 | var capture = window.capture; 5 | var guide = window.guide; 6 | 7 | try { 8 | guide.remove(); 9 | capture.remove(); 10 | monitor.remove(); 11 | } 12 | catch (e) {} 13 | 14 | capture = (function() { 15 | const rect = {}; 16 | let box; 17 | 18 | const calc = () => ({ 19 | left: Math.min(rect.lt.x, rect.rb.x), 20 | top: Math.min(rect.lt.y, rect.rb.y), 21 | width: Math.abs(rect.rb.x - rect.lt.x), 22 | height: Math.abs(rect.rb.y - rect.lt.y) 23 | }); 24 | 25 | function update(e) { 26 | rect.rb.x = e.clientX; 27 | rect.rb.y = e.clientY; 28 | 29 | for (const [key, value] of Object.entries(calc())) { 30 | box.style[key] = value + 'px'; 31 | } 32 | } 33 | function remove() { 34 | chrome.runtime.sendMessage({ 35 | method: 'captured', 36 | ...calc(), 37 | devicePixelRatio: window.devicePixelRatio, 38 | title: document.title, 39 | service: window.service // used by Reverse Image Search extension 40 | }); 41 | guide.remove(); 42 | capture.remove(); 43 | monitor.remove(); 44 | } 45 | function mousedown(e) { 46 | // prevent content selection on Firefox 47 | e.stopPropagation(); 48 | e.preventDefault(); 49 | box = document.createElement('div'); 50 | box.setAttribute('class', 'itrisearch-box'); 51 | 52 | rect.lt = { 53 | x: e.clientX, 54 | y: e.clientY 55 | }; 56 | rect.rb = { 57 | x: e.clientX, 58 | y: e.clientY 59 | }; 60 | 61 | document.addEventListener('mousemove', update); 62 | document.addEventListener('mouseup', remove); 63 | document.documentElement.appendChild(box); 64 | } 65 | 66 | return { 67 | install: function() { 68 | document.addEventListener('mousedown', mousedown); 69 | }, 70 | remove: function() { 71 | document.removeEventListener('mousedown', mousedown); 72 | document.removeEventListener('mousemove', update); 73 | document.removeEventListener('mouseup', remove); 74 | for (const e of [...document.querySelectorAll('.itrisearch-box')]) { 75 | e.remove(); 76 | } 77 | } 78 | }; 79 | })(); 80 | 81 | guide = (function() { 82 | let guide1; 83 | let guide2; 84 | let guide3; 85 | function position(left, top) { 86 | guide1.style.width = left + 'px'; 87 | guide2.style.height = top + 'px'; 88 | } 89 | function update(e) { 90 | position(e.clientX, e.clientY); 91 | } 92 | return { 93 | install() { 94 | guide1 = document.createElement('div'); 95 | guide2 = document.createElement('div'); 96 | guide3 = document.createElement('div'); 97 | guide1.setAttribute('class', 'itrisearch-guide-1'); 98 | guide2.setAttribute('class', 'itrisearch-guide-2'); 99 | guide3.setAttribute('class', 'itrisearch-guide-3'); 100 | document.documentElement.append(guide3, guide1, guide2); 101 | document.addEventListener('mousemove', update, false); 102 | }, 103 | remove() { 104 | document.removeEventListener('mousemove', update, false); 105 | for (const e of [...document.querySelectorAll('.itrisearch-guide-1, .itrisearch-guide-2, .itrisearch-guide-3')]) { 106 | e.remove(); 107 | } 108 | capture.remove(); 109 | } 110 | }; 111 | })(); 112 | 113 | monitor = (function() { 114 | const keydown = e => { 115 | if (e.code === 'Escape') { 116 | guide.remove(); 117 | capture.remove(); 118 | monitor.remove(); 119 | chrome.runtime.sendMessage({ 120 | method: 'aborted' 121 | }); 122 | } 123 | }; 124 | return { 125 | install() { 126 | window.addEventListener('keydown', keydown); 127 | }, 128 | remove() { 129 | window.removeEventListener('keydown', keydown); 130 | } 131 | }; 132 | })(); 133 | 134 | guide.install(); 135 | capture.install(); 136 | monitor.install(); 137 | -------------------------------------------------------------------------------- /inject/response.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | // container 5 | const container = document.querySelector('ocr-container') || document.createElement('ocr-container'); 6 | if (container.isConnected === false) { 7 | document.body.append(container); 8 | } 9 | 10 | const em = document.createElement('ocr-result'); 11 | em.dataset.page = chrome.runtime.getManifest().homepage_url + '#faq8'; 12 | container.append(em); 13 | 14 | const command = em.command = /Firefox/.test(navigator.userAgent) ? (name, ...args) => { 15 | em.setAttribute('command', JSON.stringify({name, args})); 16 | em.dispatchEvent(new Event('command')); 17 | } : (name, ...args) => em[name](...args); 18 | 19 | const ocr = (lang, src) => { 20 | // store screenshot url in local storage 21 | chrome.storage.local.set({ 'ocr-screenshot': src }); 22 | 23 | 24 | const report = report => { 25 | command('message', report.status); 26 | 27 | if (report.status === 'recognizing text') { 28 | command('progress', report.progress); 29 | } 30 | else if ( 31 | report.status === 'loading language traineddata' || 32 | report.status === 'loaded language traineddata' 33 | ) { 34 | command('progress', report.progress, 'lang'); 35 | } 36 | }; 37 | const controller = new AbortController(); 38 | em.addEventListener('closed', () => controller.abort()); 39 | return self.execute({ 40 | lang, 41 | src, 42 | accuracy: em.dataset.accuracy, 43 | signal: controller.signal 44 | }, report); 45 | }; 46 | 47 | // if there is oResult object, run in inverted colors 48 | const run = em.run = async (oResult, mode = 'normal') => { 49 | const src = await self.crop(em.href, em.box, mode); 50 | 51 | command('progress', 0); 52 | command('clear'); 53 | 54 | const lang = oResult?.lang || em.dataset.language || 'eng'; 55 | 56 | let o; 57 | try { 58 | if (lang !== 'detect') { 59 | o = await ocr(lang, src); 60 | } 61 | else { 62 | await Promise.all([ 63 | ocr('eng', src).then(o => { 64 | return new Promise(resolve => chrome.i18n.detectLanguage(o.text, r => { 65 | resolve({ 66 | lang: 'eng', 67 | o, 68 | r 69 | }); 70 | })); 71 | }), 72 | ocr('ara', src).then(o => { 73 | return new Promise(resolve => chrome.i18n.detectLanguage(o.text, r => { 74 | resolve({ 75 | lang: 'ara', 76 | o, 77 | r 78 | }); 79 | })); 80 | }), 81 | ocr('jpn', src).then(o => { 82 | return new Promise(resolve => chrome.i18n.detectLanguage(o.text, r => { 83 | resolve({ 84 | lang: 'jpn', 85 | o, 86 | r 87 | }); 88 | })); 89 | }) 90 | ]).then(async a => { 91 | const r = a.sort((a, b) => { 92 | return b.o.confidence - a.o.confidence; 93 | })[0]; 94 | if (r.r.languages.length) { 95 | const ln = r.r.languages[0].language; 96 | 97 | const e = r.lang.startsWith(ln) ? r.lang : em.dataset.languages.split(', ').filter(s => s.startsWith(ln)).shift(); 98 | 99 | if (e) { 100 | if (a.some(o => o.lang === e)) { 101 | o = a.filter(o => o.lang === e).shift().o; 102 | } 103 | else { 104 | o = await ocr(e, src); 105 | } 106 | o.lang = e; 107 | } 108 | command('rename', `Auto Detect (${e || r.lang})`); 109 | command('message', `Detected language is "${e || r.lang}". Please wait...`); 110 | } 111 | o = o || r.o; 112 | }); 113 | } 114 | command('progress', 1); 115 | 116 | // in case "confidence" is not acceptable, rerun with inverted colors 117 | if (o.confidence < 50 && !oResult) { 118 | command('message', `Low confidence (${o.confidence}%). Trying with inverted colors. Please wait...`); 119 | await new Promise(resolve => setTimeout(resolve, 2000)); 120 | run(o, 'invert'); 121 | return; 122 | } 123 | if (mode === 'invert' && o.confidence < 20 && oResult.confidence < 20) { 124 | command('message', `Low confidence (${o.confidence}%) again! Trying with inverted colors. Please wait...`); 125 | 126 | run(o, 'gray'); 127 | return; 128 | } 129 | if (oResult) { 130 | if (oResult.confidence > o.confidence) { 131 | o = oResult; 132 | } 133 | } 134 | 135 | if (o.text.trim() === '') { 136 | command('build', 'No text was detected! Edit the image and drop it here to retry!'); 137 | } 138 | else { 139 | command('build', o.hocr); 140 | command('enable'); 141 | } 142 | } 143 | catch (e) { 144 | console.warn(e); 145 | command('message', e.message || e); 146 | } 147 | }; 148 | 149 | // events 150 | em.addEventListener('open-link', e => chrome.runtime.sendMessage({ 151 | method: 'open-link', 152 | href: e.detail 153 | })); 154 | em.addEventListener('fetch-resource', async e => { 155 | const {href, options} = e.detail; 156 | const t = (msg, timeout = 3000) => command('toast', 'post', { 157 | new: msg, 158 | old: 'Post Result' 159 | }, timeout); 160 | 161 | try { 162 | const r = await fetch(href, options); 163 | if (r.ok || r.status === 0) { 164 | t('Done'); 165 | } 166 | else { 167 | throw Error('Error ' + r.status); 168 | } 169 | } 170 | catch (e) { 171 | console.warn(e); 172 | t(e.message); 173 | } 174 | }); 175 | em.addEventListener('language-changed', () => run()); 176 | em.addEventListener('accuracy-changed', () => { 177 | chrome.runtime.sendMessage({ 178 | method: 'remove-indexeddb' 179 | }, run); 180 | }); 181 | em.addEventListener('save-preference', e => { 182 | chrome.storage.local.set(e.detail); 183 | }); 184 | em.addEventListener('closed', e => { 185 | if (e.metaKey || e.ctrlKey) { 186 | chrome.runtime.sendMessage({ 187 | method: 'remove-indexeddb' 188 | }); 189 | } 190 | if (e.shiftKey) { 191 | for (const e of document.querySelectorAll('ocr-result')) { 192 | e.remove(); 193 | } 194 | } 195 | if (!document.querySelector('ocr-result')) { 196 | container.remove(); 197 | } 198 | }); 199 | 200 | // Drag and drop support 201 | em.addEventListener('dragover', e => { 202 | const types = [...e.dataTransfer.items].map(e => e.type); 203 | if (types.some(s => s.startsWith('image/'))) { 204 | e.preventDefault(); 205 | } 206 | }); 207 | em.addEventListener('drop', e => { 208 | const entry = [...e.dataTransfer.files].filter(e => e.type && e.type.startsWith('image/')).shift(); 209 | if (entry) { 210 | e.preventDefault(); 211 | const reader = new FileReader(); 212 | reader.onload = () => { 213 | em.href = reader.result; 214 | em.box = { 215 | left: 0, 216 | top: 0, 217 | width: 0, 218 | height: 0 219 | }; 220 | em.run(); 221 | }; 222 | reader.readAsDataURL(entry); 223 | } 224 | }); 225 | } 226 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "update_url": "https://clients2.google.com/service/update2/crx", 3 | 4 | "manifest_version": 3, 5 | "version": "1.0.0", 6 | "name": "AI Snipping Tool", 7 | "description": "capture the screenshot and extract text from the image", 8 | "default_locale": "en", 9 | "permissions": [ 10 | "storage", 11 | "scripting", 12 | "contextMenus" 13 | ], 14 | "host_permissions": [ 15 | "" 16 | ], 17 | "background": { 18 | "service_worker": "worker.js" 19 | }, 20 | "icons": { 21 | 22 | "48": "icons/logo.png" 23 | 24 | }, 25 | "action": {}, 26 | "content_security_policy": { 27 | "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'" 28 | }, 29 | "web_accessible_resources": [{ 30 | "resources": ["/engine/index.html", "/engine/preview-controls.js"], 31 | "matches": [""] 32 | }] 33 | 34 | } 35 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // const notify = e => chrome.notifications.create({ 4 | // type: 'basic', 5 | // iconUrl: '/icons/icon-48.png', 6 | // title: chrome.runtime.getManifest().name, 7 | // message: e.message || e 8 | // }); 9 | 10 | chrome.action.onClicked.addListener(async tab => { 11 | try { 12 | await chrome.scripting.insertCSS({ 13 | target: { 14 | tabId: tab.id 15 | }, 16 | files: ['inject/inject.css'] 17 | }); 18 | await chrome.scripting.executeScript({ 19 | target: { 20 | tabId: tab.id 21 | }, 22 | files: ['inject/inject.js'] 23 | }); 24 | chrome.action.setIcon({ 25 | tabId: tab.id, 26 | path: { 27 | 28 | '48': 'icons/logo.png' 29 | } 30 | }); 31 | } 32 | catch (e) { 33 | console.error(e); 34 | // notify(e); 35 | } 36 | }); 37 | 38 | chrome.runtime.onMessage.addListener((request, sender, response) => { 39 | if (request.method === 'captured' || request.method === 'aborted') { 40 | chrome.action.setIcon({ 41 | tabId: sender.tab.id, 42 | path: { 43 | 44 | '48': 'icons/logo.png' 45 | } 46 | }); 47 | } 48 | // 49 | if (request.method === 'captured') { 50 | const { devicePixelRatio, left, top, width, height } = request; 51 | 52 | if (!width || !height) { 53 | return; 54 | // return notify('Please select a region. Either width or height of the captured area was zero'); 55 | } 56 | chrome.tabs.captureVisibleTab(sender.tab.windowId, { 57 | format: 'png' 58 | }, async href => { 59 | try { 60 | const target = { 61 | tabId: sender.tab.id 62 | }; 63 | if (/Firefox/.test(navigator.userAgent) === false) { 64 | await chrome.scripting.executeScript({ 65 | target, 66 | files: ['/inject/custom-elements.min.js'] 67 | }); 68 | await chrome.scripting.executeScript({ 69 | target, 70 | files: ['/inject/elements.js'] 71 | }); 72 | } 73 | else { 74 | await chrome.scripting.executeScript({ 75 | target, 76 | func: () => { 77 | const s = document.createElement('script'); 78 | s.src = chrome.runtime.getURL('/inject/elements.js'); 79 | document.body.append(s); 80 | } 81 | }); 82 | } 83 | await chrome.scripting.executeScript({ 84 | target, 85 | files: ['/engine/helper.js'] 86 | }); 87 | await chrome.scripting.executeScript({ 88 | target, 89 | files: ['/inject/response.js'] 90 | }); 91 | await chrome.scripting.insertCSS({ 92 | target, 93 | files: ['/inject/font-awesome.min.css'] 94 | }); 95 | // start 96 | chrome.storage.local.get({ 97 | 'post-method': 'POST', 98 | 'post-href': '', 99 | 'post-body': '', 100 | 'lang': 'eng', 101 | 'frequently-used': ['eng', 'fra', 'deu', 'rus', 'ara'], 102 | 'accuracy': '4.0.0' 103 | }, prefs => chrome.scripting.executeScript({ 104 | target, 105 | func: (prefs, href, box) => { 106 | const em = document.querySelector('ocr-result:last-of-type'); 107 | em.command('configure', prefs); 108 | em.command('prepare'); 109 | 110 | em.href = href; 111 | em.box = box; 112 | 113 | em.run(); 114 | }, 115 | args: [prefs, href, { 116 | width: width * devicePixelRatio, 117 | height: height * devicePixelRatio, 118 | left: left * devicePixelRatio, 119 | top: top * devicePixelRatio 120 | }] 121 | })); 122 | } 123 | catch (e) { 124 | console.error(e); 125 | 126 | } 127 | }); 128 | } 129 | else if (request.method === 'open-link') { 130 | chrome.tabs.create({ 131 | url: request.href, 132 | index: sender.tab.index + 1 133 | }); 134 | } 135 | else if (request.method === 'remove-indexeddb') { 136 | caches.delete('traineddata').finally(response); 137 | try { 138 | indexedDB.databases().then(as => { 139 | for (const { name } of as) { 140 | indexedDB.deleteDatabase(name); 141 | } 142 | }); 143 | } 144 | catch (e) { } 145 | 146 | return true; 147 | } 148 | }); 149 | 150 | chrome.runtime.onInstalled.addListener((details) => { 151 | // We no longer use IndexedDB 152 | try { 153 | indexedDB.databases().then(as => { 154 | for (const { name } of as) { 155 | indexedDB.deleteDatabase(name); 156 | } 157 | }); 158 | } 159 | catch (e) { } 160 | 161 | if (details.reason === chrome.runtime.OnInstalledReason.INSTALL) { 162 | 163 | chrome.tabs.create({ 164 | url: "https://github.com/gitgoap/HackFest-24-IIT-Dhanbad", 165 | }); 166 | } else if (details.reason === chrome.runtime.OnInstalledReason.UPDATE) { 167 | 168 | } else if ( 169 | details.reason === chrome.runtime.OnInstalledReason.CHROME_UPDATE 170 | ) { 171 | 172 | } else if ( 173 | details.reason === chrome.runtime.OnInstalledReason.SHARED_MODULE_UPDATE 174 | ) { 175 | 176 | } 177 | chrome.contextMenus.create({ 178 | "id": 'ocr_start', 179 | "title": "screenshot", 180 | "visible": true, 181 | }); 182 | }); 183 | 184 | chrome.contextMenus.onClicked.addListener(async tab => { 185 | 186 | try { 187 | let queryOptions = { active: true, lastFocusedWindow: true }; 188 | let [current_tab] = await chrome.tabs.query(queryOptions); 189 | console.log(current_tab) 190 | 191 | await chrome.scripting.insertCSS({ 192 | target: { 193 | tabId: current_tab.id 194 | }, 195 | files: ['inject/inject.css'] 196 | }); 197 | await chrome.scripting.executeScript({ 198 | target: { 199 | tabId: current_tab.id 200 | }, 201 | files: ['inject/inject.js'] 202 | }); 203 | chrome.action.setIcon({ 204 | tabId: current_tab.id, 205 | path: { 206 | 207 | '48': 'icons/logo.png' 208 | } 209 | }); 210 | } 211 | catch (e) { 212 | console.error(e); 213 | 214 | } 215 | }) 216 | 217 | chrome.runtime.setUninstallURL('https://amanprakash.dev/'); 218 | --------------------------------------------------------------------------------