├── GeneratedSD.png
├── LICENSE
├── README.md
├── VivaldiSDgen.js
├── icon128.png
├── icon16.png
├── icon32.png
├── icon48.png
├── icon64.png
├── logo.svg
└── manifest.json
/GeneratedSD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/GeneratedSD.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The BSD 2-Clause License
2 |
3 | Copyright (c) 2018 André Zanghelini
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vivaldi Speed Dial Generator
2 |
3 | This extension makes the [Vivaldi browser](https://vivaldi.com/) automatically generate a cool Speed Dial image based on possible graphics defined in the meta or link tags, instead of generating a picture of the page.
4 |
5 | 
6 |
7 | # Installing
8 |
9 | 1) Head to the [releases page](https://github.com/An-dz/VSDGenerator/releases)
10 | 2) Download the latest version CRX file anywhere in your computer
11 | 3) Open Vivaldi
12 | 4) Open the extensions page
13 | 4) Enable _"Developer mode"_ at the top right
14 | 5) Drag the CRX file inside the Extensions page
15 |
16 | # How to use it
17 |
18 | Just install the extension and reload your Speed Dial images, nothing more.
19 |
20 | Just notice that since there are certain websites that link to non-existent resources the extension checks the resources before using them, this slows down the script execution and the generation may not fire. To be sure you don't fall for this problem make sure to cache the page first by visiting it before creating an image.
21 |
22 | **Will this work on another browser or Speed Dial extension?**
23 |
24 | Probably not. This extension uses a singularity of Vivaldi Speed Dial generation to inject itself and this is probably not the same on other browsers or extensions.
25 |
--------------------------------------------------------------------------------
/VivaldiSDgen.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const customChecker = [];
4 |
5 | /**
6 | * Enumeration for different types
7 | */
8 | const powers = {
9 | colour: {
10 | custom: 4,
11 | theme_color: 3,
12 | mask_icon: 2,
13 | ms_tile_color: 1,
14 | },
15 | image: {
16 | // for custom getters so it's not overwritten by others
17 | custom: 7,
18 | // social media image, probably related with content
19 | // meta[property="og:image"]
20 | og_image: 6,
21 | // social media image, probably related with content
22 | // meta[name="twitter:image"]
23 | twitter: 5,
24 | // SVG icon, excellent quality
25 | // link[sizes="any"]
26 | svg: 4,
27 | // high probability of an SVG icon
28 | // link[rel="mask-icon"]
29 | mask_icon: 3,
30 | // icons with defined sizes, some apple icons might fall here
31 | // link[sizes]
32 | icon_sizes: 2,
33 | // MS tile, good guaranteed quality of 144px
34 | // meta[name="msapplication-TileImage"]
35 | ms_tile_image: 2.0144,
36 | // could be anything from 1px to infinity
37 | // we assume the old iPhone 1 57px
38 | // link[rel="apple-touch-icon"]
39 | apple: 2.0057,
40 | // favicon for the rescue
41 | favicon: 1,
42 | favicon_ico: 0.5,
43 | },
44 | };
45 |
46 | const SD_data = {
47 | colour: {
48 | power: 0,
49 | },
50 | image: {
51 | power: 0,
52 | list: [],
53 | },
54 | };
55 |
56 | async function testImage(prev_power, src) {
57 | // console.log("Testing Image...", src);
58 | let response;
59 | try {
60 | response = await fetch(src, {method: "HEAD"});
61 | console.log(response);
62 |
63 | if (!response.ok) {
64 | console.log("failed 1");
65 | SD_data.image.power = prev_power;
66 | const [list_power, list_src] = SD_data.image.list.pop();
67 | setImage(list_power, list_src);
68 | }
69 | } catch(e) {
70 | console.log("failed 2");
71 | console.log(response);
72 | SD_data.image.power = prev_power;
73 | const [list_power, list_src] = SD_data.image.list.pop();
74 | setImage(list_power, list_src);
75 | }
76 | // console.log("Finished fetch.");
77 | }
78 |
79 | function setBackground(power, colour) {
80 | if (SD_data.colour.power < power) {
81 | SD_data.colour.power = power;
82 |
83 | document.getElementById("Vivaldi_SDG_colour").textContent = `
84 | :root html, :root body {
85 | background-color: ${colour} !important;
86 | }
87 | `;
88 | }
89 | }
90 |
91 | async function setImage(power, src) {
92 | // const prev_power = SD_data.image.power;
93 |
94 | if (SD_data.image.power < power) {
95 | // we must check the existence of the image as there are sites
96 | // dumb enough to put the tag but linked to a dead image
97 | // await testImage(prev_power, src);
98 |
99 | SD_data.image.power = power;
100 |
101 | const centralise = (
102 | power < powers.image.custom &&
103 | power !== powers.image.twitter &&
104 | power !== powers.image.og_image
105 | ) ? "auto 50%" : "cover";
106 |
107 | const position = power >= powers.image.custom ? "top center" : "center";
108 |
109 | document.getElementById("Vivaldi_SDG_logo").textContent = `
110 | :root body {
111 | background-image: url('${src}') !important;
112 | background-position: ${position} !important;
113 | background-size: ${centralise} !important;
114 | background-repeat: no-repeat !important;
115 | }
116 | `;
117 |
118 | return;
119 | }
120 |
121 | SD_data.image.list.push([power, src]);
122 | SD_data.image.list.sort((a, b) => (a[0] > b[0]) - (b[0] > a[0]));
123 | }
124 |
125 | async function setImageTagAsSD(selector, extraPower) {
126 | const power = powers.image.custom + (extraPower ? extraPower : 0);
127 |
128 | const imageElement = document.querySelector(selector);
129 |
130 | if (imageElement) {
131 | await setImage(power, imageElement.src);
132 | return;
133 | }
134 |
135 | customChecker.push(async element => {
136 | if (element.tagName !== "IMG") {
137 | return;
138 | }
139 |
140 | const image = document.querySelector(selector);
141 |
142 | if (image) {
143 | await setImage(power, image.src);
144 | }
145 | });
146 | }
147 |
148 | function createStyles() {
149 | const hidder = document.createElement("style");
150 | hidder.type = "text/css";
151 | hidder.id = "Vivaldi_SDG_hidder";
152 | hidder.textContent = `
153 | :root html, :root body {
154 | height: 100vh !important;
155 | margin: 0 !important;
156 | padding: 0 !important;
157 | visibility: visible !important;
158 | opacity: 1 !important;
159 | }
160 | html > :not(body), body * {
161 | display: none !important;
162 | visibility: hidden !important;
163 | }
164 | `;
165 |
166 | const colour = document.createElement("style");
167 | colour.type = "text/css";
168 | colour.id = "Vivaldi_SDG_colour";
169 |
170 | const logo = document.createElement("style");
171 | logo.type = "text/css";
172 | logo.id = "Vivaldi_SDG_logo";
173 |
174 | document.head.appendChild(hidder);
175 | document.head.appendChild(colour);
176 | document.head.appendChild(logo);
177 | }
178 |
179 | // check each element loaded in the head
180 | async function metaAnalyser(element) {
181 | const tagName = element.tagName;
182 |
183 | if (tagName === "LINK") {
184 | const rel = element.rel;
185 | const sizes = element.sizes;
186 |
187 | if (sizes.length > 0) {
188 | let size = 0.0001;
189 |
190 | if (rel === "apple-touch-icon") {
191 | size = 0;
192 | }
193 |
194 | if (sizes.value === "any") {
195 | await setImage(powers.image.svg, element.href);
196 | return;
197 | }
198 |
199 | if (sizes.length === 1) {
200 | size += Number.parseInt(sizes.value, 10);
201 | await setImage(powers.image.icon_sizes + size / 10000, element.href);
202 | return;
203 | }
204 |
205 | let max = 0;
206 |
207 | for (var idx = sizes.length - 1; idx >= 0; idx--) {
208 | max = Number.max(max, Number.parseInt(sizes[idx], 10));
209 | }
210 |
211 | await setImage(powers.image.icon_sizes + (max + size) / 10000, element.href);
212 |
213 | return;
214 | }
215 |
216 | if (rel === "mask-icon") {
217 | setBackground(powers.colour.mask_icon, element.getAttribute("color"));
218 | await setImage(powers.image.mask_icon, element.href);
219 | return;
220 | }
221 |
222 | if (rel === "apple-touch-icon") {
223 | await setImage(powers.image.apple, element.href);
224 | return;
225 | }
226 |
227 | if (rel.search(/\bicon\b/) > -1) {
228 | await setImage(powers.image.favicon, element.href);
229 | }
230 |
231 | return;
232 | }
233 |
234 | if (tagName === "META") {
235 | const name = element.name;
236 |
237 | if (element.property === "og:image") {
238 | await setImage(powers.image.og_image, element.content);
239 | return;
240 | }
241 |
242 | switch (name) {
243 | case "theme-color":
244 | setBackground(powers.colour.theme_color, element.content);
245 | break;
246 | case "msapplication-TileColor":
247 | setBackground(powers.colour.ms_tile_color, element.content);
248 | break;
249 | case "msapplication-TileImage":
250 | await setImage(powers.image.ms_tile_image, element.content);
251 | break;
252 | case "twitter:image":
253 | await setImage(powers.image.twitter, element.content);
254 | break;
255 | default:
256 | }
257 | }
258 | }
259 |
260 | /* Add custom functions below this line to avoid merge conflicts */
261 |
262 |
263 | /* Add custom functions above this line to avoid merge conflicts */
264 |
265 | /**
266 | * Custom functions for specific sites
267 | */
268 | const custom = {
269 | /*
270 | * # Example 1
271 | * Getting an image from the page
272 | *
273 | * ```javascript
274 | * "www.example.com": async () => await setImageTagAsSD(".title img"),
275 | * ```
276 | *
277 | * # Example 2
278 | * Getting multiple possibilities from the page
279 | *
280 | * The number as second argument defines which
281 | * should be selected if multiple are found,
282 | * higher number means higher preference.
283 | *
284 | * Don't use negative numbers
285 | *
286 | * ```javascript
287 | * "www.example.com": async () => await Promise.all([
288 | * setImageTagAsSD("img.a", 3),
289 | * setImageTagAsSD("img.b", 2),
290 | * setImageTagAsSD("img.c", 1),
291 | * setImageTagAsSD("img.d", 0),
292 | * ]),
293 | * ```
294 | *
295 | * # Example 3
296 | * You can also call a more complex function you add above
297 | *
298 | * For example, you may need to read some custom parameter
299 | * because there's a script to lazyload images and the URL
300 | * is inside a data-url parameter, or you may need to inject
301 | * a script on the page as the image URL itself is lazy loaded,
302 | * and you'll need to intercept when this call is done.
303 | *
304 | * ```javascript
305 | * "www.example.com": callSomeThing,
306 | * ```
307 | */
308 | };
309 |
310 | // apply only when generating a SD
311 | if (
312 | window.innerHeight === 838 &&
313 | window.innerWidth === 1024 &&
314 | window.innerHeight === window.outerHeight &&
315 | window.innerWidth === window.outerWidth
316 | ) {
317 | // check the loading until the head is loaded
318 | const dom_observer = new MutationObserver(async mutations => {
319 | mutations.forEach(async change => {
320 | change.addedNodes.forEach(async element => {
321 | if (element.tagName === "HEAD") {
322 | createStyles();
323 | await setImage(powers.image.favicon_ico, "/favicon.ico");
324 | custom[window.location.host] && await custom[window.location.host]();
325 | return;
326 | }
327 |
328 | await metaAnalyser(element);
329 | customChecker.forEach(f => f(element));
330 | });
331 | });
332 | });
333 |
334 | dom_observer.observe(document, {childList: true, subtree: true});
335 | }
336 |
--------------------------------------------------------------------------------
/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/icon128.png
--------------------------------------------------------------------------------
/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/icon16.png
--------------------------------------------------------------------------------
/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/icon32.png
--------------------------------------------------------------------------------
/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/icon48.png
--------------------------------------------------------------------------------
/icon64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/An-dz/VSDGenerator/1df47800d7f80579fd0eb04bb8bc6cac14dfb6f5/icon64.png
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
27 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Speed Dial Generator",
3 | "version": "2.0",
4 | "description": "Make Vivaldi automatically generate a cool SD.",
5 | "author": "André Zanghelini (An_dz)",
6 | "homepage_url": "https://github.com/An-dz/VSDGenerator",
7 | "icons": {
8 | "16": "icon16.png",
9 | "32": "icon32.png",
10 | "48": "icon48.png",
11 | "64": "icon64.png",
12 | "128": "icon128.png"
13 | },
14 | "content_scripts": [ {
15 | "js": [ "VivaldiSDgen.js" ],
16 | "matches": [ "" ],
17 | "run_at": "document_start"
18 | } ],
19 | "manifest_version": 2
20 | }
21 |
--------------------------------------------------------------------------------