├── .gitattributes ├── .gitignore ├── .opera └── manifest.json ├── .work ├── chrome_market │ ├── descr.en.txt │ ├── descr.es.txt │ ├── descr.fr.txt │ ├── descr.hu.txt │ ├── descr.sr.txt │ ├── descr.uk.txt │ ├── promo_img │ │ ├── promo_1400x560.png │ │ ├── promo_1400x560.psd │ │ ├── promo_440x280.png │ │ ├── promo_440x280.psd │ │ ├── promo_920x680.png │ │ └── promo_920x680.psd │ └── screenshots │ │ └── en │ │ ├── 1.jpg │ │ └── 2.jpg ├── icons_psd │ ├── 128.psd │ ├── 19.psd │ ├── 38.psd │ └── 48.psd └── opera_market │ ├── 64.png │ ├── descr.en.txt │ ├── descr.es.txt │ ├── descr.fr.txt │ ├── descr.hu.txt │ ├── descr.sr.txt │ ├── descr.uk.txt │ ├── promo │ ├── 300x188.png │ └── 300x188.psd │ └── screenshots │ ├── 1.jpg │ └── 2.jpg ├── _locales ├── en │ └── messages.json ├── es │ └── messages.json ├── fr │ └── messages.json ├── hu │ └── messages.json ├── sr │ └── messages.json └── uk │ └── messages.json ├── build ├── build.json ├── build.py ├── releases │ ├── 0.1.1.zip │ ├── 0.1.3.zip │ ├── 0.1.zip │ ├── 0.2.1.zip │ ├── 0.2.10.zip │ ├── 0.2.2.zip │ ├── 0.2.3.zip │ ├── 0.2.4.zip │ ├── 0.2.5.zip │ ├── 0.2.6.zip │ ├── 0.2.7.zip │ ├── 0.2.8.1.zip │ ├── 0.2.8.zip │ ├── 0.2.9.zip │ ├── 0.2.zip │ ├── 0.3.0.zip │ └── changelog.txt └── releases_opera │ ├── 0.1.1.nex │ ├── 0.1.2.nex │ ├── 0.1.3.nex │ ├── 0.2.1.nex │ ├── 0.2.10.nex │ ├── 0.2.2.nex │ ├── 0.2.3.nex │ ├── 0.2.4.nex │ ├── 0.2.5.nex │ ├── 0.2.6.nex │ ├── 0.2.7.nex │ ├── 0.2.8.1.nex │ ├── 0.2.8.nex │ ├── 0.2.9.nex │ └── 0.2.nex ├── css ├── options.css └── popup.css ├── img ├── ext_icons │ ├── 128.png │ ├── 16.png │ ├── 32.png │ └── 48.png ├── help_screen.png ├── pin-26.png └── ua_1px.png ├── js ├── background.js ├── backup.js ├── helpers │ ├── localizer.js │ └── quick_options.js ├── options.js ├── popup.js ├── storage.js └── utils.js ├── lib └── jsTabs │ ├── tabs.css │ └── tabs.js ├── manifest.json ├── options.html ├── popup.html └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | -------------------------------------------------------------------------------- /.opera/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "description": "__MSG_extDescr__", 4 | "version": "0.2.10", 5 | "manifest_version": 2, 6 | 7 | "developer": { "name": "Mykhailo Onikiienko", 8 | "url": "http://onikienko.pp.ua" }, 9 | 10 | "default_locale": "en", 11 | "icons": { 12 | "16": "img/ext_icons/16.png", 13 | "48": "img/ext_icons/48.png", 14 | "128": "img/ext_icons/128.png" 15 | }, 16 | 17 | "browser_action": { 18 | "default_icon": { 19 | "19": "img/ext_icons/19.png", 20 | "38": "img/ext_icons/38.png" 21 | }, 22 | "default_title": "__MSG_extName__", 23 | "default_popup": "popup.html" 24 | }, 25 | 26 | "background": { 27 | "scripts": ["js/storage.js", "js/background.js"], 28 | "persistent": true 29 | }, 30 | "options_page": "options.html", 31 | 32 | "permissions": [ 33 | "storage", 34 | "tabs", 35 | "opera://favicon/" 36 | ] 37 | } -------------------------------------------------------------------------------- /.work/chrome_market/descr.en.txt: -------------------------------------------------------------------------------- 1 | ► Save opened tabs in the group. 2 | ✓ Tab Groups are stored in the sync Chrome storage. Sign in to the browser to get them on other PC. 3 | ✓ Assign a group name. 4 | ✓ Edit, add new tabs, delete, move the group from the list. 5 | 6 | ► Session Manager provides quick and convenient access to the latest browser sessions. 7 | ✓ Access to the currently open (and earlier) browser session. 8 | ✓ Recover any session in one click. 9 | ✓ !!! Sessions are not synchronized. 10 | 11 | 12 | ☀ Tips: 13 | ● To quickly save all open tabs in a group, open the extension window and press "ENTER." Later you can edit the name of the group. 14 | ● "Ctrl + click" on group name (or session) - open tabs in a new window. 15 | 16 | 17 | Tab Hamster source code: https://github.com/onikienko/TabHamster 18 | -------------------------------------------------------------------------------- /.work/chrome_market/descr.es.txt: -------------------------------------------------------------------------------- 1 | ► Guarda pestañas abiertas como un grupo. 2 | ✓ Los Grupos de Pestañas se guardan el espacio de almacenamiento para sincronización. Ingrese a su cuenta en el navegador para obtenerlos en otro PC. 3 | ✓ Asigne un nombre al grupo. 4 | ✓ Edite, agregue nuevas pestañas, elimine, mueva el grupo dentro de la lista. 5 | 6 | ► El Administrador de Sesiones brinda una rápida y conveniente forma de acceso a las ultimas sesiones de navegación. 7 | ✓ Acceda a la sesión del navegador actualmente abierta, así como a las anteriores. 8 | ✓ Recupere rápidamente cualquier sesión con un sólo clic. 9 | ✓! Las sesiones NO se sincronizan. 10 | 11 | 12 | ☀ Consejos: 13 | ● Para guardar rápidamente todas las pestañas abiertas en un grupo, abra la ventana de la extensión y presione 'ENTER'. Luego, puede editar el nombre del grupo. 14 | ● 'Ctrl + click' en el nombre de un grupo (o sesión) lo abrirá en una nueva ventana. 15 | 16 | 17 | Código fuente de Tab Hamster: https://github.com/onikienko/TabHamster -------------------------------------------------------------------------------- /.work/chrome_market/descr.fr.txt: -------------------------------------------------------------------------------- 1 | ► Enregistrer les onglets ouverts dans le groupe. 2 | ✓ Les groupes d'onglets sont synchronisés avec l'espace de stockage de Chrome. Connectez-vous au navigateur afin de les récupérer sur un autre PC. 3 | ✓ Attribuer un nom de groupe. 4 | ✓ Modifier, ajouter de nouveaux onglets, supprimer, déplacer le groupe de la liste. 5 | 6 | ► Le Gestionnaire de Session fournit un accès rapide et pratique aux dernières sessions du navigateur. 7 | ✓ Accès à la session actuellement ouverte (et versions antérieures) du navigateur. 8 | ✓ Récupération d'une session en un seul clic. 9 | ✓! Les sessions ne sont pas synchronisés. 10 | 11 | 12 | ☀ Conseils: 13 | ● Pour sauvegarder rapidement tous les onglets ouverts dans un groupe, ouvrez la fenêtre d'extension et appuyez sur "ENTRER". Plus tard, vous pouvez modifier le nom du groupe. 14 | ● "Ctrl + clic" sur le nom du groupe (ou session) - ouvre les onglets dans une nouvelle fenêtre. 15 | 16 | 17 | Tab Hamster source code : https://github.com/onikienko/TabHamster 18 | -------------------------------------------------------------------------------- /.work/chrome_market/descr.hu.txt: -------------------------------------------------------------------------------- 1 | ► A megnyitott fülek csoportokba történő mentése. 2 | ✓ A fül csoportok a Chrome szinkronizációs adatbázisában tárolódnak. Ezeket a bejelentkezés után egy másik számítógépen is elérhetjük. 3 | ✓ Név hozzárendelése a csoportokhoz. 4 | ✓ Szerkesztés, új fülek hozzáadása, törlés és a csoportok mozgatása a listán. 5 | 6 | ► A folyamatmenedzselő gyors és kényelmes hozzáférést biztosít a Chrome folyamatokhoz. 7 | ✓ Hozzáférés az éppen megnyitott (és a korábbi) böngészési folyamatokhoz. 8 | ✓ Egy kattintással visszaállítható bármelyik folyamat. 9 | ✓! A folyamatok nem szinkronizálódnak. 10 | 11 | 12 | ☀ Tippek: 13 | ● Gyorsan el tudjuk menteni az összes megnyitott fület egy csoportba, ha a kiterjesztés ablakában megnyomjuk az "ENTER" billentyűt. A csoportot később tudjuk szerkeszteni. 14 | ● "Ctrl + klikk' a mentett csoport (vagy folyamat) nevén új ablakba nyitja meg a füleket. 15 | 16 | Tab Hamster forráskód: https://github.com/onikienko/TabHamster 17 | -------------------------------------------------------------------------------- /.work/chrome_market/descr.sr.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/descr.sr.txt -------------------------------------------------------------------------------- /.work/chrome_market/descr.uk.txt: -------------------------------------------------------------------------------- 1 | ► Зберігай відкриті вкладки (таби) в групи. 2 | ✓ Всі збережені групи зберігаються в сховище Chrome. Це сховище синхронізується. Це дає можливість користуватися ними на різних машинах (дома, в офісі). 3 | ✓ Для роботи синхронізації треба виконати вхід в обліковий запис Chrome. 4 | ✓ Призначай групам ім'я. 5 | ✓ Редагуй, додавай нові таби, видаляй, переміщуй групи. 6 | 7 | ► Менеджер сесій - швидкий та зручний доступ до 30 останніх сесій браузера. 8 | ✓ Доступ до відкритих зараз (та раніше) вкладок браузера. 9 | ✓ Швидке відновлення будь-якої сесії. 10 | ✓ !!! Сесії не синхронізуються. 11 | 12 | 13 | ☀ Підказки: 14 | ● Для того, щоб швидко зберегти всі відкриті таби в групу, відкрий вікно розширення та натисни клавішу 'ENTER' на клавіатурі. Нова група буде мати ім'я з поточних дати та часу. (Пізніше можна буде відредагувати) 15 | ● "Ctrl + клік" на імені групи (або сесії) - відкрити всі вкладки в новому вікні. 16 | 17 | 18 | Tab Hamster source code: https://github.com/onikienko/TabHamster 19 | -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_1400x560.png -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_1400x560.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_1400x560.psd -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_440x280.png -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_440x280.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_440x280.psd -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_920x680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_920x680.png -------------------------------------------------------------------------------- /.work/chrome_market/promo_img/promo_920x680.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/promo_img/promo_920x680.psd -------------------------------------------------------------------------------- /.work/chrome_market/screenshots/en/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/screenshots/en/1.jpg -------------------------------------------------------------------------------- /.work/chrome_market/screenshots/en/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/chrome_market/screenshots/en/2.jpg -------------------------------------------------------------------------------- /.work/icons_psd/128.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/icons_psd/128.psd -------------------------------------------------------------------------------- /.work/icons_psd/19.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/icons_psd/19.psd -------------------------------------------------------------------------------- /.work/icons_psd/38.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/icons_psd/38.psd -------------------------------------------------------------------------------- /.work/icons_psd/48.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/icons_psd/48.psd -------------------------------------------------------------------------------- /.work/opera_market/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/64.png -------------------------------------------------------------------------------- /.work/opera_market/descr.en.txt: -------------------------------------------------------------------------------- 1 | ► Save open tabs in the group. 2 | ✓ Tab Groups are stored in the sync Opera storage. Sign in to browser to get them on other PC. 3 | ✓ Assign a group name. 4 | ✓ Edit, add new tabs, delete, move the group from the list. 5 | 6 | ► Session Manager provides quick and convenient access to the latest Opera sessions. 7 | ✓ Access to the currently open (and earlier) browser session. 8 | ✓ Rapid recovery of any session in one click. 9 | ✓! Sessions are not synchronized. 10 | 11 | 12 | ☀ Tips: 13 | ● To quickly save all open tabs in a group, open the extension window and press "ENTER". Later, you can edit the name of the group. 14 | ● "Ctrl + click" on group name (or session) - open tabs in a new window. -------------------------------------------------------------------------------- /.work/opera_market/descr.es.txt: -------------------------------------------------------------------------------- 1 | ► Guarda pestañas abiertas como un grupo. 2 | ✓ Los Grupos de Pestañas se guardan el espacio de almacenamiento para sincronización. Ingrese a su cuenta en el navegador para obtenerlos en otro PC. 3 | ✓ Asigne un nombre al grupo. 4 | ✓ Edite, agregue nuevas pestañas, elimine, mueva el grupo dentro de la lista. 5 | 6 | ► El Administrador de Sesiones brinda una rápida y conveniente forma de acceso a las ultimas sesiones de navegación. 7 | ✓ Acceda a la sesión del navegador actualmente abierta, así como a las anteriores. 8 | ✓ Recupere rápidamente cualquier sesión con un sólo clic. 9 | ✓! Las sesiones NO se sincronizan. 10 | 11 | 12 | ☀ Consejos: 13 | ● Para guardar rápidamente todas las pestañas abiertas en un grupo, abra la ventana de la extensión y presione 'ENTER'. Luego, puede editar el nombre del grupo. 14 | ● 'Ctrl + click' en el nombre de un grupo (o sesión) lo abrirá en una nueva ventana. 15 | 16 | 17 | Código fuente de Tab Hamster: https://github.com/onikienko/TabHamster -------------------------------------------------------------------------------- /.work/opera_market/descr.fr.txt: -------------------------------------------------------------------------------- 1 | ► Enregistrer les onglets ouverts dans le groupe. 2 | ✓ Les groupes d'onglets sont synchronisés avec l'espace de stockage d'Opéra. Connectez-vous au navigateur afin de les récupérer sur un autre PC. 3 | ✓ Attribuer un nom de groupe. 4 | ✓ Modifier, ajouter de nouveaux onglets, supprimer, déplacer le groupe de la liste. 5 | 6 | ► Le Gestionnaire de Session fournit un accès rapide et pratique aux dernières sessions d'Opéra. 7 | ✓ Accès à la session actuellement ouverte (et versions antérieures) du navigateur. 8 | ✓ Récupération d'une session en un seul clic. 9 | ✓! Les sessions ne sont pas synchronisés. 10 | 11 | 12 | ☀ Conseils: 13 | ● Pour sauvegarder rapidement tous les onglets ouverts dans un groupe, ouvrez la fenêtre d'extension et appuyez sur "ENTRER". Plus tard, vous pouvez modifier le nom du groupe. 14 | ● "Ctrl + clic" sur le nom du groupe (ou session) - ouvre les onglets dans une nouvelle fenêtre. 15 | -------------------------------------------------------------------------------- /.work/opera_market/descr.hu.txt: -------------------------------------------------------------------------------- 1 | ► A megnyitott fülek csoportokba történő mentése. 2 | ✓ A fül csoportok az Opera szinkronizációs adatbázisában tárolódnak. Ezeket a bejelentkezés után egy másik számítógépen 3 | is elérhetjük. 4 | ✓ Név hozzárendelése a csoportokhoz. 5 | ✓ Szerkesztés, új fülek hozzáadása, törlés és a csoportok mozgatása a listán. 6 | 7 | ► A folyamatmenedzselő gyors és kényelmes hozzáférést biztosít az Opera folyamatokhoz. 8 | ✓ Hozzáférés az éppen megnyitott (és a korábbi) böngészési folyamatokhoz. 9 | ✓ Egy kattintással visszaállítható bármelyik folyamat. 10 | ✓! A folyamatok nem szinkronizálódnak. 11 | 12 | 13 | ☀ Tippek: 14 | ● Gyorsan el tudjuk menteni az összes megnyitott fület egy csoportba, ha a kiterjesztés ablakában megnyomjuk az "ENTER" 15 | billentyűt. A csoportot később tudjuk szerkeszteni. 16 | ● "Ctrl + klikk' a mentett csoport (vagy folyamat) nevén új ablakba nyitja meg a füleket. 17 | -------------------------------------------------------------------------------- /.work/opera_market/descr.sr.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/descr.sr.txt -------------------------------------------------------------------------------- /.work/opera_market/descr.uk.txt: -------------------------------------------------------------------------------- 1 | ► Зберігай відкриті вкладки (таби) в групи. 2 | ✓ Всі збережені групи записуються в сховище Opera. Це сховище сінхронізується. Це дає вам можливість користуватися ними на різних машинах (дома, в офісі). 3 | ✓ Для роботи сінхронізаціі треба виконати вхід в Opera аккаунт. 4 | ✓ Призначай групам ім'я. 5 | ✓ Редагуйте, додавайте нові таби, видаляйте, переміщуйте групи. 6 | 7 | ► Менеджер сесій - швидкий та зручний доступ до 30 останніх сесій бравзера. 8 | ✓ Доступ до відкритих зараз (та раніше) вкладок бравзера. 9 | ✓ Швидке поновлення будь якої сесії. 10 | ✓ !!! Сесії не сінхронізуються. 11 | 12 | 13 | ☀ Підказки: 14 | ● Щоб швидко зберегти всі відкриті таби в групу, відкрий вікно розширення та натисни клавішу 'ENTER' на клавіатурі. Нова група буде мати ім'я з поточних дати та часу. (Пізніше можна буде відредагувати) 15 | ● "Ctrl + клік" на імені групи (або сесії) - відкрити всі вкладки в новому вікні. 16 | 17 | 18 | Tab Hamster source code: https://github.com/onikienko/TabHamster -------------------------------------------------------------------------------- /.work/opera_market/promo/300x188.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/promo/300x188.png -------------------------------------------------------------------------------- /.work/opera_market/promo/300x188.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/promo/300x188.psd -------------------------------------------------------------------------------- /.work/opera_market/screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/screenshots/1.jpg -------------------------------------------------------------------------------- /.work/opera_market/screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/.work/opera_market/screenshots/2.jpg -------------------------------------------------------------------------------- /_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "TabHamster", 4 | "description": "do not translate extension name" 5 | }, 6 | "extDescr": { 7 | "message": "Session manager and tabs saver. With sync", 8 | "description": "" 9 | }, 10 | 11 | "p_optionsBtn": { 12 | "message": "Settings", 13 | "description": "Prefix 'p_' - popup" 14 | }, 15 | "p_helpBtn": { 16 | "message": "Help", 17 | "description": "Title for help button" 18 | }, 19 | "p_GroupNameToSave": { 20 | "message": "New group from opened tabs", 21 | "description": "Placeholder in text input" 22 | }, 23 | "p_groupBtn": { 24 | "message": "Save", 25 | "description": "Btn hint" 26 | }, 27 | "p_tabName_Saved": { 28 | "message": "Saved groups", 29 | "description": "" 30 | }, 31 | "p_tabName_Sessions": { 32 | "message": "Sessions", 33 | "description": "" 34 | }, 35 | "p_synStorageUsage_title": { 36 | "message": "Browser's sync storage usage", 37 | "description": "" 38 | }, 39 | 40 | "p_newGroupName_popup": { 41 | "message": "New group name:", 42 | "description": "Modal window dialog" 43 | }, 44 | "p_addLinkTitle_popup": { 45 | "message": "Title", 46 | "description": "Modal window dialog" 47 | }, 48 | "p_addLinkLink_popup": { 49 | "message": "URL", 50 | "description": "Modal window dialog" 51 | }, 52 | "p_overwriteGroup_popup": { 53 | "message": "The group with the same name already exists. Do you want overwrite old one with current tabs?", 54 | "description": "Modal window dialog" 55 | }, 56 | "p_delGroup_btn_title": { 57 | "message": "Del group" 58 | }, 59 | "p_editGroupName_btn_title": { 60 | "message": "Edit group name" 61 | }, 62 | "p_groupInNewWindow_btn_title": { 63 | "message": "Open in new window (Ctrl + click)" 64 | }, 65 | "p_addLinkToGroup_btn_title": { 66 | "message": "New link to group" 67 | }, 68 | "p_delLinkFromGroup_btn_title": { 69 | "message": "Delete" 70 | }, 71 | "p_editLink_btn_title": { 72 | "message": "Edit" 73 | }, 74 | "p_upGroup_btn_title": { 75 | "message": "Up" 76 | }, 77 | "p_downGroup_btn_title": { 78 | "message": "Down" 79 | }, 80 | "p_windowsNumbers_text": { 81 | "message": "Win:" 82 | }, 83 | "p_tabsNumbers_text": { 84 | "message": "Tabs:" 85 | }, 86 | "p_window_text": { 87 | "message": "Window" 88 | }, 89 | "p_quotaBitesPerItem_error": { 90 | "message": "Can't save to sync storage. Too many tabs. Try to close one more." 91 | }, 92 | "p_quotaBites_error": { 93 | "message": "Sync storage is full. Del unused groups." 94 | }, 95 | "p_syncStorageDefault_error": { 96 | "message": "Sync storage error. Try again later." 97 | }, 98 | "p_currentSession": { 99 | "message": "Current" 100 | }, 101 | 102 | 103 | "o_tabs_help": { 104 | "message": "Help", 105 | "description": "Tab name. Prefix 'o_' for options page" 106 | }, 107 | "o_tabs_backup": { 108 | "message": "Backup / Restore", 109 | "description": "Tab name" 110 | }, 111 | "o_tabs_main": { 112 | "message": "Settings", 113 | "description": "Settings tab" 114 | }, 115 | "o_tabs_about": { 116 | "message": "About", 117 | "description": "About tab" 118 | }, 119 | "o_error": { 120 | "message": "Save error", 121 | "description": "Error during save settings" 122 | }, 123 | "o_saved": { 124 | "message": "Saved", 125 | "description": "Settings saved OK" 126 | }, 127 | "o_savedTabs_legend": { 128 | "message": "Filter for tabs saving (not sessions)", 129 | "description": "Legend tag" 130 | }, 131 | "o_savedTabs_pinned": { 132 | "message": "Add pinned tabs to set", 133 | "description": "" 134 | }, 135 | "o_savedTabs_cur_win": { 136 | "message": "Current window only", 137 | "description": "" 138 | }, 139 | "o_common_legend": { 140 | "message": "Main", 141 | "description": "" 142 | }, 143 | "o_dateFormat_text": { 144 | "message": "Date format", 145 | "description": "" 146 | }, 147 | "o_dateFormat_eu": { 148 | "message": "DD-MM-YYYY", 149 | "description": "EU date format" 150 | }, 151 | "o_dateFormat_us": { 152 | "message": "YYYY-MM-DD", 153 | "description": "US date format" 154 | }, 155 | "o_help_syncStorage": { 156 | "message": "Tab Groups are stored in the sync storage. Sign in to browser to get them on other PC.", 157 | "description": "" 158 | }, 159 | "o_help_syncStorage_0": { 160 | "message": "Sessions are not sync.", 161 | "description": "" 162 | }, 163 | "o_help_syncStorage_1": { 164 | "message": "Sync storage is limited in size. You can see storage usage (No.1 on the screenshot).", 165 | "description": "" 166 | }, 167 | "o_help_syncStorage_2": { 168 | "message": "There is also a limit on the size of the recorded data at a time. It is approximately 17 - 24 tabs.", 169 | "description": "" 170 | }, 171 | "o_help_tipsLegend": { 172 | "message": "Tips", 173 | "description": "" 174 | }, 175 | "o_help_quickAdd": { 176 | "message": "To quickly save all open tabs in a group, just open extension window and press 'ENTER'.", 177 | "description": "" 178 | }, 179 | "o_help_showLinks": { 180 | "message": "To view tabs in saved group click on gray triangle (No.2 on the screenshot)", 181 | "description": "" 182 | }, 183 | "o_help_inNewWindow": { 184 | "message": "'Ctrl + click' on group name (or session) will open it in a new window. Or click on the icon next to the group name (No.3 on the screenshot)", 185 | "description": "" 186 | }, 187 | "o_help_editGroup": { 188 | "message": "You can edit, delete, change position for every saved group (No.4 on the screenshot)", 189 | "description": "" 190 | }, 191 | "o_help_overFavicon": { 192 | "message": "Place mouse pointer over favicon to view url of tab.", 193 | "description": "" 194 | }, 195 | "o_help_settings": { 196 | "message": "Look at the extensions settings", 197 | "description": "" 198 | }, 199 | "o_sessionWatcher_toggle": { 200 | "message": "Watch sessions. Unchecked will disable the automatic session saving. Extension will reload automatically!!!", 201 | "description": "" 202 | }, 203 | 204 | "o_backup_backupLegend": { 205 | "message": "Backup ", 206 | "description": "" 207 | }, 208 | "o_backup_backupInstruction": { 209 | "message": "Copy text from text area below (CTRL+A to select whole text and CTRL+C to copy). Then past (CTRL+V) and save text in a file. For example TabHamsterBackup.txt. Use UTF-8 encoding for this file.", 210 | "description": "" 211 | }, 212 | "o_backup_backupInstructionMore": { 213 | "message": "You will be able to use this data to restore your saved groups and extension options. You cannot restore sessions.", 214 | "description": "" 215 | }, 216 | 217 | "o_backup_restoreLegend": { 218 | "message": "Restore", 219 | "description": "" 220 | }, 221 | "o_backup_restoreInstruction": { 222 | "message": "Past your previously saved backup in this text area. Make sure that you past UTF-8 encoded text. Then press 'Restore...' button. Extension will be automatically restarted.", 223 | "description": "" 224 | }, 225 | "o_backup_restoreInstructionMore": { 226 | "message": "Attention! All your currently saved groups and options will be erased.", 227 | "description": "" 228 | }, 229 | "o_backup_restoreButton": { 230 | "message": "Restore options and saved groups", 231 | "description": "" 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "TabHamster", 4 | "description": "no traducir el nombre de la extensión" 5 | }, 6 | "extDescr": { 7 | "message": "Administrador de sesiones y almacén de pestañas. Con sincronización.", 8 | "description": "" 9 | }, 10 | 11 | "p_optionsBtn": { 12 | "message": "Opciones", 13 | "description": "Prefix 'p_' - popup" 14 | }, 15 | "p_helpBtn": { 16 | "message": "Ayuda", 17 | "description": "Título para el botón de ayuda" 18 | }, 19 | "p_GroupNameToSave": { 20 | "message": "Pestañas a un nuevo grupo", 21 | "description": "Indicador de lugar para ingreso de texto" 22 | }, 23 | "p_groupBtn": { 24 | "message": "Guardar", 25 | "description": "Ayuda para Botón" 26 | }, 27 | "p_tabName_Saved": { 28 | "message": "Grupos guardados", 29 | "description": "" 30 | }, 31 | "p_tabName_Sessions": { 32 | "message": "Sesiones", 33 | "description": "" 34 | }, 35 | "p_synStorageUsage_title": { 36 | "message": "Uso del espacio de sincronización del Navegador", 37 | "description": "" 38 | }, 39 | 40 | "p_newGroupName_popup": { 41 | "message": "Nuevo nombre para el grupo:", 42 | "description": "" 43 | }, 44 | "p_addLinkTitle_popup": { 45 | "message": "Título", 46 | "description": "" 47 | }, 48 | "p_addLinkLink_popup": { 49 | "message": "URL", 50 | "description": "" 51 | }, 52 | "p_delGroup_btn_title": { 53 | "message": "Eliminar grupo" 54 | }, 55 | "p_editGroupName_btn_title": { 56 | "message": "Editar nombre del grupo" 57 | }, 58 | "p_groupInNewWindow_btn_title": { 59 | "message": "Abrir en una nueva ventana (Ctrl + click)" 60 | }, 61 | "p_addLinkToGroup_btn_title": { 62 | "message": "Nuevo vínculo a grupo" 63 | }, 64 | "p_delLinkFromGroup_btn_title": { 65 | "message": "Eliminar" 66 | }, 67 | "p_editLink_btn_title": { 68 | "message": "Editar" 69 | }, 70 | "p_upGroup_btn_title": { 71 | "message": "Subir" 72 | }, 73 | "p_downGroup_btn_title": { 74 | "message": "Bajar" 75 | }, 76 | "p_windowsNumbers_text": { 77 | "message": "Ventana:" 78 | }, 79 | "p_tabsNumbers_text": { 80 | "message": "Pestaña:" 81 | }, 82 | "p_window_text": { 83 | "message": "Ventana" 84 | }, 85 | "p_quotaBitesPerItem_error": { 86 | "message": "No se puede guardar al almacenamiento de sincronización. Muchas Pestañas. Intente cerrar alguna." 87 | }, 88 | "p_quotaBites_error": { 89 | "message": "El espacio de Sincronización está lleno. Eliminar grupos en desuso." 90 | }, 91 | "p_syncStorageDefault_error": { 92 | "message": "Error al almacenar sincronización. Intente nuevamente luego." 93 | }, 94 | "p_currentSession": { 95 | "message": "Actual" 96 | }, 97 | 98 | 99 | "o_tabs_help": { 100 | "message": "Ayuda", 101 | "description": "Nombre de la Pestaña. Prefijo 'o_' por Opciones" 102 | }, 103 | "o_tabs_main": { 104 | "message": "Opciones", 105 | "description": "Pestaña Opciones" 106 | }, 107 | "o_tabs_about": { 108 | "message": "Acerca de", 109 | "description": "Pestaña con información del producto" 110 | }, 111 | "o_error": { 112 | "message": "Error al guardar", 113 | "description": "Error al guardar las opciones" 114 | }, 115 | "o_saved": { 116 | "message": "Guardado", 117 | "description": "Opciones correctamente guardadas" 118 | }, 119 | "o_savedTabs_legend": { 120 | "message": "Filtro para guardado de pestañas (no sesiones)", 121 | "description": "Etiqueta para leyenda" 122 | }, 123 | "o_savedTabs_pinned": { 124 | "message": "Agregar pestañas Fijas al conjunto", 125 | "description": "" 126 | }, 127 | "o_savedTabs_cur_win": { 128 | "message": "Únicamente ventana actual", 129 | "description": "" 130 | }, 131 | "o_common_legend": { 132 | "message": "Principal", 133 | "description": "" 134 | }, 135 | "o_dateFormat_text": { 136 | "message": "Formato de fecha", 137 | "description": "" 138 | }, 139 | "o_dateFormat_eu": { 140 | "message": "DD-MM-YYYY", 141 | "description": "Formato europeo" 142 | }, 143 | "o_dateFormat_us": { 144 | "message": "YYYY-MM-DD", 145 | "description": "Formato americano" 146 | }, 147 | "o_help_syncStorage": { 148 | "message": "Los grupos de pestañas son guardados en el espacio de sincronización. Inicie sesión en el navegador para obtenerlos en otro dispositivo.", 149 | "description": "" 150 | }, 151 | "o_help_syncStorage_0": { 152 | "message": "Las sesiones no se sincronizan.", 153 | "description": "" 154 | }, 155 | "o_help_syncStorage_1": { 156 | "message": "El espacio de sincronización es de tamaño limitado. Puede consultar el uso de dicho espacio (Vea No.1 en la captura de pantalla).", 157 | "description": "" 158 | }, 159 | "o_help_syncStorage_2": { 160 | "message": "Existe también un limite en el tamaño de la información guardada para un mismo momento. Es de aproximadamente 17 a 24 pestañas.", 161 | "description": "" 162 | }, 163 | "o_help_tipsLegend": { 164 | "message": "Consejos", 165 | "description": "" 166 | }, 167 | "o_help_quickAdd": { 168 | "message": "Para guardar rápidamente todas las pestañas abiertas en un grupo, abra la ventana de la extensión y presione 'ENTER'.", 169 | "description": "" 170 | }, 171 | "o_help_showLinks": { 172 | "message": "Para ver las pestañas guardadas en un grupo presione sobre el triángulo gris (No.2 en la captura de pantalla)", 173 | "description": "" 174 | }, 175 | "o_help_inNewWindow": { 176 | "message": "'Ctrl + click' en el nombre de un grupo (o sesión) lo abrirá en una nueva ventana. O presione en el icono al costado del nombre del grupo (No.3 en la captura de pantalla).", 177 | "description": "" 178 | }, 179 | "o_help_editGroup": { 180 | "message": "Usted puede editar, eliminar y cambiar la posición para cada grupo guardado (No.4 en la captura de pantalla)", 181 | "description": "" 182 | }, 183 | "o_help_overFavicon": { 184 | "message": "Ubique el puntero del ratón sobre el favicon para ver la url de una pestaña.", 185 | "description": "" 186 | }, 187 | "o_help_settings": { 188 | "message": "Mire las opciones de la extensión", 189 | "description": "" 190 | } 191 | } -------------------------------------------------------------------------------- /_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "TabHamster", 4 | "description": "ne pas traduire le nom de l'extension" 5 | }, 6 | "extDescr": { 7 | "message": "Gestionnaire de session et sauveur d'onglets. Avec synchronisation", 8 | "description": "" 9 | }, 10 | 11 | "p_optionsBtn": { 12 | "message": "Paramètres", 13 | "description": "Préfixe 'p_' - popup" 14 | }, 15 | "p_helpBtn": { 16 | "message": "Aide", 17 | "description": "Titre pour le bouton d'aide" 18 | }, 19 | "p_GroupNameToSave": { 20 | "message": "Nouveau groupe pour les onglets ouverts", 21 | "description": "Espace réservé à la saisie de texte" 22 | }, 23 | "p_groupBtn": { 24 | "message": "Sauvegarder", 25 | "description": "Btn hint" 26 | }, 27 | "p_tabName_Saved": { 28 | "message": "Groupes Sauvegardés", 29 | "description": "" 30 | }, 31 | "p_tabName_Sessions": { 32 | "message": "Sessions", 33 | "description": "" 34 | }, 35 | "p_synStorageUsage_title": { 36 | "message": "Utilisation du stockage de synchronisation du navigateur", 37 | "description": "" 38 | }, 39 | 40 | "p_newGroupName_popup": { 41 | "message": "Nom du nouveau groupe :", 42 | "description": "" 43 | }, 44 | "p_addLinkTitle_popup": { 45 | "message": "Titre", 46 | "description": "" 47 | }, 48 | "p_addLinkLink_popup": { 49 | "message": "URL", 50 | "description": "" 51 | }, 52 | "p_delGroup_btn_title": { 53 | "message": "Suppr. groupe" 54 | }, 55 | "p_editGroupName_btn_title": { 56 | "message": "Modifier le nom du groupe" 57 | }, 58 | "p_groupInNewWindow_btn_title": { 59 | "message": "Ouvrir dans une nouvelle fenêtre (Ctrl + clic)" 60 | }, 61 | "p_addLinkToGroup_btn_title": { 62 | "message": "Nouveau lien dans le groupe" 63 | }, 64 | "p_delLinkFromGroup_btn_title": { 65 | "message": "Supprimer" 66 | }, 67 | "p_editLink_btn_title": { 68 | "message": "Modifier" 69 | }, 70 | "p_upGroup_btn_title": { 71 | "message": "Haut" 72 | }, 73 | "p_downGroup_btn_title": { 74 | "message": "Bas" 75 | }, 76 | "p_windowsNumbers_text": { 77 | "message": "Fenêtre :" 78 | }, 79 | "p_tabsNumbers_text": { 80 | "message": "Onglets :" 81 | }, 82 | "p_window_text": { 83 | "message": "Fenêtre" 84 | }, 85 | "p_quotaBitesPerItem_error": { 86 | "message": "Impossible de synchroniser avec l'espace de stockage. Trop d'onglets. Essayez d'en fermer un de plus." 87 | }, 88 | "p_quotaBites_error": { 89 | "message": "L'espace de synchronisation est plein. Supprimer des groupes non utilisés." 90 | }, 91 | "p_syncStorageDefault_error": { 92 | "message": "Erreur de synchronisation. Essayez plus tard." 93 | }, 94 | "p_currentSession": { 95 | "message": "Actuel" 96 | }, 97 | 98 | 99 | "o_tabs_help": { 100 | "message": "Aide", 101 | "description": "Nom d'onglet. Préfixe 'o_' pour la page d'options" 102 | }, 103 | "o_tabs_main": { 104 | "message": "Paramètres", 105 | "description": "Onglet des paramètres" 106 | }, 107 | "o_tabs_about": { 108 | "message": "À Propos", 109 | "description": "Onglet à propos" 110 | }, 111 | "o_error": { 112 | "message": "Erreur de sauvegarde", 113 | "description": "Erreur pendant la sauvegarde des paramètres" 114 | }, 115 | "o_saved": { 116 | "message": "Sauvegardé", 117 | "description": "Paramètres sauvegardés OK" 118 | }, 119 | "o_savedTabs_legend": { 120 | "message": "Filtre pour les onglets sauvegardés (exceptés les sessions)", 121 | "description": "Légende de l'étiquette" 122 | }, 123 | "o_savedTabs_pinned": { 124 | "message": "Ajouter les onglets épinglés", 125 | "description": "" 126 | }, 127 | "o_savedTabs_cur_win": { 128 | "message": "Fenêtre actuel seulement", 129 | "description": "" 130 | }, 131 | "o_common_legend": { 132 | "message": "Principal", 133 | "description": "" 134 | }, 135 | "o_dateFormat_text": { 136 | "message": "Format de la date", 137 | "description": "" 138 | }, 139 | "o_dateFormat_eu": { 140 | "message": "DD-MM-YYYY", 141 | "description": "Format de date EU" 142 | }, 143 | "o_dateFormat_us": { 144 | "message": "YYYY-MM-DD", 145 | "description": "Format de date US" 146 | }, 147 | "o_help_syncStorage": { 148 | "message": "Les groupes d'onglets sont stockés dans l'espace de stockage synchronisé. Connectez-vous au navigateur pour les récupérer sur un autre PC.", 149 | "description": "" 150 | }, 151 | "o_help_syncStorage_0": { 152 | "message": "Les sessions ne sont pas synchronisés", 153 | "description": "" 154 | }, 155 | "o_help_syncStorage_1": { 156 | "message": "L'espace de stockage synchronisé est limité en taille. Vous pouvez voir l'espace utilisé (No.1 sur la capture d'écran).", 157 | "description": "" 158 | }, 159 | "o_help_syncStorage_2": { 160 | "message": "Il existe également une limite à la taille de données enregistrées en une fois. Approximativement 17 - 24 onglets.", 161 | "description": "" 162 | }, 163 | "o_help_tipsLegend": { 164 | "message": "Astuces", 165 | "description": "" 166 | }, 167 | "o_help_quickAdd": { 168 | "message": "Pour rapidement sauvegarder tous les onglets ouvert dans un groupe, ouvrez juste la fenêtre d'extension et appuyez sur 'ENTER'.", 169 | "description": "" 170 | }, 171 | "o_help_showLinks": { 172 | "message": "Pour voir les onglets d'un groupe cliquez sur le triangle gris (No.2 sur la capture d'écran)", 173 | "description": "" 174 | }, 175 | "o_help_inNewWindow": { 176 | "message": "'Ctrl + clic' sur le nom du groupe (ou de la session) l'ouvrira dans une nouvelle fenêtre. Ou cliquez sur l'icône à côté du nom du groupe (No.3 sur la capture d'écran)", 177 | "description": "" 178 | }, 179 | "o_help_editGroup": { 180 | "message": "Vous pouvez modifier, supprimer, changer la position de chaque groupe sauvegardé (No.4 sur la capture d'écran)", 181 | "description": "" 182 | }, 183 | "o_help_overFavicon": { 184 | "message": "Placer le curseur de la souris sur le favicon pour voir l'url de l'onglet.", 185 | "description": "" 186 | }, 187 | "o_help_settings": { 188 | "message": "Voir les paramètres de l'extension", 189 | "description": "" 190 | }, 191 | "o_sessionWatcher_toggle": { 192 | "message": "Regarde les sessions. Décocher permet de désactiver la sauvegarde automatique de la session. Redémarrer le navigateur pour que les changements prennent effet", 193 | "description": "" 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /_locales/hu/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extDescr": { 3 | "description": "", 4 | "message": "Böngészési folyamatok kezelése és fülek mentése. Szinkronizációval" 5 | }, 6 | "extName": { 7 | "description": "do not translate extension name", 8 | "message": "TabHamster" 9 | }, 10 | "o_common_legend": { 11 | "description": "", 12 | "message": "Fő beállítások" 13 | }, 14 | "o_dateFormat_eu": { 15 | "description": "EU date format", 16 | "message": "nap / hónap / év" 17 | }, 18 | "o_dateFormat_text": { 19 | "description": "", 20 | "message": "Időformátum" 21 | }, 22 | "o_dateFormat_us": { 23 | "description": "US date format", 24 | "message": "év / hónap / nap" 25 | }, 26 | "o_error": { 27 | "description": "Error during save settings", 28 | "message": "Hiba a mentésnél" 29 | }, 30 | "o_help_editGroup": { 31 | "description": "", 32 | "message": "Egyszerűen tudjuk szerkeszteni, törölni és módosítani a mentett csoportokat (a negyedik pont a képernyőfotón)." 33 | }, 34 | "o_help_inNewWindow": { 35 | "description": "", 36 | "message": "'Ctrl + klikk' a mentett csoport (vagy folyamat) nevén új ablakba nyitja meg a füleket. Járható út még a kis ikonokra történő kattintás is. (a harmadik pont a képernyőképen)" 37 | }, 38 | "o_help_overFavicon": { 39 | "description": "", 40 | "message": "Ha az egérmutatót a favikonokra mozgatjuk, akkor rögtön meglátjuk a hivatkozásokat." 41 | }, 42 | "o_help_quickAdd": { 43 | "description": "", 44 | "message": "A kiterjesztés ablakában az ENTER billentyű lenyomásával azonnal egy csoportba menthetjük a megnyitott füleket." 45 | }, 46 | "o_help_settings": { 47 | "description": "", 48 | "message": "Tekintsük át a beállításokat!" 49 | }, 50 | "o_help_showLinks": { 51 | "description": "", 52 | "message": "Ahhoz, hogy áttekintsük a csoportba mentett lapokat, kattintsunk a szürke háromszögre (a második pont a képernyőfotón)." 53 | }, 54 | "o_help_syncStorage": { 55 | "description": "", 56 | "message": "A fülcsoportok bekerülnek a szinkronizációs adatbázisba. Ezeket a bejelentkezés után egy másik számítógépen is elérhetjük." 57 | }, 58 | "o_help_syncStorage_0": { 59 | "description": "", 60 | "message": "A folyamatok nem kerülnek szinkronizálásra." 61 | }, 62 | "o_help_syncStorage_1": { 63 | "description": "", 64 | "message": "A szinkronizációs adatbázis mérete limitált. A kihasználtságot mi magunk is ellenőrizhetjük (első pont a lenti képernyőképen)." 65 | }, 66 | "o_help_syncStorage_2": { 67 | "description": "", 68 | "message": "Az egy időben történő adatrögzítés limitálva van. Ez körülbelül 17 - 24 fület jelent." 69 | }, 70 | "o_help_tipsLegend": { 71 | "description": "", 72 | "message": "Tippek" 73 | }, 74 | "o_saved": { 75 | "description": "Settings saved OK", 76 | "message": "Mentve" 77 | }, 78 | "o_savedTabs_cur_win": { 79 | "description": "", 80 | "message": "Csak a jelenlegi ablak" 81 | }, 82 | "o_savedTabs_legend": { 83 | "description": "Legend tag", 84 | "message": "Szűrő a fülek mentéséhez (nem folyamatok)" 85 | }, 86 | "o_savedTabs_pinned": { 87 | "description": "", 88 | "message": "A rögzített füleket is hozzáadja" 89 | }, 90 | "o_tabs_about": { 91 | "description": "About tab", 92 | "message": "Névjegy" 93 | }, 94 | "o_tabs_help": { 95 | "description": "Tab name. Prefix 'o_' for options page", 96 | "message": "Segítség" 97 | }, 98 | "o_tabs_main": { 99 | "description": "Settings tab", 100 | "message": "Beállítások" 101 | }, 102 | "p_GroupNameToSave": { 103 | "description": "Placeholder in text input", 104 | "message": "Új csoport a megnyitott fülekkel" 105 | }, 106 | "p_addLinkLink_popup": { 107 | "description": "", 108 | "message": "Hivatkozás" 109 | }, 110 | "p_addLinkTitle_popup": { 111 | "description": "", 112 | "message": "Cím" 113 | }, 114 | "p_addLinkToGroup_btn_title": { 115 | "message": "Új hivatkozás a csoportba" 116 | }, 117 | "p_currentSession": { 118 | "message": "Aktuális" 119 | }, 120 | "p_delGroup_btn_title": { 121 | "message": "Csoport törlés" 122 | }, 123 | "p_delLinkFromGroup_btn_title": { 124 | "message": "Törlés" 125 | }, 126 | "p_downGroup_btn_title": { 127 | "message": "Le" 128 | }, 129 | "p_editGroupName_btn_title": { 130 | "message": "Csoportnév szerkesztés" 131 | }, 132 | "p_editLink_btn_title": { 133 | "message": "Szerkesztés" 134 | }, 135 | "p_groupBtn": { 136 | "description": "Btn hint", 137 | "message": "Mentés" 138 | }, 139 | "p_groupInNewWindow_btn_title": { 140 | "message": "Megnyitás új ablakban (Ctrl + klikk)" 141 | }, 142 | "p_helpBtn": { 143 | "description": "Title for help button", 144 | "message": "Segítség" 145 | }, 146 | "p_newGroupName_popup": { 147 | "description": "", 148 | "message": "Az új csoport neve:" 149 | }, 150 | "p_optionsBtn": { 151 | "description": "Prefix 'p_' - popup", 152 | "message": "Beállítások" 153 | }, 154 | "p_quotaBitesPerItem_error": { 155 | "message": "A szinkronizációs adatbázisba történő mentés nem sikerült. Túl sok a fül. Próbáljon meg bezárni párat." 156 | }, 157 | "p_quotaBites_error": { 158 | "message": "A szinkronizációs adatbázis megtelt. Érdemes törölni a használaton kívüli csoportokat." 159 | }, 160 | "p_synStorageUsage_title": { 161 | "description": "", 162 | "message": "A böngészők szinkronizációs adatbázisának kihasználtsága" 163 | }, 164 | "p_syncStorageDefault_error": { 165 | "message": "Szinkronizációs hiba. Próbálja megismételni a műveletet." 166 | }, 167 | "p_tabName_Saved": { 168 | "description": "", 169 | "message": "Mentett csoportok" 170 | }, 171 | "p_tabName_Sessions": { 172 | "description": "", 173 | "message": "Folyamatok" 174 | }, 175 | "p_tabsNumbers_text": { 176 | "message": "Fülek:" 177 | }, 178 | "p_upGroup_btn_title": { 179 | "message": "Fel" 180 | }, 181 | "p_window_text": { 182 | "message": "Ablak" 183 | }, 184 | "p_windowsNumbers_text": { 185 | "message": "Ablak:" 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /_locales/sr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extDescr": { 3 | "description": "", 4 | "message": "Менаџер сесија и чувар картица" 5 | }, 6 | "extName": { 7 | "description": "do not translate extension name", 8 | "message": "TabHamster" 9 | }, 10 | "o_backup_backupInstruction": { 11 | "description": "", 12 | "message": "Копирајте текст са текстуалног поља испод (CTRL+A за избор целог текста и CTRL+C за копирање). Затим налепите (CTRL+V) и сачувајте текст у датотеку, на пример TabHamsterBackup.txt (користите UTF-8 кодирање)." 13 | }, 14 | "o_backup_backupInstructionMore": { 15 | "description": "", 16 | "message": "Моћи ћете да користите ове податке за враћање сачуваних група и опција проширења. Не можете вратити сесије." 17 | }, 18 | "o_backup_backupLegend": { 19 | "description": "", 20 | "message": "Сачувај резервну копију " 21 | }, 22 | "o_backup_restoreButton": { 23 | "description": "", 24 | "message": "Врати опције и сачуване групе" 25 | }, 26 | "o_backup_restoreInstruction": { 27 | "description": "", 28 | "message": "Налепите претходно сачувану резервну копију у ово текстуално поље (будите сигурни да сте налепили UTF-8 кодирани текст) а затим притисните дугме 'Врати...'. Проширење ће се аутоматски поново покренути." 29 | }, 30 | "o_backup_restoreInstructionMore": { 31 | "description": "", 32 | "message": "Пажња! Све тренутне сачуване групе и опције ће бити избрисане." 33 | }, 34 | "o_backup_restoreLegend": { 35 | "description": "", 36 | "message": "Врати" 37 | }, 38 | "o_common_legend": { 39 | "description": "", 40 | "message": "Опште" 41 | }, 42 | "o_dateFormat_eu": { 43 | "description": "EU date format", 44 | "message": "ДД-ММ-ГГГГ" 45 | }, 46 | "o_dateFormat_text": { 47 | "description": "", 48 | "message": "Формат датума" 49 | }, 50 | "o_dateFormat_us": { 51 | "description": "US date format", 52 | "message": "ГГГГ-ММ-ДД" 53 | }, 54 | "o_error": { 55 | "description": "Error during save settings", 56 | "message": "Грешка приликом чувања подешавања" 57 | }, 58 | "o_help_editGroup": { 59 | "description": "", 60 | "message": "Можете уређивати, брисати, мењати позицију сваке сачуване групе (бр.4 на слици)." 61 | }, 62 | "o_help_inNewWindow": { 63 | "description": "", 64 | "message": "'Ctrl + клик' на име групе (или сесије) ће отворити картице у новом прозору. Такође и клик на иконицу поред имена групе (бр.3 на слици)." 65 | }, 66 | "o_help_overFavicon": { 67 | "description": "", 68 | "message": "Поставите курсор миша преко фавикона да бисте видели УРЛ картице." 69 | }, 70 | "o_help_quickAdd": { 71 | "description": "", 72 | "message": "Да бисте брзо сачували све отворене картице као групу, само отворите прозор проширења и притисните 'ENTER'." 73 | }, 74 | "o_help_settings": { 75 | "description": "", 76 | "message": "У подешавањима проширења можете одабрати формат датума и изменити филтер за чување картица." 77 | }, 78 | "o_help_showLinks": { 79 | "description": "", 80 | "message": "Да бисте видели картице у сачуваној групи кликните на сиви троугао (бр.2 на слици)." 81 | }, 82 | "o_help_syncStorage": { 83 | "description": "", 84 | "message": "Групе картица се чувају у складишту синхронизације. Пријавите се својим налогом у прегледачу да бисте их имали на другом рачунару." 85 | }, 86 | "o_help_syncStorage_0": { 87 | "description": "", 88 | "message": "Сесије се не синхронизују." 89 | }, 90 | "o_help_syncStorage_1": { 91 | "description": "", 92 | "message": "Складиште синхронизације је ограничено величином. Можете видети искоришћеност складишта (бр.1 на слици). Ако је складиште пуно, избришите групе које не користите." 93 | }, 94 | "o_help_syncStorage_2": { 95 | "description": "", 96 | "message": "Постоји и ограничење у величини снимљених података у исто време. То је око 17 - 24 картица." 97 | }, 98 | "o_help_tipsLegend": { 99 | "description": "", 100 | "message": "Савети" 101 | }, 102 | "o_saved": { 103 | "description": "Settings saved OK", 104 | "message": "Сачувано" 105 | }, 106 | "o_savedTabs_cur_win": { 107 | "description": "", 108 | "message": "Само тренутни прозор" 109 | }, 110 | "o_savedTabs_legend": { 111 | "description": "Legend tag", 112 | "message": "Филтер за чување картица (не сесија)" 113 | }, 114 | "o_savedTabs_pinned": { 115 | "description": "", 116 | "message": "Додај закачене картице у групу" 117 | }, 118 | "o_sessionWatcher_toggle": { 119 | "description": "", 120 | "message": "Надгледај сесије. Одчекирајте да бисте онемогућили аутоматско чување сесија. Проширење ће се аутоматски поново покренути!!!" 121 | }, 122 | "o_tabs_about": { 123 | "description": "About tab", 124 | "message": "Информације" 125 | }, 126 | "o_tabs_backup": { 127 | "description": "Tab name", 128 | "message": "Сачувај резервну копију / Врати" 129 | }, 130 | "o_tabs_help": { 131 | "description": "Tab name. Prefix 'o_' for options page", 132 | "message": "Помоћ" 133 | }, 134 | "o_tabs_main": { 135 | "description": "Settings tab", 136 | "message": "Подешавања" 137 | }, 138 | "p_GroupNameToSave": { 139 | "description": "Placeholder in text input", 140 | "message": "Нова група од отворених картица" 141 | }, 142 | "p_addLinkLink_popup": { 143 | "description": "Modal window dialog", 144 | "message": "УРЛ" 145 | }, 146 | "p_addLinkTitle_popup": { 147 | "description": "Modal window dialog", 148 | "message": "Наслов" 149 | }, 150 | "p_addLinkToGroup_btn_title": { 151 | "message": "Додај линк у групу" 152 | }, 153 | "p_currentSession": { 154 | "message": "Тренутна" 155 | }, 156 | "p_delGroup_btn_title": { 157 | "message": "Избриши групу" 158 | }, 159 | "p_delLinkFromGroup_btn_title": { 160 | "message": "Избриши" 161 | }, 162 | "p_downGroup_btn_title": { 163 | "message": "Доле" 164 | }, 165 | "p_editGroupName_btn_title": { 166 | "message": "Уреди име групе" 167 | }, 168 | "p_editLink_btn_title": { 169 | "message": "Уреди" 170 | }, 171 | "p_groupBtn": { 172 | "description": "Btn hint", 173 | "message": "Сачувај" 174 | }, 175 | "p_groupInNewWindow_btn_title": { 176 | "message": "Отвори у новом прозору (Ctrl + клик)" 177 | }, 178 | "p_helpBtn": { 179 | "description": "Title for help button", 180 | "message": "Помоћ" 181 | }, 182 | "p_newGroupName_popup": { 183 | "description": "Modal window dialog", 184 | "message": "Ново име групе:" 185 | }, 186 | "p_optionsBtn": { 187 | "description": "Prefix 'p_' - popup", 188 | "message": "Подешавања" 189 | }, 190 | "p_overwriteGroup_popup": { 191 | "description": "Modal window dialog", 192 | "message": "Група са истим именом већ постоји. Да ли желите да замените стару са тренутним картицама?" 193 | }, 194 | "p_quotaBitesPerItem_error": { 195 | "message": "Не може се сачувати у складишту синхронизације. Има превише картица. Покушајте са брисањем једне или више њих." 196 | }, 197 | "p_quotaBites_error": { 198 | "message": "Скаладиште синхронизације је пуно. Избриши некоришћене групе." 199 | }, 200 | "p_synStorageUsage_title": { 201 | "description": "", 202 | "message": "Искоришћеност складишта синхронизације прегледача" 203 | }, 204 | "p_syncStorageDefault_error": { 205 | "message": "Грешка при синхронизовању складишта. Покушајте поново касније." 206 | }, 207 | "p_tabName_Saved": { 208 | "description": "", 209 | "message": "Сачуване групе" 210 | }, 211 | "p_tabName_Sessions": { 212 | "description": "", 213 | "message": "Сесије" 214 | }, 215 | "p_tabsNumbers_text": { 216 | "message": "Картица:" 217 | }, 218 | "p_upGroup_btn_title": { 219 | "message": "Горе" 220 | }, 221 | "p_window_text": { 222 | "message": "Прозор" 223 | }, 224 | "p_windowsNumbers_text": { 225 | "message": "Прозора:" 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /_locales/uk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extName": { 3 | "message": "TabHamster", 4 | "description": "Ім'я розширення. Не перекладати" 5 | }, 6 | "extDescr": { 7 | "message": "Зберігай відкриті вкладки, керуй сесіями браузера", 8 | "description": "Короткий опис розширення" 9 | }, 10 | 11 | "p_optionsBtn": { 12 | "message": "Налаштування", 13 | "description": "Підказка для кнопки. Префікс 'p_' вказує, що текст відноситься до popup" 14 | }, 15 | "p_helpBtn": { 16 | "message": "Допомога", 17 | "description": "Підказка для кнопки" 18 | }, 19 | "p_GroupNameToSave": { 20 | "message": "Нова група з відкритих вкладок", 21 | "description": "Placeholder в текстовому полі для нового набору" 22 | }, 23 | "p_groupBtn": { 24 | "message": "Зберегти відкриті в групу", 25 | "description": "Підказка для кнопки." 26 | }, 27 | "p_tabName_Saved": { 28 | "message": "Збережені", 29 | "description": "Ім'я таба для збережених груп вкладок" 30 | }, 31 | "p_tabName_Sessions": { 32 | "message": "Сесії", 33 | "description": "Ім'я таба для збережених сесій" 34 | }, 35 | "p_synStorageUsage_title": { 36 | "message": "Відсоток використання сховища Chrome", 37 | "description": "Хінт для показу зайнятого місця в сховищі" 38 | }, 39 | 40 | "p_newGroupName_popup": { 41 | "message": "Нове ім'я групи:", 42 | "description": "Модальне вікно для редагування імені групи вкладок" 43 | }, 44 | "p_addLinkTitle_popup": { 45 | "message": "Назва", 46 | "description": "Модальне вікно для редагування імені групи вкладок" 47 | }, 48 | "p_addLinkLink_popup": { 49 | "message": "URL", 50 | "description": "Модальне вікно для редагування імені групи вкладок" 51 | }, 52 | "p_overwriteGroup_popup": { 53 | "message": "Група з таким ім'ям вже існує. Переписати вже існуючу групу поточним набором вкладок?", 54 | "description": "Модальне вікно " 55 | }, 56 | "p_delGroup_btn_title": { 57 | "message": "Видалити групу" 58 | }, 59 | "p_editGroupName_btn_title": { 60 | "message": "Змінити ім'я групи" 61 | }, 62 | "p_groupInNewWindow_btn_title": { 63 | "message": "Відкрити в новому вікні (Ctrl + клік)" 64 | }, 65 | "p_addLinkToGroup_btn_title": { 66 | "message": "Додати посилання в групу" 67 | }, 68 | "p_delLinkFromGroup_btn_title": { 69 | "message": "Видалити з групи" 70 | }, 71 | "p_editLink_btn_title": { 72 | "message": "Редагувати" 73 | }, 74 | "p_upGroup_btn_title": { 75 | "message": "Вгору" 76 | }, 77 | "p_downGroup_btn_title": { 78 | "message": "Вниз" 79 | }, 80 | "p_windowsNumbers_text": { 81 | "message": "Вікон:" 82 | }, 83 | "p_tabsNumbers_text": { 84 | "message": "Табів:" 85 | }, 86 | "p_window_text": { 87 | "message": "Вікно" 88 | }, 89 | "p_quotaBitesPerItem_error": { 90 | "message": "Забагато табів для запису в сховище. Закрийте декілька та спробуйте знову." 91 | }, 92 | "p_quotaBites_error": { 93 | "message": "Сховище заповнене. Видаліть групи, які не використовуються." 94 | }, 95 | "p_syncStorageDefault_error": { 96 | "message": "Помилка під час роботи з сховищем. Спробуйте пізніше." 97 | }, 98 | "p_currentSession": { 99 | "message": "Поточна" 100 | }, 101 | 102 | 103 | "o_tabs_help": { 104 | "message": "Допомога", 105 | "description": "Підказка для кнопки. Префікс 'o_' вказує, що текст відноситься до options.html" 106 | }, 107 | "o_tabs_backup": { 108 | "message": "Резерв / Відновлення", 109 | "description": "Ім'я вкладки" 110 | }, 111 | "o_tabs_main": { 112 | "message": "Налаштування", 113 | "description": "Ім'я вкладки" 114 | }, 115 | "o_tabs_about": { 116 | "message": "Про розширення", 117 | "description": "Ім'я вкладки" 118 | }, 119 | "o_error": { 120 | "message": "Помилка під час збереження налаштувань", 121 | "description": "Повідомлення про помилку під час збереження налаштувань в storage" 122 | }, 123 | "o_saved": { 124 | "message": "Зміни збережені", 125 | "description": "Повідомлення про успішне збереження настройок" 126 | }, 127 | "o_savedTabs_legend": { 128 | "message": "Фільтр для вкладок що зберігаються (На сесії не поширюється)", 129 | "description": "Legend tag" 130 | }, 131 | "o_savedTabs_pinned": { 132 | "message": "Додавати закріплені вкладки (Pinned tabs) в набір", 133 | "description": "" 134 | }, 135 | "o_savedTabs_cur_win": { 136 | "message": "Зберігати в набір тільки вкладки з поточного вікна", 137 | "description": "" 138 | }, 139 | "o_common_legend": { 140 | "message": "Загальні", 141 | "description": "" 142 | }, 143 | "o_dateFormat_text": { 144 | "message": "Формат дати", 145 | "description": "" 146 | }, 147 | "o_dateFormat_eu": { 148 | "message": "ДД-MM-РРРР", 149 | "description": "EU date format" 150 | }, 151 | "o_dateFormat_us": { 152 | "message": "РРРР-MM-ДД", 153 | "description": "US date format" 154 | }, 155 | "o_help_syncStorage": { 156 | "message": "Групи табів зберігаються у сховище браузера. Це сховище синхронізується. Всі збережені вкладки будуть доступні, наприклад, дома і на роботі. За умови, якщо вхід у браузер виконаний з вашим аккаунтом.", 157 | "description": "" 158 | }, 159 | "o_help_syncStorage_0": { 160 | "message": "Сесії не синхронізуються.", 161 | "description": "" 162 | }, 163 | "o_help_syncStorage_1": { 164 | "message": "Сховище для синхронізації має обмежений розмір. Відсоток його використання, можна побачити біля вкладки (№1 на скріншоті). В більшості випадків цього розміру буде достатньо. Якщо сховище буде переповнене, то можна видалити групи, що не використовуються.", 165 | "description": "" 166 | }, 167 | "o_help_syncStorage_2": { 168 | "message": "Також є обмеження на розмір даних що записуються за один раз. Це приблизно 17 - 24 вкладок (табів).", 169 | "description": "" 170 | }, 171 | "o_help_tipsLegend": { 172 | "message": "Поради та підказки", 173 | "description": "" 174 | }, 175 | "o_help_quickAdd": { 176 | "message": "Щоб швидко зберегти всі відкриті таби в групу, відкрий вікно розширення та натисни клавішу 'ENTER'. Нова група буде мати ім'я з поточних дати та часу. (Пізніше можна буде відредагувати)", 177 | "description": "" 178 | }, 179 | "o_help_showLinks": { 180 | "message": "Щоб продивитись збережені в групі таби клацни на сірий трикутник біля ім'я групи (№2 на скріншоті)", 181 | "description": "" 182 | }, 183 | "o_help_inNewWindow": { 184 | "message": "'Ctrl+клік' на імені збереженої групи (або сесії) відкриє її в новому вікні браузера. Або клік на іконці біля ім'я групи (№3 на скріншоті)", 185 | "description": "" 186 | }, 187 | "o_help_editGroup": { 188 | "message": "Збережені групи можна редагувати, видаляти, змінювати їх позицію.(№4 на скріншоті)", 189 | "description": "" 190 | }, 191 | "o_help_overFavicon": { 192 | "message": "Щоб побачити url вкладки, наведіть курсор на його favicon", 193 | "description": "" 194 | }, 195 | "o_help_settings": { 196 | "message": "В налаштуваннях можна вибрати формат дати та змінити фільтри для збереження табів", 197 | "description": "" 198 | }, 199 | "o_sessionWatcher_toggle": { 200 | "message": "Слідкувати за сесіями. Зніміть прапорець, щоб вимкнути автоматичне збереження сесій. Розширення буде автоматично перезавантажене!!!", 201 | "description": "" 202 | }, 203 | 204 | "o_backup_backupLegend": { 205 | "message": "Резерв", 206 | "description": "" 207 | }, 208 | "o_backup_backupInstruction": { 209 | "message": "Скопіюй текст з текстового поля нижче (CTRL+A щоб вибрати весь текст і CTRL+C щоб скопіювати). Потім встав (CTRL+V) і збережи текст в будь-якому файлі. Наприклад, TabHamsterBackup.txt. Використовуй UTF-8 кодування для цього файлу.", 210 | "description": "" 211 | }, 212 | "o_backup_backupInstructionMore": { 213 | "message": "З допомогою цих даних ти будеш мати можливість відновити свої збереженні групи і налаштування. (Дивись інструкцію в полі вище). Відновити сесії неможливо.", 214 | "description": "" 215 | }, 216 | 217 | "o_backup_restoreLegend": { 218 | "message": "Відновлення", 219 | "description": "" 220 | }, 221 | "o_backup_restoreInstruction": { 222 | "message": "Встав попередньо збережені резервні данні в текстове поле нижче. Данні мають бути кодовані в UTF-8. Потім натисни кнопку 'Відновити...' (під текстовим полем). В разі успіху розширення буде автоматично перезавантажене.", 223 | "description": "" 224 | }, 225 | "o_backup_restoreInstructionMore": { 226 | "message": " Увага! Всі збережені на даний момент групи і налаштування будуть знищені. Замість них будуть завантажені дані з резерву.", 227 | "description": "" 228 | }, 229 | "o_backup_restoreButton": { 230 | "message": "Відновити налаштування і збережені групи", 231 | "description": "" 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /build/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [".*", "readme.md", "build"], 3 | "reminder": "", 4 | "minify": { 5 | "dirs": ["../js", "../lib/jsTabs"], 6 | "exclude": ["*.min.js"] 7 | }, 8 | "changelog": { 9 | "filename": "changelog.txt", 10 | "datetimeformat": "%d.%m.%Y %H:%M" 11 | } 12 | } -------------------------------------------------------------------------------- /build/build.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | 3 | ########################################################################## 4 | # Chrome Extensions builder 5 | # This is a part of Chrome Extensions Box 6 | # Read more on GitHub - https://github.com/onikienko/chrome-extensions-box 7 | ########################################################################## 8 | 9 | import os, json, fnmatch, zipfile, datetime, urllib.request, shutil, sys 10 | from zipfile import ZipFile 11 | from urllib.parse import urlencode 12 | from urllib.error import HTTPError, URLError 13 | 14 | CUR_PATH = os.path.abspath(os.curdir) 15 | PRJ_PATH = os.path.abspath(os.pardir) 16 | PRJ_PAR_DIR, PRJ_DIR_NAME = os.path.split(PRJ_PATH) 17 | BUILD_PATH = os.path.join(CUR_PATH, 'releases') 18 | PACKAGE_FILE = os.path.join(CUR_PATH, 'build.json') 19 | MANIFEST_FILE = os.path.join(PRJ_PATH, 'manifest.json') 20 | TMP_DIR = os.path.join(CUR_PATH, 'tmp') 21 | 22 | ARG_NO_MINIFY = '-m' 23 | 24 | exclude_dirs = [] 25 | # TODO collect and print errors after creating a package 26 | # errors = [] 27 | 28 | 29 | def getJSON(json_file): 30 | try: 31 | f = open(json_file, 'rt', encoding='utf-8') 32 | j = json.load(f) 33 | f.close() 34 | return j 35 | except: 36 | print('Error reading', json_file, 'file') 37 | exit() 38 | 39 | 40 | def isNameMatch(name, match_list): 41 | for pt in match_list: 42 | if fnmatch.fnmatch(name, pt): 43 | return True 44 | return False 45 | 46 | 47 | def writeToChangelogFile(): 48 | if changelog['filename']: 49 | print('\nWriting to changelog file ...') 50 | changelog_path = os.path.join(BUILD_PATH, changelog['filename']) 51 | 52 | if changelog['datetimeformat']: 53 | try: 54 | timestamp = ' (' + datetime.datetime.now().strftime(changelog['datetimeformat']) + ')' 55 | except ValueError: 56 | print('Invalid changelog.datetimeformat string in package.json') 57 | timestamp = '' 58 | else: 59 | timestamp = '' 60 | try: 61 | if not os.path.isfile(changelog_path): 62 | f = open(changelog_path, 'w', encoding='utf-8') 63 | f.close() 64 | 65 | with open(changelog_path, 'r+', encoding='utf-8') as f: 66 | lines = f.readlines() 67 | f.seek(0) 68 | f.writelines([build_version + timestamp + '\n'] + lines) 69 | 70 | f.close() 71 | print('Ok\n') 72 | except Exception: 73 | print('Error reading or writing changelog file\n') 74 | 75 | 76 | def minifyJs(file_path, arcname): 77 | print('Start minifying', arcname[1:]) 78 | 79 | try: 80 | f = open(file_path, encoding='utf-8') 81 | data = urllib.parse.urlencode({'js_code': f.read(), 'utf-8': 'utf-8'}) 82 | data = data.encode('utf-8') 83 | req = urllib.request.Request(url='http://marijnhaverbeke.nl/uglifyjs') 84 | minified = urllib.request.urlopen(req, data).read().decode('utf-8') 85 | except (HTTPError, URLError) as error: 86 | print('HTTP or URL error:', error) 87 | print(arcname[1:], 'was not minified') 88 | return file_path 89 | 90 | try: 91 | if not os.path.isdir(TMP_DIR): 92 | os.makedirs(TMP_DIR) 93 | tmp_file_path = os.path.join(TMP_DIR, os.path.basename(file_path)) 94 | tmp_file = open(tmp_file_path, 'w+', encoding='utf-8') 95 | tmp_file.write(minified) 96 | tmp_file.close() 97 | except Exception: 98 | print('Error during the creation temp file') 99 | print(arcname[1:], 'was not minified') 100 | return file_path 101 | 102 | print('Ok. File was minified') 103 | return tmp_file_path 104 | 105 | # get and check manifest version 106 | try: 107 | build_version = getJSON(MANIFEST_FILE)['version'] 108 | except KeyError: 109 | print('Error. Check "manifest.json" file. "version" is not defined') 110 | exit() 111 | if os.path.exists(os.path.join(BUILD_PATH, build_version + '.zip')): 112 | print('Error. Version', build_version, 'already exists') 113 | exit() 114 | 115 | # get package info 116 | try: 117 | package = getJSON(PACKAGE_FILE) 118 | exclude, minify, reminder, changelog = package['exclude'], package['minify'], package['reminder'], package[ 119 | "changelog"] 120 | if ARG_NO_MINIFY not in sys.argv: 121 | for index, dirname in enumerate(minify['dirs']): 122 | minify['dirs'][index] = os.path.abspath(dirname) 123 | else: 124 | minify['dirs'] = [] 125 | except Exception: 126 | print('Error in build.json file') 127 | exit() 128 | 129 | if reminder: 130 | i = input(reminder + ' ' + ' Press "y" (then "ENTER") to continue or just "ENTER" to abort: ') 131 | if i.lower() != 'y': 132 | exit() 133 | 134 | if not os.path.isdir(BUILD_PATH): 135 | os.makedirs(BUILD_PATH) 136 | 137 | zipname = os.path.join(BUILD_PATH, build_version + '.zip') 138 | z = ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED) 139 | print('\nStart building package ', os.path.abspath(zipname), '...\n') 140 | 141 | for root, dirs, files in os.walk(PRJ_PATH): 142 | is_exclude_dir = root.startswith(tuple(exclude_dirs)) 143 | if (isNameMatch(os.path.basename(root), exclude) or is_exclude_dir) and root != PRJ_PATH: 144 | if not is_exclude_dir: 145 | exclude_dirs.append(root) 146 | continue 147 | else: 148 | for name in files: 149 | if not isNameMatch(name, exclude): 150 | fullname = os.path.join(root, name) 151 | arcname = fullname[len(PRJ_PAR_DIR):] 152 | if len(minify['dirs']) > 0 and root.startswith(tuple(minify['dirs'])) and fnmatch.fnmatch(name, 153 | '*.js') and not isNameMatch( 154 | name, 155 | minify[ 156 | 'exclude']): 157 | fullname = minifyJs(fullname, arcname) 158 | z.write(fullname, arcname) 159 | print(arcname[1:], 'was added to the package\n') 160 | z.close() 161 | print('Package was created') 162 | 163 | if os.path.exists(TMP_DIR): 164 | print('\nDeleting temporary files...') 165 | shutil.rmtree(TMP_DIR, ignore_errors=False, onerror=None) 166 | print('Ok') 167 | 168 | writeToChangelogFile() 169 | 170 | print('Done!') 171 | input('\nPress "Enter" to exit') -------------------------------------------------------------------------------- /build/releases/0.1.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.1.1.zip -------------------------------------------------------------------------------- /build/releases/0.1.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.1.3.zip -------------------------------------------------------------------------------- /build/releases/0.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.1.zip -------------------------------------------------------------------------------- /build/releases/0.2.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.1.zip -------------------------------------------------------------------------------- /build/releases/0.2.10.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.10.zip -------------------------------------------------------------------------------- /build/releases/0.2.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.2.zip -------------------------------------------------------------------------------- /build/releases/0.2.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.3.zip -------------------------------------------------------------------------------- /build/releases/0.2.4.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.4.zip -------------------------------------------------------------------------------- /build/releases/0.2.5.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.5.zip -------------------------------------------------------------------------------- /build/releases/0.2.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.6.zip -------------------------------------------------------------------------------- /build/releases/0.2.7.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.7.zip -------------------------------------------------------------------------------- /build/releases/0.2.8.1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.8.1.zip -------------------------------------------------------------------------------- /build/releases/0.2.8.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.8.zip -------------------------------------------------------------------------------- /build/releases/0.2.9.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.9.zip -------------------------------------------------------------------------------- /build/releases/0.2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.2.zip -------------------------------------------------------------------------------- /build/releases/0.3.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases/0.3.0.zip -------------------------------------------------------------------------------- /build/releases/changelog.txt: -------------------------------------------------------------------------------- 1 | 0.3.0 (12.04.2024 16:01) 2 | Manifest v3 support 3 | Fix Session Watcher behavior on the first run 4 | Fix favicon appearance if browser uses dark theme 5 | Add 32px icon 6 | Remove "ru" locale 7 | Remove Opera support 8 | 0.2.10 (08.12.2015 12:32) 9 | Add Serbian translation (thanks to Sijera) 10 | Fix Opera manifest to persistent background page 11 | Open tabs from background page (not from popup) 12 | 0.2.9 (23.09.2015 13:49) 13 | Manual Backup/Restore for saved groups and options (see Options Page). 14 | 0.2.8.1 (16.04.2015 09:29) 15 | Fix error with Group Saving when option 'Current window only' unchecked 16 | 0.2.8 (15.04.2015 10:04) 17 | Save with the same name dialogue 18 | Fix styles 19 | 0.2.7 (10.04.2015 10:54) 20 | Ability to turn off Session Watcher (see Options page) 21 | Fix locales 22 | 0.2.6 (26.11.2014 12:59) 23 | Add French translation (thanks to Aurélien Léger aka Dexyne) 24 | Changed monospaced font in popup to Tahoma, san-serif 25 | 0.2.5 (12.11.2014 17:37) 26 | Fix problem with group and session expanding (thanks to Ricardo J. Barberis aka richarson) 27 | Fix Open In New Window bug 28 | 0.2.4 (02.06.2014 18:39) 29 | Hungarian translation (thanks to Zoltan Mihics aka Med1on) 30 | 0.2.3 (12.02.2014 08:54) 31 | Add Ukrainian translation 32 | Pinned tabs are shown as Pinned in Saved Groups 33 | Pinned tabs open as Pinned 34 | 0.2.2 (31.01.2014 16:51) 35 | Fix error messages 36 | Minimize tab title to 49 chars 37 | 0.2.1 (15.10.2013 19:31) 38 | Fix error with edit link which starts with 'file://' or 'chrome-devtools://' 39 | 0.2 (14.10.2013 16:02) 40 | Spanish translation. Thanks to Martin Irigaray. 41 | Fix error with edit file:// link 42 | 0.1.3 (25.09.2013 18:43) 43 | 0.1.2 (25.09.2013 18:40) 44 | Fix error with pinned tabs 45 | Icon for pinned tabs 46 | 0.1.1 (23.09.2013 22:19) 47 | Upd ru/messages.json 48 | 0.1 (23.09.2013 22:07) 49 | Initial release 50 | -------------------------------------------------------------------------------- /build/releases_opera/0.1.1.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.1.1.nex -------------------------------------------------------------------------------- /build/releases_opera/0.1.2.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.1.2.nex -------------------------------------------------------------------------------- /build/releases_opera/0.1.3.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.1.3.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.1.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.1.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.10.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.10.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.2.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.2.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.3.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.3.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.4.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.4.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.5.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.5.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.6.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.6.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.7.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.7.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.8.1.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.8.1.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.8.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.8.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.9.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.9.nex -------------------------------------------------------------------------------- /build/releases_opera/0.2.nex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/build/releases_opera/0.2.nex -------------------------------------------------------------------------------- /css/options.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #efefef; 3 | font-family: Arial, sans-serif; 4 | font-size: 15px; 5 | color: #393939; 6 | } 7 | 8 | a { 9 | color: #0099CC; 10 | } 11 | 12 | hr { 13 | border: none; 14 | background-color: #e5e5e5; 15 | height: 1px; 16 | margin: 15px 0; 17 | } 18 | 19 | #message_place { 20 | position: fixed; 21 | width: 100%; 22 | top: 0; 23 | text-align: center; 24 | font-size: 17px; 25 | font-weight: 500; 26 | margin-top: 10px; 27 | color: #ffffff; 28 | } 29 | 30 | #message_place span { 31 | padding: 12px 48px; 32 | border-bottom-left-radius: 4px; 33 | border-bottom-right-radius: 4px; 34 | box-shadow: 0 2px 10px #a9a9a9; 35 | display: none; 36 | } 37 | 38 | #error { 39 | background-color: #f0a9a5; 40 | } 41 | 42 | #success { 43 | background-color: #7bd743; 44 | } 45 | 46 | #page { 47 | margin: 20px auto; 48 | width: 800px; 49 | background-color: #ffffff; 50 | border: 1px solid #dddddd; 51 | border-radius: 3px; 52 | box-shadow: 0 3px 12px #a9a9a9; 53 | } 54 | 55 | header { 56 | padding: 20px; 57 | font-size: 18px; 58 | } 59 | 60 | header img { 61 | margin-right: 12px; 62 | vertical-align: middle; 63 | } 64 | 65 | footer { 66 | border-top: 1px solid #dddddd; 67 | padding: 0 20px; 68 | } 69 | 70 | .copyright { 71 | text-align: right; 72 | font-size: 11px; 73 | color: #a9a9a9; 74 | } 75 | 76 | dl dt { 77 | float: left; 78 | font-weight: bold; 79 | margin-right: 10px; 80 | padding: 5px; 81 | width: 100px; 82 | } 83 | 84 | dl dd { 85 | margin: 2px 0; 86 | padding: 5px 0; 87 | } 88 | 89 | fieldset { 90 | border: 1px solid #dddddd; 91 | border-radius: 3px; 92 | margin-bottom: 20px; 93 | } 94 | 95 | #help li { 96 | margin-bottom: 10px; 97 | } 98 | 99 | .help_screenshot { 100 | text-align: center; 101 | margin-bottom: 20px; 102 | } 103 | 104 | #ua_flag { 105 | height: 20px; 106 | background: url("../img/ua_1px.png") 0 50% repeat-x; 107 | } 108 | 109 | textarea { 110 | width: 100%; 111 | /*margin: 0 auto;*/ 112 | } 113 | 114 | #restore_btn { 115 | margin: 15px auto; 116 | padding: 10px; 117 | text-align: center; 118 | font-weight: bold; 119 | } 120 | -------------------------------------------------------------------------------- /css/popup.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 450px; 3 | overflow-x: hidden; 4 | font-family: Tahoma, sans-serif; 5 | font-size: 13px; 6 | color: #4a4a4a; 7 | margin-top: 50px; 8 | } 9 | 10 | header { 11 | font-size: 15px; 12 | background-color: #33B5E5; 13 | color: #ffffff; 14 | padding: 8px 16px; 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | } 20 | 21 | header input { 22 | outline: none; 23 | padding-left: 7px; 24 | width: 220px; 25 | } 26 | 27 | #window_actions { 28 | float: right; 29 | } 30 | 31 | header a { 32 | margin: 0 0 0 6px; 33 | text-decoration: none; 34 | color: #ffffff; 35 | font-size: 18px; 36 | } 37 | 38 | /* override jsTabs styles */ 39 | ul.tabs_nav { 40 | margin-left: 10px; 41 | } 42 | 43 | .tabs_nav a { 44 | letter-spacing: normal; 45 | font-weight: normal; 46 | } 47 | 48 | .tabs_content { 49 | padding: 8px; 50 | } 51 | 52 | #error_msg { 53 | display: none; 54 | background-color: #FF4444; 55 | color: #ffffff; 56 | padding: 12px; 57 | margin: 5px 0; 58 | } 59 | 60 | #sync_storage_usage { 61 | float: right; 62 | padding: 0; 63 | margin: 5px 0 0 0; 64 | } 65 | 66 | .group { 67 | font-size: 14px; 68 | padding: 8px 3px; 69 | border-bottom: 1px solid #E2F4FB; 70 | 71 | } 72 | 73 | .open_group { 74 | margin-left: 8px; 75 | cursor: pointer; 76 | color: #0099CC; 77 | } 78 | 79 | .group_action { 80 | float: right; 81 | } 82 | 83 | .spoiler, .group_action > span, .link_action > span, .open_in_new_window { 84 | margin-left: 5px; 85 | cursor: pointer; 86 | color: lightgrey; 87 | } 88 | 89 | .spoiler:hover, .group_action > span:hover, .link_action > span:hover, .up:hover, .down:hover, .open_in_new_window:hover { 90 | color: #4a4a4a; 91 | } 92 | 93 | .link { 94 | font-size: 13px; 95 | margin-top: 8px; 96 | } 97 | 98 | .link a { 99 | color: #4a4a4a; 100 | text-decoration: none; 101 | } 102 | 103 | .link a:hover { 104 | color: #4a4a4a; 105 | text-decoration: underline; 106 | } 107 | 108 | .numbers_of_windows, .numbers_of_tabs { 109 | font-size: 13px; 110 | margin-left: 8px; 111 | color: lightgrey; 112 | } 113 | 114 | .win { 115 | padding: 8px; 116 | margin: 0 0 0 15px; 117 | } 118 | 119 | .win_title { 120 | font-size: 14px; 121 | color: #0099CC; 122 | border-bottom: 1px solid #E2F4FB; 123 | cursor: pointer; 124 | } 125 | 126 | .favi { 127 | display: inline-flex; 128 | margin: 0 8px; 129 | border-radius: 8px; 130 | background-color: lightgray; 131 | height: 16px; 132 | width: 16px; 133 | vertical-align: text-bottom; 134 | } 135 | 136 | .pinned_icon { 137 | width: 12px; 138 | height: 12px; 139 | margin-right: 3px; 140 | vertical-align: middle; 141 | } 142 | 143 | /* popup form */ 144 | .popup__overlay { 145 | display: none; 146 | position: fixed; 147 | left: 0; 148 | top: 0; 149 | width: 100%; 150 | height: 100%; 151 | text-align: center; 152 | background-color: rgba(0, 0, 0, 0.5); 153 | } 154 | 155 | .popup__overlay:after { 156 | display: inline-block; 157 | height: 100%; 158 | width: 0; 159 | vertical-align: middle; 160 | content: ''; 161 | } 162 | 163 | .popup { 164 | display: inline-block; 165 | position: relative; 166 | max-width: 85%; 167 | padding: 15px; 168 | border: 1px solid #606060; 169 | border-radius: 4px; 170 | background: #fdfdfd; 171 | vertical-align: middle; 172 | } 173 | 174 | .popup-form__row { 175 | margin: 1em 0; 176 | } 177 | 178 | label { 179 | display: inline-block; 180 | width: 120px; 181 | text-align: left; 182 | } 183 | 184 | input[type="text"] { 185 | width: 220px; 186 | margin: 0; 187 | padding: 2px; 188 | border: 1px solid; 189 | border-color: #999 #ccc #ccc; 190 | border-radius: 2px; 191 | } 192 | 193 | input[type="button"] { 194 | padding: 6px 16px; 195 | border: 0; 196 | border-radius: 2px; 197 | cursor: pointer; 198 | background: #33B5E5; 199 | color: #fff; 200 | } 201 | 202 | input[type="button"].cancel { 203 | background: #cccccc; 204 | } 205 | 206 | .popup__close:hover { 207 | color: red; 208 | background: #ddd; 209 | } 210 | 211 | /* popup form */ 212 | -------------------------------------------------------------------------------- /img/ext_icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/ext_icons/128.png -------------------------------------------------------------------------------- /img/ext_icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/ext_icons/16.png -------------------------------------------------------------------------------- /img/ext_icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/ext_icons/32.png -------------------------------------------------------------------------------- /img/ext_icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/ext_icons/48.png -------------------------------------------------------------------------------- /img/help_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/help_screen.png -------------------------------------------------------------------------------- /img/pin-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/pin-26.png -------------------------------------------------------------------------------- /img/ua_1px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onikienko/TabHamster/958b81de20f39d9765d68fddc54c554d87c0f194/img/ua_1px.png -------------------------------------------------------------------------------- /js/background.js: -------------------------------------------------------------------------------- 1 | // it's copied from ./storage.js file 2 | // had to copy/past here to this code works with mv3 service worker 3 | var storage = { 4 | area: chrome.storage.sync, 5 | default_options: { 6 | date_format: 'eu', // or 'us' 7 | pinned: 0, //do not add pinned tabs 8 | cur_win: 1, // save tabs from current window only 9 | active_tab: '#saved', //or '#sessions' 10 | session_watcher: 1, //extension will watch sessions 11 | }, 12 | }; 13 | 14 | const session_config = { 15 | session_numbers: 30, 16 | prefix: 'tg_', 17 | }; 18 | 19 | chrome.runtime.onInstalled.addListener(function (details) { 20 | 21 | // good place to set default options 22 | function setDefaults(callback) { 23 | storage.area.get(function (stored_options) { 24 | var default_options = storage.default_options, 25 | option, 26 | new_options = {}; 27 | 28 | for (option in default_options) { 29 | if (!stored_options.hasOwnProperty(option)) { 30 | new_options[option] = default_options[option]; 31 | } 32 | } 33 | if (Object.keys(new_options).length !== 0) { 34 | // save to area if new default options is appeared 35 | storage.area.set(new_options, function () { 36 | if (typeof callback === 'function') { 37 | callback(); 38 | } 39 | }); 40 | } else { 41 | if (typeof callback === 'function') { 42 | callback(); 43 | } 44 | } 45 | }); 46 | } 47 | 48 | switch (details.reason) { 49 | case 'install': // if ext is first installed 50 | setDefaults(function () { 51 | // show options page 52 | chrome.tabs.create({'url': 'options.html#help'}); 53 | }); 54 | break; 55 | case 'update': 56 | setDefaults(); 57 | break; 58 | default: 59 | break; 60 | } 61 | }); 62 | 63 | chrome.runtime.onUpdateAvailable.addListener(function (details) { 64 | chrome.runtime.reload(); 65 | }); 66 | 67 | chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { 68 | switch (msg.msg) { 69 | case 'openLinksInNewWindow': 70 | chrome.windows.create({'type': 'normal', 'focused': true}, function (win) { 71 | msg.links.forEach(function (el) { 72 | chrome.tabs.create({'windowId': win.id, 'url': el.url, 'pinned': el.pinned}); 73 | }); 74 | chrome.tabs.remove(win.tabs[0].id); // remove default New Tab Page 75 | //sendResponse({msg: 'OK'}); 76 | //window.close(); 77 | }); 78 | break; 79 | case 'openLinksInCurrentWindow': 80 | msg.links.forEach(function (el) { 81 | chrome.tabs.create({'url': el.url, 'pinned': el.pinned}); 82 | }); 83 | break; 84 | case 'openWindowLinksInCurrentWindow': 85 | msg.links.forEach(function (el) { 86 | if (el.windowId === parseInt(msg.window_id, 10)) { 87 | chrome.tabs.create({'url': el.url, 'pinned': el.pinned}); 88 | } 89 | }); 90 | break; 91 | } 92 | }); 93 | 94 | /*SESSION WATCHER*/ 95 | 96 | // remove old sessions from chrome.storage.local (if sessions numbers >= config.session_numbers) 97 | function cleanupSessions() { 98 | chrome.storage.local.get(function (items) { 99 | var key = (function () { 100 | var item, 101 | id; 102 | for (item in items) { 103 | if (item.indexOf(session_config.prefix) === 0) { 104 | id = item; 105 | break; 106 | } 107 | } 108 | return id ? id.slice(session_config.prefix.length) : id; 109 | }()), 110 | items_keys = Object.keys(items); 111 | 112 | if (key && items_keys.length >= session_config.session_numbers) { 113 | items_keys.forEach(function (el) { 114 | var el_id = el.slice(session_config.prefix.length); 115 | key = el_id < key ? el_id : key; 116 | }); 117 | chrome.storage.local.remove(session_config.prefix + key); 118 | } 119 | }); 120 | } 121 | 122 | async function updateCurrentSession() { 123 | const session_storage = await chrome.storage.session.get('session_id'); 124 | let session_id = session_storage.session_id; 125 | if (!session_id) { 126 | session_id = session_config.prefix + (new Date()).getTime(); 127 | await chrome.storage.session.set({session_id}); 128 | 129 | cleanupSessions(); 130 | } 131 | chrome.tabs.query({}, function (tabs) { 132 | const session = { 133 | name: '', 134 | tabs: [], 135 | }; 136 | const obj = {}; 137 | tabs.forEach(function (tab) { 138 | const obj = {}; 139 | if (!tab.url.startsWith('chrome-devtools://')) { 140 | obj.pinned = tab.pinned; 141 | obj.title = tab.title; 142 | obj.url = tab.url; 143 | obj.windowId = tab.windowId; 144 | session.tabs.push(obj); 145 | } 146 | }); 147 | 148 | obj[session_id] = session; 149 | chrome.storage.local.set(obj); 150 | }); 151 | } 152 | 153 | function onUpdated(tab_id, change_info) { 154 | if (change_info.url) { 155 | updateCurrentSession(); 156 | } 157 | } 158 | 159 | chrome.tabs.onUpdated.addListener(onUpdated); 160 | chrome.tabs.onMoved.addListener(updateCurrentSession); 161 | chrome.tabs.onAttached.addListener(updateCurrentSession); 162 | chrome.tabs.onRemoved.addListener(updateCurrentSession); 163 | chrome.tabs.onReplaced.addListener(updateCurrentSession); 164 | 165 | storage.area.get({session_watcher: storage.default_options.session_watcher}, async function (from_storage) { 166 | if (!from_storage.session_watcher) { 167 | chrome.tabs.onUpdated.hasListener(onUpdated) && chrome.tabs.onUpdated.removeListener(onUpdated); 168 | chrome.tabs.onMoved.hasListener(updateCurrentSession) && chrome.tabs.onMoved.removeListener(updateCurrentSession); 169 | chrome.tabs.onAttached.hasListener(updateCurrentSession) && chrome.tabs.onAttached.removeListener(updateCurrentSession); 170 | chrome.tabs.onRemoved.hasListener(updateCurrentSession) && chrome.tabs.onRemoved.removeListener(updateCurrentSession); 171 | chrome.tabs.onReplaced.hasListener(updateCurrentSession) && chrome.tabs.onReplaced.removeListener(updateCurrentSession); 172 | } else { 173 | const session_storage = await chrome.storage.session.get('session_id'); 174 | if (!session_storage.session_id) { 175 | updateCurrentSession(); 176 | } 177 | } 178 | }); 179 | -------------------------------------------------------------------------------- /js/backup.js: -------------------------------------------------------------------------------- 1 | var backup = { 2 | storage_area: chrome.storage.sync, 3 | 4 | getStoredKeys: function (callback) { 5 | var stored_keys = []; 6 | 7 | this.storage_area.get(function (storage_items) { 8 | var storage_key_name; 9 | 10 | // collect saved groups and extension options keys 11 | for (storage_key_name in storage_items) { 12 | if (storage_key_name.indexOf('tg_') === 0 || storage.default_options.hasOwnProperty(storage_key_name)) { 13 | stored_keys.push(storage_key_name); 14 | } 15 | } 16 | callback(stored_keys); 17 | }); 18 | }, 19 | 20 | getBackupData: function (callback) { 21 | var self = this; 22 | 23 | this.getStoredKeys(function (stored_keys) { 24 | self.storage_area.get(stored_keys, function (storage_items) { 25 | callback(JSON.stringify(storage_items)); 26 | }); 27 | }); 28 | }, 29 | 30 | cleanupBeforeReplace: function (callback) { 31 | var self = this; 32 | 33 | this.getStoredKeys(function (stored_keys) { 34 | self.storage_area.remove(stored_keys, function () { 35 | callback(chrome.runtime.lastError); 36 | }); 37 | }); 38 | }, 39 | 40 | replaceFromBackup: function (backup_data, callback) { 41 | var backup, 42 | storage_area = this.storage_area; 43 | 44 | try { 45 | backup = JSON.parse(backup_data); 46 | } catch(e) { 47 | callback('error'); 48 | } 49 | 50 | if (backup && typeof backup === 'object') { 51 | this.cleanupBeforeReplace(function (cleanup_error) { 52 | if (!cleanup_error) { 53 | storage_area.set(backup, function () { 54 | callback(chrome.runtime.lastError); 55 | }); 56 | } else { 57 | callback(cleanup_error); 58 | } 59 | }); 60 | } 61 | } 62 | }; -------------------------------------------------------------------------------- /js/helpers/localizer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a part of Chrome Extensions Box 3 | * Read more on GitHub - https://github.com/onikienko/chrome-extensions-box 4 | * 5 | * Parse html and replace all {{property name from message.json}} from text nodes, title, alt, value and placeholder attrs 6 | * with chrome.i18n.getMessage http://developer.chrome.com/extensions/i18n.html 7 | */ 8 | window.addEventListener('load', function () { 9 | function translator(html) { 10 | var i, 11 | length, 12 | attrs_to_check = ['title', 'alt', 'placeholder', 'value']; 13 | 14 | function replacer(text) { 15 | return text.replace(/\{\{([\s\S]*?)\}\}/gm, function (str, g1) { 16 | return chrome.i18n.getMessage(g1.trim()) || str; 17 | }); 18 | } 19 | 20 | if (html.attributes) { 21 | attrs_to_check.forEach(function (el) { 22 | if (html.attributes[el]) { 23 | html.attributes[el].value = replacer(html.attributes[el].value); 24 | } 25 | }); 26 | } 27 | 28 | if (html.nodeType === 3) { //text node 29 | html.data = replacer(html.data); 30 | } else { 31 | for (i = 0, length = html.childNodes.length; i < length; i++) { 32 | translator(html.childNodes[i]); 33 | } 34 | } 35 | } 36 | 37 | translator(document.body); 38 | translator(document.head); 39 | }); -------------------------------------------------------------------------------- /js/helpers/quick_options.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a part of Chrome Extensions Box 3 | * Read more on GitHub - https://github.com/onikienko/chrome-extensions-box 4 | */ 5 | 6 | /* 7 | Modified Chrome Extensions Box 8 | Make showMessage global 9 | */ 10 | var quick_options = { 11 | showMessage: function (msg) { 12 | var el = document.getElementById(msg === 'error' ? 'error' : 'success'); 13 | el.style.display = 'inline'; 14 | setTimeout(function () { 15 | el.style.display = 'none'; 16 | }, 2501); 17 | } 18 | }; 19 | /* 20 | end of modifications 21 | */ 22 | 23 | window.addEventListener('load', function () { 24 | /*This event will dispatch as soon as options page will be ready*/ 25 | var event = new CustomEvent('optionsPageReady'); 26 | 27 | function saveToStorage(val) { 28 | storage.area.set(val, function () { 29 | quick_options.showMessage(chrome.runtime.lastError ? quick_options.showMessage('error') : quick_options.showMessage('success')); 30 | // dispatch custom event 31 | document.dispatchEvent(new CustomEvent('optionSaved', { 32 | detail: { 33 | success: chrome.runtime.lastError ? false : true, 34 | val: val 35 | } 36 | })); 37 | }); 38 | } 39 | 40 | storage.area.get(storage.default_options, function (items) { 41 | var inputs = document.querySelectorAll('input'), 42 | selects = document.querySelectorAll('select'), 43 | textareas = document.querySelectorAll('textarea'); 44 | 45 | [].forEach.call(inputs, function (el) { 46 | var storage_name = el.getAttribute('data-storage'), 47 | text_el; 48 | if (storage_name && items.hasOwnProperty(storage_name)) { 49 | switch (el.type) { 50 | case 'checkbox': 51 | if (parseInt(items[storage_name], 10) === 1) { 52 | el.checked = true; 53 | } 54 | el.addEventListener('change', function () { 55 | var val = {}; 56 | items[storage_name] = el.checked ? 1 : 0; 57 | val[storage_name] = items[storage_name]; 58 | saveToStorage(val); 59 | }, false); 60 | break; 61 | 62 | case 'radio': 63 | if (el.value === items[storage_name].toString()) { 64 | el.checked = 'checked'; 65 | } 66 | el.addEventListener('change', function () { 67 | var val = {}; 68 | if (el.checked) { 69 | items[storage_name] = el.value; 70 | val[storage_name] = items[storage_name]; 71 | saveToStorage(val); 72 | } 73 | }, false); 74 | break; 75 | 76 | case 'range': 77 | case 'color': 78 | case 'date': 79 | case 'time': 80 | case 'month': 81 | case 'week': 82 | el.value = items[storage_name]; 83 | el.addEventListener('change', function () { 84 | var val = {}; 85 | items[storage_name] = el.value; 86 | val[storage_name] = items[storage_name]; 87 | saveToStorage(val); 88 | }, false); 89 | break; 90 | 91 | case 'text': 92 | case 'password': 93 | case 'email': 94 | case 'tel': 95 | case 'number': 96 | el.value = items[storage_name]; 97 | break; 98 | 99 | case 'submit': 100 | case 'button': 101 | [].forEach.call(document.querySelectorAll('[data-storage="' + storage_name + '"]'), function (elem) { 102 | if (elem.type !== 'submit' && elem.type !== 'button') { 103 | text_el = elem; 104 | } 105 | }); 106 | if (text_el) { 107 | el.addEventListener('click', function () { 108 | var val = {}; 109 | items[storage_name] = text_el.value; 110 | val[storage_name] = items[storage_name]; 111 | saveToStorage(val); 112 | }, false); 113 | } 114 | break; 115 | 116 | } 117 | } 118 | }); 119 | 120 | [].forEach.call(textareas, function (el) { 121 | var storage_name = el.getAttribute('data-storage'); 122 | if (storage_name && items.hasOwnProperty(storage_name)) { 123 | el.value = items[storage_name]; 124 | } 125 | }); 126 | 127 | [].forEach.call(selects, function (el) { 128 | var storage_name = el.getAttribute('data-storage'), 129 | options, 130 | i; 131 | if (storage_name && items.hasOwnProperty(storage_name)) { 132 | if ((el.multiple && Array.isArray(items[storage_name])) || !el.multiple) { 133 | options = el.options; 134 | for (i = options.length; i--;) { 135 | if (el.multiple) { 136 | if (items[storage_name].indexOf(options[i].value) !== -1) { 137 | options[i].selected = 'selected'; 138 | } 139 | } else { 140 | if (options[i].value === items[storage_name]) { 141 | options[i].selected = 'selected'; 142 | break; 143 | } 144 | } 145 | } 146 | el.addEventListener('change', function () { 147 | var val = {}, 148 | array_of_selected = [], 149 | i; 150 | if (el.multiple) { 151 | for (i = options.length; i--;) { 152 | if (options[i].selected) { 153 | array_of_selected.push(options[i].value); 154 | } 155 | } 156 | items[storage_name] = array_of_selected; 157 | } else { 158 | items[storage_name] = options[options.selectedIndex].value; 159 | } 160 | val[storage_name] = items[storage_name]; 161 | saveToStorage(val); 162 | }, false); 163 | } 164 | } 165 | }); 166 | 167 | /* Options page is ready. Dispatch event */ 168 | document.dispatchEvent(event); 169 | 170 | }); 171 | }, false); 172 | -------------------------------------------------------------------------------- /js/options.js: -------------------------------------------------------------------------------- 1 | var main_tabs = new Tabs('#main_tabs'); 2 | 3 | document.addEventListener('optionSaved', function (event) { 4 | // reload extension after toggle session watcher option 5 | if (event.detail.val.hasOwnProperty('session_watcher') && event.detail.success) { 6 | chrome.runtime.reload(); 7 | } 8 | }, false); 9 | 10 | document.addEventListener('optionsPageReady', function () { 11 | var restore_btn = document.getElementById('restore_btn'); 12 | 13 | backup.getBackupData(function (data) { 14 | document.getElementById('backup_text').value = data; 15 | }); 16 | 17 | restore_btn.onclick = function () { 18 | backup.replaceFromBackup(document.getElementById('restore_text').value, function (error) { 19 | if (error) { 20 | quick_options.showMessage('error'); 21 | } else { 22 | chrome.runtime.reload(); 23 | } 24 | }) 25 | } 26 | }, false); -------------------------------------------------------------------------------- /js/popup.js: -------------------------------------------------------------------------------- 1 | function faviconURL(u) { 2 | const url = new URL(chrome.runtime.getURL('/_favicon/')); 3 | url.searchParams.set('pageUrl', u); 4 | url.searchParams.set('size', '32'); 5 | return url.toString(); 6 | } 7 | 8 | chrome.storage.session.get(function (session_storage_items) { 9 | chrome.storage.local.get(function (session_items) { 10 | chrome.storage.sync.get(function (storage_items) { 11 | /** 12 | * Popup window for edit operation 13 | * @param popup_id 14 | * @param callback {function} fired on submit. 15 | */ 16 | function Popup(popup_id, callback) { 17 | this.popup_container = document.querySelector('#' + popup_id); 18 | this.callback = callback; 19 | this.go(); 20 | } 21 | 22 | Popup.prototype = { 23 | go: function () { 24 | var self = this, 25 | data_container = self.popup_container.querySelectorAll('.data'), 26 | submit_btn = this.popup_container.querySelector('.submit_btn'); 27 | 28 | function submitData() { 29 | var data = {}; 30 | [].forEach.call(data_container, function (el) { 31 | data[el.id] = el.value; 32 | }); 33 | self.popup_container.style.display = 'none'; 34 | self.callback(data); 35 | } 36 | 37 | this.popup_container.style.display = 'block'; 38 | if (data_container.length) { 39 | data_container[0].focus(); 40 | data_container[0].select(); 41 | // submit on 'enter' for last text input 42 | if (data_container[data_container.length - 1].getAttribute('type') === 'text') { 43 | data_container[data_container.length - 1].onkeyup = function (e) { 44 | if (e.keyCode === 13) { 45 | submitData(); 46 | } 47 | }; 48 | } 49 | } else { 50 | submit_btn.focus(); 51 | this.popup_container.onkeyup = function (e) { 52 | if (e.keyCode === 13) { 53 | submitData(); 54 | } 55 | }; 56 | } 57 | 58 | submit_btn.onclick = function (e) { 59 | submitData(); 60 | }; 61 | 62 | this.popup_container.querySelector('.cancel').onclick = function (e) { 63 | self.popup_container.style.display = 'none'; 64 | }; 65 | }, 66 | }; 67 | 68 | 69 | function GroupModel(area) { 70 | this.storageArea = (function () { 71 | return area === 'session' ? chrome.storage.local : chrome.storage.sync; 72 | }()); 73 | // all saved groups as object 74 | this.data_local_copy = (function () { 75 | var item, 76 | groups_data = (function () { 77 | return area === 'session' ? session_items : storage_items; 78 | }()), 79 | obj = {}; 80 | 81 | for (item in groups_data) { 82 | if (item.indexOf('tg_') === 0) { 83 | obj[item] = groups_data[item]; 84 | } 85 | } 86 | return obj; 87 | }()); 88 | } 89 | 90 | GroupModel.prototype = { 91 | getGroups: function () { 92 | return this.data_local_copy; 93 | }, 94 | 95 | getStorageNameByName: function (name) { 96 | var storage_name; 97 | 98 | for (storage_name in this.data_local_copy) { 99 | if (this.data_local_copy[storage_name].name === name) { 100 | return storage_name; 101 | } 102 | } 103 | return false; 104 | }, 105 | 106 | nextIndex: function () { 107 | var last_index = 0, 108 | group; 109 | 110 | for (group in this.data_local_copy) { 111 | if (this.data_local_copy[group].index > last_index) { 112 | last_index = this.data_local_copy[group].index; 113 | } 114 | } 115 | return last_index + 1; 116 | }, 117 | 118 | del: function (storage_name, callback) { 119 | var self = this; 120 | 121 | this.storageArea.remove(storage_name, function () { 122 | if (!chrome.runtime.lastError) { 123 | delete self.data_local_copy[storage_name]; 124 | callback({err: 0}); 125 | } else { 126 | callback({err: 1, msg: chrome.runtime.lastError.message}); 127 | } 128 | }); 129 | }, 130 | 131 | add: function (name, group, callback) { 132 | var now = new Date(), 133 | new_group = {}, 134 | storage_name = 'tg_' + now.getTime(), 135 | self = this; 136 | 137 | new_group[storage_name] = { 138 | name: name === undefined || name.length === 0 ? '' : name, 139 | index: this.nextIndex(), 140 | color: '', // TODO personal color for each group 141 | tabs: group, 142 | }; 143 | this.storageArea.set(new_group, function () { 144 | if (!chrome.runtime.lastError) { 145 | self.data_local_copy[storage_name] = new_group[storage_name]; 146 | callback({err: 0, storage_name: storage_name, new_group: new_group[storage_name]}); 147 | } else { 148 | callback({err: 1, msg: chrome.runtime.lastError.message}); 149 | } 150 | }); 151 | }, 152 | 153 | upd: function (storage_name, value, callback) { 154 | var obj = {}, 155 | self = this; 156 | 157 | obj[storage_name] = value; 158 | this.storageArea.set(obj, function () { 159 | if (!chrome.runtime.lastError) { 160 | self.data_local_copy[storage_name] = value; 161 | callback({err: 0, storage_name: storage_name, new_group: obj[storage_name]}); 162 | } else { 163 | callback({err: 1, msg: chrome.runtime.lastError.message}); 164 | } 165 | }); 166 | }, 167 | 168 | move: function (storage_name, sibling_storage_name, callback) { 169 | var self = this, 170 | obj = {}, 171 | sibling_obj = {}, 172 | i = this.data_local_copy[storage_name].index; 173 | 174 | obj[storage_name] = this.data_local_copy[storage_name]; 175 | obj[storage_name].index = this.data_local_copy[sibling_storage_name].index; 176 | sibling_obj[sibling_storage_name] = this.data_local_copy[sibling_storage_name]; 177 | sibling_obj[sibling_storage_name].index = i; 178 | this.storageArea.set(obj, function () { 179 | if (!chrome.runtime.lastError) { 180 | self.storageArea.set(sibling_obj, function () { 181 | if (!chrome.runtime.lastError) { 182 | self.data_local_copy[storage_name] = obj[storage_name]; 183 | self.data_local_copy[sibling_storage_name] = sibling_obj[sibling_storage_name]; 184 | callback({err: 0}); 185 | } else { 186 | callback({err: 1, msg: chrome.runtime.lastError.message}); 187 | } 188 | }); 189 | } else { 190 | callback({err: 1, msg: chrome.runtime.lastError.message}); 191 | } 192 | }); 193 | }, 194 | }; 195 | 196 | 197 | function LinkModel(area) { 198 | this.storage = (function () { 199 | return area === 'session' ? chrome.storage.local : chrome.storage.sync; 200 | }()); 201 | this.model = area === 'session' ? sessionModel : groupModel; 202 | } 203 | 204 | LinkModel.prototype = { 205 | getLocalLinkIndexById: function (storage_name, link_id) { 206 | var i, 207 | tabs = (this.model.getGroups())[storage_name].tabs, 208 | max = tabs.length; 209 | 210 | for (i = 0; i < max; i++) { 211 | if (tabs[i].id === parseInt(link_id, 10)) { 212 | return i; 213 | } 214 | } 215 | }, 216 | 217 | groupLinksByWindowId: function (storage_name) { 218 | var tabs = (this.model.getGroups())[storage_name].tabs, 219 | windows = [], // arr with unique windowId 220 | grouped_by_windows = []; // links grouped by windowId [[{tab1}, {tab2}],[{tab3}]]... 221 | 222 | tabs.forEach(function (el) { 223 | var window_index = windows.indexOf(el.windowId); 224 | if (window_index === -1) { 225 | windows.push(el.windowId); 226 | grouped_by_windows.push([]); 227 | grouped_by_windows[windows.length - 1].push(el); 228 | } else { 229 | grouped_by_windows[window_index].push(el); 230 | } 231 | }); 232 | return {grouped_by_windowId: grouped_by_windows, windowId_arr: windows}; 233 | }, 234 | 235 | nextIndex: function (storage_name) { 236 | var tabs = (this.model.getGroups())[storage_name].tabs, 237 | last_index = 0; 238 | 239 | tabs.forEach(function (el) { 240 | if (el.id > last_index) { 241 | last_index = el.id; 242 | } 243 | }); 244 | return last_index + 1; 245 | }, 246 | 247 | del: function (storage_name, link_id, callback) { 248 | var group = (this.model.getGroups())[storage_name]; 249 | group.tabs.splice(this.getLocalLinkIndexById(storage_name, link_id), 1); 250 | 251 | this.model.upd(storage_name, group, function (answ) { 252 | callback(answ); 253 | }); 254 | }, 255 | 256 | add: function (storage_name, link_data, callback) { 257 | var group = (this.model.getGroups())[storage_name]; 258 | 259 | group.tabs.push(link_data); 260 | this.model.upd(storage_name, group, function (answ) { 261 | callback(answ); 262 | }); 263 | }, 264 | 265 | upd: function (storage_name, link_index, link_data, callback) { 266 | var group = (this.model.getGroups())[storage_name]; 267 | 268 | group.tabs[link_index] = link_data; 269 | this.model.upd(storage_name, group, function (answ) { 270 | callback(answ); 271 | }); 272 | }, 273 | }; 274 | 275 | var groupModel = new GroupModel('saved'), 276 | linkModel = new LinkModel('saved'), 277 | sessionModel = new GroupModel('session'), 278 | sessionLinkModel = new LinkModel('session'), 279 | TAB_TITLE_LENGTH = 48, 280 | ui_msg = { 281 | title_del_group: chrome.i18n.getMessage('p_delGroup_btn_title'), 282 | title_edit_group_name: chrome.i18n.getMessage('p_editGroupName_btn_title'), 283 | title_add_link_to_group: chrome.i18n.getMessage('p_addLinkToGroup_btn_title'), 284 | title_group_in_new_window: chrome.i18n.getMessage('p_groupInNewWindow_btn_title'), 285 | title_edit_link: chrome.i18n.getMessage('p_editLink_btn_title'), 286 | title_del_link: chrome.i18n.getMessage('p_delLinkFromGroup_btn_title'), 287 | title_up_link: chrome.i18n.getMessage('p_upGroup_btn_title'), 288 | title_down_link: chrome.i18n.getMessage('p_downGroup_btn_title'), 289 | 290 | windows_numbers: chrome.i18n.getMessage('p_windowsNumbers_text'), 291 | tabs_numbers: chrome.i18n.getMessage('p_tabsNumbers_text'), 292 | 293 | window_text: chrome.i18n.getMessage('p_window_text'), 294 | 295 | quota_bytes_item: chrome.i18n.getMessage('p_quotaBites_error'), 296 | quota_default_item: chrome.i18n.getMessage('p_syncStorageDefault_error'), 297 | quota_bytes_per_item: chrome.i18n.getMessage('p_quotaBitesPerItem_error'), 298 | 299 | current_session: chrome.i18n.getMessage('p_currentSession'), 300 | }, 301 | 302 | tabsGrabber = { 303 | tabsFilter: function () { 304 | var obj = {}; 305 | 306 | if (storage_items.cur_win) { 307 | obj.currentWindow = true; 308 | } 309 | return obj; 310 | }, 311 | 312 | collectTabs: function (callback) { 313 | chrome.tabs.query(this.tabsFilter(), function (tabs) { 314 | var links = []; 315 | 316 | tabs.forEach(function (tab, index) { 317 | var link = {}; 318 | 319 | if (!tab.pinned || storage_items.pinned === 1) { 320 | link.url = tab.url; 321 | link.title = tab.title.length > TAB_TITLE_LENGTH ? tab.title.slice(0, TAB_TITLE_LENGTH + 1) : tab.title; 322 | link.id = index; 323 | if (tab.pinned) { 324 | link.pinned = tab.pinned; 325 | } 326 | links.push(link); 327 | } 328 | }); 329 | callback(links); 330 | }); 331 | }, 332 | }, 333 | 334 | openLinksInNewWindow = function (links) { 335 | chrome.runtime.sendMessage({ 336 | msg: 'openLinksInNewWindow', 337 | links: links, 338 | }); 339 | window.close(); 340 | }, 341 | 342 | openLinksInCurrentWindow = function (links) { 343 | chrome.runtime.sendMessage({ 344 | msg: 'openLinksInCurrentWindow', 345 | links: links, 346 | }); 347 | }, 348 | 349 | openWindowLinksInCurrentWindow = function (links, window_id) { 350 | chrome.runtime.sendMessage({ 351 | msg: 'openWindowLinksInCurrentWindow', 352 | links: links, 353 | window_id: window_id, 354 | }); 355 | }, 356 | 357 | savedUI = { 358 | groupHtmlElement: function (storage_name, group) { 359 | var title = group.name; 360 | 361 | if (group.name === '') { 362 | title = utils.formatDate(new Date(parseInt(storage_name.slice('tg_'.length), 10)), storage_items.date_format); 363 | } 364 | return '
' + 365 | '' + 366 | '' + title + '' + 367 | ' ' + 368 | '' + 369 | '' + 370 | '' + 371 | '' + 372 | '' + 373 | '' + 374 | '' + 375 | '
'; 376 | }, 377 | 378 | linkHtmlElement: function (storage_name, link) { 379 | var a_text = link.title, 380 | a_title = '', 381 | pin_icon = link.pinned ? '' : '', 382 | favicon_src = faviconURL(link.url); 383 | 384 | if (link.title.length >= TAB_TITLE_LENGTH) { 385 | a_text = link.title.slice(0, TAB_TITLE_LENGTH) + '...'; 386 | a_title = 'title="' + link.title + '"'; 387 | } 388 | return ''; 396 | }, 397 | 398 | openGroup: function (storage_name, mouse_button) { 399 | // mouse_button = 0 - cur window, 1 - new 400 | var tabs = (groupModel.getGroups())[storage_name].tabs; 401 | 402 | if (mouse_button === 1) { 403 | openLinksInNewWindow(tabs); 404 | } else { 405 | openLinksInCurrentWindow(tabs); 406 | //tabs.forEach(function (el) { 407 | // chrome.tabs.create({'url': el.url, 'pinned': el.pinned}); 408 | //}); 409 | } 410 | }, 411 | 412 | showGroups: function () { 413 | var html = '
', 414 | self = this, 415 | groups = groupModel.getGroups(), 416 | tag = document.getElementById('saved'), 417 | sorted_group_list = Object.keys(groups).sort(function (a, b) { 418 | return groups[b].index - groups[a].index; 419 | }); 420 | sorted_group_list.forEach(function (el) { 421 | html += self.groupHtmlElement(el, groups[el]); 422 | }); 423 | tag.innerHTML = html; 424 | this.showSyncStorageUsage(); 425 | }, 426 | 427 | addGroup: function () { 428 | var input_field = document.getElementById('new_group_name'), 429 | name = input_field.value, 430 | popup, 431 | storage_name = groupModel.getStorageNameByName(name), 432 | stored_group, 433 | self = this; 434 | 435 | tabsGrabber.collectTabs(function (tabs) { 436 | // check if group name already exist 437 | if (storage_name !== false && name !== '') { 438 | // group with the same name already exist 439 | // show Popup with dialog 440 | popup = new Popup('overwrite_group', function (data) { 441 | input_field.value = ''; 442 | stored_group = groupModel.getGroups()[storage_name]; 443 | stored_group.tabs = tabs; 444 | groupModel.upd(storage_name, stored_group, function (answ) { 445 | if (answ.err === 0) { 446 | self.showSyncStorageUsage(); 447 | } else { 448 | self.showErrorMsg(answ.msg); 449 | } 450 | }); 451 | }); 452 | } else { 453 | // new group name 454 | input_field.value = ''; 455 | groupModel.add(name, tabs, function (answ) { 456 | var el, 457 | groups_el; 458 | 459 | if (answ.err === 0) { 460 | self.showSyncStorageUsage(); 461 | el = document.createElement('div'); 462 | groups_el = document.getElementById('saved'); 463 | el.innerHTML = self.groupHtmlElement(answ.storage_name, answ.new_group); 464 | groups_el.insertBefore(el, groups_el.getElementsByTagName('div')[0]); 465 | groups_el.innerHTML = groups_el.innerHTML.replace(/(
)*|(<\/div>)*/g, ''); 466 | } else { 467 | // storage Error 468 | self.showErrorMsg(answ.msg); 469 | } 470 | }); 471 | } 472 | }); 473 | }, 474 | 475 | editGroupName: function (storage_name, el) { 476 | var group = (groupModel.getGroups())[storage_name], 477 | new_name, 478 | popup, 479 | self = this; 480 | 481 | document.getElementById('popup-new_group_name').value = group.name; 482 | popup = new Popup('edit_group_name', function (data) { 483 | new_name = data['popup-new_group_name']; 484 | if (new_name !== undefined || new_name !== '') { 485 | group.name = new_name; 486 | groupModel.upd(storage_name, group, function (answ) { 487 | if (answ.err === 0) { 488 | el.getElementsByClassName('open_group')[0].innerText = new_name; 489 | self.showSyncStorageUsage(); 490 | } else { 491 | self.showErrorMsg(answ.msg); 492 | } 493 | }); 494 | } 495 | 496 | }); 497 | }, 498 | 499 | delGroup: function (storage_name, el) { 500 | var self = this; 501 | 502 | groupModel.del(storage_name, function (answ) { 503 | if (answ.err === 0) { 504 | el.remove(); 505 | self.showSyncStorageUsage(); 506 | } else { 507 | self.showErrorMsg(answ.msg); 508 | } 509 | }); 510 | }, 511 | 512 | moveGroup: function (storage_name, sibling_storage_name) { 513 | var self = this; 514 | 515 | groupModel.move(storage_name, sibling_storage_name, function (answ) { 516 | if (answ.err === 0) { 517 | self.showGroups(); 518 | } else { 519 | self.showErrorMsg(answ.msg); 520 | } 521 | }); 522 | }, 523 | 524 | showGroupLinks: function (storage_name, el) { 525 | var link_list = document.createElement('div'), 526 | spoiler = el.getElementsByClassName('spoiler')[0], 527 | html = '', 528 | self = this; 529 | 530 | (groupModel.getGroups())[storage_name].tabs.forEach(function (el) { 531 | html += self.linkHtmlElement(storage_name, el); 532 | }); 533 | link_list.classList.add('links'); 534 | link_list.innerHTML = html; 535 | el.insertBefore(link_list, null); 536 | spoiler.setAttribute('name', 'opened'); 537 | spoiler.innerHTML = ' ▼'; 538 | }, 539 | 540 | hideGroupLinks: function (el) { 541 | var spoiler = el.getElementsByClassName('spoiler')[0]; 542 | 543 | spoiler.setAttribute('name', 'closed'); 544 | spoiler.innerHTML = ' ►'; 545 | el.getElementsByClassName('links')[0].remove(); 546 | }, 547 | 548 | delLink: function (storage_name, link_id, el) { 549 | linkModel.del(storage_name, link_id, function (answ) { 550 | if (answ.err === 0) { 551 | el.remove(); 552 | } 553 | }); 554 | }, 555 | 556 | addLink: function (storage_name, el) { 557 | var self = this, 558 | popup = new Popup('add_link', function (data) { 559 | var obj = { 560 | id: linkModel.nextIndex(storage_name), 561 | title: data['popup-add_link_name'], 562 | url: utils.correctUrl(data['popup-add_link_link']), 563 | }; 564 | linkModel.add(storage_name, obj, function (answ) { 565 | if (answ.err === 0) { 566 | var links_div = el.getElementsByClassName('links'); 567 | if (links_div.length) { 568 | self.hideGroupLinks(el); 569 | } 570 | self.showGroupLinks(storage_name, el); 571 | self.showSyncStorageUsage(); 572 | } else { 573 | self.showErrorMsg(answ.msg); 574 | } 575 | }); 576 | }); 577 | }, 578 | 579 | editLink: function (storage_name, link_id, el) { 580 | var group_node = el.parentNode.parentNode, 581 | tabs = (groupModel.getGroups())[storage_name].tabs, 582 | link_index = linkModel.getLocalLinkIndexById(storage_name, link_id), 583 | popup, 584 | self = this; 585 | 586 | document.getElementById('popup-edit_link_name').value = tabs[link_index].title; 587 | document.getElementById('popup-edit_link_url').value = tabs[link_index].url; 588 | popup = new Popup('edit_link', function (data) { 589 | var link_data = tabs[link_index]; 590 | link_data.title = data['popup-edit_link_name']; 591 | link_data.url = utils.correctUrl(data['popup-edit_link_url']); 592 | linkModel.upd(storage_name, link_index, link_data, function (answ) { 593 | if (answ.err === 0) { 594 | self.hideGroupLinks(group_node); 595 | self.showGroupLinks(storage_name, group_node); 596 | self.showSyncStorageUsage(); 597 | } else { 598 | self.showErrorMsg(answ.msg); 599 | } 600 | }); 601 | }); 602 | }, 603 | 604 | showErrorMsg: function (msg) { 605 | var el = document.getElementById('error_msg'), 606 | text = ui_msg.quota_default_item; 607 | 608 | el.style.display = 'block'; 609 | switch (msg) { 610 | case 'QUOTA_BYTES_PER_ITEM quota exceeded': 611 | text = ui_msg.quota_bytes_per_item; 612 | break; 613 | case 'QUOTA_BYTES quota exceeded': 614 | text = ui_msg.quota_bytes_item; 615 | break; 616 | } 617 | el.innerText = text; 618 | setTimeout(function () { 619 | el.style.display = 'none'; 620 | }, 4581); 621 | }, 622 | 623 | showSyncStorageUsage: function () { 624 | chrome.storage.sync.getBytesInUse(null, function (bytesInUse) { 625 | var percent_in_use = (bytesInUse * 100 / chrome.storage.sync.QUOTA_BYTES).toFixed(2), 626 | el = document.querySelector('ul.tabs_nav>li a'), 627 | text = el.innerText; 628 | 629 | el.innerText = text.slice(0, text.indexOf('|')) + '| ' + percent_in_use + '%'; 630 | }); 631 | }, 632 | 633 | setHandlers: function () { 634 | var self = this, 635 | saved = document.getElementById('saved'); 636 | 637 | document.getElementById('new_group').addEventListener('click', function (e) { 638 | e.preventDefault(); 639 | self.addGroup(); 640 | }, false); 641 | 642 | document.getElementById('new_group_name').addEventListener('keyup', function (e) { 643 | if (e.keyCode === 13) { 644 | self.addGroup(); 645 | } 646 | }, false); 647 | 648 | saved.addEventListener('click', function (e) { 649 | var el = e.target, 650 | group_node, 651 | link_node, 652 | btn, 653 | sibling, 654 | link; 655 | 656 | function linkInfo(link_node_id) { 657 | return { 658 | storage_name: link_node_id.slice(0, link_node_id.lastIndexOf('_')), 659 | id: link_node_id.slice(link_node_id.lastIndexOf('_') + 1), 660 | }; 661 | } 662 | 663 | e.stopPropagation(); 664 | switch (el.className) { 665 | case 'open_group': 666 | // e.button - 0 - left mouse click (cur win), 1 - mouse wheel click (new win) 667 | // mouse wheel works only with no scroll 668 | btn = e.button; 669 | if (btn === 0 && e.ctrlKey === true) { 670 | self.openGroup(el.parentNode.id, 1); 671 | } else { 672 | self.openGroup(el.parentNode.id, e.button); 673 | } 674 | break; 675 | case 'open_in_new_window': 676 | // e.button - 0 - left mouse click (cur win), 1 - mouse wheel click (new win) 677 | self.openGroup(el.parentNode.id, 1); 678 | break; 679 | case 'up': 680 | group_node = el.parentNode.parentNode; 681 | sibling = group_node.previousSibling; 682 | if (sibling && sibling.className === 'group') { 683 | self.moveGroup(group_node.id, sibling.id); 684 | } 685 | break; 686 | case 'down': 687 | group_node = el.parentNode.parentNode; 688 | sibling = group_node.nextSibling; 689 | if (sibling && sibling.className === 'group') { 690 | self.moveGroup(group_node.id, sibling.id); 691 | } 692 | break; 693 | case 'del_group': 694 | group_node = el.parentNode.parentNode; 695 | self.delGroup(group_node.id, group_node); 696 | break; 697 | case 'edit_group': 698 | group_node = el.parentNode.parentNode; 699 | self.editGroupName(group_node.id, group_node); 700 | break; 701 | case 'add_link': 702 | group_node = el.parentNode.parentNode; 703 | self.addLink(group_node.id, group_node); 704 | break; 705 | case 'spoiler': 706 | group_node = el.parentNode; 707 | if (el.getAttribute('name') === 'closed') { 708 | self.showGroupLinks(group_node.id, group_node); 709 | } else { 710 | self.hideGroupLinks(group_node); 711 | } 712 | break; 713 | case 'del_link': 714 | link_node = el.parentNode.parentNode; 715 | link = linkInfo(link_node.id); 716 | self.delLink(link.storage_name, link.id, link_node); 717 | break; 718 | case 'edit_link': 719 | link_node = el.parentNode.parentNode; 720 | link = linkInfo(link_node.id); 721 | self.editLink(link.storage_name, link.id, link_node); 722 | break; 723 | } 724 | 725 | }, false); 726 | 727 | }, 728 | 729 | go: function () { 730 | this.showGroups(); 731 | this.setHandlers(); 732 | }, 733 | }, 734 | 735 | 736 | sessionsUI = { 737 | groupHtmlElement: function (storage_name, group) { 738 | var title, 739 | date = utils.formatDate(new Date(parseInt(storage_name.slice('tg_'.length), 10)), storage_items.date_format), 740 | group_info = (function () { 741 | return { 742 | numbers_of_windows: sessionLinkModel.groupLinksByWindowId(storage_name).windowId_arr.length, 743 | numbers_of_tabs: (sessionModel.getGroups())[storage_name].tabs.length, 744 | }; 745 | }()); 746 | 747 | if (group.name === '') { 748 | title = date; 749 | } 750 | if (storage_name === session_storage_items.session_id) { 751 | title = ui_msg.current_session + ' (' + date + ')'; 752 | } 753 | return '
' + 754 | '' + 755 | '' + title + '' + 756 | ' ' + 757 | '' + ui_msg.windows_numbers + ' ' + group_info.numbers_of_windows + '' + 758 | '' + ui_msg.tabs_numbers + ' ' + group_info.numbers_of_tabs + '' + 759 | '' + 760 | '' + 761 | '' + 762 | '
'; 763 | }, 764 | 765 | linkHtmlElement: function (storage_name, link) { 766 | var a_text = link.title, 767 | a_title = '', 768 | text_length = 44, 769 | pin_icon = link.pinned === true ? '' : '', 770 | favicon_src = faviconURL(link.url); 771 | 772 | if (link.title.length > text_length) { 773 | a_text = link.title.slice(0, text_length) + '...'; 774 | a_title = 'title="' + link.title + '"'; 775 | } 776 | return ''; 783 | }, 784 | 785 | openGroup: function (storage_name, mouse_button) { 786 | // mouse_button = 0 - cur window, 1 - new 787 | var grouped, 788 | links = []; 789 | 790 | if (mouse_button === 1) { 791 | grouped = sessionLinkModel.groupLinksByWindowId(storage_name); 792 | grouped.windowId_arr.forEach(function (el, index) { 793 | links = []; 794 | grouped.grouped_by_windowId[index].forEach(function (link) { 795 | links.push(link); 796 | }); 797 | openLinksInNewWindow(links); 798 | }); 799 | } else { 800 | openLinksInCurrentWindow((sessionModel.getGroups())[storage_name].tabs); 801 | //(sessionModel.getGroups())[storage_name].tabs.forEach(function (el) { 802 | // chrome.tabs.create({'url': el.url, 'pinned': el.pinned}); 803 | //}); 804 | } 805 | }, 806 | 807 | openWindow: function (storage_name, window_id, mouse_button) { 808 | // mouse_button = 0 - cur window, 1 - new 809 | var tabs = (sessionModel.getGroups())[storage_name].tabs, 810 | links = []; 811 | 812 | if (mouse_button === 1) { 813 | tabs.forEach(function (el) { 814 | if (el.windowId === parseInt(window_id, 10)) { 815 | links.push(el); 816 | } 817 | }); 818 | openLinksInNewWindow(links); 819 | } else { 820 | openWindowLinksInCurrentWindow(tabs, window_id); 821 | //tabs.forEach(function (el) { 822 | // if (el.windowId === parseInt(window_id, 10)) { 823 | // chrome.tabs.create({'url': el.url, 'pinned': el.pinned}); 824 | // } 825 | //}); 826 | } 827 | }, 828 | 829 | showGroups: function () { 830 | var html = '
', 831 | groups = sessionModel.getGroups(), 832 | self = this, 833 | tag = document.getElementById('sessions'), 834 | sorted_names_list = Object.keys(groups).sort(function (a, b) { 835 | return parseInt(b.slice('tg_'.length), 10) - parseInt(a.slice('tg_'.length), 10); 836 | }); 837 | sorted_names_list.forEach(function (el) { 838 | html += self.groupHtmlElement(el, groups[el]); 839 | }); 840 | tag.innerHTML = html; 841 | }, 842 | 843 | delGroup: function (storage_name, el) { 844 | var self = this; 845 | 846 | sessionModel.del(storage_name, function (answ) { 847 | if (answ.err === 0) { 848 | el.remove(); 849 | } else { 850 | self.showErrorMsg(answ.msg); 851 | } 852 | }); 853 | }, 854 | 855 | showGroupLinks: function (storage_name, el) { 856 | var link_list = document.createElement('div'), 857 | spoiler = el.getElementsByClassName('spoiler')[0], 858 | html = '', 859 | self = this, 860 | grouped_links = sessionLinkModel.groupLinksByWindowId(storage_name); 861 | 862 | grouped_links.grouped_by_windowId.forEach(function (link_arr, ind) { 863 | var win_html = '
' + 864 | '
▪ ' + ui_msg.window_text + ' ' + (ind + 1) + '
'; 865 | 866 | link_arr.forEach(function (link) { 867 | win_html += self.linkHtmlElement(storage_name, link); 868 | }); 869 | win_html += '
'; 870 | html += win_html; 871 | }); 872 | link_list.classList.add('links'); 873 | link_list.innerHTML = html; 874 | el.insertBefore(link_list, null); 875 | spoiler.setAttribute('name', 'opened'); 876 | spoiler.innerHTML = ' ▼'; 877 | }, 878 | 879 | hideGroupLinks: function (el) { 880 | var spoiler = el.getElementsByClassName('spoiler')[0]; 881 | 882 | spoiler.setAttribute('name', 'closed'); 883 | spoiler.innerHTML = ' ►'; 884 | el.getElementsByClassName('links')[0].remove(); 885 | }, 886 | setHandlers: function () { 887 | var self = this; 888 | 889 | document.getElementById('sessions').addEventListener('click', function (e) { 890 | var el = e.target, 891 | group_node, 892 | btn, 893 | win_info; 894 | 895 | e.stopPropagation(); 896 | switch (el.className) { 897 | case 'del_group': 898 | group_node = el.parentNode.parentNode; 899 | self.delGroup(group_node.id, group_node); 900 | break; 901 | case 'spoiler': 902 | group_node = el.parentNode; 903 | if (el.getAttribute('name') === 'closed') { 904 | self.showGroupLinks(group_node.id, group_node); 905 | } else { 906 | self.hideGroupLinks(group_node); 907 | } 908 | break; 909 | case 'open_group': 910 | // e.button - 0 - left mouse click (cur win), 1 - mouse wheel click (new win) 911 | // mouse wheel works only with no scroll 912 | btn = e.button; 913 | if (btn === 0 && e.ctrlKey === true) { 914 | self.openGroup(el.parentNode.id, 1); 915 | } else { 916 | self.openGroup(el.parentNode.id, e.button); 917 | } 918 | break; 919 | case 'open_in_new_window': 920 | // e.button - 0 - left mouse click (cur win), 1 - mouse wheel click (new win) 921 | self.openGroup(el.parentNode.id, 1); 922 | break; 923 | case 'win_title': 924 | // e.button - 0 - left mouse click (cur win), 1 - mouse wheel click (new win) 925 | // mouse wheel works only with no scroll 926 | btn = e.button; 927 | win_info = (function () { 928 | var win_id_start = el.id.lastIndexOf('_'); 929 | return { 930 | storage_name: el.id.slice(0, win_id_start), 931 | window_id: el.id.slice(win_id_start + 1), 932 | }; 933 | }()); 934 | if (btn === 0 && e.ctrlKey === true) { 935 | self.openWindow(win_info.storage_name, win_info.window_id, 1); 936 | } else { 937 | self.openWindow(win_info.storage_name, win_info.window_id, 0); 938 | } 939 | break; 940 | } 941 | 942 | }, false); 943 | }, 944 | 945 | go: function () { 946 | this.showGroups(); 947 | this.setHandlers(); 948 | }, 949 | }, 950 | 951 | // mainUI = { 952 | // setHandlers: function () { 953 | // document.body.onkeyup = function (e) { 954 | // if (e.keyCode === 78 && e.target.tagName !== 'input') { 955 | // alert(e.target.tagName); 956 | // } 957 | // }; 958 | // } 959 | // }, 960 | 961 | navigation; 962 | 963 | if (!chrome.runtime.lastError) { 964 | /* window tab navigation */ 965 | navigation = new Tabs('#main_tabs'); 966 | navigation.toggle(storage_items.active_tab); 967 | navigation.onToggle(function (tab_name) { 968 | chrome.storage.sync.set({active_tab: tab_name}); 969 | }); 970 | /* popup tab END */ 971 | 972 | if (storage_items.session_watcher) { 973 | sessionsUI.go(); 974 | } else { 975 | navigation.toggle('#saved'); 976 | // hide session tab 977 | document.querySelector('.tabs_nav').querySelector('[href="#sessions"]').parentNode.style.display = 'none'; 978 | } 979 | savedUI.go(); 980 | // mainUI.setHandlers(); 981 | 982 | } else { 983 | // show sync storage error 984 | document.getElementById('error_msg').style.display = 'block'; 985 | document.getElementById('error_msg').innerText = ui_msg.quota_default_item; 986 | } 987 | }); 988 | }); 989 | }); 990 | // TODO blink group el after create, move 991 | // TODO hоt keys 992 | -------------------------------------------------------------------------------- /js/storage.js: -------------------------------------------------------------------------------- 1 | var storage = { 2 | area: chrome.storage.sync, 3 | default_options: { 4 | date_format: 'eu', // or 'us' 5 | pinned: 0, //do not add pinned tabs 6 | cur_win: 1, // save tabs from current window only 7 | active_tab: '#saved', //or '#sessions' 8 | session_watcher: 1 //extension will watch sessions 9 | } 10 | }; -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | var utils = { 2 | formatDate: function (date, date_format) { 3 | var res, 4 | day = plusZero(date.getDate()), 5 | month = plusZero(date.getMonth() + 1), 6 | year = date.getFullYear(), 7 | hours = plusZero(date.getHours()), 8 | minutes = plusZero(date.getMinutes()); 9 | 10 | function plusZero(d) { 11 | return (d.toString()).length === 2 ? d : '0' + d; 12 | } 13 | 14 | switch (date_format) { 15 | case 'eu': 16 | res = day + '-' + month + '-' + year + ' ' + hours + ':' + minutes; 17 | break; 18 | case 'us': 19 | res = year + '-' + month + '-' + day + ' ' + hours + ':' + minutes; 20 | break; 21 | } 22 | return res; 23 | }, 24 | 25 | correctUrl: function (url) { 26 | var start_with = ['http://', 'https://', 'ftp://', 'opera://', 'chrome://', 'chrome-extension://', 'chrome-devtools://', 'file://']; 27 | return start_with.some(function (el) { 28 | return (url.indexOf(el) === 0); 29 | }) ? url : 'https://' + url; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /lib/jsTabs/tabs.css: -------------------------------------------------------------------------------- 1 | ul.tabs_nav { 2 | list-style: none; 3 | margin: 0 0 0 20px; 4 | padding: 0; 5 | } 6 | .tabs_nav li { 7 | display: inline; 8 | float: left; 9 | border: 1px solid #dddddd; 10 | background-color: #ededed; 11 | padding: 5px; 12 | margin-right: 4px; 13 | margin-bottom: -1px; 14 | border-top-right-radius: 3px; 15 | border-top-left-radius: 3px; 16 | cursor: pointer; 17 | } 18 | .tabs_nav li.active { 19 | border-bottom: 1px solid #ffffff; 20 | background-color: #ffffff; 21 | } 22 | .tabs_nav a { 23 | text-decoration: none; 24 | font-weight: bold; 25 | letter-spacing: -1px; 26 | color: #402e16; 27 | } 28 | .tabs_content { 29 | clear: both; 30 | border-top: 1px solid #dddddd; 31 | padding: 20px; 32 | } -------------------------------------------------------------------------------- /lib/jsTabs/tabs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jsTabs. No jQuery. IE 10+ 3 | * https://github.com/onikienko/jsTabs 4 | * 5 | * @param {string} tabs_id ID of Tabs container with # 6 | * @param {function|object} [handlers] onToggle handlers 7 | * @constructor 8 | */ 9 | function Tabs(tabs_id, handlers) { 10 | this.html = document.querySelector(tabs_id); 11 | this.nav_links = this.html.querySelectorAll('.tabs_nav li a'); 12 | this.ontoggle_handlers_list = {}; 13 | 14 | var self = this; 15 | this.nav_links_array = (function () { 16 | var arr = []; 17 | [].forEach.call(self.nav_links, function (el) { 18 | arr.push(el.hash); 19 | }); 20 | return arr; 21 | }()); 22 | if (handlers) { 23 | this.onToggle(handlers); 24 | } 25 | this.go_(); 26 | } 27 | 28 | Tabs.prototype = { 29 | fireOnToggle_: function (tab_name) { 30 | if (this.ontoggle_handlers_list.hasOwnProperty(tab_name) && this.ontoggle_handlers_list[tab_name].length > 0) { 31 | this.ontoggle_handlers_list[tab_name].forEach(function (handler) { 32 | handler(tab_name); 33 | }); 34 | } 35 | }, 36 | 37 | toggle: function (tab_name) { 38 | if (tab_name && this.nav_links_array.indexOf(tab_name) !== -1) { 39 | [].forEach.call(this.html.querySelectorAll('.tabs_content>div'), function (el) { 40 | el.style.display = ('#' + el.id === tab_name) ? 'block' : 'none'; 41 | }); 42 | [].forEach.call(this.nav_links, function (el) { 43 | if (el.hash === tab_name) { 44 | el.parentNode.classList.add('active'); 45 | } else { 46 | el.parentNode.classList.remove('active'); 47 | } 48 | }); 49 | this.fireOnToggle_(tab_name); 50 | } 51 | }, 52 | 53 | /** 54 | * 55 | * @param {function|object} handlers {'#tab_name': handler_function} or a single function to bind it on every toggle 56 | */ 57 | onToggle: function (handlers) { 58 | var tab, 59 | self = this; 60 | 61 | function addHandler(tab_name, handler) { 62 | if (!self.ontoggle_handlers_list[tab_name]) { 63 | self.ontoggle_handlers_list[tab_name] = []; 64 | } 65 | self.ontoggle_handlers_list[tab_name].push(handler); 66 | } 67 | 68 | if (typeof handlers === 'function') { 69 | this.nav_links_array.forEach(function (el) { 70 | addHandler(el, handlers); 71 | }); 72 | } else if (typeof handlers === 'object') { 73 | for (tab in handlers) { 74 | if (this.nav_links_array.indexOf(tab) !== -1) { 75 | addHandler(tab, handlers[tab]); 76 | } 77 | } 78 | } 79 | }, 80 | 81 | onNavClick_: function () { 82 | var self = this; 83 | self.html.querySelector('.tabs_nav').addEventListener('click', function (e) { 84 | var hash = e.target.hash, 85 | li; 86 | if (!hash) { 87 | if (e.target.tagName === 'LI') { 88 | li = e.target.querySelector('a'); 89 | hash = li.hash; 90 | } 91 | } 92 | if (hash && self.nav_links_array.indexOf(hash) !== -1 && !e.target.parentElement.classList.contains('active')) { 93 | self.toggle(hash); 94 | } 95 | e.preventDefault(); 96 | e.stopPropagation(); 97 | }, false); 98 | }, 99 | 100 | go_: function () { 101 | if (this.nav_links_array.length > 0) { 102 | var hash = window.location.hash; 103 | if (!hash || this.nav_links_array.indexOf(hash) === -1) { 104 | hash = this.nav_links[0].hash; 105 | } 106 | this.toggle(hash); 107 | this.onNavClick_(); 108 | } 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "__MSG_extName__", 3 | "description": "__MSG_extDescr__", 4 | "version": "0.3.0", 5 | "manifest_version": 3, 6 | "author": "Mykhailo Onikiienko", 7 | 8 | "default_locale": "en", 9 | "icons": { 10 | "16": "img/ext_icons/16.png", 11 | "32": "img/ext_icons/32.png", 12 | "48": "img/ext_icons/48.png", 13 | "128": "img/ext_icons/128.png" 14 | }, 15 | 16 | "action": { 17 | "default_icon": { 18 | "16": "img/ext_icons/16.png", 19 | "32": "img/ext_icons/32.png", 20 | "48": "img/ext_icons/48.png" 21 | }, 22 | "default_popup": "popup.html" 23 | }, 24 | 25 | "background": { 26 | "service_worker": "js/background.js" 27 | }, 28 | "options_page": "options.html", 29 | 30 | "permissions": [ 31 | "storage", 32 | "tabs", 33 | "favicon" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{extName}} 6 | 7 | 8 | 9 | 10 | 11 |
12 | {{o_error}} 13 | {{o_saved}} 14 |
15 | 16 |
17 |
18 | 19 | {{extName}} 20 |
21 | 22 |
23 | 29 | 30 |
31 | 32 |
33 |
34 | {{ o_common_legend }} 35 |

36 | {{ o_dateFormat_text }} 37 | 41 |

42 |

43 |
44 | 45 |
46 | {{ o_savedTabs_legend }} 47 |

48 | 49 |

50 |
51 |
52 | 53 |
54 |
55 | {{ o_backup_restoreLegend }} 56 |

{{ o_backup_restoreInstruction }}

57 |

{{ o_backup_restoreInstructionMore }}

58 | 59 | 60 |
61 |
62 | {{ o_backup_backupLegend }} 63 |

{{ o_backup_backupInstruction }}

64 |

{{ o_backup_backupInstructionMore }}

65 | 66 |
67 |
68 | 69 |
70 |

{{ o_help_syncStorage }}

71 |

{{ o_help_syncStorage_0 }}

72 |

{{ o_help_syncStorage_1 }}

73 |

{{ o_help_syncStorage_2 }}

74 | 75 |
76 |
77 | {{ o_help_tipsLegend }} 78 |
    79 |
  • {{ o_help_quickAdd }}
  • 80 |
  • {{ o_help_showLinks }}
  • 81 |
  • {{ o_help_inNewWindow }}
  • 82 |
  • {{ o_help_editGroup }}
  • 83 |
  • {{ o_help_overFavicon }}
  • 84 |
  • {{ o_help_settings }}
  • 85 |
86 |
87 |
88 | 89 |
90 |
91 |
Source Code:
92 |
GitHub
93 |
94 |
95 | Spanish translation by Martin Irigaray 96 |
97 | Hungarian translation by Zoltan Mihics 98 |
99 | French translation by Aurélien Léger 100 |
101 | Serbian translation by Sijera 102 |
103 | Thanks to: Ricardo J. Barberis 104 |
105 |

'Pin' icon by icon8.com

106 |
107 | 108 |
109 |
110 | 111 |
112 | 115 |
116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | + 13 | 14 | ? 15 | 16 | 17 |
18 | 19 | 20 | 30 | 44 | 58 | 67 | 68 | 69 |
70 | 74 | 75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | TabHamster Chrome Extension 2 | =========================== 3 | 4 | Session manager and tabs saver. With sync. 5 | ------------------------------------------ 6 | 7 | ![](https://raw.githubusercontent.com/onikienko/TabHamster/master/img/ext_icons/128.png) 8 | 9 | Install for your browser: 10 | 11 | **[Chrome Web Store](https://chrome.google.com/webstore/detail/tabhamster/mkfjjmjmnplabnplceaekkjcmdddokee)** 12 | 13 | ~~**[Opera Market](https://addons.opera.com/extensions/details/tabhamster/)**~~ (not available for Opera anymore) 14 | 15 | ------------------------ 16 | 17 | ### Glory to Ukraine. 🇺🇦 18 | --------------------------------------------------------------------------------