├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── config ├── _chrome.js ├── _edge.js ├── _firefox.js └── watch.js ├── package-lock.json ├── package.json └── src ├── _locales ├── en │ └── messages.json └── fr │ └── messages.json ├── background.coffee ├── background ├── icon.coffee └── open-pages.coffee ├── browser_action ├── css │ ├── _base.scss │ ├── _search.scss │ ├── _variables.scss │ └── browser_action.scss ├── js │ ├── browser_action.coffee │ ├── domain_search.coffee │ ├── email_finder.coffee │ ├── lead_button.coffee │ └── list_selection.coffee ├── popup.html └── templates │ ├── departments.hbs │ ├── finder.hbs │ └── search_results.hbs ├── content_script ├── css │ └── websites-sources.scss └── js │ ├── hunter-authentication.coffee │ ├── hunter-extension-detection.coffee │ └── websites-source.coffee ├── manifest.json ├── shared ├── css │ ├── fonts.css │ └── lib │ │ ├── bootstrap-tooltip.min.css │ │ ├── bootstrap.min.css │ │ └── fontawesome-all.css ├── fonts │ ├── Inter-Medium.woff2 │ ├── Inter-Regular.woff2 │ ├── Inter-SemiBold.woff2 │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.woff2 │ └── fa-solid-900.woff2 ├── img │ ├── default_lead_icon.png │ ├── icon128.png │ ├── icon16.png │ ├── icon19.png │ ├── icon19_grey.png │ ├── icon38.png │ ├── icon38_grey.png │ ├── icon48.png │ ├── location_icon.png │ ├── orange_transparent_logo.png │ ├── search_grey.png │ └── white_transparent_logo.png └── js │ ├── account.coffee │ ├── analytics.coffee │ ├── api.coffee │ ├── lib │ ├── bootstrap.js │ ├── handlebars.js │ ├── jquery.min.js │ └── purify.min.js │ ├── use-counter.coffee │ └── utilities.coffee └── source_popup ├── css └── source-popup.scss ├── js └── source-popup.coffee └── popup.html /.gitignore: -------------------------------------------------------------------------------- 1 | /build-chrome 2 | build-chrome.zip 3 | /build-firefox 4 | build-firefox.zip 5 | /build-edge 6 | build-edge.zip 7 | Archive.zip 8 | /node_modules 9 | .DS_Store 10 | .sass-cache 11 | yarn.lock 12 | .ruby-version 13 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | // Load the plugins 3 | grunt.loadNpmTasks("grunt-contrib-watch"); 4 | grunt.loadNpmTasks("grunt-contrib-coffee"); 5 | grunt.loadNpmTasks("grunt-contrib-compass"); 6 | grunt.loadNpmTasks("grunt-contrib-cssmin"); 7 | grunt.loadNpmTasks("grunt-contrib-copy"); 8 | grunt.loadNpmTasks("grunt-text-replace"); 9 | grunt.loadNpmTasks("grunt-contrib-handlebars"); 10 | 11 | // Load the various task configuration files 12 | var configs = require("load-grunt-configs")(grunt); 13 | grunt.initConfig(configs); 14 | 15 | grunt.registerTask("editmanifestforfirefox", function() { 16 | var manifest = grunt.file.readJSON("./src/manifest.json"); 17 | 18 | // On Firefox, with Manifest V3, we use "background" files and not service workers 19 | manifest.background.scripts =["js/lib/jquery.min.js", "js/shared.js", "js/background.js"] 20 | delete manifest.background.service_worker; 21 | 22 | // On Firefox, with Manifest V3, we have to sign the extension with our unique ID 23 | manifest.browser_specific_settings = { "gecko": { "id": "firefox@hunter.io" } }; 24 | 25 | grunt.file.write("./build-firefox/manifest.json", JSON.stringify(manifest, null, 2)); 26 | grunt.log.writeln("Manifest updated for Firefox"); 27 | }); 28 | 29 | // Default task 30 | grunt.registerTask("default", ["watch"]); 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Hunter Web Services, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hunter's browser extension 2 | Hunter's browser extension is an easy way to find email addresses from anywhere on the web, with just one click. 3 | 4 | * [Chrome Web Store](https://chrome.google.com/webstore/detail/hunter/hgmhmanijnjhaffoampdlllchpolkdnj) 5 | * [Add-on for Edge](https://microsoftedge.microsoft.com/addons/detail/hunter-email-finder-ext/dmgcgojogkfomifjfeeafajhmgilkofk) 6 | * [Add-on for Firefox](https://addons.mozilla.org/en-US/firefox/addon/hunterio/) 7 | 8 | [Hunter's API](https://hunter.io/api) is used to fetch the data and users' accounts information. 9 | 10 | ## Generate the builds 11 | 12 | The builds are automatically generated with Grunt for each browser. To install it globally, run: 13 | 14 | ```shell 15 | npm install -g grunt-cli 16 | ``` 17 | 18 | To watch the changes and launch the builds, change to the project's root directory and run: 19 | 20 | ```shell 21 | grunt 22 | ``` 23 | 24 | ## Test the extension locally 25 | 26 | On Chrome: 27 | 28 | 1. Go to the extensions page (chrome://extensions) 29 | 2. Click **Load unpacked extension...** 30 | 3. Select the folder `build-chrome` 31 | 32 | On Edge: 33 | 34 | 1. Go to the extensions page (edge://extensions/) 35 | 2. Click **Load unpacked** 36 | 3. Select the folder `build-edge` 37 | 38 | On Firefox: 39 | 40 | 1. Go to the debugging page (about:debugging) 41 | 2. Go to the tab **This Firefox** 42 | 3. Click **Load Temporary Add-on...** 43 | 4. Select a file inside `build-firefox` 44 | -------------------------------------------------------------------------------- /config/_chrome.js: -------------------------------------------------------------------------------- 1 | module.exports.tasks = { 2 | // Compile coffee 3 | coffee: { 4 | background_load: { 5 | options: { bare: true }, 6 | src: "src/background.coffee", 7 | dest: "build-chrome/background.js" 8 | }, 9 | background: { 10 | options: { bare: true, join: true }, 11 | src: "src/background/*.coffee", 12 | dest: "build-chrome/js/background.js" 13 | }, 14 | browser_action: { 15 | options: { bare: true }, 16 | src: "src/browser_action/js/*.coffee", 17 | dest: "build-chrome/js/browser_action.js" 18 | }, 19 | shared: { 20 | options: { bare: true, join: true }, 21 | src: "src/shared/js/*.coffee", 22 | dest: "build-chrome/js/shared.js" 23 | }, 24 | hunter_content_script: { 25 | options: { bare: true }, 26 | src: "src/content_script/js/hunter-*.coffee", 27 | dest: "build-chrome/js/hunter_content_script.js" 28 | }, 29 | websites_content_script: { 30 | options: { bare: true }, 31 | src: "src/content_script/js/websites-*.coffee", 32 | dest: "build-chrome/js/websites_content_script.js" 33 | }, 34 | source_popup: { 35 | src: "src/source_popup/js/source-popup.coffee", 36 | dest: "build-chrome/js/source_popup.js" 37 | } 38 | }, 39 | 40 | // Process the Handlebars template 41 | handlebars: { 42 | compile: { 43 | options: { 44 | namespace: "JST" 45 | }, 46 | files: { 47 | "build-chrome/js/templates.js": ["src/**/*.hbs"] 48 | } 49 | } 50 | }, 51 | 52 | // SASS compilation 53 | compass: { 54 | browser_action: { 55 | options: { 56 | sourcemap: false, 57 | noLineComments: true, 58 | outputStyle: "compact", 59 | sassDir: "src/browser_action/css", 60 | cssDir: "build-chrome/css" 61 | } 62 | }, 63 | 64 | websites_content_script: { 65 | options: { 66 | sourcemap: false, 67 | noLineComments: true, 68 | outputStyle: "compact", 69 | sassDir: "src/content_script/css", 70 | cssDir: "build-chrome/css" 71 | } 72 | }, 73 | 74 | source_popup: { 75 | options: { 76 | sourcemap: false, 77 | noLineComments: true, 78 | outputStyle: "compact", 79 | sassDir: "src/source_popup/css", 80 | cssDir: "build-chrome/css" 81 | } 82 | } 83 | }, 84 | 85 | // Copy the libraries already in CSS 86 | cssmin: { 87 | shared: { 88 | src: "src/shared/css/*.css", 89 | dest: "build-chrome/css/shared.min.css" 90 | } 91 | }, 92 | 93 | // Copy other files (HTML, libraries, images and fonts) 94 | copy: { 95 | browser_action: { 96 | src: "src/browser_action/popup.html", 97 | dest: "build-chrome/html/browser_popup.html" 98 | }, 99 | source_popup: { 100 | src: "src/source_popup/popup.html", 101 | dest: "build-chrome/html/source_popup.html" 102 | }, 103 | images: { 104 | expand: true, 105 | cwd: "src/shared/img/", 106 | src: ["**"], 107 | dest: "build-chrome/img/" 108 | }, 109 | fonts: { 110 | expand: true, 111 | cwd: "src/shared/fonts/", 112 | src: ["**"], 113 | dest: "build-chrome/fonts/", 114 | options: { 115 | processContentExclude: ["*.{ttf,woff,woff2}"] 116 | } 117 | }, 118 | js_libs: { 119 | expand: true, 120 | cwd: "src/shared/js/lib", 121 | src: ["**"], 122 | dest: "build-chrome/js/lib" 123 | }, 124 | css_libs: { 125 | expand: true, 126 | cwd: "src/shared/css/lib", 127 | src: ["**"], 128 | dest: "build-chrome/css/lib" 129 | }, 130 | locale_files: { 131 | expand: true, 132 | cwd: "src/_locales", 133 | src: ["**"], 134 | dest: "build-chrome/_locales" 135 | }, 136 | manifest: { 137 | src: "src/manifest.json", 138 | dest: "build-chrome/manifest.json" 139 | } 140 | }, 141 | 142 | replace: { 143 | // This prevents an error to be displayed in Chrome console because the 144 | // source map doesn't exist. For the Firefox add-on, we don't do any 145 | // modification because Mozilla required to use unaltered library files. 146 | source_map_removal: { 147 | src: ["build-chrome/js/lib/purify.min.js"], 148 | overwrite: true, 149 | replacements: [{ 150 | from: "//# sourceMappingURL=purify.min.js.map", 151 | to: "" 152 | }] 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /config/_edge.js: -------------------------------------------------------------------------------- 1 | module.exports.tasks = { 2 | // Copy the build for Chrome before making the adaptations 3 | copy: { 4 | build_edge: { 5 | expand: true, 6 | cwd: "build-chrome/", 7 | src: ["**"], 8 | dest: "build-edge/" 9 | } 10 | }, 11 | 12 | // Do all the replacements to adapt from Chrome to Edge 13 | replace: { 14 | tracking_parameters_edge: { 15 | src: ["build-edge/**/*.js", "build-edge/**/*.html"], 16 | overwrite: true, 17 | replacements: [{ 18 | from: "utm_source=chrome_extension", 19 | to: "utm_source=edge_addon" 20 | }, 21 | { 22 | from: "utm_medium=chrome_extension", 23 | to: "utm_medium=edge_addon" 24 | }, 25 | { 26 | from: "from=chrome_extension", 27 | to: "from=edge_addon" 28 | }] 29 | }, 30 | 31 | website_welcome_url_edge: { 32 | src: ["build-edge/**/*.js", "build-edge/**/*.html"], 33 | overwrite: true, 34 | replacements: [{ 35 | from: "/chrome/welcome", 36 | to: "/edge/welcome" 37 | }] 38 | }, 39 | 40 | website_uninstall_url_edge: { 41 | src: ["build-edge/**/*.js", "build-edge/**/*.html"], 42 | overwrite: true, 43 | replacements: [{ 44 | from: "/chrome/uninstall", 45 | to: "/edge/uninstall" 46 | }] 47 | }, 48 | 49 | origin_header_edge: { 50 | src: ["build-edge/**/*.js"], 51 | overwrite: true, 52 | replacements: [{ 53 | from: "\"Email-Hunter-Origin\": \"chrome_extension\"", 54 | to: "\"Email-Hunter-Origin\": \"edge_addon\"", 55 | }] 56 | }, 57 | 58 | store_link_edge: { 59 | src: ["build-edge/html/browser_popup.html"], 60 | overwrite: true, 61 | replacements: [{ 62 | from: "https://chrome.google.com/webstore/detail/email-hunter/hgmhmanijnjhaffoampdlllchpolkdnj/reviews", 63 | to: "https://microsoftedge.microsoft.com/addons/detail/hunter-email-finder-ext/dmgcgojogkfomifjfeeafajhmgilkofk" 64 | }] 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /config/_firefox.js: -------------------------------------------------------------------------------- 1 | module.exports.tasks = { 2 | // Copy the build for Chrome before making the adaptations 3 | copy: { 4 | build_firefox: { 5 | expand: true, 6 | cwd: "build-chrome/", 7 | src: ["**"], 8 | dest: "build-firefox/" 9 | } 10 | }, 11 | 12 | // Do all the replacements to adapt from Chrome to Firefox 13 | replace: { 14 | browser_api_firefox: { 15 | src: ["build-firefox/**/*.js"], 16 | overwrite: true, 17 | replacements: [{ 18 | from: "chrome.storage.sync", 19 | to: "browser.storage.local" 20 | }, 21 | { 22 | from: "chrome.runtime", 23 | to: "browser.runtime" 24 | }, 25 | { 26 | from: "chrome.extension.onMessage.addListener", 27 | to: "browser.runtime.onMessage.addListener" 28 | }, 29 | { 30 | from: "chrome.extension.getURL", 31 | to: "browser.extension.getURL" 32 | }, 33 | { 34 | from: "chrome.action.setIcon", 35 | to: "browser.action.setIcon" 36 | }, 37 | { 38 | from: "chrome.tabs", 39 | to: "browser.tabs" 40 | }, 41 | { 42 | from: "chrome.contextMenus", 43 | to: "browser.contextMenus" 44 | }] 45 | }, 46 | 47 | tracking_parameters_firefox: { 48 | src: ["build-firefox/**/*.js", "build-firefox/**/*.html"], 49 | overwrite: true, 50 | replacements: [{ 51 | from: "utm_source=chrome_extension", 52 | to: "utm_source=firefox_addon" 53 | }, 54 | { 55 | from: "utm_medium=chrome_extension", 56 | to: "utm_medium=firefox_addon" 57 | }, 58 | { 59 | from: "from=chrome_extension", 60 | to: "from=firefox_addon" 61 | }] 62 | }, 63 | 64 | website_welcome_url_firefox: { 65 | src: ["build-firefox/**/*.js", "build-firefox/**/*.html"], 66 | overwrite: true, 67 | replacements: [{ 68 | from: "/chrome/welcome", 69 | to: "/firefox/welcome" 70 | }] 71 | }, 72 | 73 | website_uninstall_url_firefox: { 74 | src: ["build-firefox/**/*.js", "build-firefox/**/*.html"], 75 | overwrite: true, 76 | replacements: [{ 77 | from: "/chrome/uninstall", 78 | to: "/firefox/uninstall" 79 | }] 80 | }, 81 | 82 | origin_header_firefox: { 83 | src: ["build-firefox/**/*.js"], 84 | overwrite: true, 85 | replacements: [{ 86 | from: "\"Email-Hunter-Origin\": \"chrome_extension\"", 87 | to: "\"Email-Hunter-Origin\": \"firefox_addon\"", 88 | }] 89 | }, 90 | 91 | store_link_firefox: { 92 | src: ["build-firefox/html/browser_popup.html"], 93 | overwrite: true, 94 | replacements: [{ 95 | from: "https://chrome.google.com/webstore/detail/email-hunter/hgmhmanijnjhaffoampdlllchpolkdnj/reviews", 96 | to: "https://addons.mozilla.org/firefox/addon/hunterio/reviews/add" 97 | }] 98 | }, 99 | 100 | fonts_path_firefox: { 101 | src: ["build-firefox/**/*.css"], 102 | overwrite: true, 103 | replacements: [{ 104 | from: "chrome-extension://__MSG_@@extension_id__/", 105 | to: "../" 106 | }] 107 | }, 108 | 109 | images_path_firefox: { 110 | src: ["build-firefox/**/*.js"], 111 | overwrite: true, 112 | replacements: [{ 113 | from: "getURL(\"shared/img/", 114 | to: "getURL(\"img/" 115 | }] 116 | } 117 | }, 118 | 119 | // Add the application ID in the manifest.json 120 | editmanifestforfirefox: {} 121 | } 122 | -------------------------------------------------------------------------------- /config/watch.js: -------------------------------------------------------------------------------- 1 | module.exports.tasks = { 2 | // Watch any modification to the package 3 | watch: { 4 | scripts: { 5 | files: ["src/**/*.js", "src/**/*.css", "src/**/*.scss", "src/**/*.html", "src/**/*.json", "src/**/*/*.coffee", "src/**/*/*.hbs"], 6 | tasks: ["coffee", "handlebars", "compass", "cssmin", "copy", "replace", "editmanifestforfirefox"], 7 | options: { 8 | spawn: false, 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hunter-extension", 3 | "version": "1.0.0", 4 | "description": "Hunter's browser extension", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/hunter-io/browser-extension.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/hunter-io/browser-extension/issues" 11 | }, 12 | "homepage": "https://github.com/hunter-io/browser-extension#readme", 13 | "devDependencies": { 14 | "grunt": "^1.5.3", 15 | "grunt-contrib-coffee": "^2.1.0", 16 | "grunt-contrib-compass": "^0.8.0", 17 | "grunt-contrib-copy": "^1.0.0", 18 | "grunt-contrib-cssmin": "^2.2.1", 19 | "grunt-contrib-handlebars": "^3.0.0", 20 | "grunt-contrib-watch": "^1.0.0", 21 | "grunt-text-replace": "^0.4.0", 22 | "load-grunt-configs": "^1.0.0" 23 | }, 24 | "dependencies": { 25 | "lodash": "^4.17.21" 26 | }, 27 | "license": "Apache-2.0" 28 | } 29 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Hunter - Email Finder Extension" 4 | }, 5 | "extension_description": { 6 | "message": "Find email addresses from anywhere on the web, with just one click." 7 | }, 8 | "see_results": { 9 | "message": "See results on hunter.io" 10 | }, 11 | "searches": { 12 | "message": "searches" 13 | }, 14 | "upgrade": { 15 | "message": "Upgrade" 16 | }, 17 | "sign_in": { 18 | "message": "Sign in" 19 | }, 20 | "create_free_account": { 21 | "message": "Create a free account" 22 | }, 23 | "are_you_statisfied": { 24 | "message": "Are you satisfied with Hunter extension?" 25 | }, 26 | "yes": { 27 | "message": "Yes" 28 | }, 29 | "no": { 30 | "message": "No" 31 | }, 32 | "hunter_extension_is_free": { 33 | "message": "Hunter extension is free." 34 | }, 35 | "please_let_review": { 36 | "message": "Please help us spread the word by letting a review!" 37 | }, 38 | "add_a_review": { 39 | "message": "Add a review" 40 | }, 41 | "we_are_sorry": { 42 | "message": "We are sorry to hear this." 43 | }, 44 | "please_contact_us_for_feedback": { 45 | "message": "Please contact us at contact@hunter.io if you want to give us feedback or ask any question. We would be happy to help." 46 | }, 47 | "you_reached_daily_limit": { 48 | "message": "You've reached your daily limit!" 49 | }, 50 | "please_connect_account": { 51 | "message": "Please connect your Hunter account to use the extension. It's free." 52 | }, 53 | "you_are_not_authenticated": { 54 | "message": "You are no longer authenticated" 55 | }, 56 | "please_sign_in_to_continue": { 57 | "message": "Please sign in to continue" 58 | }, 59 | "not_enough_data": { 60 | "message": "Not enough data" 61 | }, 62 | "not_enough_data_for": { 63 | "message": "There is not enough data available online for" 64 | }, 65 | "find_out_more": { 66 | "message": "Find out more information" 67 | }, 68 | "webmail_domain": { 69 | "message": "Webmail domain" 70 | }, 71 | "domain_used_for_personal_emails": { 72 | "message": "This domain is used to create personal email addresses. These email addresses are not returned in Hunter which is designed for professional use only." 73 | }, 74 | "email_pattern": { 75 | "message": "Email pattern" 76 | }, 77 | "type": { 78 | "message": "Type" 79 | }, 80 | "personal": { 81 | "message": "Personal" 82 | }, 83 | "generic": { 84 | "message": "Generic" 85 | }, 86 | "apply_filters": { 87 | "message": "Apply filters" 88 | }, 89 | "departments": { 90 | "message": "Departments" 91 | }, 92 | "department_executive": { 93 | "message": "Executive" 94 | }, 95 | "department_it": { 96 | "message": "IT / Engineering" 97 | }, 98 | "department_finance": { 99 | "message": "Finance" 100 | }, 101 | "department_management": { 102 | "message": "Management" 103 | }, 104 | "department_sales": { 105 | "message": "Sales" 106 | }, 107 | "department_legal": { 108 | "message": "Legal" 109 | }, 110 | "department_support": { 111 | "message": "Support" 112 | }, 113 | "department_hr": { 114 | "message": "Human Resources" 115 | }, 116 | "department_marketing": { 117 | "message": "Marketing" 118 | }, 119 | "department_communication": { 120 | "message": "Writing & Communication" 121 | }, 122 | "department_education": { 123 | "message": "Education" 124 | }, 125 | "department_design": { 126 | "message": "Arts & Design" 127 | }, 128 | "department_health": { 129 | "message": "Medical & Health" 130 | }, 131 | "department_operations": { 132 | "message": "Operations & Logistics" 133 | }, 134 | "clear_filters": { 135 | "message": "Clear filters" 136 | }, 137 | "find_by_name": { 138 | "message": "Find by name" 139 | }, 140 | "no_results_found": { 141 | "message": "No results found" 142 | }, 143 | "we_could_not_find_with_filters": { 144 | "message": "We couldn't find any results matching your criterias. Update or clear your filters." 145 | }, 146 | "no_results_for": { 147 | "message": "No results for" 148 | }, 149 | "could_not_find_email_for_person": { 150 | "message": "We couldn't find the email address for this person." 151 | }, 152 | "save_leads_in": { 153 | "message": "Save leads in" 154 | }, 155 | "go_to_my_leads": { 156 | "message": "Go to my leads" 157 | }, 158 | "not_available_on_linkedin": { 159 | "message": "Hunter is no longer available on LinkedIn" 160 | }, 161 | "find_emails_with_email_finder": { 162 | "message": "To find the email address of people you have identified, please use the Email Finder." 163 | }, 164 | "email_finder": { 165 | "message": "Email Finder" 166 | }, 167 | "find_emails_from_websites": { 168 | "message": "Find email addresses from websites" 169 | }, 170 | "open_hunter_on_websites": { 171 | "message": "Open Hunter when you visit websites to find email addresses." 172 | }, 173 | "dashboard": { 174 | "message": "Dashboard" 175 | }, 176 | "connection_refused": { 177 | "message": "The connection to Hunter has been refused" 178 | }, 179 | "visit_website_for_information": { 180 | "message": "Please visit the website for more information." 181 | }, 182 | "go_to_hunter": { 183 | "message": "Go to hunter.io" 184 | }, 185 | "something_went_wrong_with_the_query": { 186 | "message": "Sorry, something went wrong with the query." 187 | }, 188 | "results_for_domain": { 189 | "message": "$COUNT$ result$S$ for $DOMAIN$", 190 | "placeholders": { 191 | "count": { 192 | "content": "$1", 193 | "example": "1,120" 194 | }, 195 | "s": { 196 | "content": "$2", 197 | "example": "s" 198 | }, 199 | "domain": { 200 | "content": "$3", 201 | "example": "hunter.io" 202 | } 203 | } 204 | }, 205 | "see_all_the_results": { 206 | "message": "See all the results ($COUNT$ more)", 207 | "placeholders": { 208 | "count": { 209 | "content": "$1", 210 | "example": "1,120" 211 | } 212 | } 213 | }, 214 | "save": { 215 | "message": "Save" 216 | }, 217 | "sign_up_to_uncover_more_emails": { 218 | "message": "Please sign up to uncover the email addresses" 219 | }, 220 | "verifying": { 221 | "message": "Verifying" 222 | }, 223 | "retry": { 224 | "message": "Retry" 225 | }, 226 | "email_verification_takes_longer": { 227 | "message": "The email verification is taking longer than expected. Please try again later." 228 | }, 229 | "valid": { 230 | "message": "Valid" 231 | }, 232 | "invalid": { 233 | "message": "Invalid" 234 | }, 235 | "accept_all": { 236 | "message": "Accept all" 237 | }, 238 | "unknown": { 239 | "message": "Unknown" 240 | }, 241 | "first_name": { 242 | "message": "First name" 243 | }, 244 | "last_name": { 245 | "message": "Last name" 246 | }, 247 | "first_name_initial": { 248 | "message": "First name initial" 249 | }, 250 | "last_name_initial": { 251 | "message": "Last name initial" 252 | }, 253 | "enter_full_name_to_find_email": { 254 | "message": "Please enter the full name of the person to find the email address." 255 | }, 256 | "we_found_this_email_on_the_web": { 257 | "message": "We found this email address $COUNT$ time$S$ on the web.", 258 | "placeholders": { 259 | "count": { 260 | "content": "$1", 261 | "example": "12" 262 | }, 263 | "s": { 264 | "content": "$2", 265 | "example": "s" 266 | } 267 | } 268 | }, 269 | "loading": { 270 | "message": "Loading" 271 | }, 272 | "saved": { 273 | "message": "Saved" 274 | }, 275 | "failed": { 276 | "message": "Failed" 277 | }, 278 | "create_a_new_list": { 279 | "message": "Create a new list" 280 | }, 281 | "click_to_copy": { 282 | "message": "Click to copy" 283 | }, 284 | "save_as_lead": { 285 | "message": "Save as lead" 286 | }, 287 | "january": { 288 | "message": "January" 289 | }, 290 | "february": { 291 | "message": "February" 292 | }, 293 | "march": { 294 | "message": "March" 295 | }, 296 | "april": { 297 | "message": "April" 298 | }, 299 | "june": { 300 | "message": "June" 301 | }, 302 | "july": { 303 | "message": "July" 304 | }, 305 | "august": { 306 | "message": "August" 307 | }, 308 | "september": { 309 | "message": "September" 310 | }, 311 | "october": { 312 | "message": "October" 313 | }, 314 | "november": { 315 | "message": "November" 316 | }, 317 | "december": { 318 | "message": "December" 319 | }, 320 | "jan": { 321 | "message": "Jan" 322 | }, 323 | "feb": { 324 | "message": "Feb" 325 | }, 326 | "mar": { 327 | "message": "Mar" 328 | }, 329 | "apr": { 330 | "message": "Apr" 331 | }, 332 | "may": { 333 | "message": "May" 334 | }, 335 | "jun": { 336 | "message": "Jun" 337 | }, 338 | "jul": { 339 | "message": "Jul" 340 | }, 341 | "aug": { 342 | "message": "Aug" 343 | }, 344 | "sep": { 345 | "message": "Sep" 346 | }, 347 | "oct": { 348 | "message": "Oct" 349 | }, 350 | "nov": { 351 | "message": "Nov" 352 | }, 353 | "dec": { 354 | "message": "Dec" 355 | }, 356 | "verify_email": { 357 | "message": "Verify email" 358 | }, 359 | "removed": { 360 | "message": "Removed" 361 | }, 362 | "something_went_wrong_on_our_side": { 363 | "message": "Something went wrong on our side. Please try again later." 364 | }, 365 | "please_complete_your_registration": { 366 | "message": "Please complete your registration on the website to use the extension." 367 | }, 368 | "your_account_has_been_restricted": { 369 | "message": "Your account has been restricted. Please log in for more information." 370 | }, 371 | "you_have_reached_your_daily_quota": { 372 | "message": "You have reached your free monthly quota. Please upgrade to a premium plan to do more searches." 373 | }, 374 | "you_have_reached_your_temporary_quota": { 375 | "message": "You have reached your temporary quota. You will get your full access as soon as we validate your account." 376 | }, 377 | "your_have_reached_your_monthly_quota": { 378 | "message": "You have reached your monthly quota. Please upgrade to do more searches." 379 | }, 380 | "your_have_reached_your_monthly_enterprise_quota": { 381 | "message": "You have reached your monthly quota. Please contact your account manager to update your plan." 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "Hunter - Extension Email Finder" 4 | }, 5 | "extension_description": { 6 | "message": "Trouvez des adresses email n'importe où sur le web, en un seul clic." 7 | }, 8 | "see_results": { 9 | "message": "Voir les résultats sur hunter.io" 10 | }, 11 | "searches": { 12 | "message": "recherches" 13 | }, 14 | "upgrade": { 15 | "message": "Mettre à niveau" 16 | }, 17 | "sign_in": { 18 | "message": "Se connecter" 19 | }, 20 | "create_free_account": { 21 | "message": "Créer un compte gratuit" 22 | }, 23 | "are_you_statisfied": { 24 | "message": "Êtes-vous satisfait de l'extension Hunter?" 25 | }, 26 | "yes": { 27 | "message": "Oui" 28 | }, 29 | "no": { 30 | "message": "Non" 31 | }, 32 | "hunter_extension_is_free": { 33 | "message": "L'extension Hunter est gratuite." 34 | }, 35 | "please_let_review": { 36 | "message": "Aidez-nous à faire connaître notre produit en laissant un commentaire !" 37 | }, 38 | "add_a_review": { 39 | "message": "Laisser un avis" 40 | }, 41 | "we_are_sorry": { 42 | "message": "Nous sommes désolés d'apprendre cela." 43 | }, 44 | "please_contact_us_for_feedback": { 45 | "message": "Veuillez nous contacter à l'adresse contact@hunter.io si vous souhaitez nous donner votre avis ou poser une question. Nous serions ravis de vous aider." 46 | }, 47 | "you_reached_daily_limit": { 48 | "message": "Vous avez atteint votre limite quotidienne !" 49 | }, 50 | "please_connect_account": { 51 | "message": "Veuillez connecter votre compte Hunter pour utiliser l'extension. C'est gratuit." 52 | }, 53 | "you_are_not_authenticated": { 54 | "message": "Vous n'êtes plus authentifié" 55 | }, 56 | "please_sign_in_to_continue": { 57 | "message": "Veuillez vous connecter pour continuer" 58 | }, 59 | "not_enough_data": { 60 | "message": "Pas assez de données" 61 | }, 62 | "not_enough_data_for": { 63 | "message": "Il n'y a pas suffisamment de données disponibles en ligne pour" 64 | }, 65 | "find_out_more": { 66 | "message": "Trouver plus d'informations" 67 | }, 68 | "webmail_domain": { 69 | "message": "Domaine de messagerie Web" 70 | }, 71 | "domain_used_for_personal_emails": { 72 | "message": "Ce domaine est utilisé pour créer des adresses email personnelles. Ces adresses email ne sont pas retournées dans Hunter, qui est conçu uniquement pour un usage professionnel." 73 | }, 74 | "email_pattern": { 75 | "message": "Modèle d'adresse email" 76 | }, 77 | "type": { 78 | "message": "Type" 79 | }, 80 | "personal": { 81 | "message": "Personnel" 82 | }, 83 | "generic": { 84 | "message": "Générique" 85 | }, 86 | "apply_filters": { 87 | "message": "Filtrer" 88 | }, 89 | "departments": { 90 | "message": "Départements" 91 | }, 92 | "department_executive": { 93 | "message": "Direction" 94 | }, 95 | "department_it": { 96 | "message": "IT / Ingénierie" 97 | }, 98 | "department_finance": { 99 | "message": "Finance" 100 | }, 101 | "department_management": { 102 | "message": "Gestion" 103 | }, 104 | "department_sales": { 105 | "message": "Ventes" 106 | }, 107 | "department_legal": { 108 | "message": "Juridique" 109 | }, 110 | "department_support": { 111 | "message": "Assistance" 112 | }, 113 | "department_hr": { 114 | "message": "Ressources humaines" 115 | }, 116 | "department_marketing": { 117 | "message": "Marketing" 118 | }, 119 | "department_communication": { 120 | "message": "Rédaction et communication" 121 | }, 122 | "department_education": { 123 | "message": "Éducation" 124 | }, 125 | "department_design": { 126 | "message": "Arts et design" 127 | }, 128 | "department_health": { 129 | "message": "Médical et santé" 130 | }, 131 | "department_operations": { 132 | "message": "Opérations et logistique" 133 | }, 134 | "clear_filters": { 135 | "message": "Effacer les filtres" 136 | }, 137 | "find_by_name": { 138 | "message": "Chercher par nom" 139 | }, 140 | "no_results_found": { 141 | "message": "Aucun résultat trouvé" 142 | }, 143 | "we_could_not_find_with_filters": { 144 | "message": "Nous n'avons trouvé aucun résultat correspondant à vos critères de recherche. Mettez à jour ou effacez vos filtres." 145 | }, 146 | "no_results_for": { 147 | "message": "Aucun résultat pour" 148 | }, 149 | "could_not_find_email_for_person": { 150 | "message": "Nous n'avons pas pu trouver l'adresse email pour cette personne." 151 | }, 152 | "save_leads_in": { 153 | "message": "Enregistrer les prospects dans" 154 | }, 155 | "go_to_my_leads": { 156 | "message": "Ouvrir la liste" 157 | }, 158 | "not_available_on_linkedin": { 159 | "message": "Hunter n'est plus disponible sur LinkedIn" 160 | }, 161 | "find_emails_with_email_finder": { 162 | "message": "Pour trouver l'adresse email des personnes que vous avez identifiées, utilisez le Email Finder." 163 | }, 164 | "email_finder": { 165 | "message": "Recherche d'adresse email" 166 | }, 167 | "find_emails_from_websites": { 168 | "message": "Trouver des adresses email à partir de sites web" 169 | }, 170 | "open_hunter_on_websites": { 171 | "message": "Ouvrez Hunter lorsque vous visitez des sites web pour trouver des adresses email." 172 | }, 173 | "dashboard": { 174 | "message": "Tableau de bord" 175 | }, 176 | "connection_refused": { 177 | "message": "La connexion à Hunter a été refusée" 178 | }, 179 | "visit_website_for_information": { 180 | "message": "Veuillez consulter le site web pour plus d'informations." 181 | }, 182 | "go_to_hunter": { 183 | "message": "Aller sur hunter.io" 184 | }, 185 | "something_went_wrong_with_the_query": { 186 | "message": "Désolé, quelque chose s'est mal passé avec la requête." 187 | }, 188 | "results_for_domain": { 189 | "message": "$COUNT$ résultat$S$ pour $DOMAIN$", 190 | "placeholders": { 191 | "count": { 192 | "content": "$1", 193 | "example": "1 120" 194 | }, 195 | "s": { 196 | "content": "$2", 197 | "example": "s" 198 | }, 199 | "domain": { 200 | "content": "$3", 201 | "example": "hunter.io" 202 | } 203 | } 204 | }, 205 | "see_all_the_results": { 206 | "message": "Voir tous les résultats ($COUNT$ de plus)", 207 | "placeholders": { 208 | "count": { 209 | "content": "$1", 210 | "example": "1 120" 211 | } 212 | } 213 | }, 214 | "save": { 215 | "message": "Enregistrer" 216 | }, 217 | "sign_up_to_uncover_more_emails": { 218 | "message": "Veuillez vous inscrire pour découvrir les adresses email" 219 | }, 220 | "verifying": { 221 | "message": "Vérification en cours" 222 | }, 223 | "retry": { 224 | "message": "Réessayer" 225 | }, 226 | "email_verification_takes_longer": { 227 | "message": "La vérification de l'adresse email prend plus de temps que prévu. Veuillez réessayer plus tard." 228 | }, 229 | "valid": { 230 | "message": "Valide" 231 | }, 232 | "invalid": { 233 | "message": "Invalide" 234 | }, 235 | "accept_all": { 236 | "message": "Tout accepter" 237 | }, 238 | "unknown": { 239 | "message": "Inconnu" 240 | }, 241 | "first_name": { 242 | "message": "Prénom" 243 | }, 244 | "last_name": { 245 | "message": "Nom de famille" 246 | }, 247 | "first_name_initial": { 248 | "message": "Initiale du prénom" 249 | }, 250 | "last_name_initial": { 251 | "message": "Initiale du nom de famille" 252 | }, 253 | "enter_full_name_to_find_email": { 254 | "message": "Veuillez saisir le nom complet de la personne pour trouver l'adresse email." 255 | }, 256 | "we_found_this_email_on_the_web": { 257 | "message": "Nous avons trouvé cette adresse email $COUNT$ fois sur le web.", 258 | "placeholders": { 259 | "count": { 260 | "content": "$1", 261 | "example": "12" 262 | }, 263 | "s": { 264 | "content": "$2", 265 | "example": "s" 266 | } 267 | } 268 | }, 269 | "loading": { 270 | "message": "Chargement" 271 | }, 272 | "saved": { 273 | "message": "Enregistré" 274 | }, 275 | "failed": { 276 | "message": "Échec" 277 | }, 278 | "create_a_new_list": { 279 | "message": "Créer une nouvelle liste" 280 | }, 281 | "click_to_copy": { 282 | "message": "Cliquez pour copier" 283 | }, 284 | "save_as_lead": { 285 | "message": "Enregistrer" 286 | }, 287 | "january": { 288 | "message": "Janvier" 289 | }, 290 | "february": { 291 | "message": "Février" 292 | }, 293 | "march": { 294 | "message": "Mars" 295 | }, 296 | "april": { 297 | "message": "Avril" 298 | }, 299 | "may": { 300 | "message": "Mai" 301 | }, 302 | "june": { 303 | "message": "Juin" 304 | }, 305 | "july": { 306 | "message": "Juillet" 307 | }, 308 | "august": { 309 | "message": "Août" 310 | }, 311 | "september": { 312 | "message": "Septembre" 313 | }, 314 | "october": { 315 | "message": "Octobre" 316 | }, 317 | "november": { 318 | "message": "Novembre" 319 | }, 320 | "december": { 321 | "message": "Décembre" 322 | }, 323 | "jan": { 324 | "message": "Jan" 325 | }, 326 | "feb": { 327 | "message": "Fév" 328 | }, 329 | "mar": { 330 | "message": "Mar" 331 | }, 332 | "apr": { 333 | "message": "Avr" 334 | }, 335 | "jun": { 336 | "message": "Juin" 337 | }, 338 | "jul": { 339 | "message": "Juil" 340 | }, 341 | "aug": { 342 | "message": "Août" 343 | }, 344 | "sep": { 345 | "message": "Sep" 346 | }, 347 | "oct": { 348 | "message": "Oct" 349 | }, 350 | "nov": { 351 | "message": "Nov" 352 | }, 353 | "dec": { 354 | "message": "Déc" 355 | }, 356 | "verify_email": { 357 | "message": "Vérifier l'email" 358 | }, 359 | "removed": { 360 | "message": "Supprimé" 361 | }, 362 | "something_went_wrong_on_our_side": { 363 | "message": "Un problème est survenu de notre côté. Veuillez réessayer plus tard." 364 | }, 365 | "please_complete_your_registration": { 366 | "message": "Veuillez compléter votre inscription sur le site web pour utiliser l'extension." 367 | }, 368 | "your_account_has_been_restricted": { 369 | "message": "Votre compte a été restreint. Veuillez vous connecter pour plus d'informations." 370 | }, 371 | "you_have_reached_your_daily_quota": { 372 | "message": "Vous avez atteint votre quota mensuel gratuit. Veuillez passer à un abonnement premium pour effectuer plus de recherches." 373 | }, 374 | "you_have_reached_your_temporary_quota": { 375 | "message": "Vous avez atteint votre quota temporaire. Vous aurez un accès complet dès que nous aurons validé votre compte." 376 | }, 377 | "your_have_reached_your_monthly_quota": { 378 | "message": "Vous avez atteint votre quota mensuel. Veuillez passer à un abonnement supérieur pour effectuer plus de recherches." 379 | }, 380 | "your_have_reached_your_monthly_enterprise_quota": { 381 | "message": "Vous avez atteint votre quota mensuel. Veuillez contacter votre responsable de compte pour mettre à jour votre plan." 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /src/background.coffee: -------------------------------------------------------------------------------- 1 | try 2 | importScripts '/js/shared.js', '/js/background.js' 3 | catch e 4 | console.error e 5 | -------------------------------------------------------------------------------- /src/background/icon.coffee: -------------------------------------------------------------------------------- 1 | window = self 2 | 3 | # When an URL changes 4 | # 5 | # Check if email addresses are available for the current domain and update the 6 | # color of the browser icon 7 | # 8 | LaunchColorChange = -> 9 | chrome.tabs.query { 10 | currentWindow: true 11 | active: true 12 | }, (tabArray) -> 13 | if tabArray[0]["url"] != window.currentDomain 14 | try 15 | hostname = new URL(tabArray[0]['url']) 16 | Utilities.findRelevantDomain hostname.host, (domain) -> 17 | window.currentDomain = domain 18 | updateIconColor() 19 | catch e 20 | # The tab is not a valid URL 21 | setGreyIcon() 22 | 23 | # API call to check if there is at least one email address 24 | # We use a special API call for this task to minimize the ressources. 25 | # 26 | # Endpoint: https://extension-api.hunter.io/data-for-domain?domain=site.com 27 | # 28 | updateIconColor = -> 29 | if window.currentDomain.indexOf(".") == -1 30 | setGreyIcon() 31 | else 32 | Utilities.dataFoundForDomain window.currentDomain, (results) -> 33 | if results 34 | setColoredIcon() 35 | else 36 | setGreyIcon() 37 | 38 | 39 | setGreyIcon = -> 40 | chrome.action.setIcon path: 41 | "19": chrome.runtime.getURL("../img/icon19_grey.png") 42 | "38": chrome.runtime.getURL("../img/icon38_grey.png") 43 | return 44 | 45 | setColoredIcon = -> 46 | chrome.action.setIcon path: 47 | "19": chrome.runtime.getURL("../img/icon19.png") 48 | "38": chrome.runtime.getURL("../img/icon38.png") 49 | return 50 | 51 | # When an URL change 52 | chrome.tabs.onUpdated.addListener (tabid, changeinfo, tab) -> 53 | if tab != undefined 54 | if tab.url != undefined and changeinfo.status == "complete" 55 | LaunchColorChange() 56 | 57 | # When the active tab changes 58 | chrome.tabs.onActivated.addListener -> 59 | LaunchColorChange() 60 | -------------------------------------------------------------------------------- /src/background/open-pages.coffee: -------------------------------------------------------------------------------- 1 | # Open a tab when it's installed 2 | # 3 | chrome.runtime.onInstalled.addListener (object) -> 4 | if object.reason == "install" 5 | chrome.tabs.create url: "https://hunter.io/users/sign_up?from=chrome_extension&utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=new_install" 6 | return 7 | 8 | # Open another tab when it's uninstalled 9 | # 10 | chrome.runtime.setUninstallURL "https://hunter.io/chrome/uninstall?from=chrome_extension&utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=uninstall" 11 | -------------------------------------------------------------------------------- /src/browser_action/css/_base.scss: -------------------------------------------------------------------------------- 1 | // Popup 2 | // 3 | ::selection { 4 | background: var(--colors-grey-800); 5 | color: #fff; 6 | } 7 | 8 | html { 9 | width: 620px; 10 | min-height: 520px; 11 | position: relative; 12 | } 13 | 14 | body { 15 | width: 620px; 16 | font-display: swap; 17 | font-family: var(--fonts-family-body) !important; 18 | font-feature-settings: var(--fonts-family-feature-settings); 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | font-size: 1.3rem; 22 | color: var(--colors-grey-800); 23 | margin-top: 6rem; // header height 24 | 25 | &::-webkit-scrollbar { 26 | display: none; 27 | } 28 | } 29 | 30 | #domain-search { 31 | display: none; 32 | } 33 | 34 | a:focus, a:active { 35 | outline: none; 36 | } 37 | 38 | strong { 39 | font-weight: 600; 40 | } 41 | 42 | .fa-spin { 43 | -webkit-animation-duration: 400ms !important; 44 | animation-duration: 400ms !important; 45 | } 46 | 47 | abbr { 48 | text-decoration: none; 49 | } 50 | 51 | // Confidence score 52 | // 53 | .score { 54 | display: inline-block; 55 | width: 8px; 56 | height: 8px; 57 | border-radius: 50%; 58 | border: 2px solid transparent; 59 | 60 | &.low-score { 61 | border-color: var(--colors-danger-600); 62 | } 63 | 64 | &.average-score { 65 | background: linear-gradient(0deg, var(--colors-warning-500), var(--colors-warning-500) 50%, transparent 0, transparent); 66 | border-color: var(--colors-warning-500); 67 | } 68 | 69 | &.high-score { 70 | background-color: var(--colors-success-600); 71 | } 72 | } 73 | 74 | 75 | // Buttons 76 | // prefixed with "h-" to avoid Bootstrap collison 77 | // 78 | .h-button { 79 | // Initial 80 | --h-button-padding: var(--spacing-03) var(--spacing-04); 81 | --h-button-gap: var(--spacing-02); 82 | --h-button-border-color: var(--colors-grey-300); 83 | --h-button-border-radius: 4px; 84 | --h-button-box-shadow: 0px 1px 1px rgba(29, 38, 46, 0.05); 85 | --h-button-font-size: 1.3rem; 86 | --h-button-line-height: calc(16/13); 87 | --h-button-text-shadow: none; 88 | --h-button-color: var(--colors-grey-800); 89 | --h-button-background: #fff; 90 | --h-button-height: 4rem; 91 | --h-button-width: max-content; 92 | 93 | // Hover 94 | --h-button-color-hover: var(--colors-grey-800); 95 | --h-button-background-hover: var(--colors-grey-100); 96 | --h-button-border-color-hover: var(--colors-grey-300); 97 | --h-button-box-shadow-hover: 0px 1px 4px 2px rgba(29, 38, 46, 0.04), 0px 1px 1px rgba(29, 38, 46, 0.05); 98 | 99 | // Focus-visible 100 | --h-button-box-shadow-focus: 0px 0px 0px 2px var(--colors-secondary-600), 0px 1px 1px rgba(29, 38, 46, 0.05); 101 | 102 | // Active 103 | --h-button-color-active: var(--colors-grey-800); 104 | --h-button-background-active: var(--colors-grey-100); 105 | --h-button-box-shadow-active: none; 106 | 107 | display: inline-flex; 108 | align-items: center; 109 | gap: var(--h-button-gap); 110 | padding: var(--h-button-padding); 111 | height: var(--h-button-height); 112 | width: var(--h-button-width); 113 | border: 1px solid var(--h-button-border-color); 114 | border-radius: var(--h-button-border-radius); 115 | box-shadow: var(--h-button-box-shadow); 116 | font-size: var(--h-button-font-size); 117 | font-weight: 500; 118 | line-height: var(--h-button-line-height); 119 | text-decoration: none; 120 | text-shadow: var(--h-button-text-shadow); 121 | background: var(--h-button-background); 122 | color: var(--h-button-color); 123 | transition: background-color 250ms ease, color 250ms ease-in, border-color 250ms ease-in; 124 | 125 | &:hover, 126 | &:focus { 127 | color: var(--h-button-color-hover); 128 | background-color: var(--h-button-background-hover); 129 | border-color: var(--h-button-border-color-hover); 130 | box-shadow: var(--h-button-box-shadow-hover); 131 | text-decoration: none; 132 | outline: none; 133 | } 134 | 135 | &:focus-visible { 136 | box-shadow: var(--h-button-box-shadow-focus); 137 | } 138 | 139 | &:active, 140 | &.active, 141 | &[aria-expanded="true"] { 142 | color: var(--h-button-color-active); 143 | background-color: var(--h-button-background-active); 144 | box-shadow: var(--h-button-box-shadow-active); 145 | } 146 | 147 | &:active { 148 | transform: translateY(0.5px); 149 | } 150 | 151 | &:disabled, 152 | &.disabled { 153 | opacity: .5; 154 | user-select: none; 155 | pointer-events: none; 156 | } 157 | } 158 | 159 | .h-button--primary { 160 | // Default 161 | --h-button-border-color: transparent; 162 | --h-button-box-shadow: none; 163 | --h-button-text-shadow: 0px 1px 0px rgba(212, 69, 25, 0.5); 164 | --h-button-color: #fff; 165 | --h-button-background: var(--colors-primary-600); 166 | 167 | // Hover 168 | --h-button-color-hover: #fff; 169 | --h-button-background-hover: var(--colors-primary-700); 170 | --h-button-border-color-hover: var(--colors-primary-700); 171 | --h-button-box-shadow-hover: none; 172 | 173 | // Focus-visible 174 | --h-button-box-shadow-focus: 0px 0px 0px 1px #FFFFFF, 0px 0px 0px 3px var(--colors-primary-700); 175 | 176 | // Active 177 | --h-button-color-active: #fff; 178 | --h-button-background-active: var(--colors-primary-700); 179 | --h-button-box-shadow-active: none; 180 | 181 | // Specifities 182 | border-bottom-color: var(--colors-primary-700); 183 | } 184 | 185 | .h-button--ghost { 186 | // Default 187 | --h-button-border-color: transparent; 188 | --h-button-box-shadow: none; 189 | --h-button-background: transparent; 190 | 191 | // Hover 192 | --h-button-background-hover: var(--colors-grey-100); 193 | --h-button-border-color-hover: transparent; 194 | --h-button-box-shadow-hover: none; 195 | 196 | // Focus-visible 197 | --h-button-box-shadow-focus: 0px 0px 0px 1px var(--colors-grey-100), 0px 0px 0px 3px var(--colors-grey-300); 198 | 199 | // Active 200 | --h-button-background-active: var(--colors-grey-100); 201 | --h-button-box-shadow-active: none; 202 | } 203 | 204 | // Sizes modifier 205 | // 206 | .h-button--sm { 207 | --h-button-padding: var(--spacing-02); 208 | --h-button-font-size: 1.2rem; 209 | --h-button-line-height: calc(16/12); 210 | --h-button-height: 3.2rem; 211 | } 212 | 213 | .h-button--xs { 214 | --h-button-padding: var(--spacing-01) var(--spacing-02); 215 | --h-button-gap: var(--spacing-01); 216 | --h-button-font-size: 1.1rem; 217 | --h-button-line-height: calc(16/11); 218 | --h-button-height: 2.4rem; 219 | } 220 | 221 | 222 | // Tags 223 | // 224 | .tag { 225 | --c-tag-background: var(--colors-grey-200); 226 | --c-tag-background-hover: var(--colors-grey-300); 227 | --c-tag-color: var(--colors-grey-700); 228 | --c-tag-icon-color: var(--colors-grey-600); 229 | --c-tag-padding: var(--spacing-01) var(--spacing-02); 230 | 231 | display: inline-flex; 232 | align-items: center; 233 | padding: var(--c-tag-padding); 234 | overflow: hidden; 235 | border: 0; 236 | border-radius: 2px; 237 | background-color: var(--c-tag-background); 238 | color: var(--c-tag-color); 239 | font-size: 1.2rem; 240 | font-weight: 600; 241 | line-height: 1.33333; // 16px 242 | 243 | .score { 244 | margin-right: var(--spacing-01); 245 | } 246 | } 247 | 248 | .tag__icon { 249 | color: var(--c-tag-icon-color); 250 | margin-right: var(--spacing-01); 251 | } 252 | 253 | .tag__label { 254 | display: flex; 255 | align-items: center; 256 | white-space: nowrap; 257 | overflow: hidden; 258 | color: var(--c-tag-color); 259 | text-decoration: none; 260 | text-overflow: ellipsis; 261 | } 262 | 263 | /* Size */ 264 | .tag--sm { 265 | --c-tag-padding: var(--spacing-005) var(--spacing-02); 266 | 267 | font-size: 1.1rem; 268 | font-weight: 500; 269 | line-height: calc(16/11); 270 | } 271 | 272 | /* Colors */ 273 | .tag--success { 274 | --c-tag-background: var(--colors-success-200); 275 | --c-tag-background-hover: var(--colors-success-300); 276 | --c-tag-color: var(--colors-success-700); 277 | --c-tag-icon-color: var(--colors-success-600); 278 | } 279 | 280 | .tag--warning { 281 | --c-tag-background: var(--colors-warning-200); 282 | --c-tag-background-hover: var(--colors-warning-300); 283 | --c-tag-color: var(--colors-warning-800); 284 | --c-tag-icon-color: var(--colors-warning-600); 285 | } 286 | 287 | .tag--danger { 288 | --c-tag-background: var(--colors-danger-200); 289 | --c-tag-background-hover: var(--colors-danger-300); 290 | --c-tag-color: var(--colors-danger-700); 291 | --c-tag-icon-color: var(--colors-danger-600); 292 | } 293 | 294 | .tag--info { 295 | --c-tag-background: var(--colors-secondary-200); 296 | --c-tag-background-hover: var(--colors-secondary-300); 297 | --c-tag-color: var(--colors-secondary-700); 298 | --c-tag-icon-color: var(--colors-secondary-600); 299 | } 300 | 301 | 302 | // Select 303 | // 304 | .h-select { 305 | // Initial 306 | --h-select-padding: calc(var(--spacing-02) - 1px) var(--spacing-05) calc(var(--spacing-02) - 1px) var(--spacing-03); 307 | --h-select-border-color: var(--colors-grey-300); 308 | --h-select-border-radius: 4px; 309 | --h-select-font-size: 1.3rem; 310 | --h-select-line-height: calc(16/13); 311 | --h-select-color: var(--colors-grey-900); 312 | --h-select-background: #fff; 313 | --h-select-height: 4rem; 314 | 315 | // Hover 316 | --h-select-border-color-hover: var(--colors-grey-400); 317 | 318 | // Focus 319 | --h-select-border-color-focus: var(--colors-secondary-600); 320 | --h-select-box-shadow-focus: 0px 0px 0px 2px var(--colors-secondary-300); 321 | 322 | // Error 323 | --h-select-border-color-error: var(--colors-danger-700); 324 | 325 | appearance: none; 326 | display: inline-block; 327 | padding: var(--h-select-padding); 328 | height: var(--h-select-height); 329 | border: 1px solid var(--h-select-border-color); 330 | border-radius: var(--h-select-border-radius); 331 | font-size: var(--h-select-font-size); 332 | font-weight: normal; 333 | line-height: var(--h-select-line-height); 334 | text-decoration: none; 335 | background-color: var(--h-select-background); 336 | background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iOCIgaGVpZ2h0PSI1IiB2aWV3Qm94PSIwIDAgOCA1IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNNy44MzAwNyAwLjk0NTEyM0w0LjM3NTI4IDQuMTk3NjJDNC4yNzA3MyA0LjMxNTgxIDQuMTM0MzYgNC4zNjU4MSA0LjAwMDI2IDQuMzY1ODFDMy44NjYxNSA0LjM2NTgxIDMuNzMwMjQgNC4zMTYwOCAzLjYyNTIzIDQuMjE2NjdMMC4xNzA0NDMgMC45NDUxMjNDLTAuMDQ4NDM1NCAwLjczNjAxNyAtMC4wNTc1MjY5IDAuMzkwNTM5IDAuMTQ5MDc4IDAuMTcyMzQyQzAuMzU2NTkzIC0wLjA0ODEyODEgMC43MDMyMDggLTAuMDU0OTQ2NyAwLjkyMDQ5NiAwLjE1MTg4Nkw0LjAwMDI2IDMuMDY4TDcuMDgwMDEgMC4xNDk2MTNDNy4yOTczNSAtMC4wNTcwODMzIDcuNjQyNTUgLTAuMDQ5MjY0NiA3Ljg1MTQzIDAuMTcwOTIxQzguMDU3MzYgMC4zOTA1MzkgOC4wNDgyNiAwLjczNjAxNyA3LjgzMDA3IDAuOTQ1MTIzWiIgZmlsbD0iIzcwNzg4MCIvPgo8L3N2Zz4K"); 337 | background-repeat: no-repeat; 338 | background-position: right 10px top calc(50% + 1px); 339 | color: var(--h-select-color); 340 | transition: background-color 250ms ease, color 250ms ease-in, border-color 250ms ease-in; 341 | 342 | &:hover { 343 | border-color: var(--h-select-border-color-hover) !important; 344 | } 345 | 346 | &:focus { 347 | outline: none; 348 | border-color: var(--h-select-border-color-focus) !important; 349 | box-shadow: var(--h-select-box-shadow-focus) !important; 350 | } 351 | 352 | &.error { 353 | border-color: var(--h-select-border-color-error) !important; 354 | } 355 | 356 | &:disabled, 357 | &.disabled { 358 | opacity: .5; 359 | user-select: none; 360 | pointer-events: none; 361 | } 362 | } 363 | 364 | // Hide default browser arrow on IE 365 | .h-select::-ms-expand { 366 | display: none; 367 | } 368 | 369 | // Sizes modifier 370 | // 371 | .h-select--sm { 372 | --h-select-padding: calc(var(--spacing-02) - 1px) var(--spacing-05) calc(var(--spacing-02) - 1px) var(--spacing-02); 373 | --h-select-font-size: 1.2rem; 374 | --h-select-line-height: calc(16/12); 375 | --h-select-height: 3.2rem; 376 | } 377 | 378 | 379 | // Alerts 380 | // 381 | .h-alert { 382 | --h-alert-border: var(--colors-grey-400); 383 | --h-alert-background: var(--colors-grey-100); 384 | --h-alert-title-color: var(--colors-grey-800); 385 | --h-alert-icon-color: var(--colors-grey-600); 386 | 387 | position: relative; 388 | display: flex; 389 | align-items: top; 390 | justify-content: space-between; 391 | width: 100%; 392 | padding: var(--spacing-04); 393 | background-color: var(--h-alert-background); 394 | border: 1px solid var(--h-alert-border); 395 | border-radius: 4px; 396 | font-size: 1.4rem; 397 | } 398 | 399 | .h-alert__icon-wrapper { 400 | padding-right: var(--spacing-03); 401 | } 402 | 403 | .h-alert__icon { 404 | font-size: 1.6rem; 405 | line-height: 1.25; 406 | color: var(--h-alert-icon-color); 407 | } 408 | 409 | .h-alert__content { 410 | flex: 1; 411 | } 412 | 413 | .h-alert__description { 414 | margin-top: var(--spacing-005); 415 | font-size: 1.3rem; 416 | line-height: 1.5385; 417 | color: var(--colors-grey-800); 418 | 419 | a { 420 | color: currentColor; 421 | text-decoration-line: underline; 422 | text-decoration-color: var(--colors-grey-500); 423 | text-underline-offset: var(--spacing-01); 424 | cursor: pointer; 425 | 426 | &:hover, 427 | &:focus { 428 | text-decoration-color: currentColor; 429 | } 430 | } 431 | 432 | ul, 433 | ol, 434 | p { 435 | &:last-child { 436 | margin-bottom: 0; 437 | } 438 | } 439 | 440 | ul, 441 | ol { 442 | padding-left: 1em; 443 | } 444 | } 445 | 446 | .h-alert--warning { 447 | --h-alert-border: var(--colors-warning-400); 448 | --h-alert-background: var(--colors-warning-100); 449 | --h-alert-icon-color: var(--colors-warning-600); 450 | --h-alert-title-color: var(--colors-warning-800); 451 | } 452 | 453 | // Empty States 454 | // 455 | .empty-state { 456 | padding: var(--spacing-14) var(--spacing-10); 457 | display: flex; 458 | align-items: center; 459 | justify-content: center; 460 | flex-direction: column; 461 | gap: var(--spacing-08); 462 | 463 | .empty-state__title { 464 | font-weight: 500; 465 | font-size: 1.6rem; 466 | line-height: 1.25; 467 | color: var(--colors-grey-900); 468 | margin-top: 0 !important; 469 | margin-bottom: var(--spacing-02) !important; 470 | } 471 | } 472 | 473 | .empty-state__img { 474 | max-width: 20rem; 475 | height: auto; 476 | } 477 | 478 | .empty-state__icon { 479 | font-size: 12rem; 480 | line-height: 1; 481 | color: var(--colors-grey-300); 482 | } 483 | 484 | .empty-state__content { 485 | font-size: 1.3rem; 486 | text-align: center; 487 | color: var(--colors-grey-700); 488 | line-height: 1.5; 489 | } 490 | 491 | .empty-state__actions { 492 | display: flex; 493 | align-items: center; 494 | justify-content: center; 495 | gap: var(--spacing-02); 496 | margin-top: var(--spacing-04); 497 | 498 | a:not([class*=btn]):not([class*=h-button]) { 499 | font-size: 1.2rem; 500 | color: var(--colors-grey-700); 501 | text-decoration-line: underline; 502 | text-decoration-color: var(--colors-grey-300); 503 | text-underline-offset: var(--spacing-01); 504 | 505 | &:hover, 506 | &:focus { 507 | text-decoration-color: var(--colors-grey-400); 508 | } 509 | } 510 | } 511 | 512 | .empty-state--horizontal { 513 | flex-direction: row; 514 | 515 | .empty-state__content { 516 | text-align: left; 517 | 518 | p, 519 | ul, 520 | ol { 521 | max-width: 36rem; 522 | } 523 | } 524 | 525 | .empty-state__actions { 526 | justify-content: flex-start; 527 | } 528 | } 529 | 530 | // Avatar 531 | // 532 | .h-avatar { 533 | --h-avatar-size: 2.4rem; 534 | --h-avatar-background: var(--colors-grey-200); 535 | --h-avatar-color: var(--colors-grey-600); 536 | --h-avatar-radius: 50%; 537 | 538 | // Hover 539 | --h-avatar-background-hover: var(--colors-secondary-600); 540 | --h-avatar-color-hover: #fff; 541 | 542 | display: inline-flex; 543 | align-items: center; 544 | justify-content: center; 545 | width: var(--h-avatar-size); 546 | aspect-ratio: 1/1; 547 | border-radius: var(--h-avatar-radius); 548 | background-color: var(--h-avatar-background); 549 | color: var(--h-avatar-color); 550 | font-size: .8rem; 551 | font-weight: 500; 552 | overflow: hidden; 553 | transition: background-color 150ms ease-in, color 150ms ease-in; 554 | 555 | img { 556 | object-fit: cover; 557 | transition: opacity 150ms ease-in; 558 | } 559 | } 560 | 561 | // Sizes 562 | .h-avatar--auto { 563 | --h-avatar-size: auto; 564 | } 565 | 566 | .h-avatar--xs { 567 | --h-avatar-size: 1.6rem; 568 | } 569 | 570 | .h-avatar--sm { 571 | --h-avatar-size: 2rem; 572 | } 573 | -------------------------------------------------------------------------------- /src/browser_action/css/_search.scss: -------------------------------------------------------------------------------- 1 | // Popup header 2 | // 3 | .header { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | display: flex; 9 | align-items: center; 10 | padding: var(--spacing-02) var(--spacing-06); 11 | background-color: var(--colors-grey-900); 12 | color: #fff; 13 | min-height: 5.2rem; 14 | z-index: 1001; 15 | } 16 | 17 | .header__logo { 18 | fill: #fff; 19 | } 20 | 21 | .header__metas { 22 | margin-left: auto; 23 | display: flex; 24 | align-items: center; 25 | gap: var(--spacing-02); 26 | font-size: 1.2rem; 27 | font-weight: 500; 28 | line-height: calc(16/12); 29 | } 30 | 31 | .header__all-results { 32 | display: none; 33 | transition: color 200ms ease-in; 34 | 35 | span { 36 | text-decoration: underline; 37 | color: #fff; 38 | } 39 | 40 | &:hover, 41 | &:focus { 42 | text-decoration: none; 43 | 44 | span { 45 | text-decoration: none; 46 | color: var(--colors-grey-200); 47 | } 48 | } 49 | 50 | &::after { 51 | content: "•"; 52 | display: inline-block; 53 | color: var(--colors-grey-400); 54 | margin-left: calc(var(--spacing-02) - .25em); 55 | font-weight: 500; 56 | text-decoration: none; 57 | } 58 | } 59 | 60 | // Account 61 | .account-not-logged, 62 | .account-logged, 63 | .account-upgrade-cta { 64 | display: none; 65 | } 66 | 67 | .account-avatar { 68 | margin-left: var(--spacing-01); 69 | text-decoration: none; 70 | } 71 | 72 | .account-avatar__img { 73 | width: 2rem; 74 | height: 2rem; 75 | } 76 | 77 | .account-not-logged { 78 | .h-button:not(:first-child) { 79 | margin-left: var(--spacing-01); 80 | } 81 | } 82 | 83 | .account-upgrade-cta { 84 | margin: 0 var(--spacing-01) 0 var(--spacing-02); 85 | } 86 | 87 | 88 | // Loading placeholder 89 | // 90 | @keyframes placeHolderShimmer{ 91 | 0%{ 92 | background-position: -700px 0 93 | } 94 | 100%{ 95 | background-position: 700px 0 96 | } 97 | } 98 | 99 | #loading-placeholder { 100 | padding: 1px 24px 24px; 101 | 102 | .background-masker { 103 | animation-duration: 2s; 104 | animation-fill-mode: forwards; 105 | animation-iteration-count: infinite; 106 | animation-name: placeHolderShimmer; 107 | animation-timing-function: linear; 108 | background: #f6f6f6; 109 | background: linear-gradient(to right, #f3f3f3 8%, #eee 18%, #f3f3f3 33%); 110 | background-size: 700px 104px; 111 | position: relative; 112 | } 113 | 114 | .people-search-block { 115 | margin-top: 32px; 116 | height: 56px; 117 | } 118 | 119 | .result-name-block { 120 | margin-top: 24px; 121 | height: 16px; 122 | width: 200px; 123 | } 124 | 125 | .result-email-block { 126 | margin-top: 8px; 127 | height: 16px; 128 | width: 300px; 129 | } 130 | } 131 | 132 | // No results 133 | // 134 | .ds-no-results { 135 | .leads-manager, 136 | .results-header, 137 | .ds-results, 138 | .filters { 139 | display: none; 140 | } 141 | } 142 | 143 | 144 | // Leads list (footer) 145 | // 146 | .leads-manager { 147 | display: flex; 148 | align-items: center; 149 | gap: var(--spacing-04); 150 | justify-content: space-between; 151 | position: fixed; 152 | bottom: 0; 153 | left: 0; 154 | width: 100%; 155 | padding: var(--spacing-04) var(--spacing-06); 156 | background: #FFFFFF; 157 | border-top: 1px solid var(--colors-grey-300); 158 | box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.04); 159 | z-index: 1001; 160 | } 161 | 162 | .list-select-container { 163 | display: flex; 164 | align-items: center; 165 | gap: var(--spacing-02); 166 | 167 | label { 168 | font-weight: 500; 169 | font-size: 1.3rem; 170 | color: var(--colors-grey-800); 171 | margin: 0; 172 | } 173 | } 174 | 175 | .leads-manager__list { 176 | width: 24rem; 177 | } 178 | 179 | .leads-manager__link { 180 | font-size: 1.2rem; 181 | text-align: right; 182 | text-decoration-line: underline; 183 | color: var(--colors-grey-700); 184 | text-decoration-color: var(--colors-grey-400); 185 | transition: text-decoration-color 100ms ease-in; 186 | 187 | &:hover, 188 | &:focus { 189 | color: var(--colors-grey-800); 190 | text-decoration-color: transparent; 191 | } 192 | } 193 | 194 | // Error notifications or empty states 195 | // 196 | .error { 197 | display: none; 198 | margin: 0px 20px 90px 20px; 199 | } 200 | 201 | #linkedin-notification, 202 | #empty-notification, 203 | #blocked-notification { 204 | display: none; 205 | padding: var(--spacing-16) var(--spacing-10); 206 | } 207 | 208 | // Results header 209 | // 210 | .results-header { 211 | display: flex; 212 | align-items: center; 213 | justify-content: space-between; 214 | padding: var(--spacing-04) var(--spacing-06); 215 | } 216 | 217 | .results-header__count { 218 | margin: 0; 219 | font-size: 1.4rem; 220 | font-weight: 400; 221 | line-height: calc(24/14); 222 | color: var(--colors-grey-900); 223 | } 224 | 225 | .results-header__pattern { 226 | display: none; 227 | font-size: 1.3rem; 228 | line-height: calc(24/13); 229 | color: var(--colors-grey-700); 230 | } 231 | 232 | // Filters 233 | // 234 | .filters { 235 | display: flex; 236 | align-items: center; 237 | gap: var(--spacing-02); 238 | padding: var(--spacing-04) var(--spacing-06); 239 | background: var(--colors-grey-100); 240 | border-top: 1px solid var(--colors-grey-300); 241 | border-bottom: 1px solid var(--colors-grey-300); 242 | 243 | [data-selected-filters] { 244 | .fa-angle-down { 245 | order: 10; 246 | } 247 | 248 | &::after { 249 | content: attr(data-selected-filters); 250 | width: var(--spacing-04); 251 | height: var(--spacing-04); 252 | background-color: var(--colors-grey-700); 253 | color: #fff; 254 | border-radius: 50%; 255 | font-size: 1rem; 256 | line-height: calc(16/10); 257 | } 258 | } 259 | } 260 | 261 | .filters-dropdown { 262 | padding: 0; 263 | background: #fff; 264 | box-shadow: 0px 4px 12px rgba(29, 38, 46, 0.12), 0px 12px 24px rgba(29, 38, 46, 0.08); 265 | border: 1px solid var(--colors-grey-300); 266 | border-radius: 4px; 267 | overflow: hidden; 268 | min-width: 19rem; 269 | 270 | &:lang(fr) { 271 | min-width: 21rem; 272 | } 273 | } 274 | 275 | .filters-dropdown__submit { 276 | display: block; 277 | width: 100%; 278 | padding: var(--spacing-02); 279 | border: 0; 280 | border-top: 1px solid var(--colors-grey-300); 281 | background: #fff; 282 | text-align: center; 283 | font-size: 1.3rem; 284 | font-weight: 600; 285 | line-height: calc(20/13); 286 | color: var(--colors-secondary-700); 287 | transition: background-color 150ms ease-in; 288 | 289 | &:hover, 290 | &:focus { 291 | background-color: var(--colors-grey-100); 292 | } 293 | } 294 | 295 | .filters-choice { 296 | list-style-type: none; 297 | padding: var(--spacing-04); 298 | margin: 0; 299 | max-height: 24rem; 300 | overflow: auto; 301 | } 302 | 303 | .filters-choice__item { 304 | &:not(:first-child) { 305 | margin-top: var(--spacing-02); 306 | } 307 | } 308 | 309 | .filters-choice__checkbox, 310 | .filters-choice__radio { 311 | display: flex; 312 | align-items: center; 313 | gap: var(--spacing-02); 314 | 315 | input { 316 | margin: 0; 317 | } 318 | 319 | label { 320 | margin: 0; 321 | font-size: 1.2rem; 322 | line-height: calc(16/12); 323 | font-weight: 500; 324 | color: var(--colors-grey-800); 325 | width: max-content; 326 | } 327 | } 328 | 329 | .filters__by-name { 330 | margin-left: auto; 331 | 332 | &[aria-expanded="true"] { 333 | .fa-angle-down { 334 | transform: rotate(180deg); 335 | } 336 | } 337 | } 338 | 339 | .filters__clear { 340 | display: none; 341 | } 342 | 343 | 344 | // Email Finder 345 | // 346 | // Form 347 | .find-by-name { 348 | display: none; 349 | } 350 | 351 | .ds-finder { 352 | display: flex; 353 | align-items: center; 354 | justify-content: space-between; 355 | gap: var(--spacing-02); 356 | padding: var(--spacing-04); 357 | background-color: var(--colors-grey-100); 358 | border-bottom: 1px solid var(--colors-grey-300); 359 | } 360 | 361 | .ds-finder-form { 362 | flex: 1; 363 | display: flex; 364 | min-width: 0; 365 | border: 1px solid var(--colors-grey-300); 366 | border-radius: 4px; 367 | background-color: #fff; 368 | transition: border-color 150ms ease-in, box-shadow 150ms ease-in; 369 | 370 | &:focus-within { 371 | border-color: var(--colors-secondary-600); 372 | box-shadow: 0px 0px 0px 2px var(--colors-secondary-300); 373 | } 374 | } 375 | 376 | .ds-finder-form-name { 377 | flex: 1; 378 | display: flex; 379 | min-width: 0; 380 | align-items: center; 381 | gap: var(--spacing-02); 382 | padding: var(--spacing-01) var(--spacing-03); 383 | 384 | .ds-finder-form-name__field { 385 | padding: var(--spacing-01) 0; 386 | border: 0; 387 | height: auto; 388 | min-width: 0; 389 | border: 0; 390 | background-color: #fff; 391 | 392 | &:hover, 393 | &:focus { 394 | border: 0 !important; 395 | outline: 0; 396 | } 397 | } 398 | } 399 | 400 | .ds-finder-form-name__icon { 401 | color: var(--colors-grey-500); 402 | } 403 | 404 | .ds-finder-form__at { 405 | padding: var(--spacing-03); 406 | border-left: 1px solid var(--colors-grey-300); 407 | border-right: 1px solid var(--colors-grey-300); 408 | color: var(--colors-grey-500); 409 | font-size: 1.4rem; 410 | line-height: calc(16/14); 411 | } 412 | 413 | .ds-finder-form-company { 414 | flex: 1; 415 | display: flex; 416 | align-items: center; 417 | gap: var(--spacing-02); 418 | padding: var(--spacing-01) var(--spacing-03); 419 | max-width: 28rem; 420 | } 421 | 422 | .ds-finder-form-company__logo { 423 | height: 1.6rem; 424 | width: auto; 425 | } 426 | 427 | .ds-finder-form-company__name { 428 | flex: 1; 429 | font-size: 1.3rem; 430 | line-height: calc(20/13); 431 | white-space: nowrap; 432 | max-width: 50%; 433 | overflow: hidden; 434 | text-overflow: ellipsis; 435 | } 436 | 437 | .ds-finder-form__submit { 438 | padding: var(--spacing-02) var(--spacing-04); 439 | border-width: 0 0 0 1px; 440 | border-color: var(--colors-grey-300); 441 | border-radius: 0 4px 4px 0; 442 | background-color: #fff; 443 | font-size: 1.6rem; 444 | line-height: calc(24/16); 445 | color: var(--colors-grey-700); 446 | transition: background-color 150ms ease-in, color 150ms ease-in; 447 | 448 | &:hover, 449 | &:focus { 450 | background-color: var(--colors-grey-100); 451 | color: var(--colors-grey-900); 452 | } 453 | } 454 | 455 | .ds-finder__close { 456 | font-size: 1.6rem; 457 | color: var(--colors-grey-600); 458 | } 459 | 460 | // Result 461 | .ds-result--single { 462 | padding: var(--spacing-06); 463 | 464 | .ds-result__fullname { 465 | font-size: 1.4rem; 466 | } 467 | 468 | .ds-result__sources { 469 | padding-top: var(--spacing-04); 470 | margin-top: var(--spacing-06); 471 | border-top: 1px solid var(--colors-grey-300); 472 | } 473 | 474 | .ds-sources-list { 475 | margin-top: var(--spacing-06); 476 | } 477 | } 478 | 479 | // Results 480 | // 481 | .ds-results { 482 | padding-bottom: var(--spacing-16); 483 | } 484 | 485 | .ds-result { 486 | padding: var(--spacing-04) var(--spacing-06); 487 | border-bottom: 1px solid var(--colors-grey-300); 488 | } 489 | 490 | .ds-result__avatar { 491 | display: flex; 492 | width: 7.2rem; 493 | height: 7.2rem; 494 | margin-right: var(--spacing-02); 495 | border-radius: 50%; 496 | overflow: hidden; 497 | background: #fff; 498 | 499 | img { 500 | max-width: 100%; 501 | height: auto; 502 | object-fit: contain; 503 | } 504 | } 505 | 506 | .ds-result__data { 507 | display: flex; 508 | gap: var(--spacing-02); 509 | } 510 | 511 | .ds-result__primary { 512 | flex: 0 0 35%; 513 | max-width: 35%; 514 | } 515 | 516 | .ds-result__secondary { 517 | flex: 1; 518 | padding-left: var(--spacing-02); 519 | } 520 | 521 | .ds-result__fullname { 522 | font-weight: 600; 523 | font-size: 1.3rem; 524 | line-height: calc(20/13); 525 | color: var(--colors-grey-1000); 526 | } 527 | 528 | .ds-result__email { 529 | display: inline-block; 530 | max-width: 100%; 531 | font-weight: 500; 532 | font-size: 1.3rem; 533 | line-height: calc(20/13); 534 | color: var(--colors-grey-800); 535 | overflow: hidden; 536 | text-overflow: ellipsis; 537 | white-space: nowrap; 538 | user-select: none; 539 | 540 | &.copy-email { 541 | user-select: all; 542 | cursor: pointer; 543 | } 544 | 545 | span { 546 | filter: blur(4px); 547 | } 548 | } 549 | 550 | .ds-result__verification { 551 | margin-top: var(--spacing-01); 552 | display: flex; 553 | gap: var(--spacing-02); 554 | align-items: center; 555 | line-height: calc(16/12); 556 | } 557 | 558 | .ds-result__attribute { 559 | display: flex; 560 | align-items: center; 561 | gap: var(--spacing-01); 562 | font-size: 1.2rem; 563 | line-height: calc(16/12); 564 | color: var(--colors-grey-800); 565 | 566 | .far { 567 | color: var(--colors-grey-500); 568 | } 569 | 570 | &:not(:first-child) { 571 | margin-top: var(--spacing-01); 572 | } 573 | } 574 | 575 | .ds-result__department { 576 | text-transform: capitalize; 577 | } 578 | 579 | .ds-result__save { 580 | align-self: center; 581 | text-align: right; 582 | } 583 | 584 | .ds-result__source { 585 | align-self: center; 586 | padding: var(--spacing-005); 587 | min-width: 9rem; 588 | font-size: 1.2rem; 589 | text-align: right; 590 | color: var(--colors-grey-700); 591 | background-color: transparent; 592 | border: 0; 593 | padding: 0; 594 | 595 | &:hover, 596 | &:focus { 597 | color: var(--colors-grey-900); 598 | } 599 | } 600 | 601 | .ds-result__social { 602 | &[href*="twitter.com"] { 603 | color: var(--colors-brand-twitter-blue); 604 | } 605 | 606 | &[href*="linkedin.com"] { 607 | color: var(--colors-brand-linkedin-blue); 608 | } 609 | 610 | &:hover, 611 | &:focus { 612 | text-decoration: none; 613 | opacity: .8; 614 | } 615 | } 616 | 617 | .ds-result__sources { 618 | display: none; 619 | color: var(--colors-grey-700); 620 | font-size: 1.3rem; 621 | line-height: calc(16/13); 622 | } 623 | 624 | .ds-sources-list { 625 | margin: var(--spacing-06) 0 0 0; 626 | padding: 0; 627 | list-style-type: none; 628 | } 629 | 630 | .ds-sources-list__item { 631 | display: flex; 632 | align-items: center; 633 | gap: var(--spacing-02); 634 | 635 | &:not(:first-child) { 636 | margin-top: var(--spacing-02); 637 | } 638 | } 639 | 640 | .ds-sources-list__link { 641 | flex: 1; 642 | max-width: max-content; 643 | overflow: hidden; 644 | text-overflow: ellipsis; 645 | font-size: 1.3rem; 646 | line-height: calc(20/13); 647 | white-space: nowrap; 648 | color: var(--colors-secondary-700); 649 | 650 | &:hover, 651 | &:focus { 652 | color: var(--colors-secondary-800); 653 | text-decoration: underline; 654 | } 655 | } 656 | 657 | .ds-sources-list__date { 658 | margin-left: auto; 659 | color: var(--colors-grey-600); 660 | font-size: 1.2rem; 661 | line-height: calc(20/12); 662 | } 663 | 664 | .ds-sources-list--outdated, 665 | .ds-sources-list__item--outdated { 666 | .ds-sources-list__link { 667 | pointer-events: none; 668 | color: var(--colors-grey-600); 669 | text-decoration: line-through; 670 | } 671 | } 672 | 673 | .ds-sources__toggle { 674 | display: flex; 675 | align-items: center; 676 | gap: var(--spacing-01); 677 | padding: 0; 678 | background-color: transparent; 679 | border: 0; 680 | font-size: 1.2rem; 681 | line-height: calc(16/12); 682 | color: var(--colors-grey-700) 683 | } 684 | 685 | 686 | // Other popup rendering 687 | // 688 | .connect-container, 689 | .connect-again-container, 690 | .no-result-container, 691 | .no-result-with-filters-container, 692 | .webmail-container { 693 | display: none; 694 | height: auto; 695 | padding: var(--spacing-16) var(--spacing-10); 696 | } 697 | 698 | .no-finder-result { 699 | display: none; 700 | border-bottom: 1px solid var(--colors-grey-300); 701 | } 702 | 703 | .connect-message, 704 | .connect-again-container { 705 | margin-bottom: 15px; 706 | } 707 | 708 | #error-message-container { 709 | display: none; 710 | margin: var(--spacing-02); 711 | } 712 | 713 | .see-more { 714 | margin: var(--spacing-06); 715 | } 716 | 717 | // Feedback 718 | // 719 | .feedback-notification, 720 | .rate-notification, 721 | .contact-notification { 722 | display: none; 723 | } 724 | 725 | .feedback-block { 726 | display: flex; 727 | align-items: center; 728 | gap: var(--spacing-02); 729 | padding: var(--spacing-04) var(--spacing-06); 730 | border-radius: .4rem; 731 | margin: var(--spacing-02) var(--spacing-02) var(--spacing-04); 732 | background: var(--colors-secondary-100); 733 | min-height: 9.2rem; 734 | line-height: calc(20/14); 735 | 736 | .h-button { 737 | min-width: 8rem; 738 | justify-content: center; 739 | white-space: nowrap; 740 | } 741 | 742 | p { 743 | margin: 0; 744 | } 745 | } 746 | 747 | .feedback-block__title { 748 | font-size: 1.4rem; 749 | font-weight: 500; 750 | } 751 | 752 | .feedback-block__actions { 753 | margin-left: auto; 754 | display: flex; 755 | gap: var(--spacing-02); 756 | justify-content: flex-end; 757 | } 758 | 759 | .tooltip-inner { 760 | padding: 7px 10px; 761 | font-size: 13px; 762 | } 763 | 764 | 765 | // Hidden area to copy emails 766 | // 767 | #copy-area { 768 | position: fixed; 769 | top: 0px; 770 | left: 0px; 771 | } 772 | -------------------------------------------------------------------------------- /src/browser_action/css/_variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | /** 3 | * COLORS 4 | */ 5 | /* Brand Colors 6 | -------------------------------------------------------*/ 7 | /* Primary - Orange */ 8 | --colors-primary-100: #FEF0EC; 9 | --colors-primary-200: #FFDCD1; 10 | --colors-primary-300: #FFB59E; 11 | --colors-primary-400: #FC9272; 12 | --colors-primary-500: #FF784F; 13 | --colors-primary-600: #FA5320; 14 | --colors-primary-700: #D44519; 15 | --colors-primary-800: #903013; 16 | --colors-primary-900: #5D2514; 17 | --colors-primary-1000: #4B1A0B; 18 | 19 | /* Secondary - Blue */ 20 | --colors-secondary-100: #F2F9FF; 21 | --colors-secondary-200: #E6F4FF; 22 | --colors-secondary-300: #C8E6FF; 23 | --colors-secondary-400: #9BD2FF; 24 | --colors-secondary-500: #62B3F6; 25 | --colors-secondary-600: #0F8DF4; 26 | --colors-secondary-700: #0578D6; 27 | --colors-secondary-800: #00569C; 28 | --colors-secondary-900: #003F73; 29 | --colors-secondary-1000: #002A4D; 30 | 31 | /* Neutral - Greys */ 32 | --colors-grey-100: #F7F9FA; 33 | --colors-grey-200: #EEF0F3; 34 | --colors-grey-300: #DFE3E8; 35 | --colors-grey-400: #C7CDD4; 36 | --colors-grey-500: #A1A7AD; 37 | --colors-grey-600: #707880; 38 | --colors-grey-700: #4C555E; 39 | --colors-grey-800: #3A444F; 40 | --colors-grey-900: #28323B; 41 | --colors-grey-1000: #1D262E; 42 | 43 | 44 | /* Notifications Colors 45 | -------------------------------------------------------*/ 46 | /* Success - Green */ 47 | --colors-success-100: #F2FFF2; 48 | --colors-success-200: #E6FCE5; 49 | --colors-success-300: #C6F2C5; 50 | --colors-success-400: #94DE93; 51 | --colors-success-500: #57C555; 52 | --colors-success-600: #24A522; 53 | --colors-success-700: #078605; 54 | --colors-success-800: #056704; 55 | --colors-success-900: #035502; 56 | --colors-success-1000: #024201; 57 | 58 | /* Danger - Red */ 59 | --colors-danger-100: #FFF6F6; 60 | --colors-danger-200: #FFE1E1; 61 | --colors-danger-300: #FFC8C7; 62 | --colors-danger-400: #FEA6A5; 63 | --colors-danger-500: #FB7C79; 64 | --colors-danger-600: #EE413F; 65 | --colors-danger-700: #D91F1C; 66 | --colors-danger-800: #B60A07; 67 | --colors-danger-900: #860604; 68 | --colors-danger-1000: #640E0C; 69 | 70 | /* Warning - Orangish */ 71 | --colors-warning-100: #FFFCEB; 72 | --colors-warning-200: #FFF6C9; 73 | --colors-warning-300: #FFEFA1; 74 | --colors-warning-400: #FDE34E; 75 | --colors-warning-500: #F6D028; 76 | --colors-warning-600: #E0B115; 77 | --colors-warning-700: #B78B06; 78 | --colors-warning-800: #8E6506; 79 | --colors-warning-900: #694907; 80 | --colors-warning-1000: #4B3207; 81 | 82 | /* Support colors */ 83 | --colors-magenta-200: #F1ABFC; 84 | --colors-magenta-500: #D946EF; 85 | --colors-magenta-700: #BC26D3; 86 | 87 | --colors-emerald-200: #A7F3D0; 88 | --colors-emerald-500: #10B981; 89 | --colors-emerald-700: #047857; 90 | 91 | --colors-cardinal-200: #FDA4AF; 92 | --colors-cardinal-500: #F43F5E; 93 | --colors-cardinal-700: #BE123C; 94 | 95 | --colors-cyan-200: #A5F3FC; 96 | --colors-cyan-500: #06B6D4; 97 | --colors-cyan-700: #0E7490; 98 | 99 | 100 | /* Other brands 101 | -------------------------------------------------------*/ 102 | --colors-brand-twitter-blue: #1da1f2; 103 | --colors-brand-linkedin-blue: #0077b5; 104 | --colors-brand-facebook-blue: #3b5998; 105 | --colors-brand-youtube-red: #cd201f; 106 | --colors-brand-instagram-purple: #c13584; 107 | --colors-brand-android-green: #a4c639; 108 | --colors-brand-itunes-black: #050708; 109 | --colors-brand-github-black: #333; 110 | 111 | /* Integrations */ 112 | --colors-brand-salesforce: #009edb; 113 | --colors-brand-salesforce--rgb: 0, 158, 219; 114 | --colors-brand-hubspot: #ff7a59; 115 | --colors-brand-hubspot--rgb: 255, 122, 89; 116 | --colors-brand-pipedrive: #26292c; 117 | --colors-brand-pipedrive--rgb: 38, 41, 44; 118 | --colors-brand-zoho: #ce2232; 119 | --colors-brand-zoho--rgb: 206, 34, 50; 120 | --colors-brand-zapier: #ff4a00; 121 | --colors-brand-zapier--rgb: 255, 74, 0; 122 | --colors-brand-outlook: #0078d4; 123 | --colors-brand-outlook--rgb: 0, 120, 212; 124 | --colors-brand-gmail: #4286f5; 125 | --colors-brand-gmail--rgb: 66, 134, 245; 126 | 127 | /** 128 | * SPACING 129 | */ 130 | --spacing-base: 4px; 131 | --spacing-005: calc(var(--spacing-base) / 2); // 2px 132 | --spacing-01: calc(var(--spacing-base)); // 4px 133 | --spacing-02: calc(var(--spacing-base) * 2); // 8px 134 | --spacing-03: calc(var(--spacing-base) * 3); // 12px 135 | --spacing-04: calc(var(--spacing-base) * 4); // 16px 136 | --spacing-05: calc(var(--spacing-base) * 5); // 20px 137 | --spacing-06: calc(var(--spacing-base) * 6); // 24px 138 | --spacing-08: calc(var(--spacing-base) * 8); // 32px 139 | --spacing-10: calc(var(--spacing-base) * 10); // 40px 140 | --spacing-12: calc(var(--spacing-base) * 12); // 48px 141 | --spacing-14: calc(var(--spacing-base) * 14); // 56px 142 | --spacing-16: calc(var(--spacing-base) * 16); // 64px 143 | --spacing-20: calc(var(--spacing-base) * 20); // 80px 144 | --spacing-24: calc(var(--spacing-base) * 24); // 96px 145 | --spacing-30: calc(var(--spacing-base) * 30); // 120px 146 | 147 | /** 148 | * TYPOGRAPHY 149 | */ 150 | --fonts-family-body: "Inter UI", "Inter", -apple-system,system-ui, "Segoe UI", Roboto, Noto, Oxygen-Sans, Ubuntu, Cantrell, "Helvetica Neue", sans-serif; 151 | --fonts-family-feature-settings: "cv01", "cv02", "cv03", "cv04", "cv11"; 152 | } 153 | -------------------------------------------------------------------------------- /src/browser_action/css/browser_action.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'base'; 3 | @import 'search'; 4 | -------------------------------------------------------------------------------- /src/browser_action/js/browser_action.coffee: -------------------------------------------------------------------------------- 1 | loadAccountInformation = -> 2 | Account.get (json) -> 3 | if json == "none" 4 | $(".account-loading").hide() 5 | $(".account-not-logged").show() 6 | else 7 | $(".account-loading").hide() 8 | 9 | $(".account-calls-used").text Utilities.numberWithCommas(json.data.requests.searches.used) 10 | $(".account-calls-available").text Utilities.numberWithCommas(json.data.requests.searches.available) 11 | $(".account-avatar__img").attr "src", "https://ui-avatars.com/api/?name=" + json.data.first_name + "+" + json.data.last_name + "&background=0F8DF4&color=fff&rounded=true" 12 | $(".account-avatar__img").attr "alt", json.data.first_name + " " + json.data.last_name 13 | 14 | if json.data.plan_level == 0 15 | $(".account-upgrade-cta").show() 16 | 17 | $(".account-logged").show() 18 | 19 | displayError = (html) -> 20 | $("#error-message").html html 21 | $("html, body").animate({ scrollTop: 0 }, 300); 22 | $("#error-message-container").delay(300).slideDown() 23 | 24 | # Prepare what will be diplayed depending on the current page and domain 25 | # - If it's not on a domain name, a default page explain how it works 26 | # - If on LinkedIn, a page explains the feature is no longer available 27 | # - Otherwise, the Domain Search is launched 28 | # 29 | chrome.tabs.query { 30 | active: true 31 | currentWindow: true 32 | }, (tabs) -> 33 | 34 | # We track the event 35 | Analytics.trackEvent "Open browser popup" 36 | 37 | # We do the localization for the text in the HTML 38 | $("[data-locale]").each () -> 39 | $(this).text(chrome.i18n.getMessage($(this).data("locale"))) 40 | 41 | $("[data-locale-title]").each () -> 42 | $(this).prop("title", chrome.i18n.getMessage($(this).data("localeTitle"))) 43 | 44 | Account.getApiKey (api_key) -> 45 | # Get account information 46 | loadAccountInformation() 47 | 48 | chrome.storage.sync.get 'current_leads_list_id', (value) -> 49 | window.current_leads_list_id = value.current_leads_list_id 50 | ListSelection.appendSelector() 51 | 52 | window.api_key = api_key 53 | window.url = tabs[0].url 54 | 55 | currentDomain = new URL(tabs[0].url).hostname 56 | 57 | # We clean the subdomains when relevant 58 | Utilities.findRelevantDomain currentDomain, (domain) -> 59 | window.domain = domain 60 | 61 | # We display a special message on LinkedIn 62 | if window.domain == "linkedin.com" 63 | $("#linkedin-notification").show() 64 | $("#loading-placeholder").hide() 65 | 66 | # We display a soft 404 if there is no domain name 67 | else if window.domain == "" or window.domain.indexOf(".") == -1 68 | $("#empty-notification").show() 69 | $("#loading-placeholder").hide() 70 | 71 | else 72 | # Launch the Domain Search 73 | domainSearch = new DomainSearch 74 | domainSearch.launch() 75 | -------------------------------------------------------------------------------- /src/browser_action/js/domain_search.coffee: -------------------------------------------------------------------------------- 1 | DomainSearch = -> 2 | { 3 | # Used to display full department names 4 | department_names: { 5 | executive: chrome.i18n.getMessage("department_executive"), 6 | it: chrome.i18n.getMessage("department_it"), 7 | finance: chrome.i18n.getMessage("department_finance"), 8 | management: chrome.i18n.getMessage("department_finance"), 9 | sales: chrome.i18n.getMessage("department_sales"), 10 | legal: chrome.i18n.getMessage("department_legal"), 11 | support: chrome.i18n.getMessage("department_support"), 12 | hr: chrome.i18n.getMessage("department_hr"), 13 | marketing: chrome.i18n.getMessage("department_marketing"), 14 | communication: chrome.i18n.getMessage("department_communication"), 15 | education: chrome.i18n.getMessage("department_education"), 16 | design:chrome.i18n.getMessage("department_design"), 17 | health: chrome.i18n.getMessage("department_health"), 18 | operations: chrome.i18n.getMessage("department_operations") 19 | } 20 | 21 | launch: -> 22 | @domain = window.domain 23 | @trial = (typeof window.api_key == "undefined" || window.api_key == "") 24 | @fetch() 25 | 26 | fetch: () -> 27 | _this = @ 28 | _this.cleanSearchResults() 29 | 30 | _this.department = _this.departmentFilter() 31 | _this.type = _this.typeFilter() 32 | 33 | if _this.department or _this.type 34 | $('.filters__clear').show() 35 | else 36 | $('.filters__clear').hide() 37 | 38 | $.ajax 39 | url: Api.domainSearch(_this.domain, _this.department, _this.type, window.api_key) 40 | headers: "Email-Hunter-Origin": "chrome_extension" 41 | type: "GET" 42 | data: format: "json" 43 | dataType: "json" 44 | jsonp: false 45 | error: (xhr) -> 46 | $("#loading-placeholder").hide() 47 | $("#domain-search").show() 48 | $(".filters").css("visibility", "hidden") 49 | 50 | if xhr.status == 400 51 | displayError chrome.i18n.getMessage("something_went_wrong_with_the_query") 52 | else if xhr.status == 401 53 | $(".connect-again-container").show() 54 | else if xhr.status == 403 55 | $("#blocked-notification").show() 56 | else if xhr.status == 429 57 | unless _this.trial 58 | Account.returnRequestsError (e) -> 59 | displayError e 60 | else 61 | $(".connect-container").show() 62 | else 63 | displayError DOMPurify.sanitize(xhr.responseJSON["errors"][0]["details"]) 64 | 65 | success: (result) -> 66 | _this.webmail = result.data.webmail 67 | _this.pattern = result.data.pattern 68 | _this.accept_all = result.data.accept_all 69 | _this.verification = result.data.verification 70 | _this.organization = result.data.organization 71 | _this.results = result.data.emails 72 | _this.results_count = result.meta.results 73 | _this.offset = result.meta.offset 74 | _this.type = result.meta.params.type 75 | 76 | $("#loading-placeholder").hide() 77 | $("#domain-search").show() 78 | $(".filters").removeAttr("style") 79 | 80 | # Not logged in: we hide the Email Finder 81 | if _this.trial 82 | $(".filters__by-name").hide() 83 | $(".find-by-name").hide() 84 | 85 | _this.manageFilters() 86 | _this.clearFilters() 87 | _this.render() 88 | 89 | render: -> 90 | # Is webmail -> STOP 91 | if @webmail == true 92 | $("#domain-search").addClass("ds-no-results") 93 | $(".webmail-container .domain").text @domain 94 | $(".webmail-container").show() 95 | return 96 | 97 | # No results -> STOP 98 | if @results_count == 0 99 | if @type or @department 100 | $(".no-result-with-filters-container").show() 101 | else 102 | $("#domain-search").addClass("ds-no-results") 103 | $(".no-result-container .domain").text @domain 104 | $(".no-result-container").show() 105 | 106 | return 107 | 108 | # Display: the current domain 109 | $("#current-domain").text @domain 110 | 111 | # Remove "no-results" class 112 | $("#domain-search").removeClass("ds-no-results") 113 | 114 | # Activate dropdown 115 | $("[data-toggle='dropdown']").dropdown() 116 | 117 | # Display: complete search link or Sign up CTA 118 | unless @trial 119 | $(".header-search-link").attr "href", "https://hunter.io/search/" + @domain + "?utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=browser_popup" 120 | $(".header-search-link").show() 121 | 122 | # Display: the number of results 123 | if @results_count == 1 then s = "" else s = "s" 124 | $("#domain-search .results-header__count").html chrome.i18n.getMessage("results_for_domain", [DOMPurify.sanitize(Utilities.numberWithCommas(@results_count)), s, DOMPurify.sanitize(@domain)]) 125 | 126 | # Display: the email pattern if any 127 | if @pattern != null 128 | $(".results-header__pattern").show() 129 | $(".results-header__pattern strong").html(@addPatternTitle(@pattern) + "@" + @domain) 130 | $("[data-toggle='tooltip']").tooltip() 131 | 132 | # Email Finder 133 | $(".ds-finder-form-company__name").text @organization 134 | $(".ds-finder-form-company__logo").attr "src", "https://logo.clearbit.com/" + @domain 135 | emailFinder = new EmailFinder 136 | emailFinder.validateForm() 137 | @openFindbyName() 138 | 139 | # Display: the updated number of requests 140 | loadAccountInformation() 141 | 142 | # We count call to measure use 143 | countCall() 144 | @feedbackNotification() 145 | 146 | # Display: the results 147 | @showResults() 148 | 149 | # Render: set again an auto height on html 150 | $("html").css 151 | height: "auto" 152 | 153 | # Display: link to see more 154 | if @results_count > 10 155 | remaining_results = @results_count - 10 156 | $(".search-results").append "" + chrome.i18n.getMessage("see_all_the_results", DOMPurify.sanitize(Utilities.numberWithCommas(remaining_results))) + "" 157 | 158 | 159 | showResults: -> 160 | _this = @ 161 | @results.slice(0,10).forEach (result) -> 162 | 163 | # Sources 164 | if result.sources.length == 1 165 | result.sources_link = "1 source" 166 | else if result.sources.length >= 20 167 | result.sources_link = "20+ sources" 168 | else 169 | result.sources_link = result.sources.length + " sources" 170 | 171 | # Confidence score color 172 | if result.confidence < 30 173 | result.confidence_score_class = "low-score" 174 | else if result.confidence >= 70 175 | result.confidence_score_class = "high-score" 176 | else 177 | result.confidence_score_class = "average-score" 178 | 179 | # Save leads button 180 | unless _this.trial 181 | result.lead_button = "" 182 | else 183 | result.lead_button = "" 184 | 185 | # Full name 186 | if result.first_name != null && result.last_name != null 187 | result.full_name = DOMPurify.sanitize(result.first_name) + " " + DOMPurify.sanitize(result.last_name) 188 | 189 | if result.department 190 | result.department = DOMPurify.sanitize(_this.department_names[result.department]) 191 | 192 | 193 | Handlebars.registerHelper "userDate", (options) -> 194 | new Handlebars.SafeString(Utilities.dateInWords(options.fn(this))) 195 | 196 | Handlebars.registerHelper "ifIsVerified", (verification_status, options) -> 197 | if verification_status == "valid" 198 | return options.fn(this) 199 | options.inverse this 200 | 201 | Handlebars.registerHelper "ifIsAcceptAll", (options) -> 202 | if _this.accept_all 203 | return options.fn(this) 204 | options.inverse this 205 | 206 | # Integrate the result 207 | template = JST["src/browser_action/templates/search_results.hbs"] 208 | result_tag = $(Utilities.localizeHTML(template(result))) 209 | $(".search-results").append(result_tag) 210 | 211 | # Add the lead's data 212 | save_lead_button = result_tag.find(".save-lead-button") 213 | save_lead_button.data 214 | email: result.value 215 | lead = new LeadButton 216 | lead.saveButtonListener(save_lead_button) 217 | lead.disableSaveLeadButtonIfLeadExists(save_lead_button) 218 | 219 | # Hide beautifully if the user is not logged 220 | if _this.trial 221 | result_tag.find(".ds-result__email").removeClass("copy-email") 222 | result_tag.find(".ds-result__email").attr("title", chrome.i18n.getMessage("sign_up_to_uncover_more_emails")) 223 | result_tag.find(".ds-result__email").html result_tag.find(".ds-result__email").text().replace("**", "aaa") 224 | 225 | @openSources() 226 | $(".search-results").show() 227 | $("[data-toggle='tooltip']").tooltip() 228 | 229 | # For people not logged in, the copy and verification functions are not displayed 230 | if _this.trial 231 | $(".ds-result__verification").remove() 232 | else 233 | @searchVerificationListener() 234 | Utilities.copyEmailListener() 235 | 236 | 237 | searchVerificationListener: -> 238 | _this = @ 239 | $(".verification-link").unbind("click").click -> 240 | 241 | verification_link_tag = $(this) 242 | verification_result_tag = $(this).parent().find(".verification-result") 243 | 244 | email = verification_link_tag.data("email") 245 | 246 | return if !email 247 | 248 | verification_link_tag.html(" " + chrome.i18n.getMessage("verifying") + "…") 249 | verification_link_tag.attr("disabled", "true") 250 | 251 | # Launch the API call 252 | $.ajax 253 | url: Api.emailVerifier(email, window.api_key) 254 | headers: "Email-Hunter-Origin": "chrome_extension" 255 | type: "GET" 256 | data: format: "json" 257 | dataType: "json" 258 | jsonp: false 259 | error: (xhr, statusText, err) -> 260 | verification_link_tag.removeAttr("disabled") 261 | verification_link_tag.show() 262 | 263 | if xhr.status == 400 264 | displayError chrome.i18n.getMessage("something_went_wrong_with_the_query") 265 | else if xhr.status == 401 266 | $(".connect-again-container").show() 267 | else if xhr.status == 403 268 | $("#domain-search").hide() 269 | $("#blocked-notification").show() 270 | else if xhr.status == 429 271 | unless _this.trial 272 | Account.returnRequestsError (e) -> 273 | displayError e 274 | else 275 | $(".connect-container").show() 276 | else 277 | displayError DOMPurify.sanitize(xhr.responseJSON["errors"][0]["details"]) 278 | 279 | 280 | success: (result, statusText, xhr) -> 281 | if xhr.status == 202 282 | verification_link_tag.removeAttr("disabled") 283 | verification_link_tag.html(" " + chrome.i18n.getMessage("retry")) 284 | displayError chrome.i18n.getMessage("email_verification_takes_longer") 285 | 286 | else if xhr.status == 222 287 | verification_link_tag.removeAttr("disabled") 288 | verification_link_tag.html(" " + chrome.i18n.getMessage("retry")) 289 | displayError DOMPurify.sanitize(result.errors[0].details) 290 | return 291 | 292 | else 293 | verification_link_tag.remove() 294 | 295 | if result.data.status == "valid" 296 | verification_result_tag.html(" 297 | 298 | 299 | 300 | " + result.data.score + "% 301 | 302 | ") 303 | else if result.data.status == "invalid" 304 | verification_result_tag.html(" 305 | 306 | 307 | 308 | " + result.data.score + "% 309 | 310 | ") 311 | else if result.data.status == "accept_all" 312 | verification_result_tag.html(" 313 | 314 | 315 | 316 | " + result.data.score + "% 317 | 318 | ") 319 | else 320 | verification_result_tag.html(" 321 | 322 | 323 | 324 | " + result.data.score + "% 325 | 326 | ") 327 | 328 | # We update the number of requests 329 | loadAccountInformation() 330 | 331 | 332 | openSources: -> 333 | $(".sources-link").unbind("click").click (e) -> 334 | if $(this).parents(".ds-result").find(".ds-result__sources").is(":visible") 335 | $(this).parents(".ds-result").find(".ds-result__sources").slideUp 300 336 | $(this).find(".fa-angle-up").removeClass("fa-angle-up").addClass("fa-angle-down") 337 | else 338 | $(this).parents(".ds-result").find(".ds-result__sources").slideDown 300 339 | $(this).find(".fa-angle-down").removeClass("fa-angle-down").addClass("fa-angle-up") 340 | 341 | openFindbyName: -> 342 | $(".filters__by-name").unbind("click").click (e) -> 343 | if $("#find-by-name").is(":visible") 344 | $(this).attr "aria-expanded", "false" 345 | $("#find-by-name").hide() 346 | else 347 | $(this).attr "aria-expanded", "true" 348 | $("#find-by-name").show() 349 | $("#full-name-field").focus() 350 | 351 | 352 | manageFilters: -> 353 | _this = @ 354 | $(document).on 'click', '.dropdown .dropdown-menu', (e) -> 355 | e.stopPropagation() 356 | 357 | $('.apply-filters').unbind().on "click", -> 358 | checked = $(this).parent().find('[type="checkbox"]:checked, [type="radio"]:checked') 359 | checkedCount = checked.length 360 | dropdownContainer = $(this).parents(".dropdown") 361 | 362 | dropdownContainer.removeClass("open") 363 | dropdownContainer.find('.h-button[data-toggle]').attr("aria-expanded", "false") 364 | 365 | if checkedCount > 0 366 | dropdownContainer.find('.h-button[data-toggle]').attr("data-selected-filters", checkedCount) 367 | else 368 | dropdownContainer.find('.h-button[data-toggle]').removeAttr("data-selected-filters") 369 | 370 | _this.fetch() 371 | 372 | typeFilter: -> 373 | value = $("#type-filters [type='radio']:checked").val() 374 | 375 | departmentFilter: -> 376 | department = '' 377 | $("#departments-filters [type='checkbox']").each -> 378 | if $(this).is(":checked") 379 | department = $(this).val() 380 | return false 381 | 382 | department 383 | 384 | clearFilters: -> 385 | _this = @ 386 | $(".clear-filters").unbind().on "click", -> 387 | $('.filters').find('[type="checkbox"]:checked, [type="radio"]:checked').each -> 388 | $(this).prop("checked", false) 389 | 390 | $('.filters [data-selected-filters]').removeAttr("data-selected-filters") 391 | _this.fetch() 392 | 393 | 394 | addPatternTitle: (pattern) -> 395 | pattern = pattern 396 | .replace("{first}", "{first}") 397 | .replace("{last}", "{last}") 398 | .replace("{f}", "{f}") 399 | .replace("{l}", "{l}") 400 | pattern 401 | 402 | 403 | cleanSearchResults: -> 404 | $("#loading-placeholder").show() 405 | $(".search-results").html "" 406 | $(".no-result-with-filters-container").hide() 407 | 408 | 409 | feedbackNotification: -> 410 | chrome.storage.sync.get "calls_count", (value) -> 411 | if value["calls_count"] >= 10 412 | chrome.storage.sync.get "has_given_feedback", (value) -> 413 | if typeof value["has_given_feedback"] == "undefined" 414 | $(".feedback-notification").slideDown 300 415 | 416 | # Ask to note the extension 417 | $("#open-rate-notification").click -> 418 | $(".feedback-notification").slideUp 300 419 | $(".rate-notification").slideDown 300 420 | 421 | # Ask to give use feedback 422 | $("#open-contact-notification").click -> 423 | $(".feedback-notification").slideUp 300 424 | $(".contact-notification").slideDown 300 425 | 426 | $(".feedback-link").click -> 427 | chrome.storage.sync.set "has_given_feedback": true 428 | } 429 | -------------------------------------------------------------------------------- /src/browser_action/js/email_finder.coffee: -------------------------------------------------------------------------------- 1 | EmailFinder = -> 2 | { 3 | full_name: @full_name 4 | domain: @domain 5 | email: @email 6 | score: @score 7 | sources: @sources 8 | 9 | validateForm: -> 10 | _this = @ 11 | 12 | $("#email-finder-search").unbind().submit -> 13 | if ( 14 | $("#full-name-field").val().indexOf(" ") == -1 || 15 | $("#full-name-field").val().length <= 4 16 | ) 17 | $("#full-name-field").tooltip(title: chrome.i18n.getMessage("enter_full_name_to_find_email")).tooltip("show") 18 | else 19 | _this.submit() 20 | 21 | false 22 | 23 | submit: -> 24 | $(".ds-finder-form__submit .far").removeClass("fa-search").addClass("fa-spinner-third fa-spin") 25 | @domain = window.domain 26 | @full_name = $("#full-name-field").val() 27 | @fetch() 28 | 29 | fetch: -> 30 | @cleanFinderResults() 31 | 32 | if typeof $("#full-name-field").data("bs.tooltip") != "undefined" 33 | $("#full-name-field").tooltip("destroy") 34 | 35 | _this = @ 36 | $.ajax 37 | url: Api.emailFinder(_this.domain, _this.full_name, window.api_key) 38 | headers: "Email-Hunter-Origin": "chrome_extension" 39 | type: "GET" 40 | data: format: "json" 41 | dataType: "json" 42 | jsonp: false 43 | error: (xhr, statusText, err) -> 44 | $(".ds-finder-form__submit .far").removeClass("fa-spinner-third fa-spin").addClass("fa-search") 45 | 46 | if xhr.status == 400 47 | displayError chrome.i18n.getMessage("something_went_wrong_with_the_query") 48 | else if xhr.status == 401 49 | $(".connect-again-container").show() 50 | else if xhr.status == 403 51 | $("#domain-search").hide() 52 | $("#blocked-notification").show() 53 | else if xhr.status == 429 54 | unless _this.trial 55 | Account.returnRequestsError (e) -> 56 | displayError e 57 | else 58 | $(".connect-container").show() 59 | else 60 | displayError DOMPurify.sanitize(xhr.responseJSON["errors"][0]["details"]) 61 | 62 | success: (result) -> 63 | if result.data.email == null 64 | $(".ds-finder-form__submit .far").removeClass("fa-spinner-third fa-spin").addClass("fa-search") 65 | $(".no-finder-result .person-name").text(_this.full_name) 66 | $(".no-finder-result").slideDown 200 67 | else 68 | _this.domain = result.data.domain 69 | _this.email = result.data.email 70 | _this.score = result.data.score 71 | _this.accept_all = result.data.accept_all 72 | _this.verification = result.data.verification 73 | _this.position = result.data.position 74 | _this.company = result.data.company 75 | _this.twitter = result.data.twitter 76 | _this.linkedin = result.data.linkedin 77 | _this.sources = result.data.sources 78 | _this.first_name = Utilities.toTitleCase(result.data.first_name) 79 | _this.last_name = Utilities.toTitleCase(result.data.last_name) 80 | 81 | _this.render() 82 | 83 | render: -> 84 | $(".ds-finder-form__submit .far").removeClass("fa-spinner-third fa-spin").addClass("fa-search") 85 | 86 | # Display: complete search link or Sign up CTA 87 | unless @trial 88 | $(".header-search-link").attr "href", "https://hunter.io/search/" + @domain + "?utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=browser_popup" 89 | $(".header-search-link").show() 90 | 91 | # Confidence score color 92 | if @score < 30 93 | @confidence_score_class = "low-score" 94 | else if @score > 70 95 | @confidence_score_class = "high-score" 96 | else 97 | @confidence_score_class = "average-score" 98 | 99 | # Display: the method used 100 | if @sources.length > 0 101 | s = if @sources.length == 1 then "" else "s" 102 | @method = chrome.i18n.getMessage("we_found_this_email_on_the_web", [@sources.length, s]) 103 | else 104 | @method = "This email address is our best guess for this person. We haven't found it on the web." 105 | 106 | # Prepare the template 107 | Handlebars.registerHelper "ifIsVerified", (verification_status, options) -> 108 | if verification_status == "valid" 109 | return options.fn(this) 110 | options.inverse this 111 | 112 | Handlebars.registerHelper "md5", (options) -> 113 | new Handlebars.SafeString(Utilities.MD5(options.fn(this))) 114 | 115 | template = JST["src/browser_action/templates/finder.hbs"] 116 | finder_html = $(Utilities.localizeHTML(template(@))) 117 | 118 | # Generate the DOM with the template and display it 119 | $("#email-finder").html finder_html 120 | $("#email-finder").slideDown 200 121 | 122 | # Display: the sources if any 123 | if @sources.length > 0 124 | $(".ds-result__sources").show() 125 | 126 | # Display: the tooltips 127 | $("[data-toggle='tooltip']").tooltip() 128 | 129 | # Event: the copy action 130 | Utilities.copyEmailListener() 131 | 132 | # Display: the button to save the lead 133 | lead_button = $(".ds-result--single .save-lead-button") 134 | lead_button.data 135 | first_name: @first_name 136 | last_name: @last_name 137 | email: @email 138 | confidence_score: @score 139 | 140 | lead = new LeadButton 141 | lead.saveButtonListener(lead_button) 142 | lead.disableSaveLeadButtonIfLeadExists(lead_button) 143 | 144 | cleanFinderResults: -> 145 | $("#email-finder").html "" 146 | $("#email-finder").slideUp 200 147 | $(".no-finder-result").slideUp 200 148 | } 149 | -------------------------------------------------------------------------------- /src/browser_action/js/lead_button.coffee: -------------------------------------------------------------------------------- 1 | LeadButton = -> 2 | { 3 | first_name: @first_name 4 | last_name: @last_name 5 | position: @position 6 | email: @email 7 | company: @organization 8 | website: window.domain 9 | source: "Hunter (Domain Search)" 10 | phone_number: @phone_number 11 | linkedin_url: @linkedin 12 | twitter: @twitter 13 | 14 | saveButtonListener: (selector) -> 15 | _this = this 16 | $(selector).unbind("click").click -> 17 | lead_button = $(this) 18 | lead = lead_button.data() 19 | 20 | lead_button.prop "disabled", true 21 | lead_button.html("") 22 | 23 | attributes = [ 24 | "first_name" 25 | "last_name" 26 | "position" 27 | "email" 28 | "company" 29 | "website" 30 | "source" 31 | "phone_number" 32 | "linkedin_url" 33 | "twitter" 34 | "email" 35 | ] 36 | 37 | lead = {} 38 | attributes.forEach (attribute) -> 39 | if _this[attribute] == undefined 40 | lead[attribute] = lead_button.data(attribute) 41 | else 42 | lead[attribute] = _this[attribute] 43 | 44 | if window.current_leads_list_id 45 | lead["leads_list_id"] = window.current_leads_list_id 46 | 47 | _this.save(lead, lead_button) 48 | 49 | save: (lead, button) -> 50 | $.ajax 51 | url: Api.leads(api_key) 52 | headers: "Email-Hunter-Origin": "chrome_extension" 53 | type: "POST" 54 | data: lead 55 | dataType: "json" 56 | jsonp: false 57 | error: (xhr, statusText, err) -> 58 | button.replaceWith(" " + chrome.i18n.getMessage("failed") + "") 59 | displayError DOMPurify.sanitize(xhr.responseJSON["errors"][0]["details"]) 60 | 61 | if xhr.status == 422 62 | window.current_leads_list_id = undefined 63 | 64 | success: (response) -> 65 | button.replaceWith(" " + chrome.i18n.getMessage("saved") + "") 66 | 67 | disableSaveLeadButtonIfLeadExists: (selector) -> 68 | $(selector).each -> 69 | lead_button = $(this) 70 | lead = $(this).data() 71 | $.ajax 72 | url: Api.leadsExist(lead.email, window.api_key) 73 | headers: "Email-Hunter-Origin": "chrome_extension" 74 | type: "GET" 75 | data: format: "json" 76 | dataType: "json" 77 | jsonp: false 78 | success: (response) -> 79 | if response.data.id != null 80 | lead_button.replaceWith(" " + chrome.i18n.getMessage("saved") + "") 81 | } 82 | -------------------------------------------------------------------------------- /src/browser_action/js/list_selection.coffee: -------------------------------------------------------------------------------- 1 | ListSelection = 2 | appendSelector: -> 3 | _this = this 4 | _this.getLeadsLists (json) -> 5 | if json != "none" 6 | $(".list-select-container").append "" 7 | 8 | # We determine the selected list 9 | if window.current_leads_list_id 10 | selected_list_id = window.current_leads_list_id 11 | else 12 | selected_list_id = json.data.leads_lists[0].id 13 | 14 | # We add all the lists in the select field 15 | json.data.leads_lists.forEach (val, i) -> 16 | if parseInt(selected_list_id) == parseInt(val.id) 17 | selected = "selected='selected'" 18 | else 19 | selected = "" 20 | $(".leads-manager__list").append "" 21 | 22 | # We add a link to the current list 23 | $(".leads-manager__link").attr "href", "https://hunter.io/leads?leads_list_id=" + selected_list_id + "&utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=browser_popup" 24 | $(".leads-manager__list").append "" 25 | 26 | _this.updateCurrent() 27 | 28 | updateCurrent: -> 29 | $(".leads-manager__list").on "change", -> 30 | if $(this).val() == "new_list" 31 | Utilities.openInNewTab "https://hunter.io/leads-lists/new?utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension" 32 | else 33 | chrome.storage.sync.set "current_leads_list_id": $(this).val() 34 | window.current_leads_list_id = $(this).val() 35 | $(".leads-manager__link").attr "href", "https://hunter.io/leads?leads_list_id="+$(this).val()+"&utm_source=chrome_extension&utm_medium=chrome_extension&utm_campaign=extension&utm_content=browser_popup" 36 | 37 | getLeadsLists: (callback) -> 38 | Account.getApiKey (api_key) -> 39 | if api_key != "" 40 | $.ajax 41 | url: Api.leadsList(window.api_key) 42 | headers: "Email-Hunter-Origin": "chrome_extension" 43 | type: "GET" 44 | dataType: "json" 45 | jsonp: false 46 | success: (json) -> 47 | callback json 48 | else 49 | callback "none" 50 | -------------------------------------------------------------------------------- /src/browser_action/templates/departments.hbs: -------------------------------------------------------------------------------- 1 | {{#each departments}} 2 | {{#ifGreaterThanZero this.[1]}} 3 |
4 | {{#departmentName}}{{this.[0]}}{{/departmentName}}: {{this.[1]}} 5 |
6 | {{/ifGreaterThanZero}} 7 | {{/each}} 8 | {{#ifGreaterThanZero departments.[3].[1]}} 9 |
●●●
10 | {{/ifGreaterThanZero}} 11 | -------------------------------------------------------------------------------- /src/browser_action/templates/finder.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
{{first_name}} {{last_name}}
10 | 11 |
12 | {{#ifIsVerified verification.status}} 13 | 14 | 15 | 16 | {{score}}% 17 | 18 | 19 | {{else}} 20 | {{#ifIsAcceptAll}} 21 | 22 | 23 | 24 | {{score}}% 25 | 26 | 27 | {{else}} 28 |
29 | 30 | 31 | 32 | {{score}}% 33 | 34 | 35 |
36 | {{/ifIsAcceptAll}} 37 | {{/ifIsVerified}} 38 |
39 |
40 |
41 | {{#if phone_number}} 42 |
43 | 44 | {{phone_number}} 45 |
46 | {{/if}} 47 | {{#if position}} 48 |
49 | 50 | {{position}} 51 |
52 | {{else if department}} 53 |
54 | 55 | {{department}} 56 | 57 |
58 | {{/if}} 59 |
60 | {{#if linkedin}} 61 | 64 | {{/if}} 65 | {{#if twitter}} 66 | 69 | {{/if}} 70 |
71 |
72 |
73 | 74 |
75 |
76 | 77 | {{#if sources}} 78 |
79 |
{{{method}}}
80 | 97 |
98 | {{/if}} 99 |
100 | -------------------------------------------------------------------------------- /src/browser_action/templates/search_results.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{#if full_name}} 5 |
{{full_name}}
6 | {{/if}} 7 | 8 |
9 | {{#ifIsVerified verification.status}} 10 | 11 | 12 | 13 | {{confidence}}% 14 | 15 | 16 | {{else}} 17 | {{#ifIsAcceptAll}} 18 | 19 | 20 | 21 | {{confidence}}% 22 | 23 | 24 | {{else}} 25 |
26 | 27 | 28 | 29 | {{confidence}}% 30 | 31 | 32 |
33 | 36 | {{/ifIsAcceptAll}} 37 | {{/ifIsVerified}} 38 |
39 |
40 |
41 | {{#if phone_number}} 42 |
43 | 44 | {{phone_number}} 45 |
46 | {{/if}} 47 | {{#if position}} 48 |
49 | 50 | {{position}} 51 |
52 | {{else if department}} 53 |
54 | 55 | {{department}} 56 | 57 |
58 | {{/if}} 59 |
60 | {{#if linkedin}} 61 | 64 | {{/if}} 65 | {{#if twitter}} 66 | 69 | {{/if}} 70 |
71 |
72 |
73 | {{{lead_button}}} 74 |
75 | 79 |
80 | 81 | {{#if sources}} 82 |
83 | 100 |
101 | {{/if}} 102 |
103 | -------------------------------------------------------------------------------- /src/content_script/css/websites-sources.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Hightlight of the email address found on the page 3 | */ 4 | 5 | .hunter-email { 6 | background-color: yellow; 7 | color: #222; 8 | display: inline-block; 9 | padding: 0 2px; 10 | } 11 | 12 | /* 13 | Pointer of Hunter that shows the email address found 14 | */ 15 | 16 | #hunter-email-pointer { 17 | display: none; 18 | position: absolute; 19 | width: 50px; 20 | filter: drop-shadow(0px 1px 2px rgba(0,0,0,0.5)); 21 | z-index: 1000001; 22 | border: 0; 23 | margin: 0; 24 | padding: 0; 25 | } 26 | 27 | /* 28 | Message that indicates the status of the search of the email address on the 29 | page. 30 | */ 31 | 32 | #hunter-email-status { 33 | display: none; 34 | position: fixed; 35 | box-sizing: content-box; 36 | right: 20px; 37 | top: 20px; 38 | background-color: #fff; 39 | color: #fff; 40 | border: 0; 41 | border-radius: 8px; 42 | padding: 25px; 43 | font-size: 13px; 44 | width: 280px; 45 | height: 100px; 46 | box-shadow: 0 0px 40px 0px rgba(0,0,0,0.3); 47 | z-index: 1000000001; 48 | } 49 | 50 | #hunter-email-status-close { 51 | display: none; 52 | position: fixed; 53 | right: 47px; 54 | top: 39px; 55 | font-size: 28px; 56 | color: #aaa; 57 | z-index: 1000000002; 58 | } 59 | 60 | #hunter-email-status-close:hover { 61 | cursor: pointer; 62 | color: #888; 63 | } 64 | -------------------------------------------------------------------------------- /src/content_script/js/hunter-authentication.coffee: -------------------------------------------------------------------------------- 1 | if location.pathname == "/chrome/welcome" or location.pathname == "/firefox/welcome" or location.pathname == "/search" 2 | 3 | # Hunter extension can be used without authentification but the data returned by 4 | # the API is limited. When the quota is reached, the user is invited to log in. 5 | # The extention will read the API key on the website and store it in 6 | # Chrome local storage. 7 | # 8 | api_key = document.getElementById("api_key").innerHTML.trim() 9 | Account.setApiKey api_key 10 | -------------------------------------------------------------------------------- /src/content_script/js/hunter-extension-detection.coffee: -------------------------------------------------------------------------------- 1 | # We send 2 data points to the web app: 2 | # - That the extension is installed. 3 | # - The version currently installed. Can be used to debug with support. 4 | # 5 | $("#is-extension-installed").val true 6 | $("#extension-version").val chrome.runtime.getManifest().version 7 | -------------------------------------------------------------------------------- /src/content_script/js/websites-source.coffee: -------------------------------------------------------------------------------- 1 | # When the page loads, if it comes from a source in Hunter products, there is 2 | # a hash at the end with the email address to highlight. It will search it in 3 | # this order: 4 | # 1. Visible email address? 5 | # 2. Email address in a mailto link? 6 | # 3. Email address visible elsewhere in the code? 7 | 8 | PageContent = 9 | getEmailInHash: -> 10 | if window.location.hash 11 | hash = window.location.hash 12 | if hash.indexOf(":") != -1 and hash.split(":")[0] 13 | email = hash.split(":")[1] 14 | if @validateEmail(email) 15 | email 16 | else 17 | false 18 | else 19 | false 20 | else 21 | false 22 | 23 | validateEmail: (email) -> 24 | re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 25 | re.test email 26 | 27 | highlightEmail: (email) -> 28 | containers = @getVisibleEmailContainers(email) 29 | if containers.length > 0 30 | # We add a tag around the matching visible email addresses to highlight them 31 | $(containers).html($(containers[0]).html().replace(email, "" + email + "")) 32 | @scrollToEmail() 33 | @addLocationIcon() 34 | @displayMessage "found", email, containers.length 35 | else 36 | # Next check: is it in a mailto address? 37 | @highlightMailto email 38 | 39 | highlightMailto: (email) -> 40 | if $("a[href=\"mailto:" + email + "\"]:visible").length 41 | $("a[href=\"mailto:" + email + "\"]").addClass "hunter-email" 42 | @scrollToEmail() 43 | @addLocationIcon() 44 | @displayMessage "mailto", email, 1 45 | else 46 | @searchCode(email) 47 | 48 | searchCode: (email) -> 49 | if $("html").html().indexOf(email) != -1 50 | @displayMessage "code", email, 0 51 | else 52 | @displayMessage "notfound", email, 0 53 | 54 | scrollToEmail: -> 55 | $("html, body").animate { scrollTop: $(".hunter-email:first").offset().top - 300 }, 500 56 | 57 | addLocationIcon: -> 58 | setTimeout (-> 59 | $(".hunter-email").each (index) -> 60 | emailEl = $(this) 61 | position = emailEl.offset() 62 | emailWidth = emailEl.outerWidth() 63 | emailHeight = emailEl.outerHeight() 64 | $("body").prepend "\"Here" 65 | $("#hunter-email-pointer").css 66 | "top": position.top - 63 67 | "left": position.left + emailWidth / 2 - 25 68 | $("#hunter-email-pointer").fadeIn 300 69 | ), 1500 70 | 71 | displayMessage: (message, email, count) -> 72 | src = chrome.runtime.getURL("/html/source_popup.html") + "?email=" + email + "&count=" + count + "&message=" + message 73 | $("body").prepend "" 74 | $("body").prepend "
×
" 75 | $("#hunter-email-status, #hunter-email-status-close").fadeIn 300 76 | 77 | $("#hunter-email-status-close").on "click", -> 78 | $("#hunter-email-status, #hunter-email-status-close, #hunter-email-pointer").fadeOut() 79 | 80 | getVisibleEmailContainers: (email) -> 81 | return $("body, body *").contents().filter(-> 82 | @nodeType == 3 and @nodeValue.indexOf(email) >= 0 83 | ).parent ":visible" 84 | 85 | email = PageContent.getEmailInHash() 86 | if email 87 | PageContent.highlightEmail email 88 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extension_name__", 3 | "short_name": "Hunter", 4 | "version": "3.1.4", 5 | "manifest_version": 3, 6 | "description": "__MSG_extension_description__", 7 | "homepage_url": "https://hunter.io", 8 | "icons": { 9 | "16": "img/icon16.png", 10 | "48": "img/icon48.png", 11 | "128": "img/icon128.png" 12 | }, 13 | "default_locale": "en", 14 | "action": { 15 | "default_icon": { 16 | "19": "img/icon19.png", 17 | "38": "img/icon38.png" 18 | }, 19 | "default_title": "Hunter", 20 | "default_popup": "html/browser_popup.html" 21 | }, 22 | "permissions": [ 23 | "tabs", 24 | "storage" 25 | ], 26 | "host_permissions": [ 27 | "https://*.hunter.io/" 28 | ], 29 | "background": { 30 | "service_worker": "background.js" 31 | }, 32 | "content_scripts": [ 33 | { 34 | "matches": [ 35 | "*://*.hunter.io/*" 36 | ], 37 | "js": [ 38 | "js/lib/jquery.min.js", 39 | "js/shared.js", 40 | "js/hunter_content_script.js" 41 | ] 42 | }, 43 | { 44 | "matches": [ 45 | "*://*/*" 46 | ], 47 | "js": [ 48 | "js/lib/jquery.min.js", 49 | "js/lib/purify.min.js", 50 | "js/shared.js", 51 | "js/websites_content_script.js" 52 | ], 53 | "css": [ 54 | "css/websites-sources.css" 55 | ] 56 | } 57 | ], 58 | "web_accessible_resources": [{ 59 | "resources": ["img/location_icon.png", "html/source_popup.html"], 60 | "matches": ["*://*/*"] 61 | }] 62 | } 63 | -------------------------------------------------------------------------------- /src/shared/css/fonts.css: -------------------------------------------------------------------------------- 1 | /* 2 | Font Awesome 3 | */ 4 | 5 | @font-face { 6 | font-family: 'Font Awesome 6 Brands'; 7 | font-style: normal; 8 | font-weight: normal; 9 | src: url("chrome-extension://__MSG_@@extension_id__/fonts/fa-brands-400.woff2") format("woff2"); 10 | } 11 | 12 | @font-face { 13 | font-family: 'Font Awesome 6 Pro'; 14 | font-style: normal; 15 | font-weight: 400; 16 | src: url("chrome-extension://__MSG_@@extension_id__/fonts/fa-regular-400.woff2") format("woff2"); 17 | } 18 | 19 | @font-face { 20 | font-family: 'Font Awesome 6 Pro'; 21 | font-style: normal; 22 | font-weight: 900; 23 | src: url("chrome-extension://__MSG_@@extension_id__/fonts/fa-solid-900.woff2") format("woff2"); 24 | } 25 | 26 | 27 | /* 28 | Inter 29 | */ 30 | 31 | @font-face { 32 | font-family: 'Inter UI'; 33 | font-style: normal; 34 | font-weight: normal; 35 | src: url('chrome-extension://__MSG_@@extension_id__/fonts/Inter-Regular.woff2') format('woff2'); 36 | } 37 | 38 | @font-face { 39 | font-family: 'Inter UI'; 40 | font-style: normal; 41 | font-weight: 500; 42 | src: url('chrome-extension://__MSG_@@extension_id__/fonts/Inter-Medium.woff2') format('woff2'); 43 | } 44 | 45 | @font-face { 46 | font-family: 'Inter UI'; 47 | font-style: normal; 48 | font-weight: 600; 49 | src: url('chrome-extension://__MSG_@@extension_id__/fonts/Inter-SemiBold.woff2') format('woff2'); 50 | } 51 | -------------------------------------------------------------------------------- /src/shared/css/lib/bootstrap-tooltip.min.css: -------------------------------------------------------------------------------- 1 | .tooltip.top .tooltip-arrow,.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip{position:absolute;z-index:1070;display:block;font-family:Lato,Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{left:50%;margin-left:-5px}.tooltip.top-left .tooltip-arrow{right:5px;margin-bottom:-5px}.tooltip.top-right .tooltip-arrow{left:5px;margin-bottom:-5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{top:0;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px}.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px}.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px} 2 | -------------------------------------------------------------------------------- /src/shared/css/lib/fontawesome-all.css: -------------------------------------------------------------------------------- 1 | /* Font Awesome v6 */ 2 | .fa, 3 | .fas, 4 | .fa-solid, 5 | .far, 6 | .fa-regular, 7 | .fab, 8 | .fa-brands { 9 | -moz-osx-font-smoothing: grayscale; 10 | -webkit-font-smoothing: antialiased; 11 | display: var(--fa-display, inline-block); 12 | font-family: "Font Awesome 6 Pro"; 13 | font-style: normal; 14 | font-variant: normal; 15 | line-height: 1; 16 | text-rendering: auto; } 17 | 18 | .far { 19 | font-weight: 400; 20 | } 21 | 22 | .fas { 23 | font-weight: 900; 24 | } 25 | 26 | .fa-shield::before { 27 | content: "\f132"; } 28 | 29 | .fa-shield-check::before { 30 | content: "\f2f7"; } 31 | 32 | .fa-shield-exclamation::before { 33 | content: "\e247"; } 34 | 35 | .fa-shield-halved::before { 36 | content: "\f3ed"; } 37 | 38 | .fa-shield-xmark::before { 39 | content: "\e24c"; } 40 | 41 | .fa-shield-slash::before { 42 | content: "\e24b"; } 43 | 44 | .fa-user-shield::before { 45 | content: "\f505"; } 46 | 47 | .fa-xmark::before { 48 | content: "\f00d"; } 49 | 50 | .fa-spinner-third::before { 51 | content: "\f3f4"; } 52 | 53 | 54 | /* Brands */ 55 | .fab, 56 | .fa-brands { 57 | font-family: 'Font Awesome 6 Brands'; 58 | font-weight: 400; 59 | } 60 | 61 | .fa-linkedin:before { 62 | content: "\f08c"; } 63 | 64 | .fa-linkedin-in:before { 65 | content: "\f0e1"; } 66 | 67 | .fa-twitter:before { 68 | content: "\f099"; } 69 | 70 | .fa-twitter-square:before { 71 | content: "\f081"; } 72 | 73 | .fa-external-link::before { 74 | content: "\f08e"; } 75 | 76 | .fa-plus::before { 77 | content: "\2b"; } 78 | 79 | .fa-angle-up::before { 80 | content: "\f106"; } 81 | 82 | .fa-angle-down::before { 83 | content: "\f107"; } 84 | 85 | .fa-times::before { 86 | content: "\f00d"; } 87 | 88 | .fa-exclamation-triangle::before { 89 | content: "\f071"; } 90 | 91 | .fa-question::before { 92 | content: "\3f"; } 93 | 94 | .fa-browser::before { 95 | content: "\f37e"; } 96 | 97 | .fa-warning::before { 98 | content: "\f071"; } 99 | 100 | .fa-check::before { 101 | content: "\f00c"; } 102 | 103 | .fa-thumbs-down::before { 104 | content: "\f165"; } 105 | 106 | .fa-thumbs-up::before { 107 | content: "\f164"; } 108 | 109 | .fa-briefcase::before { 110 | content: "\f0b1"; } 111 | 112 | .fa-phone::before { 113 | content: "\f095"; } 114 | 115 | .fa-circle-user::before { 116 | content: "\f2bd"; } 117 | 118 | .fa-user-lock::before { 119 | content: "\f502"; } 120 | 121 | .fa-search::before { 122 | content: "\f002"; } 123 | 124 | /* Animation */ 125 | .fa-spin { 126 | -webkit-animation-name: fa-spin; 127 | animation-name: fa-spin; 128 | -webkit-animation-delay: var(--fa-animation-delay, 0); 129 | animation-delay: var(--fa-animation-delay, 0); 130 | -webkit-animation-direction: var(--fa-animation-direction, normal); 131 | animation-direction: var(--fa-animation-direction, normal); 132 | -webkit-animation-duration: var(--fa-animation-duration, 2s); 133 | animation-duration: var(--fa-animation-duration, 2s); 134 | -webkit-animation-iteration-count: var(--fa-animation-iteration-count, infinite); 135 | animation-iteration-count: var(--fa-animation-iteration-count, infinite); 136 | -webkit-animation-timing-function: var(--fa-animation-timing, linear); 137 | animation-timing-function: var(--fa-animation-timing, linear); } 138 | 139 | @keyframes fa-spin { 140 | 0% { transform: rotate(0deg); } 141 | 100% { transform: rotate(360deg); } 142 | } 143 | -------------------------------------------------------------------------------- /src/shared/fonts/Inter-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/Inter-Medium.woff2 -------------------------------------------------------------------------------- /src/shared/fonts/Inter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/Inter-Regular.woff2 -------------------------------------------------------------------------------- /src/shared/fonts/Inter-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/Inter-SemiBold.woff2 -------------------------------------------------------------------------------- /src/shared/fonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /src/shared/fonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /src/shared/fonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/fonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /src/shared/img/default_lead_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/default_lead_icon.png -------------------------------------------------------------------------------- /src/shared/img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon128.png -------------------------------------------------------------------------------- /src/shared/img/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon16.png -------------------------------------------------------------------------------- /src/shared/img/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon19.png -------------------------------------------------------------------------------- /src/shared/img/icon19_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon19_grey.png -------------------------------------------------------------------------------- /src/shared/img/icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon38.png -------------------------------------------------------------------------------- /src/shared/img/icon38_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon38_grey.png -------------------------------------------------------------------------------- /src/shared/img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/icon48.png -------------------------------------------------------------------------------- /src/shared/img/location_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/location_icon.png -------------------------------------------------------------------------------- /src/shared/img/orange_transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/orange_transparent_logo.png -------------------------------------------------------------------------------- /src/shared/img/search_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/search_grey.png -------------------------------------------------------------------------------- /src/shared/img/white_transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunter-io/browser-extension/4924f80d4ee5cbd5b0a82f424308730b4308e37e/src/shared/img/white_transparent_logo.png -------------------------------------------------------------------------------- /src/shared/js/account.coffee: -------------------------------------------------------------------------------- 1 | Account = 2 | get: (callback) -> 3 | @getApiKey (api_key) -> 4 | if api_key != '' 5 | url = 'https://api.hunter.io/v2/account?api_key=' + api_key 6 | $.ajax 7 | url: url 8 | headers: 'Email-Hunter-Origin': 'chrome_extension' 9 | type: 'GET' 10 | dataType: 'json' 11 | jsonp: false 12 | success: (json) -> 13 | callback json 14 | else 15 | callback 'none' 16 | 17 | setApiKey: (api_key) -> 18 | chrome.storage.sync.set { 'api_key': api_key }, -> 19 | console.info 'Hunter extension successfully installed.' 20 | 21 | getApiKey: (fn) -> 22 | chrome.storage.sync.get { 'api_key': false }, (value) -> 23 | if value.api_key 24 | api_key = value.api_key 25 | else 26 | api_key = '' 27 | fn api_key 28 | 29 | returnRequestsError: (fn) -> 30 | $.ajax 31 | url: "https://api.hunter.io/v2/account?api_key=" + window.api_key 32 | headers: 'Email-Hunter-Origin': 'chrome_extension' 33 | type: 'GET' 34 | data: format: 'json' 35 | dataType: 'json' 36 | error: -> 37 | return chrome.i18n.getMessage("something_went_wrong_on_our_side") 38 | success: (result) -> 39 | # the user account hasn't be validated yet. The phone is probably 40 | # missing. 41 | if result.data.requests.searches.available == 0 && result.data.requests.verifications.available == 0 && result.data.plan_level == 0 42 | fn(chrome.i18n.getMessage("please_complete_your_registration")) 43 | 44 | # Otherwise the user has probably been frozen. 45 | else if result.data.requests.searches.available == 0 && result.data.requests.verifications.available == 0 && result.data.plan_level > 0 46 | fn(chrome.i18n.getMessage("your_account_has_been_restricted")) 47 | 48 | # the user has a free account, so it means he consumed all his 49 | # free calls. 50 | else if result.data.plan_level == 0 51 | fn(chrome.i18n.getMessage("you_have_reached_your_daily_quota")) 52 | 53 | # the user account has been soft frozen. 54 | else if result.data.requests.searches.available == 250 && result.data.requests.verifications.available == 250 55 | fn(chrome.i18n.getMessage("you_have_reached_your_temporary_quota")) 56 | 57 | # the user is on a premium plan and reached his quota 58 | else if result.data.plan_level < 4 59 | fn(chrome.i18n.getMessage("your_have_reached_your_monthly_quota")) 60 | 61 | else 62 | fn(chrome.i18n.getMessage("your_have_reached_your_monthly_enterprise_quota")) 63 | -------------------------------------------------------------------------------- /src/shared/js/analytics.coffee: -------------------------------------------------------------------------------- 1 | # Counts the main actions made with the extension to know which features 2 | # are the most successful 3 | # 4 | Analytics = trackEvent: (eventName) -> 5 | url = 'https://hunter.io/events?name=' + eventName 6 | $.ajax 7 | url: url 8 | type: 'POST' 9 | dataType: 'json' 10 | jsonp: false 11 | success: (json) -> 12 | # Done! 13 | 14 | analytics = Object.create(Analytics) 15 | -------------------------------------------------------------------------------- /src/shared/js/api.coffee: -------------------------------------------------------------------------------- 1 | Api = 2 | # Domain Search 3 | domainSearch: (domain, department, type, api_key) -> 4 | if department != null && typeof department != 'undefined' 5 | department = '&department=' + department 6 | else 7 | department = '' 8 | 9 | if type != null && typeof type != 'undefined' 10 | type = '&type=' + type 11 | else 12 | type = '' 13 | 14 | if api_key 15 | auth = '&api_key=' + api_key 16 | 'https://api.hunter.io/v2/domain-search?limit=10&offset=0&domain=' + domain + department + type + auth 17 | else 18 | 'https://api.hunter.io/trial/v2/domain-search?domain=' + domain 19 | 20 | # Email Finder 21 | emailFinder: (domain, full_name, api_key) -> 22 | if full_name != null && typeof full_name != 'undefined' 23 | full_name = '&full_name=' + encodeURIComponent(full_name) 24 | else 25 | full_name = '' 26 | 27 | if api_key 28 | auth = '&api_key=' + api_key 29 | 'https://api.hunter.io/v2/email-finder?domain=' + domain + full_name + auth 30 | else 31 | 'https://api.hunter.io/trial/v2/email-finder?domain=' + domain + full_name 32 | 33 | # Email Verifier 34 | emailVerifier: (email, api_key) -> 35 | if api_key 36 | auth = '&api_key=' + api_key 37 | 'https://api.hunter.io/v2/email-verifier?email=' + encodeURIComponent(email) + auth 38 | else 39 | 'https://api.hunter.io/trial/v2/email-finder?domain=' + encodeURIComponent(email) 40 | 41 | # Email count 42 | emailCount: (domain) -> 43 | 'https://api.hunter.io/v2/email-count?domain=' + domain 44 | 45 | # Leads 46 | leads: (api_key) -> 47 | 'https://api.hunter.io/v2/leads?api_key=' + api_key 48 | 49 | # Leads exist 50 | leadsExist: (email, api_key) -> 51 | 'https://api.hunter.io/v2/leads/exist?email=' + encodeURIComponent(email) + '&api_key=' + api_key 52 | 53 | # Leads lists 54 | leadsList: (api_key) -> 55 | 'https://api.hunter.io/v2/leads_lists?limit=100&api_key=' + api_key 56 | 57 | # Check if there is any email for a domain name 58 | dataForDomain: (domain) -> 59 | 'https://extension-api.hunter.io/data-for-domain?domain=' + domain 60 | -------------------------------------------------------------------------------- /src/shared/js/lib/purify.min.js: -------------------------------------------------------------------------------- 1 | /*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */ 2 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,(function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){return e}),a||(a=function(e){return e}),u||(u=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1?n-1:0),o=1;o/gm),U=a(/^data-[\-\w.\u00B7-\uFFFF]/),j=a(/^aria-[\-\w]+$/),B=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),P=a(/^(?:\w+script|data):/i),W=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),G="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function q(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:K(),n=function(t){return e(t)};if(n.version="2.3.1",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var r=t.document,o=t.document,a=t.DocumentFragment,l=t.HTMLTemplateElement,c=t.Node,s=t.Element,u=t.NodeFilter,f=t.NamedNodeMap,x=void 0===f?t.NamedNodeMap||t.MozNamedAttrMap:f,Y=t.Text,X=t.Comment,$=t.DOMParser,Z=t.trustedTypes,J=s.prototype,Q=N(J,"cloneNode"),ee=N(J,"nextSibling"),te=N(J,"childNodes"),ne=N(J,"parentNode");if("function"==typeof l){var re=o.createElement("template");re.content&&re.content.ownerDocument&&(o=re.content.ownerDocument)}var oe=V(Z,r),ie=oe&&ze?oe.createHTML(""):"",ae=o,le=ae.implementation,ce=ae.createNodeIterator,se=ae.createDocumentFragment,ue=ae.getElementsByTagName,fe=r.importNode,me={};try{me=w(o).documentMode?o.documentMode:{}}catch(e){}var de={};n.isSupported="function"==typeof ne&&le&&void 0!==le.createHTMLDocument&&9!==me;var pe=z,ge=H,he=U,ye=j,ve=P,be=W,Te=B,Ae=null,xe=S({},[].concat(q(k),q(E),q(D),q(R),q(M))),Se=null,we=S({},[].concat(q(L),q(F),q(I),q(C))),Ne=null,ke=null,Ee=!0,De=!0,Oe=!1,Re=!1,_e=!1,Me=!1,Le=!1,Fe=!1,Ie=!1,Ce=!0,ze=!1,He=!0,Ue=!0,je=!1,Be={},Pe=null,We=S({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Ge=null,qe=S({},["audio","video","img","source","image","track"]),Ke=null,Ve=S({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Ye="http://www.w3.org/1998/Math/MathML",Xe="http://www.w3.org/2000/svg",$e="http://www.w3.org/1999/xhtml",Ze=$e,Je=!1,Qe=null,et=o.createElement("form"),tt=function(e){Qe&&Qe===e||(e&&"object"===(void 0===e?"undefined":G(e))||(e={}),e=w(e),Ae="ALLOWED_TAGS"in e?S({},e.ALLOWED_TAGS):xe,Se="ALLOWED_ATTR"in e?S({},e.ALLOWED_ATTR):we,Ke="ADD_URI_SAFE_ATTR"in e?S(w(Ve),e.ADD_URI_SAFE_ATTR):Ve,Ge="ADD_DATA_URI_TAGS"in e?S(w(qe),e.ADD_DATA_URI_TAGS):qe,Pe="FORBID_CONTENTS"in e?S({},e.FORBID_CONTENTS):We,Ne="FORBID_TAGS"in e?S({},e.FORBID_TAGS):{},ke="FORBID_ATTR"in e?S({},e.FORBID_ATTR):{},Be="USE_PROFILES"in e&&e.USE_PROFILES,Ee=!1!==e.ALLOW_ARIA_ATTR,De=!1!==e.ALLOW_DATA_ATTR,Oe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Re=e.SAFE_FOR_TEMPLATES||!1,_e=e.WHOLE_DOCUMENT||!1,Fe=e.RETURN_DOM||!1,Ie=e.RETURN_DOM_FRAGMENT||!1,Ce=!1!==e.RETURN_DOM_IMPORT,ze=e.RETURN_TRUSTED_TYPE||!1,Le=e.FORCE_BODY||!1,He=!1!==e.SANITIZE_DOM,Ue=!1!==e.KEEP_CONTENT,je=e.IN_PLACE||!1,Te=e.ALLOWED_URI_REGEXP||Te,Ze=e.NAMESPACE||$e,Re&&(De=!1),Ie&&(Fe=!0),Be&&(Ae=S({},[].concat(q(M))),Se=[],!0===Be.html&&(S(Ae,k),S(Se,L)),!0===Be.svg&&(S(Ae,E),S(Se,F),S(Se,C)),!0===Be.svgFilters&&(S(Ae,D),S(Se,F),S(Se,C)),!0===Be.mathMl&&(S(Ae,R),S(Se,I),S(Se,C))),e.ADD_TAGS&&(Ae===xe&&(Ae=w(Ae)),S(Ae,e.ADD_TAGS)),e.ADD_ATTR&&(Se===we&&(Se=w(Se)),S(Se,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&S(Ke,e.ADD_URI_SAFE_ATTR),e.FORBID_CONTENTS&&(Pe===We&&(Pe=w(Pe)),S(Pe,e.FORBID_CONTENTS)),Ue&&(Ae["#text"]=!0),_e&&S(Ae,["html","head","body"]),Ae.table&&(S(Ae,["tbody"]),delete Ne.tbody),i&&i(e),Qe=e)},nt=S({},["mi","mo","mn","ms","mtext"]),rt=S({},["foreignobject","desc","title","annotation-xml"]),ot=S({},E);S(ot,D),S(ot,O);var it=S({},R);S(it,_);var at=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:$e,tagName:"template"});var n=g(e.tagName),r=g(t.tagName);if(e.namespaceURI===Xe)return t.namespaceURI===$e?"svg"===n:t.namespaceURI===Ye?"svg"===n&&("annotation-xml"===r||nt[r]):Boolean(ot[n]);if(e.namespaceURI===Ye)return t.namespaceURI===$e?"math"===n:t.namespaceURI===Xe?"math"===n&&rt[r]:Boolean(it[n]);if(e.namespaceURI===$e){if(t.namespaceURI===Xe&&!rt[r])return!1;if(t.namespaceURI===Ye&&!nt[r])return!1;var o=S({},["title","style","font","a","script"]);return!it[n]&&(o[n]||!ot[n])}return!1},lt=function(e){p(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ie}catch(t){e.remove()}}},ct=function(e,t){try{p(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(n.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Se[e])if(Fe||Ie)try{lt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},st=function(e){var t=void 0,n=void 0;if(Le)e=""+e;else{var r=h(e,/^[\r\n\t ]+/);n=r&&r[0]}var i=oe?oe.createHTML(e):e;if(Ze===$e)try{t=(new $).parseFromString(i,"text/html")}catch(e){}if(!t||!t.documentElement){t=le.createDocument(Ze,"template",null);try{t.documentElement.innerHTML=Je?"":i}catch(e){}}var a=t.body||t.documentElement;return e&&n&&a.insertBefore(o.createTextNode(n),a.childNodes[0]||null),Ze===$e?ue.call(t,_e?"html":"body")[0]:_e?t.documentElement:a},ut=function(e){return ce.call(e.ownerDocument||e,e,u.SHOW_ELEMENT|u.SHOW_COMMENT|u.SHOW_TEXT,null,!1)},ft=function(e){return!(e instanceof Y||e instanceof X)&&!("string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof x&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI&&"function"==typeof e.insertBefore)},mt=function(e){return"object"===(void 0===c?"undefined":G(c))?e instanceof c:e&&"object"===(void 0===e?"undefined":G(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},dt=function(e,t,r){de[e]&&m(de[e],(function(e){e.call(n,t,r,Qe)}))},pt=function(e){var t=void 0;if(dt("beforeSanitizeElements",e,null),ft(e))return lt(e),!0;if(h(e.nodeName,/[\u0080-\uFFFF]/))return lt(e),!0;var r=g(e.nodeName);if(dt("uponSanitizeElement",e,{tagName:r,allowedTags:Ae}),!mt(e.firstElementChild)&&(!mt(e.content)||!mt(e.content.firstElementChild))&&T(/<[/\w]/g,e.innerHTML)&&T(/<[/\w]/g,e.textContent))return lt(e),!0;if("select"===r&&T(/