├── promo.png ├── sketches ├── icon.sketch ├── folder.sketch └── exports │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ ├── favicon.png │ ├── icon32@4x.png │ ├── folder-dark.png │ ├── folder-dark@2x.png │ ├── folder-light.png │ └── folder-light@2x.png ├── unpacked-extension ├── icons │ ├── icon16.png │ ├── icon32.png │ ├── icon48.png │ └── icon128.png ├── resources │ ├── folder@2x.png │ └── folder-dark@2x.png ├── newtab.html ├── manifest.json ├── newtab.css └── newtab.js └── README.md /promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/promo.png -------------------------------------------------------------------------------- /sketches/icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/icon.sketch -------------------------------------------------------------------------------- /sketches/folder.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/folder.sketch -------------------------------------------------------------------------------- /sketches/exports/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/icon16.png -------------------------------------------------------------------------------- /sketches/exports/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/icon32.png -------------------------------------------------------------------------------- /sketches/exports/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/icon48.png -------------------------------------------------------------------------------- /sketches/exports/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/favicon.png -------------------------------------------------------------------------------- /sketches/exports/icon32@4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/icon32@4x.png -------------------------------------------------------------------------------- /sketches/exports/folder-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/folder-dark.png -------------------------------------------------------------------------------- /sketches/exports/folder-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/folder-dark@2x.png -------------------------------------------------------------------------------- /sketches/exports/folder-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/folder-light.png -------------------------------------------------------------------------------- /unpacked-extension/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/icons/icon16.png -------------------------------------------------------------------------------- /unpacked-extension/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/icons/icon32.png -------------------------------------------------------------------------------- /unpacked-extension/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/icons/icon48.png -------------------------------------------------------------------------------- /sketches/exports/folder-light@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/sketches/exports/folder-light@2x.png -------------------------------------------------------------------------------- /unpacked-extension/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/icons/icon128.png -------------------------------------------------------------------------------- /unpacked-extension/resources/folder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/resources/folder@2x.png -------------------------------------------------------------------------------- /unpacked-extension/resources/folder-dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamschwartz/chrome-new-tab/HEAD/unpacked-extension/resources/folder-dark@2x.png -------------------------------------------------------------------------------- /unpacked-extension/newtab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | New Tab 4 | 5 |
6 |
7 |
8 |
9 | 10 | -------------------------------------------------------------------------------- /unpacked-extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Adam Schwartz", 3 | "name": "New Tab", 4 | "version": "15.0", 5 | "manifest_version": 3, 6 | "minimum_chrome_version": "110", 7 | "description": "A minimalist replacement for Chrome's New Tab page.", 8 | "icons": { 9 | "128": "icons/icon128.png", 10 | "48": "icons/icon48.png", 11 | "32": "icons/icon32.png", 12 | "16": "icons/icon16.png" 13 | }, 14 | "permissions": [ 15 | "favicon", 16 | "bookmarks" 17 | ], 18 | "chrome_url_overrides": { 19 | "newtab": "newtab.html" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Chrome New Tab 2 | 3 | ### On the Chrome Web Store 4 | 5 | Visit the [New Tab on the Chrome Web Store](https://chrome.google.com/webstore/detail/new-tab/adcpijkmbecohfalcbafjgadfnpchhlg) and choose "Add to Chrome". 6 | 7 | [Buy me a beer →](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=D6XM3J8GW548W) 8 | 9 | ### Local Installation 10 | `git clone` this repo. Go to Chrome Settings > Extensions ([chrome://extensions](chrome://extensions)). Choose "Load unpacked extension" and choose the `unpacked-extension` folder in this repo. Disable the extension to switch back to the original Chrome New Tab page. 11 | -------------------------------------------------------------------------------- /unpacked-extension/newtab.css: -------------------------------------------------------------------------------- 1 | * { 2 | font: inherit; 3 | box-sizing: inherit; 4 | } 5 | 6 | :root { 7 | --menu-box-shadow: 0 0 0 0.5px rgba(0, 0, 0, .25), 0 2px 11px rgba(0, 0, 0, .3); 8 | } 9 | 10 | body { 11 | margin: 0; 12 | padding: 0; 13 | font-family: -apple-system, BlinkMacSystemFont, sans-serif; 14 | font-size: 12px; 15 | line-height: 1.5; 16 | color: #3c4043; 17 | background: #fff; 18 | box-sizing: border-box; 19 | -webkit-user-select: none; 20 | min-height: 100vh; 21 | } 22 | 23 | @media (prefers-color-scheme: dark) { 24 | body { 25 | color: #fff; 26 | background: #1f2020; 27 | } 28 | } 29 | 30 | @media (prefers-color-scheme: dark) { 31 | .bookmarks { 32 | background: #3c3c3c; 33 | border-bottom: 1px solid #454746; 34 | } 35 | } 36 | 37 | ul { 38 | list-style: none; 39 | } 40 | 41 | .bookmarks { 42 | display: flex; 43 | } 44 | 45 | #bookmarks-bar { 46 | margin-right: auto; 47 | } 48 | 49 | #other-bookmarks > div > ul > li:not(:last-child) { 50 | display: none; 51 | } 52 | 53 | .bookmarks ul { 54 | display: flex; 55 | flex-wrap: wrap; 56 | margin: 0; 57 | padding: 4px 6px; 58 | } 59 | 60 | .bookmarks ul li { 61 | position: relative; 62 | display: inline-flex; 63 | align-items: center; 64 | justify-content: center; 65 | } 66 | 67 | .bookmarks .wrap.wrap-is-close { 68 | display: none; 69 | } 70 | 71 | .bookmarks li a { 72 | display: inline-block; 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | max-width: 142px; 76 | letter-spacing: .004em; 77 | white-space: nowrap; 78 | text-decoration: none; 79 | color: inherit; 80 | padding: 5px 6px; 81 | margin: 0 2px; 82 | border-radius: 99em; 83 | cursor: pointer; 84 | transition: background .3s ease; 85 | } 86 | 87 | .bookmarks li a:focus { 88 | outline: none; 89 | } 90 | 91 | .bookmarks li a:hover, .bookmarks li a:focus { 92 | background: #edeeee; 93 | } 94 | 95 | .bookmarks li a:hover:active, .bookmarks li a.open { 96 | background: #e1e2e2; 97 | transition: none; 98 | } 99 | 100 | @media (prefers-color-scheme: dark) { 101 | .bookmarks li a:hover, .bookmarks li a:focus { 102 | background: #35363a; 103 | } 104 | 105 | .bookmarks li a:hover:active, .bookmarks li a.open { 106 | background: #505154; 107 | } 108 | } 109 | 110 | .bookmarks li .icon { 111 | display: inline-block; 112 | vertical-align: middle; 113 | width: 16px; 114 | height: 16px; 115 | background-size: 100% 100%; 116 | margin-right: 8px; 117 | position: relative; 118 | top: -1px; 119 | } 120 | 121 | .folder.open + div { 122 | position: absolute; 123 | background: #fff; 124 | padding: 10px 0; 125 | z-index: 100; 126 | border-radius: 10px; 127 | box-shadow: var(--menu-box-shadow); 128 | top: calc(100% + 4px); 129 | left: 2px; 130 | } 131 | 132 | #other-bookmarks .folder.open + div { 133 | left: auto; 134 | right: 2px; 135 | } 136 | 137 | @media (prefers-color-scheme: dark) { 138 | .folder.open + div { 139 | background: #292a2d; 140 | } 141 | } 142 | 143 | .folder.open + div .folder.open + div { 144 | left: 50px; 145 | top: calc(100% + 4px); 146 | } 147 | 148 | #other-bookmarks .folder.open + div .folder.open + div { 149 | left: auto; 150 | right: 50px; 151 | } 152 | 153 | .folder.open + div ul { 154 | display: block; 155 | padding: 0; 156 | min-width: 240px; 157 | max-width: min(calc(100vw - 32px), 400px); 158 | } 159 | 160 | .folder.open + div ul li { 161 | display: block; 162 | } 163 | 164 | .folder.open + div li + li { 165 | margin-left: 0; 166 | } 167 | 168 | .folder.open + div li a { 169 | width: 100%; 170 | display: block; 171 | padding: 4px 24px; 172 | border-radius: 0; 173 | max-width: 100%; 174 | transition: none; 175 | font-size: 13px; 176 | margin: 0; 177 | } 178 | 179 | .folder.open + div li .icon { 180 | margin-right: 12px; 181 | } 182 | 183 | .folder.open + div li a:hover { 184 | background: #e8e8e9; 185 | } 186 | 187 | @media (prefers-color-scheme: dark) { 188 | .folder.open + div li a:hover { 189 | background: #3f4042; 190 | } 191 | } 192 | 193 | .folder.open + div li { 194 | position: relative; 195 | } 196 | 197 | a.empty { 198 | opacity: .4; 199 | pointer-events: none; 200 | } 201 | 202 | a.empty .icon { 203 | display: none; 204 | } 205 | 206 | .folder .icon { 207 | background-image: -webkit-image-set( 208 | url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAAAnElEQVQ4EWNgoBAwxsTEhP379y+EkZHxOsis////87CwsPQtWrToKTFmMwE1rwQqvAfU+A+EgQYp/vnz50lWVhYPMQYwRkVF/V+2bBkjsuLo6OgZQMPSkcVwskEGYJNMSkrixSaOLAbSC3YBsiCpbBaQhuLiYlL1gdX39vYyMJGlE0nTqAEMDOBYePLkCVKwkMYEG7ByJSg1D1UAADSbNeenulTvAAAAAElFTkSuQmCC) 1x, 209 | url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAAA/klEQVRYCWNgGAUDHAKM0dHRnkA3zPr//78MHrd8Z2RkXKWqqprW0NDwC486kqWYiLAcZCgn0IHxt2/fbibZBgIaGKOiov6D1CxbtowRl9q4uDizP3/+HAPJs7CwWC1atOgULrWkihPlAJChwKjqBIZCGakW4FIPjNInQLk0UBQQBYDxXwvUtBCo+DtRGggogqa5WUSHAAHzSJaGRT3RIUCyDURqgIcAkeqprmzAQ4AF5qXi4mIYky50b28v2J4BD4FRB4yGwGgIjIbAaAjA64InT0ANFPqDAY8CFlDbDNQ8WrlyJd29D7IbFAJp0AYiXR0AtTONrpaOWjYoQwAAC6BINlFJu5AAAAAASUVORK5CYII=) 2x 210 | ); 211 | } 212 | 213 | @media (prefers-color-scheme: dark) { 214 | .folder .icon { 215 | background-image: -webkit-image-set( 216 | url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAEKADAAQAAAABAAAAEAAAAAA0VXHyAAAAhUlEQVQ4EWNgoBAw/v//PwxoRggQX4eaxQOk+xgZGZ9C+XgpJqDsSiC+B8T/oFgRSD8BGgwyiDAAKvyPrgooNAMkTgxgAClCNwDEBwrzYhNHFgPpBYUBVgOQFeJjs4Aki0vK8KnBKdfb08UACkSKwKgBDAzgWPjy5QvZAUlxOiDbZqppBADD9FXo0IZu2gAAAABJRU5ErkJggg==) 1x, 217 | url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAIKADAAQAAAABAAAAIAAAAACshmLzAAAAzElEQVRYCWNgGAUDHQL////3BOLHQIwPfANKLgBiNqq7F2goIcuRHdZJCweALcBnMFCBGRD/gWIzfGpJlgPbDiQIaQQq6YSppRINCnlPBphhRDiADagWlA5A6YFa4DEjyCSQ5YxAQMgR1JSH2ctETUPJMQseAuRopoaeAQ8BFpgvikvKYEy60L09XWB7BjwERh0wGgKjITAaAqMhAK8Lvnz5Qpc6AN2SAY8CUHvgMdBVMuguoxP/CSgE0oD4CZ0sRLYGZCfI7lEwwkMAAK3HSSeocPB2AAAAAElFTkSuQmCC) 2x 218 | ); 219 | } 220 | } 221 | 222 | .menu { 223 | margin: 0; 224 | padding: 0; 225 | list-style: none; 226 | } 227 | 228 | .menu { 229 | position: absolute; 230 | white-space: nowrap; 231 | z-index: 200; 232 | background: #fff; 233 | padding: 10px 0; 234 | font-size: 13px; 235 | border-radius: 10px; 236 | box-shadow: var(--menu-box-shadow); 237 | } 238 | 239 | @media (prefers-color-scheme: dark) { 240 | .menu { 241 | background: #292a2d; 242 | } 243 | } 244 | 245 | .menu a { 246 | display: block; 247 | color: inherit; 248 | width: 100%; 249 | padding: 4px 24px; 250 | text-decoration: none; 251 | } 252 | 253 | .menu .icon { 254 | margin-right: 12px; 255 | } 256 | 257 | .menu a:hover { 258 | background: #e8e8e9; 259 | } 260 | 261 | @media (prefers-color-scheme: dark) { 262 | .menu a:hover { 263 | background: #3f4042; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /unpacked-extension/newtab.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const foldersOpen = {} 4 | 5 | const bookmarksBarEl = document.getElementById('bookmarks-bar') 6 | const otherBookmarksEl = document.getElementById('other-bookmarks') 7 | 8 | function renderAll(nodes, target, toplevel) { 9 | var ul = document.createElement('ul') 10 | 11 | for (var i = 0; i < nodes.length; i++) { 12 | var node = nodes[i] 13 | render(node, ul) 14 | } 15 | 16 | if (ul.childNodes.length === 0) { 17 | render({ id: 'empty', className: 'empty', title: '(empty)' }, ul) 18 | } 19 | 20 | if (toplevel) { 21 | target.appendChild(ul) 22 | 23 | } else { 24 | var wrap = document.createElement('div') 25 | wrap.appendChild(ul) 26 | target.appendChild(wrap) 27 | } 28 | 29 | return ul 30 | } 31 | 32 | function render(node, target) { 33 | var li = document.createElement('li') 34 | var a = document.createElement('a') 35 | 36 | var url = node.url || node.appLaunchUrl 37 | if (url) a.href = url 38 | 39 | a.innerText = node.title || node.name || '' 40 | if (node.tooltip) a.title = node.tooltip 41 | 42 | setClass(a, node) 43 | 44 | a.insertBefore(getIcon(node), a.firstChild) 45 | 46 | if (url) { 47 | var items = getMenuItems(node) 48 | 49 | a.oncontextmenu = event => { 50 | renderMenu(items, event.pageX, event.pageY) 51 | return false 52 | } 53 | 54 | var urlStart = url.substring(0, 6) 55 | if (urlStart === 'chrome' || urlStart === 'file:/') { 56 | a.onclick = function(event) { 57 | openLink(node, event.metaKey) 58 | return false 59 | } 60 | } 61 | 62 | } else if (!node.children && !node.type) { 63 | a.style.pointerEvents = 'none' 64 | } 65 | 66 | li.appendChild(a) 67 | 68 | // Folders 69 | if (node.children) { 70 | a.onclick = function() { 71 | toggle(node, a, getChildrenFunction(node)) 72 | return false 73 | } 74 | 75 | var items = getMenuItems(node) 76 | 77 | a.oncontextmenu = event => { 78 | renderMenu(items, event.pageX, event.pageY) 79 | return false 80 | } 81 | } 82 | 83 | target.appendChild(li) 84 | return li 85 | } 86 | 87 | function getMenuItems(node) { 88 | const items = [] 89 | 90 | if (node.children) { 91 | items.push({ 92 | label: 'Open all links in folder', 93 | action: () => { 94 | openLinks(node) 95 | } 96 | }) 97 | 98 | } else { 99 | items.push({ 100 | label: 'Open link in tab', 101 | action: () => { 102 | openLink(node, 1) 103 | } 104 | }) 105 | } 106 | 107 | items.push({ 108 | label: 'Edit bookmarks', 109 | action: () => { 110 | openLink({ url: 'chrome://bookmarks' }, 1) 111 | } 112 | }) 113 | 114 | return items 115 | } 116 | 117 | function onMenuClick(item) { 118 | return function() { 119 | item.action() 120 | return false 121 | } 122 | } 123 | 124 | function renderMenu(items, x, y) { 125 | closeMenu() 126 | 127 | var ul = document.createElement('ul') 128 | ul.className = 'menu' 129 | 130 | for (var i = 0; i < items.length; i++) { 131 | var li = document.createElement('li') 132 | var a = document.createElement('a') 133 | a.setAttribute('tabindex', 0) 134 | a.innerText = items[i].label 135 | a.onclick = onMenuClick(items[i]) 136 | li.appendChild(a) 137 | ul.appendChild(li) 138 | } 139 | 140 | document.body.appendChild(ul) 141 | 142 | ul.style.left = Math.max(Math.min(x, window.innerWidth + window.scrollX - ul.clientWidth), 0) + 'px' 143 | ul.style.top = Math.max(Math.min(y, window.innerHeight + window.scrollY - ul.clientHeight), 0) + 'px' 144 | 145 | ul.onmousedown = function(event) { 146 | event.stopPropagation() 147 | return true 148 | } 149 | 150 | return ul 151 | } 152 | 153 | document.onmousedown = function(event) { 154 | closeMenu() 155 | } 156 | 157 | document.onkeydown = function() { 158 | if (event.keyCode == 27) { 159 | closeMenu() 160 | closeOpenFolders() 161 | } 162 | } 163 | 164 | function closeMenu(ul) { 165 | const menu = document.querySelector('body > .menu') 166 | if (menu) menu.remove() 167 | } 168 | 169 | const closeOpenFolders = (scopeElement = document.body) => { 170 | const folders = scopeElement.querySelectorAll('.folder.open') 171 | Array.from(folders).reverse().forEach(folder => folder.click()) 172 | } 173 | 174 | document.body.addEventListener('mousedown', event => { 175 | if (event.target.tagName === 'A' && event.target.href) { 176 | return 177 | } 178 | 179 | const closestFolder = event.target.closest('.folder') 180 | 181 | if (!closestFolder) { 182 | closeOpenFolders() 183 | } else { 184 | closeOpenFolders(closestFolder) 185 | } 186 | }) 187 | 188 | function getChildrenFunction(node) { 189 | switch(node.id) { 190 | default: 191 | if (node.children) 192 | return function(callback) { 193 | callback(node.children) 194 | } 195 | else 196 | return function(callback) { 197 | chrome.bookmarks.getSubTree(node.id, function(result) { 198 | if (!result) return 199 | callback(result[0].children) 200 | }) 201 | } 202 | } 203 | } 204 | 205 | function setClass(target, node, isOpen) { 206 | if (node.className) { 207 | target.classList.add(node.className) 208 | } 209 | 210 | if (node.children) { 211 | target.classList.add('folder') 212 | target.setAttribute('tabindex', 0) 213 | } 214 | 215 | if (isOpen) { 216 | target.classList.add('open') 217 | } else { 218 | target.classList.remove('open') 219 | } 220 | } 221 | 222 | function getIcon(node) { 223 | var url, url2x 224 | if (node.icons) { 225 | var size 226 | for (var i in node.icons) { 227 | var iconInfo = node.icons[i] 228 | if (iconInfo.url && (!size || (iconInfo.size < size && iconInfo.size > 15))) { 229 | url = iconInfo.url 230 | if (iconInfo.size > 31) url2x = iconInfo.url 231 | size = iconInfo.size 232 | } 233 | } 234 | 235 | } else if (node.icon) { 236 | url = node.icon 237 | 238 | } else if (node.url || node.appLaunchUrl) { 239 | let pageURL = node.url || node.appLaunchUrl 240 | pageURL = pageURL.replace(/ /g, '%20') 241 | 242 | if (!pageURL || pageURL.substr(0, 11) === 'javascript:' || pageURL.substr(0, 5) === 'data:') { 243 | pageURL = 'https://example.com' 244 | } 245 | 246 | // See https://bugs.chromium.org/p/chromium/issues/detail?id=104102 247 | url = `chrome-extension://${chrome.runtime.id}/_favicon/?pageUrl=${encodeURIComponent(pageURL)}&size=16` 248 | url2x = `chrome-extension://${chrome.runtime.id}/_favicon/?pageUrl=${encodeURIComponent(pageURL)}&size=32` 249 | } 250 | 251 | var icon = document.createElement(url ? 'img' : 'div') 252 | icon.className = 'icon' 253 | icon.src = url 254 | if (url && url2x) icon.srcset = url2x + ' 2x' 255 | icon.alt = '' 256 | return icon 257 | } 258 | 259 | // toggle folder open state 260 | function toggle(node, a) { 261 | var isOpen = foldersOpen[node.id] 262 | setClass(a, node, !isOpen) 263 | a.open = !isOpen 264 | 265 | if (isOpen) { 266 | delete foldersOpen[node.id] 267 | if (a.nextSibling) { 268 | var children = (a.nextSibling.tagName == 'DIV' ? a.nextSibling.firstChild : a.nextSibling).children 269 | for (var i = 0; i < children.length; i++) { 270 | var child = children[i].firstChild 271 | if (child.open) 272 | child.onclick() 273 | } 274 | 275 | toggleOpenClose(node, a, isOpen) 276 | } 277 | 278 | } else { 279 | foldersOpen[node.id] = true 280 | 281 | var siblings = a.parentNode.parentNode.children 282 | for (var i = 0; i < siblings.length; i++) { 283 | var sibling = siblings[i].firstChild 284 | if (sibling != a && sibling.open) 285 | sibling.onclick() 286 | } 287 | 288 | if (a.nextSibling) 289 | toggleOpenClose(node, a, isOpen) 290 | 291 | else 292 | getChildrenFunction(node)(function(result) { 293 | if (!a.nextSibling && foldersOpen[node.id]) { 294 | renderAll(result, a.parentNode) 295 | toggleOpenClose(node, a, isOpen) 296 | } 297 | }) 298 | } 299 | } 300 | 301 | function toggleOpenClose(node, a, isOpen) { 302 | var wrap = a.nextSibling 303 | wrap.className = 'wrap ' + (isOpen ? 'wrap-is-close' : 'wrap-is-open') 304 | } 305 | 306 | // opens immediate children of given node in new tabs 307 | function openLinks(node) { 308 | chrome.tabs.getCurrent(function(tab) { 309 | getChildrenFunction(node)(function(result) { 310 | for (var i = 0; i < result.length; i++) 311 | openLink(result[i], 2) 312 | }) 313 | }) 314 | } 315 | 316 | // opens given node 317 | function openLink(node, newtab) { 318 | var url = node.url || node.appLaunchUrl 319 | if (url) { 320 | chrome.tabs.getCurrent(function(tab) { 321 | if (newtab) 322 | chrome.tabs.create({url: url, active: (newtab == 1), openerTabId: tab.id}) 323 | else 324 | chrome.tabs.update(tab.id, {url: url}) 325 | }) 326 | } 327 | } 328 | 329 | chrome.bookmarks.getTree(function(result) { 330 | var nodes = result[0].children 331 | 332 | const bookmarksBar = nodes[0] 333 | const otherBookmarks = nodes[1] 334 | 335 | if (bookmarksBar.children.length) { 336 | renderAll(bookmarksBar.children, bookmarksBarEl) 337 | } else { 338 | bookmarksBarEl.remove() 339 | } 340 | 341 | if (otherBookmarks.children.length) { 342 | renderAll([otherBookmarks], otherBookmarksEl) 343 | } else { 344 | otherBookmarksEl.remove() 345 | } 346 | }) 347 | --------------------------------------------------------------------------------