Last active
April 14, 2025 22:15
-
-
Save rbreaves/d195582a42838627dd94d9707df0c53a to your computer and use it in GitHub Desktop.
Tmux or i3 style Tile Zooming for Vivaldi Web Browser
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// References | |
// https://forum.vivaldi.net/topic/10549/modding-vivaldi?page=1 | |
// https://forum.vivaldi.net/topic/33122/custom-keyboard-shortcuts-mod | |
// vivaldi://experiments | |
// Enable "Allow for using CSS modifications". | |
// Restart vivaldi | |
// mod /Applications/Vivaldi.app/Contents/Frameworks/Vivaldi\ Framework.framework/Versions/[current version]/Resources/vivaldi/window.html | |
// insert inside body tag | |
// <script src="custom.js"></script> | |
// Debug via inspect here on Window.html | |
// vivaldi://inspect/#apps/ | |
// List all tiles | |
// [...document.querySelectorAll('.webpageview')].filter(el => el.offsetParent !== null) | |
(function tileZoomHotkeyMod() { | |
const SHORTCUTS = { | |
"Ctrl+Shift+Z": () => { | |
const stack = document.querySelector('#webpage-stack > .tiled.visible'); | |
if (!stack) return; | |
const allViews = [...stack.querySelectorAll('.webpageview.tiled.visible.pagefocusstop')]; | |
const activeView = stack.querySelector('.webpageview.active.tiled.visible.pagefocusstop'); | |
if (!activeView || allViews.length < 2) return; | |
const isZoomed = activeView.dataset.zoomed === 'true'; | |
if (!isZoomed) { | |
const allTiles = [...stack.querySelectorAll('.tile')]; | |
allTiles.forEach(tile => { | |
const containsActive = tile.contains(activeView); | |
tile.dataset.prevDisplay = tile.style.display || ''; | |
tile.dataset.prevFlex = tile.style.flex || ''; | |
tile.dataset.prevHeight = tile.style.height || ''; | |
tile.dataset.prevGridTemplateRows = tile.style.gridTemplateRows || ''; | |
tile.dataset.prevGridTemplateColumns = tile.style.gridTemplateColumns || ''; | |
if (!containsActive) { | |
tile.style.display = 'none'; | |
} else { | |
tile.style.display = 'block'; | |
tile.style.flex = '1 1 100%'; | |
tile.style.height = '100%'; | |
tile.style.gridTemplateRows = 'unset'; | |
tile.style.gridTemplateColumns = 'unset'; | |
} | |
}); | |
let node = activeView.parentElement; | |
while (node && node !== stack) { | |
if (node.classList.contains('tiled')) { | |
node.dataset.prevGridTemplateRows = node.style.gridTemplateRows || ''; | |
node.dataset.prevGridTemplateColumns = node.style.gridTemplateColumns || ''; | |
node.style.gridTemplateRows = 'unset'; | |
node.style.gridTemplateColumns = 'unset'; | |
} | |
node = node.parentElement; | |
} | |
stack.dataset.prevStyle = stack.getAttribute('style') || ''; | |
stack.style.gridTemplateRows = 'unset'; | |
stack.style.gridTemplateColumns = '100%'; | |
activeView.dataset.zoomed = 'true'; | |
} else { | |
const allTiles = [...stack.querySelectorAll('.tile')]; | |
allTiles.forEach(tile => { | |
tile.style.display = tile.dataset.prevDisplay || ''; | |
tile.style.flex = tile.dataset.prevFlex || ''; | |
tile.style.height = tile.dataset.prevHeight || ''; | |
tile.style.gridTemplateRows = tile.dataset.prevGridTemplateRows || ''; | |
tile.style.gridTemplateColumns = tile.dataset.prevGridTemplateColumns || ''; | |
delete tile.dataset.prevDisplay; | |
delete tile.dataset.prevFlex; | |
delete tile.dataset.prevHeight; | |
delete tile.dataset.prevGridTemplateRows; | |
delete tile.dataset.prevGridTemplateColumns; | |
}); | |
let node = activeView.parentElement; | |
while (node && node !== stack) { | |
if (node.classList.contains('tiled')) { | |
node.style.gridTemplateRows = node.dataset.prevGridTemplateRows || ''; | |
node.style.gridTemplateColumns = node.dataset.prevGridTemplateColumns || ''; | |
delete node.dataset.prevGridTemplateRows; | |
delete node.dataset.prevGridTemplateColumns; | |
} | |
node = node.parentElement; | |
} | |
stack.setAttribute('style', stack.dataset.prevStyle || ''); | |
delete stack.dataset.prevStyle; | |
activeView.dataset.zoomed = 'false'; | |
} | |
}, | |
"Ctrl+Shift+H": () => { | |
const activeView = document.querySelector('.webpageview.active.tiled.visible.pagefocusstop'); | |
if (!activeView) return; | |
const tile = activeView.closest('.tile'); | |
if (!tile) return; | |
const container = tile.parentElement?.classList.contains('tiled') ? tile.parentElement : null; | |
if (!container) return; | |
const isHidden = tile.dataset.hidden === 'true'; | |
if (!isHidden) { | |
tile.dataset.prevDisplay = tile.style.display || ''; | |
tile.dataset.prevFlex = tile.style.flex || ''; | |
tile.style.display = 'none'; | |
tile.dataset.hidden = 'true'; | |
if (container.style.gridTemplateRows.includes(' ')) { | |
container.dataset.prevGridTemplateRows = container.style.gridTemplateRows; | |
container.style.gridTemplateRows = container.style.gridTemplateRows.split(' ').slice(1).join(' '); | |
} | |
if (container.style.gridTemplateColumns.includes(' ')) { | |
container.dataset.prevGridTemplateColumns = container.style.gridTemplateColumns; | |
container.style.gridTemplateColumns = container.style.gridTemplateColumns.split(' ').slice(1).join(' '); | |
} | |
const siblings = [...container.querySelectorAll(':scope > .tile')].filter(t => t !== tile && t.style.display !== 'none'); | |
siblings.forEach(sib => { | |
sib.dataset.prevFlexSibling = sib.style.flex || ''; | |
sib.style.flex = '1 1 auto'; | |
}); | |
} else { | |
tile.style.display = tile.dataset.prevDisplay || ''; | |
tile.style.flex = tile.dataset.prevFlex || ''; | |
delete tile.dataset.prevDisplay; | |
delete tile.dataset.prevFlex; | |
delete tile.dataset.hidden; | |
if (container.dataset.prevGridTemplateRows) { | |
container.style.gridTemplateRows = container.dataset.prevGridTemplateRows; | |
delete container.dataset.prevGridTemplateRows; | |
} | |
if (container.dataset.prevGridTemplateColumns) { | |
container.style.gridTemplateColumns = container.dataset.prevGridTemplateColumns; | |
delete container.dataset.prevGridTemplateColumns; | |
} | |
const siblings = [...container.querySelectorAll(':scope > .tile')].filter(t => t !== tile); | |
siblings.forEach(sib => { | |
if ('prevFlexSibling' in sib.dataset) { | |
sib.style.flex = sib.dataset.prevFlexSibling; | |
delete sib.dataset.prevFlexSibling; | |
} | |
}); | |
} | |
}, | |
"Ctrl+Shift+R": () => { | |
const tiles = document.querySelectorAll('.tile'); | |
tiles.forEach(tile => { | |
tile.style.display = ''; | |
tile.style.flex = ''; | |
tile.style.height = ''; | |
tile.style.gridTemplateRows = ''; | |
tile.style.gridTemplateColumns = ''; | |
delete tile.dataset.prevDisplay; | |
delete tile.dataset.prevFlex; | |
delete tile.dataset.prevHeight; | |
delete tile.dataset.prevGridTemplateRows; | |
delete tile.dataset.prevGridTemplateColumns; | |
delete tile.dataset.hidden; | |
}); | |
const tiledContainers = document.querySelectorAll('.tiled'); | |
tiledContainers.forEach(container => { | |
container.style.gridTemplateRows = ''; | |
container.style.gridTemplateColumns = ''; | |
delete container.dataset.prevGridTemplateRows; | |
delete container.dataset.prevGridTemplateColumns; | |
}); | |
const allViews = document.querySelectorAll('.webpageview.tiled.visible.pagefocusstop'); | |
allViews.forEach(view => { | |
delete view.dataset.zoomed; | |
}); | |
const stack = document.querySelector('#webpage-stack > .tiled.visible'); | |
if (stack?.dataset.prevStyle) { | |
stack.setAttribute('style', stack.dataset.prevStyle || ''); | |
delete stack.dataset.prevStyle; | |
} | |
console.log("[TileZoomHotkey] Layout reset."); | |
} | |
}; | |
function keyCombo(sourceWindow, combination, autoRepeat) { | |
const customShortcut = SHORTCUTS[combination]; | |
if (sourceWindow !== vivaldiWindowId || autoRepeat) return; | |
if (customShortcut) customShortcut(); | |
} | |
function initMod() { | |
if (document.querySelector("#browser") && vivaldi.tabsPrivate) { | |
vivaldi.tabsPrivate.onKeyboardShortcut.addListener(keyCombo); | |
console.log("[TileZoomHotkey] Ready — Ctrl+Shift+Z (zoom), Ctrl+Shift+H (hide), Ctrl+Shift+R (reset)."); | |
} else { | |
setTimeout(initMod, 500); | |
} | |
} | |
initMod(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment