diff options
Diffstat (limited to 'glide')
| -rw-r--r-- | glide/.config/glide/glide.ts | 241 | ||||
| -rw-r--r-- | glide/.config/glide/sitesearch.glide.ts | 147 | ||||
| -rw-r--r-- | glide/.config/glide/tabbar.glide.ts | 256 |
3 files changed, 639 insertions, 5 deletions
diff --git a/glide/.config/glide/glide.ts b/glide/.config/glide/glide.ts index 445091f..49c66de 100644 --- a/glide/.config/glide/glide.ts +++ b/glide/.config/glide/glide.ts @@ -24,6 +24,8 @@ glide.prefs.set("sidebar.visibility", "hide-sidebar"); // glide.prefs.set("browser.urlbar.placeholderName", "DuckDuckGo"); // glide.prefs.set("browser.urlbar.placeholderName.private", "DuckDuckGo"); +glide.keymaps.set("normal", "<C-x><C-c>", "quit"); + // Previous tab glide.keymaps.set("normal", "<A-p>", "tab_prev"); glide.keymaps.del('normal', '<C-k>'); @@ -48,7 +50,7 @@ glide.keymaps.set('command', '<C-n>', 'commandline_focus_next'); // Scroll up glide.keymaps.set('normal', '<C-p>', 'caret_move up'); -glide.keymaps.set('normal', 'k'); +glide.keymaps.del('normal', 'k'); glide.keymaps.set('command', '<C-p>', 'commandline_focus_back'); // Go back @@ -65,6 +67,7 @@ glide.keymaps.set("op-pending", "<C-g>", "mode_change normal"); // Close tab glide.keymaps.set('normal', 'x', 'tab_close'); glide.keymaps.del('normal', '<leader>d'); +glide.keymaps.set('normal', 'X', 'tab_reopen'); // Scroll down glide.keymaps.set('normal', '<C-v>', 'scroll_page_down'); @@ -79,8 +82,62 @@ glide.keymaps.set('normal', '<A-x>', 'commandline_show'); glide.keymaps.set('normal', 't', 'commandline_show tab_new '); glide.keymaps.del('normal', ':'); +const keyboard_quit = glide.excmds.create({ + name: "keyboard_quit", + description: 'Cancel whatever is going on and return to normal', +}, () => { + if (glide.findbar.is_open()) { + glide.findbar.close(); + } + + if (glide.commandline.is_active()) { + glide.commandline.close(); + } + + glide.excmds.execute('mode_change normal'); +}); + +glide.keymaps.set(['insert', 'command', 'normal'], '<C-g>', 'keyboard_quit'); + +// Search +const search_next = glide.excmds.create({ + name: "search_next", + description: "Open search or find the next result", +}, () => { + if (glide.findbar.is_focused()) { + glide.findbar.next_match(); + } + else { + glide.findbar.open(); + glide.excmds.execute('mode_change insert'); + } +}); + +const search_prev = glide.excmds.create({ + name: "search_prev", + description: "Open search of find the previous result", +}, () => { + if (glide.findbar.is_focused()) { + glide.findbar.previous_match(); + } + else { + glide.findbar.open(); + glide.excmds.execute('mode_change insert'); + } +}); + +glide.keymaps.set('normal', '<C-s>', 'search_next'); +glide.keymaps.set('normal', '<C-r>', 'search_prev'); +glide.keymaps.set('insert', '<C-s>', 'search_next'); +glide.keymaps.set('insert', '<C-r>', 'search_prev'); + async function installPuntAddons() { - await glide.addons.install("https://addons.mozilla.org/firefox/downloads/file/4677239/1password_x_password_manager-8.12.1.3.xpi"); + await glide.addons.install("https://addons.mozilla.org/firefox/downloads/file/4677239/1password_x_password_manager-8.12.1.3.xpi"); + await glide.addons.install('https://addons.mozilla.org/firefox/downloads/file/4508409/vue_js_devtools-7.7.7.xpi'); +} + +async function installPersonalAddons() { + await glide.addons.install('https://addons.mozilla.org/firefox/downloads/file/4628286/keepassxc_browser-1.9.11.xpi'); } glide.keymaps.set('normal', '<leader>m', async function() { @@ -99,10 +156,10 @@ async function open_or_activate(url) { const tabs = await glide.tabs.query({url: `*://${url.hostname}/*`}); const tab = tabs.find(t => t.url.startsWith(url.href)); - if (tabs.length === 0) { - glide.excmds.execute(`tab_new ${url}`); - } else { + if (tab) { await browser.tabs.update(tab.id, { active: true }); + } else { + glide.excmds.execute(`tab_new ${url}`); } } @@ -121,3 +178,177 @@ glide.keymaps.set('normal', '<leader>gd', async function() { ], }); }); + +glide.addons.install("https://addons.mozilla.org/firefox/downloads/file/4675310/ublock_origin-1.69.0.xpi"); + +// From https://github.com/glide-browser/glide/discussions/147#discussioncomment-15617575 + +interface Heading { + text: string; + tag: string; + id: string; + xpath: string; +} + +glide.keymaps.set("normal", "gh", async () => { + const activeTabId = await glide.tabs.active(); + + // Extract all visible headings from the page + const headings = await glide.content.execute((): Heading[] => { + function isVisible(element: HTMLElement): boolean { + const style = window.getComputedStyle(element); + return style.display !== 'none' && + style.visibility !== 'hidden' && + style.opacity !== '0' && + element.offsetParent !== null; + } + + function getXPath(element: Element): string { + let path = ''; + for (let el: Element | null = element; el && el.nodeType === Node.ELEMENT_NODE; el = el.parentElement) { + let index = 0; + for (let sibling = el.previousSibling; sibling; sibling = sibling.previousSibling) { + if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as Element).tagName === el.tagName) { + index++; + } + } + const tagName = el.tagName.toLowerCase(); + const pathIndex = `[${index + 1}]`; + path = `/${tagName}${pathIndex}${path}`; + } + return path; + } + + return Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')) + .filter((heading) => isVisible(heading as HTMLElement)) + .map((heading): Heading => ({ + text: heading.textContent?.trim() || '', + tag: heading.tagName.toLowerCase(), + id: heading.id || '', + xpath: getXPath(heading) + })); + }, { tab_id: activeTabId }); + + if (headings.length === 0) { + glide.commandline.show({ + title: "No visible headings found", + options: [] + }); + return; + } + + glide.commandline.show({ + title: "Jump to heading", + options: headings.map((heading) => { + const level = parseInt(heading.tag.substring(1)); + const indent = '*'.repeat(level); + + return { + label: `${indent}${indent ? ' ' : ''}${heading.text}`, + async execute() { + await glide.content.execute((xpath: string) => { + const target = document.evaluate( + xpath, + document, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE, + null + ).singleNodeValue as HTMLElement | null; + + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + target.style.outline = '2px solid orange'; + setTimeout(() => { target.style.outline = ''; }, 2000); + } + }, { + tab_id: activeTabId, + args: [heading.xpath] + }); + }, + }; + }), + }); +}, { description: "Jump to heading in current page" }); + +glide.keymaps.set('normal', 'ab', async () => { + const currentTab = await glide.tabs.get_first({ active: true }); + + glide.commandline.show({ + title: 'Add', + options: [{ + label: currentTab.title, + async execute({ input }) { + await browser.bookmarks.create({ + url: currentTab.url, + title: input || currentTab.title, + }); + } + }], + }); +}); + +glide.keymaps.set('normal', 'kb', async () => { + const bookmarks = await browser.bookmarks.search({}); + + glide.commandline.show({ + title: 'Remove', + options: bookmarks.map((b) => ({ + label: b.title, + async execute() { + await browser.bookmarks.remove(b.id); + }, + })), + }); +}); + + +// Split windows + +const split_window = glide.excmds.create({ + name: 'split_window', + description: 'Ask for a tab and show it in a split with this window', +}, async () => { + const activeTab = await glide.tabs.active(); + const tabs = await glide.tabs.query({}); + + glide.commandline.show({ + title: "Show other", + options: tabs.map(t => ({ + label: t.title, + async execute() { + glide.unstable.split_views.create([activeTab.id, t.id]); + } + })), + }); +}); + +const unsplit_window = glide.excmds.create({ + name: 'unsplit_window', + description: 'Remove the current split of windows', +}, ({ tab_id }) => { + glide.unstable.split_views.separate(tab_id); +}); + +const other_window = glide.excmds.create({ + name: 'other_window', + description: 'Focus other window', +}, async ({ tab_id }) => { + const split_tabs = await glide.unstable.split_views.get(tab_id); + const other_tab = split_tabs.tabs.filter(t => t.id !== tab_id)[0]; + + await browser.tabs.update(other_tab.id, { active: true }); +}); + +glide.keymaps.set('normal', '<C-x>4b', 'split_window'); +glide.keymaps.set('normal', '<C-x>1', 'unsplit_window'); +glide.keymaps.set('normal', '<A-o>', 'other_window'); + +glide.include('sitesearch.glide.ts'); +glide.include('tabbar.glide.ts'); + +const toggle_reader_mode = glide.excmds.create({ + name: 'toggle_reader_mode', + description: 'Toggle Reader Mode for the current tab', +}, () => { + browser.tabs.toggleReaderMode(); +}); diff --git a/glide/.config/glide/sitesearch.glide.ts b/glide/.config/glide/sitesearch.glide.ts new file mode 100644 index 0000000..f575015 --- /dev/null +++ b/glide/.config/glide/sitesearch.glide.ts @@ -0,0 +1,147 @@ +// From https://github.com/glide-browser/glide/discussions/147#discussioncomment-15337351 + +/** + * custom search providers + */ +const search_info: Record<string, { url: string, sep: string }> = { + 'youtube': { + url: "https://www.youtube.com/results?search_query=", sep: "+" + } +} as const + +/* + * pick tabs via a selection of bookmarks and history + */ +glide.keymaps.set("normal", "<leader>t", async () => { + + //let combined: Array<Browser.Bookmarks.BookmarkTreeNode | Browser.History.HistoryItem> = [] + let combined = [] + const tabs = await browser.tabs.query({}); + tabs.forEach(entry => combined.push({ + title: entry.title ?? 'Unnamed Tab', + url: entry.url, + type: 'T', + favIconUrl: entry.favIconUrl + })); + + const bookmarks = await browser.bookmarks.search({}); + bookmarks + .filter(bmark => bmark.type !== 'folder') + .forEach(bmark => combined.push({ title: bmark.title, url: bmark.url, type: 'B', favIconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAE/ElEQVR4Xu2cW2wUVRjHvzPdXdou0gvUemniegnRQtquimBNa4kpJfrCJrQ+Gewjb5oQfJPLG8akjzxawpMtyeKDpuBDkWqEBNMFEcUINEQTTEVrwbZ7mTmeb9xZ5j6zZXbd7X4n2YdOz3xz5rf//3fOmZ1zGPgsyelvY7m0vIcz/jpw3iNOi/k8teKqMQYLHNg5YNJnI7teG3drIPNq/cTZb95lirKPAx/wqlul/5+TGLy/d6j/tF37HQEJMD0CzNgaBmPmMT6yu3/UfNAWkKoaLo9xDs1VqorVNtsCyQII4YAif+J0hXAoBO2bWqCxfh20Nm8wVNto+nu1rSzleXcXFuHXO/Pqx64IuyX0djMAOnXm/B6FQ9KpgZtjHRDreBzCobpS3kNZYi/eX4JLV6/D8krafL05YbWntYMFQMnp2eZc5t4tJ1v1vdwFG9Y3lqXx5bpINifDzKUrVkhS3ajWuxUATU59/R4HZcyucR2PtUH388+Wq91lvQ4qCSEZCmOnR4b6EnisAGjizMxsfnxjaeDAti5oa20qScNvdg8GGveZy19a4nFxRJYVyIoPFxYxl8s/3TDkJBwnDQ/1txgBTZ23npmPVO2ANCB4g+lMDhQTJEzcF1LXDNxEHlLF80BBLoBiT7bDtq2bA/2mtWDlUJC+4QhpRUAyK+nzcxdWDwjP3PLcU9DW0hS41coNCO8lJ6yWEQlaXx4aUEnkI4K+dPBooKG/++hDQzzMnbEn2gFdoLfacjpLgPQEmh+JQm+8E6IN9erhJQJkFSbOAgZ74yokAuRgXFTSYO+LpQe0vWdLILkjt+vtQOJoQUJnP4W//r4Hd+bvwuL9f2xjY4/86KbW0uagSgak3fn8nwtw8/ZvFkiYuF/peoEAIQEnSG8N7CBAGoEff5mz2I0A6fSBOennW7cNiiFApsxzMfUDAUIC2IvZFQKUp0KAPEZNBIgAPdzAmhRECiIFGQiUYrJK3byLyCgHUQ6iHEQ5qBgNUJL2oEWACFAxhrLWJQWRgkhBtgSq4WcffcPpiSI9UfRnZZqL0VzMn1KcapGCSEFWAlLiTfWgkvzCU141pyDW3gZ1x4+pYOT9HwD/3X4JgUau5gDVfXwIWFenev/8yjWQDxxxVVFNAUJrSfv3GYAox0+4Wq1mABWstT5qVIx4a8zNajUDSG8ts6fcrFYTgOysZYbkZLU1D8jRWmZCDlZb84DcrOXHakEAYmJF9PDuvp14PV+LWez61aAedygnT4FyclK9hPTOsPjs9RwM6iv4Pb+oxx12C+omXFb7lBKQGlt7n9ncYxWFyr2yb0CMpcRiurgW7X9XUIAMXEP5AYQL6UKRcDyx89U5AmR+T/qri6lwJJTQw6mIHFQpCorWR2z3ECCL5b8hAuTxnjQBcgGE457G+rA67jEXshiOvyRptCESGidAOgKFtRoMUtF1kcK4hxSUJ6ACYmxBioTiDYwVxj2OgCanZqaL2SsoqKlGObp5XHmIS6K0grvU7Ih3pqRIOOEGxzgO8tgWx3wj1QQIVx3iwroCoJamI29s7z7s58sxDI7c9u+oVkBLyyvw/fUbD5pvmmt5QTIAUjdyy2Rn/ew8VQ0KkmVZhZPO/LeJgN1cqyhAWBkhZTO5pNNOMFrASgeEysFVhhocQcd2rlU0IO0Er93vKhUQJuQ/RL7Rcg4OArkknfDaDtAJ1L+Y5wqFFVK3lAAAAABJRU5ErkJggg==' })); + + const topsites = await browser.topSites.get({includeFavicon: true}); + topsites + .forEach(s => combined.push({ title: s.title, url: s.url, type: 'S', favIconUrl: s.favicon})); + + const history = await browser.history.search({ text: "", maxResults: 100 }); + history.forEach(entry => combined.push({ title: entry.title, url: entry.url, type: 'H', favIconUrl: null })); + + // filtering + const newtab = (await browser.runtime.getManifest()).chrome_url_overrides?.newtab + const startpage = glide.prefs.get("browser.startup.homepage") + + let filtered_combined = combined.filter(e => e.url !== startpage && e.url !== newtab) + + glide.commandline.show({ + title: "open", + options: filtered_combined.map((entry) => ({ + label: entry.title, + render() { + return DOM.create_element("div", { + style: { + display: "flex", + alignItems: "center", + gap: "8px", + }, + children: [ + entry.favIconUrl + ? DOM.create_element("img", [], { + src: entry.favIconUrl, + style: { + width: '16px', + height: '16px', + } + }) + : DOM.create_element("span", [entry.type], { + style: { color: "#777", fontSize: "0.9em" }, + }), + DOM.create_element("span", [entry.title]), + DOM.create_element("span", [entry.url], { + style: { color: "#777", fontSize: "0.9em" }, + }), + ], + }); + }, + async execute({ input: input }) { + if (entry.title.toLowerCase().includes(input.toLowerCase())) { + const tab = await glide.tabs.get_first({ + url: entry.url, + }); + if (tab) { + const windowid = tab.windowId; + if (windowid === undefined) { + return + } + await browser.windows.update(windowid, { + focused: true + }) + await browser.tabs.update(tab.id, { + active: true, + }); + } else { + + await browser.tabs.create({ + active: true, + url: entry.url, + windowId: browser.windows.getCurrent().id, + }); + } + } else { + const terms = input.split(" ",) + const first = terms[0] + if (terms.length > 1 && first !== undefined && first in search_info) { + let info = search_info[first]; + let query = info?.url + terms.slice(1).join(info?.sep) + browser.tabs.create({ + active: true, + url: query + }); + return; + } + + let url: URL; + try { + url = new URL(input) + } catch (_) { + try { + url = new URL("http://" + input) // firefox automatically makes this https + + // avoids single word searches becoming URLs + if (url.hostname.split(".").length == 1 && url.hostname !== "localhost") { + throw "probably not a hostname"; + } + } catch (_) { // probably not a url + browser.search.search({ + query: terms.filter(s => s).join(" "), + disposition: "NEW_TAB", + }) + return + } + + } + // so it IS a URL! + + const tab = await glide.tabs.get_first({ url: url.href }); + + if (tab) { + browser.tabs.update(tab.id, { active: true }); + } + else { + browser.tabs.create({ active: true, url }); + } + } + + }, + })), + }); +}, { description: "Open the site searcher" }); diff --git a/glide/.config/glide/tabbar.glide.ts b/glide/.config/glide/tabbar.glide.ts new file mode 100644 index 0000000..3ad7817 --- /dev/null +++ b/glide/.config/glide/tabbar.glide.ts @@ -0,0 +1,256 @@ +// Status bar +// https://github.com/glide-browser/glide/discussions/147#discussioncomment-15573076 + +const status_bar_id = "glide-status-bar" + +const mode_colors: Record<keyof GlideModes, string> = { + "command": "--glide-mode-command", + "hint": "--glide-mode-hint", + "ignore": "--glide-mode-ignore", + "insert": "--glide-mode-insert", + "normal": "--glide-mode-normal", + "op-pending": "--glide-mode-op-pending", + "visual": "--glide-mode-visual", +} +const fallback_mode_color = "--glide-fallback-mode" + +glide.autocmds.create("ConfigLoaded", async () => { + const existing = document.getElementById(status_bar_id) + if (existing) { + existing.remove() + } + + const status_bar = DOM.create_element("div", { + id: status_bar_id, + children: [ + DOM.create_element("div", { + className: "glide-status-tabs", + children: [] + }), + DOM.create_element("div", { + className: "glide-status-right", + children: [] + }) + ] + }) + + const browser = document.getElementById("browser") + if (browser) { + browser.appendChild(status_bar) + } + + setTimeout(() => { + update_status_bar() + }, 100) +}) + +glide.autocmds.create("WindowLoaded", async () => { + let status_bar = document.getElementById(status_bar_id) as HTMLElement + if (!status_bar) { + status_bar = DOM.create_element("div", { + id: status_bar_id, + children: [ + DOM.create_element("div", { + className: "glide-status-tabs", + children: [] + }), + DOM.create_element("div", { + className: "glide-status-right", + children: [] + }) + ] + }) as HTMLElement + + const browser = document.getElementById("browser") + if (browser) { + browser.appendChild(status_bar) + } + } + + await update_status_bar() +}) + +function ensure_status_bar() { + let status_bar = document.getElementById(status_bar_id) as HTMLElement + if (!status_bar) { + status_bar = DOM.create_element("div", { + id: status_bar_id, + children: [ + DOM.create_element("div", { + className: "glide-status-tabs", + children: [] + }), + DOM.create_element("div", { + className: "glide-status-right", + children: [] + }) + ] + }) as HTMLElement + + const browser = document.getElementById("browser") + if (browser) { + browser.appendChild(status_bar) + } + } else { + let tabs = status_bar.querySelector(".glide-status-tabs") + let right = status_bar.querySelector(".glide-status-right") + if (!tabs || !right) { + status_bar.innerHTML = "" + status_bar.appendChild(DOM.create_element("div", { + className: "glide-status-tabs", + children: [] + })) + status_bar.appendChild(DOM.create_element("div", { + className: "glide-status-right", + children: [] + })) + } + } + return status_bar +} + +async function update_status_bar() { + // Ensure status bar exists + const status_bar = ensure_status_bar() as HTMLElement + if (!status_bar) return + + const tabs_container = status_bar.querySelector(".glide-status-tabs") + const right = status_bar.querySelector(".glide-status-right") + if (!tabs_container || !right) { + // If elements don't exist, recreate them + ensure_status_bar() + return + } + + try { + const current_window = await browser.windows.getCurrent(); + const tabs = await browser.tabs.query({ + windowId: current_window.id, + }); + const active_tab = await glide.tabs.active(); + const tab_index = tabs.findIndex(t => t.id == active_tab.id); + const url = active_tab.url; + const title = active_tab.title || "Untitled"; + + let display_url = url || "about:blank"; + + if (display_url.length > 50) { + display_url = display_url.substring(0, 47) + "..."; + } + + tabs_container.textContent = `[${tab_index + 1}/${tabs.length}]`; + right.textContent = `${title} | ${display_url}`; + } catch (e) { + tabs_container.textContent = e; + right.textContent = "Error loading tabs" + } +} + +glide.autocmds.create("ModeChanged", "*", (args) => { + const style_id = "glide-custom-mode-indicator" + glide.styles.remove(style_id) + glide.styles.add(` + #browser { + border-bottom: 3px solid var(${mode_colors[args.new_mode] ?? fallback_mode_color}) + } + `, { id: style_id }) +}) + +glide.autocmds.create("UrlEnter", /.*/, async () => { + await update_status_bar() +}) + +browser.tabs.onActivated.addListener(async () => { + await update_status_bar() +}) + +browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => { + if (changeInfo.url || changeInfo.title) { + await update_status_bar() + } +}) + +browser.tabs.onCreated.addListener(async () => { + await update_status_bar() +}) + +browser.tabs.onRemoved.addListener(async () => { + await update_status_bar() +}); + +browser.windows.onFocusChanged.addListener(async (windowId) => { + await update_status_bar(); +}); + +glide.styles.add(` + #${status_bar_id} { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 24px; + background-color: #1e1e1e; + color: #d4d4d4; + font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', 'Courier New', monospace; + font-size: 12px; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 12px; + border-top: 1px solid #3e3e3e; + z-index: 10000; + box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3); + overflow: hidden; + } + + #${status_bar_id} .glide-status-tabs { + display: flex; + align-items: center; + gap: 0; + flex: 1; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + -ms-overflow-style: none; + } + + #${status_bar_id} .glide-status-tabs::-webkit-scrollbar { + display: none; + } + + #${status_bar_id} .glide-tab-item { + color: #858585; + cursor: pointer; + padding: 2px 4px; + border-radius: 2px; + white-space: nowrap; + transition: background-color 0.2s ease, color 0.2s ease; + } + + #${status_bar_id} .glide-tab-item:hover { + background-color: #2e2e2e; + color: #d4d4d4; + } + + #${status_bar_id} .glide-tab-item.active { + color: #569cd6; + font-weight: 600; + background-color: #2a2a2a; + } + + #${status_bar_id} .glide-tab-separator { + color: #3e3e3e; + user-select: none; + } + + #${status_bar_id} .glide-status-right { + color: #858585; + margin-left: 12px; + white-space: nowrap; + flex-shrink: 0; + } + + #browser { + padding-bottom: 24px; + } + `, { id: "glide-status-bar-styles" }); |
