Open in Dialog mod
-
I've created a mod to open links or a search in a dialog popup.
You can right click on a link to get a context menu option to open the link as dialog.
[Dialog Tab] Open Link
If text is selected and you right click on the selected text you get the options
[Dialog Tab] Search for "..."
(where "..." is the selected text) and[Dialog Tab] Search with
(which has all your search engines as sub context menu entries).
The
[Dialog Tab] Search for "..."
option searches with your default search engine (it uses the normal one or the one for private windows depending on the window state) for the selected text. With the[Dialog Tab] Search with
option you can choose which search engine to use.
Features:
- with
Esc
or clicking outside the dialog you can close the dialog - press
Ctrl+Alt+Period
orCtrl+Shift+F
to open the dialog and search for the selected text - click with the middle mouse button on a link and hold it for 0.5 seconds to open the link with a dialog
- holding
Ctrl+Alt
and click a link with left or middle mouse button to open the link in a dialog - holding middle mouse button or holding
Ctrl+Alt
and click a link with left or middle mouse button now works from web panels and open dialogs - changes to your search engines are recognized and the menus get updated
- if you edit the URL of a dialog and hit
Enter
the dialog will open the URL - if it's not a URL the dialog will use the default search engine to search for the input
The dialog has a menu at the top that opens up on mouse over:
You can use the buttons to navigate back and forward, show a reader view of the website, open the current website in a new tab in the foreground or background and copy the URL. To leave the reader view, simply click the button again.
You can also stack multiple tabs which would look like this:
JS code:
/** * Opens links in a dialog, either by key combinations, holding middle mouse button or context menu * Forum link: https://forum.vivaldi.net/topic/92501/open-in-dialog-mod?_=1717490394230 */ (function () { let searchEngineCollection, defaultSearchId, privateSearchId, createdContextMenuIds = [], webviews = new Map(), fromPanel; // Wait for the browser to come to a ready state setTimeout(function waitDialog() { const browser = document.getElementById('browser'); if (!browser) { setTimeout(waitDialog, 300); return; } // Create a context menu item to call on a link createContextMenuOption(); // create initial search engine context menus updateSearchEnginesAndContextMenu(); // detect changes in search engines and recreate the context menus vivaldi.searchEngines.onTemplateUrlsChanged.addListener(() => { removeContextMenuSelectSearch(); updateSearchEnginesAndContextMenu(); }); // Setup keyboard shortcuts vivaldi.tabsPrivate.onKeyboardShortcut.addListener(keyCombo); chrome.runtime.onMessage.addListener((message) => { if (message.url) { fromPanel = message.fromPanel; dialogTab(message.url, message.fromPanel); } }); chrome.webNavigation.onCompleted.addListener((details) => { let fromPanel = false, webview; if (details.tabId < 0) { let view = Array.from(webviews.values()).pop(); webview = view?.webview; fromPanel = view?.fromPanel; } else { webview = document.querySelector(`.webpanel-content webview[src*="${details.url}"]`); if (webview) fromPanel = true; else webview = document.querySelector(`webview[tab_id="${details.tabId}"]`); } webview?.executeScript({code: `(${setUrlClickObserver})(${fromPanel})`}); }); }, 300); /** * Checks if a link is clicked by middle mouse while pressing Ctrl + Alt, then fires an event with the Url */ function setUrlClickObserver(fromPanel = false) { if (this.dialogEventListenerSet) return; let timer; document.addEventListener('pointerdown', function (event) { // Check if the Ctrl key, Shift key, and middle mouse button were pressed if (event.ctrlKey && event.altKey && (event.button === 0 || event.button === 1)) { callDialog(event); } else if (event.button === 1) { timer = setTimeout(() => callDialog(event), 500); } }); document.addEventListener('pointerup', function (event) { if (event.button === 1) { clearTimeout(timer); } }); this.dialogEventListenerSet = true; let callDialog = (event) => { let link = getLinkElement(event.target); if (link) { event.preventDefault(); chrome.runtime.sendMessage({url: link.href, fromPanel: fromPanel}); } }; let getLinkElement = (el) => { let childLink = el.querySelector('a[href]:not([href="#"])'); if (childLink) { return childLink; } while (el) { if (el.tagName != null && el.tagName.toLowerCase() === 'a') { return el.getAttribute('href') !== '#' ? el : null; } el = el.parentNode; } return null; }; } /** * Creates context menu items to open dialog tab */ function createContextMenuOption() { chrome.contextMenus.create({ id: 'dialog-tab-link', title: '[Dialog Tab] Open Link', contexts: ['link'] }); chrome.contextMenus.create({ id: 'search-dialog-tab', title: '[Dialog Tab] Search for "%s"', contexts: ['selection'] }); chrome.contextMenus.create({ id: 'select-search-dialog-tab', title: '[Dialog Tab] Search with', contexts: ['selection'] }); chrome.contextMenus.onClicked.addListener(function (itemInfo) { if (itemInfo.menuItemId === 'dialog-tab-link') { dialogTab(itemInfo.linkUrl); } else if (itemInfo.menuItemId === 'search-dialog-tab') { let engineId = window.incognito ? privateSearchId : defaultSearchId; dialogTabSearch(engineId, itemInfo.selectionText); } else if (itemInfo.parentMenuItemId === 'select-search-dialog-tab') { let engineId = itemInfo.menuItemId.substr(itemInfo.parentMenuItemId.length); dialogTabSearch(engineId, itemInfo.selectionText); } }); } /** * Creates sub-context menu items for select search engine menu item */ function createContextMenuSelectSearch() { searchEngineCollection.forEach(function (engine) { if (!createdContextMenuIds.includes(engine.id)) { chrome.contextMenus.create({ id: 'select-search-dialog-tab' + engine.id, parentId: 'select-search-dialog-tab', title: engine.name, contexts: ['selection'] }); createdContextMenuIds.push(engine.id); } }); } /** * updates the search engines and context menu */ async function updateSearchEnginesAndContextMenu() { const searchEngines = await vivaldi.searchEngines.getTemplateUrls(); searchEngineCollection = searchEngines.templateUrls; defaultSearchId = searchEngines.defaultSearch; privateSearchId = searchEngines.defaultPrivate; createContextMenuSelectSearch(); } /** * Updates sub-context menu items for select search engine menu item */ function removeContextMenuSelectSearch() { searchEngineCollection.forEach(function (engine) { if (createdContextMenuIds.includes(engine.id)) { chrome.contextMenus.remove('select-search-dialog-tab' + engine.id); createdContextMenuIds.splice(createdContextMenuIds.indexOf(engine.id), 1); } }); } /** * Prepares url for search, calls dailogTab function * @param {String} engineId engine id of the engine to be used * @param {int} selectionText the text to search */ async function dialogTabSearch(engineId, selectionText) { let searchRequest = await vivaldi.searchEngines.getSearchRequest(engineId, selectionText); dialogTab(searchRequest.url); } /** * Handle a potential keyboard shortcut (copy from KeyboardMachine) * @param {number} id I don't know what this does, but it's an extra argument * @param {String} combination written in the form (CTRL+SHIFT+ALT+KEY) */ function keyCombo(id, combination) { /** Open Default Search Engine in Dialog and search for selected text */ const searchForSelectedText = async () => { let tabs = await chrome.tabs.query({active: true}); vivaldi.utilities.getSelectedText(tabs[0].id, (text) => dialogTabSearch(defaultSearchId, text)); }; const SHORTCUTS = { 'Ctrl+Alt+Period': searchForSelectedText, 'Ctrl+Shift+F': searchForSelectedText, Esc: () => removeDialog(Array.from(webviews.keys()).pop()) }; const customShortcut = SHORTCUTS[combination]; if (customShortcut) { customShortcut(); } } /** * removes the dialog */ function removeDialog(webviewId) { let data = webviews.get(webviewId); if (data) { data.divContainer.remove(); webviews.delete(webviewId); } } /** * Checks if the current window is the correct window to show the dialog and then opens the dialog * @param {string} linkUrl the url to load * @param {boolean} fromPanel indicates whether the dialog is opened from a panel */ function dialogTab(linkUrl, fromPanel = undefined) { chrome.windows.getLastFocused(function (window) { if (window.id === vivaldiWindowId && window.state !== chrome.windows.WindowState.MINIMIZED) { showDialog(linkUrl, fromPanel); } }); } /** * Opens a link in a dialog like display in the current visible tab * @param {string} linkUrl the url to load * @param {boolean} fromPanel indicates whether the dialog is opened from a panel */ function showDialog(linkUrl, fromPanel) { const dialogContainer = document.createElement('div'), dialogTab = document.createElement('div'), webview = document.createElement('webview'), webviewId = 'dialog-' + getWebviewId(), progressBar = document.createElement('div'), optionsContainer = document.createElement('div'); if (fromPanel === undefined && webviews.size !== 0) { fromPanel = Array.from(webviews.values()).pop().fromPanel; } webviews.set(webviewId, { divContainer: dialogContainer, webview: webview, fromPanel: fromPanel }); //#region dialogTab properties dialogTab.setAttribute('class', 'dialog-tab'); dialogTab.style.width = 85 - 5 * webviews.size + '%'; dialogTab.style.height = 95 - 5 * webviews.size + '%'; //#endregion //#region progressBar properties progressBar.setAttribute('class', 'progress-bar'); progressBar.id = 'progressBar-' + webviewId; //#endregion //#region optionsContainer properties optionsContainer.setAttribute('class', 'options-container'); optionsContainer.innerHTML = getEllipsisContent(); let timeout; optionsContainer.addEventListener('mouseover', function () { if (optionsContainer.children.length === 1) { optionsContainer.innerHTML = ''; showWebviewOptions(webviewId, optionsContainer); } if (timeout) { clearTimeout(timeout); timeout = undefined; } }); optionsContainer.addEventListener('mouseleave', function () { if (!timeout) { timeout = setTimeout(() => { while (optionsContainer.firstChild) { optionsContainer.removeChild(optionsContainer.firstChild); } optionsContainer.innerHTML = getEllipsisContent(); }, 1500); } }); //#endregion //#region webview properties webview.setAttribute('src', linkUrl); webview.id = webviewId; let progress = 0, interval; const clearProgressInterval = (loadStop) => { if (interval) { clearInterval(interval); interval = undefined; } if (loadStop) { const progressbar = document.getElementById('progressBar-' + webviewId); progressbar.style.width = '100%'; setTimeout(() => { progress = 0; progressbar.style.visibility = 'hidden'; progressbar.style.width = progress + '%'; }, 250); } } webview.addEventListener('loadstart', function () { this.style.backgroundColor = 'var(--colorBorder)'; const progressbar = document.getElementById('progressBar-' + webviewId); progressbar.style.visibility = 'visible'; if (!interval) { interval = setInterval(() => { if (progress >= 100) { clearProgressInterval(); } else { progress++; progressBar.style.width = progress + '%'; } }, 10); } if (document.getElementById('input-' + this.id) !== null) { document.getElementById('input-' + this.id).value = this.src; } }); webview.addEventListener('loadstop', function () { clearProgressInterval(true); }); fromPanel && webview.addEventListener('mousedown', (event) => event.stopPropagation()); //#endregion //#region dialogContainer properties dialogContainer.setAttribute('class', 'dialog-container'); let stopEvent = (event) => { event.preventDefault(); event.stopPropagation(); if (event.target.id === 'input-' + webviewId) { const inputElement = event.target; // Calculate the cursor position based on the click location const offsetX = event.clientX - inputElement.getBoundingClientRect().left; // Create a canvas to measure text width const context = document.createElement('canvas').getContext('2d'); context.font = window.getComputedStyle(inputElement).font; // Measure the width of the text up to each character let cursorPosition = 0, textWidth = 0; for (let i = 0; i < inputElement.value.length; i++) { const charWidth = context.measureText(inputElement.value[i]).width; if (textWidth + charWidth > offsetX) { cursorPosition = i; break; } textWidth += charWidth; cursorPosition = i + 1; } // Manually focus the input element and set the cursor position inputElement.focus({preventScroll: true}); inputElement.setSelectionRange(cursorPosition, cursorPosition); } }; fromPanel && document.body.addEventListener('pointerdown', stopEvent); dialogContainer.addEventListener('click', function (event) { if (event.target === this) { fromPanel && document.body.removeEventListener('pointerdown', stopEvent); removeDialog(webviewId); } }); //#endregion dialogTab.appendChild(optionsContainer); dialogTab.appendChild(progressBar); dialogTab.appendChild(webview); dialogContainer.appendChild(dialogTab); // Get for current tab and append divContainer fromPanel ? document.querySelector('#browser').appendChild(dialogContainer) : document.querySelector('.active.visible.webpageview').appendChild(dialogContainer); } /** * Displays open in tab buttons and current url in input element * @param {string} webviewId is the id of the webview * @param {Object} thisElement the current instance divOptionContainer (div) element */ function showWebviewOptions(webviewId, thisElement) { let inputId = 'input-' + webviewId, data = webviews.get(webviewId), webview = data ? data.webview : undefined; if (webview && document.getElementById(inputId) === null) { const input = document.createElement('input', 'text'); input.value = webview.src; input.id = inputId; input.setAttribute('class', 'dialog-input'); input.addEventListener('keydown', async function (event) { if (event.key === 'Enter') { let value = input.value; if ( value.startsWith('http://') || value.startsWith('https://') || value.startsWith('file://') || value.startsWith('vivaldi://') || value === 'about:blank' ) { webview.src = value; } else { const searchRequest = await vivaldi.searchEngines.getSearchRequest(defaultSearchId, value); webview.src = searchRequest.url; } } }); let buttonBack = createOptionsButton(getBackButtonContent(), webview.back.bind(webview)), buttonForward = createOptionsButton(getForwardButtonContent(), webview.forward.bind(webview)), buttonReload = createOptionsButton(getReloadButtonContent(), webview.reload.bind(webview)), buttonReaderView = createOptionsButton(getReaderViewButtonContent(), showReaderView.bind(this, webview)), buttonNewTab = createOptionsButton(getNewTabButtonContent(), openNewTab.bind(this, inputId, true)), buttonBackgroundTab = createOptionsButton(getBackgroundTabButtonContent(), openNewTab.bind(this, inputId, false)); thisElement.append(buttonBack, buttonForward, buttonReload, buttonReaderView, buttonNewTab, buttonBackgroundTab, input); } } /** * Create a button with default style for the web view options. * @param {Node | string} content the content of the button to display * @param {Function} clickListenerCallback the click listeners callback function */ function createOptionsButton(content, clickListenerCallback) { const button = document.createElement('button'); button.setAttribute('class', 'options-button'); if (typeof content === 'string') { button.innerHTML = content; } else { button.appendChild(content); } button.addEventListener('click', function (event) { if (event.target === this || this.firstChild) { clickListenerCallback(); } }); return button; } /** * Returns a random, verified id. */ function getWebviewId() { let tempId = 0; while (true) { if (document.getElementById('dialog-' + tempId) === null) { break; } tempId = Math.floor(Math.random() * 1000 + 1); } return tempId; } /** * Sets the webviews content to a reader version * * @param {webview} webview the webview to update */ function showReaderView(webview) { if (webview.src.includes('https://reader-next.pages.dev/?url=')) { webview.src = webview.src.replace('https://reader-next.pages.dev/?url=', ''); } else { webview.src = 'https://reader-next.pages.dev?url=' + webview.src; const injectCSS = () => { const script = ` const style = document.createElement('style'); style.textContent = \` body { max-width: 100ch; } body > *:not(#output):not(#title) { display: none; } \`; document.head.appendChild(style); `; webview.executeScript({code: script}); webview.removeEventListener('loadstop', injectCSS); }; webview.addEventListener('loadstop', injectCSS); } } /** * Opens a new Chrome tab with specified active boolean value * @param {string} inputId is the id of the input containing current url * @param {boolean} active indicates whether the tab is active or not (background tab) */ function openNewTab(inputId, active) { const url = document.getElementById(inputId).value; chrome.tabs.create({url: url, active: active}); } /** * Returns string of ellipsis svg icon */ function getEllipsisContent() { return '<svg xmlns="http://www.w3.org/2000/svg" height="2em" viewBox="0 0 448 512"><path d="M8 256a56 56 0 1 1 112 0A56 56 0 1 1 8 256zm160 0a56 56 0 1 1 112 0 56 56 0 1 1 -112 0zm216-56a56 56 0 1 1 0 112 56 56 0 1 1 0-112z"/></svg>'; } /** * Gets the svg icon for the back button */ function getBackButtonContent() { const svg = document.querySelector('.button-toolbar [name="Back"] svg'); return svg ? svg.cloneNode(true) : '<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.2 288 416 288c17.7 0 32-14.3 32-32s-14.3-32-32-32l-306.7 0L214.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>'; } /** * Gets the svg icon for the forward button */ function getForwardButtonContent() { const svg = document.querySelector('.button-toolbar [name="Forward"] svg'); return svg ? svg.cloneNode(true) : '<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"/></svg>'; } /** * Gets the svg icon for the reload button */ function getReloadButtonContent() { const svg = document.querySelector('.button-toolbar [name="Reload"] svg'); return svg ? svg.cloneNode(true) : '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M125.7 160H176c17.7 0 32 14.3 32 32s-14.3 32-32 32H48c-17.7 0-32-14.3-32-32V64c0-17.7 14.3-32 32-32s32 14.3 32 32v51.2L97.6 97.6c87.5-87.5 229.3-87.5 316.8 0s87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3s-163.8-62.5-226.3 0L125.7 160z"/></svg>'; } /** * Gets the svg icon for the reader view button */ function getReaderViewButtonContent() { return '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M3 4h10v1H3zM3 6h10v1H3zM3 8h10v1H3zM3 10h6v1H3z"></path></svg>'; } /** * Returns string of external link alt svg icon */ function getNewTabButtonContent() { return '<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M320 0c-17.7 0-32 14.3-32 32s14.3 32 32 32h82.7L201.4 265.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L448 109.3V192c0 17.7 14.3 32 32 32s32-14.3 32-32V32c0-17.7-14.3-32-32-32H320zM80 32C35.8 32 0 67.8 0 112V432c0 44.2 35.8 80 80 80H400c44.2 0 80-35.8 80-80V320c0-17.7-14.3-32-32-32s-32 14.3-32 32V432c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V112c0-8.8 7.2-16 16-16H192c17.7 0 32-14.3 32-32s-14.3-32-32-32H80z"/></svg>'; } /** * Returns string of external link square alt svg icon */ function getBackgroundTabButtonContent() { return '<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><path d="M384 32c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96C0 60.7 28.7 32 64 32H384zM160 144c-13.3 0-24 10.7-24 24s10.7 24 24 24h94.1L119 327c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l135-135V328c0 13.3 10.7 24 24 24s24-10.7 24-24V168c0-13.3-10.7-24-24-24H160z"/></svg>'; } })();
CSS code:
@keyframes dialog-tab-animate-progress-bar { 0% { left: -100%; } 100% { left: 100%; } } .dialog-container { z-index: 999; position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, .4); transition-property: background-color; transition-duration: 0.1s; transition-timing-function: ease; transition-delay: 0s; backdrop-filter: blur(1px); border-radius: var(--radius); display: flex; justify-content: center; align-items: center; .dialog-tab { position: relative; display: flex; flex-direction: column; .options-container { height: 35px; width: 100%; text-align: center; align-items: center; color: white; z-index: 1160; .options-button { background: transparent; margin: 0 0.5rem 0 0.5rem; border: unset; cursor: pointer; } .dialog-input { background: var(--colorAccentBgAlpha); color: white; border: unset; width: 20%; margin: 0 0.5rem 0 0.5rem; padding: 0.25rem 0.5rem; } } .progress-bar { height: 5px; max-width: calc(100% - var(--radius) * 2); margin: 0 calc(var(--radius) / 2); background-color: #0080ff; border-radius: var(--radius); transition: width 0.2s linear; overflow: hidden; position: relative; &[style*="width: 100%"]:after { content: ""; width: 50%; height: 100%; background-image: linear-gradient( to right, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.5) ); position: absolute; top: 0; left: -100%; animation: dialog-tab-animate-progress-bar 1.5s linear infinite; } } webview { position: relative; flex: 1; overflow: hidden; border-radius: var(--radius); } } }
I've used code parts from @biruktes and @tam710562 (Web Page preview, Open link or new tab in a dialog (mod))
EDIT 1: some code clean up & added fix for duplicate dialog
EDIT 2: fixed Esc not working with latest Vivaldi snapshot
EDIT 3: holding middle mouse button or holdingCtrl+Alt
and click a link with left or middle mouse button now works from web panels and open dialogs
EDIT 4: addedCtrl+Shift+F
as shortcut to search for selected text in a dialog & bug fixes
EDIT 5: improved detecting if dialog is called from a web panel
EDIT 6: improved search engine handling (thanks to @tam710562)
EDIT 7: fixed dialog not opening correctly from web panel (latest Snapshot)
EDIT 8: it's now possible to edit the dialogs URL
EDIT 9: improved opening dialog from web panels
EDIT 10: added a new button for the options toolbar to show the current website in a reader view
EDIT 11: bug fixes & added a new button to reload the current page
EDIT 12: improved detection of links
EDIT 13: improved position of progress bar and added animation
EDIT 14: improved positioning and split mod into JS and CSS file for easier maintenance - with
-
I pasted all the provided code in a custom.js file and after extensive testing I can assure everyone it runs without a single error. Furthermore I couldn’t detect any slowdowns, it’s as if it doesn’t use any resources at all. Good job!
-
-
I've found parts of the mod in the forum, but can't find the thread anymore. The mod was broken and didn't had all of the features it currently has. If anyone finds the mod I will add it as reference.
Should you find it, let us know.
-
@luetage said in Open in Dialog mod:
I pasted all the provided code in a custom.js file and after extensive testing I can assure everyone it runs without a single error. Furthermore I couldn’t detect any slowdowns, it’s as if it doesn’t use any resources at all. Good job!
Thanks for appreciating my hard work
I've added the code to the first post. I don't know how I could forget the code x'D
-
The real author of Dialog Tab Mod is @biruktes
with the help of @tam710562 who provided the code for search engine integration, and more improvements.I use it from the first post in 2019 and it is not BROKEN at all:
First post:
https://forum.vivaldi.net/topic/29845/web-page-previewSecond post:
https://forum.vivaldi.net/topic/38084/open-link-or-new-tab-in-a-dialog-mod -
@barbudo2005 Thanks, I will add them in the first post. If I remember correctly, the part with the search engines wasn't working anymore, but maybe I made a mistake.
-
This is the code I have in custom.js:
/*DIALOG TAB*/ (function () { var searchEngineCollection; // Wait for the browser to come to a ready state setTimeout(function wait() { const browser = document.getElementById('browser'); if (browser) { chrome.storage.local.get({ 'SEARCH_ENGINE_COLLECTION': { engines: [] } }, function (res) { searchEngineCollection = res.SEARCH_ENGINE_COLLECTION; // Create a context menu item to call on a link createContextMenuOption(); // Setup keyboard shortcuts vivaldi.tabsPrivate.onKeyboardShortcut.addListener(keyCombo); }); chrome.storage.local.onChanged.addListener(function (changes, namespace) { if (changes.SEARCH_ENGINE_COLLECTION) { searchEngineCollection = changes.SEARCH_ENGINE_COLLECTION.newValue; createOrRemoveContextMenuSelectSearch(changes.SEARCH_ENGINE_COLLECTION.oldValue) } }); } else { setTimeout(wait, 300); } }, 300); /** * Creates context menu items to open dialog tab */ function createContextMenuOption() { chrome.contextMenus.create({ 'id': 'dialog-tab-link', 'title': '&DIALOG TAB Open Link', 'contexts': ['link'] }); chrome.contextMenus.create({ 'id': 'search-dialog-tab', 'title': 'DIALOG TAB SEARCH for "%s" with GOOGLE', 'contexts': ['selection'] }); chrome.contextMenus.create({ 'id': 'select-search-dialog-tab', 'title': 'DIALOG TAB SEARCH WITH', 'contexts': ['selection'] }); createContextMenuSelectSearch(); chrome.contextMenus.onClicked.addListener(function (itemInfo) { chrome.windows.getLastFocused(function (window) { if (window.id === vivaldiWindowId && window.state !== 'minimized') { if (itemInfo.menuItemId === "dialog-tab-link") { dialogTab(itemInfo.linkUrl); } else if (itemInfo.menuItemId === 'search-dialog-tab') { var engineId = window.incognito ? searchEngineCollection.defaultPrivate : searchEngineCollection.default; dialogTabSearch(engineId, itemInfo.selectionText); } else if (itemInfo.parentMenuItemId === 'select-search-dialog-tab') { var engineId = itemInfo.menuItemId.substr(itemInfo.parentMenuItemId.length); dialogTabSearch(engineId, itemInfo.selectionText); } } }); }); } /** * Creates sub-context menu items for select search engine menu item */ function createContextMenuSelectSearch() { searchEngineCollection.engines.filter(e => e.removed !== true).forEach(function (engine) { chrome.contextMenus.create({ 'id': 'select-search-dialog-tab' + engine.id, 'parentId': 'select-search-dialog-tab', 'title': engine.name, 'contexts': ['selection'] }); }); } /** * Updates sub-context menu items for select search engine menu item * @param {Object} oldValue the value that is used as reference to old sub-menu items */ function createOrRemoveContextMenuSelectSearch(oldValue) { oldValue.engines.filter(e => e.removed !== true).forEach(function (engine) { chrome.contextMenus.remove('select-search-dialog-tab' + engine.id); }); createContextMenuSelectSearch(); } /** * Prepares url for search, calls dailogTab function * @param {String} engineId engine id of the engine to be used * @param {int} selectionText the text to search */ function dialogTabSearch(engineId, selectionText) { dialogTab(getEngine(engineId).url.replace(/%s/g, selectionText)); } /** * Returns engine from the collection variable with matching id * @param {int} engineId engine id of the required engine */ function getEngine(engineId) { return searchEngineCollection.engines.find(function (engine) { return engine.id === engineId; }); } /** * Handle a potential keyboard shortcut (copy from KeyboardMachine) * @param {String} combination written in the form (CTRL+SHIFT+ALT+KEY) * @param {boolean} extras I don't know what this does, but it's an extra argument */ function keyCombo(combination, extras) { const SHORTCUTS = { "Shift+Alt+Period": () => { // Open Default Search Engine in Dialog dialogTab(getEngine(searchEngineCollection.default).url.split("?")[0]); } }; const customShortcut = SHORTCUTS[combination]; if (customShortcut) { customShortcut(); } } /**/ /** * Opens a link in a dialog like display in the current visible tab * @param {string} linkUrl the url to load */ function dialogTab(linkUrl) { var webview = document.createElement("webview"); var webviewId = "dialog-" + getWebviewId(); var divOptionContainer = document.createElement("div"); var divContainer = document.createElement("div"); var progressBarContainer = document.createElement("div"); var progressBar = document.createElement("div"); //#region webview properties webview.setAttribute("src", linkUrl); webview.id = webviewId; webview.style.width = "90%"; webview.style.height = "95%"; webview.style.padding = "auto"; webview.style.margin = "30px 0px 0px 50px"; webview.style.overflow = "hidden"; webview.style.borderRadius = "10px"; webview.style.borderColor = "#3f81ea"; webview.style.borderBottomWidth= "1.5px"; webview.style.borderTopWidth= "1.5px"; webview.style.borderLeftWidth= "1.5px"; webview.style.borderRightWidth= "1.5px"; webview.style.borderStyle= "solid"; webview.style.backgroundColor = "rgba(23,25,26)"; /*webview.style.boxShadow = "3px 3px 3px 3px rgba(100,100,100)";*/ webview.addEventListener("loadstart", function () { this.style.backgroundColor = "rgba(23,25,26)"; this.style.borderColor = "rgba(59,129,230)"; document.getElementById("progressBar-" + webviewId).style.display = "block"; if (document.getElementById("input-" + this.id) !== null) { document.getElementById("input-" + this.id).value = this.src; } }); webview.addEventListener("loadstop", function () { document.getElementById("progressBar-" + webviewId).style.display = "none"; }); //#endregion //#region divOptionContainer properties divOptionContainer.style.height = "4%"; divOptionContainer.style.textAlign = "center"; divOptionContainer.style.margin = "auto"; divOptionContainer.style.color = "white"; divOptionContainer.style.zIndex = "300000000000"; divOptionContainer.innerHTML = getEllipsisContent(); divOptionContainer.firstElementChild.addEventListener("mouseover", function () { showWebviewOptions(webviewId, divOptionContainer); }); //#endregion //#region divContainer properties divContainer.setAttribute("class", "dialog-tab"); divContainer.style.zIndex = "300000000000"; divContainer.style.position = "fixed"; divContainer.style.top = "0"; divContainer.style.right = "0"; divContainer.style.bottom = "0"; divContainer.style.left = "0"; divContainer.style.backgroundColor = "rgba(23,24,25,.4)"; divContainer.style.transitionProperty = "background-color"; divContainer.style.transitionDuration = "0.1s"; divContainer.style.transitionTimingFunction = "ease"; divContainer.style.transitionDelay = "0s"; divContainer.addEventListener("click", function (event) { if (event.target === this) { this.remove(); } }); //#endregion //#region progressBarContainer properties progressBarContainer.style.width = "77%"; progressBarContainer.style.margin = "1.3rem auto auto"; progressBar.id = "progressBar-" + webviewId; progressBar.style.height = "5px"; progressBar.style.width = "10%"; progressBar.style.backgroundColor = "#0080ff"; progressBar.style.borderRadius = "5px"; //#endregion progressBarContainer.appendChild(progressBar); divContainer.appendChild(divOptionContainer); divContainer.appendChild(webview); divContainer.appendChild(progressBarContainer); // Get for current tab and append divContainer document.getElementsByClassName('active visible webpageview')[0].appendChild(divContainer); } /** * Displays open in tab buttons and current url in input element * @param {string} webviewId is the id of the webview * @param {Object} thisElement the current instance divOptionContainer (div) element */ function showWebviewOptions(webviewId, thisElement) { var inputId = "input-" + webviewId; console.log((document.getElementById(inputId) === null), webviewId); if (document.getElementById(inputId) === null) { var webviewSrc = document.getElementById(webviewId).src; var input = document.createElement('input', 'text'); var buttonNewTab = document.createElement('button'); var buttonBackgroundTab = document.createElement('button'); input.value = webviewSrc; input.id = inputId; input.setAttribute("readonly", ""); input.style.background = "transparent"; input.style.color = "rgba(22,24,25)"; input.style.border = "unset"; input.style.width = "20%"; input.style.margin = "0 0.5rem 0 0.5rem"; buttonNewTab.style.background = "transparent"; buttonNewTab.style.margin = "0 0.5rem 0 0.5rem"; buttonNewTab.style.border = "unset"; buttonNewTab.innerHTML = getNewtabContent(); buttonNewTab.addEventListener("click", function (event) { if (event.target === this || this.firstChild) { openNewTab(inputId, true); } }); buttonBackgroundTab.style.background = "transparent"; buttonBackgroundTab.style.margin = "0 0.5rem 0 0.5rem"; buttonBackgroundTab.style.border = "unset"; buttonBackgroundTab.innerHTML = getBacktabContent(); buttonBackgroundTab.addEventListener("click", function (event) { if (event.target === this || this.firstChild) { openNewTab(inputId, false); } }); thisElement.appendChild(buttonNewTab); thisElement.appendChild(buttonBackgroundTab); thisElement.appendChild(input); console.log(webviewSrc, thisElement); } } /** * Returns a random, verified id. */ function getWebviewId() { var tempId = 0; while (true) { if (document.getElementById("dialog-" + tempId) === null) { break; } tempId = Math.floor(Math.random() * 1000 + 1); } return tempId; } /** * Opens a new chrome tab with specified active boolean value * @param {string} inputId is the id of the input containing current url * @param {boolean} active indicates whether the tab is active or not (background tab) */ function openNewTab(inputId, active) { var url = document.getElementById(inputId).value; chrome.tabs.create({ "url": url, "active": active }); } /** * Returns string of ellipsis svg icon */ function getEllipsisContent() { return "<svg id=\"optionsIco\" aria-hidden=\"true\" focusable=\"false\" data-prefix=\"fas\" data-icon=\"ellipsis-h\" class=\"svg-inline--fa fa-ellipsis-h fa-w-16\" style=\"width: 25px; vertical-align: middle; margin: 0 0.5rem;\" role=\"img\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><path fill=\"currentColor\" d=\"M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z\"></path></svg>"; } /** * Returns string of external link alt svg icon */ function getNewtabContent() { return "<svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"fas\" data-icon=\"external-link-alt\" class=\"svg-inline--fa fa-external-link-alt fa-w-18\" style=\"width: 25px;\" role=\"img\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 576 512\"><path fill=\"currentColor\" d=\"M576 24v127.984c0 21.461-25.96 31.98-40.971 16.971l-35.707-35.709-243.523 243.523c-9.373 9.373-24.568 9.373-33.941 0l-22.627-22.627c-9.373-9.373-9.373-24.569 0-33.941L442.756 76.676l-35.703-35.705C391.982 25.9 402.656 0 424.024 0H552c13.255 0 24 10.745 24 24zM407.029 270.794l-16 16A23.999 23.999 0 0 0 384 303.765V448H64V128h264a24.003 24.003 0 0 0 16.97-7.029l16-16C376.089 89.851 365.381 64 344 64H48C21.49 64 0 85.49 0 112v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V287.764c0-21.382-25.852-32.09-40.971-16.97z\"></path></svg>"; } /** * Returns string of external link square alt svg icon */ function getBacktabContent() { return "<svg aria-hidden=\"true\" focusable=\"false\" data-prefix=\"fas\" data-icon=\"external-link-square-alt\" class=\"svg-inline--fa fa-external-link-square-alt fa-w-14\" style=\"width: 21px;\" role=\"img\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 448 512\"><path fill=\"currentColor\" d=\"M448 80v352c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V80c0-26.51 21.49-48 48-48h352c26.51 0 48 21.49 48 48zm-88 16H248.029c-21.313 0-32.08 25.861-16.971 40.971l31.984 31.987L67.515 364.485c-4.686 4.686-4.686 12.284 0 16.971l31.029 31.029c4.687 4.686 12.285 4.686 16.971 0l195.526-195.526 31.988 31.991C358.058 263.977 384 253.425 384 231.979V120c0-13.255-10.745-24-24-24z\"></path></svg>"; } })();
I made some changes in the UI.
-
@barbudo2005 If you test the code with the latest Vivaldi Snapshot (6.5.3189.3), the search engines aren't working anymore. That's why I mentioned it is broken.
-
I might look more into it later. but I fixed the search engine part by doing.
function dialogTabSearch(engineId, selectionText) { dialogTab("https://www.google.com"); }
-
@oudstand Just tried out your mod on my snapshot version and works really well! So well that I tried to create a chain command for it, then realized that "oh wait... dammit".
Two small things I noticed :
- you can keep opening dialogs on top of dialogs. nothing breaks, but maybe a "way" to see/noticed how many are opened or something would be neat (at least, I didnt see any)
- the UI on top to navigate back and forward works well, but it's not "vivaldi-looking", so to speak. Maybe if you can find a way to "reuse" existing icons (like the forward one) in the browser it would look even better (especially if it worked with themes!)
Again, great mod. Congrats!
-
@MasterLeo29 Thank you very much! This are some nice suggestions. I've updated the code and the first post. Now stacked dialogs will look like this:
Also I've adjusted the position and look of the navigation elements. I'm using the back and forward button from Vivaldi (if available) and added a fallback. Also I added a slight background to the URL.
Let me know what you think about it.
-
@oudstand Gave it a new test. The stacked dialogs do look nice. As for the "menu" changes, took me a while to "find" the menu because it was "hidden" when I tested it on my "big" monitor.
The main div you create for the items (the one with the on hover effect) has a "margin-top: 3.5%" that, on my monitor, made it so that the menu stood behind the dialog eheh.
Maybe changing the units from '%' to a better scaling one ? like vh (not an CSS guru, so you may want to try some stuff out)
-
@MasterLeo29 I've changed the positioning a bit. Maybe you can test it again?
-
@oudstand I can and I did! Looks fixed to me. Congrats once more
Edit : I'm even adding this one to my daily driver
-
Works great. Thanks for sharing
-
@oudstand While using it today, I noticed an odd bug. When using the Ctrl+Alt+Middle mouse click to open in dialog, it was opening two dialogs of the same url.
After a bit of debugging, I think I have a fix for you to test/include.
on your 'setUrlClickObserver', when adding the mousedown event listener, on the first if statement I added a 'return' so that it would not trigger the "show dialog" a second time
something like this snippet :
// Check if the Ctrl key, Alt key, and middle mouse button were pressed if ( event.ctrlKey && event.altKey && (event.button === 0 || event.button === 1) ) { showDialog(event); return; }
Hope it helps
-
@MasterLeo29 said in Open in Dialog mod:
@oudstand While using it today, I noticed an odd bug. When using the Ctrl+Alt+Middle mouse click to open in dialog, it was opening two dialogs of the same url.
After a bit of debugging, I think I have a fix for you to test/include.
on your 'setUrlClickObserver', when adding the mousedown event listener, on the first if statement I added a 'return' so that it would not trigger the "show dialog" a second time
something like this snippet :
// Check if the Ctrl key, Alt key, and middle mouse button were pressed if ( event.ctrlKey && event.altKey && (event.button === 0 || event.button === 1) ) { showDialog(event); return; }
Hope it helps
Oh good idea. You probably click slower than I do
-
@oudstand
Nicely done! May I use this script to complement my Vivaldi Custom UI Mod? Of course with full credits. -
@srazzano4 Yes, feel free to use.