Theme Preview Vivaldi Forum

Theme Preview for Vivaldi and Vivaldi Forum in Vivaldi Forum

// ==UserScript==
// @name         Theme Preview Vivaldi Forum
// @namespace    tam710562
// @version      0.2
// @description  Theme Preview for Vivaldi and Vivaldi Forum in Vivaldi Forum
// @author       Hồng Minh Tâm
// @include      http*://forum.vivaldi.net/topic/*/*
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  GM_addStyle([
    '.theme-preview .themebox { display: inline-block; margin: 15px 15px 5px 0; vertical-align: top; width: 110px }',
    '.theme-preview .themebox .themebox-image svg { width: 100%; height: auto; }',
    '.theme-preview .themebox .themebox-title { text-align: center; font-weight: 700;white-space: normal; overflow: hidden; text-overflow: ellipsis; }',
  ].join(''));

  // Written by luetage
  function compMode(theme) {
    if (theme.themePage == 0) {
      theme.themePage = false;
    } else {
      theme.themePage = true;
    }
    if (theme.themeWin == 0) {
      theme.themeWin = false;
    } else {
      theme.themeWin = true;
    }
    if (theme.themeTabs == 0) {
      theme.themeTabs = false;
    } else {
      theme.themeTabs = true;
    }
    return {
      colors: {
        accentBg: '#' + theme.themeAc,
        baseBg: '#' + theme.themeBg,
        baseFg: '#' + theme.themeFg,
        highlightBg: '#' + theme.themeHi,
      },
      name: theme.themeName,
      settings: {
        accentFromPage: theme.themePage,
        accentOnWindow: theme.themeWin,
        borderRadius: theme.themeRound,
        tabsTransparent: theme.themeTabs
      },
      version: 0.1
    };
  }

  function checkTheme(theme) {
    if (
      typeof theme.colors !== 'object' ||
      typeof theme.colors.accentBg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colors.accentBg) ||
      typeof theme.colors.baseBg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colors.baseBg) ||
      typeof theme.colors.baseFg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colors.baseFg) ||
      typeof theme.colors.highlightBg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colors.highlightBg) ||
      typeof theme.name !== 'string' ||
      typeof theme.settings !== 'object' ||
      typeof theme.settings.accentFromPage !== 'boolean' ||
      typeof theme.settings.accentOnWindow !== 'boolean' ||
      (typeof theme.settings.borderRadius !== 'number' && typeof theme.settings.borderRadius !== 'string') ||
      typeof theme.settings.tabsTransparent !== 'boolean' ||
      typeof theme.version !== 'number'
    ) {
      return false;
    } else {
      return true;
    }
  }

  function checkThemeForum(theme) {
    if (
      typeof theme.themeName !== 'string' ||
      typeof theme.colorBg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorBg) ||
      typeof theme.colorFg !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorFg) ||
      typeof theme.colorHi !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorHi) ||
      typeof theme.colorBtn !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorBtn) ||
      typeof theme.colorDrop !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorDrop) ||
      typeof theme.colorLi !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorLi) ||
      typeof theme.colorLi2 !== 'string' ||
      !/^#(?:[0-9a-f]{3}){1,2}$/i.test(theme.colorLi2)
    ) {
      return false;
    } else {
      return true;
    }
  }

  function shadeHexColor(color, percent) {
    var f = parseInt(color.slice(1), 16), t = percent < 0 ? 0 : 255, p = percent < 0 ? percent * -1 : percent, R = f >> 16, G = f >> 8 & 0x00FF, B = f & 0x0000FF;
    return "#" + (0x1000000 + (Math.round((t - R) * p) + R) * 0x10000 + (Math.round((t - G) * p) + G) * 0x100 + (Math.round((t - B) * p) + B)).toString(16).slice(1);
  }

  function htmlToElement(html) {
    var template = document.createElement('template');
    template.innerHTML = html.trim();
    return template.content.firstChild;
  }

  function createThemeImage(theme) {
    var colorBgDarker = shadeHexColor(theme.colors.baseBg, -0.2);
    var colorBgDark = shadeHexColor(theme.colors.baseBg, -0.1);
    var colorBgLightIntense = shadeHexColor(theme.colors.baseBg, 0.1);
    var colorBgLight = theme.settings.accentOnWindow ? shadeHexColor(theme.colors.accentBg, 0.08) : shadeHexColor(theme.colors.baseBg, 0.08);

    var themeImage = htmlToElement('<svg xmlns="http://www.w3.org/2000/svg" width="96" height="76" viewBox="0 0 96 76"><rect fill="var(--colorBgDarker)" x="0" y="16" width="96" height="57"></rect><rect fill="var(--colorBg)" width="96" height="10"></rect><path fill="var(--colorAccentBg)" d="M0 10h96v6H0v-6zm0-6h30v6H0V4z"></path><rect fill="var(--colorBgDark)" y="16" width="6" height="57"></rect><rect fill="var(--colorBgLightIntense)" x="6" y="16" width="18" height="57"></rect><rect fill="var(--colorBgLight)" x="31" y="4" width="30" height="6"></rect><rect fill="var(--colorBg)" y="73" width="96" height="3"></rect><rect fill="var(--colorFg)" x="8" y="69" width="6" height="2"></rect><rect fill="var(--colorHighlightBg)" x="16" y="69" width="6" height="2"></rect></svg>');
    themeImage.style.setProperty('--colorBgDarker', colorBgDarker);
    themeImage.style.setProperty('--colorBg', theme.settings.accentOnWindow ? theme.colors.accentBg : theme.colors.baseBg);
    themeImage.style.setProperty('--colorAccentBg', theme.settings.accentOnWindow ? theme.colors.baseBg : theme.colors.accentBg);
    themeImage.style.setProperty('--colorBgDark', colorBgDark);
    themeImage.style.setProperty('--colorBgLightIntense', colorBgLightIntense);
    themeImage.style.setProperty('--colorBgLight', colorBgLight);
    themeImage.style.setProperty('--colorFg', theme.colors.baseFg);
    themeImage.style.setProperty('--colorHighlightBg', theme.colors.highlightBg);
    return themeImage;
  }

  function createThemeForumImage(theme) {
    var themeForumImage = htmlToElement('<svg xmlns="http://www.w3.org/2000/svg" width="110" height="76" viewBox="0 0 110 76"><rect fill="var(--colorBg)" x="0" y="0" width="110" height="76"></rect><rect fill="#121212" x="0" y="14" width="110" height="12"></rect><rect fill="var(--colorDrop)" x="85" y="26" width="25" height="50"></rect><rect fill="var(--colorBtn)" x="6" y="32" width="15" height="5"></rect><rect fill="var(--colorHi)" x="23" y="32" width="15" height="5"></rect><circle fill="var(--colorFg)" cx="40" cy="60" r="5"></circle><circle fill="var(--colorLi)" cx="55" cy="60" r="5"></circle><circle fill="var(--colorLi2)" cx="70" cy="60" r="5"></circle></svg>');
    themeForumImage.style.setProperty('--colorBg', theme.colorBg);
    themeForumImage.style.setProperty('--colorFg', theme.colorFg);
    themeForumImage.style.setProperty('--colorHi', theme.colorHi);
    themeForumImage.style.setProperty('--colorBtn', theme.colorBtn);
    themeForumImage.style.setProperty('--colorDrop', theme.colorDrop);
    themeForumImage.style.setProperty('--colorLi', theme.colorLi);
    themeForumImage.style.setProperty('--colorLi2', theme.colorLi2);
    return themeForumImage;
  }

  function createThemeBox(theme, isForum) {
    var themeboxImage = document.createElement('div');
    themeboxImage.className = 'themebox-image';
    if (isForum) {
      themeboxImage.appendChild(createThemeForumImage(theme));
    } else {
      themeboxImage.appendChild(createThemeImage(theme));
    }
    var themeboxTitle = document.createElement('div');
    themeboxTitle.className = 'themebox-title';
    themeboxTitle.innerHTML = theme.name || theme.themeName;
    var themebox = document.createElement('div');
    themebox.className = 'themebox';
    themebox.appendChild(themeboxImage);
    themebox.appendChild(themeboxTitle);
    return themebox;
  }

  function createThemePreview(theme, code) {
    if (theme.themeName) {
      theme = compMode(theme);
    }
    if (checkTheme(theme)) {
      var themePreview = document.createElement('div');
      themePreview.className = 'theme-preview';
      themePreview.appendChild(createThemeBox(theme));
      code.parentNode.insertBefore(themePreview, code.nextSibling);
    }
  }

  function createThemeCollectionPreview(themes, code) {
    var themePreview = document.createElement('div');
    themePreview.className = 'theme-preview';
    themes.forEach(function (theme) {
      if (theme.themeName) {
        theme = compMode(theme);
      }
      if (checkTheme(theme)) {
        themePreview.appendChild(createThemeBox(theme));
      }
    });
    if (themePreview.childElementCount > 0) {
      code.parentNode.insertBefore(themePreview, code.nextSibling);
    }
  }

  function createThemeForumPreview(theme, code) {
    var themePreview = document.createElement('div');
    themePreview.className = 'theme-preview';
    themePreview.appendChild(createThemeBox(theme, true));
    code.parentNode.insertBefore(themePreview, code.nextSibling);
  }

  function themePreview() {
    var codes = document.querySelectorAll('p:not([data-theme-preview="true"]), code:not([data-theme-preview="true"])');
    codes.forEach(function (code) {
      code.dataset.themePreview = true;
      if (!code.querySelector('code')) {
        try {
          var theme = JSON.parse(code.innerText.trim());
          if (checkThemeForum(theme)) {
            createThemeForumPreview(theme, code);
          } else if (Array.isArray(theme)) {
            createThemeCollectionPreview(theme, code);
          } else {
            createThemePreview(theme, code);
          }
        } catch (e) {
          // console.error(e);
        }
      }
    });
  }

  var observeDOM = (function () {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
      eventListenerSupported = window.addEventListener;

    return function (obj, callback) {
      if (MutationObserver) {
        var obs = new MutationObserver(function (mutations, observer) {
          if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) {
            callback();
          }
        });
        obs.observe(obj, {
          childList: true,
          subtree: true
        });
      } else if (eventListenerSupported) {
        obj.addEventListener('DOMNodeInserted', callback, false);
        obj.addEventListener('DOMNodeRemoved', callback, false);
      }
    };
  })();

  observeDOM(document.getElementById('content'), themePreview);
})();