diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce56492 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +bookmark tabs to right + + +Except as otherwise noted, this software is distributed under the terms of the GNU General Public License, version 2 or later. See LICENSE for the full text. Alternate licensing is available. \ No newline at end of file diff --git a/background.js b/background.js new file mode 100644 index 0000000..b016894 --- /dev/null +++ b/background.js @@ -0,0 +1,51 @@ +function showBookmarkWindow(tabs) { + browser.windows.create({ + type: "popup", url: "/bookmarks.html", + top: 0, left: 0, width: 500, height: 250 + }).then( + (window) => { + browser.tabs.onUpdated.addListener( + (tabid, change, tab) => { + if (tabid == window.tabs[0].id && tab.status == 'complete') { + browser.runtime.sendMessage({ + type: "add-bookmarks", + pages: tabs.map((t) => ({ + index: t.index, + title: t.title, + url: t.url}) + ) + }); + browser.tabs.onUpdated.removeListener(this); + } + }); + }, onerror); +} + +function bookmarkRight(window, index) { + browser.tabs.query({windowId: window}).then( + (tabs) => { + showBookmarkWindow(tabs.filter((t) => t.index > index)); + }); +} + +browser.contextMenus.create({ + contexts: ['tab'], + id: 'bookmark-right', + title: "Bookmark Tabs to the Right" + }); +browser.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId == 'bookmark-right') { + bookmarkRight(tab.windowId, tab.index); + } + }); + +browser.commands.onCommand.addListener((command) => { + if (command == 'bookmark-right') { + browser.tabs.query({ + active: true, + windowId: browser.windows.WINDOW_ID_CURRENT + }).then((tabs) => { + bookmarkRight(browser.windows.WINDOW_ID_CURRENT, tabs[0].index); + }); + } + }); diff --git a/bookmarkright.svg b/bookmarkright.svg new file mode 100644 index 0000000..cbf1074 --- /dev/null +++ b/bookmarkright.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/bookmarks.css b/bookmarks.css new file mode 100644 index 0000000..fb024d4 --- /dev/null +++ b/bookmarks.css @@ -0,0 +1,136 @@ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +button { + -moz-appearance: button; + margin: 0px 5px 2px; + padding: 4px; + min-width: 6.3em; + color: ButtonText; + text-shadow: none; +} + +input { + font: -moz-field; +} + +html, body { + width: 100%; + min-height: 250px; + font: -moz-dialog; + background-color: -moz-Dialog; + color: -moz-DialogText +} + +input[type=checkbox] { + -moz-appearance: none; + margin: 0; + + -moz-context-properties: fill, stroke; + fill: #2292d0; + stroke: none; + width: 21px; + height: 21px; +} + +input[type=checkbox]:checked { + background: url("chrome://global/skin/in-content/check.svg") +} + +.content { + display: grid; + grid-gap: 3px; + margin: 5px; +} + +.content > * { + grid-column: 2; +} + +.content > label { + margin-top: 3px; + grid-column: 1; +} + +#bookmarks { + display: grid; + grid-gap: 3px; + + overflow-y: scroll; + max-height: 250px; + padding: 5px; + background-color: -moz-Field; + color: -moz-FieldText; + font: -moz-list; + -moz-appearance: treeview; +} + +#bookmarks > * { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#bookmarks > *:nth-child(3n+1) { + grid-column: 1; +} +#bookmarks > *:nth-child(3n+2) { + grid-column: 2; +} +#bookmarks > *:nth-child(3n+3) { + grid-column: 3; +} + +#folder { + font: -moz-list; + padding: 2px 0px; +} + +.buttons { + display: flex; +} + +.buttons::before { + content: ' '; + flex: auto; +} \ No newline at end of file diff --git a/bookmarks.html b/bookmarks.html new file mode 100644 index 0000000..e1cb039 --- /dev/null +++ b/bookmarks.html @@ -0,0 +1,26 @@ + + + + + + + Bookmark Tabs + + + +
+ +
+
+ + + + +
+ + +
+
+ + + diff --git a/bookmarks.js b/bookmarks.js new file mode 100644 index 0000000..1bdb3a0 --- /dev/null +++ b/bookmarks.js @@ -0,0 +1,108 @@ +var pages = null; + +function populateTable() { + let list = document.getElementById('bookmarks'); + + let old_height = document.documentElement.clientHeight; + + for (let p of pages) { + let input = document.createElement('input'); + input.type = 'checkbox'; + input.id = 'page' + p.index; + input.checked = true; + + let title = document.createElement('div'); + title.textContent = p.title; + + let url = document.createElement('div'); + url.textContent = p.url; + + list.append(input, title, url); + } + + /* browser.windows.update only takes the size including the window frame + and doesn't take relative changes so we need to measure the difference */ + browser.windows.get(browser.windows.WINDOW_ID_CURRENT).then( + (win) => { + console.log(win.height, document.documentElement.scrollHeight, old_height) + browser.windows.update(browser.windows.WINDOW_ID_CURRENT, { + height: win.height + document.documentElement.scrollHeight - old_height + }); + }); +} + +browser.runtime.onMessage.addListener((msg) => { + if (msg.type == 'add-bookmarks') { + //currently tabs are always provided in order but this isn't documented anywhere + pages = msg.pages.sort((a, b) => a.index > b.index); + + populateTable(); + } + }); + +browser.bookmarks.getTree().then( + (root) => { + function l(a, node) { + if ('children' in node) { + let opt = document.createElement('option'); + opt.value = node.id; + opt.text = node.title; + opt.setAttribute('data-level', 0); + a.push(opt); + a.push(...(node.children.reduce(l, []) + .map((e) => { + e.setAttribute('data-level', (e.getAttribute('data-level')|0) + 1); + return e; + }))); + + } + return a; + } + let opts = root[0].children.reduce(l, []); + + for (let opt of opts) { + opt.text = "\u00A0".repeat((opt.getAttribute('data-level')|0)*4) + opt.text; + } + + document.getElementById('folder').append(...opts); + }, console.log); + +function close() { + browser.windows.remove(browser.windows.WINDOW_ID_CURRENT); +} + +document.getElementById('ok').addEventListener('click', (ev) => { + function f(folder) { + let promises = [] + for (let p of pages) { + if (document.getElementById('page' + p.index).checked) { + promises.push( + browser.bookmarks.create({ + parentId: folder, + title: p.title, + url: p.url, + })); + } + } + Promise.all(promises).then((bookmarks) => { + p2 = []; + bookmarks.forEach((b, i) => { + p2.push(browser.bookmarks.move(b.id, {index: i})); + }); + Promise.all(p2).then(close); + }); + } + + ev.target.disabled = true; + + if (document.getElementById('name').value.length > 0) { + browser.bookmarks.create({ + title: document.getElementById('name').value, + parentId: document.getElementById('folder').value + }).then((node) => {f(node.id)}, console.log); + } else { + f(document.getElementById('folder').value); + } + }); + +document.getElementById('cancel').addEventListener('click', close); \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..477563b --- /dev/null +++ b/manifest.json @@ -0,0 +1,34 @@ +{ + "name": "Bookmark Tabs to the Right", + "description": "Bookmark tabs to the right of a tab by right clicking or the current tab with a key command", + "homepage_url": "https://github.com/mdejean/bookmarktoright", + "manifest_version": 2, + "version": "1.0", + "icons": { + "16": "bookmarkright.svg", + "48": "bookmarkright.svg", + "96": "bookmarkright.svg" + }, + "applications": { + "gecko": { + "strict_min_version": "55.0a1" + } + }, + + "commands": { + "bookmark-right": { + "suggested_key": { "default": "Ctrl+Shift+Period" }, + "description": "Bookmark tabs to the right" + } + }, + + "permissions": [ + "bookmarks", + "tabs", + "contextMenus" + ], + + "background": { + "scripts": ["background.js"] + } +}