You have no aside tabs
-
-
-
+
+
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
index e2ff917..e2a1831 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1,178 +1,222 @@
-.aside.pane
+.tabsAside.background
{
- font-family: 'Segoe UI', 'Segoe MDL2 Assets';
- user-select: none;
- position: fixed;
- z-index: 999;
-
- right: 0px;
- top: 0px;
- width: 40%;
- min-width: 500px;
- min-height: 100%;
-
- background-color: #f7f7f7;
- border: 1px solid rgba(100, 100, 100, .5);
- box-sizing: border-box;
- box-shadow: 6px 0px 12px black;
-
- transition: .2s;
+ z-index: 9999 !important;
+ background-color: rgba(255, 255, 255, .5) !important;
+ position: fixed !important;
+ top: 0 !important;
+ bottom: 0 !important;
+ right: 0 !important;
+ left: 0 !important;
+ transition: .2s !important;
+ opacity: 0;
}
- .aside.pane > .header
+.tabsAside.pane
+{
+ font-family: 'Segoe UI', 'Segoe MDL2 Assets' !important;
+ user-select: none !important;
+ position: fixed !important;
+
+ right: 0px !important;
+ top: 0px !important;
+ bottom: 0px !important;
+ overflow: auto !important;
+
+ width: 40%;
+ min-width: 500px !important;
+
+ background-color: #f7f7f7 !important;
+ border: 1px solid rgba(100, 100, 100, .5) !important;
+ border-width: 0px 0px 0px 1px !important;
+ box-sizing: border-box !important;
+ box-shadow: 6px 0px 12px black !important;
+
+ font-size: small !important;
+
+ transform: translateX(110%); /* Pane is hidden */
+ transition: .2s !important;
+}
+
+ .tabsAside.pane[opened]
{
- margin: 20px 40px;
+ transform: translateX(0px) !important;
}
- .aside.pane > .header > .title
+
+ /* Pane header*/
+ .tabsAside.pane > header
+ {
+ margin: 20px 40px !important;
+ }
+ .tabsAside.pane > header > div
{
- display: grid;
- grid-template-columns: 1fr auto;
+ display: grid !important;
+ grid-template-columns: 1fr auto !important;
}
- .aside.pane > .header > .title > h1
+ .tabsAside.pane > header > div > h1
{
- margin: 10px 0px;
- font-weight: normal;
- font-size: 21pt;
+ margin: 10px 0px !important;
+ font-weight: normal !important;
+ font-size: 21pt !important;
}
- .aside.pane > .header > .title > button
+ .tabsAside.pane > header > div > button
{
- margin: auto;
+ margin: auto !important;
}
- .aside.pane > .header > .title > nav
+ .tabsAside.pane > header > div > nav
{
- top: 70px;
- right: 40px;
+ top: 70px !important;
+ right: 40px !important;
}
- .aside.pane > .header > .title > nav > div
+ .tabsAside.pane > header > div > nav > div
{
- box-shadow: 0px 4px 5px -2px rgba(100, 100, 100, .5);
+ box-shadow: 0px 4px 5px -2px rgba(100, 100, 100, .5) !important;
}
- .aside.pane > .header > .title > nav > p
+ .tabsAside.pane > header > div > nav > p
{
- margin: 10px;
+ margin: 10px !important;
}
- .aside.pane > .header > hr
+ .tabsAside.pane > header > hr
{
- border: 1px solid #8a8a8a;
+ border: 1px solid #8a8a8a !important;
}
- .aside.pane > #content > h2
+ .tabsAside.pane > section > h2
{
- margin: 0px 40px;
- font-weight: normal;
+ margin: 0px 40px !important;
+ font-weight: normal !important;
}
- .aside.pane > #content > .set > .setHeader
+ /* Collection header */
+ .tabsAside.pane > section > div
{
- margin: 0px 20px;
- display: grid;
- grid-template-columns: 1fr auto;
+ transition: .2s !important;
}
- .aside.pane > #content > .set > .setHeader small
+ .tabsAside.pane > section > div > div:first-child
+ {
+ margin: 0px 20px !important;
+ display: grid !important;
+ grid-template-columns: auto 1fr auto auto auto !important;
+ grid-column-gap: 10px !important;
+ align-items: center !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > small
{
- color: gray;
+ color: gray !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > span
+ {
+ font-weight: 600 !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > a
+ {
+ font-size: 11pt !important;
}
- .aside.pane > #content > .set > .setHeader span
+ .tabsAside.pane > section > div > div:first-child > div
{
- font-weight: 600;
+ display: none !important; /* TODO: Implement this menu */
}
- .aside.pane > #content > .set > .setHeader a
- {
- font-size: 11pt;
- font-weight: 500;
- }
- .aside.pane > #content > .set > .setHeader nav
- {
- width: 200px;
- margin-top: 10px;
- right: 50px;
- }
-
- .aside.pane > #content > .set > .setHeader > div:first-child
- {
- margin: auto 0px;
- }
-
- .aside.pane > #content > .set > .collection
- {
- padding: 10px 0px;
- padding-left: 40px;
- white-space: nowrap;
- overflow: auto;
- }
-
- .aside.pane > #content > .set > .collection > .item
+ .tabsAside.pane > section > div > div:first-child > div > nav
{
- width: 175px;
- height: 148px;
- margin: 5px;
-
- background-color: #c2c2c2;
- background-image: url("../images/tab_thumbnail.png");
-
- display: inline-grid;
- grid-template-rows: 1fr auto;
-
- box-shadow: 0px 0px 5px rgba(100, 100, 100, .5);
- transition: .25s;
- cursor: pointer;
+ width: 200px !important;
+ margin-top: 10px !important;
+ right: 50px !important;
}
- .aside.pane > #content > .set > .collection > .item:hover
+
+ /* Tabs collection */
+ .tabsAside.pane > section > div > div:last-child
+ {
+ margin: 0px 0px 0px 20px !important;
+ padding: 10px 40px 10px 20px !important;
+ white-space: nowrap !important;
+ overflow: auto !important;
+ }
+
+ .tabsAside.pane > section > div > div:last-child:hover::-webkit-scrollbar-thumb
+ {
+ visibility: visible !important;
+ }
+
+ .tabsAside.pane > section > div > div:last-child > div
+ {
+ width: 175px !important;
+ height: 148px !important;
+ margin: 5px !important;
+
+ background-color: #c2c2c2 !important;
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail.png");
+
+ display: inline-grid !important;
+ grid-template-rows: 1fr auto !important;
+
+ box-shadow: 0px 0px 5px rgba(100, 100, 100, .5) !important;
+ transition: .25s !important;
+ cursor: pointer !important;
+ }
+ .tabsAside.pane > section > div > div:last-child > div:hover
{
- filter: brightness(120%);
- box-shadow: 0px 0px 15px rgba(100, 100, 100, .5);
+ filter: brightness(120%) !important;
+ box-shadow: 0px 0px 15px rgba(100, 100, 100, .5) !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption
+ .tabsAside.pane > section > div > div:last-child > div > div
{
- background-color: rgba(233, 233, 233, .75);
- grid-row: 2;
- display: grid;
- grid-template-columns: auto 1fr auto;
+ background-color: rgba(233, 233, 233, .75) !important;
+ grid-row: 2 !important;
+ display: grid !important;
+ grid-template-columns: auto 1fr auto !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > button
+ .tabsAside.pane > section > div > div:last-child > div > div > button
{
- margin: auto;
- margin-right: 5px;
- visibility: hidden;
+ margin: auto !important;
+ margin-right: 5px !important;
+ display: none !important;
}
- .aside.pane > #content > .set > .collection > .item:hover > .caption > button
+ .tabsAside.pane > section > div > div:last-child > div:hover > div > button
{
- visibility: visible;
+ display: initial !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > div
+ .tabsAside.pane > section > div > div:last-child > div > div > div
{
- width: 20px;
- height: 20px;
- margin: 10px;
+ width: 20px !important;
+ height: 20px !important;
+ margin: 10px !important;
- background-image: url("../images/tab_icon.png");
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon.png");
+ background-size: 20px !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > span
+ .tabsAside.pane > section > div > div:last-child > div > div > span
{
- margin: auto 0px;
+ overflow: hidden !important;
+ margin: auto 0px !important;
+ margin-right: 10px !important;
+ }
+ .tabsAside.pane > section > div > div:last-child > div:hover > div > span
+ {
+ margin-right: 5px !important;
}
@media only screen and (max-width: 500px)
{
- .aside.pane
+ .tabsAside.pane
{
- right: initial;
- width: 100%;
- left: 0px;
- min-width: initial;
+ width: initial !important;
+ left: 0px !important;
+ min-width: initial !important;
}
}
\ No newline at end of file
diff --git a/css/style.dark.css b/css/style.dark.css
index 2350d46..11f541d 100644
--- a/css/style.dark.css
+++ b/css/style.dark.css
@@ -1,54 +1,59 @@
-.aside.pane
+.tabsAside[darkmode].background
+{
+ background-color: rgba(0, 0, 0, .5) !important;
+}
+
+.tabsAside[darkmode] .pane
{
- background-color: #333333;
- color: white;
+ background-color: #333333 !important;
+ color: white !important;
}
-.aside button
-{
- color: white;
-}
-.aside button:hover
-{
- background-color: gray;
-}
-.aside button:active
-{
- background-color: dimgray;
-}
-
-.aside.pane > #content > .set > .setHeader small
-{
- color: lightgray;
-}
-
-.aside ::-webkit-scrollbar-thumb
-{
- background: gray;
- border-radius: 3px;
-}
- ::-webkit-scrollbar-thumb:hover
+ .tabsAside[darkmode] .pane button
{
- background: dimgray;
+ color: white !important;
+ }
+ .tabsAside[darkmode] .pane button:hover
+ {
+ background-color: gray !important;
+ }
+ .tabsAside[darkmode] .pane button:active
+ {
+ background-color: dimgray !important;
}
-.aside.pane > #content > .set > .collection > .item
-{
- background-color: #0c0c0c;
- background-image: url("../images/tab_thumbnail_dark.png");
-}
+ .tabsAside[darkmode] .pane > section > div > div:first-child > div:first-child > small
+ {
+ color: lightgray !important;
+ }
-.aside.pane > #content > .set > .collection > .item > .caption
-{
- background-color: rgba(50, 50, 50, .75);
-}
+ .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb
+ {
+ background: gray !important;
+ border-radius: 3px !important;
+ }
+ .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb:hover
+ {
+ background: dimgray !important;
+ }
-.aside nav
-{
- background-color: #3f3f3f;
-}
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div
+ {
+ background-color: #0c0c0c !important;
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail_dark.png");
+ }
-.aside.pane > #content > .set > .collection > .item > .caption > div
-{
- background-image: url("../images/tab_icon_dark.png");
-}
\ No newline at end of file
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div > div
+ {
+ background-color: rgba(50, 50, 50, .75) !important;
+ }
+
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div > div > div
+ {
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon_dark.png");
+ }
+
+ .tabsAside[darkmode] .pane nav
+ {
+ background-color: #3f3f3f !important;
+ }
\ No newline at end of file
diff --git a/css/style.generic.css b/css/style.generic.css
index 4057a94..0de65e8 100644
--- a/css/style.generic.css
+++ b/css/style.generic.css
@@ -1,123 +1,106 @@
-.aside ::-webkit-scrollbar
+/* Custom scrollbar */
+.tabsAside ::-webkit-scrollbar
{
- height: 6px;
+ height: 6px !important;
}
-.aside ::-webkit-scrollbar-thumb
+.tabsAside ::-webkit-scrollbar-thumb
{
- background: darkgray;
- border-radius: 3px;
+ visibility: hidden !important;
+ background: darkgray !important;
+ border-radius: 3px !important;
}
- ::-webkit-scrollbar-thumb:hover
+ .tabsAside ::-webkit-scrollbar-thumb:hover
{
- background: gray;
+ background: gray !important;
}
-.aside a
+.tabsAside::-webkit-scrollbar
{
- font-family: 'Segoe UI', 'Segoe MDL2 Assets';
- color: #0078d7;
-}
- .aside a:hover
- {
- text-decoration: underline;
- cursor: pointer;
- }
-
-.aside .slider
-{
- position: relative;
- display: inline-block;
-
- border-radius: 13px;
- width: 50px;
- height: 26px;
-
- background-color: #cccccc;
- transition: .4s;
+ width: 6px !important;
}
- .aside .slider:before
- {
- position: absolute;
- content: "";
-
- height: 16px;
- width: 16px;
- left: 5px;
- top: 5px;
- border-radius: 50%;
-
- margin: auto;
-
- background-color: white;
- transition: .3s;
- }
-
- .aside input:checked + .slider,
- .aside input:focus + .slider
- {
- background-color: #0078d7;
- }
-
- .aside input:checked + .slider:before
- {
- transform: translateX(24px);
- background-color: #3f3f3f;
- }
-
-.aside nav
+.tabsAside::-webkit-scrollbar-thumb
{
- font-family: 'Segoe UI';
- user-select: none;
+ background: gray !important;
+ border-radius: 3px !important;
+}
+ .tabsAside::-webkit-scrollbar-thumb:hover
+ {
+ background: darkgray !important;
+ }
- position: absolute;
- width: 250px;
+/* Links style */
+.tabsAside a
+{
+ font-family: 'Segoe UI', 'Segoe MDL2 Assets' !important;
+ color: #0078d7 !important;
+}
+ .tabsAside a:hover
+ {
+ text-decoration: underline !important;
+ cursor: pointer !important;
+ }
- box-shadow: 0px 0px 10px black;
- background-color: white;
- border-radius: 5px;
+ .tabsAside a:visited
+ {
+ color: #0078d7 !important;
+ }
- z-index: 10;
+/* Buttons style */
+.tabsAside button
+{
+ font-family: 'Segoe MDL2 Assets' !important;
+ width: 32px !important;
+ height: 32px !important;
+ background-color: transparent !important;
+ border: none !important;
+ cursor: pointer !important;
+}
+ .tabsAside button:hover
+ {
+ background-color: #c6c6c6 !important;
+ }
+ .tabsAside button:active
+ {
+ background-color: gray !important;
+ }
- visibility: hidden;
+/* Context menus style */
+.tabsAside nav
+{
+ font-family: 'Segoe UI' !important;
+ user-select: none !important;
+
+ position: absolute !important;
+ width: 250px !important;
+
+ box-shadow: 0px 0px 10px black !important;
+ background-color: white !important;
+ border-radius: 5px !important;
+
+ z-index: 10 !important;
+
+ visibility: hidden !important;
}
- .aside nav button
+ .tabsAside nav button
{
- font-family: 'Segoe UI';
- cursor: pointer;
- background-color: transparent;
- border: none;
+ font-family: 'Segoe UI' !important;
+ cursor: pointer !important;
+ background-color: transparent !important;
+ border: none !important;
- font-size: medium;
- text-align: start;
+ font-size: medium !important;
+ text-align: start !important;
- padding: 10px;
- width: 100%;
- height: initial;
+ padding: 10px !important;
+ width: 100% !important;
+ height: initial !important;
}
- .aside button + nav:active,
- .aside button:focus + nav
+ .tabsAside button + nav:active,
+ .tabsAside button:focus + nav
{
- visibility: visible;
- }
-
-.aside button
-{
- font-family: 'Segoe MDL2 Assets';
- width: 32px;
- height: 32px;
- background-color: transparent;
- border: none;
- cursor: pointer;
-}
- .aside button:hover
- {
- background-color: #c6c6c6;
- }
- .aside button:active
- {
- background-color: gray;
+ visibility: visible !important;
}
\ No newline at end of file
diff --git a/js/aside-script.js b/js/aside-script.js
index 2c02062..370b616 100644
--- a/js/aside-script.js
+++ b/js/aside-script.js
@@ -1,40 +1,327 @@
-chrome.runtime.onMessage.addListener(function(sender, request, sendResponse)
+if (document.location.protocol == "chrome-extension:")
+ InitializeStandalone();
+else
{
- var pane = document.querySelector("#aside-pane");
- if (pane.style.transform == "translateX(110%)")
+ setTimeout(function ()
{
- pane.style.transform = "translateX(0%)";
- }
- else
- {
- pane.style.transform = "translateX(110%)";
- }
+ var pane = document.querySelector(".tabsAside.pane");
- UpdateTheme();
-
- sendResponse();
-});
+ if (pane == null)
+ {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', chrome.extension.getURL("collections.html"), true);
+ xhr.onreadystatechange = function ()
+ {
+ if (this.status !== 200 || document.querySelector("#aside-pane") != null)
+ return;
-var xhr = new XMLHttpRequest();
-xhr.open('GET', chrome.extension.getURL("collections.html"), true);
-xhr.onreadystatechange = function ()
+ if (document.querySelector(".tabsAside.pane") == null)
+ {
+ document.body.innerHTML += this.responseText;
+ chrome.runtime.sendMessage({ command: "loadData" }, function (collections)
+ {
+ if (document.querySelector(".tabsAside.pane section div") == null)
+ collections.forEach(i =>
+ {
+ AddCollection(i);
+ });
+ });
+ }
+
+ setTimeout(function ()
+ {
+ pane = document.querySelector(".tabsAside.pane");
+
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches)
+ pane.parentElement.setAttribute("darkmode", "");
+
+ document.querySelector(".tabsAside .saveTabs").onclick = SetTabsAside;
+
+
+ document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i =>
+ {
+ i.onclick = function () { window.open(i.value, '_blank'); };
+ });
+
+ document.querySelector(".tabsAside.background").addEventListener("click", function (event)
+ {
+ if (event.target == pane.parentElement)
+ {
+ pane.removeAttribute("opened");
+ pane.parentElement.style.opacity = 0;
+ document.body.style.overflow = "";
+ setTimeout(function ()
+ {
+ pane.parentElement.remove();
+ }, 300);
+ }
+ });
+
+ pane.setAttribute("opened", "");
+ pane.parentElement.style.opacity = 1;
+ document.body.style.overflow = "hidden";
+ }, 50);
+ };
+ xhr.send();
+ }
+ else
+ {
+ if (pane.hasAttribute("opened"))
+ {
+ pane.removeAttribute("opened");
+ pane.parentElement.style.opacity = 0;
+ document.body.style.overflow = "";
+ setTimeout(function ()
+ {
+ if (!pane.hasAttribute("opened"))
+ pane.parentElement.remove();
+ }, 300);
+ }
+ else
+ {
+ pane.setAttribute("opened", "");
+ pane.parentElement.style.opacity = 1;
+ }
+ }
+ }, 50);
+}
+
+function InitializeStandalone()
{
- if (this.status !== 200 || document.querySelector("#aside-pane") != null)
- return;
+ pane = document.querySelector(".tabsAside.pane");
- document.body.innerHTML += this.responseText.split("%EXTENSION_PATH%").join(chrome.extension.getURL(""));
-};
-xhr.send();
-
-function UpdateTheme()
-{
- var css = document.querySelector("#aside-pane #darkCSS")
if (window.matchMedia("(prefers-color-scheme: dark)").matches)
{
- css.removeAttribute("disabled");
+ pane.parentElement.setAttribute("darkmode", "");
+ document.querySelector("#icon").href = "icons/dark/empty/16.png";
}
- else
+
+ document.querySelector(".tabsAside .saveTabs").onclick = SetTabsAside;
+
+ document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i =>
{
- css.setAttribute("disabled", true);
+ i.onclick = function () { window.open(i.value, '_blank'); };
+ });
+
+ chrome.runtime.sendMessage({ command: "loadData" }, function (collections)
+ {
+ if (document.querySelector(".tabsAside.pane section div") == null)
+ collections.forEach(i =>
+ {
+ AddCollection(i);
+ });
+ });
+}
+
+function AddCollection(collection)
+{
+ var list = document.querySelector(".tabsAside section");
+ list.querySelector("h2").setAttribute("hidden", "");
+
+ var rawTabs = "";
+
+ for (var i = 0; i < collection.links.length; i++)
+ {
+ rawTabs +=
+ "" +
+ "" +
+ "" +
+ "" +
+ "" + collection.titles[i] + "" +
+ "" +
+ "" +
+ "";
}
-}
\ No newline at end of file
+
+ list.innerHTML += "" +
+ "" +
+ "Tabs: " + collection.links.length + "" +
+ "" + GetAgo(collection.timestamp) + "" +
+ "Restore tabs" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+
+ "" + rawTabs + "" +
+ ""
+
+ list.querySelectorAll("a").forEach(i =>
+ {
+ i.onclick = function () { RestoreTabs(i.parentElement.parentElement) };
+ });
+
+ list.querySelectorAll("div > div:last-child > div > span").forEach(i =>
+ {
+ i.onclick = function () { window.open(i.getAttribute("value"), '_blank'); };
+ })
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > button").forEach(i =>
+ {
+ i.onclick = function () { RemoveTabs(i.parentElement.parentElement) };
+ });
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > div > nav > button:first-child").forEach(i =>
+ {
+ i.onclick = function () { AddToFavorites(i.parentElement.parentElement.parentElement.parentElement) };
+ });
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > div > nav > button:last-child").forEach(i =>
+ {
+ i.onclick = function () { ShareTabs(i.parentElement.parentElement.parentElement.parentElement) };
+ });
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:last-child > div > div > button").forEach(i =>
+ {
+ i.onclick = function () { RemoveOneTab(i.parentElement.parentElement) };
+ });
+}
+
+function SetTabsAside()
+{
+ chrome.runtime.sendMessage({ command: "saveTabs" });
+}
+
+function RestoreTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "restoreTabs",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ },
+ function ()
+ {
+ if (collectionData.parentElement.children.length < 3)
+ {
+ RemoveElement(collectionData);
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(collectionData);
+ }
+ );
+}
+
+function RemoveTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "deleteTabs",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ },
+ function ()
+ {
+ if (collectionData.parentElement.children.length < 3)
+ {
+ RemoveElement(collectionData);
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(collectionData);
+ }
+ );
+}
+
+function AddToFavorites(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "toFavorites",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ });
+}
+
+function ShareTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "share",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ });
+}
+
+function RemoveOneTab(tabData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "removeTab",
+ collectionIndex: Array.prototype.slice.call(tabData.parentElement.parentElement.parentElement.children).indexOf(tabData.parentElement.parentElement) - 1,
+ tabIndex: Array.prototype.slice.call(tabData.parentElement.children).indexOf(tabData)
+ },
+ function ()
+ {
+ tabData.parentElement.previousElementSibling.children[0].textContent = "Tabs: " + tabData.parentElement.children.length - 1;
+ if (tabData.parentElement.children.length < 2)
+ {
+ RemoveElement(tabData.parentElement.parentElement);
+ if (document.querySelector("tabsAside.pane > section").children.length < 2)
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(tabData);
+ });
+}
+
+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";
+ else if (minutes < 60)
+ return Math.floor(minutes) + " minutes ago";
+
+ else if (Math.floor(minutes / 60) == 1)
+ return "1 hour 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";
+ 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";
+ 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";
+ 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";
+ else
+ return Math.floor(minutes / 365 / 24 / 60) + "years ago";
+}
+
+function RemoveElement(el)
+{
+ el.style.opacity = 0;
+ setTimeout(function ()
+ {
+ el.remove();
+ }, 200);
+}
+
+// TODO: Add more actions
+// TODO: Make backup system
+// TODO: Override websites' CSS
\ No newline at end of file
diff --git a/js/background.js b/js/background.js
index bc22dac..6e50923 100644
--- a/js/background.js
+++ b/js/background.js
@@ -1,33 +1,130 @@
+chrome.browserAction.onClicked.addListener(function (tab)
+{
+ if (tab.url.startsWith("http"))
+ {
+ chrome.tabs.executeScript(tab.id,
+ {
+ file: "js/aside-script.js",
+ allFrames: true,
+ runAt: "document_idle"
+ });
+ }
+ else if (tab.url.startsWith("chrome-extension") && tab.url.endsWith("TabsAside.html"))
+ chrome.tabs.remove(tab.id);
+ else
+ {
+ chrome.tabs.create({
+ url: chrome.extension.getURL("TabsAside.html"),
+ active: true
+ });
+ }
+});
+
+chrome.tabs.onActivated.addListener(function (activeInfo)
+{
+ chrome.tabs.query({ url: chrome.extension.getURL("TabsAside.html") }, function (result)
+ {
+ if (result.length)
+ setTimeout(function ()
+ {
+ result.forEach(i =>
+ {
+ if (activeInfo.tabId != i.id)
+ chrome.tabs.remove(i.id);
+ });
+ }, 200);
+ });
+});
+
function UpdateTheme()
{
if (window.matchMedia("(prefers-color-scheme: dark)").matches)
{
- chrome.browserAction.setIcon(
- {
- path:
- {
- "128": "icons/dark/empty/128.png",
- "48": "icons/dark/empty/48.png",
- "32": "icons/dark/empty/32.png",
- "16": "icons/dark/empty/16.png"
- }
- });
+ if (collections.length)
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/dark/full/128.png",
+ "48": "icons/dark/full/48.png",
+ "32": "icons/dark/full/32.png",
+ "16": "icons/dark/full/16.png"
+ }
+ });
+ else
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/dark/empty/128.png",
+ "48": "icons/dark/empty/48.png",
+ "32": "icons/dark/empty/32.png",
+ "16": "icons/dark/empty/16.png"
+ }
+ });
}
else
{
- chrome.browserAction.setIcon(
- {
- path:
- {
- "128": "icons/light/empty/128.png",
- "48": "icons/light/empty/48.png",
- "32": "icons/light/empty/32.png",
- "16": "icons/light/empty/16.png"
- }
- });
+ if (collections.length)
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/light/full/128.png",
+ "48": "icons/light/full/48.png",
+ "32": "icons/light/full/32.png",
+ "16": "icons/light/full/16.png"
+ }
+ });
+ else
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/light/empty/128.png",
+ "48": "icons/light/empty/48.png",
+ "32": "icons/light/empty/32.png",
+ "16": "icons/light/empty/16.png"
+ }
+ });
}
}
+var collections = JSON.parse(localStorage.getItem("sets"));
+if (collections == null)
+ collections = [];
+
+chrome.runtime.onMessage.addListener(function (message, sender, sendResponse)
+{
+ switch (message.command)
+ {
+ case "loadData":
+ sendResponse(collections);
+ break;
+ case "saveTabs":
+ SaveCollection();
+ break;
+ case "restoreTabs":
+ RestoreCollection(message.collectionIndex);
+ sendResponse();
+ break;
+ case "deleteTabs":
+ DeleteCollection(message.collectionIndex);
+ sendResponse();
+ break;
+ case "removeTab":
+ RemoveTab(message.collectionIndex, message.tabIndex);
+ sendResponse();
+ break;
+ case "toFavorites":
+ AddTabsToFavorites(message.collectionIndex);
+ break;
+ case "share":
+ ShareTabs(message.collectionIndex);
+ break;
+ }
+});
+
UpdateTheme();
chrome.windows.onCreated.addListener(UpdateTheme);
chrome.windows.onRemoved.addListener(UpdateTheme);
@@ -45,6 +142,117 @@ chrome.tabs.onDetached.addListener(UpdateTheme);
chrome.tabs.onAttached.addListener(UpdateTheme);
chrome.tabs.onRemoved.addListener(UpdateTheme);
chrome.tabs.onReplaced.addListener(UpdateTheme);
-chrome.tabs.onZoomChange.addListener(UpdateTheme);
-// TODO: Load saved tabs
\ No newline at end of file
+function SaveCollection()
+{
+ chrome.tabs.query({ currentWindow: true }, function (tabs)
+ {
+ tabs = tabs.filter(i => !i.url.startsWith("chrome-extension") && !i.url.endsWith("TabsAside.html"));
+
+ var collection =
+ {
+ timestamp: Date.now(),
+ tabsCount: tabs.length,
+ titles: tabs.map(tab => tab.title ?? ""),
+ links: tabs.map(tab => tab.url ?? ""),
+ icons: tabs.map(tab => tab.favIconUrl ?? "")//,
+ //tumbnails: tabs.map(tab => chrome.tabs.captureVisibleTab)
+ };
+
+ var rawData;
+ if (localStorage.getItem("sets") === null)
+ rawData = [collection];
+ else
+ {
+ rawData = JSON.parse(localStorage.getItem("sets"));
+ rawData.unshift(collection);
+ }
+
+ localStorage.setItem("sets", JSON.stringify(rawData));
+
+ collections = JSON.parse(localStorage.getItem("sets"));
+
+ chrome.tabs.create({});
+ chrome.tabs.remove(tabs.map(tab => tab.id));
+ });
+
+ UpdateTheme();
+}
+
+function DeleteCollection(collectionIndex)
+{
+ collections = collections.filter(i => i != collections[collectionIndex]);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
+
+function RestoreCollection(collectionIndex)
+{
+ collections[collectionIndex].links.forEach(i =>
+ {
+ chrome.tabs.create(
+ {
+ url: i,
+ active: false
+ });
+ });
+
+ collections = collections.filter(i => i != collections[collectionIndex]);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
+
+function AddTabsToFavorites(collectionIndex)
+{
+ alert("Adding to favorites");
+ /*for (var i = 0; i < collections[collectionIndex].links.length; i++)
+ {
+ chrome.bookmarks.create(
+ {
+ title: collections[collectionIndex].titles[i],
+ url: collections[collectionIndex].links[i],
+ });
+ }*/
+}
+
+function ShareTabs(collectionId)
+{
+ alert("Sharing");
+}
+
+function RemoveTab(collectionIndex, tabIndex)
+{
+ var set = collections[collectionIndex];
+ if (--set.tabsCount < 1)
+ {
+ collections = collections.filter(i => i != set);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+ return;
+ }
+
+ var titles = [];
+ var links = [];
+ var icons = [];
+
+ for (var i = set.links.length - 1; i >= 0; i--)
+ {
+ if (i == tabIndex)
+ continue;
+
+ titles.unshift(set.titles[i]);
+ links.unshift(set.links[i]);
+ icons.unshift(set.icons[i]);
+ }
+
+ set.titles = titles;
+ set.links = links;
+ set.icons = icons;
+
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index b2e32af..e5bfb34 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,9 +2,15 @@
"name": "Tabs Aside",
"version": "0.1",
"manifest_version": 2,
- "description": "Classic Microsoft Edge \"Tabs Aside\" feature",
+ "description": "Classic Microsoft Edge \"Tabs Aside\" feature for Chromium browers",
"author": "Michael \"XFox\" Gordeev",
- "permissions": [ "tabs" ],
+ "permissions":
+ [
+ "tabs",
+ "unlimitedStorage",
+ "",
+ "bookmarks"
+ ],
"icons":
{
@@ -13,27 +19,25 @@
"32": "icons/light/empty/32.png",
"16": "icons/light/empty/16.png"
},
+ "browser_action": { "default_icon": "icons/light/empty/32.png" },
"web_accessible_resources": [ "*" ],
- "browser_action":
- {
- "default_popup": "popup/popup.html"
- },
"content_scripts":
[
{
"matches": [ "" ],
- "js": [ "js/aside-script.js" ],
"css":
[
"css/style.css",
- "css/style.generic.css"
- ]
+ "css/style.generic.css",
+ "css/style.dark.css"
+ ],
+ "run_at": "document_start"
}
],
"background":
{
- "scripts": ["js/background.js"],
+ "scripts": [ "js/background.js" ],
"persistent": true
}
}
\ No newline at end of file
diff --git a/popup/popup.html b/popup/popup.html
deleted file mode 100644
index 9dadfd8..0000000
--- a/popup/popup.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/popup/trigger.js b/popup/trigger.js
deleted file mode 100644
index ce46c81..0000000
--- a/popup/trigger.js
+++ /dev/null
@@ -1,11 +0,0 @@
-chrome.tabs.query({ active: true, currentWindow: true }, function(tabs)
-{
- chrome.tabs.sendMessage(
- tabs[0].id,
- { },
- function()
- {
- window.close();
- }
- );
-});
\ No newline at end of file
+",
+ "bookmarks"
+ ],
"icons":
{
@@ -13,27 +19,25 @@
"32": "icons/light/empty/32.png",
"16": "icons/light/empty/16.png"
},
+ "browser_action": { "default_icon": "icons/light/empty/32.png" },
"web_accessible_resources": [ "*" ],
- "browser_action":
- {
- "default_popup": "popup/popup.html"
- },
"content_scripts":
[
{
"matches": [ "" ],
- "js": [ "js/aside-script.js" ],
"css":
[
"css/style.css",
- "css/style.generic.css"
- ]
+ "css/style.generic.css",
+ "css/style.dark.css"
+ ],
+ "run_at": "document_start"
}
],
"background":
{
- "scripts": ["js/background.js"],
+ "scripts": [ "js/background.js" ],
"persistent": true
}
}
\ No newline at end of file
diff --git a/popup/popup.html b/popup/popup.html
deleted file mode 100644
index 9dadfd8..0000000
--- a/popup/popup.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/popup/trigger.js b/popup/trigger.js
deleted file mode 100644
index ce46c81..0000000
--- a/popup/trigger.js
+++ /dev/null
@@ -1,11 +0,0 @@
-chrome.tabs.query({ active: true, currentWindow: true }, function(tabs)
-{
- chrome.tabs.sendMessage(
- tabs[0].id,
- { },
- function()
- {
- window.close();
- }
- );
-});
\ No newline at end of file
+
\ No newline at end of file
diff --git a/css/style.css b/css/style.css
index e2ff917..e2a1831 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1,178 +1,222 @@
-.aside.pane
+.tabsAside.background
{
- font-family: 'Segoe UI', 'Segoe MDL2 Assets';
- user-select: none;
- position: fixed;
- z-index: 999;
-
- right: 0px;
- top: 0px;
- width: 40%;
- min-width: 500px;
- min-height: 100%;
-
- background-color: #f7f7f7;
- border: 1px solid rgba(100, 100, 100, .5);
- box-sizing: border-box;
- box-shadow: 6px 0px 12px black;
-
- transition: .2s;
+ z-index: 9999 !important;
+ background-color: rgba(255, 255, 255, .5) !important;
+ position: fixed !important;
+ top: 0 !important;
+ bottom: 0 !important;
+ right: 0 !important;
+ left: 0 !important;
+ transition: .2s !important;
+ opacity: 0;
}
- .aside.pane > .header
+.tabsAside.pane
+{
+ font-family: 'Segoe UI', 'Segoe MDL2 Assets' !important;
+ user-select: none !important;
+ position: fixed !important;
+
+ right: 0px !important;
+ top: 0px !important;
+ bottom: 0px !important;
+ overflow: auto !important;
+
+ width: 40%;
+ min-width: 500px !important;
+
+ background-color: #f7f7f7 !important;
+ border: 1px solid rgba(100, 100, 100, .5) !important;
+ border-width: 0px 0px 0px 1px !important;
+ box-sizing: border-box !important;
+ box-shadow: 6px 0px 12px black !important;
+
+ font-size: small !important;
+
+ transform: translateX(110%); /* Pane is hidden */
+ transition: .2s !important;
+}
+
+ .tabsAside.pane[opened]
{
- margin: 20px 40px;
+ transform: translateX(0px) !important;
}
- .aside.pane > .header > .title
+
+ /* Pane header*/
+ .tabsAside.pane > header
+ {
+ margin: 20px 40px !important;
+ }
+ .tabsAside.pane > header > div
{
- display: grid;
- grid-template-columns: 1fr auto;
+ display: grid !important;
+ grid-template-columns: 1fr auto !important;
}
- .aside.pane > .header > .title > h1
+ .tabsAside.pane > header > div > h1
{
- margin: 10px 0px;
- font-weight: normal;
- font-size: 21pt;
+ margin: 10px 0px !important;
+ font-weight: normal !important;
+ font-size: 21pt !important;
}
- .aside.pane > .header > .title > button
+ .tabsAside.pane > header > div > button
{
- margin: auto;
+ margin: auto !important;
}
- .aside.pane > .header > .title > nav
+ .tabsAside.pane > header > div > nav
{
- top: 70px;
- right: 40px;
+ top: 70px !important;
+ right: 40px !important;
}
- .aside.pane > .header > .title > nav > div
+ .tabsAside.pane > header > div > nav > div
{
- box-shadow: 0px 4px 5px -2px rgba(100, 100, 100, .5);
+ box-shadow: 0px 4px 5px -2px rgba(100, 100, 100, .5) !important;
}
- .aside.pane > .header > .title > nav > p
+ .tabsAside.pane > header > div > nav > p
{
- margin: 10px;
+ margin: 10px !important;
}
- .aside.pane > .header > hr
+ .tabsAside.pane > header > hr
{
- border: 1px solid #8a8a8a;
+ border: 1px solid #8a8a8a !important;
}
- .aside.pane > #content > h2
+ .tabsAside.pane > section > h2
{
- margin: 0px 40px;
- font-weight: normal;
+ margin: 0px 40px !important;
+ font-weight: normal !important;
}
- .aside.pane > #content > .set > .setHeader
+ /* Collection header */
+ .tabsAside.pane > section > div
{
- margin: 0px 20px;
- display: grid;
- grid-template-columns: 1fr auto;
+ transition: .2s !important;
}
- .aside.pane > #content > .set > .setHeader small
+ .tabsAside.pane > section > div > div:first-child
+ {
+ margin: 0px 20px !important;
+ display: grid !important;
+ grid-template-columns: auto 1fr auto auto auto !important;
+ grid-column-gap: 10px !important;
+ align-items: center !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > small
{
- color: gray;
+ color: gray !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > span
+ {
+ font-weight: 600 !important;
+ }
+
+ .tabsAside.pane > section > div > div:first-child > a
+ {
+ font-size: 11pt !important;
}
- .aside.pane > #content > .set > .setHeader span
+ .tabsAside.pane > section > div > div:first-child > div
{
- font-weight: 600;
+ display: none !important; /* TODO: Implement this menu */
}
- .aside.pane > #content > .set > .setHeader a
- {
- font-size: 11pt;
- font-weight: 500;
- }
- .aside.pane > #content > .set > .setHeader nav
- {
- width: 200px;
- margin-top: 10px;
- right: 50px;
- }
-
- .aside.pane > #content > .set > .setHeader > div:first-child
- {
- margin: auto 0px;
- }
-
- .aside.pane > #content > .set > .collection
- {
- padding: 10px 0px;
- padding-left: 40px;
- white-space: nowrap;
- overflow: auto;
- }
-
- .aside.pane > #content > .set > .collection > .item
+ .tabsAside.pane > section > div > div:first-child > div > nav
{
- width: 175px;
- height: 148px;
- margin: 5px;
-
- background-color: #c2c2c2;
- background-image: url("../images/tab_thumbnail.png");
-
- display: inline-grid;
- grid-template-rows: 1fr auto;
-
- box-shadow: 0px 0px 5px rgba(100, 100, 100, .5);
- transition: .25s;
- cursor: pointer;
+ width: 200px !important;
+ margin-top: 10px !important;
+ right: 50px !important;
}
- .aside.pane > #content > .set > .collection > .item:hover
+
+ /* Tabs collection */
+ .tabsAside.pane > section > div > div:last-child
+ {
+ margin: 0px 0px 0px 20px !important;
+ padding: 10px 40px 10px 20px !important;
+ white-space: nowrap !important;
+ overflow: auto !important;
+ }
+
+ .tabsAside.pane > section > div > div:last-child:hover::-webkit-scrollbar-thumb
+ {
+ visibility: visible !important;
+ }
+
+ .tabsAside.pane > section > div > div:last-child > div
+ {
+ width: 175px !important;
+ height: 148px !important;
+ margin: 5px !important;
+
+ background-color: #c2c2c2 !important;
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail.png");
+
+ display: inline-grid !important;
+ grid-template-rows: 1fr auto !important;
+
+ box-shadow: 0px 0px 5px rgba(100, 100, 100, .5) !important;
+ transition: .25s !important;
+ cursor: pointer !important;
+ }
+ .tabsAside.pane > section > div > div:last-child > div:hover
{
- filter: brightness(120%);
- box-shadow: 0px 0px 15px rgba(100, 100, 100, .5);
+ filter: brightness(120%) !important;
+ box-shadow: 0px 0px 15px rgba(100, 100, 100, .5) !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption
+ .tabsAside.pane > section > div > div:last-child > div > div
{
- background-color: rgba(233, 233, 233, .75);
- grid-row: 2;
- display: grid;
- grid-template-columns: auto 1fr auto;
+ background-color: rgba(233, 233, 233, .75) !important;
+ grid-row: 2 !important;
+ display: grid !important;
+ grid-template-columns: auto 1fr auto !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > button
+ .tabsAside.pane > section > div > div:last-child > div > div > button
{
- margin: auto;
- margin-right: 5px;
- visibility: hidden;
+ margin: auto !important;
+ margin-right: 5px !important;
+ display: none !important;
}
- .aside.pane > #content > .set > .collection > .item:hover > .caption > button
+ .tabsAside.pane > section > div > div:last-child > div:hover > div > button
{
- visibility: visible;
+ display: initial !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > div
+ .tabsAside.pane > section > div > div:last-child > div > div > div
{
- width: 20px;
- height: 20px;
- margin: 10px;
+ width: 20px !important;
+ height: 20px !important;
+ margin: 10px !important;
- background-image: url("../images/tab_icon.png");
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon.png");
+ background-size: 20px !important;
}
- .aside.pane > #content > .set > .collection > .item > .caption > span
+ .tabsAside.pane > section > div > div:last-child > div > div > span
{
- margin: auto 0px;
+ overflow: hidden !important;
+ margin: auto 0px !important;
+ margin-right: 10px !important;
+ }
+ .tabsAside.pane > section > div > div:last-child > div:hover > div > span
+ {
+ margin-right: 5px !important;
}
@media only screen and (max-width: 500px)
{
- .aside.pane
+ .tabsAside.pane
{
- right: initial;
- width: 100%;
- left: 0px;
- min-width: initial;
+ width: initial !important;
+ left: 0px !important;
+ min-width: initial !important;
}
}
\ No newline at end of file
diff --git a/css/style.dark.css b/css/style.dark.css
index 2350d46..11f541d 100644
--- a/css/style.dark.css
+++ b/css/style.dark.css
@@ -1,54 +1,59 @@
-.aside.pane
+.tabsAside[darkmode].background
+{
+ background-color: rgba(0, 0, 0, .5) !important;
+}
+
+.tabsAside[darkmode] .pane
{
- background-color: #333333;
- color: white;
+ background-color: #333333 !important;
+ color: white !important;
}
-.aside button
-{
- color: white;
-}
-.aside button:hover
-{
- background-color: gray;
-}
-.aside button:active
-{
- background-color: dimgray;
-}
-
-.aside.pane > #content > .set > .setHeader small
-{
- color: lightgray;
-}
-
-.aside ::-webkit-scrollbar-thumb
-{
- background: gray;
- border-radius: 3px;
-}
- ::-webkit-scrollbar-thumb:hover
+ .tabsAside[darkmode] .pane button
{
- background: dimgray;
+ color: white !important;
+ }
+ .tabsAside[darkmode] .pane button:hover
+ {
+ background-color: gray !important;
+ }
+ .tabsAside[darkmode] .pane button:active
+ {
+ background-color: dimgray !important;
}
-.aside.pane > #content > .set > .collection > .item
-{
- background-color: #0c0c0c;
- background-image: url("../images/tab_thumbnail_dark.png");
-}
+ .tabsAside[darkmode] .pane > section > div > div:first-child > div:first-child > small
+ {
+ color: lightgray !important;
+ }
-.aside.pane > #content > .set > .collection > .item > .caption
-{
- background-color: rgba(50, 50, 50, .75);
-}
+ .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb
+ {
+ background: gray !important;
+ border-radius: 3px !important;
+ }
+ .tabsAside[darkmode] .pane ::-webkit-scrollbar-thumb:hover
+ {
+ background: dimgray !important;
+ }
-.aside nav
-{
- background-color: #3f3f3f;
-}
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div
+ {
+ background-color: #0c0c0c !important;
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_thumbnail_dark.png");
+ }
-.aside.pane > #content > .set > .collection > .item > .caption > div
-{
- background-image: url("../images/tab_icon_dark.png");
-}
\ No newline at end of file
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div > div
+ {
+ background-color: rgba(50, 50, 50, .75) !important;
+ }
+
+ .tabsAside[darkmode] .pane > section > div > div:last-child > div > div > div
+ {
+ background-image: url("chrome-extension://__MSG_@@extension_id__/images/tab_icon_dark.png");
+ }
+
+ .tabsAside[darkmode] .pane nav
+ {
+ background-color: #3f3f3f !important;
+ }
\ No newline at end of file
diff --git a/css/style.generic.css b/css/style.generic.css
index 4057a94..0de65e8 100644
--- a/css/style.generic.css
+++ b/css/style.generic.css
@@ -1,123 +1,106 @@
-.aside ::-webkit-scrollbar
+/* Custom scrollbar */
+.tabsAside ::-webkit-scrollbar
{
- height: 6px;
+ height: 6px !important;
}
-.aside ::-webkit-scrollbar-thumb
+.tabsAside ::-webkit-scrollbar-thumb
{
- background: darkgray;
- border-radius: 3px;
+ visibility: hidden !important;
+ background: darkgray !important;
+ border-radius: 3px !important;
}
- ::-webkit-scrollbar-thumb:hover
+ .tabsAside ::-webkit-scrollbar-thumb:hover
{
- background: gray;
+ background: gray !important;
}
-.aside a
+.tabsAside::-webkit-scrollbar
{
- font-family: 'Segoe UI', 'Segoe MDL2 Assets';
- color: #0078d7;
-}
- .aside a:hover
- {
- text-decoration: underline;
- cursor: pointer;
- }
-
-.aside .slider
-{
- position: relative;
- display: inline-block;
-
- border-radius: 13px;
- width: 50px;
- height: 26px;
-
- background-color: #cccccc;
- transition: .4s;
+ width: 6px !important;
}
- .aside .slider:before
- {
- position: absolute;
- content: "";
-
- height: 16px;
- width: 16px;
- left: 5px;
- top: 5px;
- border-radius: 50%;
-
- margin: auto;
-
- background-color: white;
- transition: .3s;
- }
-
- .aside input:checked + .slider,
- .aside input:focus + .slider
- {
- background-color: #0078d7;
- }
-
- .aside input:checked + .slider:before
- {
- transform: translateX(24px);
- background-color: #3f3f3f;
- }
-
-.aside nav
+.tabsAside::-webkit-scrollbar-thumb
{
- font-family: 'Segoe UI';
- user-select: none;
+ background: gray !important;
+ border-radius: 3px !important;
+}
+ .tabsAside::-webkit-scrollbar-thumb:hover
+ {
+ background: darkgray !important;
+ }
- position: absolute;
- width: 250px;
+/* Links style */
+.tabsAside a
+{
+ font-family: 'Segoe UI', 'Segoe MDL2 Assets' !important;
+ color: #0078d7 !important;
+}
+ .tabsAside a:hover
+ {
+ text-decoration: underline !important;
+ cursor: pointer !important;
+ }
- box-shadow: 0px 0px 10px black;
- background-color: white;
- border-radius: 5px;
+ .tabsAside a:visited
+ {
+ color: #0078d7 !important;
+ }
- z-index: 10;
+/* Buttons style */
+.tabsAside button
+{
+ font-family: 'Segoe MDL2 Assets' !important;
+ width: 32px !important;
+ height: 32px !important;
+ background-color: transparent !important;
+ border: none !important;
+ cursor: pointer !important;
+}
+ .tabsAside button:hover
+ {
+ background-color: #c6c6c6 !important;
+ }
+ .tabsAside button:active
+ {
+ background-color: gray !important;
+ }
- visibility: hidden;
+/* Context menus style */
+.tabsAside nav
+{
+ font-family: 'Segoe UI' !important;
+ user-select: none !important;
+
+ position: absolute !important;
+ width: 250px !important;
+
+ box-shadow: 0px 0px 10px black !important;
+ background-color: white !important;
+ border-radius: 5px !important;
+
+ z-index: 10 !important;
+
+ visibility: hidden !important;
}
- .aside nav button
+ .tabsAside nav button
{
- font-family: 'Segoe UI';
- cursor: pointer;
- background-color: transparent;
- border: none;
+ font-family: 'Segoe UI' !important;
+ cursor: pointer !important;
+ background-color: transparent !important;
+ border: none !important;
- font-size: medium;
- text-align: start;
+ font-size: medium !important;
+ text-align: start !important;
- padding: 10px;
- width: 100%;
- height: initial;
+ padding: 10px !important;
+ width: 100% !important;
+ height: initial !important;
}
- .aside button + nav:active,
- .aside button:focus + nav
+ .tabsAside button + nav:active,
+ .tabsAside button:focus + nav
{
- visibility: visible;
- }
-
-.aside button
-{
- font-family: 'Segoe MDL2 Assets';
- width: 32px;
- height: 32px;
- background-color: transparent;
- border: none;
- cursor: pointer;
-}
- .aside button:hover
- {
- background-color: #c6c6c6;
- }
- .aside button:active
- {
- background-color: gray;
+ visibility: visible !important;
}
\ No newline at end of file
diff --git a/js/aside-script.js b/js/aside-script.js
index 2c02062..370b616 100644
--- a/js/aside-script.js
+++ b/js/aside-script.js
@@ -1,40 +1,327 @@
-chrome.runtime.onMessage.addListener(function(sender, request, sendResponse)
+if (document.location.protocol == "chrome-extension:")
+ InitializeStandalone();
+else
{
- var pane = document.querySelector("#aside-pane");
- if (pane.style.transform == "translateX(110%)")
+ setTimeout(function ()
{
- pane.style.transform = "translateX(0%)";
- }
- else
- {
- pane.style.transform = "translateX(110%)";
- }
+ var pane = document.querySelector(".tabsAside.pane");
- UpdateTheme();
-
- sendResponse();
-});
+ if (pane == null)
+ {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', chrome.extension.getURL("collections.html"), true);
+ xhr.onreadystatechange = function ()
+ {
+ if (this.status !== 200 || document.querySelector("#aside-pane") != null)
+ return;
-var xhr = new XMLHttpRequest();
-xhr.open('GET', chrome.extension.getURL("collections.html"), true);
-xhr.onreadystatechange = function ()
+ if (document.querySelector(".tabsAside.pane") == null)
+ {
+ document.body.innerHTML += this.responseText;
+ chrome.runtime.sendMessage({ command: "loadData" }, function (collections)
+ {
+ if (document.querySelector(".tabsAside.pane section div") == null)
+ collections.forEach(i =>
+ {
+ AddCollection(i);
+ });
+ });
+ }
+
+ setTimeout(function ()
+ {
+ pane = document.querySelector(".tabsAside.pane");
+
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches)
+ pane.parentElement.setAttribute("darkmode", "");
+
+ document.querySelector(".tabsAside .saveTabs").onclick = SetTabsAside;
+
+
+ document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i =>
+ {
+ i.onclick = function () { window.open(i.value, '_blank'); };
+ });
+
+ document.querySelector(".tabsAside.background").addEventListener("click", function (event)
+ {
+ if (event.target == pane.parentElement)
+ {
+ pane.removeAttribute("opened");
+ pane.parentElement.style.opacity = 0;
+ document.body.style.overflow = "";
+ setTimeout(function ()
+ {
+ pane.parentElement.remove();
+ }, 300);
+ }
+ });
+
+ pane.setAttribute("opened", "");
+ pane.parentElement.style.opacity = 1;
+ document.body.style.overflow = "hidden";
+ }, 50);
+ };
+ xhr.send();
+ }
+ else
+ {
+ if (pane.hasAttribute("opened"))
+ {
+ pane.removeAttribute("opened");
+ pane.parentElement.style.opacity = 0;
+ document.body.style.overflow = "";
+ setTimeout(function ()
+ {
+ if (!pane.hasAttribute("opened"))
+ pane.parentElement.remove();
+ }, 300);
+ }
+ else
+ {
+ pane.setAttribute("opened", "");
+ pane.parentElement.style.opacity = 1;
+ }
+ }
+ }, 50);
+}
+
+function InitializeStandalone()
{
- if (this.status !== 200 || document.querySelector("#aside-pane") != null)
- return;
+ pane = document.querySelector(".tabsAside.pane");
- document.body.innerHTML += this.responseText.split("%EXTENSION_PATH%").join(chrome.extension.getURL(""));
-};
-xhr.send();
-
-function UpdateTheme()
-{
- var css = document.querySelector("#aside-pane #darkCSS")
if (window.matchMedia("(prefers-color-scheme: dark)").matches)
{
- css.removeAttribute("disabled");
+ pane.parentElement.setAttribute("darkmode", "");
+ document.querySelector("#icon").href = "icons/dark/empty/16.png";
}
- else
+
+ document.querySelector(".tabsAside .saveTabs").onclick = SetTabsAside;
+
+ document.querySelectorAll(".tabsAside.pane > header nav button").forEach(i =>
{
- css.setAttribute("disabled", true);
+ i.onclick = function () { window.open(i.value, '_blank'); };
+ });
+
+ chrome.runtime.sendMessage({ command: "loadData" }, function (collections)
+ {
+ if (document.querySelector(".tabsAside.pane section div") == null)
+ collections.forEach(i =>
+ {
+ AddCollection(i);
+ });
+ });
+}
+
+function AddCollection(collection)
+{
+ var list = document.querySelector(".tabsAside section");
+ list.querySelector("h2").setAttribute("hidden", "");
+
+ var rawTabs = "";
+
+ for (var i = 0; i < collection.links.length; i++)
+ {
+ rawTabs +=
+ "" +
+ "" +
+ "
";
}
-}
\ No newline at end of file
+
+ list.innerHTML += "" +
+ "" +
+ "" + collection.titles[i] + "" +
+ "" +
+ "
" +
+ "" +
+ "
"
+
+ list.querySelectorAll("a").forEach(i =>
+ {
+ i.onclick = function () { RestoreTabs(i.parentElement.parentElement) };
+ });
+
+ list.querySelectorAll("div > div:last-child > div > span").forEach(i =>
+ {
+ i.onclick = function () { window.open(i.getAttribute("value"), '_blank'); };
+ })
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > button").forEach(i =>
+ {
+ i.onclick = function () { RemoveTabs(i.parentElement.parentElement) };
+ });
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > div > nav > button:first-child").forEach(i =>
+ {
+ i.onclick = function () { AddToFavorites(i.parentElement.parentElement.parentElement.parentElement) };
+ });
+ document.querySelectorAll(".tabsAside.pane > section > div > div:first-child > div > nav > button:last-child").forEach(i =>
+ {
+ i.onclick = function () { ShareTabs(i.parentElement.parentElement.parentElement.parentElement) };
+ });
+
+ document.querySelectorAll(".tabsAside.pane > section > div > div:last-child > div > div > button").forEach(i =>
+ {
+ i.onclick = function () { RemoveOneTab(i.parentElement.parentElement) };
+ });
+}
+
+function SetTabsAside()
+{
+ chrome.runtime.sendMessage({ command: "saveTabs" });
+}
+
+function RestoreTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "restoreTabs",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ },
+ function ()
+ {
+ if (collectionData.parentElement.children.length < 3)
+ {
+ RemoveElement(collectionData);
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(collectionData);
+ }
+ );
+}
+
+function RemoveTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "deleteTabs",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ },
+ function ()
+ {
+ if (collectionData.parentElement.children.length < 3)
+ {
+ RemoveElement(collectionData);
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(collectionData);
+ }
+ );
+}
+
+function AddToFavorites(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "toFavorites",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ });
+}
+
+function ShareTabs(collectionData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "share",
+ collectionIndex: Array.prototype.slice.call(collectionData.parentElement.children).indexOf(collectionData) - 1
+ });
+}
+
+function RemoveOneTab(tabData)
+{
+ chrome.runtime.sendMessage(
+ {
+ command: "removeTab",
+ collectionIndex: Array.prototype.slice.call(tabData.parentElement.parentElement.parentElement.children).indexOf(tabData.parentElement.parentElement) - 1,
+ tabIndex: Array.prototype.slice.call(tabData.parentElement.children).indexOf(tabData)
+ },
+ function ()
+ {
+ tabData.parentElement.previousElementSibling.children[0].textContent = "Tabs: " + tabData.parentElement.children.length - 1;
+ if (tabData.parentElement.children.length < 2)
+ {
+ RemoveElement(tabData.parentElement.parentElement);
+ if (document.querySelector("tabsAside.pane > section").children.length < 2)
+ setTimeout(function ()
+ {
+ document.querySelector(".tabsAside.pane > section > h2").removeAttribute("hidden");
+ }, 250);
+ }
+ else
+ RemoveElement(tabData);
+ });
+}
+
+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";
+ else if (minutes < 60)
+ return Math.floor(minutes) + " minutes ago";
+
+ else if (Math.floor(minutes / 60) == 1)
+ return "1 hour 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";
+ 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";
+ 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";
+ 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";
+ else
+ return Math.floor(minutes / 365 / 24 / 60) + "years ago";
+}
+
+function RemoveElement(el)
+{
+ el.style.opacity = 0;
+ setTimeout(function ()
+ {
+ el.remove();
+ }, 200);
+}
+
+// TODO: Add more actions
+// TODO: Make backup system
+// TODO: Override websites' CSS
\ No newline at end of file
diff --git a/js/background.js b/js/background.js
index bc22dac..6e50923 100644
--- a/js/background.js
+++ b/js/background.js
@@ -1,33 +1,130 @@
+chrome.browserAction.onClicked.addListener(function (tab)
+{
+ if (tab.url.startsWith("http"))
+ {
+ chrome.tabs.executeScript(tab.id,
+ {
+ file: "js/aside-script.js",
+ allFrames: true,
+ runAt: "document_idle"
+ });
+ }
+ else if (tab.url.startsWith("chrome-extension") && tab.url.endsWith("TabsAside.html"))
+ chrome.tabs.remove(tab.id);
+ else
+ {
+ chrome.tabs.create({
+ url: chrome.extension.getURL("TabsAside.html"),
+ active: true
+ });
+ }
+});
+
+chrome.tabs.onActivated.addListener(function (activeInfo)
+{
+ chrome.tabs.query({ url: chrome.extension.getURL("TabsAside.html") }, function (result)
+ {
+ if (result.length)
+ setTimeout(function ()
+ {
+ result.forEach(i =>
+ {
+ if (activeInfo.tabId != i.id)
+ chrome.tabs.remove(i.id);
+ });
+ }, 200);
+ });
+});
+
function UpdateTheme()
{
if (window.matchMedia("(prefers-color-scheme: dark)").matches)
{
- chrome.browserAction.setIcon(
- {
- path:
- {
- "128": "icons/dark/empty/128.png",
- "48": "icons/dark/empty/48.png",
- "32": "icons/dark/empty/32.png",
- "16": "icons/dark/empty/16.png"
- }
- });
+ if (collections.length)
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/dark/full/128.png",
+ "48": "icons/dark/full/48.png",
+ "32": "icons/dark/full/32.png",
+ "16": "icons/dark/full/16.png"
+ }
+ });
+ else
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/dark/empty/128.png",
+ "48": "icons/dark/empty/48.png",
+ "32": "icons/dark/empty/32.png",
+ "16": "icons/dark/empty/16.png"
+ }
+ });
}
else
{
- chrome.browserAction.setIcon(
- {
- path:
- {
- "128": "icons/light/empty/128.png",
- "48": "icons/light/empty/48.png",
- "32": "icons/light/empty/32.png",
- "16": "icons/light/empty/16.png"
- }
- });
+ if (collections.length)
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/light/full/128.png",
+ "48": "icons/light/full/48.png",
+ "32": "icons/light/full/32.png",
+ "16": "icons/light/full/16.png"
+ }
+ });
+ else
+ chrome.browserAction.setIcon(
+ {
+ path:
+ {
+ "128": "icons/light/empty/128.png",
+ "48": "icons/light/empty/48.png",
+ "32": "icons/light/empty/32.png",
+ "16": "icons/light/empty/16.png"
+ }
+ });
}
}
+var collections = JSON.parse(localStorage.getItem("sets"));
+if (collections == null)
+ collections = [];
+
+chrome.runtime.onMessage.addListener(function (message, sender, sendResponse)
+{
+ switch (message.command)
+ {
+ case "loadData":
+ sendResponse(collections);
+ break;
+ case "saveTabs":
+ SaveCollection();
+ break;
+ case "restoreTabs":
+ RestoreCollection(message.collectionIndex);
+ sendResponse();
+ break;
+ case "deleteTabs":
+ DeleteCollection(message.collectionIndex);
+ sendResponse();
+ break;
+ case "removeTab":
+ RemoveTab(message.collectionIndex, message.tabIndex);
+ sendResponse();
+ break;
+ case "toFavorites":
+ AddTabsToFavorites(message.collectionIndex);
+ break;
+ case "share":
+ ShareTabs(message.collectionIndex);
+ break;
+ }
+});
+
UpdateTheme();
chrome.windows.onCreated.addListener(UpdateTheme);
chrome.windows.onRemoved.addListener(UpdateTheme);
@@ -45,6 +142,117 @@ chrome.tabs.onDetached.addListener(UpdateTheme);
chrome.tabs.onAttached.addListener(UpdateTheme);
chrome.tabs.onRemoved.addListener(UpdateTheme);
chrome.tabs.onReplaced.addListener(UpdateTheme);
-chrome.tabs.onZoomChange.addListener(UpdateTheme);
-// TODO: Load saved tabs
\ No newline at end of file
+function SaveCollection()
+{
+ chrome.tabs.query({ currentWindow: true }, function (tabs)
+ {
+ tabs = tabs.filter(i => !i.url.startsWith("chrome-extension") && !i.url.endsWith("TabsAside.html"));
+
+ var collection =
+ {
+ timestamp: Date.now(),
+ tabsCount: tabs.length,
+ titles: tabs.map(tab => tab.title ?? ""),
+ links: tabs.map(tab => tab.url ?? ""),
+ icons: tabs.map(tab => tab.favIconUrl ?? "")//,
+ //tumbnails: tabs.map(tab => chrome.tabs.captureVisibleTab)
+ };
+
+ var rawData;
+ if (localStorage.getItem("sets") === null)
+ rawData = [collection];
+ else
+ {
+ rawData = JSON.parse(localStorage.getItem("sets"));
+ rawData.unshift(collection);
+ }
+
+ localStorage.setItem("sets", JSON.stringify(rawData));
+
+ collections = JSON.parse(localStorage.getItem("sets"));
+
+ chrome.tabs.create({});
+ chrome.tabs.remove(tabs.map(tab => tab.id));
+ });
+
+ UpdateTheme();
+}
+
+function DeleteCollection(collectionIndex)
+{
+ collections = collections.filter(i => i != collections[collectionIndex]);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
+
+function RestoreCollection(collectionIndex)
+{
+ collections[collectionIndex].links.forEach(i =>
+ {
+ chrome.tabs.create(
+ {
+ url: i,
+ active: false
+ });
+ });
+
+ collections = collections.filter(i => i != collections[collectionIndex]);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
+
+function AddTabsToFavorites(collectionIndex)
+{
+ alert("Adding to favorites");
+ /*for (var i = 0; i < collections[collectionIndex].links.length; i++)
+ {
+ chrome.bookmarks.create(
+ {
+ title: collections[collectionIndex].titles[i],
+ url: collections[collectionIndex].links[i],
+ });
+ }*/
+}
+
+function ShareTabs(collectionId)
+{
+ alert("Sharing");
+}
+
+function RemoveTab(collectionIndex, tabIndex)
+{
+ var set = collections[collectionIndex];
+ if (--set.tabsCount < 1)
+ {
+ collections = collections.filter(i => i != set);
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+ return;
+ }
+
+ var titles = [];
+ var links = [];
+ var icons = [];
+
+ for (var i = set.links.length - 1; i >= 0; i--)
+ {
+ if (i == tabIndex)
+ continue;
+
+ titles.unshift(set.titles[i]);
+ links.unshift(set.links[i]);
+ icons.unshift(set.icons[i]);
+ }
+
+ set.titles = titles;
+ set.links = links;
+ set.icons = icons;
+
+ localStorage.setItem("sets", JSON.stringify(collections));
+
+ UpdateTheme();
+}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index b2e32af..e5bfb34 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,9 +2,15 @@
"name": "Tabs Aside",
"version": "0.1",
"manifest_version": 2,
- "description": "Classic Microsoft Edge \"Tabs Aside\" feature",
+ "description": "Classic Microsoft Edge \"Tabs Aside\" feature for Chromium browers",
"author": "Michael \"XFox\" Gordeev",
- "permissions": [ "tabs" ],
+ "permissions":
+ [
+ "tabs",
+ "unlimitedStorage",
+ "" +
+ "Tabs: " + collection.links.length + "" +
+ "" + GetAgo(collection.timestamp) + "" +
+ "Restore tabs" +
+ "
" +
+
+ "" +
+ "" +
+ "" +
+ "
" +
+ "" +
+ "" + rawTabs + "
" +
+ "