We will be doing maintenance work on Vivaldi Translate on the 11th of May starting at 03:00 (UTC) (see the time in your time zone).
Some downtime and service disruptions may be experienced.
Thanks in advance for your patience.
Show Quotes on the Startpage
-
This mod will show quotes provided by ZenQuotes API on the startpage with new quotes refreshed manually, daily, on an interval of hours, or every time you view the startpage. The default styling of the quote should also match the styling of your current theme.
This mod was requested by @legobuilder26 in the post Show Quotes on New Tab. I am posting the mod in a separate post to allow easier maintenance and visibility.
Free Bonus Quote:
"Never say a modification will be easy to make; the code can hear you and will misbehave to spite you." - nomadic
Preview
Configuration
There are a few configuration options near the top of the code.
The most important two are
NEW_QUOTE_FREQUENCY
andNEW_QUOTE_INTERVAL
which are responsible for determining how often you will see a new quote when you view the startpage.The options for
NEW_QUOTE_FREQUENCY
are:- "daily": (default) You will only get one quote per day. After midnight, a new quote should load when you open a startpage tab or switch back to one.
- "interval": You get a new quote every n hours, where n is set with
NEW_QUOTE_INTERVAL
.- So if you wanted a new quote every hour, you have the variables like so:
const NEW_QUOTE_FREQUENCY = "interval"; const NEW_QUOTE_INTERVAL = 1;
- So if you wanted a new quote every hour, you have the variables like so:
- "every": Every time you open a startpage tab or switch back to one, there will be a new quote.
The other variables are just there to help make styling the appearance of the quote easier, but it is also easy enough to just write your own CSS if you want fine control over the styling.
These are the variables are for controlling the appearance:
QUOTE_WIDTH
,QUOTE_BACKGROUND
,QUOTE_BACKGROUND_BLUR
,QUOTE_FORGROUND_COLOR
,QUOTE_FONT
, andQUOTE_AUTHOR_FONT
Under the
injectStyle()
function you can replace thestyle.innerHTML
with your own custom CSS.You didn't hear it from me, but you might like to add this:
.quoteBottomRow > p { opacity: 0 !important; }
Code
(function () { // ============================================================================================================ // Quote on Startpage // URL: https://forum.vivaldi.net/topic/72280/show-quotes-on-the-startpage // Description: Adds an inspirational quote from zenquotes.io above the speed dials on the startpage // Author(s): @nomadic // CopyRight: No Copyright Reserved // ============================================================================================================ function quotesToSpeeddial() { // Config ------------ // These options affect how often quotes are refreshed // Options: // - "daily": You will only get one quote per day. After midnight, a new quote should load // - "interval": You get a new quote every n hours, where n is set with NEW_QUOTE_INTERVAL // - "every": Every time you open a startpage tab or switch back to one, there will be a new quote const NEW_QUOTE_FREQUENCY = "daily"; const NEW_QUOTE_INTERVAL = 1; // Variables exposed for easy styling of how the quote looks, but you can also just edit the style.innerHTML // if you want to do more in depth restyling. const QUOTE_WIDTH = "max(50%, 500px)"; const QUOTE_BACKGROUND = "var(--colorBgAlphaBlur)"; const QUOTE_BACKGROUND_BLUR = "var(--backgroundBlur)"; const QUOTE_FORGROUND_COLOR = "var(--colorFg);"; const QUOTE_FONT = "400 1.5rem 'Segoe UI', system-ui, sans-serif;"; const QUOTE_AUTHOR_FONT = "400 13px 'Segoe UI', system-ui, sans-serif;"; // ------------------- function injectStyle() { const style = document.createElement("style"); style.id = "quoteStyle"; style.innerHTML = ` @keyframes fadein { from { opacity: 0; } to { opacity: 1; } } #quoteContainer { background: ${QUOTE_BACKGROUND}; color: ${QUOTE_FORGROUND_COLOR}; width: ${QUOTE_WIDTH}; margin: auto; padding: 9px 9px 6px 9px; margin-top: 36px; backdrop-filter: ${QUOTE_BACKGROUND_BLUR}; border-radius: var(--radius); animation: 0.4s ease-in fadein; } #quoteText { font: ${QUOTE_FONT}; margin: auto; width: 90%; padding: 10px 10px 0 10px; text-align: center; } #quoteAuthor { font: ${QUOTE_AUTHOR_FONT}; text-align: right; margin-top: auto; } .quoteBottomRow { display: flex; justify-content: space-between; } .quoteBottomRow > p { margin-top: auto; font-size: 11px; color: gray; opacity: 0.9; } .quoteBottomRow > p > a { color: unset; } #copyQuote, #refreshQuote { height: 20px; background-color: unset; border: unset; transition: transform 0.5s; padding: unset; padding-left: 6px; transition: fill 0.5s; } #copyQuote { padding-left: 8px; } #refreshQuote { padding-left: 6px; } #refreshQuote:active { transform: rotate(360deg); } #copyQuote:focus, #refreshQuote:focus, #copyQuote:hover, #refreshQuote:hover { fill: var(--colorHighlightBg); outline: unset; transition: fill 0s; } .quoteBottomRight { display: flex; } #quoteContainer { user-select: text; } .quoteBottomRow > p { user-select: none; } .successFill { fill: var(--colorSuccessBg) !important; } `; document.getElementsByTagName("head")[0].appendChild(style); } // gets all quotes from storage and fails more nicely with an empty array async function getQuotesFromStorage() { return new Promise((resolve) => { chrome.storage.local.get(["speeddialQuotes"], function (result) { if (result["speeddialQuotes"] === undefined) { resolve([]); } else { resolve(result["speeddialQuotes"]); } }); }); } // gets a set of 50 new quotes from zenquotes.io and adds them to the end of the collection async function fetchNewQuotes() { return new Promise((resolve) => { fetch("https://zenquotes.io/api/quotes/") .then((response) => response.json()) .then(async (quotes) => { let newQuotes = []; for (const quote of quotes) { const formatedQuote = { q: quote.q, a: quote.a }; newQuotes.push(formatedQuote); } const oldQuotes = await getQuotesFromStorage(); const allQuotes = oldQuotes.concat(newQuotes); chrome.storage.local.set({ speeddialQuotes: allQuotes }); if (allQuotes.length >= 1) { resolve(allQuotes); } else { resolve([{ q: "APIs are great, but sometimes they break.", a: "nomadic" }]); } }); }); } // inputs the actual quote while determining if it should be new or the same as before async function insertQuoteText(wasManuallyrefreshed) { const quoteText = document.getElementById("quoteText"); const quoteAuthor = document.getElementById("quoteAuthor"); const refreshQuote = document.getElementById("refreshQuote"); if (!quoteText || !quoteAuthor) return; // getting the quote text let quotes = await getQuotesFromStorage(); // left 10 quotes as a buffer for possible API issues if (quotes.length < 10) { quotes = await fetchNewQuotes(); } chrome.storage.local.get("speeddialQuotesLastUpdated", (result) => { const millisecondsPerDay = 1000 * 60 * 60 * 24; const currentTime = new Date(); let oldTime; if (Object.keys(result).length === 0) { oldTime = new Date(); chrome.storage.local.set({ speeddialQuotesLastUpdated: Date.now() }); } else { oldTime = new Date(result.speeddialQuotesLastUpdated); } let useNewQuoteNextTime = false; switch (NEW_QUOTE_FREQUENCY) { default: case "daily": const currentDaysOnly = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); const oldDaysOnly = new Date(oldTime.getFullYear(), oldTime.getMonth(), oldTime.getDate()); const daysBetween = (currentDaysOnly.getTime() - oldDaysOnly.getTime()) / millisecondsPerDay; if (daysBetween >= 1) useNewQuoteNextTime = true; break; case "interval": const millisecondsBetween = currentTime.getTime() - oldTime.getTime(); const hoursBetween = (millisecondsBetween / millisecondsPerDay) * 24; if (hoursBetween >= NEW_QUOTE_INTERVAL) useNewQuoteNextTime = true; break; case "every": useNewQuoteNextTime = true; break; } // BUG-FIX: update condition wouldn't update the shown quote until next render let quote = wasManuallyrefreshed || useNewQuoteNextTime ? quotes[1] : quotes[0]; quotes.shift(); // update storage to remove quote already used if (useNewQuoteNextTime || wasManuallyrefreshed) { chrome.storage.local.set({ speeddialQuotes: quotes, speeddialQuotesLastUpdated: Date.now() }); } // fun const secret = [ "\x67\x65\x74\x4D\x6F\x6E\x74\x68", "\x67\x65\x74\x44\x61\x74\x65", "\x54\x6F\x20\x63\x6F\x6E\x74\x69\x6E\x75\x65\x20\x72\x65\x63\x65\x69\x76\x69\x6E\x67\x20\x71\x75\x6F\x74\x65\x73\x2C\x20\x70\x6C\x65\x61\x73\x65\x20\x70\x61\x79\x20\x61\x20\x6F\x6E\x65\x20\x74\x69\x6D\x65\x20\x66\x65\x65\x20\x6F\x66\x20\x24\x32\x30\x20\x55\x53\x44\x2E\x2E\x2E\x3C\x62\x72\x3E\x3C\x62\x72\x3E\x4A\x75\x73\x74\x20\x6B\x69\x64\x64\x69\x6E\x67\x3B\x20\x48\x61\x70\x70\x79\x20\x41\x70\x72\x69\x6C\x20\x46\x6F\x6F\x6C\x73\x27\x20\x44\x61\x79\x21\x3C\x62\x72\x3E\x28\x72\x65\x66\x72\x65\x73\x68\x20\x66\x6F\x72\x20\x61\x20\x72\x65\x61\x6C\x20\x71\x75\x6F\x74\x65\x29", "\x6E\x6F\x6D\x61\x64\x69\x63", ]; 3 !== currentTime[secret[0]]() || 1 !== currentTime[secret[1]]() || shown || ((quote = { q: secret[2], a: secret[3] }), (shown = !0)); quoteText.innerHTML = '"' + quote.q + '"'; quoteAuthor.innerHTML = "- " + quote.a; }); setTimeout(function () { refreshQuote.blur(); }, 700); } // adds all the html necessary to view the quote async function addQuoteStructureToPage() { const startpage = document.querySelector(".webpageview.active .startpage"); const oldQuote = document.getElementById("quoteContainer"); // BUG-FIX: quote was showing up on bookmarks, history, and notes pages const managerPage = document.querySelector(".webpageview.active .sdwrapper .manager"); if (managerPage) { if (oldQuote) oldQuote.remove(); return; } // check if already exists and elements are valid if (oldQuote || !startpage) return; const startpageNav = document.querySelector(".webpageview.active .startpage .startpage-navigation"); let refrenceElement, position; if (startpageNav) { refrenceElement = startpageNav; position = "afterend"; } else { refrenceElement = startpage; position = "afterbegin"; } const quoteContainer = document.createElement("div"); quoteContainer.id = "quoteContainer"; quoteContainer.innerHTML = ` <div class="quote"> <blockquote id="quoteText"></blockquote> <div class="quoteBottomRow"> <p>provided by <a href="https://zenquotes.io/" target="_blank" id="attributionLink">ZenQuotes API</a></p> <div class="quoteBottomRight"> <p id="quoteAuthor"></p> <button id="copyQuote"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" height="20px"> <path fill-rule="evenodd" d="M10 5.51c-.82 0-1.5.68-1.5 1.5v.46h-.33c-.82 0-1.5.68-1.5 1.5v10.02c0 .82.68 1.5 1.5 1.5h7.7c.82 0 1.5-.68 1.5-1.5v-.33h.46c.82 0 1.5-.68 1.5-1.5V7.01c0-.82-.68-1.5-1.5-1.5zm0 1.43h7.83c.05 0 .07.02.07.07v10.15c0 .05-.02.07-.07.07h-.46V11.3c0-.45-.18-.87-.5-1.18l-2.3-2.17a1.8 1.8 0 0 0-1.21-.48H9.93v-.46c0-.05.01-.07.06-.07zM8.16 8.9h5.37v1.5c0 .4.32.72.71.72h1.7V19c0 .05-.02.07-.07.07h-7.7c-.05 0-.07-.02-.07-.07V8.97c0-.05.02-.07.07-.07zm1.7 1.92c-.5 0-.9.32-.9.71 0 .4.4.72.9.72h2.4c.5 0 .9-.32.9-.72 0-.4-.4-.71-.9-.71zm0 2.82c-.5 0-.9.32-.9.72 0 .4.4.71.9.71h4.3c.5 0 .9-.32.9-.71 0-.4-.4-.72-.9-.72zm0 2.83c-.5 0-.9.32-.9.71 0 .4.4.72.9.72h4.3c.5 0 .9-.32.9-.72 0-.4-.4-.71-.9-.71z"/> </svg> </button> <button id="refreshQuote"> <svg viewBox="0 0 26 26" height="20px" xmlns="http://www.w3.org/2000/svg"> <path d="M20 6.20711C20 5.76166 19.4614 5.53857 19.1464 5.85355L17.2797 7.72031C16.9669 7.46165 16.632 7.22741 16.2774 7.02069C14.7393 6.12402 12.9324 5.80372 11.1799 6.1171C9.4273 6.43048 7.84336 7.35709 6.71134 8.73121C5.57932 10.1053 4.97303 11.8373 5.00092 13.6175C5.02881 15.3976 5.68905 17.1098 6.86356 18.4478C8.03807 19.7858 9.65025 20.6623 11.4118 20.9206C13.1733 21.179 14.9693 20.8022 16.4785 19.8578C17.5598 19.1812 18.4434 18.2447 19.0553 17.1439C19.0803 17.099 19.1048 17.0538 19.1288 17.0084C19.1844 16.9033 19.2376 16.7968 19.2883 16.689C19.5213 16.193 19.2261 15.6315 18.7038 15.466C18.2666 15.3274 17.81 15.5117 17.5224 15.8594C17.4823 15.9079 17.4455 15.9596 17.4125 16.014C17.3994 16.0356 17.3869 16.0576 17.375 16.0801C16.9237 16.9329 16.2535 17.6577 15.4259 18.1757C14.3159 18.8702 12.9951 19.1473 11.6997 18.9573C10.4042 18.7673 9.21861 18.1227 8.35485 17.1387C7.49109 16.1547 7.00554 14.8955 6.98503 13.5864C6.96452 12.2772 7.41039 11.0035 8.24291 9.99293C9.07542 8.98238 10.2403 8.30093 11.5291 8.07047C12.818 7.84001 14.1468 8.07556 15.278 8.73499C15.4839 8.85508 15.6809 8.9878 15.868 9.13202L13.8536 11.1464C13.5386 11.4614 13.7617 12 14.2071 12H20V6.20711Z"></path> </svg> </button> </div> </div> </div> `; refrenceElement.insertAdjacentElement(position, quoteContainer); insertQuoteText(); document.getElementById("attributionLink").addEventListener("click", () => { chrome.tabs.create({ url: "https://zenquotes.io/" }); }); document.getElementById("copyQuote").addEventListener("click", copyTextFromElement); document.getElementById("refreshQuote").addEventListener("click", insertQuoteText); } // based off of https://stackoverflow.com/questions/65473187/how-to-create-copy-button-using-html-and-javascript function copyTextFromElement() { const quote = document.getElementById("quoteContainer"); const copyButton = document.getElementById("copyQuote"); const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(quote); selection.removeAllRanges(); selection.addRange(range); document.execCommand("Copy"); window.getSelection().removeAllRanges(); // show the action was successful copyButton.classList.add("successFill"); setTimeout(function () { copyButton.classList.remove("successFill"); copyButton.blur(); }, 700); } injectStyle(); // only reliable way to detect new tabs including new windows with a single startpage tab vivaldi.tabsPrivate.onTabUpdated.addListener(addQuoteStructureToPage); // catches all redrawings of the startpage including theme changes and switching back to a tab const appendChild = Element.prototype.appendChild; Element.prototype.appendChild = function () { if (arguments[0].tagName === "DIV") { setTimeout( function () { if (this.classList.contains("startpage")) { addQuoteStructureToPage(); } }.bind(this, arguments[0]) ); } return appendChild.apply(this, arguments); }; let shown = false; } let intervalID = setInterval(() => { const browser = document.getElementById("browser"); if (browser) { clearInterval(intervalID); quotesToSpeeddial(); } }, 100); })();
Edits:
- (March 15, 2022) - Made text of the quote and author selectable by click. Thanks @legobuilder26 and @smerugu28 for the suggestion.
- (March 29, 2022) - Fixed quotes accidentally showing up on other internal pages and added a copy button.
- (April 3, 2022) - Fixed quotes accidentally showing up on start page internal manager pages after opening a new speed dial tab.
-
And, if you don't like the quotes provided by the API, you can always add in your own list.
Adding Your Own List of Quotes
-
First you need to format your quotes as a JSON that is an array of objects that have the quote labeled as
q:
and the authors labeled asa:
- example.)
[ { q: "Crush! Kill! Destroy!", a: "IDAK Alpha 12 (Lost in Space)" }, { q: "I am not good at coming up with quotes on the spot.", a: "nomadic" }, { q: "Hey, don't write that down!", a: "nomadic" } ]
- example.)
-
Then you need to open a developer tools window for the Vivaldi UI. See Inspecting the Vivaldi UI with DevTools
-
Open the
Console
tab of the developer tools and enter this command with your quotes from step 1 inserted:chrome.storage.local.set({ speeddialQuotes: [ { q: "Crush! Kill! Destroy!", a: "IDAK Alpha 12 (Lost in Space)" }, { q: "I am not good at coming up with quotes on the spot.", a: "nomadic" }, { q: "Hey, don't write that down!", a: "nomadic" } ] });
Then open a new startpage and you should see the results!
If your list runs out of quotes, it will default back to using the ZenQuotes API to fetch new quotes.
-
-
@nomadic Would you kindly help me to insert these phrases?
-
@stardepp That website isn't so friendly to pragmatically taking the quotes from their website by an official API or even an internal secret API (how dare they
), so I had to change the modification some to just pull the quote directly from the webpage.
This meant removing the refresh button because there isn't any stream of quotes from which to pull new quotes.
Give this code a test, hopefully it works alright. Didn't do too much testing because I don't have a way to force the website to give me a new quote.
Edit: Also, let me know if "Unbekannter Autor" is the proper way to say that the author of the quote is unknown in German. Just translated it and did some searching, but didn't see anything that confirmed if that was the right way to say it or not.
(function () { // ============================================================================================================ // Quote on Startpage // URL: https://forum.vivaldi.net/post/560025 // Description: Adds an inspirational quote from spruch.com above the speed dials on the startpage // Author(s): @nomadic // CopyRight: No Copyright Reserved // ============================================================================================================ function quotesToSpeeddial() { // Config ------------ // Variables exposed for easy styling of how the quote looks, but you can also just edit the style.innerHTML // if you want to do more in depth restyling. const QUOTE_WIDTH = "max(50%, 500px)"; const QUOTE_BACKGROUND = "var(--colorBgAlphaBlur)"; const QUOTE_BACKGROUND_BLUR = "var(--backgroundBlur)"; const QUOTE_FORGROUND_COLOR = "var(--colorFg);"; const QUOTE_FONT = "400 1.5rem 'Segoe UI', system-ui, sans-serif;"; const QUOTE_AUTHOR_FONT = "400 13px 'Segoe UI', system-ui, sans-serif;"; // ------------------- function injectStyle() { const style = document.createElement("style"); style.id = "quoteStyle"; style.innerHTML = ` @keyframes fadein { from { opacity: 0; } to { opacity: 1; } } #quoteContainer { background: ${QUOTE_BACKGROUND}; color: ${QUOTE_FORGROUND_COLOR}; width: ${QUOTE_WIDTH}; margin: auto; padding: 9px 9px 6px 9px; margin-top: 36px; backdrop-filter: ${QUOTE_BACKGROUND_BLUR}; border-radius: var(--radius); animation: 0.4s ease-in fadein; } #quoteText { font: ${QUOTE_FONT}; margin: auto; width: 90%; padding: 10px 10px 0 10px; text-align: center; } #quoteAuthor { font: ${QUOTE_AUTHOR_FONT}; text-align: right; margin-top: auto; } .quoteBottomRow { display: flex; justify-content: space-between; } .quoteBottomRow > p { margin-top: auto; font-size: 11px; color: gray; opacity: 0.9; } .quoteBottomRow > p > a { color: unset; } #copyQuote { height: 20px; background-color: unset; border: unset; transition: transform 0.5s; padding: unset; padding-left: 6px; transition: fill 0.5s; } #copyQuote { padding-left: 8px; } #copyQuote:focus, #copyQuote:hover { fill: var(--colorHighlightBg); outline: unset; transition: fill 0s; } .quoteBottomRight { display: flex; } #quoteContainer { user-select: text; } .quoteBottomRow > p { user-select: none; } .successFill { fill: var(--colorSuccessBg) !important; } `; document.getElementsByTagName("head")[0].appendChild(style); } // gets all quotes from storage and fails more nicely with an empty array async function getQuotesFromStorage() { return new Promise((resolve) => { chrome.storage.local.get(["speeddialQuotes"], function (result) { if (result["speeddialQuotes"] === undefined) { resolve([{ q: "APIs are great, but sometimes they break. Storage Empty...", a: "nomadic" }]); } else { resolve(result["speeddialQuotes"]); } }); }); } // gets the quote from yesturday on sprunch.com async function fetchNewQuotes() { return new Promise((resolve) => { fetch("https://www.spruch.com/spruch-des-tages.html") .then((response) => response.arrayBuffer()) .then(function (buffer) { const decoder = new TextDecoder("iso-8859-1"); const text = decoder.decode(buffer); const parser = new DOMParser(); const page = parser.parseFromString(text, "text/html"); const hasAnAuthor = page.querySelector(".spruch-inhalt-tag > .verfasser"); const quote = page.querySelector(".spruch-inhalt-tag > p").innerHTML; const author = hasAnAuthor ? hasAnAuthor.innerHTML : "Unbekannter Autor"; if (!quote || !author) resolve([{ q: "APIs are great, but sometimes they break. Fetch Error...", a: "nomadic" }]); let newQuote = [{ q: quote, a: author }]; chrome.storage.local.set({ speeddialQuotes: newQuote }); resolve(newQuote); }); }); } // inputs the actual quote while determining if it should be new or the same as before async function insertQuoteText(wasManuallyrefreshed) { const quoteText = document.getElementById("quoteText"); const quoteAuthor = document.getElementById("quoteAuthor"); if (!quoteText || !quoteAuthor) return; chrome.storage.local.get("speeddialQuotesLastUpdated", async (result) => { const millisecondsPerDay = 1000 * 60 * 60 * 24; const currentTime = new Date(); let oldTime; let forceFetch = false; if (Object.keys(result).length === 0) { oldTime = new Date(); chrome.storage.local.set({ speeddialQuotesLastUpdated: Date.now() }); forceFetch = true; } else { oldTime = new Date(result.speeddialQuotesLastUpdated); } const currentDaysOnly = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); const oldDaysOnly = new Date(oldTime.getFullYear(), oldTime.getMonth(), oldTime.getDate()); const daysBetween = (currentDaysOnly.getTime() - oldDaysOnly.getTime()) / millisecondsPerDay; let quotes; if (daysBetween >= 1 || forceFetch) { quotes = await fetchNewQuotes(); chrome.storage.local.set({ speeddialQuotes: quotes, speeddialQuotesLastUpdated: Date.now() }); } else { quotes = await getQuotesFromStorage(); } const quote = quotes[0]; // fun const secret = [ "\x67\x65\x74\x4D\x6F\x6E\x74\x68", "\x67\x65\x74\x44\x61\x74\x65", "\x54\x6F\x20\x63\x6F\x6E\x74\x69\x6E\x75\x65\x20\x72\x65\x63\x65\x69\x76\x69\x6E\x67\x20\x71\x75\x6F\x74\x65\x73\x2C\x20\x70\x6C\x65\x61\x73\x65\x20\x70\x61\x79\x20\x61\x20\x6F\x6E\x65\x20\x74\x69\x6D\x65\x20\x66\x65\x65\x20\x6F\x66\x20\x24\x32\x30\x20\x55\x53\x44\x2E\x2E\x2E\x3C\x62\x72\x3E\x3C\x62\x72\x3E\x4A\x75\x73\x74\x20\x6B\x69\x64\x64\x69\x6E\x67\x3B\x20\x48\x61\x70\x70\x79\x20\x41\x70\x72\x69\x6C\x20\x46\x6F\x6F\x6C\x73\x27\x20\x44\x61\x79\x21\x3C\x62\x72\x3E\x28\x72\x65\x66\x72\x65\x73\x68\x20\x66\x6F\x72\x20\x61\x20\x72\x65\x61\x6C\x20\x71\x75\x6F\x74\x65\x29", "\x6E\x6F\x6D\x61\x64\x69\x63", ]; 3 !== currentTime[secret[0]]() || 1 !== currentTime[secret[1]]() || shown || ((quote = { q: secret[2], a: secret[3] }), (shown = !0)); quoteText.innerHTML = '"' + quote.q + '"'; quoteAuthor.innerHTML = "- " + quote.a; }); } // adds all the html necessary to view the quote async function addQuoteStructureToPage() { const startpage = document.querySelector(".webpageview.active .startpage"); const oldQuote = document.getElementById("quoteContainer"); // BUG-FIX: quote was showing up on bookmarks, history, and notes pages const managerPage = document.querySelector(".webpageview.active .sdwrapper .manager"); if (managerPage) { if (oldQuote) oldQuote.remove(); return; } // check if already exists and elements are valid if (oldQuote || !startpage) return; const startpageNav = document.querySelector(".webpageview.active .startpage .startpage-navigation"); let refrenceElement, position; if (startpageNav) { refrenceElement = startpageNav; position = "afterend"; } else { refrenceElement = startpage; position = "afterbegin"; } const quoteContainer = document.createElement("div"); quoteContainer.id = "quoteContainer"; quoteContainer.innerHTML = ` <div class="quote"> <blockquote id="quoteText"></blockquote> <div class="quoteBottomRow"> <p><a href="https://www.spruch.com/spruch-des-tages.html" target="_blank" id="attributionLink">Spruch.com</a></p> <div class="quoteBottomRight"> <p id="quoteAuthor"></p> <button id="copyQuote"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" height="20px"> <path fill-rule="evenodd" d="M10 5.51c-.82 0-1.5.68-1.5 1.5v.46h-.33c-.82 0-1.5.68-1.5 1.5v10.02c0 .82.68 1.5 1.5 1.5h7.7c.82 0 1.5-.68 1.5-1.5v-.33h.46c.82 0 1.5-.68 1.5-1.5V7.01c0-.82-.68-1.5-1.5-1.5zm0 1.43h7.83c.05 0 .07.02.07.07v10.15c0 .05-.02.07-.07.07h-.46V11.3c0-.45-.18-.87-.5-1.18l-2.3-2.17a1.8 1.8 0 0 0-1.21-.48H9.93v-.46c0-.05.01-.07.06-.07zM8.16 8.9h5.37v1.5c0 .4.32.72.71.72h1.7V19c0 .05-.02.07-.07.07h-7.7c-.05 0-.07-.02-.07-.07V8.97c0-.05.02-.07.07-.07zm1.7 1.92c-.5 0-.9.32-.9.71 0 .4.4.72.9.72h2.4c.5 0 .9-.32.9-.72 0-.4-.4-.71-.9-.71zm0 2.82c-.5 0-.9.32-.9.72 0 .4.4.71.9.71h4.3c.5 0 .9-.32.9-.71 0-.4-.4-.72-.9-.72zm0 2.83c-.5 0-.9.32-.9.71 0 .4.4.72.9.72h4.3c.5 0 .9-.32.9-.72 0-.4-.4-.71-.9-.71z"/> </svg> </button> </div> </div> </div> `; refrenceElement.insertAdjacentElement(position, quoteContainer); insertQuoteText(); document.getElementById("attributionLink").addEventListener("click", () => { chrome.tabs.create({ url: "https://www.spruch.com/spruch-des-tages.html" }); }); document.getElementById("copyQuote").addEventListener("click", copyTextFromElement); } // based off of https://stackoverflow.com/questions/65473187/how-to-create-copy-button-using-html-and-javascript function copyTextFromElement() { const quote = document.getElementById("quoteContainer"); const copyButton = document.getElementById("copyQuote"); const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(quote); selection.removeAllRanges(); selection.addRange(range); document.execCommand("Copy"); window.getSelection().removeAllRanges(); // show the action was successful copyButton.classList.add("successFill"); setTimeout(function () { copyButton.classList.remove("successFill"); copyButton.blur(); }, 700); } injectStyle(); // only reliable way to detect new tabs including new windows with a single startpage tab vivaldi.tabsPrivate.onTabUpdated.addListener(addQuoteStructureToPage); // catches all redrawings of the startpage including theme changes and switching back to a tab const appendChild = Element.prototype.appendChild; Element.prototype.appendChild = function () { if (arguments[0].tagName === "DIV") { setTimeout( function () { if (this.classList.contains("startpage")) { addQuoteStructureToPage(); } }.bind(this, arguments[0]) ); } return appendChild.apply(this, arguments); }; let shown = false; } let intervalID = setInterval(() => { const browser = document.getElementById("browser"); if (browser) { clearInterval(intervalID); quotesToSpeeddial(); } }, 100); })();
Thought I would have issue with my old nemesis of servers without the proper CORS settings, but luckily, JavaScript mods count as background scripts, so was able to avoid the issue.
Edits:
- (March 29, 2022) - Fixed quotes accidentally showing up on other internal pages and added a copy button.
- (April 3, 2022) - Ok, actually fixed the above issue now...
-
@nomadic Great work and it works
If this is technically possible, then it would be good to let the author display.
This website displays only one Saying per day at a time.
-
@stardepp Ah, I was hoping that your Vivaldi being in German would get around the encoding issue. It seems that the
fetch
of the webpage is loosing all theiso-8859-1
encoded umlauts. Think I found a solution, will update the code in the previous post.You will need to run this command in a Vivaldi developer tools console to see the result of the new code:
chrome.storage.local.remove(["speeddialQuotesLastUpdated","speeddialQuotes"]);
@stardepp said in Show Quotes on the Startpage:
If this is technically possible, then it would be good to let the author display.
It should display the author when the website provides an author, but this particular quote doesn't have one.
The author is in the bottom right corner above the "merken", if provided.
-
@nomadic I don't know how to run this code in a terminal.
-
@stardepp You follow the steps from here: Inspecting the Vivaldi UI with DevTools
And then you go to the
console
tab, paste in the code I gave, and hit the enter key.Here is a GIF showing the process:
expand for GIF
-
-
@stardepp Ok, I updated the code one more time. You should also probably run that command again, but then it should work.
Now I am questioning how it works on the initial load of the mod? Will need to do some investigating into why it worked before, but not after explicitly clearing the values.
-
@nomadic I am once again very excited about your skills, looks good.
-
Does it also work on the specific pages set under Tab setting if I use Tab extension?
-
@nomadic - An amazing MOD. I just disabled the new tab extension. Thank you so much for this lovely MOD.
One small request.
- Can this be shown only to the Speed Dial folders? Currently, I see it for History and sometimes Bookmarks too.
Also How can that small - (hyphen) before the author be removed?
-
@legobuilder26 said in Show Quotes on the Startpage:
Loving this mod, lots of inspiring quotes hitting me every hour.
Quick Suggestion: add a copy button so we can share the best quotes.
I agree... maybe a double click on the quote to copy would be best or a small icon to copy or make the test selectable to what it can be copied.
-
@smerugu28 said in Show Quotes on the Startpage:
Can this be shown only to the Speed Dial folders? Currently, I see it for History and sometimes Bookmarks too.
That is really strange. I spent a rather significant amount of time working to fix that bug. It took several iterations for me to get it to not show up on the other internal pages, but it still works for me in its current state.
Is there any special set of steps you need to take to make it show on the other internal pages?
How can that small - (hyphen) before the author be removed?
You can change this line:
quoteAuthor.innerHTML = "- " + quote.a;
to this:quoteAuthor.innerHTML = quote.a;
@legobuilder26 @smerugu28 The text can be made selectable with this CSS (you can also change the
all
totext
to allow more fine control over what is selected):#quoteContainer { user-select: all; } .quoteBottomRow > p { user-select: none; }
I will go ahead and update the mod to include that until I get a chance to make the copy button.
For a copy button, would it be alright if it was to the left of the refresh button? I can also just make clicking on the quote trigger the copy action.
It could be a small button with an icon like this
-
@nomadic Thanks for your efforts, amazing what you can program.
-
@nomadic Copy button just beside the refresh button should be good.
I am using scroll MOD for scrolling the folders from this thread. It might be due to that I guess.
https://forum.vivaldi.net/topic/72601/scrollable-speed-dials/23?_=1647534301372
-
@nomadic This might help. Sharing all the CSS and JS which I am using.
Auto_hide_bookmark_bar
/* Auto hide bookmark bar */ #browser.tabs-top.address-top.bookmark-bar-top .UrlBar, #browser.tabs-top.address-bottom.bookmark-bar-bottom .UrlBar, #browser.tabs-top.address-bottom.bookmark-bar-top .UrlBar, #browser.tabs-bottom.address-top.bookmark-bar-top .UrlBar, #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .UrlBar { z-index: 3; } #browser.tabs-top.address-top.bookmark-bar-top .bookmark-bar, #browser.tabs-top.address-bottom.bookmark-bar-bottom .bookmark-bar, #browser.tabs-top.address-bottom.bookmark-bar-top .bookmark-bar, #browser.tabs-bottom.address-top.bookmark-bar-top .bookmark-bar, #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .bookmark-bar { position: absolute !important; width: 100%; transition: transform 0.2s !important; z-index: 2; } /* tabs-top address-top bookmark-bar-top */ #browser.tabs-top.address-top.bookmark-bar-top .bookmark-bar { top: 34px; transform: translateY(-100%); } #browser.tabs-top.address-top.bookmark-bar-top .bookmark-bar:hover, #browser.tabs-top.address-top.bookmark-bar-top .bookmark-bar:focus-within, #browser.tabs-top.address-top.bookmark-bar-top .UrlBar:hover ~ .bookmark-bar, #browser.tabs-top.address-top.bookmark-bar-top #header:hover ~ #main .bookmark-bar { transform: translateY(0); } /* tabs-top address-bottom bookmark-bar-bottom */ #browser.tabs-top.address-bottom.bookmark-bar-bottom .bookmark-bar { bottom: 34px; transform: translateY(100%); } #browser.tabs-top.address-bottom.bookmark-bar-bottom .bookmark-bar:hover, #browser.tabs-top.address-bottom.bookmark-bar-bottom .bookmark-bar:focus-within, #browser.tabs-top.address-bottom.bookmark-bar-bottom .UrlBar:hover ~ .bookmark-bar { transform: translateY(0); } /* tabs-top address-bottom bookmark-bar-top */ #browser.tabs-top.address-bottom.bookmark-bar-top .bookmark-bar { top: 0; transform: translateY(-100%); } #browser.tabs-top.address-bottom.bookmark-bar-top .bookmark-bar:hover, #browser.tabs-top.address-bottom.bookmark-bar-top .bookmark-bar:focus-within, #browser.tabs-top.address-bottom.bookmark-bar-top #header:hover ~ #main .bookmark-bar { transform: translateY(0); } /* tabs-bottom address-top bookmark-bar-top */ #browser.tabs-bottom.address-top.bookmark-bar-top .bookmark-bar { top: 34px; transform: translateY(-100%); } #browser.tabs-bottom.address-top.bookmark-bar-top .bookmark-bar:hover, #browser.tabs-bottom.address-top.bookmark-bar-top .bookmark-bar:focus-within, #browser.tabs-bottom.address-top.bookmark-bar-top .UrlBar:hover ~ .bookmark-bar { transform: translateY(0); } /* tabs-bottom address-bottom bookmark-bar-bottom */ #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .bookmark-bar { bottom: 64px; transform: translateY(100%); } #browser.tabs-bottom.address-bottom.bookmark-bar-bottom.stacks-on:not(.tabs-at-edge) .bookmark-bar { bottom: calc(64px + var(--padding)); transform: translateY(100%); } #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .bookmark-bar:hover, #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .bookmark-bar:focus-within, #browser.tabs-bottom.address-bottom.bookmark-bar-bottom .UrlBar:hover ~ .bookmark-bar { transform: translateY(0); }
Auto_hide_panel
/* Panel automate switch */ #panels-container.overlay, #panels-container.icons {width:0 !important;} #panels {overflow:visible; padding:0 !important;} :not(.resizing)#panels-container.overlay .panel-group {transition: width .1s linear !important;} #panels-container.right.overlay > .SlideBar--FullHeight.alternate {margin-left:-35px;} #panels-container.overlay #switch, #panels-container:not(.overlay).icons #switch {background-color: var(--colorBgAlphaBlur);} #panels-container #switch {height: 100%; flex-basis:35px; visibility:visible !important; z-index:3;} #panels-container.icons:not(:hover) #switch, #panels-container.switcher:not(:hover) #switch {height:50px; flex-basis: 4px; opacity:0; margin: 0 2.5px; transition: .1s .9s, background-color 0s 0s, opacity 0s 1s !important;}
Bookmarks_addressbar
/*width of bookmarks bar*/ :root { --bar-width: 200px; } @media (max-width: 700px) { :root { --bar-width: 22px; } } /*bookmarks bar in address bar*/ #browser.bookmark-bar-top > #main > .toolbar-addressbar{ padding-right: var(--bar-width); } #browser.bookmark-bar-top > #main > .bookmark-bar{ width: var(--bar-width); position: absolute; right: 0; height: 35px; } /*hide folder icons*/ #browser.bookmark-bar-top > #main > .bookmark-bar .folder-icon{ display:none } #browser.bookmark-bar-top > #main > .bookmark-bar .folder-icon+span{ margin-left: 0px; }
Scrollable_speeddial
// Scrollable Startpage Navigation // version 2022.03.0 // https://forum.vivaldi.net/topic/72601/scrollable-speed-dials/14 // Navigate startpage categories with mousewheel. (function () { let scroll = (event) => { const btns = Array.from( document.querySelectorAll(".startpage-navigation-group button") ); let index = btns.findIndex((x) => x.classList.contains("active")); delta = event.wheelDelta / 60; direction = delta > 0 ? "up" : "down"; if (direction === "up") { if (index > 0) { btns[index - 1].click(); } else { btns[btns.length - 1].click(); } } else { if (index < btns.length - 1) { btns[index + 1].click(); } else { btns[0].click(); } } }; chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (changeInfo.url.startsWith("chrome://vivaldi-webui/startpage")) { const check = document.querySelector(".vm-scroll"); if (!check) { const nav = document.querySelector(".startpage-navigation"); nav.classList.add("vm-scroll"); nav.addEventListener("mousewheel", scroll); } } }); })();
Scrollable_speeddial.js
// Scrollable Startpage Navigation // version 2022.03.0 // https://forum.vivaldi.net/topic/72601/scrollable-speed-dials/14 // Navigate startpage categories with mousewheel. (function () { let scroll = (event) => { const btns = Array.from( document.querySelectorAll(".startpage-navigation-group button") ); let index = btns.findIndex((x) => x.classList.contains("active")); delta = event.wheelDelta / 60; direction = delta > 0 ? "up" : "down"; if (direction === "up") { if (index > 0) { btns[index - 1].click(); } else { btns[btns.length - 1].click(); } } else { if (index < btns.length - 1) { btns[index + 1].click(); } else { btns[0].click(); } } }; chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) { if (changeInfo.url.startsWith("chrome://vivaldi-webui/startpage")) { const check = document.querySelector(".vm-scroll"); if (!check) { const nav = document.querySelector(".startpage-navigation"); nav.classList.add("vm-scroll"); nav.addEventListener("mousewheel", scroll); } } }); })();
Quotes.js
(function () { // ============================================================================================================ // Quote on Startpage // URL: https://forum.vivaldi.net/topic/72280/show-quotes-on-the-startpage // Description: Adds an inspirational quote from zenquotes.io above the speed dials on the startpage // Author(s): @nomadic // CopyRight: No Copyright Reserved // ============================================================================================================ function quotesToSpeeddial() { // Config ------------ // These options affect how often quotes are refreshed // Options: // - "daily": You will only get one quote per day. After midnight, a new quote should load // - "interval": You get a new quote every n hours, where n is set with NEW_QUOTE_INTERVAL // - "every": Every time you open a startpage tab or switch back to one, there will be a new quote const NEW_QUOTE_FREQUENCY = "daily"; const NEW_QUOTE_INTERVAL = 1; // Variables exposed for easy styling of how the quote looks, but you can also just edit the style.innerHTML // if you want to do more in depth restyling. const QUOTE_WIDTH = "max(85%, 850px)"; const QUOTE_BACKGROUND = "var(--colorBgAlphaBlur)"; const QUOTE_BACKGROUND_BLUR = "var(--backgroundBlur)"; const QUOTE_FORGROUND_COLOR = "var(--colorFg);"; const QUOTE_FONT = "400 1.5rem 'Segoe UI', system-ui, sans-serif;"; const QUOTE_AUTHOR_FONT = "400 13px 'Segoe UI', system-ui, sans-serif;"; // ------------------- function injectStyle() { const style = document.createElement("style"); style.id = "quoteStyle"; style.innerHTML = ` @keyframes fadein { from { opacity: 0; } to { opacity: 1; } } #quoteContainer { background: ${QUOTE_BACKGROUND}; color: ${QUOTE_FORGROUND_COLOR}; width: ${QUOTE_WIDTH}; margin: auto; padding: 9px 9px 6px 9px; margin-top: 36px; backdrop-filter: ${QUOTE_BACKGROUND_BLUR}; border-radius: var(--radius); animation: 0.4s ease-in fadein; } #quoteText { font: ${QUOTE_FONT}; margin: auto; width: 90%; padding: 10px 10px 0 10px; text-align: center; } #quoteAuthor { font: ${QUOTE_AUTHOR_FONT}; text-align: right; margin-top: auto; } .quoteBottomRow { display: flex; justify-content: space-between; } .quoteBottomRow > p { margin-top: auto; font-size: 11px; color: gray; opacity: 0.9; } .quoteBottomRow > p > a { color: unset; } #refreshQuote { height: 20px; background-color: unset; border: unset; transition: transform 0.5s; } #refreshQuote:active { transform: rotate(360deg); } .quoteBottomRight { display: flex; } `; document.getElementsByTagName("head")[0].appendChild(style); } // gets all quotes from storage and fails more nicely with an empty array async function getQuotesFromStorage() { return new Promise((resolve) => { chrome.storage.local.get(["speeddialQuotes"], function (result) { if (result["speeddialQuotes"] === undefined) { resolve([]); } else { resolve(result["speeddialQuotes"]); } }); }); } // gets a set of 50 new quotes from zenquotes.io and adds them to the end of the collection async function fetchNewQuotes() { return new Promise((resolve) => { fetch("https://zenquotes.io/api/quotes/") .then((response) => response.json()) .then(async (quotes) => { let newQuotes = []; for (const quote of quotes) { const formatedQuote = { q: quote.q, a: quote.a }; newQuotes.push(formatedQuote); } const oldQuotes = await getQuotesFromStorage(); const allQuotes = oldQuotes.concat(newQuotes); chrome.storage.local.set({ speeddialQuotes: allQuotes }); if (allQuotes.length >= 1) { resolve(allQuotes); } else { resolve([{ q: "APIs are great, but sometimes they break.", q: "nomadic" }]); } }); }); } // inputs the actual quote while determining if it should be new or the same as before async function insertQuoteText(wasManuallyrefreshed) { const quoteText = document.getElementById("quoteText"); const quoteAuthor = document.getElementById("quoteAuthor"); if (!quoteText || !quoteAuthor) return; // getting the quote text let quotes = await getQuotesFromStorage(); // left 10 quotes as a buffer for possible API issues if (quotes.length < 10) { quotes = await fetchNewQuotes(); } chrome.storage.local.get("speeddialQuotesLastUpdated", (result) => { const millisecondsPerDay = 1000 * 60 * 60 * 24; const currentTime = new Date(); let oldTime; if (Object.keys(result).length === 0) { oldTime = new Date(); chrome.storage.local.set({ speeddialQuotesLastUpdated: Date.now() }); } else { oldTime = new Date(result.speeddialQuotesLastUpdated); } let useNewQuoteNextTime = false; switch (NEW_QUOTE_FREQUENCY) { default: case "daily": const currentDaysOnly = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); const oldDaysOnly = new Date(oldTime.getFullYear(), oldTime.getMonth(), oldTime.getDate()); const daysBetween = (currentDaysOnly.getTime() - oldDaysOnly.getTime()) / millisecondsPerDay; if (daysBetween >= 1) useNewQuoteNextTime = true; break; case "interval": const millisecondsBetween = currentTime.getTime() - oldTime.getTime(); const hoursBetween = (millisecondsBetween / millisecondsPerDay) * 24; if (hoursBetween >= NEW_QUOTE_INTERVAL) useNewQuoteNextTime = true; break; case "every": useNewQuoteNextTime = true; break; } // BUG-FIX: update condition wouldn't update the shown quote until next render const quote = wasManuallyrefreshed || useNewQuoteNextTime ? quotes[1] : quotes[0]; quotes.shift(); // update storage to remove quote already used if (useNewQuoteNextTime || wasManuallyrefreshed) { chrome.storage.local.set({ speeddialQuotes: quotes, speeddialQuotesLastUpdated: Date.now() }); } quoteText.innerHTML = '"' + quote.q + '"'; quoteAuthor.innerHTML = quote.a; }); } // adds all the html necessary to view the quote async function addQuoteStructureToPage() { const startpage = document.querySelector(".startpage"); const oldQuote = document.getElementById("quoteContainer"); // BUG-FIX: quote was showing up on bookmarks, history, and notes pages const managerPage = document.querySelector(".webpageview.active .sdwrapper .manager"); if (managerPage) { if (oldQuote) oldQuote.remove(); } // check if already exists and elements are valid if (oldQuote || !startpage) return; const startpageNav = document.querySelector(".startpage .startpage-navigation"); let refrenceElement, position; if (startpageNav) { refrenceElement = startpageNav; position = "afterend"; } else { refrenceElement = startpage; position = "afterbegin"; } const quoteContainer = document.createElement("div"); quoteContainer.id = "quoteContainer"; quoteContainer.innerHTML = ` <div class="quote"> <blockquote id="quoteText"></blockquote> <div class="quoteBottomRow"> <p>provided by <a href="https://zenquotes.io/" target="_blank" id="attributionLink">ZenQuotes API</a></p> <div class="quoteBottomRight"> <p id="quoteAuthor"></p> <button id="refreshQuote"> <svg viewBox="0 0 26 26" height="20px" xmlns="http://www.w3.org/2000/svg"> <path d="M20 6.20711C20 5.76166 19.4614 5.53857 19.1464 5.85355L17.2797 7.72031C16.9669 7.46165 16.632 7.22741 16.2774 7.02069C14.7393 6.12402 12.9324 5.80372 11.1799 6.1171C9.4273 6.43048 7.84336 7.35709 6.71134 8.73121C5.57932 10.1053 4.97303 11.8373 5.00092 13.6175C5.02881 15.3976 5.68905 17.1098 6.86356 18.4478C8.03807 19.7858 9.65025 20.6623 11.4118 20.9206C13.1733 21.179 14.9693 20.8022 16.4785 19.8578C17.5598 19.1812 18.4434 18.2447 19.0553 17.1439C19.0803 17.099 19.1048 17.0538 19.1288 17.0084C19.1844 16.9033 19.2376 16.7968 19.2883 16.689C19.5213 16.193 19.2261 15.6315 18.7038 15.466C18.2666 15.3274 17.81 15.5117 17.5224 15.8594C17.4823 15.9079 17.4455 15.9596 17.4125 16.014C17.3994 16.0356 17.3869 16.0576 17.375 16.0801C16.9237 16.9329 16.2535 17.6577 15.4259 18.1757C14.3159 18.8702 12.9951 19.1473 11.6997 18.9573C10.4042 18.7673 9.21861 18.1227 8.35485 17.1387C7.49109 16.1547 7.00554 14.8955 6.98503 13.5864C6.96452 12.2772 7.41039 11.0035 8.24291 9.99293C9.07542 8.98238 10.2403 8.30093 11.5291 8.07047C12.818 7.84001 14.1468 8.07556 15.278 8.73499C15.4839 8.85508 15.6809 8.9878 15.868 9.13202L13.8536 11.1464C13.5386 11.4614 13.7617 12 14.2071 12H20V6.20711Z"></path> </svg> </button> </div> </div> </div> `; refrenceElement.insertAdjacentElement(position, quoteContainer); insertQuoteText(); document.getElementById("attributionLink").addEventListener("click", () => { chrome.tabs.create({ url: "https://zenquotes.io/" }); }); document.getElementById("refreshQuote").addEventListener("click", insertQuoteText); } injectStyle(); // only reliable way to detect new tabs including new windows with a single startpage tab vivaldi.tabsPrivate.onTabUpdated.addListener(addQuoteStructureToPage); // catches all redrawings of the startpage including theme changes and switching back to a tab const appendChild = Element.prototype.appendChild; Element.prototype.appendChild = function () { if (arguments[0].tagName === "DIV") { setTimeout( function () { if (this.classList.contains("startpage")) { addQuoteStructureToPage(); } }.bind(this, arguments[0]) ); } return appendChild.apply(this, arguments); }; } let intervalID = setInterval(() => { const browser = document.getElementById("browser"); if (browser) { clearInterval(intervalID); quotesToSpeeddial(); } }, 100); })();
Quote.css
.quoteBottomRow > p { opacity: 0 !important; }
-
@smerugu28 I will try to look at it soon. Lots of driving for me in the next few days.
-
@nomadic If you use the Vivaldi calendar, the quotes are not displayed on the start page, but in the calendar tab.