├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── HISTORY_en.md ├── HISTORY_ru.md ├── LICENSE_en ├── LICENSE_ru ├── README.md ├── README_en.md ├── README_ru.md ├── package.json ├── src └── loaders │ └── manifest-loader.js ├── static ├── _locales │ ├── be │ │ └── messages.json │ ├── en_US │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── it │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── ru │ │ └── messages.json │ └── uk │ │ └── messages.json ├── browser-action │ ├── css │ │ └── browser-action.css │ ├── img │ │ ├── amazon-icon-16.png │ │ ├── checkmark-success-icon-16.png │ │ ├── clipboard_copy_icon&16.png │ │ ├── cog_icon&16.png │ │ ├── google-icon-16.png │ │ └── vk-icon-16.png │ ├── index.html │ └── js │ │ └── browser-action.js ├── global │ ├── css │ │ └── global.css │ ├── img │ │ ├── cogs_icon&24.png │ │ ├── delete_icon&16.png │ │ ├── emotion_smile_icon&16.png │ │ ├── info-icon.svg │ │ ├── list_bullets_icon&16.png │ │ ├── no-logo.svg │ │ ├── notification-icon-80.png │ │ ├── off_icon&16.png │ │ ├── options-checkbox-tick-icon-11.png │ │ ├── playback_next_icon&16.png │ │ ├── playback_play_icon&16.png │ │ ├── playback_prev_icon&16.png │ │ ├── playback_stop_icon&16.png │ │ ├── pozitone-icon-128.png │ │ ├── pozitone-icon-16.png │ │ ├── pozitone-icon-19.png │ │ ├── pozitone-icon-38.png │ │ ├── pozitone-icon-48.png │ │ ├── pozitone-notification-icon-80.png │ │ ├── pozitone.svg │ │ ├── projects │ │ │ ├── pe-icon-64.png │ │ │ ├── pwm-icon-128.svg │ │ │ ├── sttb-icon-50.svg │ │ │ └── swaggy-icon-48.svg │ │ ├── round_plus_icon&16.png │ │ ├── sound_down_icon&16.png │ │ ├── sound_high_icon&16.png │ │ ├── sound_mute_icon&16.png │ │ └── sound_up_icon&16.png │ └── js │ │ ├── angular │ │ ├── angular-route.min.js │ │ └── angular.min.js │ │ ├── background-v2.js │ │ ├── background.js │ │ ├── bowser.js │ │ ├── const.js │ │ ├── global-v2.js │ │ ├── global.js │ │ ├── i18n.js │ │ ├── i18next │ │ ├── i18next.min.js │ │ ├── i18nextBrowserLanguageDetector.min.js │ │ └── i18nextXHRBackend.js │ │ ├── log.js │ │ ├── mixpanel-2.2.min.js │ │ ├── notifications.js │ │ ├── page.js │ │ ├── pozitone-host-api.js │ │ ├── pozitone-module-sdk.js │ │ ├── punycode.min.js │ │ ├── tracking.js │ │ ├── utils.js │ │ └── voice-control.js ├── manifest.json ├── modules │ ├── com_audioaddict │ │ └── js │ │ │ └── page-watcher.js │ ├── com_classicalradio │ │ ├── img │ │ │ └── classicalradio-logo-120.png │ │ └── js │ │ │ └── page-watcher.js │ ├── com_google_play_music │ │ ├── img │ │ │ ├── google-play-music-logo-80.png │ │ │ └── google-play-music.svg │ │ └── js │ │ │ ├── dom-to-image.js │ │ │ └── page-watcher.js │ ├── com_jazzradio │ │ ├── img │ │ │ └── jazzradio-logo-120.png │ │ └── js │ │ │ └── page-watcher.js │ ├── com_radiotunes │ │ ├── img │ │ │ └── radiotunes-logo-80.png │ │ └── js │ │ │ └── page-watcher.js │ ├── com_rockradio │ │ ├── img │ │ │ └── rockradio-logo-120.png │ │ └── js │ │ │ └── page-watcher.js │ ├── com_soundcloud │ │ ├── img │ │ │ ├── soundcloud-logo-48.svg │ │ │ └── soundcloud-logo-80.png │ │ └── js │ │ │ └── page-watcher.js │ ├── com_vgmradio │ │ ├── img │ │ │ └── vgmradio-logo-120.svg │ │ └── js │ │ │ └── page-watcher.js │ ├── com_vk_audio │ │ ├── img │ │ │ ├── vk-logo-32.png │ │ │ ├── vk-logo-80.png │ │ │ └── vk-logo-80.svg │ │ └── js │ │ │ ├── page-watcher-2.js │ │ │ ├── page-watcher-loader.js │ │ │ └── page-watcher.js │ ├── fm_di │ │ ├── img │ │ │ └── di-logo-120.svg │ │ └── js │ │ │ └── page-watcher.js │ ├── general │ │ └── js │ │ │ └── page-watcher.js │ ├── ru_101 │ │ └── js │ │ │ ├── page-watcher.js │ │ │ └── uppod-player-api.js │ └── ru_ok_audio │ │ ├── img │ │ ├── ok-logo-80.svg │ │ ├── ok-logo-orange-32.png │ │ └── ok-logo-orange-80.png │ │ └── js │ │ └── page-watcher.js └── options │ ├── css │ └── options.css │ ├── img │ └── switch-language.svg │ ├── index.html │ ├── js │ ├── app.js │ ├── controllers │ │ ├── external-modules-list.js │ │ ├── misc.js │ │ ├── settings-modules-list.js │ │ ├── settings.js │ │ └── voice-control.js │ └── options.js │ └── partials │ ├── about.html │ ├── contribution.html │ ├── external-modules-list.html │ ├── feedback.html │ ├── help.html │ ├── projects.html │ ├── settings-general.html │ ├── settings-module.html │ ├── settings-modules-list.html │ ├── settings.html │ ├── voice-control.html │ └── ❤.html ├── webpack.config.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.{js,json}] 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 13 | indent_brace_style = K&R 14 | curly_bracket_next_line = false 15 | 16 | [*.{js}] 17 | quote_type = single 18 | spaces_around_operators = true 19 | spaces_around_brackets = inside 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: PoziWorld 2 | custom: paypal.me/PoziTone 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE_en: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 PoziWorld 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE_ru: -------------------------------------------------------------------------------- 1 | Лицензия MIT 2 | 3 | Copyright (c) 2013-2016 PoziWorld 4 | 5 | Данная лицензия разрешает лицам, получившим копию данного программного 6 | обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное 7 | Обеспечение»), безвозмездно использовать Программное Обеспечение без 8 | ограничений, включая неограниченное право на использование, копирование, 9 | изменение, добавление, публикацию, распространение, сублицензирование и/или 10 | продажу копий Программного Обеспечения, также как и лицам, которым 11 | предоставляется данное Программное Обеспечение, при соблюдении следующих 12 | условий: 13 | 14 | Указанное выше уведомление об авторском праве и данные условия должны быть 15 | включены во все копии или значимые части данного Программного Обеспечения. 16 | 17 | ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО 18 | ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ 19 | ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И 20 | ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ 21 | ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ ТРЕБОВАНИЙ ПО 22 | ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ 23 | СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ 24 | ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [PoziTonePoziTone](https://pozitone.com) 2 | ======= 3 | 4 | 5 | An extension for the Google Chrome, Opera, Microsoft Edge, and other Chromium-based browsers that adds new features to your favorite online media players. 6 | 7 | [Learn more about PoziTone](README_en.md). 8 | 9 | --- 10 | 11 | 12 | Расширение для браузеров Google Chrome, Opera, Яндекс, Microsoft Edge и других, основанных на Chromium, которое добавляет новые возможности Вашим любимым онлайн-медиаплеерам. 13 | 14 | [Узнать больше о PoziTone](README_ru.md). 15 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | > Это английская версия, см. [README_ru.md](README_ru.md) для русской. 2 | 3 | [PoziTonePoziTone](https://pozitone.com) 4 | ======= 5 | 6 | An extension for the Google Chrome, Opera, Microsoft Edge, and other Chromium-based browsers that adds new [features](#features) to your favorite [online media players](#supported-online-media-players). 7 | 8 | ___ 9 | 10 | ##### Table of contents 11 | 12 | * [Features](#features) 13 | * [Supported online media players](#supported-online-media-players) 14 | * [Supported via external modules](#supported-via-external-modules) 15 | * [Installation](#installation) 16 | * [Demo](#demo) 17 | * [Keyboard shortcuts](#keyboard-shortcuts) 18 | * [Set up / change keyboards shortcuts](#set-up--change-keyboards-shortcuts) 19 | * [Demo: how to set up / change keyboards shortcuts](#demo-how-to-set-up--change-keyboards-shortcuts) 20 | * [Global keyboard shortcuts (outside of the browser window)](#global-keyboard-shortcuts-outside-of-the-browser-window) 21 | * [Gratitudes](#gratitudes) 22 | * [Credits](#credits) 23 | * [Privacy](#privacy) 24 | * [Contribution](#contribution) 25 | * [Spread the word](#spread-the-word) 26 | * [Translation](#translation) 27 | * [Incentive](#incentive) 28 | * [Feedback](#feedback) 29 | * [PoziTone on social networks](#pozitone-on-social-networks) 30 | 31 | ___ 32 | 33 | Features 34 | -------- 35 | 36 | * **Displays info about now playing song, podcast, video, etc.** 37 | 38 | _Familiarizing with a new genre or band?_ 39 | 40 | PoziTone will show you track info (its name, artist name) via a pop-up notification on every track change. 41 | 42 | * **Easy player control outside of browser window** 43 | 44 | _Want to stop/resume playback, mute/unmute, switch track or add it to playlist?_ 45 | 46 | Three easy ways to do it: 47 | * Click a button in the pop-up notification. 48 | * Press [keyboard shortcuts](#keyboard-shortcuts). 49 | * Or, **_just say a [command](https://github.com/PoziWorld/PoziWorld-Elf/wiki/Commands)_***! 50 | 51 | \* (Voice control app [PoziWorld Elf](https://github.com/PoziWorld/PoziWorld-Elf) needs to be installed) 52 | 53 | * **List of recently played** 54 | 55 | _What track played five minutes ago?_ 56 | 57 | PoziTone keeps information about the last 10 tracks played. 58 | 59 | [(back to table of contents)](#table-of-contents) 60 | 61 | 62 | Supported online media players 63 | -------- 64 | 65 | * [SoundCloud](https://soundcloud.com) (full version of site) 66 | * [Google Play Music](https://play.google.com/music) 67 | * [AudioAddict](http://www.audioaddict.com) network: 68 | * [ClassicalRadio.com](http://www.classicalradio.com) 69 | * [Digitally Imported](https://www.di.fm) (DI, DI.FM, or DI Radio) 70 | * [JAZZRADIO.com](http://www.jazzradio.com) 71 | * [RadioTunes](http://www.radiotunes.com) 72 | * [ROCKRADIO.COM](http://www.rockradio.com) 73 | * [VK](https://vk.com) (audio, full version of site – old & new) 74 | * [VGM Radio](https://vgmradio.com) 75 | * [Odnoklassniki](http://ok.ru) (audio, full version of site) 76 | * [Online radio 101.ru](http://101.ru) (stations' main player) 77 | 78 | More to come... 79 | 80 | 81 | ### Supported via external modules 82 | 83 | An external PoziTone module is a standalone extension which is able to provide [PoziTone features](#features) by communicating directly with PoziTone behind the scenes. 84 | 85 | * [YouTube embedded player](https://chrome.google.com/webstore/detail/youtube-embedded-player-p/bajalgkbfjloemafmkiheboebghhibbg "PoziTone module for YouTube embedded player") ([for Opera](https://addons.opera.com/en/extensions/details/youtube-embedded-player-pozitone-module/ "“PoziTone module for YouTube embedded player” for Opera"), [for Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/endgoolfeicagiackhdalbfkinelcgin "“PoziTone module for YouTube embedded player” for Microsoft Edge")). 86 | * [SoundCloud Widget](https://chrome.google.com/webstore/detail/pozitone-module-for-sound/iijloaojdghegopdahladbajodcgnmgh "PoziTone module for SoundCloud Widget") ([for Opera](https://addons.opera.com/en/extensions/details/soundcloud-widget-pozitone-module/ "“PoziTone module for SoundCloud Widget” for Opera"), [for Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/imijjplgbohoagfnhbdlfhfcgfikgjab "“PoziTone module for SoundCloud Widget” for Microsoft Edge")). 87 | * [Sovyatnik](https://chrome.google.com/webstore/detail/sovyatnik-pozitone-module/ihdoljplikdgegdooeohfmgaaabcbmpn "PoziTone module for Sovyatnik") ([for Opera](https://addons.opera.com/en/extensions/details/soviatnik-pozitone-modul/ "“PoziTone module for Sovyatnik” for Opera"), [for Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/fkgcpgookmofjfedpfhadkgkhddcdbpp "“PoziTone module for Sovyatnik” for Microsoft Edge")). 88 | 89 | 90 | _Are you a developer?_ Want to add a support for your favorite player and publish it as a PoziTone module under your name? – Check [PoziTone module SDK](https://github.com/PoziWorld/PoziTone-module-SDK). 91 | 92 | [(back to table of contents)](#table-of-contents) 93 | 94 | 95 | Installation 96 | -------- 97 | 98 | * [PoziTone in the Chrome Web Store](https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/details?hl=en) 99 | * [PoziTone in the Opera add-ons catalog](https://addons.opera.com/en/extensions/details/pozitone/?display=en_US) 100 | * [PoziTone in the Microsoft Edge Addons store](https://microsoftedge.microsoft.com/addons/detail/mnfohmojhhcbbnafeehfhghjaeaokjbl) 101 | 102 | [(back to table of contents)](#table-of-contents) 103 | 104 | 105 | Demo 106 | -------- 107 | 108 | Go to [SoundCloud](https://soundcloud.com) and play any track. 109 | 110 | PoziTone should display a notification with the track info. 111 | 112 | [(back to table of contents)](#table-of-contents) 113 | 114 | 115 | Keyboard shortcuts 116 | -------- 117 | 118 | ### Preset 119 | 120 | 1. Alt+Shift+D — “Add to playlist”. 121 | 2. Alt+Shift+P — “Stop / Play”. 122 | 3. Alt+Shift+M — “Mute / Unmute”. 123 | 4. Alt+Shift+Q — “Show notification”. 124 | 125 | ### Also available 126 | 127 | Not preset, but can be easily [set up](#set-up--change-keyboards-shortcuts) with the help of next subsection. 128 | 129 | 5. Alt+Shift+F — “I like it!”. 130 | 6. Alt+Shift+N — “Next”. 131 | 7. Alt+Shift+B — “Previous”. 132 | 8. Alt+Shift+A — “Increase sound volume”. 133 | 9. Alt+Shift+Z — “Decrease sound volume”. 134 | 10. Alt+Shift+R — “Activate the extension”. 135 | 136 | ### Set up / change keyboards shortcuts 137 | 138 | 1. Right-click on the PoziTone icon to the right of the address bar. 139 | 2. Click on “Manage extensions”. 140 | 3. Scroll down the page. 141 | 4. Click on the blue “Keyboard shortcuts” link. 142 | 5. Find PoziTone in the list. 143 | 6. Make any necessary changes. 144 | 7. Click on “OK”. 145 | 146 | #### Demo: how to set up / change keyboards shortcuts 147 | 148 | [![Demo: how to set up / change PoziTone keyboards shortcuts](https://cloud.githubusercontent.com/assets/8120840/3534655/a7c7ce58-07ef-11e4-89a5-c7b52e92b943.png)](https://cloud.githubusercontent.com/assets/8120840/3534660/b7bfb960-07ef-11e4-9102-7d1aa1b25496.gif) 149 | 150 | ### Global keyboard shortcuts (outside of the browser window) 151 | 152 | By default, while the browser is not in the foreground, the shortcut will be inactive. 153 | 154 | You can make the shortcuts work even when the browser is not in the foreground: see [steps 1–7](#set-up--change-keyboards-shortcuts) above, change “In Chrome” to “Global” on step 6. 155 | 156 | [(back to table of contents)](#table-of-contents) 157 | 158 | 159 | Gratitudes 160 | -------- 161 | 162 | - To all those who give us life and happiness! 163 | - To the inspirers and testers of PoziTone: Darya, Evgeny, Mel, Nike, Sergey. 164 | - [UserEcho](https://userecho.com/?pcode=1014487) — for the excellent service, kindness and generosity! 165 | - Translators: 166 | - Spanish — [Paco_Zamo (Francisco Zamorano)](https://www.transifex.com/user/profile/Paco_Zamo/); 167 | - Polish — jurczak (Łukasz Jurczak); 168 | - Italian — [Aeco (Giuseppe Mariniello)](https://www.transifex.com/user/profile/Aeco/); 169 | - Ukrainian — [ivan.zusko (Ivan Zusko)](https://www.transifex.com/user/profile/ivan.zusko/), [KovalenkoStas (Коваленко Станіслав)](https://www.transifex.com/user/profile/KovalenkoStas/). 170 | - Belarusian — [natasmirnova1392 (Natalia Smirnova)](https://www.transifex.com/user/profile/natasmirnova1392/). 171 | 172 | [(back to table of contents)](#table-of-contents) 173 | 174 | 175 | Credits 176 | -------- 177 | 178 | - The button icons — [Gentleface Mono Icon Set](http://gentleface.com/free_icon_set.html), Gentleface, [CC BY-NC 3.0](http://creativecommons.org/licenses/by-nc/3.0/). 179 | - [The Play icon](http://thenounproject.com/term/play/5206/) — Michael Rowe, [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/us/). 180 | - The Info icon — [Freepik](http://www.freepik.com) from [www.flaticon.com](http://www.flaticon.com), [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/). 181 | - The appearance of the “PoziTone Options” and “Recent Tracks” pages: 182 | * Copyright 2014 The Chromium Authors. All rights reserved. 183 | * Use of this source code is governed by a BSD-style license that can be found in the [LICENSE](http://src.chromium.org/viewvc/chrome/trunk/src/LICENSE) file. 184 | - All trademarks used (trade names, logos, brands, names, service marks, etc.) are the property of their respective owners. 185 | 186 | [(back to table of contents)](#table-of-contents) 187 | 188 | 189 | Privacy 190 | -------- 191 | 192 | This extension does not read, change, store, or transmit any of your personal data (e.g., logins, passwords, messages, contacts) from any of the sites or your computer in absolutely any form. 193 | 194 | [(back to table of contents)](#table-of-contents) 195 | 196 | 197 | Contribution 198 | -------- 199 | ### Spread the word 200 | 201 | If you find PoziTone useful, why not recommend it to your friends, colleagues, and/or followers? 202 | 203 | There are a few ways you can help our cause: 204 | 205 | * Follow us on [Telegram](https://t.me/PoziTone), [Facebook](https://facebook.com/PoziTone), [Twitter](https://twitter.com/PoziTone), [VK](https://vk.com/PoziTone), [Odnoklassniki](https://ok.ru/group/54738320621596), and [Instagram](https://instagram.com/PoziTone). 206 | * Share the [installation link](https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm) ([for Opera](https://addons.opera.com/extensions/details/pozitone/)) on your social networks and/or blog. 207 | * [Rate PoziTone](https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/reviews) ([for Opera](https://addons.opera.com/extensions/details/pozitone/#feedback-container)) – the feedback really helps others find this extension! 208 | 209 | ### Translation 210 | 211 | Sprechen Sie Deutsch? Vous parlez français? 212 | 213 | We want as many PoziTone users as possible to be able to interact with the extension in their native language. 214 | 215 | If you know any foreign language or just noticed a discrepancy in the translation, please [let us know](https://www.transifex.com/poziworld/pozitone/). 216 | 217 | ### Incentive 218 | 219 | If you appreciate the work we have done, please consider stimulating PoziTone development via [PayPal](https://www.paypal.me/pozitone), [Square Cash](https://cash.me/$PoziTone), or [Coinbase](https://commerce.coinbase.com/checkout/6c0d5cf3-cd19-4b8b-bda3-e843716226df). 220 | 221 | [(back to table of contents)](#table-of-contents) 222 | 223 | 224 | Feedback 225 | -------- 226 | 227 | We appreciate your feedback! 228 | 229 | - Please [share your ideas](https://feedback.pozitone.com/). 230 | - Don't be shy to [ask questions](mailto:feedback@pozitone.com). 231 | - [Bug reports](https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/support) ([for Opera](https://addons.opera.com/extensions/details/pozitone/?reports#feedback-container)) are valuable! 232 | - We like [praises](https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/reviews) ([for Opera](https://addons.opera.com/extensions/details/pozitone/#feedback-container)), who doesn't? :) 233 | - Stick is helpful, but [carrot](https://cash.me/$PoziTone) is sweeter. ;) 234 | 235 | [(back to table of contents)](#table-of-contents) 236 | 237 | 238 | PoziTone on social networks 239 | -------- 240 | 241 | - [PoziTone on Telegram](https://t.me/PoziTone) 242 | - [PoziTone on VK](https://vk.com/PoziTone) 243 | - [PoziTone on Twitter](https://twitter.com/PoziTone) 244 | - [PoziTone on Facebook](https://facebook.com/PoziTone) 245 | - [PoziTone on Odnoklassniki](https://ok.ru/group/54738320621596) 246 | - [PoziTone on 101.ru](https://101.ru/?an=User_Info&userId=709962) 247 | - [PoziTone on Instagram](https://instagram.com/PoziTone) 248 | 249 | [(back to table of contents)](#table-of-contents) 250 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pozitone", 3 | "version": "1.0.3", 4 | "description": "PoziTone is a browser extension (add-on) for the Google Chrome, Opera, Microsoft Edge, and other Chromium-based browsers that adds new features to your favorite online media players.", 5 | "scripts": { 6 | "dev": "NODE_ENV=development webpack --mode development --progress", 7 | "build": "NODE_ENV=production webpack --mode production", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/PoziWorld/PoziTone.git" 13 | }, 14 | "keywords": [ 15 | "pozitone", 16 | "media player", 17 | "browser", 18 | "extension", 19 | "addon", 20 | "chrome", 21 | "edge", 22 | "opera", 23 | "chromium" 24 | ], 25 | "author": "PoziWorld, Inc.", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/PoziWorld/PoziTone/issues" 29 | }, 30 | "homepage": "https://pozitone.com", 31 | "devDependencies": { 32 | "clean-webpack-plugin": "0.1.19", 33 | "copy-webpack-plugin": "4.5.2", 34 | "fs": "^0.0.1-security", 35 | "immutable": "4.0.0-rc.9", 36 | "path": "0.12.7", 37 | "webpack": "^5.0.0-beta.9", 38 | "webpack-clean": "1.2.2", 39 | "webpack-cli": "^3.3.10" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/loaders/manifest-loader.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const fs = require( 'fs' ); 3 | const { mergeDeep } = require( 'immutable' ); 4 | 5 | /** 6 | * https://nodejs.org/api/fs.html#fs_fs_readfilesync_path_options 7 | */ 8 | 9 | const PACKAGE_JSON_PATH = './package.json'; 10 | const PACKAGE_JSON_ENCODING = 'utf8'; 11 | 12 | /** 13 | * https://developer.chrome.com/extensions/options#full_page 14 | * https://developer.chrome.com/extensions/options#embedded_options 15 | * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/options_ui#Syntax 16 | * 17 | * @type {string} 18 | */ 19 | 20 | const OPTIONS_PAGE_PATH = 'options/index.html'; 21 | 22 | /** 23 | * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/applications#Extension_ID_format 24 | * 25 | * @type {string} 26 | */ 27 | 28 | const EXTENSION_ID_AFFIX = '@poziworld.com'; 29 | 30 | module.exports = adaptManifestJson; 31 | 32 | /** 33 | * Build browser-specific manifest.json files, as not all manifest.json keys are supported by all browsers. 34 | * Also, copy over some details from package.json. 35 | * 36 | * https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json 37 | * 38 | * Inspired by https://stackoverflow.com/a/44249538 39 | * 40 | * @param {string} source - Stringified original manifest.json's contents. 41 | * @return {string} - Stringified updated browser-specific manifest.json's contents. 42 | */ 43 | 44 | function adaptManifestJson( source ) { 45 | let manifestJsonAsJs = JSON.parse( source ); 46 | const packageJsonContents = fs.readFileSync( PACKAGE_JSON_PATH, PACKAGE_JSON_ENCODING ); 47 | const packageJsonAsJs = JSON.parse( packageJsonContents ); 48 | let newProperties = { 49 | version: packageJsonAsJs.version, 50 | author: packageJsonAsJs.author, 51 | homepage_url: packageJsonAsJs.homepage, 52 | }; 53 | 54 | /** 55 | * See supportedBrowsers in webpack.config.js. 56 | * 57 | * @todo Find a cleaner way. 58 | */ 59 | 60 | const browserName = path.basename( this._compiler.outputPath ); 61 | 62 | switch ( browserName ) { 63 | case 'chromium': 64 | { 65 | newProperties.background = { 66 | persistent: true, 67 | }; 68 | newProperties.options_page = OPTIONS_PAGE_PATH; 69 | 70 | break; 71 | } 72 | case 'firefox': 73 | { 74 | newProperties.applications = { 75 | gecko: { 76 | id: packageJsonAsJs.name + EXTENSION_ID_AFFIX, 77 | } 78 | }; 79 | newProperties.options_ui = { 80 | page: OPTIONS_PAGE_PATH, 81 | browser_style: true, 82 | }; 83 | 84 | delete manifestJsonAsJs.version_name; 85 | 86 | break; 87 | } 88 | } 89 | 90 | const merged = mergeDeep( manifestJsonAsJs, newProperties ); 91 | const mergedJson = JSON.stringify( merged ); 92 | 93 | this.emitFile( 'manifest.json', mergedJson ); 94 | 95 | return mergedJson; 96 | } 97 | -------------------------------------------------------------------------------- /static/browser-action/css/browser-action.css: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product : PoziTone 4 | Author : PoziWorld 5 | Copyright : Copyright (c) 2013-2016 PoziWorld 6 | License : pozitone.com/license 7 | File : browser-action/css/browser-action.css 8 | Description : Popup Styles 9 | 10 | Table of Contents: 11 | 12 | Globals 13 | Content 14 | 15 | ============================================================================ */ 16 | 17 | /* ============================================================================= 18 | 19 | Globals 20 | 21 | ============================================================================ */ 22 | 23 | body 24 | { 25 | min-width: initial; 26 | } 27 | 28 | /* ============================================================================= 29 | 30 | Content 31 | 32 | ============================================================================ */ 33 | 34 | #content 35 | { 36 | padding: 0 2rem; 37 | min-width: 50rem; 38 | max-width: 50rem; 39 | } 40 | .pageHeading 41 | { 42 | width: 50rem; 43 | max-width: none; 44 | min-width: none; 45 | } 46 | #recentTracks 47 | { 48 | margin: 1.15rem 0 1.4rem 1.8rem; 49 | } 50 | .recentTrack 51 | , #tunesSuggestion 52 | { 53 | line-height: normal !important; 54 | padding: .25rem 0; 55 | transition: all .1s ease; 56 | } 57 | .recentTrack:hover 58 | { 59 | background-color: #fafafa; 60 | } 61 | .recentTrackImg 62 | { 63 | display: inline-block; 64 | vertical-align: top; 65 | width: 3.2rem; 66 | height: 3.2rem; 67 | } 68 | .recentTrackActions 69 | { 70 | display: none; 71 | position: absolute; 72 | z-index: 2; 73 | top: .25rem; 74 | width: 0; 75 | height: 0; 76 | background-color: inherit; 77 | background-image: url(/global/img/cogs_icon&24.png); 78 | background-position: .4rem .4rem; 79 | background-repeat: no-repeat; 80 | overflow: hidden; 81 | } 82 | .recentTrack:hover .recentTrackActions 83 | { 84 | display: block; 85 | width: 3.2rem; 86 | height: 3.2rem; 87 | } 88 | .recentTrack:hover .recentTrackActions:hover 89 | { 90 | width: 100%; 91 | height: 100%; 92 | } 93 | .recentTrackInfo 94 | , #tunesSuggestionInfo 95 | { 96 | display: inline-block; 97 | position: relative; 98 | vertical-align: top; 99 | font-size: 1.2em; 100 | line-height: 1.5em; 101 | padding: .84rem 0 0 .66em; 102 | max-width: 42rem; 103 | } 104 | .recentTrackActions:hover + .recentTrackInfo:after 105 | { 106 | content: ''; 107 | position: absolute; 108 | top: 0; 109 | right: 0; 110 | bottom: 0; 111 | left: 0; 112 | background-color: #fafafa; 113 | } 114 | .recentTrackActionsList 115 | { 116 | height: 100%; 117 | margin-left: 4.3rem !important; 118 | } 119 | .recentTrackActionsListItem 120 | { 121 | float: left; 122 | padding: .4rem 0; 123 | line-height: normal !important; 124 | } 125 | .recentTrackAction 126 | { 127 | margin: 0 1.1rem 0 0; 128 | width: 2.4rem; 129 | height: 2.4rem; 130 | cursor: pointer; 131 | } 132 | .recentTrackActionImg 133 | { 134 | margin-top: .18rem; 135 | width: 1.6rem; 136 | height: 1.6rem; 137 | } 138 | .copyToClipboardSuccess 139 | { 140 | position: absolute; 141 | top: 50%; 142 | left: 50%; 143 | margin: -.8rem 0 0 -.8rem; 144 | } 145 | 146 | /* ============================================================================= 147 | 148 | Privacy statement 149 | 150 | ============================================================================ */ 151 | 152 | #privacyStatementsContainer 153 | { 154 | z-index: 2; 155 | left: 0; 156 | width: auto; 157 | } 158 | .privacyStatement 159 | { 160 | font-size: 1.3em; 161 | white-space: pre-line; 162 | } 163 | -------------------------------------------------------------------------------- /static/browser-action/img/amazon-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/amazon-icon-16.png -------------------------------------------------------------------------------- /static/browser-action/img/checkmark-success-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/checkmark-success-icon-16.png -------------------------------------------------------------------------------- /static/browser-action/img/clipboard_copy_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/clipboard_copy_icon&16.png -------------------------------------------------------------------------------- /static/browser-action/img/cog_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/cog_icon&16.png -------------------------------------------------------------------------------- /static/browser-action/img/google-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/google-icon-16.png -------------------------------------------------------------------------------- /static/browser-action/img/vk-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/browser-action/img/vk-icon-16.png -------------------------------------------------------------------------------- /static/browser-action/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 55 |
56 |
57 |

58 |
59 |
    60 |
  • 61 | 65 |
  • 66 |
67 | 141 |
142 |
143 |
144 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /static/browser-action/js/browser-action.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | Table of Contents: 9 | 10 | Popup 11 | init() 12 | populateRecentTracks() 13 | addEventListeners() 14 | onCopyToClipboardCtaClick() 15 | requestClipboardWritePermission() 16 | copyToClipboard() 17 | trackData() 18 | composeRecentTrackActionUrl() 19 | encodeQuery() 20 | Events 21 | 22 | ============================================================================ */ 23 | 24 | /* ============================================================================= 25 | 26 | Popup 27 | 28 | ============================================================================ */ 29 | 30 | ( function () { 31 | window.strPage = 'browser-action'; 32 | 33 | const strLogPage = 'browserAction'; 34 | const strLogPageDivider = '.'; 35 | 36 | const strListId = 'recentTracks'; 37 | const strListElementSelector = '.recentTrack'; 38 | const strListElementInfoSelector = '.recentTrackInfo'; 39 | 40 | const strRecentTrackActionUrl = 'https://go.pozitone.com/s/?'; 41 | 42 | setUp(); 43 | 44 | /** 45 | * Make the logic readily available. 46 | */ 47 | 48 | function setUp() { 49 | document.addEventListener( 'DOMContentLoaded', init ); 50 | } 51 | 52 | /** 53 | * Initialize the view. 54 | **/ 55 | 56 | function init() { 57 | populateRecentTracks(); 58 | Page.trackPageView(); 59 | } 60 | 61 | /** 62 | * Populate the Recently Played list. 63 | **/ 64 | 65 | function populateRecentTracks() { 66 | StorageSync.get( 'arrRecentTracks', function( objReturn ) { 67 | var arrRecentTracks = objReturn.arrRecentTracks 68 | , strHtml = '' 69 | ; 70 | 71 | for ( var i = ( arrRecentTracks.length - 1 ); i >= 0; i-- ) { 72 | var arrRecentTrack = arrRecentTracks[ i ] 73 | , strSrc = arrRecentTrack[ 2 ] 74 | , strExtensionId = arrRecentTrack[ 3 ] 75 | ; 76 | 77 | // External module 78 | if ( typeof strExtensionId === 'string' && strExtensionId !== '' ) { 79 | strSrc = 'chrome-extension://' 80 | + strExtensionId 81 | + ( strSrc[ 0 ] === '/' ? '' : '/' ) 82 | + strSrc 83 | ; 84 | } 85 | 86 | strHtml += Page.template( 87 | 'recentTrackRow' 88 | , { 89 | track : arrRecentTrack[ 0 ] 90 | , src : strSrc 91 | , alt : arrRecentTrack[ 1 ] 92 | } 93 | ); 94 | } 95 | 96 | var boolIsNullCase = strHtml === '' 97 | , $content = document.getElementById( strListId ) 98 | ; 99 | 100 | if ( ! boolIsNullCase ) { 101 | $content.innerHTML = strHtml; 102 | } 103 | 104 | poziworldExtension.i18n.init() 105 | .then( Page.localize.bind( null, 'popup' ) ) 106 | .then( addEventListeners ) 107 | .then( setLinksUrls.bind( null, boolIsNullCase, $content ) ); 108 | } ); 109 | } 110 | 111 | /** 112 | * Add event listeners. 113 | **/ 114 | 115 | function addEventListeners() { 116 | addEvent( 117 | document.getElementById( 'toolbarOpenOptionsPageBtn' ) 118 | , 'click' 119 | , function() { 120 | const strLog = 'toolbar'; 121 | 122 | trackData( 123 | strLog 124 | , 'openOptions' 125 | , { strPage : strPage } 126 | ); 127 | 128 | Global.openOptionsPage( strLogPage + strLogPageDivider + strLog ); 129 | } 130 | ); 131 | 132 | addEvent( 133 | document.getElementById( 'toolbarClosePopupPageBtn' ) 134 | , 'click' 135 | , function() { 136 | trackData( 137 | 'toolbar' 138 | , 'closePopup' 139 | , { strPage : strPage } 140 | ); 141 | 142 | window.close(); 143 | } 144 | ); 145 | 146 | Page.addDevelopersMessageEventListeners(); 147 | 148 | addEvent( 149 | document.querySelectorAll( '#tunesSuggestionInfo a' ) 150 | , 'click' 151 | , function( objEvent ) { 152 | const $this = objEvent.target; 153 | 154 | trackData( 155 | 'tunesSuggestion' 156 | , 'followLink' 157 | , { strPerformer : $this.dataset.performer } 158 | ); 159 | 160 | Global.createTabOrUpdate( $this.href ); 161 | 162 | objEvent.preventDefault(); 163 | } 164 | ); 165 | 166 | addEvent( 167 | document.getElementsByClassName( 'recentTrack' ) 168 | , 'mouseleave' 169 | , function( objEvent ) { 170 | const $this = objEvent.currentTarget; 171 | 172 | $this.querySelector( '.fadeOutFadeIn' ).classList.remove( 'show' ); 173 | $this.querySelector( '.fadeInFadeOut' ).classList.remove( 'show' ); 174 | } 175 | ); 176 | 177 | addEvent( 178 | document.getElementsByClassName( 'providerAction' ) 179 | , 'click' 180 | , function( objEvent ) { 181 | const $this = objEvent.currentTarget; 182 | const strProvider = $this.dataset.provider; 183 | const strTrack = $this.parentNode.parentNode.dataset.track; 184 | const strUrl = composeRecentTrackActionUrl( strProvider, strTrack ); 185 | 186 | trackData( 187 | 'recentTracks' 188 | , 'providerAction' 189 | , { strProvider : strProvider } 190 | ); 191 | 192 | Global.createTabOrUpdate( strUrl ); 193 | } 194 | ); 195 | 196 | addEvent( 197 | document.getElementsByClassName( 'copyToClipboard' ) 198 | , 'click' 199 | , onCopyToClipboardCtaClick 200 | ); 201 | } 202 | 203 | /** 204 | * "Copy to clipboard" call-to-action is clicked on. 205 | * 206 | * @param {Event} objEvent - MouseEvent object. 207 | **/ 208 | 209 | function onCopyToClipboardCtaClick( objEvent ) { 210 | chrome.permissions.contains( { permissions : [ 'clipboardWrite' ] }, function( boolIsGranted ) { 211 | if ( boolIsGranted ) { 212 | copyToClipboard( objEvent ); 213 | } 214 | else { 215 | requestClipboardWritePermission( objEvent ); 216 | } 217 | } ); 218 | } 219 | 220 | /** 221 | * "clipboardWrite" permission hasn't been granted yet, request it. 222 | * 223 | * @param {Event} objEvent - MouseEvent object. 224 | **/ 225 | 226 | function requestClipboardWritePermission( objEvent ) { 227 | const strLog = 'requestClipboardWritePermission'; 228 | const $privacyStatementsContainer = document.getElementById( 'privacyStatementsContainer' ); 229 | 230 | Page.toggleElement( $privacyStatementsContainer, true ); 231 | 232 | chrome.permissions.request( { permissions: [ 'clipboardWrite' ] }, function( boolIsGranted ) { 233 | Global.checkForRuntimeError( 234 | function() { 235 | trackData( 236 | 'recentTracks' 237 | , strLog 238 | , { boolIsGranted : boolIsGranted } 239 | ); 240 | 241 | if ( boolIsGranted ) { 242 | copyToClipboard( objEvent ); 243 | } 244 | } 245 | , undefined 246 | , { strAction : strLog } 247 | , true 248 | ); 249 | 250 | Page.toggleElement( $privacyStatementsContainer, false ); 251 | } ); 252 | } 253 | 254 | /** 255 | * "clipboardWrite" permission granted, copy to clipboard. 256 | * 257 | * @param {Event} objEvent - MouseEvent object. 258 | **/ 259 | 260 | function copyToClipboard( objEvent ) { 261 | let $this = objEvent.currentTarget || objEvent.target; 262 | const $text = $this.closest( strListElementSelector ); 263 | 264 | if ( ! $text ) { 265 | /** 266 | * @todo Track. 267 | */ 268 | return; 269 | } 270 | 271 | const $trackInfo = $text.querySelector( strListElementInfoSelector ); 272 | 273 | if ( ! $trackInfo ) { 274 | /** 275 | * @todo Track. 276 | */ 277 | return; 278 | } 279 | 280 | trackData( 281 | 'recentTracks' 282 | , 'copyToClipboard' 283 | ); 284 | 285 | // http://stackoverflow.com/a/11128179/561712 286 | var objSelection = window.getSelection(); 287 | var objRange = document.createRange(); 288 | 289 | objRange.selectNodeContents( $trackInfo ); 290 | objSelection.removeAllRanges(); 291 | objSelection.addRange( objRange ); 292 | 293 | document.execCommand( 'copy' ); 294 | objSelection.removeAllRanges(); 295 | 296 | // Clicked on (target is) the button icon 297 | if ( ! $this.classList.contains( 'cta' ) ) { 298 | $this = $this.parentNode; 299 | } 300 | 301 | Page.showSuccess( $this.children[ 0 ] ); 302 | Page.showSuccess( $this.children[ 1 ] ); 303 | } 304 | 305 | /** 306 | * If user participates in UEIP, track some helpful insights. 307 | * 308 | * @param {string} strLog - The log entry marker. 309 | * @param {string} strAction - The action being tracked. 310 | * @param {Object} [objData] - Additional data to track. 311 | **/ 312 | 313 | function trackData( strLog, strAction, objData ) { 314 | let objTrackingData = { 315 | strAction : strAction 316 | , strLanguage : poziworldExtension.i18n.getLanguage() 317 | , strVersion : strConstExtensionVersion 318 | , strVersionName : strConstExtensionVersionName 319 | }; 320 | 321 | if ( typeof objData === 'object' && ! Global.isEmpty( objData ) ) { 322 | for ( let strKey in objData ) { 323 | if ( objData.hasOwnProperty( strKey ) && typeof strKey === 'string' ) { 324 | objTrackingData[ strKey ] = objData[ strKey ]; 325 | } 326 | } 327 | } 328 | 329 | if ( typeof strLog === 'string' && strLog !== '' ) { 330 | strLog = strLogPage + strLogPageDivider + strLog; 331 | } 332 | else { 333 | strLog = strLogPage; 334 | } 335 | 336 | chrome.runtime.sendMessage( 337 | { 338 | strReceiver : 'background' 339 | , strLog : strLog 340 | , objVars : objTrackingData 341 | } 342 | ); 343 | } 344 | 345 | /** 346 | * Compose a URL from the given parameters 347 | * 348 | * @param {string} strProvider - Service provider. 349 | * @param {string} strQuery - Query. 350 | * @return {string} 351 | **/ 352 | 353 | function composeRecentTrackActionUrl( strProvider, strQuery ) { 354 | if ( typeof strProvider === 'undefined' 355 | || typeof strQuery === 'undefined' 356 | || strProvider === '' 357 | || strQuery === '' 358 | ) { 359 | return ''; 360 | } 361 | 362 | return strRecentTrackActionUrl 363 | + 'p=' + strProvider 364 | + '&q=' + encodeQuery( strQuery ) 365 | + '&v=' + strConstExtensionVersion 366 | + '&l=' + poziworldExtension.i18n.getLanguage() 367 | ; 368 | } 369 | 370 | /** 371 | * Encode query 372 | * 373 | * @param {string} strQuery - Query. 374 | * @return {string} 375 | **/ 376 | 377 | function encodeQuery( strQuery ) { 378 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent 379 | return encodeURIComponent( strQuery ) 380 | // Note that although RFC3986 reserves "!", RFC5987 does not, 381 | // so we do not need to escape it 382 | .replace( /['()]/g, escape ) // i.e., %27 %28 %29 383 | .replace( /\*/g, '%2A' ) 384 | // The following are not required for percent-encoding 385 | // per RFC5987, so we can allow for a little better readability 386 | // over the wire: |`^ 387 | .replace( /%(?:7C|60|5E)/g, unescape ) 388 | ; 389 | } 390 | 391 | /** 392 | * The null case (when there are no items in the Recently Played list) shows suggestions for different styles of music. The text comes from the translation, but the translation doesn't contain URLs. 393 | * 394 | * @param {boolean} nullCase - Whether there are any items in the Recently Played list. 395 | * @param {HTMLElement} container 396 | */ 397 | 398 | function setLinksUrls( nullCase, container ) { 399 | if ( nullCase ) { 400 | container.querySelector( '[data-performer="funbox"]' ).href = 'https://vk.com/funboxband'; 401 | container.querySelector( '[data-performer="nickybutter"]' ).href = 'https://soundcloud.com/nickybutter'; 402 | container.querySelector( '[data-performer="theroux"]' ).href = 'https://soundcloud.com/theroux'; 403 | container.querySelector( '[data-performer="emilyclibourn"]' ).href = 'https://soundcloud.com/emilyclibourn'; 404 | } 405 | } 406 | } )(); 407 | -------------------------------------------------------------------------------- /static/global/img/cogs_icon&24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/cogs_icon&24.png -------------------------------------------------------------------------------- /static/global/img/delete_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/delete_icon&16.png -------------------------------------------------------------------------------- /static/global/img/emotion_smile_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/emotion_smile_icon&16.png -------------------------------------------------------------------------------- /static/global/img/info-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /static/global/img/list_bullets_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/list_bullets_icon&16.png -------------------------------------------------------------------------------- /static/global/img/no-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/global/img/notification-icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/notification-icon-80.png -------------------------------------------------------------------------------- /static/global/img/off_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/off_icon&16.png -------------------------------------------------------------------------------- /static/global/img/options-checkbox-tick-icon-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/options-checkbox-tick-icon-11.png -------------------------------------------------------------------------------- /static/global/img/playback_next_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/playback_next_icon&16.png -------------------------------------------------------------------------------- /static/global/img/playback_play_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/playback_play_icon&16.png -------------------------------------------------------------------------------- /static/global/img/playback_prev_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/playback_prev_icon&16.png -------------------------------------------------------------------------------- /static/global/img/playback_stop_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/playback_stop_icon&16.png -------------------------------------------------------------------------------- /static/global/img/pozitone-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-icon-128.png -------------------------------------------------------------------------------- /static/global/img/pozitone-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-icon-16.png -------------------------------------------------------------------------------- /static/global/img/pozitone-icon-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-icon-19.png -------------------------------------------------------------------------------- /static/global/img/pozitone-icon-38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-icon-38.png -------------------------------------------------------------------------------- /static/global/img/pozitone-icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-icon-48.png -------------------------------------------------------------------------------- /static/global/img/pozitone-notification-icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/pozitone-notification-icon-80.png -------------------------------------------------------------------------------- /static/global/img/pozitone.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/global/img/projects/pe-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/projects/pe-icon-64.png -------------------------------------------------------------------------------- /static/global/img/projects/pwm-icon-128.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/global/img/projects/sttb-icon-50.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/global/img/projects/swaggy-icon-48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/global/img/round_plus_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/round_plus_icon&16.png -------------------------------------------------------------------------------- /static/global/img/sound_down_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/sound_down_icon&16.png -------------------------------------------------------------------------------- /static/global/img/sound_high_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/sound_high_icon&16.png -------------------------------------------------------------------------------- /static/global/img/sound_mute_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/sound_mute_icon&16.png -------------------------------------------------------------------------------- /static/global/img/sound_up_icon&16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/global/img/sound_up_icon&16.png -------------------------------------------------------------------------------- /static/global/js/angular/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.14 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(q,d,C){'use strict';function v(r,k,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,y){function z(){l&&(h.cancel(l),l=null);m&&(m.$destroy(),m=null);n&&(l=h.leave(n),l.then(function(){l=null}),n=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=r.current;n=y(b,function(b){h.enter(b,null,n||f).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||k()});z()});m=c.scope=b;m.$emit("$viewContentLoaded"); 7 | m.$eval(w)}else z()}var m,n,l,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,k,h){return{restrict:"ECA",priority:-400,link:function(a,f){var b=h.current,c=b.locals;f.html(c.$template);var y=d(f.contents());b.controller&&(c.$scope=a,c=k(b.controller,c),b.controllerAs&&(a[b.controllerAs]=c),f.data("$ngControllerController",c),f.children().data("$ngControllerController",c));y(a)}}}q=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return d.extend(Object.create(a), 8 | f)}function k(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},h=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}var h={};this.when=function(a,f){var b=d.copy(f);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);h[a]=d.extend(b,a&&k(a,b));if(a){var c="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[c]=d.extend({redirectTo:a},k(c,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,c,k,q,x){function m(b){var e=s.current; 10 | (v=(p=l())&&e&&p.$$route===e.$$route&&d.equals(p.pathParams,e.pathParams)&&!p.reloadOnSearch&&!w)||!e&&!p||a.$broadcast("$routeChangeStart",p,e).defaultPrevented&&b&&b.preventDefault()}function n(){var u=s.current,e=p;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),c.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,g;d.forEach(a,function(b,e){a[e]=d.isString(b)?k.get(b):k.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(g=e.templateUrl)&&(d.isFunction(g)&&(g=g(e.params)),g=x.getTrustedResourceUrl(g),d.isDefined(g)&&(e.loadedTemplateUrl=g,b=q(g)));d.isDefined(b)&&(a.$template=b);return c.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function l(){var a,b;d.forEach(h,function(c,h){var g;if(g=!b){var k=f.path();g=c.keys;var m={};if(c.regexp)if(k=c.regexp.exec(k)){for(var l=1,n=k.length;l 1 && match[1]) || ''; 21 | } 22 | 23 | var versionIdentifier = getFirstMatch(/version\/(\d+(\.\d+)?)/i) 24 | , result 25 | 26 | if (/opera|opr/i.test(ua)) { 27 | result = { 28 | name: 'Opera' 29 | , version: versionIdentifier || getFirstMatch(/(?:opera|opr)[\s\/](\d+(\.\d+)?)/i) 30 | , versionFull: getFirstMatch(/(?:opera|opr)\/(\d+(\.\d+)+(\.\d+)+( \([a-zA-Z0-9 ]{1,50}\))?)/i) 31 | } 32 | } 33 | else if (/yabrowser/i.test(ua)) { 34 | result = { 35 | name: 'Yandex.Browser' 36 | , version: getFirstMatch(/(?:yabrowser)\/(\d+(\.\d+)?)/i) 37 | , versionFull: getFirstMatch(/(?:yabrowser)\/(\d+(\.\d+)+(\.\d+)?)/i) 38 | } 39 | } 40 | else if (/mrchrome soc/i.test(ua)) { 41 | result = { 42 | name: 'Amigo' 43 | , version: getFirstMatch(/(?:chrome)\/(\d+(\.\d+)?)/i) 44 | , versionFull: getFirstMatch(/(?:chrome)\/(\d+(\.\d+)+(\.\d+)?)/i) 45 | } 46 | 47 | result.chromeVersion = result.version; 48 | result.chromeVersionFull = result.versionFull; 49 | } 50 | else if (/nichrome\/self/i.test(ua)) { 51 | result = { 52 | name: 'Rambler-Browser' 53 | , version: getFirstMatch(/(?:nichrome\/self)\/(\d+(\.\d+)?)/i) 54 | , versionFull: getFirstMatch(/(?:chrome)\/(\d+(\.\d+)+(\.\d+)?)/i) 55 | } 56 | } 57 | else if (/dragon/i.test(ua)) { 58 | result = { 59 | name: 'Comodo Dragon' 60 | , version: getFirstMatch(/(?:dragon)\/(\d+(\.\d+)?)/i) 61 | , versionFull: getFirstMatch(/(?:dragon)\/(\d+(\.\d+)+(\.\d+)?)/i) 62 | } 63 | } 64 | else if (/corom/i.test(ua)) { 65 | result = { 66 | name: 'Cốc Cốc' 67 | , version: getFirstMatch(/(?:corom)\/(\d+(\.\d+)?)/i) 68 | , versionFull: getFirstMatch(/(?:corom)\/(\d+(\.\d+)+(\.\d+)?)/i) 69 | } 70 | } 71 | else if (/sleipnir/i.test(ua)) { 72 | result = { 73 | name: 'Sleipnir' 74 | , version: getFirstMatch(/(?:sleipnir)\/(\d+(\.\d+)?)/i) 75 | , versionFull: getFirstMatch(/(?:sleipnir)\/(\d+(\.\d+)+(\.\d+)?)/i) 76 | } 77 | } 78 | else if (/spark/i.test(ua)) { 79 | result = { 80 | name: 'Spark' 81 | , version: getFirstMatch(/(?:spark)\/(\d+(\.\S+)?)/i) 82 | , versionFull: getFirstMatch(/(?:spark)\/(\d+(\.\S+)?)/i) 83 | } 84 | } 85 | else if (/iron/i.test(ua)) { 86 | result = { 87 | name: 'SRWare Iron' 88 | , version: getFirstMatch(/(?:iron)\/(\d+(\.\d+)?)/i) 89 | , versionFull: getFirstMatch(/(?:iron)\/(\d+(\.\d+)+(\.\d+)?)/i) 90 | } 91 | } 92 | else if (/u01-04/i.test(ua)) { 93 | result = { 94 | name: 'Uran' 95 | , version: getFirstMatch(/(?:chrome)\/(\d+(\.\d+)?)/i) 96 | , versionFull: getFirstMatch(/(?:chrome)\/(\d+(\.\d+)+(\.\d+)?)/i) 97 | } 98 | 99 | result.chromeVersion = result.version; 100 | result.chromeVersionFull = result.versionFull; 101 | } 102 | else if (/ edg\//i.test(ua)) { 103 | result = { 104 | name: 'Edge (Chromium)' 105 | , version: getFirstMatch(/(?:edg)\/(\d+(\.\d+)?)/i) 106 | , versionFull: getFirstMatch(/(?:edg)\/(\d+(\.\d+)+(\.\d+)?)/i) 107 | } 108 | 109 | result.chromeVersion = result.version; 110 | result.chromeVersionFull = result.versionFull; 111 | } 112 | else if (/chrome|crios/i.test(ua)) { 113 | result = { 114 | name: 'Chrome' // or a few others that don't identify themselves 115 | , version: getFirstMatch(/(?:chrome|crios)\/(\d+(\.\d+)?)/i) 116 | , versionFull: getFirstMatch(/(?:chrome|crios)\/(\d+(\.\d+)+(\.\d+)?)/i) 117 | } 118 | 119 | result.chromeVersion = result.version; 120 | result.chromeVersionFull = result.versionFull; 121 | } 122 | else result = { 123 | name: 'Unknown' 124 | } 125 | 126 | if ( typeof result.name !== 'undefined' && [ 'Amigo', 'Chrome', 'Unknown', 'Uran', 'Edge (Chromium)' ].indexOf(result.name) === -1 ) { 127 | result.chromeVersion = getFirstMatch(/(?:chrome|crios)\/(\d+(\.\d+)?)/i); 128 | result.chromeVersionFull = getFirstMatch(/(?:chrome|crios)\/(\d+(\.\d+)+(\.\d+)?)/i); 129 | } 130 | 131 | result.userAgent = ua 132 | 133 | return result 134 | } 135 | 136 | var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '') 137 | 138 | return bowser 139 | }); 140 | -------------------------------------------------------------------------------- /static/global/js/const.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product : PoziTone 4 | Author : PoziWorld 5 | Copyright : Copyright (c) 2013-2016 PoziWorld 6 | License : pozitone.com/license 7 | File : global/js/const.js 8 | Description : Constants JavaScript 9 | 10 | Table of Contents: 11 | 12 | Constants 13 | Storage 14 | PoziTone 15 | 16 | ============================================================================ */ 17 | 18 | 'use strict'; 19 | 20 | /* ============================================================================= 21 | 22 | Constants 23 | 24 | ============================================================================ */ 25 | 26 | const 27 | // Extension 28 | strConstExtensionId = chrome.runtime.id 29 | , objConstExtensionManifest = chrome.runtime.getManifest() 30 | , strConstExtensionName = objConstExtensionManifest.name 31 | , strConstExtensionVersion = objConstExtensionManifest.version 32 | , strConstExtensionVersionName = objConstExtensionManifest.version_name || strConstExtensionVersion 33 | 34 | // Browser & UI 35 | , boolConstIsBowserAvailable = typeof bowser === 'object' 36 | , boolConstIsOpera = 37 | boolConstIsBowserAvailable && bowser.name === 'Opera' 38 | , boolConstIsYandex = 39 | boolConstIsBowserAvailable && bowser.name === 'Yandex.Browser' 40 | , boolConstIsOperaAddon = boolConstIsOpera || boolConstIsYandex 41 | , strConstChromeVersion = 42 | boolConstIsBowserAvailable ? bowser.chromeVersion : '' 43 | , boolConstUseOptionsUi = 44 | strConstChromeVersion >= '40.0' && ! boolConstIsOpera 45 | 46 | // URLs 47 | , strConstVersionParam = '%v' 48 | , strConstLangParam = '%lang' 49 | , strConstMessageUrl = 50 | 'https://poziworld.github.io/PoziTone/message/v%v/?lang=%lang&ref=ext&ueip=' 51 | , strConstTranslationUrl = 'https://www.transifex.com/poziworld/pozitone/' 52 | 53 | // External modules, separators, and Notifications 54 | , strConstGenericStringSeparator = '_' 55 | , strConstExternalModuleSeparator = strConstGenericStringSeparator 56 | , strConstNotificationIdSeparator = strConstGenericStringSeparator 57 | , strConstNotificationLinesSeparator = "\n\n" 58 | , strConstNotificationId = 59 | strConstExtensionName + strConstNotificationIdSeparator 60 | 61 | // Developers Message: Browser Action settings (tooltip, badge) 62 | , strConstBadgeOnDevelopersMessageText = '1' 63 | , strConstBadgeOnDevelopersMessageColor = [ 44, 160, 44, 255 ] 64 | 65 | // Developers Message: Alarm 66 | , strConstDevelopersMessageAlarmName = 'developersMessage' 67 | , intConstDevelopersMessageAlarmDelayMinutes = 1440 68 | 69 | // Settings 70 | , strConstSettingsPrefix = 'objSettings_' 71 | , strConstGeneralSettingsSuffix = 'general' 72 | , strConstGeneralSettings = 73 | strConstSettingsPrefix + strConstGeneralSettingsSuffix 74 | 75 | , strConstLogOnInstalled = 'chrome.runtime.onInstalled' 76 | 77 | , objConstUserSetUp = boolConstIsBowserAvailable 78 | ? { 79 | currentVersion : strConstExtensionVersion 80 | , currentVersionName : strConstExtensionVersionName 81 | , browserName : bowser.name 82 | , browserVersion : bowser.version 83 | , browserVersionFull : bowser.versionFull 84 | , chromeVersion : strConstChromeVersion 85 | , chromeVersionFull : bowser.chromeVersionFull 86 | , userAgent : bowser.userAgent 87 | 88 | /** 89 | * @todo Use a listener instead of poziworldExtension.i18n.saveExtensionLanguage 90 | */ 91 | 92 | , language : '' 93 | } 94 | : {} 95 | 96 | , objConst = { 97 | strIncentiveCarrotUrl : 'https://cash.me/$PoziTone' 98 | } 99 | ; 100 | 101 | /* ============================================================================= 102 | 103 | Storage 104 | 105 | ============================================================================ */ 106 | 107 | var StorageApi = chrome.storage 108 | , StorageLocal = StorageApi.local 109 | , StorageSync = StorageApi.sync || StorageLocal 110 | ; 111 | 112 | /* ============================================================================= 113 | 114 | PoziTone 115 | 116 | ============================================================================ */ 117 | 118 | var pozitone = {}; 119 | -------------------------------------------------------------------------------- /static/global/js/global-v2.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | Table of Contents: 9 | 10 | Global2 11 | isModuleBuiltIn() 12 | isModuleBuiltInApiCompliant() 13 | isModuleExternal() 14 | On Load 15 | Initialize 16 | 17 | ============================================================================ */ 18 | 19 | ( function () { 20 | 'use strict'; 21 | 22 | /** 23 | * Browser-specific extension-related URLs. 24 | * 25 | * @typedef {object} ExtensionStoreSpecificUrls 26 | * @property {string} chrome - URL for an extension installed via the Chrome Web Store. 27 | * @property {string} edge - URL for an extension for the new Microsoft Edge. 28 | * @property {string} opera - URL for an extension installed via the Opera add-ons catalogue. 29 | */ 30 | 31 | /** 32 | * Browser-specific extension-related URLs. 33 | * 34 | * @typedef {object} Urls 35 | * @property {ExtensionStoreSpecificUrls} installation 36 | * @property {ExtensionStoreSpecificUrls} rating 37 | * @property {ExtensionStoreSpecificUrls} feedback 38 | */ 39 | 40 | const URLS = { 41 | installation: { 42 | chrome: 'https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm', 43 | edge: 'https://microsoftedge.microsoft.com/addons/detail/mnfohmojhhcbbnafeehfhghjaeaokjbl', 44 | opera: 'https://addons.opera.com/extensions/details/pozitone/', 45 | }, 46 | rating: { 47 | chrome: 'https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/reviews', 48 | edge: 'https://microsoftedge.microsoft.com/addons/detail/mnfohmojhhcbbnafeehfhghjaeaokjbl', 49 | opera: 'https://addons.opera.com/extensions/details/pozitone/#rating-form', 50 | }, 51 | feedback: { 52 | chrome: 'https://chrome.google.com/webstore/detail/pozitone/bdglbogiolkffcmojmmkipgnpkfipijm/support', 53 | edge: 'https://feedback.pozitone.com/', 54 | opera: 'https://addons.opera.com/extensions/details/pozitone/#feedback-container', 55 | }, 56 | swaggyProject: { 57 | chrome: 'https://chrome.google.com/webstore/detail/beblcchllamebejoakjbhhajpmlkjoaf', 58 | edge: 'https://github.com/PoziWorld/Swaggy', 59 | opera: 'https://chrome.google.com/webstore/detail/beblcchllamebejoakjbhhajpmlkjoaf', 60 | }, 61 | poziworldElfProject: { 62 | chrome: 'https://github.com/PoziWorld/PoziWorld-Elf', 63 | edge: 'https://github.com/PoziWorld/PoziWorld-Elf', 64 | opera: 'https://github.com/PoziWorld/PoziWorld-Elf', 65 | }, 66 | scrollToTopButtonProject: { 67 | chrome: 'https://chrome.google.com/webstore/detail/scroll-to-top-button/chinfkfmaefdlchhempbfgbdagheknoj', 68 | edge: 'https://microsoftedge.microsoft.com/addons/detail/dobeplcigkjlbajngcgnndecohjkjmia', 69 | opera: 'https://addons.opera.com/extensions/details/scroll-to-top-button/', 70 | }, 71 | printWasteMinimizerProject: { 72 | chrome: 'https://chrome.google.com/webstore/detail/print-waste-minimizer/nhglpabogkpplpcemgiaopjoehcpajdk', 73 | edge: 'https://microsoftedge.microsoft.com/addons/detail/badkpckfhemokiobdfnjepgnllimkbia', 74 | opera: 'https://addons.opera.com/extensions/details/print-waste-minimizer/', 75 | }, 76 | }; 77 | 78 | setUp(); 79 | 80 | /** 81 | * Make the logic readily available. 82 | */ 83 | 84 | function setUp() { 85 | exposeApi(); 86 | } 87 | 88 | /** 89 | * Create an instance of the Global2 API and expose it to other parts of the extension. 90 | */ 91 | 92 | function exposeApi() { 93 | if ( typeof pozitone === 'undefined' ) { 94 | window.pozitone = {}; 95 | } 96 | 97 | pozitone.global = new Global2(); 98 | } 99 | 100 | /** 101 | * @constructor 102 | */ 103 | 104 | function Global2() { 105 | } 106 | 107 | /** 108 | * Checks whether the module is built-in. 109 | * 110 | * @type method 111 | * @param strModuleId 112 | * Module ID. 113 | * @return boolean 114 | **/ 115 | 116 | Global2.prototype.isModuleBuiltIn = function ( strModuleId ) { 117 | return strModuleId in Global.objModules; 118 | }; 119 | 120 | /** 121 | * Checks whether the module is built-in and API compliant. 122 | * 123 | * @type method 124 | * @param strModuleId 125 | * Module ID. 126 | * @param boolIsBuiltIn 127 | * Optional. Whether the module is built-in. 128 | * @return boolean 129 | **/ 130 | 131 | Global2.prototype.isModuleBuiltInApiCompliant = function ( strModuleId, boolIsBuiltIn ) { 132 | if ( boolIsBuiltIn || this.isModuleBuiltIn( strModuleId ) ) { 133 | var objModule = Global.objModules[ strModuleId ]; 134 | 135 | return typeof objModule.boolIsApiCompliant === 'boolean' && objModule.boolIsApiCompliant; 136 | } 137 | 138 | return false; 139 | }; 140 | 141 | /** 142 | * Checks whether the module is external. 143 | * 144 | * @type method 145 | * @param strModuleId 146 | * Module ID. 147 | * @return boolean 148 | **/ 149 | 150 | Global2.prototype.isModuleExternal = function ( strModuleId ) { 151 | return ! this.isModuleBuiltIn( strModuleId ); 152 | }; 153 | 154 | /** 155 | * Don't show these buttons, if they've been clicked for this track already. 156 | * 157 | * @return {string[]} - Messages-indicators. 158 | */ 159 | 160 | Global2.prototype.getAddTrackToPlaylistFeedbackMessages = function () { 161 | return [ 162 | poziworldExtension.i18n.getMessage( 'notificationAddTrackToPlaylistFeedbackSuccessfullyAdded' ), 163 | poziworldExtension.i18n.getMessage( 'notificationAddTrackToPlaylistFeedbackAlreadyInPlaylist' ), 164 | ]; 165 | }; 166 | 167 | /** 168 | * Don't show these buttons, if they've been clicked for this track already. 169 | * 170 | * @return {string} - Message-indicator. 171 | */ 172 | 173 | Global2.prototype.getFavoriteStatusSuccess = function () { 174 | return poziworldExtension.i18n.getMessage( 'notificationFavoriteStatusSuccess' ); 175 | }; 176 | 177 | /** 178 | * Some changes might require reloading the extension and reopening the Options page. 179 | * 180 | * @param {string} optionsPageTab - The options page tab/section/“subpage” to reopen after the extension reload. 181 | * @param {string} [logMessage] - The log message passed to the background view. 182 | */ 183 | 184 | Global2.prototype.reloadExtensionAndOptions = function ( optionsPageTab, logMessage ) { 185 | Global.setStorageItems( 186 | StorageLocal, 187 | { 188 | boolOpenOptionsPageOnRestart: true, 189 | strOptionsPageToOpen: optionsPageTab, 190 | }, 191 | strLog + ', reopen Options', 192 | pozitone.background ? 193 | pozitone.background.reloadExtension : 194 | requestExtensionReload.bind( null, logMessage ) 195 | ); 196 | }; 197 | 198 | /** 199 | * Return browser-specific extension installation URL. 200 | * 201 | * @returns {string} 202 | */ 203 | 204 | Global2.prototype.getInstallationUrl = function () { 205 | return getUrl( 'installation' ); 206 | }; 207 | 208 | /** 209 | * Return browser-specific extension rating/review URL. 210 | * 211 | * @returns {string} 212 | */ 213 | 214 | Global2.prototype.getRatingUrl = function () { 215 | return getUrl( 'rating' ); 216 | }; 217 | 218 | /** 219 | * Return browser-specific extension feedback/issue reporting URL. 220 | * 221 | * @returns {string} 222 | */ 223 | 224 | Global2.prototype.getFeedbackUrl = function () { 225 | return getUrl( 'feedback' ); 226 | }; 227 | 228 | /** 229 | * Return browser-specific sister project URL. 230 | * 231 | * @param {string} urlId 232 | * @returns {string} 233 | */ 234 | 235 | Global2.prototype.getSisterProjectUrl = function ( urlId ) { 236 | return getUrl( urlId ); 237 | }; 238 | 239 | /** 240 | * Ask the background view to handle the extension reload. 241 | * 242 | * @param {string} logMessage - The log message passed to the background view. 243 | */ 244 | 245 | function requestExtensionReload( logMessage ) { 246 | chrome.runtime.sendMessage( 247 | { 248 | strReceiver: 'background', 249 | strLog: logMessage, 250 | extensionReloadRequested: true, 251 | } 252 | ); 253 | } 254 | 255 | /** 256 | * Return an extension-related URL depending on the browser. 257 | * 258 | * @param {('installation'|'rating'|'feedback')} urlType 259 | */ 260 | 261 | function getUrl( urlType ) { 262 | return URLS[ urlType ][ poziworldExtension.utils.getExtensionStoreType() ]; 263 | } 264 | } )(); 265 | 266 | /* ============================================================================= 267 | 268 | On Load 269 | 270 | ============================================================================ */ 271 | 272 | /** 273 | * Initializes. 274 | * 275 | * @type method 276 | * @param No Parameters taken 277 | * @return void 278 | **/ 279 | 280 | Global.init(); 281 | -------------------------------------------------------------------------------- /static/global/js/i18next/i18nextBrowserLanguageDetector.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.i18nextBrowserLanguageDetector=t()}(this,function(){"use strict";function e(e){return i.call(a.call(arguments,1),function(t){if(t)for(var o in t)void 0===e[o]&&(e[o]=t[o])}),e}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(){return{order:["querystring","cookie","localStorage","navigator","htmlTag"],lookupQuerystring:"lng",lookupCookie:"i18next",lookupLocalStorage:"i18nextLng",caches:["localStorage"],excludeCacheFor:["cimode"]}}var n=[],i=n.forEach,a=n.slice,r={create:function(e,t,o,n){var i=void 0;if(o){var a=new Date;a.setTime(a.getTime()+60*o*1e3),i="; expires="+a.toGMTString()}else i="";n=n?"domain="+n+";":"",document.cookie=e+"="+t+i+";"+n+"path=/"},read:function(e){for(var t=e+"=",o=document.cookie.split(";"),n=0;n0){var r=n[i].substring(0,a);r===e.lookupQuerystring&&(t=n[i].substring(a+1))}}return t}},l=void 0;try{l="undefined"!==window&&null!==window.localStorage;window.localStorage.setItem("i18next.translate.boo","foo"),window.localStorage.removeItem("i18next.translate.boo")}catch(e){l=!1}var s={name:"localStorage",lookup:function(e){var t=void 0;if(e.lookupLocalStorage&&l){var o=window.localStorage.getItem(e.lookupLocalStorage);o&&(t=o)}return t},cacheUserLanguage:function(e,t){t.lookupLocalStorage&&l&&window.localStorage.setItem(t.lookupLocalStorage,e)}},g={name:"navigator",lookup:function(e){var t=[];if("undefined"!=typeof navigator){if(navigator.languages)for(var o=0;o0?t:void 0}},f={name:"htmlTag",lookup:function(e){var t=void 0,o=e.htmlTag||("undefined"!=typeof document?document.documentElement:null);return o&&"function"==typeof o.getAttribute&&(t=o.getAttribute("lang")),t}},d=function(){function e(e,t){for(var o=0;o1&&void 0!==arguments[1]?arguments[1]:{};t(this,n),this.type="languageDetector",this.detectors={},this.init(e,o)}return d(n,[{key:"init",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};this.services=t,this.options=e(n,this.options||{},o()),this.i18nOptions=i,this.addDetector(u),this.addDetector(c),this.addDetector(s),this.addDetector(g),this.addDetector(f)}},{key:"addDetector",value:function(e){this.detectors[e.name]=e}},{key:"detect",value:function(e){var t=this;e||(e=this.options.order);var o=[];e.forEach(function(e){if(t.detectors[e]){var n=t.detectors[e].lookup(t.options);n&&"string"==typeof n&&(n=[n]),n&&(o=o.concat(n))}});var n=void 0;if(o.forEach(function(e){if(!n){var o=t.services.languageUtils.formatLanguageCode(e);t.services.languageUtils.isWhitelisted(o)&&(n=o)}}),!n){var i=this.i18nOptions.fallbackLng;"string"==typeof i&&(i=[i]),i||(i=[]),n="[object Array]"===Object.prototype.toString.apply(i)?i[0]:i[0]||i.default&&i.default[0]}return n}},{key:"cacheUserLanguage",value:function(e,t){var o=this;t||(t=this.options.caches),t&&(this.options.excludeCacheFor&&this.options.excludeCacheFor.indexOf(e)>-1||t.forEach(function(t){o.detectors[t]&&o.detectors[t].cacheUserLanguage(e,o.options)}))}}]),n}();return v.type="languageDetector",v}); -------------------------------------------------------------------------------- /static/global/js/i18next/i18nextXHRBackend.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.i18nextXHRBackend = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var arr = []; 8 | var each = arr.forEach; 9 | var slice = arr.slice; 10 | 11 | function defaults(obj) { 12 | each.call(slice.call(arguments, 1), function (source) { 13 | if (source) { 14 | for (var prop in source) { 15 | if (obj[prop] === undefined) obj[prop] = source[prop]; 16 | } 17 | } 18 | }); 19 | return obj; 20 | } 21 | 22 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 23 | 24 | function addQueryString(url, params) { 25 | if (params && (typeof params === 'undefined' ? 'undefined' : _typeof(params)) === 'object') { 26 | var queryString = '', 27 | e = encodeURIComponent; 28 | 29 | // Must encode data 30 | for (var paramName in params) { 31 | queryString += '&' + e(paramName) + '=' + e(params[paramName]); 32 | } 33 | 34 | if (!queryString) { 35 | return url; 36 | } 37 | 38 | url = url + (url.indexOf('?') !== -1 ? '&' : '?') + queryString.slice(1); 39 | } 40 | 41 | return url; 42 | } 43 | 44 | // https://gist.github.com/Xeoncross/7663273 45 | function ajax(url, options, callback, data, cache) { 46 | 47 | if (data && (typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object') { 48 | if (!cache) { 49 | data['_t'] = new Date(); 50 | } 51 | // URL encoded form data must be in querystring format 52 | data = addQueryString('', data).slice(1); 53 | } 54 | 55 | if (options.queryStringParams) { 56 | url = addQueryString(url, options.queryStringParams); 57 | } 58 | 59 | try { 60 | var x; 61 | if (XMLHttpRequest) { 62 | x = new XMLHttpRequest(); 63 | } else { 64 | x = new ActiveXObject('MSXML2.XMLHTTP.3.0'); 65 | } 66 | x.open(data ? 'POST' : 'GET', url, options.async); 67 | if (!options.crossDomain) { 68 | x.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 69 | } 70 | x.withCredentials = !!options.withCredentials; 71 | if (data) { 72 | x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 73 | } 74 | if (x.overrideMimeType) { 75 | x.overrideMimeType("application/json"); 76 | } 77 | var h = options.customHeaders; 78 | if (h) { 79 | for (var i in h) { 80 | x.setRequestHeader(i, h[i]); 81 | } 82 | } 83 | x.onreadystatechange = function () { 84 | x.readyState > 3 && callback && callback(x.responseText, x); 85 | }; 86 | x.send(data); 87 | } catch (e) { 88 | console && console.log(e); 89 | } 90 | } 91 | 92 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 93 | 94 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 95 | 96 | function getDefaults() { 97 | return { 98 | loadPath: '/locales/{{lng}}/{{ns}}.json', 99 | addPath: '/locales/add/{{lng}}/{{ns}}', 100 | allowMultiLoading: false, 101 | parse: JSON.parse, 102 | crossDomain: false, 103 | ajax: ajax, 104 | async: true 105 | }; 106 | } 107 | 108 | var Backend = function () { 109 | function Backend(services) { 110 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 111 | 112 | _classCallCheck(this, Backend); 113 | 114 | this.init(services, options); 115 | 116 | this.type = 'backend'; 117 | } 118 | 119 | _createClass(Backend, [{ 120 | key: 'init', 121 | value: function init(services) { 122 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 123 | 124 | this.services = services; 125 | this.options = defaults(options, this.options || {}, getDefaults()); 126 | } 127 | }, { 128 | key: 'readMulti', 129 | value: function readMulti(languages, namespaces, callback) { 130 | var loadPath = this.options.loadPath; 131 | if (typeof this.options.loadPath === 'function') { 132 | loadPath = this.options.loadPath(languages, namespaces); 133 | } 134 | 135 | var url = this.services.interpolator.interpolate(loadPath, { lng: languages.join('+'), ns: namespaces.join('+') }); 136 | 137 | this.loadUrl(url, callback); 138 | } 139 | }, { 140 | key: 'read', 141 | value: function read(language, namespace, callback) { 142 | var loadPath = this.options.loadPath; 143 | if (typeof this.options.loadPath === 'function') { 144 | loadPath = this.options.loadPath([language], [namespace]); 145 | } 146 | 147 | var url = this.services.interpolator.interpolate(loadPath, { lng: language, ns: namespace }); 148 | 149 | this.loadUrl(url, callback); 150 | } 151 | }, { 152 | key: 'loadUrl', 153 | value: function loadUrl(url, callback) { 154 | var _this = this; 155 | 156 | this.options.ajax(url, this.options, function (data, xhr) { 157 | if (xhr.status >= 500 && xhr.status < 600) return callback('failed loading ' + url, true /* retry */); 158 | if (xhr.status >= 400 && xhr.status < 500) return callback('failed loading ' + url, false /* no retry */); 159 | 160 | var ret = void 0, 161 | err = void 0; 162 | try { 163 | ret = _this.options.parse(data, url); 164 | } catch (e) { 165 | err = 'failed parsing ' + url + ' to json'; 166 | } 167 | if (err) return callback(err, false); 168 | callback(null, ret); 169 | }); 170 | } 171 | }, { 172 | key: 'create', 173 | value: function create(languages, namespace, key, fallbackValue) { 174 | var _this2 = this; 175 | 176 | if (typeof languages === 'string') languages = [languages]; 177 | 178 | var payload = {}; 179 | payload[key] = fallbackValue || ''; 180 | 181 | languages.forEach(function (lng) { 182 | var url = _this2.services.interpolator.interpolate(_this2.options.addPath, { lng: lng, ns: namespace }); 183 | 184 | _this2.options.ajax(url, _this2.options, function (data, xhr) { 185 | //const statusCode = xhr.status.toString(); 186 | // TODO: if statusCode === 4xx do log 187 | }, payload); 188 | }); 189 | } 190 | }]); 191 | 192 | return Backend; 193 | }(); 194 | 195 | Backend.type = 'backend'; 196 | 197 | return Backend; 198 | 199 | }))); 200 | -------------------------------------------------------------------------------- /static/global/js/log.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product : PoziTone 4 | Author : PoziWorld 5 | Copyright : Copyright (c) 2013-2016 PoziWorld 6 | License : pozitone.com/license 7 | File : global/js/log.js 8 | Description : Log JavaScript 9 | 10 | Table of Contents: 11 | 12 | Log 13 | init() 14 | add() 15 | setPropertiesOnUserRecord() 16 | Listeners 17 | StorageApi.onChanged 18 | Events 19 | 20 | ============================================================================ */ 21 | 22 | /* ============================================================================= 23 | 24 | Log 25 | 26 | ============================================================================ */ 27 | 28 | const 29 | strLogDo = ', do' 30 | , strLogDoNot = ', do not' 31 | , strLogDone = ', done' 32 | , strLogError = ', error' 33 | , strLogSuccess = ', success' 34 | , strLogNoSuccess = ', no success' 35 | 36 | , strJoinUeipVar = 'strJoinUeip' 37 | , strJoinUeipAgreed = 'yes' 38 | ; 39 | 40 | var 41 | strLog = '' 42 | 43 | , Log = { 44 | strJoinUeip : null 45 | , strLastTrackedEvent : '' 46 | , intTrackCount : 0 47 | , intTrackCountMax : 20 48 | , intTrackCountDelay : 150 49 | , 50 | 51 | /** 52 | * Initialize 53 | * 54 | * @type method 55 | * @param No Parameters Taken 56 | * @return void 57 | **/ 58 | init : function() { 59 | StorageSync.get( strConstGeneralSettings, function( objReturn ) { 60 | var objGeneralSettings = objReturn[ strConstGeneralSettings ]; 61 | 62 | if ( 63 | typeof objGeneralSettings === 'object' 64 | && typeof objGeneralSettings[ strJoinUeipVar ] === 'string' 65 | ) 66 | Log.strJoinUeip = objGeneralSettings[ strJoinUeipVar ]; 67 | }); 68 | } 69 | , 70 | 71 | /** 72 | * Add new item to the log (console.log + track) 73 | * 74 | * @type method 75 | * @param strEvent 76 | * Event name/desc 77 | * @param miscVar 78 | * Optional. Var to output contents of 79 | * @param boolTrack 80 | * Optional. Whether to track this 81 | * @param boolDoNotSendData 82 | * Optional. Whether to send details of the event 83 | * @return void 84 | **/ 85 | add : function( strEvent, miscVar, boolTrack, boolDoNotSendData ) { 86 | if ( typeof miscVar === 'undefined' ) 87 | miscVar = {}; 88 | 89 | // Debug 90 | console.log( strEvent, miscVar ); 91 | 92 | // Tracking 93 | var funcTrack = function( 94 | strEvent 95 | , miscVar 96 | , boolTrack 97 | , boolDoNotSendData 98 | ) { 99 | var funcTrackRetry; 100 | 101 | if ( 102 | Log.strJoinUeip === strJoinUeipAgreed 103 | && typeof boolTrack !== 'undefined' 104 | && boolTrack 105 | ) { 106 | if ( typeof boolDoNotSendData !== 'undefined' && boolDoNotSendData ) 107 | miscVar = {}; 108 | else if ( Array.isArray( miscVar ) ) 109 | miscVar = Global.convertArrToObj( miscVar ); 110 | 111 | if ( 112 | strEvent !== Log.strLastTrackedEvent 113 | || ! Global.isEmpty( miscVar ) 114 | ) { 115 | mixpanel.track( strEvent, miscVar ); 116 | Log.strLastTrackedEvent = strEvent; 117 | 118 | if ( strEvent === strConstLogOnInstalled ) 119 | Log.setPropertiesOnUserRecord( miscVar ); 120 | } 121 | } 122 | // If storage hasn't returned value yet and at least one try left 123 | // TODO: Use observer/deferred instead 124 | else if ( 125 | Log.strJoinUeip === null 126 | && Log.intTrackCount < Log.intTrackCountMax 127 | ) { 128 | funcTrackRetry = setTimeout( 129 | function() { 130 | funcTrack( 131 | strEvent 132 | , miscVar 133 | , boolTrack 134 | , boolDoNotSendData 135 | ); 136 | } 137 | , Log.intTrackCountDelay 138 | ); 139 | Log.intTrackCount++; 140 | } 141 | 142 | // Reset if value received 143 | if ( typeof Log.strJoinUeip === 'string' ) 144 | Log.intTrackCount = 0; 145 | }; 146 | 147 | funcTrack( strEvent, miscVar, boolTrack, boolDoNotSendData ); 148 | } 149 | , 150 | 151 | /** 152 | * Set properties on a user record 153 | * 154 | * @type method 155 | * @param objProperties 156 | * The properties to set 157 | * @param funcCallback 158 | * Optional. Callback 159 | * @return void 160 | **/ 161 | setPropertiesOnUserRecord : function( objProperties, funcCallback ) { 162 | mixpanel.identify( mixpanel.get_distinct_id() ); 163 | mixpanel.people.set( objProperties, funcCallback ); 164 | } 165 | }; 166 | 167 | /* ============================================================================= 168 | 169 | Listeners 170 | 171 | ============================================================================ */ 172 | 173 | /** 174 | * Fired when one or more items change. 175 | * 176 | * @type method 177 | * @param objMessage 178 | * Message received 179 | * @param objSender 180 | * Sender of the message 181 | * @return void 182 | **/ 183 | StorageApi.onChanged.addListener( 184 | function( objChanges, strAreaName ) { 185 | if ( strAreaName === 'sync' ) { 186 | var objGeneralSettings = objChanges[ strConstGeneralSettings ]; 187 | 188 | if ( 189 | typeof objGeneralSettings === 'object' 190 | && typeof objGeneralSettings.newValue === 'object' 191 | && typeof objGeneralSettings.newValue[ strJoinUeipVar ] === 'string' 192 | ) 193 | Log.strJoinUeip = objGeneralSettings.newValue[ strJoinUeipVar ]; 194 | else if ( 195 | typeof objGeneralSettings === 'object' 196 | && typeof objGeneralSettings.newValue === 'undefined' 197 | && typeof objGeneralSettings.oldValue === 'object' 198 | ) 199 | Log.strJoinUeip = null; 200 | } 201 | } 202 | ); 203 | 204 | /* ============================================================================= 205 | 206 | Events 207 | 208 | ============================================================================ */ 209 | 210 | document.addEventListener( 'DOMContentLoaded', Log.init ); 211 | -------------------------------------------------------------------------------- /static/global/js/notifications.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | PoziTone 4 | © 2013-2018 PoziWorld, Inc. 5 | https://pozitone.com 6 | 7 | ============================================================================ */ 8 | 9 | ( function () { 10 | 'use strict'; 11 | 12 | /** 13 | * Callback in case of success. 14 | * 15 | * @callback funcSuccessCallback 16 | */ 17 | 18 | /** 19 | * Enhance functionality of https://developer.chrome.com/extensions/notifications 20 | * 21 | * @constructor 22 | */ 23 | 24 | function Notifications() { 25 | /** 26 | * Known API error messages. 27 | * 28 | * @typedef {Object} Errors 29 | * @property {string} type - What the error relates to. 30 | */ 31 | 32 | const objApiErrorMessages = { 33 | buttons: 'Adding buttons to notifications is not supported.' 34 | }; 35 | 36 | /** 37 | * Check whether the last runtime error is what is expected. 38 | * 39 | * @param {string} strErrorMessage - https://developer.chrome.com/extensions/runtime#property-lastError 40 | * @param {Errors~type} strType 41 | * @return {boolean} 42 | * @private 43 | */ 44 | 45 | Notifications.prototype._isKnownErrorMessage = function ( strErrorMessage, strType ) { 46 | Log.add( 'pozitone.notifications._getApiErrorMessage' ); 47 | 48 | return strErrorMessage === objApiErrorMessages[ strType ]; 49 | }; 50 | } 51 | 52 | /** 53 | * https://developer.chrome.com/extensions/notifications#method-create 54 | * 55 | * @param {string} [strNotificationId] - Identifier of the notification. If not set or empty, an ID will automatically be generated. If it matches an existing notification, this method first clears that notification before proceeding with the create operation. The identifier may not be longer than 500 characters. 56 | * @param {Object} objNotificationOptions - Contents of the notification. 57 | * @param {funcSuccessCallback} [funcSuccessCallback] - If successfully created. 58 | */ 59 | 60 | Notifications.prototype.create = function ( strNotificationId, objNotificationOptions, funcSuccessCallback ) { 61 | Log.add( 'pozitone.notifications.create', strNotificationId, objNotificationOptions ); 62 | 63 | const _this = this; 64 | const options = localize( objNotificationOptions ); 65 | 66 | chrome.notifications.create( 67 | strNotificationId, 68 | options, 69 | function ( strNotificationId ) { 70 | Global.checkForRuntimeError( 71 | funcSuccessCallback, 72 | function ( strErrorMessage ) { 73 | // Opera doesn't allow buttons 74 | if ( _this._isKnownErrorMessage( strErrorMessage, 'buttons' ) ) { 75 | delete options.buttons; 76 | 77 | chrome.notifications.create( 78 | strNotificationId, 79 | options, 80 | funcSuccessCallback 81 | ); 82 | } 83 | } 84 | ); 85 | } 86 | ); 87 | }; 88 | 89 | /** 90 | * The notification title and the buttons' captions need to be localized right before the notification is shown, as i18n API is async. 91 | * 92 | * @param {Object} options 93 | * @return {Object} 94 | */ 95 | 96 | function localize( options ) { 97 | options = localizeTitle( options ); 98 | 99 | const buttons = options.buttons; 100 | 101 | if ( Array.isArray( buttons ) && buttons.length ) { 102 | buttons.forEach( localizeTitle ); 103 | 104 | options.buttons = buttons; 105 | } 106 | 107 | return options; 108 | } 109 | 110 | /** 111 | * Notification itself and buttons are captioned with the “title” property, which needs to be localized. 112 | * 113 | * @param {Object} object 114 | * @return {Object} 115 | */ 116 | 117 | function localizeTitle( object ) { 118 | const title = object.title; 119 | 120 | if ( poziworldExtension.utils.isNonEmptyString( title ) ) { 121 | object.title = poziworldExtension.i18n.getMessage( title ); 122 | } 123 | 124 | return object; 125 | } 126 | 127 | if ( typeof pozitone === 'undefined' ) { 128 | window.pozitone = {}; 129 | } 130 | 131 | pozitone.notifications = new Notifications(); 132 | } )(); 133 | -------------------------------------------------------------------------------- /static/global/js/punycode.min.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/punycode v1.3.2 by @mathias */ 2 | !function(a){function b(a){throw RangeError(E[a])}function c(a,b){for(var c=a.length,d=[];c--;)d[c]=b(a[c]);return d}function d(a,b){var d=a.split("@"),e="";d.length>1&&(e=d[0]+"@",a=d[1]),a=a.replace(D,".");var f=a.split("."),g=c(f,b).join(".");return e+g}function e(a){for(var b,c,d=[],e=0,f=a.length;f>e;)b=a.charCodeAt(e++),b>=55296&&56319>=b&&f>e?(c=a.charCodeAt(e++),56320==(64512&c)?d.push(((1023&b)<<10)+(1023&c)+65536):(d.push(b),e--)):d.push(b);return d}function f(a){return c(a,function(a){var b="";return a>65535&&(a-=65536,b+=H(a>>>10&1023|55296),a=56320|1023&a),b+=H(a)}).join("")}function g(a){return 10>a-48?a-22:26>a-65?a-65:26>a-97?a-97:t}function h(a,b){return a+22+75*(26>a)-((0!=b)<<5)}function i(a,b,c){var d=0;for(a=c?G(a/x):a>>1,a+=G(a/b);a>F*v>>1;d+=t)a=G(a/F);return G(d+(F+1)*a/(a+w))}function j(a){var c,d,e,h,j,k,l,m,n,o,p=[],q=a.length,r=0,w=z,x=y;for(d=a.lastIndexOf(A),0>d&&(d=0),e=0;d>e;++e)a.charCodeAt(e)>=128&&b("not-basic"),p.push(a.charCodeAt(e));for(h=d>0?d+1:0;q>h;){for(j=r,k=1,l=t;h>=q&&b("invalid-input"),m=g(a.charCodeAt(h++)),(m>=t||m>G((s-r)/k))&&b("overflow"),r+=m*k,n=x>=l?u:l>=x+v?v:l-x,!(n>m);l+=t)o=t-n,k>G(s/o)&&b("overflow"),k*=o;c=p.length+1,x=i(r-j,c,0==j),G(r/c)>s-w&&b("overflow"),w+=G(r/c),r%=c,p.splice(r++,0,w)}return f(p)}function k(a){var c,d,f,g,j,k,l,m,n,o,p,q,r,w,x,B=[];for(a=e(a),q=a.length,c=z,d=0,j=y,k=0;q>k;++k)p=a[k],128>p&&B.push(H(p));for(f=g=B.length,g&&B.push(A);q>f;){for(l=s,k=0;q>k;++k)p=a[k],p>=c&&l>p&&(l=p);for(r=f+1,l-c>G((s-d)/r)&&b("overflow"),d+=(l-c)*r,c=l,k=0;q>k;++k)if(p=a[k],c>p&&++d>s&&b("overflow"),p==c){for(m=d,n=t;o=j>=n?u:n>=j+v?v:n-j,!(o>m);n+=t)x=m-o,w=t-o,B.push(H(h(o+x%w,0))),m=G(x/w);B.push(H(h(m,0))),j=i(d,r,f==g),d=0,++f}++d,++c}return B.join("")}function l(a){return d(a,function(a){return B.test(a)?j(a.slice(4).toLowerCase()):a})}function m(a){return d(a,function(a){return C.test(a)?"xn--"+k(a):a})}var n="object"==typeof exports&&exports&&!exports.nodeType&&exports,o="object"==typeof module&&module&&!module.nodeType&&module,p="object"==typeof global&&global;(p.global===p||p.window===p||p.self===p)&&(a=p);var q,r,s=2147483647,t=36,u=1,v=26,w=38,x=700,y=72,z=128,A="-",B=/^xn--/,C=/[^\x20-\x7E]/,D=/[\x2E\u3002\uFF0E\uFF61]/g,E={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},F=t-u,G=Math.floor,H=String.fromCharCode;if(q={version:"1.3.2",ucs2:{decode:e,encode:f},decode:j,encode:k,toASCII:m,toUnicode:l},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define("punycode",function(){return q});else if(n&&o)if(module.exports==n)o.exports=q;else for(r in q)q.hasOwnProperty(r)&&(n[r]=q[r]);else a.punycode=q}(this); 3 | -------------------------------------------------------------------------------- /static/global/js/tracking.js: -------------------------------------------------------------------------------- 1 | (function(e,b){if(!b.__SV){var a,f,i,g;window.mixpanel=b;b._i=[];b.init=function(a,e,d){function f(b,h){var a=h.split(".");2==a.length&&(b=b[a[0]],h=a[1]);b[h]=function(){b.push([h].concat(Array.prototype.slice.call(arguments,0)))}}var c=b;"undefined"!==typeof d?c=b[d]=[]:d="mixpanel";c.people=c.people||[];c.toString=function(b){var a="mixpanel";"mixpanel"!==d&&(a+="."+d);b||(a+=" (stub)");return a};c.people.toString=function(){return c.toString(1)+".people (stub)"};i="disable track track_pageview track_links track_forms register register_once alias unregister identify name_tag set_config people.set people.set_once people.increment people.append people.track_charge people.clear_charges people.delete_user".split(" "); 2 | for(g=0;g -------------------------------------------------------------------------------- /static/modules/com_jazzradio/img/jazzradio-logo-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_jazzradio/img/jazzradio-logo-120.png -------------------------------------------------------------------------------- /static/modules/com_jazzradio/js/page-watcher.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone module for JAZZRADIO.com 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | Table of Contents: 9 | 10 | Const 11 | 12 | ============================================================================ */ 13 | 14 | /* ============================================================================= 15 | 16 | Const 17 | 18 | ============================================================================ */ 19 | 20 | const strModule = 'com_jazzradio'; 21 | -------------------------------------------------------------------------------- /static/modules/com_radiotunes/img/radiotunes-logo-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_radiotunes/img/radiotunes-logo-80.png -------------------------------------------------------------------------------- /static/modules/com_radiotunes/js/page-watcher.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone module for RadioTunes 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | Table of Contents: 9 | 10 | Const 11 | 12 | ============================================================================ */ 13 | 14 | /* ============================================================================= 15 | 16 | Const 17 | 18 | ============================================================================ */ 19 | 20 | const strModule = 'com_radiotunes'; 21 | -------------------------------------------------------------------------------- /static/modules/com_rockradio/img/rockradio-logo-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_rockradio/img/rockradio-logo-120.png -------------------------------------------------------------------------------- /static/modules/com_rockradio/js/page-watcher.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone module for ROCKRADIO.COM 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | Table of Contents: 9 | 10 | Const 11 | 12 | ============================================================================ */ 13 | 14 | /* ============================================================================= 15 | 16 | Const 17 | 18 | ============================================================================ */ 19 | 20 | const strModule = 'com_rockradio'; 21 | -------------------------------------------------------------------------------- /static/modules/com_soundcloud/img/soundcloud-logo-48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/modules/com_soundcloud/img/soundcloud-logo-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_soundcloud/img/soundcloud-logo-80.png -------------------------------------------------------------------------------- /static/modules/com_vgmradio/img/vgmradio-logo-120.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/modules/com_vk_audio/img/vk-logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_vk_audio/img/vk-logo-32.png -------------------------------------------------------------------------------- /static/modules/com_vk_audio/img/vk-logo-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/com_vk_audio/img/vk-logo-80.png -------------------------------------------------------------------------------- /static/modules/com_vk_audio/img/vk-logo-80.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/modules/com_vk_audio/js/page-watcher-loader.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product: PoziTone 4 | Author: PoziWorld 5 | Copyright: (c) 2016 PoziWorld 6 | License: pozitone.com/license 7 | 8 | ============================================================================ */ 9 | 10 | // Choose the right PageWatcher to initialize, the one for the old site or for the redesigned one. 11 | 12 | ( function ( ) { 13 | if ( document.contains( document.getElementById( 'page_header_cont' ) ) ) { 14 | pozitone.pageWatcherV2.init(); 15 | } 16 | else { 17 | pozitone.pageWatcherV1.init(); 18 | } 19 | } )(); 20 | -------------------------------------------------------------------------------- /static/modules/fm_di/img/di-logo-120.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/modules/general/js/page-watcher.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product : PoziTone 4 | Author : PoziWorld 5 | Copyright : Copyright (c) 2013-2016 PoziWorld 6 | License : pozitone.com/license 7 | File : js/page-watcher.js 8 | Description : General Page Watcher JavaScript 9 | 10 | Table of Contents: 11 | 12 | General Page Watcher 13 | getVolumeDeltaSettings() 14 | processButtonClick_volumeUp() 15 | processButtonClick_volumeDown() 16 | 17 | ============================================================================ */ 18 | 19 | /* ============================================================================= 20 | 21 | General Page Watcher 22 | 23 | ============================================================================ */ 24 | 25 | var GeneralPageWatcher = { 26 | 27 | /** 28 | * Get general (default) volume delta settings and for the current player. 29 | * 30 | * @type method 31 | * @param funcSetVolume 32 | * Callback to set changed volume level. 33 | * @return void 34 | **/ 35 | getVolumeDeltaSettings : function( funcSetVolume ) { 36 | var arrSettings = [ strModuleSettings, strConstGeneralSettings ]; 37 | 38 | StorageSync.get( arrSettings, function( objReturn ) { 39 | var 40 | objModuleSettings = objReturn[ strModuleSettings ] 41 | , objGeneralSettings = objReturn[ strConstGeneralSettings ] 42 | ; 43 | 44 | // Use general delta if set to do so, use player's own delta otherwise 45 | if ( 46 | typeof objModuleSettings === 'object' 47 | && typeof objModuleSettings.boolUseGeneralVolumeDelta === 'boolean' 48 | ) { 49 | if ( 50 | objModuleSettings.boolUseGeneralVolumeDelta 51 | && typeof objGeneralSettings === 'object' 52 | ) { 53 | var intGeneralVolumeDelta = objGeneralSettings.intVolumeDelta; 54 | 55 | if ( 56 | typeof intGeneralVolumeDelta === 'number' 57 | && intGeneralVolumeDelta > 0 58 | ) 59 | funcSetVolume( intGeneralVolumeDelta ); 60 | } 61 | else { 62 | var intModuleVolumeDelta = objModuleSettings.intVolumeDelta; 63 | 64 | if ( 65 | typeof intModuleVolumeDelta === 'number' 66 | && intModuleVolumeDelta > 0 67 | ) 68 | funcSetVolume( intModuleVolumeDelta ); 69 | } 70 | } 71 | }); 72 | } 73 | , 74 | 75 | /** 76 | * Simulate "volume up" player method 77 | * 78 | * @type method 79 | * @param No Parameters Taken 80 | * @return void 81 | **/ 82 | processButtonClick_volumeUp : function() { 83 | PageWatcher.changeVolume( 'up' ); 84 | } 85 | , 86 | 87 | /** 88 | * Simulate "volume up" player method 89 | * 90 | * @type method 91 | * @param No Parameters Taken 92 | * @return void 93 | **/ 94 | processButtonClick_volumeDown : function() { 95 | PageWatcher.changeVolume( 'down' ); 96 | } 97 | }; 98 | -------------------------------------------------------------------------------- /static/modules/ru_ok_audio/img/ok-logo-80.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/modules/ru_ok_audio/img/ok-logo-orange-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/ru_ok_audio/img/ok-logo-orange-32.png -------------------------------------------------------------------------------- /static/modules/ru_ok_audio/img/ok-logo-orange-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PoziWorld/PoziTone/866c230f642bcb0efd4629b0bf8888399f36810a/static/modules/ru_ok_audio/img/ok-logo-orange-80.png -------------------------------------------------------------------------------- /static/options/img/switch-language.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 122 | 137 |
138 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /static/options/js/controllers/external-modules-list.js: -------------------------------------------------------------------------------- 1 | // Controller for External Modules list page 2 | optionsControllers.controller( 'ExternalModulesListCtrl', function( $scope, $rootScope ) { 3 | $scope.arrExternalModules = [ 4 | { 5 | strModuleId: 'com_youtube', 6 | url: { 7 | chrome: 'https://chrome.google.com/webstore/detail/youtube-embedded-player-p/bajalgkbfjloemafmkiheboebghhibbg', 8 | opera: 'https://addons.opera.com/extensions/details/youtube-embedded-player-pozitone-module/', 9 | edge: 'https://microsoftedge.microsoft.com/addons/detail/endgoolfeicagiackhdalbfkinelcgin', 10 | }, 11 | }, 12 | { 13 | strModuleId: 'com_soundcloud', 14 | url: { 15 | chrome: 'https://chrome.google.com/webstore/detail/pozitone-module-for-sound/iijloaojdghegopdahladbajodcgnmgh', 16 | opera: 'https://addons.opera.com/extensions/details/soundcloud-widget-pozitone-module/', 17 | edge: 'https://microsoftedge.microsoft.com/addons/detail/imijjplgbohoagfnhbdlfhfcgfikgjab', 18 | }, 19 | }, 20 | { 21 | strModuleId: 'ru_sovyatnik', 22 | url: { 23 | chrome: 'https://chrome.google.com/webstore/detail/sovyatnik-pozitone-module/ihdoljplikdgegdooeohfmgaaabcbmpn', 24 | opera: 'https://addons.opera.com/extensions/details/soviatnik-pozitone-modul/', 25 | edge: 'https://microsoftedge.microsoft.com/addons/detail/fkgcpgookmofjfedpfhadkgkhddcdbpp', 26 | }, 27 | }, 28 | ]; 29 | 30 | Page.localize( strPage, '#content' ); 31 | 32 | strSubpage = 'modules-external'; 33 | strSubsection = undefined; 34 | 35 | Page.trackPageView( strSubpage ); 36 | 37 | $rootScope.toggleExternalLinksListeners( 38 | true 39 | , 'content' 40 | , strPage 41 | , strSubpage 42 | ); 43 | 44 | /** 45 | * When a link leading to any website is clicked, track click. 46 | * 47 | * @type method 48 | * @param objEvent 49 | * MouseEvent object. 50 | * @return void 51 | **/ 52 | 53 | $scope.trackExternalLinkClick = function( objEvent ) { 54 | $rootScope.trackExternalLinkClick( objEvent ); 55 | }; 56 | 57 | /** 58 | * When a link leading to any website is clicked, track click. 59 | * 60 | * @type method 61 | * @param objEvent 62 | * MouseEvent object. 63 | * @return void 64 | **/ 65 | 66 | $scope.install = function( objEvent ) { 67 | const moduleId = objEvent.target.getAttribute( 'data-module-id' ); 68 | const MATCHING_RESULT_INDEX = 0; 69 | const MODULE_DETAILS_URL_KEY = 'url'; 70 | const url = $scope.arrExternalModules.filter( function ( externalModuleDetails ) { 71 | return moduleId === externalModuleDetails.strModuleId; 72 | } )[ MATCHING_RESULT_INDEX ][ MODULE_DETAILS_URL_KEY ][ poziworldExtension.utils.getExtensionStoreType() ]; 73 | 74 | Global.createTabOrUpdate( url ); 75 | }; 76 | } ); 77 | -------------------------------------------------------------------------------- /static/options/js/controllers/misc.js: -------------------------------------------------------------------------------- 1 | // Controller for Sister Projects page 2 | optionsControllers.controller( 'ProjectsCtrl', function( $scope, $rootScope ) { 3 | $scope.boolIsNotOperaAddon = ! boolConstIsOperaAddon; 4 | 5 | $scope.arrProjects = [ 6 | { 7 | strName: 'Swaggy', 8 | abbreviatedName: 'swaggy', 9 | strImageFileName: 'swaggy-icon-48.svg', 10 | /** 11 | * @todo Camelize strName instead? 12 | */ 13 | urlId: 'swaggyProject', 14 | }, 15 | { 16 | strName: 'PoziWorld Elf', 17 | abbreviatedName: 'pe', 18 | strImageFileName: 'pe-icon-64.png', 19 | urlId: 'poziworldElfProject', 20 | }, 21 | { 22 | strName: 'Scroll To Top Button', 23 | abbreviatedName: 'sttb', 24 | strImageFileName: 'sttb-icon-50.svg', 25 | urlId: 'scrollToTopButtonProject', 26 | }, 27 | { 28 | strName: 'Print Waste Minimizer', 29 | abbreviatedName: 'pwm', 30 | strImageFileName: 'pwm-icon-128.svg', 31 | urlId: 'printWasteMinimizerProject', 32 | }, 33 | ]; 34 | 35 | Page.localize( strPage, '#content' ); 36 | 37 | strSubpage = 'projects'; 38 | strSubsection = undefined; 39 | 40 | Page.trackPageView( strSubpage ); 41 | 42 | $rootScope.toggleExternalLinksListeners( 43 | true 44 | , 'content' 45 | , strPage 46 | , strSubpage 47 | ); 48 | 49 | /** 50 | * Return browser-specific sister project URL. 51 | * 52 | * @param {string} urlId 53 | */ 54 | 55 | $scope.getSisterProjectUrl = function ( urlId ) { 56 | return pozitone.global.getSisterProjectUrl( urlId ); 57 | }; 58 | } ); 59 | 60 | // Controller for Contribution page 61 | optionsControllers.controller( 'ContributionCtrl', function( $scope, $rootScope ) { 62 | Page.localize( strPage, '#content' ); 63 | 64 | strSubpage = 'contribution'; 65 | strSubsection = undefined; 66 | 67 | Page.trackPageView( strSubpage ); 68 | 69 | $rootScope.toggleExternalLinksListeners( 70 | true 71 | , 'content' 72 | , strPage 73 | , strSubpage 74 | ); 75 | 76 | document.getElementById( 'installationLink' ).href = pozitone.global.getInstallationUrl(); 77 | document.getElementById( 'rateLink' ).href = pozitone.global.getRatingUrl(); 78 | } ); 79 | 80 | // Controller for Feedback page 81 | optionsControllers.controller( 'FeedbackCtrl', function( $scope, $rootScope ) { 82 | Page.localize( strPage, '#content' ); 83 | 84 | strSubpage = 'feedback'; 85 | strSubsection = undefined; 86 | 87 | Page.trackPageView( strSubpage ); 88 | 89 | $rootScope.toggleExternalLinksListeners( 90 | true 91 | , 'content' 92 | , strPage 93 | , strSubpage 94 | ); 95 | 96 | document.getElementById( 'reviewLink' ).href = pozitone.global.getRatingUrl(); 97 | document.getElementById( 'bugLink' ).href = pozitone.global.getFeedbackUrl(); 98 | document.getElementById( 'incentiveLink' ).href = objConst.strIncentiveCarrotUrl; 99 | } ); 100 | 101 | // Controller for About page 102 | optionsControllers.controller( 'AboutCtrl', function( $scope, $rootScope ) { 103 | document.getElementById( 'logo' ).alt = strConstExtensionName; 104 | document.getElementById( 'name' ).textContent = strConstExtensionName; 105 | document.getElementById( 'version' ).textContent = strConstExtensionVersionName; 106 | 107 | Page.localize( strPage, '#content' ); 108 | setLinks(); 109 | 110 | strSubpage = 'about'; 111 | strSubsection = undefined; 112 | 113 | Page.trackPageView( strSubpage ); 114 | 115 | $rootScope.toggleExternalLinksListeners( 116 | true 117 | , 'content' 118 | , strPage 119 | , strSubpage 120 | ); 121 | 122 | /** 123 | * Some links come from the translation files and don't have URLs, so it's easier to make updates to the URLs. 124 | * Others use Markdown, so the markup is not hardcoded in the translation files. 125 | */ 126 | 127 | function setLinks() { 128 | const translationPortalLink = document.querySelector( '[data-id="translation"]' ); 129 | const translatedByText = document.getElementById( 'translatedBy' ); 130 | 131 | if ( translationPortalLink ) { 132 | translationPortalLink.href = strConstTranslationUrl; 133 | } 134 | 135 | if ( translatedByText ) { 136 | translatedByText.innerHTML = translatedByText.innerHTML.replace( 137 | // Markdown-style link: [John Doe](https://www.transifex.com/user/profile/john.doe777/) 138 | /(\[)([^\]]+\.?)(\])(\()(http[s]:\/\/(-\.)?([^\s\/?\.\#\-]+\.?)+(\/[^\s]*)?)(\))/g 139 | , '$2' 140 | ); 141 | } 142 | } 143 | } ); 144 | 145 | // Controller for Help page 146 | optionsControllers.controller( 'HelpCtrl', function( $scope ) { 147 | Options.removeNotAvailable(); 148 | Page.localize( strPage, '#content' ); 149 | 150 | strSubpage = 'help'; 151 | strSubsection = undefined; 152 | 153 | Page.trackPageView( 'help' ); 154 | 155 | // Show debug info 156 | var strHtml = ''; 157 | 158 | for ( var miscProperty in objConstUserSetUp ) { 159 | if ( objConstUserSetUp.hasOwnProperty( miscProperty ) ) { 160 | strHtml += Page.template( 161 | 'helpInfoToSubmitTmpl' 162 | , { 163 | key : miscProperty 164 | , value : objConstUserSetUp[ miscProperty ] 165 | } 166 | ); 167 | } 168 | } 169 | 170 | if ( strHtml !== '' ) 171 | document.getElementById( strHelpInfoToSubmitId ).innerHTML = strHtml; 172 | } ); 173 | 174 | // Controller for ❤ page 175 | optionsControllers.controller( '❤Ctrl', function( $scope ) { 176 | document.getElementById( 'header' ).hidden = true; 177 | document.getElementById( 'toolbar' ).hidden = true; 178 | document.getElementById( 'footer' ).hidden = true; 179 | 180 | const $$ascii = document.getElementById( '❤❤' ); 181 | const strReadyAttribute = 'data-ready'; 182 | const boolIsReady = JSON.parse( $$ascii.getAttribute( strReadyAttribute ) ); 183 | 184 | if ( ! boolIsReady ) { 185 | $$ascii.textContent = window.atob( $$ascii.textContent ); 186 | $$ascii.setAttribute( strReadyAttribute, true ); 187 | } 188 | 189 | strSubpage = '❤'; 190 | strSubsection = undefined; 191 | 192 | Page.trackPageView( '❤' ); 193 | } ); 194 | -------------------------------------------------------------------------------- /static/options/js/controllers/settings.js: -------------------------------------------------------------------------------- 1 | // Controller for General and Modules' Settings 2 | optionsControllers.controller( 'SettingsCtrl', function( 3 | $scope 4 | , $rootScope 5 | , $route 6 | , $routeParams 7 | , $location 8 | ) { 9 | // If there are no enabled modules, show modules list - START 10 | var objModules = $rootScope.objModules 11 | , strKey 12 | , intEnabledModules = 0 13 | , objModule 14 | , boolIsEnabled 15 | ; 16 | 17 | for ( strKey in objModules ) { 18 | if ( objModules.hasOwnProperty( strKey ) ) { 19 | objModule = objModules[ strKey ]; 20 | boolIsEnabled = objModule.boolIsEnabled; 21 | 22 | if ( typeof boolIsEnabled === 'boolean' && boolIsEnabled ) { 23 | intEnabledModules++; 24 | } 25 | } 26 | } 27 | 28 | if ( intEnabledModules === 0 29 | && typeof $rootScope.boolWasPageJustOpened !== 'boolean' 30 | ) { 31 | $rootScope.boolWasPageJustOpened = true; 32 | 33 | var boolPreventLocationOverriding = $rootScope.boolPreventLocationOverriding; 34 | 35 | if ( typeof boolPreventLocationOverriding !== 'boolean' 36 | || ! boolPreventLocationOverriding 37 | ) { 38 | $location.path( '/settings/modules/built-in' ); 39 | return; 40 | } 41 | } 42 | // If there are no enabled modules, show modules list - END 43 | 44 | var boolShowAdvancedSettings = $rootScope.objModules[ strConstGeneralSettingsSuffix ].boolShowAdvancedSettings 45 | , strRouteModuleId = $routeParams.moduleId 46 | , strModuleId = strRouteModuleId || strConstGeneralSettingsSuffix 47 | ; 48 | 49 | objModule = $rootScope.objModules[ strModuleId ]; 50 | 51 | // Buttons 52 | var arrAvailableNotificationButtons = objModule.arrAvailableNotificationButtons 53 | , arrActiveNotificationButtons = objModule.arrActiveNotificationButtons 54 | // Icon Formats 55 | , arrAvailableNotificationIconFormats = objModule.arrAvailableNotificationIconFormats 56 | // Title Formats 57 | , arrAvailableNotificationTitleFormats = objModule.arrAvailableNotificationTitleFormats 58 | ; 59 | 60 | // Remember chosen settings subpage 61 | strChosenSettingsSubpage = strModuleId; 62 | 63 | // When settings get saved, they use strChosenSettingsSubpage. 64 | // But this one belongs only to general, thus we need to have a way 65 | // to override strChosenSettingsSubpage. 66 | if ( typeof boolShowAdvancedSettings === 'boolean' ) { 67 | $scope.boolShowAdvancedSettings = boolShowAdvancedSettings; 68 | $scope.strConstGeneralSettingsSuffix = strConstGeneralSettingsSuffix; 69 | } 70 | 71 | // Checks if array is not empty. 72 | // If so, creates it in $scope. 73 | function checkArray( arrVar, strArrVarName ) { 74 | if ( Array.isArray( arrVar ) && ! Global.isEmpty( arrVar ) ) { 75 | $scope[ strArrVarName ] = arrVar; 76 | 77 | return true; 78 | } 79 | 80 | return false; 81 | } 82 | 83 | $scope.objModule = objModule; 84 | 85 | // Available buttons 86 | $scope.boolProvidesButtons = 87 | checkArray( 88 | arrAvailableNotificationButtons 89 | , 'arrAvailableNotificationButtons' 90 | ); 91 | 92 | // Active buttons 93 | $scope.boolHasActiveButtons = 94 | checkArray( 95 | arrActiveNotificationButtons 96 | , 'arrActiveNotificationButtons' 97 | ); 98 | 99 | // Icon Formats 100 | $scope.boolProvidesIconFormats = 101 | checkArray( 102 | arrAvailableNotificationIconFormats 103 | , 'arrAvailableNotificationIconFormats' 104 | ); 105 | 106 | // Title Formats 107 | $scope.boolProvidesTitleFormats = 108 | checkArray( 109 | arrAvailableNotificationTitleFormats 110 | , 'arrAvailableNotificationTitleFormats' 111 | ); 112 | 113 | $scope.format = 'M/d/yy h:mm:ss a'; 114 | 115 | // $includeContentLoaded doesn't trigger on Voice control page 116 | if ( ! strRouteModuleId ) { 117 | $settingsSaved = document.getElementById( 'settingsSaved' ); 118 | } 119 | 120 | // On settings page loaded 121 | $scope.$on( '$includeContentLoaded', function( $scope ) { 122 | Options.setPageValues(); 123 | 124 | Page.localize( strPage, '#content' ); 125 | 126 | strSubpage = 'settings'; 127 | strSubsection = strModuleId; 128 | 129 | Page.trackPageView( strSubpage, strSubsection ); 130 | 131 | $rootScope.toggleExternalLinksListeners( 132 | true 133 | , 'content' 134 | , strPage 135 | , strSubpage 136 | , strSubsection 137 | ); 138 | } ); 139 | 140 | /** 141 | * TODO 142 | * 143 | * @type method 144 | * @param funcResolve 145 | * @param funcReject 146 | * @return void 147 | **/ 148 | 149 | $scope.requestManagementPermission = function( funcResolve, funcReject ) { 150 | // TODO 151 | funcReject(); 152 | }; 153 | 154 | /** 155 | * Force "page refresh" if some changes can be reflected only after refresh. 156 | **/ 157 | 158 | $scope.forcePageRefresh = function() { 159 | $route.reload(); 160 | }; 161 | 162 | /** 163 | * Save new setting value. 164 | * 165 | * @param {(Event|Object)} event - Event object. 166 | * @param {string} [groupModel] - Checkbox group model name. 167 | * @param {string} [moduleOverride] - If the setting belongs to a different module, then the rest on the current Options page. 168 | * @param {function} [callbackBefore] - Additional logic to do before saving. 169 | * @param {function} [callbackAfter] - Additional logic to do after saving. 170 | **/ 171 | 172 | $scope.inputChange = function ( event, groupModel, moduleOverride, callbackBefore, callbackAfter ) { 173 | if ( poziworldExtension.utils.isType( callbackBefore, 'function' ) ) { 174 | new Promise( callbackBefore ) 175 | .then( onSettingChange.bind( null, event, groupModel, moduleOverride, callbackAfter ) ); 176 | } 177 | else { 178 | onSettingChange( event, groupModel, moduleOverride, callbackAfter ); 179 | } 180 | }; 181 | 182 | /** 183 | * Handle dropdown (select) value change. 184 | * 185 | * @param {string} id - Dropdown element ID attribute. 186 | * @param callback 187 | */ 188 | 189 | $scope.handleDropdownChange = function ( id, callback ) { 190 | this.$parent.inputChange( 191 | { 192 | target: document.getElementById( id ), 193 | }, 194 | undefined, 195 | undefined, 196 | undefined, 197 | callback 198 | ); 199 | }; 200 | 201 | /** 202 | * Apply the UI language change. 203 | */ 204 | 205 | $scope.changeUiLanguage = function () { 206 | pozitone.global.reloadExtensionAndOptions( 'settingsGeneral', 'changeUiLanguage' ); 207 | }; 208 | 209 | /** 210 | * Process the setting change. 211 | * 212 | * @param {(Event|Object)} $event - Event object. 213 | * @param {string} [strGroupModel] - Checkbox group model name. 214 | * @param {string} [strModuleOverride] - If the setting belongs to a different module, then the rest on the current Options page. 215 | * @param {function} [funcDoAfter] - Additional logic to do after saving. 216 | */ 217 | 218 | function onSettingChange( $event, strGroupModel, strModuleOverride, funcDoAfter ) { 219 | Options.onSettingChange( $event, strModuleOverride ); 220 | 221 | var _target = $event.target 222 | , strName = _target.name 223 | , strValue = _target.value 224 | , boolChecked = _target.checked 225 | ; 226 | 227 | // ng-model doesn't work right for checkbox group 228 | if ( typeof strGroupModel === 'string' ) { 229 | var arrGroup = $scope.objModule[ strGroupModel ]; 230 | 231 | // Save if just selected and not in array yet 232 | if ( boolChecked && ~~ arrGroup.indexOf( strValue ) ) { 233 | arrGroup.push( strValue ); 234 | } 235 | // Remove if just unselected and in array 236 | else if ( ! boolChecked && ~ arrGroup.indexOf( strValue ) ) { 237 | arrGroup.splice( arrGroup.indexOf( strValue ), 1 ); 238 | } 239 | } 240 | 241 | // model doesn't get changed for some reason 242 | if ( typeof strModuleOverride === 'string' ) { 243 | if ( _target.type === 'checkbox' ) { 244 | $scope[ strName ] = boolChecked; 245 | $rootScope.objModules[ strModuleOverride ][ strName ] = boolChecked; 246 | } 247 | } 248 | 249 | // Track setting change if needed 250 | if ( _target.getAttribute( 'data-track-setting-change' ) ) { 251 | var miscTrackedValue; 252 | 253 | // TODO: Add other types 254 | if ( _target.type === 'checkbox' ) { 255 | miscTrackedValue = boolChecked; 256 | } 257 | else if ( _target.tagName === 'SELECT' ) { 258 | miscTrackedValue = _target.value; 259 | } 260 | 261 | chrome.runtime.sendMessage( 262 | { 263 | strReceiver : 'background' 264 | , strLog : 'settingChange' 265 | , objVars : { 266 | strName : strName 267 | , miscValue : miscTrackedValue 268 | , strModule : strModuleId 269 | } 270 | } 271 | ); 272 | } 273 | 274 | if ( typeof funcDoAfter === 'function' ) { 275 | funcDoAfter(); 276 | } 277 | } 278 | } ); 279 | -------------------------------------------------------------------------------- /static/options/js/controllers/voice-control.js: -------------------------------------------------------------------------------- 1 | // Controller for Voice Control page 2 | optionsControllers.controller( 'VoiceControlCtrl', function( $scope, $rootScope ) { 3 | Page.localize( strPage, '#content' ); 4 | 5 | strSubpage = 'voice-control'; 6 | strSubsection = undefined; 7 | 8 | Page.trackPageView( strSubpage ); 9 | 10 | $rootScope.toggleExternalLinksListeners( 11 | true 12 | , 'content' 13 | , strPage 14 | , strSubpage 15 | ); 16 | 17 | const $enableVoiceControl = document.getElementById( 'boolEnableVoiceControl' ); 18 | const $voiceControlActivateCta = document.getElementById( 'voiceControlActivateCta' ); 19 | let boolIsOnVoiceControlDeactivationListenerSet = false; 20 | const objModules = $rootScope.objModules; 21 | 22 | if ( typeof objModules === 'object' ) { 23 | var objGeneralSettings = objModules.general; 24 | 25 | if ( typeof objGeneralSettings !== 'object' ) { 26 | /** 27 | * @todo Logic below won't work, handle error 28 | */ 29 | } 30 | } 31 | 32 | // Check whether it'd already been enabled 33 | chrome.permissions.contains( { permissions : [ 'nativeMessaging' ] }, function( boolIsGranted ) { 34 | if ( boolIsGranted ) { 35 | const boolEnableVoiceControl = objGeneralSettings.boolEnableVoiceControl; 36 | 37 | // Voice control 'Enabled' state had been saved 38 | if ( typeof boolEnableVoiceControl === 'boolean' && boolEnableVoiceControl ) { 39 | $enableVoiceControl.checked = true; 40 | $enableVoiceControl.disabled = false; 41 | $voiceControlActivateCta.disabled = false; 42 | 43 | return; 44 | } 45 | } 46 | 47 | $enableVoiceControl.checked = false; 48 | $enableVoiceControl.disabled = false; 49 | $voiceControlActivateCta.disabled = true; 50 | } ); 51 | 52 | /** 53 | * Toggle voice control setting via permissions API and in storage 54 | * 55 | * @param {MouseEvent} objEvent - MouseEvent object. 56 | **/ 57 | 58 | $scope.toggleVoiceControl = function( objEvent ) { 59 | // Avoid second click while in process 60 | $enableVoiceControl.disabled = true; 61 | 62 | // It just made it checked, so check for the opposite 63 | if ( $enableVoiceControl.checked ) { 64 | $scope.enableVoiceControl( objEvent ); 65 | } 66 | else { 67 | $scope.disableVoiceControl( objEvent ); 68 | } 69 | }; 70 | 71 | /** 72 | * Request 'nativeMessaging' permission and save the state in storage. 73 | * 74 | * @param {MouseEvent} objEvent - MouseEvent object. 75 | **/ 76 | 77 | $scope.enableVoiceControl = function( objEvent ) { 78 | const objLogDetails = {}; 79 | 80 | chrome.permissions.request( { permissions: [ 'nativeMessaging' ] }, function( boolIsGranted ) { 81 | const strPermissionRequestLog = strLog = 'enableVoiceControl'; 82 | 83 | function onFinished( boolIsEnabled ) { 84 | $enableVoiceControl.checked = boolIsEnabled; 85 | $enableVoiceControl.disabled = false; 86 | $voiceControlActivateCta.disabled = ! boolIsEnabled; 87 | 88 | Log.add( strPermissionRequestLog, objLogDetails, true ); 89 | } 90 | 91 | Global.checkForRuntimeError( 92 | function() { 93 | objLogDetails.boolIsRuntimeLastErrorNotSet = true; 94 | 95 | if ( boolIsGranted ) { 96 | objLogDetails.boolIsPermissionGranted = true; 97 | objGeneralSettings.boolEnableVoiceControl = true; 98 | 99 | let objModulesSettings = {}; 100 | 101 | objModulesSettings[ strConstGeneralSettings ] = objGeneralSettings; 102 | 103 | Global.setStorageItems( 104 | StorageSync 105 | , objModulesSettings 106 | , strPermissionRequestLog 107 | , function() { 108 | $scope.saveModulesSettingsScope( objModulesSettings ); 109 | 110 | objLogDetails.boolAreStorageItemsSet = true; 111 | 112 | onFinished( true ); 113 | } 114 | , function() { 115 | objLogDetails.boolAreStorageItemsSet = false; 116 | 117 | onFinished( false ); 118 | /** 119 | * @todo Show error message to user 120 | */ 121 | } 122 | , objLogDetails 123 | ); 124 | } 125 | else { 126 | objLogDetails.boolIsPermissionGranted = false; 127 | objLogDetails.boolAreStorageItemsSet = false; 128 | 129 | onFinished( false ); 130 | /** 131 | * @todo Show error message to user 132 | */ 133 | } 134 | } 135 | , function() { 136 | objLogDetails.boolIsRuntimeLastErrorNotSet = false; 137 | objLogDetails.boolIsPermissionGranted = false; 138 | objLogDetails.boolAreStorageItemsSet = false; 139 | 140 | onFinished( false ); 141 | /** 142 | * @todo Show error message to user 143 | */ 144 | } 145 | , objLogDetails 146 | , true 147 | ); 148 | } ); 149 | }; 150 | 151 | /** 152 | * Request 'nativeMessaging' permission and save the state in storage. 153 | * 154 | * @param {MouseEvent} objEvent - MouseEvent object. 155 | **/ 156 | 157 | $scope.disableVoiceControl = function( objEvent ) { 158 | const objLogDetails = {}; 159 | 160 | chrome.permissions.remove( { permissions: [ 'nativeMessaging' ] }, function( boolIsRemoved ) { 161 | const strPermissionsRemoveLog = strLog = 'disableVoiceControl'; 162 | 163 | function onFinished( boolIsDisabled ) { 164 | $enableVoiceControl.checked = ! boolIsDisabled; 165 | $enableVoiceControl.disabled = false; 166 | $voiceControlActivateCta.disabled = boolIsDisabled; 167 | 168 | Log.add( strPermissionsRemoveLog, objLogDetails, true ); 169 | } 170 | 171 | Global.checkForRuntimeError( 172 | function() { 173 | objLogDetails.boolIsRuntimeLastErrorNotSet = true; 174 | 175 | if ( boolIsRemoved ) { 176 | objLogDetails.boolIsPermissionRemoved = true; 177 | objGeneralSettings.boolEnableVoiceControl = false; 178 | 179 | let objModulesSettings = {}; 180 | 181 | objModulesSettings[ strConstGeneralSettings ] = objGeneralSettings; 182 | 183 | Global.setStorageItems( 184 | StorageSync 185 | , objModulesSettings 186 | , strPermissionsRemoveLog 187 | , function() { 188 | $scope.saveModulesSettingsScope( objModulesSettings ); 189 | 190 | objLogDetails.boolAreStorageItemsSet = true; 191 | 192 | onFinished( true ); 193 | } 194 | , function() { 195 | objLogDetails.boolAreStorageItemsSet = false; 196 | 197 | onFinished( false ); 198 | /** 199 | * @todo Show error message to user 200 | */ 201 | } 202 | , objLogDetails 203 | ); 204 | } 205 | else { 206 | objLogDetails.boolIsPermissionRemoved = false; 207 | objLogDetails.boolAreStorageItemsSet = false; 208 | 209 | onFinished( false ); 210 | /** 211 | * @todo Show error message to user 212 | */ 213 | } 214 | } 215 | , function() { 216 | objLogDetails.boolIsRuntimeLastErrorNotSet = false; 217 | objLogDetails.boolIsPermissionRemoved = false; 218 | objLogDetails.boolAreStorageItemsSet = false; 219 | 220 | onFinished( false ); 221 | /** 222 | * @todo Show error message to user 223 | */ 224 | } 225 | , objLogDetails 226 | , true 227 | ); 228 | } ); 229 | }; 230 | 231 | /** 232 | * When switching Options tabs (pages, subpages), 233 | * changes should be preserved. 234 | * 235 | * @todo Get rid of this duplicate 236 | * 237 | * @param {Object} objModulesSettings - Modules settings object. 238 | **/ 239 | $scope.saveModulesSettingsScope = function( objModulesSettings ) { 240 | if ( typeof objModulesSettings !== 'object' ) { 241 | return; 242 | } 243 | 244 | var strModuleSettings, strModuleId; 245 | 246 | for ( strModuleSettings in objModulesSettings ) { 247 | if ( objModulesSettings.hasOwnProperty( strModuleSettings ) ) { 248 | strModuleId = strModuleSettings.replace( strConstSettingsPrefix, '' ); 249 | 250 | $scope.$parent.objModules[ strModuleId ] = objModulesSettings[ strModuleSettings ]; 251 | } 252 | } 253 | 254 | $scope.intChanges = 0; 255 | 256 | $scope.$apply(); 257 | }; 258 | 259 | document.getElementById( 'voiceControlActivateCta' ).addEventListener( 'click', function( objEvent ) { 260 | pozitoneModule.sdk.activateVoiceControl( 261 | onVoiceControlAlreadyActivated 262 | , onVoiceControlNotActivated 263 | ); 264 | } ); 265 | 266 | function onVoiceControlAlreadyActivated() { 267 | $voiceControlActivateCta.disabled = true; 268 | $voiceControlActivateCta.title = poziworldExtension.i18n.getMessage( 'voiceControlAlreadyActivated' ); 269 | 270 | if ( ! boolIsOnVoiceControlDeactivationListenerSet ) { 271 | pozitoneModule.sdk.addOnVoiceControlDeactivationListener( onVoiceControlNotActivated ); 272 | boolIsOnVoiceControlDeactivationListenerSet = true; 273 | } 274 | } 275 | 276 | function onVoiceControlNotActivated() { 277 | $voiceControlActivateCta.disabled = false; 278 | $voiceControlActivateCta.title = ''; 279 | 280 | boolIsOnVoiceControlDeactivationListenerSet = false; 281 | } 282 | 283 | pozitoneModule.sdk.getVoiceControlStatus( function( objStatus ) { 284 | var boolIsConnected = objStatus.boolIsConnected; 285 | 286 | if ( typeof boolIsConnected === 'boolean' && boolIsConnected ) { 287 | onVoiceControlAlreadyActivated(); 288 | } 289 | } ); 290 | 291 | /** 292 | * @todo onVoiceControlNotActivated() on disconnect 293 | */ 294 | } ); 295 | -------------------------------------------------------------------------------- /static/options/js/options.js: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | 3 | Product : PoziTone 4 | Author : PoziWorld 5 | Copyright : Copyright (c) 2013-2016 PoziWorld 6 | License : pozitone.com/license 7 | File : options/js/options.js 8 | Description : Options JavaScript 9 | 10 | Table of Contents: 11 | 12 | Globals 13 | Options 14 | init() 15 | removeNotAvailable() 16 | setPageValues() 17 | onSettingChange() 18 | addEventListeners() 19 | removeModuleNotifications() 20 | initEeLauncher() 21 | Listeners 22 | runtime.onMessage 23 | Events 24 | 25 | ============================================================================ */ 26 | 27 | /* ============================================================================= 28 | 29 | Globals 30 | 31 | ============================================================================ */ 32 | 33 | const 34 | strPage = 'options' 35 | , strNotAvailableOperaSettingsClass = 'moduleAvailableNotificationButtons' 36 | , strModuleLocalPrefix = 'module_' 37 | , strSettingsId = 'settings' 38 | , strSettingsSavedId = 'settingsSaved' 39 | , strModuleSubpageId = 'settings_module' 40 | , strModuleSubpageIdPrefix = 'settings_' 41 | , strSettingsSubpageClass = 'settingsSubpage' 42 | , strMenuItemSelectedClass = 'selected' 43 | , strEnableModule = 'boolIsEnabled' 44 | 45 | , strEeLauncherKeyword = 'help' 46 | , strEeLauncherId = 'helpMenuItem' 47 | , strHelpInfoToSubmitId = 'helpInfoToSubmit' 48 | , strHelpSubmitInfoCtaId = 'helpSubmitInfoCta' 49 | ; 50 | 51 | var 52 | objParams = {} 53 | , strSubpage 54 | , strSubsection 55 | 56 | , $allInputs // All 57 | , intInputs // Num of $allInputs 58 | , $settingsSaved 59 | , $settingsSubpages 60 | 61 | , strChosenSettingsSubpage 62 | , intSettingsSubpages 63 | ; 64 | 65 | /* ============================================================================= 66 | 67 | Options 68 | 69 | ============================================================================ */ 70 | 71 | var Options = { 72 | 73 | /** 74 | * Initialize 75 | * 76 | * @type method 77 | * @param No Parameters Taken 78 | * @return void 79 | **/ 80 | init : function() { 81 | poziworldExtension.i18n.init() 82 | .then( Page.localize.bind( null, strPage ) ); 83 | Options.setPageValues(); 84 | Options.addEventListeners(); 85 | Options.initEeLauncher(); 86 | } 87 | , 88 | 89 | /** 90 | * If some settings n/a for this browser, remove them 91 | * 92 | * @type method 93 | * @param No Parameters Taken 94 | * @return void 95 | **/ 96 | removeNotAvailable : function() { 97 | if ( bowser.name === 'Opera' ) { 98 | var $elements = 99 | document 100 | .getElementsByClassName( strNotAvailableOperaSettingsClass ); 101 | 102 | for ( var i = ( $elements.length - 1 ); i >= 0; i-- ) { 103 | var $element = $elements[i]; 104 | 105 | $element.parentNode.removeChild( $element ); 106 | } 107 | } 108 | } 109 | , 110 | 111 | /** 112 | * Set values when DOM is ready 113 | * 114 | * @type method 115 | * @param No Parameters Taken 116 | * @return void 117 | **/ 118 | setPageValues : function() { 119 | $allInputs = document.querySelectorAll( '.subpage input' ); 120 | intInputs = $allInputs.length; 121 | 122 | $settingsSaved = document.getElementById( strSettingsSavedId ); 123 | $settingsSubpages = document.getElementsByClassName( strSettingsSubpageClass ); 124 | intSettingsSubpages = $settingsSubpages.length; 125 | } 126 | , 127 | 128 | /** 129 | * Assign change listeners for settings 130 | * 131 | * @type method 132 | * @param objEvent 133 | * Event object 134 | * @param strModuleOverride 135 | * When settings get saved, they use strChosenSettingsSubpage. 136 | * This one lets override strChosenSettingsSubpage. 137 | * @return void 138 | **/ 139 | onSettingChange : function( objEvent, strModuleOverride ) { 140 | var $this = objEvent.target 141 | , objTemp = {} 142 | , objModuleSettings = {} 143 | , miscSetting 144 | , strModule = strChosenSettingsSubpage 145 | , strModuleSettings 146 | ; 147 | 148 | if ( typeof strModuleOverride === 'string' ) { 149 | strModule = strModuleOverride; 150 | } 151 | 152 | var boolIsExternal = strModule !== 'general' && pozitone.global.isModuleExternal( strModule ) 153 | , StorageTemp = boolIsExternal ? StorageLocal : StorageSync 154 | ; 155 | 156 | if ( $this.type === 'checkbox' && $this.value === 'on' ) { 157 | var boolIsChecked = $this.checked; 158 | 159 | miscSetting = boolIsChecked; 160 | 161 | if ( $this.name === strEnableModule && ! boolIsChecked ) { 162 | Options.removeModuleNotifications( strModule ); 163 | } 164 | } 165 | else if ( $this.type === 'checkbox' && $this.value !== 'on' ) { 166 | var $moduleSubpage = document.getElementById( strModuleSubpageId ) 167 | , $group = $moduleSubpage.querySelectorAll( 'input[name="' + $this.name + '"]' ) 168 | , arrTemp = [] 169 | ; 170 | 171 | for ( var i = 0, l = $group.length; i < l; i++ ) { 172 | var $groupEl = $group[ i ]; 173 | 174 | if ( $groupEl.checked ) { 175 | arrTemp.push( $groupEl.value ); 176 | } 177 | } 178 | 179 | miscSetting = arrTemp; 180 | } 181 | else if ( $this.type === 'radio' || $this.tagName === 'SELECT' ) { 182 | miscSetting = $this.value; 183 | } 184 | else if ( $this.type === 'number' ) { 185 | miscSetting = parseInt( $this.value ); 186 | } 187 | 188 | if ( typeof miscSetting === 'undefined' ) { 189 | return; 190 | } 191 | 192 | strModuleSettings = strConstSettingsPrefix + strModule; 193 | 194 | // TODO: Is there a need for objTemp? 195 | objTemp[ strModuleSettings ] = {}; 196 | objTemp[ strModuleSettings ][ $this.name ] = miscSetting; 197 | objModuleSettings[ $this.name ] = miscSetting; 198 | 199 | if ( ! Global.isEmpty( objTemp ) ) { 200 | StorageTemp.get( strModuleSettings, function( objReturn ) { 201 | for ( var strKey in objModuleSettings ) { 202 | if ( objModuleSettings.hasOwnProperty( strKey ) ) 203 | objReturn[ strModuleSettings ][ strKey ] = 204 | objModuleSettings[ strKey ]; 205 | } 206 | 207 | // TODO: Add callback to Global.setStorageItems() and then utilize it 208 | StorageTemp.set( objReturn, function() { 209 | Page.showSuccess( $settingsSaved ); 210 | 211 | // Debug 212 | StorageTemp.get( null, function(data) { 213 | console.log(data); 214 | } ); 215 | } ); 216 | } ); 217 | } 218 | } 219 | , 220 | 221 | /** 222 | * Add event listeners 223 | * 224 | * @type method 225 | * @param No Parameters Taken 226 | * @return void 227 | **/ 228 | addEventListeners : function() { 229 | addEvent( 230 | $allInputs 231 | , 'change' 232 | , function( objEvent ) { Options.onSettingChange( objEvent ); } 233 | ); 234 | 235 | addEvent( 236 | document.getElementById( strHelpSubmitInfoCtaId ) 237 | , 'click' 238 | , function( objEvent ) { 239 | var $element = objEvent.target; 240 | 241 | $element.disabled = true; 242 | Log.setPropertiesOnUserRecord( 243 | objConstUserSetUp 244 | , function() { 245 | $element.innerText = 246 | poziworldExtension.i18n.getMessage( 'optionsHelpSubmitInfoCtaSuccess' ); 247 | } 248 | ); 249 | 250 | } 251 | ); 252 | } 253 | , 254 | 255 | /** 256 | * Remove all notifications for a module when just disabled it 257 | * 258 | * @type method 259 | * @param strModule 260 | * Remove notifications of this module 261 | * @return void 262 | **/ 263 | removeModuleNotifications : function( strModule ) { 264 | StorageLocal.get( 'arrTabsIds', function( objData ) { 265 | var arrTabsIds = objData.arrTabsIds; 266 | 267 | if ( typeof arrTabsIds === 'undefined' ) 268 | return; 269 | 270 | for ( var i = 0, l = arrTabsIds.length; i < l; i++ ) { 271 | var arrTabId = arrTabsIds[ i ]; 272 | 273 | if ( arrTabId[ 1 ] === strModule ) 274 | Global.removeNotification( arrTabId[ 0 ], strModule ); 275 | } 276 | }); 277 | } 278 | , 279 | 280 | /** 281 | * Initialize E.E. launcher (waits for a command) 282 | * 283 | * @type method 284 | * @param No Parameters Taken 285 | * @return void 286 | **/ 287 | initEeLauncher : function() { 288 | // http://stackoverflow.com/a/18272907 289 | let strInput = ''; 290 | 291 | window.addEventListener( 'keypress', function( objEvent ) { 292 | var c = String.fromCharCode( objEvent.keyCode ); 293 | 294 | strInput += c.toLowerCase(); 295 | 296 | if ( ~ strInput.indexOf( strEeLauncherKeyword ) ) { 297 | strInput = ''; 298 | 299 | document.getElementById( strEeLauncherId ).click(); 300 | } 301 | else if ( ~ strInput.indexOf( window.atob( 'bGlh' ) ) ) { 302 | strInput = ''; 303 | 304 | location.href='#/❤'; 305 | } 306 | } ); 307 | } 308 | }; 309 | 310 | /* ============================================================================= 311 | 312 | Listeners 313 | 314 | ============================================================================ */ 315 | 316 | /** 317 | * Listens for messages from other pages 318 | * 319 | * @type method 320 | * @param objMessage 321 | * Message received 322 | * @param objSender 323 | * Sender of the message 324 | * @return void 325 | **/ 326 | chrome.runtime.onMessage.addListener( 327 | function( objMessage, objSender, funcSendResponse ) { 328 | if ( objMessage.strReceiver === 'options' ) { 329 | var objVars = objMessage[ 'objVars' ]; 330 | 331 | for ( strProp in objVars ) { 332 | if ( objVars.hasOwnProperty( strProp ) ) 333 | document.querySelector( '[name="' + strProp + '"]' ).checked = 334 | objVars[ strProp ]; 335 | } 336 | } 337 | } 338 | ); 339 | 340 | /* ============================================================================= 341 | 342 | Events 343 | 344 | ============================================================================ */ 345 | 346 | document.addEventListener( 'DOMContentLoaded', Options.init ); 347 | -------------------------------------------------------------------------------- /static/options/partials/about.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 | 5 |
6 |
7 | 20 |

21 | 22 | 23 | () 28 |

29 |

30 | 31 |

32 |
33 |

34 |

35 |

36 |

37 |
38 |
39 | -------------------------------------------------------------------------------- /static/options/partials/contribution.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |
4 |

5 |

6 |

7 |
    8 |
  • 9 |
  • 10 |
  • 11 |
12 |
13 |
14 |

15 |

16 |

17 |

18 |
19 |
20 |

21 |

22 |
23 |
24 | -------------------------------------------------------------------------------- /static/options/partials/external-modules-list.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 8 |

9 |

14 |
15 |

22 | 28 | 33 | 39 | 46 |

47 |

52 | 59 | 65 |

66 |
67 |

71 |

75 |
76 | -------------------------------------------------------------------------------- /static/options/partials/feedback.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |

4 |
    5 |
  • 6 |
  • 7 |
  • 8 |
  • 9 |
  • 10 |
11 |
12 | -------------------------------------------------------------------------------- /static/options/partials/help.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |

4 |
5 |

6 | 10 |

11 | 16 | 20 |
21 | -------------------------------------------------------------------------------- /static/options/partials/projects.html: -------------------------------------------------------------------------------- 1 |
2 |

3 |

8 |

13 |
14 |

18 | {{strName = objProject.strName}} 23 | {{strName}} 29 | 34 | 39 |

40 |
41 |
42 | -------------------------------------------------------------------------------- /static/options/partials/settings-general.html: -------------------------------------------------------------------------------- 1 |

2 |
3 |
4 | 5 |
    6 |
  • 7 | 30 | 36 | 37 | 38 |
  • 39 |
40 |
41 |
42 | 43 |
    44 |
  • 45 | 57 |
  • 58 |
  • 59 | 67 |
  • 68 |
  • 69 | 77 |
  • 78 |
79 |
80 | 95 |
96 | 97 |
    98 |
  • 99 | 108 |
  • 109 |
110 |
111 |
112 | 113 |

114 |

115 | 121 |

122 |

123 | 129 |

130 |
    131 |
  • 132 | 141 |
  • 142 |
  • 143 | 152 |
  • 153 |
154 |
155 |
156 | -------------------------------------------------------------------------------- /static/options/partials/settings-modules-list.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 8 |

9 |

14 |
15 |

16 | 32 | 39 | 47 |

48 |

56 | {{strCaption}} 62 | {{strCaption}} 68 | 72 | 77 | {{strCaption = objModule.caption}} 78 | 79 | {{strCaption = objModule.caption}} 80 | 81 |
82 | 86 | 90 | {{strHost}} 96 | 97 | 98 | 112 |

113 |
114 |

118 |

122 |
123 | 138 | -------------------------------------------------------------------------------- /static/options/partials/settings.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 38 |
44 |
50 |
51 | -------------------------------------------------------------------------------- /static/options/partials/voice-control.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | 5 | 6 | 7 | 8 |

9 |

14 |

19 | 46 |
47 |
48 | 49 |
    50 |
  • 51 | 67 |
  • 68 |
69 |
70 |
71 | 72 |
    73 |
  • 74 | 78 |
  • 79 |
  • 80 |
  • 81 |
  • 82 |
83 |
84 |
89 | 90 |
    91 |
  • 92 | 98 |
  • 99 |
100 |
    101 |
  • 102 | 112 |
  • 113 |
  • 114 | 124 |
  • 125 |
126 |
127 |
128 |
129 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require( 'path' ); 2 | const { List, Map } = require( 'immutable' ); 3 | const CopyWebpackPlugin = require( 'copy-webpack-plugin' ); 4 | const CleanWebpackPlugin = require( 'clean-webpack-plugin' ); 5 | const WebpackCleanPlugin = require( 'webpack-clean' ); 6 | 7 | const modeDevelopment = process.env.NODE_ENV === 'development'; 8 | 9 | const defaultConfig = Map( { 10 | entry: { 11 | 'manifest': './static/manifest.json', 12 | }, 13 | output: Map( { 14 | filename: '[name].js', 15 | chunkFilename: '[name].js', 16 | path: path.resolve( __dirname, 'dist' ), 17 | } ), 18 | module: { 19 | rules: [ 20 | { 21 | test: /manifest.json$/, 22 | exclude: /node_modules/, 23 | loader: 'manifest-loader', 24 | }, 25 | ], 26 | }, 27 | plugins: List( [ 28 | new CleanWebpackPlugin( 29 | [ 30 | 'dist', 31 | ] 32 | ), 33 | 34 | new CopyWebpackPlugin( 35 | [ 36 | { 37 | from: './static', 38 | to: './', 39 | }, 40 | ] 41 | ), 42 | ] ), 43 | resolveLoader: { 44 | modules: [ 45 | path.resolve( __dirname, 'src', 'loaders' ), 46 | 'node_modules', 47 | ], 48 | }, 49 | devtool: modeDevelopment ? 50 | 'inline-cheap-module-source-map' : 51 | false, 52 | watch: modeDevelopment, 53 | } ); 54 | 55 | const supportedBrowsers = [ 56 | 'chromium', 57 | // @todo Add support for Firefox. 58 | // 'firefox', 59 | ]; 60 | 61 | module.exports = supportedBrowsers.map( browserName => { 62 | return defaultConfig 63 | // Create a separate dist folder for each browser 64 | .updateIn( 65 | [ 66 | 'output', 67 | 'path', 68 | ], 69 | () => path.resolve( __dirname, 'dist', browserName ), 70 | ) 71 | // Remove unused automatically-created JavaScript files post-build 72 | .updateIn( 73 | [ 74 | 'plugins', 75 | ], 76 | value => value.push( 77 | new WebpackCleanPlugin( 78 | [ 79 | 'manifest.js', 80 | ], 81 | { 82 | basePath: path.resolve( __dirname, 'dist', browserName ), 83 | }, 84 | ), 85 | ), 86 | ) 87 | .toJS() 88 | ; 89 | } ); 90 | --------------------------------------------------------------------------------