├── 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 |
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 |
--------------------------------------------------------------------------------