Universal Web Panel Mod
-
@oudstand I can't check this selector, but I've added it to the main branch along with the option to use the default icon. Thanks!
"Open Page in UWP" from my comment refers to the old version of UWP, which was shipped as an html page. Now I'm using the same script as yours.
-
-
@aminought glad you like the changes
-
I've added an option to display a combobox to select your favorite websites for quick access. You can fill the
FAVORITES
array on top like this:
const FAVORITES = [{caption: "YouTube", url: "https://www.youtube.com"}, {caption: "Google", url: "https://www.google.com"}];
IfFAVORITES
is filled there will be a combobox next to the input:/** * Inspired by @aminought * Forum link: https://forum.vivaldi.net/topic/94754/universal-web-panel-mod */ (function universal_web_panel() { "use strict"; const PANEL_ID = "WEBPANEL_7386ba5c-36af-495f-a850-8a7acbb242ac"; const DEFAULT_TITLE = "Universal Web Panel"; const DEFAULT_ICON = "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23cdc8c0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-search'><circle cx='11' cy='11' r='8'></circle><line x1='21' y1='21' x2='16.65' y2='16.65'></line></svg>"; const USE_DEFAULT_ICON = true; const FAVORITES = []; // [{caption: "YouTube", url: "https://www.youtube.com"}, {caption: "Google", url: "https://www.google.com"}] class UWP { #panelStackChangeObserver; #panelChangeObserver; constructor() { if (this.#panel) { this.#panelChangeObserver = this.#createPanelChangeObserver(); this.#register(); } else { this.#panelStackChangeObserver = this.#createPanelStackChangeObserver(); } } #register() { this.#isVisible ? this.#registerVisible() : this.#registerInvisible(); } #registerVisible() { if (!this.#input) { this.#createInputToolbarAndInput(); this.#addInputEvents(); FAVORITES && FAVORITES.length && this.#addComboboxEvents(); this.#addWebviewEvents(); } this.#focusInput(); if (this.#isBlank) { this.#title = DEFAULT_TITLE; this.#buttonImg = DEFAULT_ICON; } else if (USE_DEFAULT_ICON) { this.#buttonImg = DEFAULT_ICON; } } #registerInvisible() { this.#buttonImg = this.#isBlank || USE_DEFAULT_ICON ? DEFAULT_ICON : this.#webview.src; } // listeners #createPanelStackChangeObserver() { const panelStackChangeObserver = new MutationObserver((records) => { records.forEach(() => this.#handlePanelStackChange()); }); panelStackChangeObserver.observe(this.#panelStack, { childList: true }); return panelStackChangeObserver; } #createPanelChangeObserver() { const panelChangeObserver = new MutationObserver((records) => { records.forEach(() => this.#handlePanelChange()); }); panelChangeObserver.observe(this.#panel, { attributes: true, attributeFilter: ["class"], }); return panelChangeObserver; } #addInputEvents() { this.#input.addEventListener("input", () => this.#handleInput(this.#input.value.trim())); } #addComboboxEvents() { this.#combobox.addEventListener("input", () => this.#handleInput(this.#combobox.value.trim())); } #addWebviewEvents() { this.#webview.addEventListener("contentload", () => { this.#showWebview(); if (this.#isBlank) { this.#title = DEFAULT_TITLE; this.#buttonImg = DEFAULT_ICON; } else if (USE_DEFAULT_ICON) { this.#buttonImg = DEFAULT_ICON; } else { this.#buttonImg = this.#webview.src; } }); } // builders #createInputToolbarAndInput() { const input = this.#createInput(); const inputToolbar = this.#createInputToolbar(); inputToolbar.appendChild(input); if(FAVORITES && FAVORITES.length) { const combobox = this.#createCombobox(); inputToolbar.appendChild(combobox); } this.#panel.appendChild(inputToolbar); } #createInputToolbar() { const inputToolbar = document.createElement("div"); inputToolbar.className = "panel-universal-input toolbar-default full-width"; inputToolbar.width = "100%"; inputToolbar.style.height = "24px"; inputToolbar.style.width = "100%"; inputToolbar.style.height = "28px"; inputToolbar.style.padding = "0 2px"; inputToolbar.style.marginTop = "2px"; inputToolbar.style.display = "flex"; inputToolbar.style.gap = "10px"; return inputToolbar; } #createInput() { const input = document.createElement("input"); input.className = "universal-input"; input.type = "text"; input.placeholder = "Paste your link, html or javascript"; input.style.flex = 3; input.style.height = "28px"; input.style.padding = "10px"; return input; } #createCombobox() { const comboboxDropdown = document.createElement('select'); comboboxDropdown.id = "combobox-dropdown"; comboboxDropdown.className = "universal-combobox"; comboboxDropdown.style.flex = 1; // Create a default option const defaultOption = document.createElement('option'); defaultOption.textContent = "Favorites"; defaultOption.selected = true; defaultOption.disabled = true; comboboxDropdown.appendChild(defaultOption); FAVORITES.forEach(favorite => { const option = document.createElement('option'); option.value = favorite.url; option.textContent = favorite.caption; comboboxDropdown.appendChild(option); }); return comboboxDropdown; } #createHtmlview() { const htmlview = document.createElement("div"); htmlview.id = "htmlview"; htmlview.style.height = "100%"; htmlview.style.overflow = "auto"; return htmlview; } // getters get #panelStack() { return document.querySelector(".webpanel-stack"); } get #panel() { return document.querySelector(`webview[tab_id^="${PANEL_ID}"], webview[vivaldi_view_type^="${PANEL_ID}`) ?.parentElement?.parentElement; } get #button() { return document.querySelector(`button[name="${PANEL_ID}"]`); } get #content() { return this.#panel.querySelector(".webpanel-content"); } get #title() { return this.#panel.querySelector(".webpanel-title").querySelector("span"); } get #htmlview() { return this.#panel.querySelector("#htmlview"); } get #webview() { return this.#panel.querySelector("webview"); } get #input() { return this.#panel.querySelector(".universal-input"); } get #combobox() { return this.#panel.querySelector(".universal-combobox"); } get #buttonImg() { return this.#button.querySelector("img"); } get #isVisible() { return this.#panel.classList.contains("visible"); } get #isBlank() { return this.#webview.src === "about:blank"; } // setters set #title(title) { setTimeout(() => (this.#title.innerText = title), 100); } set #buttonImg(url) { this.#buttonImg.removeAttribute("srcset"); const src = url && (url.startsWith("http://") || url.startsWith("https://")) ? `chrome://favicon/size/16@1x/${url}` : url; this.#buttonImg.setAttribute("src", src); } // handlers #handleInput(value) { if ( value.startsWith("http://") || value.startsWith("https://") || value.startsWith("file://") || value.startsWith("vivaldi://") || value === "about:blank" ) { this.#openUrl(value); } else if (value.startsWith("(()") && value.endsWith(")()")) { this.#executeScript(value); } else { this.#showHtml(value); } this.#clearInput(); } #openUrl(url) { this.#showWebview(); this.#webview.src = url; } #executeScript(script) { this.#showWebview(); this.#webview.executeScript({ code: script }); } #showHtml(html) { this.#hideWebview(); if (!this.#htmlview) { const htmlview = this.#createHtmlview(); this.#content.appendChild(htmlview); } this.#htmlview.innerHTML = html; this.#title = DEFAULT_TITLE; this.#buttonImg = DEFAULT_ICON; } #handlePanelStackChange() { if (this.#panel) { this.#panelChangeObserver = this.#createPanelChangeObserver(); this.#register(); } } #handlePanelChange() { if (this.#isVisible) { this.#registerVisible(); } } // actions #showWebview() { if (this.#webview.style.display === "none") { this.#htmlview.remove(); this.#webview.style.display = ""; } } #hideWebview() { this.#webview.style.display = "none"; } #clearInput() { this.#input.value = ""; } #focusInput() { setTimeout(() => this.#input.focus(), 100); } } function getPanels() { return document.querySelector(".webpanel-stack"); } function initMod() { const panels = getPanels(); if (panels) { window.uwp = new UWP(); } else { setTimeout(initMod, 500); } } setTimeout(initMod, 500); })();
-
UPD 2/13/2024
- Added an option to display a combobox to select your favorite websites for quick access (adapted @oudstand's solution):
- Added an option to set toolbar height:
TOOLBAR_HEIGHT
. - Added an option to set radius of the input field border:
INPUT_BORDER_RADIUS
. - Updated the description.
- Added an option to display a combobox to select your favorite websites for quick access (adapted @oudstand's solution):
-
i can't seem to get the panel to work, it just stays blank. i've followed the steps outlined i believe.
-
@xef Hi! Need more info:
- OS.
- Vivaldi version.
PANEL_ID
.- Your
<button>
element. For example:<button name="WEBPANEL_f95dedab-84b7-4f35-b624-89740ba89ca2" ...></button>
. - Your
<webview>
element. For example:<webview tab_id="WEBPANEL_f95dedab-84b7-4f35-b624-89740ba89ca2_328037443" name="vivaldi-webpanel" src="about:blank" style=""></webview>
.
-
<button name="WEBPANEL_e1c8dcb3-0549-4c58-a0b0-1280223c394b" draggable="false" tabindex="-1" title="about:blank" type="button" class="ToolbarButton-Button uifocusstop" style="--activeButton: 1;"><img class="button-icon favicon" width="16" height="16" srcset="chrome://favicon/size/16@1x/about:blank 1x,chrome://favicon/size/16@2x/about:blank 2x"></button>
<webview name="vivaldi-webpanel" src="about:blank"></webview>
-
@xef OS, Vivaldi version? It should work with the latest snapshot. I cannot guarantee it will work with previous versions.
-
6.5.3206.63 (Stable channel) stable (64-bit) Linux
-
@xef The stable version of Vivaldi for Linux currently does not support the
tab_id
attribute. However, you can try using a snapshot, which works. -
bummer. i did not have success with the current version of vivaldi on windows 10, either. same issue of no panel.
-
@xef Then, you can install a snapshot or try using an older version of UWP: https://github.com/aminought/vivaldi-universal-web-panel/tree/ddd6411a1c85ae69bbaa54a62de94030dd32867d. Here, there is a slightly different configuration: you need to specify
BUTTON_NUMBER
(<button>
element) andWEBPANEL_NUMBER
(<div class="webpanel">
element). The countdown starts from zero. -
Uncaught TypeError: Cannot read properties of undefined (reading 'querySelector')
at get #panelButtonImg (universal_web_panel.js:143:26)
at set #panelButtonImg (universal_web_panel.js:161:12)
at #registerInvisible (universal_web_panel.js:39:12)
at #register (universal_web_panel.js:22:74)
at new UWP (universal_web_panel.js:18:21)
at initMod (universal_web_panel.js:250:20)we have a panel now, but it breaks when typing a link.
-
@xef Wrong
BUTTON_NUMBER
, I think. -
You could update the
#panel
function to something like this:get #panel() { // Get all webview elements in the document const webviews = document.querySelectorAll('webview'); // Iterate over all webview elements to find the one with the matching attribute value for (let webview of webviews) { // Iterate over all attributes of the webview element for (let attr of webview.attributes) { if (attr.value.includes(this.#PANEL_ID)) { // Return the parent of the parent of the webview element found with the matching attribute value return webview.parentElement?.parentElement; } } } // Return null if no webview with the specified PANEL_ID is found return null; }
Then you wouldn't have problems with different versions of Vivaldi.
-
@oudstand Thanks, but in older versions of Vivaldi, the
webview
element does not have any attribute that would indicate to which panel it belongs. -
I've updated the code locally:
In the constructor I set the default icon ifUSE_DEFAULT_ICON
istrue
and there is no panel.constructor() { if (this.#panel) { this.#panelChangeObserver = this.#createPanelChangeObserver(); this.#register(); } else { this.#panelStackChangeObserver = this.#createPanelStackChangeObserver(); if (USE_DEFAULT_ICON) { this.#buttonImg = DEFAULT_ICON; } } }
Also I've updated the style in
createFavoritesSelect
to move the icon a bit less to the right:favoritesSelect.style.width = "30px"; favoritesSelect.style.padding = "0";
-
Good job on the mod @aminought and @oudstand. Works for me. However, it didn’t work with an
about:blank
page, because somehow the title attribute can’t be fetched then and the script errors out. Just using any webpage for it is fine. The command chain to load the current page in the universal webpanel didn’t work for me, wrote another one:{"category":"CATEGORY_COMMAND_CHAIN","chain":[{"key":"96a396d8-d485-4f61-926b-bdab1eddaf94","label":"Focus Address Field","name":"COMMAND_FOCUS_ADDRESSFIELD"},{"defaultValue":1000,"key":"2eb81004-6703-46db-9933-6afcfde924e4","label":"Delay","name":"COMMAND_CHAINED_SLEEP","param":100},{"key":"ce41214f-6878-42ef-b120-3deb06cfcf2e","label":"Select All","name":"COMMAND_CLIPBOARD_SELECT_ALL"},{"key":"6dae1e1a-695b-41c5-9ca9-deb1fabe9cc9","label":"Copy","name":"COMMAND_CLIPBOARD_COPY"},{"key":"9a724d62-6b4f-4bec-affe-31a05966ccf9","label":"Web Panel 1","name":"COMMAND_SHOW_WEB_PANEL_1"},{"defaultValue":1000,"key":"2eb81004-6703-46db-9933-6afcfde924e4","label":"Delay","name":"COMMAND_CHAINED_SLEEP","param":500},{"key":"aa12b05a-94cf-4660-8272-37da19294192","label":"Paste","name":"COMMAND_CLIPBOARD_PASTE"}],"key":"3bffd13c-0ba2-4a1f-86d7-b0afe259a9ec","label":"Open Page in Universal Web Panel","name":"COMMAND_3bffd13c-0ba2-4a1f-86d7-b0afe259a9ec"}
pic for the modless
-
@aminought Thanks so much for this!
I think this may be my solution to loading url-specific userstyles / css into a webpanel. Hoping the url/omnibox in your mod can inject a userscript with css into the dom of the panel.
Installing it tonight. Thanks again!