diff --git a/TabsAside.html b/TabsAside.html index 12a9eff..d833af7 100644 --- a/TabsAside.html +++ b/TabsAside.html @@ -2,7 +2,7 @@ - Tabs aside + Tabs aside @@ -15,33 +15,37 @@ diff --git a/_locales/en/messages.json b/_locales/en/messages.json new file mode 100644 index 0000000..2f54b5c --- /dev/null +++ b/_locales/en/messages.json @@ -0,0 +1,157 @@ +{ + "name": + { + "message": "Tabs aside", + "description": "Extension name. Displayed in the manifest and pane header" + }, + "description": + { + "message": "Classic Microsoft Edge \"Tabs Aside\" feature for Chromium browsers", + "description": "Extension description" + }, + "author": + { + "message": "Michael \"XFox\" Gordeev", + "description": "Author name" + }, + "options": + { + "message": "Options", + "description": "Alternative text for options button in the pane" + }, + "loadOnRestore": + { + "message": "Load tabs on restore", + "description": "Label for option" + }, + "swapIconAction": + { + "message": "Set tabs aside on extension icon click (Alt+P or right-click to open the pane)", + "description": "Label for option" + }, + "github": + { + "message": "Visit GitHub page", + "description": "Link title" + }, + "contributors": + { + "message": "Project contributors", + "description": "Link title" + }, + "feedback": + { + "message": "Leave feedback", + "description": "Link title" + }, + "buyMeACoffee": + { + "message": "Buy me a coffee!", + "description": "Link title" + }, + "credits": + { + "message": "Developed by Michael 'XFox' Gordeev", + "description": "Options menu credits" + }, + "setAside": + { + "message": "Set current tabs aside", + "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" + }, + "nothingSaved": + { + "message": "You have no aside tabs", + "description": "Placeholder for empty pane" + }, + "removeTab": + { + "message": "Remove tab from collection", + "description": "Button hint on a tab card" + }, + "restoreTabs": + { + "message": "Restore tabs", + "description": "Collection restore action link name" + }, + "more": + { + "message": "More...", + "description": "Collections' more button title" + }, + "restoreNoRemove": + { + "message": "Restore without removing", + "description": "Context action item name" + }, + "removeCollection": + { + "message": "Remove collection", + "description": "Collection remove action name" + }, + "removeCollectionConfirm": + { + "message": "Are you sure you want to delete this collection?", + "description": "Prompt dialog content on collection deletion" + }, + "removeTabConfirm": + { + "message": "Are you sure you want to delete this tab?", + "description": "Prompt dialog content on one tab deletion" + }, + "togglePaneContext": + { + "message": "Toggle tabs aside pane", + "description": "Context action name. Used in extension context menu and manifest shortcuts" + }, + "noTabsToSave": + { + "message": "No tabs available to save", + "description": "Alert dialog message when there's no tabs to save" + }, + "tabs": + { + "message": "Tabs", + "description": "Collection tabs counter label" + }, + "ago": + { + "message": "ago", + "description": "Human friendly timestamp part (e.g. 15 hour(s) ago)" + }, + "minutes": + { + "message": "minute(s)", + "description": "Human friendly timestamp part (e.g. 15 minute(s) ago)" + }, + "hours": + { + "message": "hour(s)", + "description": "Human friendly timestamp part (e.g. 15 hour(s) ago)" + }, + "days": + { + "message": "day(s)", + "description": "Human friendly timestamp part (e.g. 15 day(s) ago)" + }, + "weeks": + { + "message": "week(s)", + "description": "Human friendly timestamp part (e.g. 15 week(s) ago)" + }, + "months": + { + "message": "month(s)", + "description": "Human friendly timestamp part (e.g. 15 month(s) ago)" + }, + "years": + { + "message": "years(s)", + "description": "Human friendly timestamp part (e.g. 15 years(s) ago)" + }, + "justNow": + { + "message": "Just now", + "description": "Human friendly timestamp part" + } +} \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json new file mode 100644 index 0000000..381f2be --- /dev/null +++ b/_locales/ru/messages.json @@ -0,0 +1,157 @@ +{ + "name": + { + "message": "Отложенные вкладки", + "description": "Extension name. Displayed in the manifest and pane header" + }, + "description": + { + "message": "Функиця классического Microsoft Edge на браузерах Chromium", + "description": "Extension description" + }, + "author": + { + "message": "Михаил \"XFox\" Гордеев", + "description": "Author name" + }, + "options": + { + "message": "Настройки", + "description": "Alternative text for options button in the pane" + }, + "loadOnRestore": + { + "message": "Загружать вкладки после открытия", + "description": "Label for option" + }, + "swapIconAction": + { + "message": "Откладывать вкладки при нажатии на иконку расширения (Alt+P или правая кнопка мыши для открытия панели)", + "description": "Label for option" + }, + "github": + { + "message": "Страница GitHub", + "description": "Link title" + }, + "contributors": + { + "message": "Свой вклад вложили", + "description": "Link title" + }, + "feedback": + { + "message": "Оставить отзыв", + "description": "Link title" + }, + "buyMeACoffee": + { + "message": "Купить мне кофе!", + "description": "Link title" + }, + "credits": + { + "message": "Разработано: Михаил 'XFox' Гордеев", + "description": "Options menu credits" + }, + "setAside": + { + "message": "Отложить открытые вкладки", + "description": "Save collection action name. Used in the pane, extension context menu and manifest shortcuts" + }, + "nothingSaved": + { + "message": "У вас нет отложенных вкладок", + "description": "Placeholder for empty pane" + }, + "removeTab": + { + "message": "Удалить вкладку из коллекции", + "description": "Button hint on a tab card" + }, + "restoreTabs": + { + "message": "Восстановить вкладки", + "description": "Collection restore action link name" + }, + "more": + { + "message": "Больше...", + "description": "Collections' more button title" + }, + "restoreNoRemove": + { + "message": "Восстановить без удаления", + "description": "Context action item name" + }, + "removeCollection": + { + "message": "Удалить коллекцию", + "description": "Collection remove action name" + }, + "removeCollectionConfirm": + { + "message": "Вы уверены, что хотите удалить эту коллекцию?", + "description": "Prompt dialog content on collection deletion" + }, + "removeTabConfirm": + { + "message": "Вы уверены, что хотите удалить эту вкладку из коллекции?", + "description": "Prompt dialog content on one tab deletion" + }, + "togglePaneContext": + { + "message": "Открыть/закрыть панель", + "description": "Context action name. Used in extension context menu and manifest shortcuts" + }, + "noTabsToSave": + { + "message": "Нет доступных для сохранения вкладок", + "description": "Alert dialog message when there's no tabs to save" + }, + "tabs": + { + "message": "Вкладок", + "description": "Collection tabs counter label" + }, + "ago": + { + "message": "назад", + "description": "Human friendly timestamp part (e.g. 15 hour(s) ago)" + }, + "minutes": + { + "message": "минут(ы)", + "description": "Human friendly timestamp part (e.g. 15 minute(s) ago)" + }, + "hours": + { + "message": "час(ов)", + "description": "Human friendly timestamp part (e.g. 15 hour(s) ago)" + }, + "days": + { + "message": "дней", + "description": "Human friendly timestamp part (e.g. 15 day(s) ago)" + }, + "weeks": + { + "message": "недель", + "description": "Human friendly timestamp part (e.g. 15 week(s) ago)" + }, + "months": + { + "message": "месяц(ев)", + "description": "Human friendly timestamp part (e.g. 15 month(s) ago)" + }, + "years": + { + "message": "лет", + "description": "Human friendly timestamp part (e.g. 15 years(s) ago)" + }, + "justNow": + { + "message": "Только что", + "description": "Human friendly timestamp part" + } +} \ No newline at end of file diff --git a/js/aside-script.js b/js/aside-script.js index e501cbd..e44ab6d 100644 --- a/js/aside-script.js +++ b/js/aside-script.js @@ -62,6 +62,8 @@ function Initialize() }); } + UpdateLocale(); + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { pane.parentElement.setAttribute("darkmode", ""); @@ -72,7 +74,8 @@ function Initialize() document.querySelector("nav > p > small").textContent = chrome.runtime.getManifest()["version"]; - var loadOnRestoreCheckbox = document.querySelector("nav > p > input[type=checkbox]"); + // Tabs dismiss option + var loadOnRestoreCheckbox = document.querySelector("#loadOnRestore"); chrome.storage.sync.get( { "loadOnRestore": false }, values => loadOnRestoreCheckbox.checked = values.loadOnRestore @@ -91,8 +94,39 @@ function Initialize() }) ); + // Exntension browser icon action + var swapIconAction = document.querySelector("#swapIconAction"); + chrome.storage.sync.get( + { "setAsideOnClick": false }, + values => swapIconAction.checked = values.setAsideOnClick + ); + chrome.storage.onChanged.addListener((changes, namespace) => + { + if (namespace == 'sync') + for (key in changes) + if (key === 'setAsideOnClick') + swapIconAction.checked = changes[key].newValue + }); + swapIconAction.addEventListener("click", () => + chrome.storage.sync.set( + { + "setAsideOnClick": swapIconAction.checked + }) + ); + document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i => - i.onclick = () => window.open(i.value, '_blank')); + i.onclick = () => + { + if (i.hasAttribute("feedback-button")) + { + if (chrome.runtime.getManifest()["update_url"] && chrome.runtime.getManifest()["update_url"].includes("edge.microsoft.com")) + window.open("https://microsoftedge.microsoft.com/addons/detail/tabs-aside/kmnblllmalkiapkfknnlpobmjjdnlhnd", "_blank") + else + window.open("https://chrome.google.com/webstore/detail/tabs-aside/mgmjbodjgijnebfgohlnjkegdpbdjgin", "_blank") + } + else + window.open(i.value, "_blank"); + }); chrome.runtime.sendMessage({ command: "loadData" }, (collections) => { @@ -104,6 +138,12 @@ function Initialize() setTimeout(() => pane.setAttribute("opened", ""), 100); } +function UpdateLocale() +{ + document.querySelectorAll("*[loc]").forEach(i => i.textContent = chrome.i18n.getMessage(i.getAttribute("loc"))); + document.querySelectorAll("*[loc_alt]").forEach(i => i.title = chrome.i18n.getMessage(i.getAttribute("loc_alt"))); +} + function AddCollection(collection) { var list = document.querySelector(".tabsAside section"); @@ -119,7 +159,7 @@ function AddCollection(collection) "
" + "
" + "" + collection.titles[i] + "" + - "" + + "" + "" + ""; } @@ -127,20 +167,22 @@ function AddCollection(collection) list.innerHTML += "
" + "
" + - "Tabs: " + collection.links.length + "" + + "" + chrome.i18n.getMessage("tabs") + ": " + collection.links.length + "" + "" + GetAgo(collection.timestamp) + "" + - "Restore tabs" + + "Restore tabs" + "
" + - "" + + "" + "" + "
" + - "" + + "" + "
" + "
" + rawTabs + "
" + - "
" + ""; + + UpdateLocale(); list.querySelectorAll(".restoreCollection").forEach(i => i.onclick = () => RestoreTabs(i.parentElement.parentElement)); @@ -187,7 +229,7 @@ function RestoreTabs(collectionData, removeCollection = true) function RemoveTabs(collectionData) { - if (!confirm("Are you sure you want to delete this collection?")) + if (!confirm(chrome.i18n.getMessage("removeCollectionConfirm"))) return; chrome.runtime.sendMessage( @@ -201,7 +243,7 @@ function RemoveTabs(collectionData) function RemoveOneTab(tabData) { - if (!confirm("Are you sure you want to delete this tab?")) + if (!confirm(chrome.i18n.getMessage("removeTabConfirm"))) return; chrome.runtime.sendMessage( @@ -212,7 +254,7 @@ function RemoveOneTab(tabData) }, () => { - tabData.parentElement.previousElementSibling.children[0].textContent = "Tabs: " + (tabData.parentElement.children.length - 1); + tabData.parentElement.previousElementSibling.children[0].textContent = chrome.i18n.getMessage("tabs") + ": " + (tabData.parentElement.children.length - 1); if (tabData.parentElement.children.length < 2) { RemoveElement(tabData.parentElement.parentElement); @@ -229,37 +271,19 @@ function GetAgo(timestamp) var minutes = (Date.now() - timestamp) / 60000; if (minutes < 1) - return "Just now"; - - else if (Math.floor(minutes) == 1) - return "1 minute ago"; + return chrome.i18n.getMessage("justNow"); else if (minutes < 60) - return Math.floor(minutes) + " minutes ago"; - - else if (Math.floor(minutes / 60) == 1) - return "1 hour ago"; + return Math.floor(minutes) + " " + chrome.i18n.getMessage("minutes") + " " + chrome.i18n.getMessage("ago"); else if (minutes < 24 * 60) - return Math.floor(minutes / 60) + " hours ago"; - - else if (Math.floor(minutes / 24 / 60) == 1) - return "1 day ago"; + return Math.floor(minutes / 60) + " " + chrome.i18n.getMessage("hours") + " " + chrome.i18n.getMessage("ago"); else if (minutes < 7 * 24 * 60) - return Math.floor(minutes / 24 / 60) + " days ago"; - - else if (Math.floor(minutes / 7 / 24 / 60) == 1) - return "1 week ago"; + return Math.floor(minutes / 24 / 60) + " " + chrome.i18n.getMessage("days") + " " + chrome.i18n.getMessage("ago"); else if (minutes < 30 * 24 * 60) - return Math.floor(minutes / 7 / 24 / 60) + " weeks ago"; - - else if (Math.floor(minutes / 30 / 24 / 60) == 1) - return "1 month ago"; + return Math.floor(minutes / 7 / 24 / 60) + " " + chrome.i18n.getMessage("weeks") + " " + chrome.i18n.getMessage("ago"); else if (minutes < 365 * 24 * 60) - return Math.floor(minutes / 30 / 24 / 60) + " months ago"; - - else if (Math.floor(minutes / 24 / 60) == 365) - return "1 year ago"; + return Math.floor(minutes / 30 / 24 / 60) + " " + chrome.i18n.getMessage("months") + " " + chrome.i18n.getMessage("ago"); else - return Math.floor(minutes / 365 / 24 / 60) + "years ago"; + return Math.floor(minutes / 365 / 24 / 60) + " " + chrome.i18n.getMessage("years") + " " + chrome.i18n.getMessage("ago"); } function RemoveElement(el) diff --git a/js/background.js b/js/background.js index f6b4ec8..19c6cbf 100644 --- a/js/background.js +++ b/js/background.js @@ -1,4 +1,4 @@ -chrome.browserAction.onClicked.addListener((tab) => +function TogglePane(tab) { if (tab.url.startsWith("http") && !tab.url.includes("chrome.google.com") @@ -38,10 +38,59 @@ chrome.browserAction.onClicked.addListener((tab) => }); })); } +} + +function ProcessCommand(command) +{ + switch(command) + { + case "set-aside": + SaveCollection(); + break; + case "toggle-pane": + chrome.tabs.query( + { + active: true, + currentWindow: true + }, + (tabs) => TogglePane(tabs[0]) + ) + break; + } +} + +chrome.browserAction.onClicked.addListener((tab) => +{ + chrome.storage.sync.get({ "setAsideOnClick": false }, values => + { + if (values.setAsideOnClick) + SaveCollection(); + else + TogglePane(tab); + }); }); +// Adding context menu options +chrome.contextMenus.create( + { + id: "toggle-pane", + contexts: ['all'], + title: chrome.i18n.getMessage("togglePaneContext") + } +); +chrome.contextMenus.create( + { + id: "set-aside", + contexts: ['all'], + title: chrome.i18n.getMessage("setAside") + } +); + var collections = JSON.parse(localStorage.getItem("sets")) || []; +chrome.commands.onCommand.addListener(ProcessCommand); +chrome.contextMenus.onClicked.addListener((info) => ProcessCommand(info.menuItemId)); + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { switch (message.command) @@ -88,6 +137,12 @@ function UpdateTheme() "16": basePath + "16.png" } }); + + // Updating badge counter + if (collections.length < 1) + chrome.browserAction.setBadgeText({ }); + else + chrome.browserAction.setBadgeText({ text: collections.length.toString() }); } UpdateTheme(); @@ -104,7 +159,7 @@ function SaveCollection() if (tabs.length < 1) { - alert("No tabs available to save"); + alert(chrome.i18n.getMessage("noTabsToSave")); return; } diff --git a/manifest.json b/manifest.json index cd9aa41..f2c7fb1 100644 --- a/manifest.json +++ b/manifest.json @@ -1,15 +1,18 @@ { - "name": "Tabs Aside", - "version": "1.5", + "name": "__MSG_name__", + "version": "1.6", "manifest_version": 2, - "description": "Classic Microsoft Edge \"Tabs Aside\" feature for Chromium browsers", - "author": "Michael \"XFox\" Gordeev", + "description": "__MSG_description__", + "author": "__MSG_author__", + "default_locale": "en", + "permissions": [ "tabs", "unlimitedStorage", "storage", - "" + "", + "contextMenus" ], "icons": @@ -26,5 +29,25 @@ { "scripts": [ "js/background.js" ], "persistent": false + }, + + "commands": + { + "set-aside": + { + "description": "__MSG_setAside__", + "suggested_key": + { + "default": "Alt+Left" + } + }, + "toggle-pane": + { + "description": "__MSG_togglePaneContext__", + "suggested_key": + { + "default": "Alt+P" + } + } } } \ No newline at end of file