Google Searching Tags Box

Make your searches easier by adding tags to your search queries with one click

当前为 2022-08-12 提交的版本,查看 最新版本

// ==UserScript==
// @name         Google Searching Tags Box
// @version      1.2
// @description  Make your searches easier by adding tags to your search queries with one click
// @author       OpenDec
// @match        https://www.google.com/*
// @match        https://www.google.co.jp/*
// @match        https://www.google.co.uk/*
// @match        https://www.google.es/*
// @match        https://www.google.ca/*
// @match        https://www.google.de/*
// @match        https://www.google.it/*
// @match        https://www.google.fr/*
// @match        https://www.google.com.au/*
// @match        https://www.google.com.tw/*
// @match        https://www.google.nl/*
// @match        https://www.google.com.br/*
// @match        https://www.google.com.tr/*
// @match        https://www.google.be/*
// @match        https://www.google.com.gr/*
// @match        https://www.google.co.in/*
// @match        https://www.google.com.mx/*
// @match        https://www.google.dk/*
// @match        https://www.google.com.ar/*
// @match        https://www.google.ch/*
// @match        https://www.google.cl/*
// @match        https://www.google.at/*
// @match        https://www.google.co.kr/*
// @match        https://www.google.ie/*
// @match        https://www.google.com.co/*
// @match        https://www.google.pl/*
// @match        https://www.google.pt/*
// @match        https://www.google.com.pk/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @run-at       document-start
// @namespace https://gf.qytechs.cn/users/873547
// @license      MIT
// ==/UserScript==

/* jshint esversion: 8 */

window.addEventListener('DOMContentLoaded', function stageReady(){
'use strict';


// --------------------------------------------------
// ---                    INIT                    ---
// --------------------------------------------------

addGlobalStyle(css(getColorMode(rgbIsDark(window.getComputedStyle(document.body).backgroundColor) ? 'dark' : 'light')));

var input = document.querySelector('input.gLFyf.gsfi');
var container = document.querySelector('.RNNXgb');
var tagsBox = document.createElement('div');
var tagsBoxWrapper = document.createElement('div');
var settings = {};
var defaultSettings = {tagsWidth: ''};
var items;
var arrTags = [];
var actions = {};
var draggedItem = null;
var draggedData = null;
var dragenterBoxCounter = 0;
var deletingZone = document.createElement('div');
var inputFile = document.createElement('input');
var contextMenu = document.createElement('div');
var contextMenuItems = [
  '<li data-action="copyTags"      ><i>📋</i> Copy Tags <kbd>Ctrl+C</kbd>',
  '<li data-action="appendTags"    ><i>📌</i> Append copied Tags <kbd>Ctrl+V</kbd>',
  '<li>',
  '<li data-action="importTags"    ><i>📂</i> Import and append Tags',
  '<li data-action="exportTags"    ><i>💾</i> Export Tags as txt',
  '<li>',
  '<li data-action="setWidthSmall" class="od-checkable od-tagswidth od-sizekey-"  ><i>◻</i> Small Tags width',
  '<li data-action="setWidthLarge" class="od-checkable od-tagswidth od-sizekey-L" ><i>▭</i> Large Tags width',
  '<li data-action="setWidthAuto"  class="od-checkable od-tagswidth od-sizekey-A" ><i>↔️</i> Auto Tags width',
  '<li>',
  '<li data-action="clearBox"      ><i>🗑️</i> Clear the Tags Box'
].join('');


// --------------------------------------------------
// ---                THE TAGS BOX                ---
// --------------------------------------------------

tagsBoxWrapper.id = 'od-tagsbox-wrapper';
tagsBox.id = 'od-tagsbox';
tagsBoxWrapper.appendChild(tagsBox);
container.parentNode.insertBefore(tagsBoxWrapper, container.nextSibling);


// --------------------------------------------------
// ---           DRAG-AND-DROP SETTINGS           ---
// --------------------------------------------------

// BOX HANDLERS
tagsBox.addEventListener('dragenter', function (e){
  e.preventDefault();
  e.dataTransfer.dropEffect = draggedItem ? 'move' : draggedData ? 'copy' : 'none' ;
});
tagsBox.addEventListener('dragover', function (e){
  e.preventDefault();
  e.dataTransfer.dropEffect = draggedItem ? 'move' : draggedData ? 'copy' : 'none' ;
});

// ITEMS HANDLERS
function itemDragstart (e){
  e.dataTransfer.effectAllowed = "move";
  deletingZone.classList.add('od-dragging');
  draggedItem = e.target;
  draggedItem.classList.add('od-draggeditem');
  for (var i = 0; i < items.length; i++) {
    items.item(i).classList.add('od-hintitem');
  }
}
function itemDragend (e){
  draggedItem = null;
  deletingZone.classList.remove('od-dragging', 'od-dragging-hover');
  for (var i = 0; i < items.length; i++) {
    items.item(i).classList.remove('od-hintitem', 'od-belowitem', 'od-draggeditem');
  }
  saveData();
}
function itemDragenter (e){
  e.preventDefault();
  if (draggedItem === null && draggedData === null){
    e.dataTransfer.effectAllowed = "none";
  }
  if (draggedItem === null){
    return false;
  }
  var swapItem = e.target;
  swapItem.classList.add('od-belowitem');
  swapItem = swapItem === draggedItem.nextSibling ? swapItem.nextSibling : swapItem;
  tagsBox.insertBefore(draggedItem, swapItem);
  items = tagsBox.getElementsByTagName('div');
}
function itemDragleave (e){
  e.target.classList.remove('od-belowitem');
}
function itemDragover (e){
  e.preventDefault();
  e.dataTransfer.dropEffect = draggedItem === null && draggedData === null ? 'none' : 'move';
}

// TAG DELETING ZONE
deletingZone.id = 'od-deletingZone';
tagsBoxWrapper.appendChild(deletingZone);

deletingZone.addEventListener('dragenter', function (e){
  e.preventDefault();
  if (!draggedItem.classList.contains('od-additem')){
    deletingZone.classList.add('od-dragging-hover');
  }
});
deletingZone.addEventListener('dragleave', function (e){
  e.preventDefault();
  deletingZone.classList.remove('od-dragging-hover');
});
deletingZone.addEventListener('dragover', function (e){
  e.preventDefault();
  e.dataTransfer.dropEffect = draggedItem.classList.contains('od-additem') ? 'none' : 'move';
});
deletingZone.addEventListener('drop', function (e){
  e.preventDefault();
  if (!draggedItem.classList.contains('od-additem')){
    removeItem(draggedItem);
  }
});


// --------------------------------------------------
// ---                INPUT FILE                ---
// --------------------------------------------------

inputFile.id = 'od-inputFile';
inputFile.type = 'file';
inputFile.style = 'display:none';
inputFile.accept = '.txt';
inputFile.addEventListener('change', function (e){ importData(this.files); });
// Required for iOS Safari
tagsBoxWrapper.appendChild(inputFile);


// --------------------------------------------------
// ---                CONTEXT MENU                ---
// --------------------------------------------------

contextMenu.id = 'od-contextMenu';
contextMenu.innerHTML = '<ul>' + contextMenuItems + '</ul>';

tagsBox.addEventListener('contextmenu', contextMenuOpen);
onoffListeners(contextMenu, 'mousedown contextmenu wheel', function(e){
  e.preventDefault();
  e.stopPropagation();
}, true);

contextMenu.querySelector('ul').addEventListener('mouseup', contextMenuClick);

tagsBoxWrapper.appendChild(contextMenu);

function contextMenuOpen(e){
  var x = e.clientX - 1;
  var y = e.clientY - 1;
  e.preventDefault();
  contextMenu.style = 'top: ' + y + 'px; left: ' + x + 'px';
  contextMenu.classList.add('open');

  setTimeout(function(){
    onoffListeners(window, 'wheel resize blur mousedown contextmenu', contextMenuClose, true);
  }, 1);
}
function contextMenuClose(e){
  setTimeout(function(){
    contextMenu.classList.remove('open');
    contextMenu.removeAttribute('style');

    onoffListeners(window, 'wheel resize blur mousedown contextmenu', contextMenuClose, false);
  }, 1);
}
function contextMenuClick(e){
  e.preventDefault();
  e.stopPropagation();

  if (contextMenu.querySelector('ul').contains(e.target)){
    var item = e.target.closest('li[data-action]');
    if (!item) return;
    contextMenuClose();
    actions[item.dataset.action]();
  }
}
function onoffListeners(element, events, listener, flag){
  var ev = events.trim().split(/ +/);
  for (var i = 0; i < ev.length; i++){
      element[(flag ? 'add' : 'remove') + 'EventListener'](ev[i], listener);
  }
}


// --------------------------------------------------
// ---            CONTEXT MENU ACTIONS            ---
// --------------------------------------------------

actions.copyTags = function(){
  clipboardCopy(encodeData(settings, arrTags))
    .then(function(){
      fxGlow(tagsBox);
    })
    .catch(function(){
      // Cannot write on clipboard
      modal(50);
    })
  ;
};
actions.appendTags = function(){
  clipboardPaste()
    .then(function(str){
      var arr = decodeData(str).tags;
      if (arr !== null){
        appendTagsArr(arr);
        fxGlow(tagsBox);
      } else {
        // Unknoun data format
        modal(10);
      }
    })
    .catch(function(){
      // Cannot read clipboard data
      modal(60);
    })
  ;
};
actions.importTags = function(){
  inputFile.value = null;
  inputFile.click();
};
actions.exportTags = function(){
  exportData(encodeData(settings, arrTags));
};
actions.setWidthSmall = function(){
  setTagsWidth('');
  saveData();
};
actions.setWidthLarge = function(){
  setTagsWidth('L');
  saveData();
};
actions.setWidthAuto = function(){
  setTagsWidth('A');
  saveData();
};
actions.clearBox = function(){
  var additem = tagsBox.querySelector('.od-additem');
  tagsBox.innerHTML = '';
  tagsBox.append(additem);
  saveData();
  fxGlow(tagsBox);
};


// --------------------------------------------------
// ---              DATA MANAGEMENT               ---
// --------------------------------------------------

function encodeData(params, tags){
  var arrParams = [params.tagsWidth];
  var strParams = arrParams.join(',');
  return ':tags' +
  (strParams ? '['+ strParams +']' : '')+
  ':' +
  tags.map(function(e){
    return e.text + e.color;
  }).join('');
}
function decodeData(str){
  var res = {};
  // New compact data format
  if (isValidDataFormat(str)){
    var parts = str.match(/^:tags(?:\[(.*)])?:(.*)$/);
    var arrParams = (parts[1] || '').split(',');
    res.params = {tagsWidth: arrParams[0]};
    res.tags = parts[2]
    .split('')
    .map(function(e){
      return {text: e.slice(0, -6), color: e.slice(-6)};
    });
  } else {
    // Backward compatibility for the old JSON format
    try{
      res.tags = JSON.parse(str || '[]');
      res.params = {};
    } catch(e) {
      res.tags = [];
      res.params = {};
    }
  }
  return res;
}
function parseData(){
  setTagsWidth(settings.tagsWidth);
  if (!tagsBox.querySelector('.od-additem')) fxFadein(addItem(), 500);
  var delay = 0;
  for (var i in arrTags){
    fxFadein(addItem(arrTags[i].text, arrTags[i].color), 500, delay += 30);
  }
}
function parseDataString(str){
  str = str.trim();
  if (str) {
    if (isValidDataFormat(str)){
      // Check for data with :tags: format
      appendTagsArr(decodeData(str).tags);
      fxGlow(tagsBox);
    } else {
      // Check for plain text, each not-blank line of the string is taken as a TAG text
      var arr = str.split(/\r?\n/).reduce(function(a, b){
        var tag = b.trim().toLowerCase();
        if (tag){
          a.push({text: tag});
        }
        return a;
      },[]);
      if (arr.length){
         appendTagsArr(arr);
         fxGlow(tagsBox);
      } else {
        // Unknoun data format
        modal(10);
      }
    }
  } else {
    // Unknoun data format
    modal(10);
  }
}
function updateData(){
  arrTags = Array.from(items).flatMap(function(e){return e.classList.contains('od-additem') ? [] : [{text: e.dataset.title, color: e.dataset.color}]; });
}
function restoreData(str){
  var data = decodeData(str);
  arrTags = data.tags;
  settings.tagsWidth = data.params.tagsWidth;
}
async function saveData(){
  updateData();
  if (arrTags.length){
    var data = encodeData(settings, arrTags);
    // Store data via GM APIs
    !!GM && await GM.setValue('odtagsbox', data);
    // Maintain forward compatibility through localStorage
    localStorage.setItem('odtagsbox', data);
  } else {
    !!GM && await GM.deleteValue('odtagsbox')
    localStorage.removeItem('odtagsbox');
  }
}
function importData(files){
  var file;
  if (window.FileReader){
    file = files[0];
    var reader = new FileReader();
    reader.addEventListener('load', function (e){
      var str = reader.result;
      if (arrTags.length === 0 && settings.tagsWidth === ''){
          // If the BOX is blank and not set, restore all data and settings using the imported data
          restoreData(str);
          parseData();
          saveData();
          fxGlow(tagsBox);
      } else {
        // Otherwise, just add TAGs to existing ones
        var arr = decodeData(str).tags;
        if (arr !== null){
          appendTagsArr(arr);
          saveData();
          fxGlow(tagsBox);
        } else {
          // Unknoun data format
          modal(10);
        }
      }
    });
    reader.addEventListener('error', function (e){
      if (e.target.error.name == 'NotReadableError'){
        modal(21);
      }
    });
    reader.readAsText(file, 'utf-8');
  } else {
    modal(20);
  }
}
function exportData(str){
  var name = 'tagsbox_data.txt';
  var blob = new Blob(['\ufeff' + str], { type: 'text/plain;charset=utf-8' });
  var objUrl = window.URL.createObjectURL(blob, { type: 'text/plain' });
  var a = document.createElement('a');
  a.href = objUrl;
  a.download = name;
  tagsBoxWrapper.appendChild(a);
  a.click();
  setTimeout(function (){
    window.URL.revokeObjectURL(objUrl);
    tagsBoxWrapper.removeChild(a);
  }, 100);
}
function isValidDataFormat(str){
  return /^:tags(?:\[.*])?:/.test(str);
}
function clipboardCopy(txt){
  // Return a promise
  if (navigator.clipboard){
    return navigator.clipboard.writeText(txt);
  } else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
    var textarea = document.createElement('textarea');
    textarea.value = txt;
    textarea.style.position = 'fixed';
    document.body.appendChild(textarea);
    textarea.focus();
    textarea.select();
    return new Promise(function(ok, ko){
      if (document.execCommand('copy')) ok();
      else ko();
      document.body.removeChild(textarea);
    });
  }
}
function clipboardPaste(){
  // Return a promise
  if (navigator.clipboard){
    var a = navigator.clipboard.readText();
    return a;
  } else if (document.queryCommandSupported && document.queryCommandSupported('paste')) {
    return new Promise(function(ok, ko){
      if (document.execCommand('paste')) ok();
      else ko();
    });
  }
}


// --------------------------------------------------
// ---               DATA TRANSFER                ---
// --------------------------------------------------

// COPY-PASTE KEYBOARD SHORTCUTS
function isCopyPasteAllowed(denyIfTextFieldsFocused){
  // Returns TRUE if nothing is selected on the page
  var actEl = document.activeElement;

  return (
    (
      !(// check if there are no focused fields
        denyIfTextFieldsFocused &&
        actEl &&
        (
          actEl.tagName.toLowerCase() === 'input' &&
          actEl.type == 'text' ||
          actEl.tagName.toLowerCase() === 'textarea'
        )
      ) &&
      (actEl.selectionStart === actEl.selectionEnd)
    ) &&
    ['none', 'caret'].includes(window.getSelection().type.toLowerCase())
  );
}

window.addEventListener('paste', function(e){
  var str = (e.clipboardData || window.clipboardData).getData('text');
  if (isCopyPasteAllowed(true)){
    parseDataString(str);
    e.preventDefault();
  }
});
window.addEventListener('copy', function(e){
  if (isCopyPasteAllowed()) {
    // Put the tags data on the clipboard
    e.clipboardData.setData('text/plain', encodeData(settings, arrTags));
    e.preventDefault();
    fxGlow(tagsBox);
  }
});

// DRAG-AND-DROP STRING OR EXTERNAL TXT FILE
function isValidDraggedDataType(data){
  // Accept only TEXT in external data type
  for (var i = 0; i < data.length; i++){
    if (data[i].type.match('^text/plain')){
      return true;
    }
  }
  return false;
}
tagsBox.addEventListener('dragenter', function (e){
  dragenterBoxCounter++;
  var data = e.dataTransfer.items;
  if (draggedData === null && isValidDraggedDataType(data)){
      draggedData = data[0];
      tagsBox.classList.add('od-dragging-external-data');
  }
});
tagsBox.addEventListener('dragleave', function (e){
  dragenterBoxCounter--;
  // Counter needed to prevent bubbling effect
  if (dragenterBoxCounter === 0){
    if(draggedData === null) return;
    draggedData = null;
    tagsBox.classList.remove('od-dragging-external-data');
  }
});
tagsBox.addEventListener('drop', function (e){
  e.preventDefault();
  tagsBox.classList.remove('od-dragging-external-data');
  var data = e.dataTransfer.items;
  // Exit if not TEXT data type
  if (!isValidDraggedDataType(data)) return false;

  if (data[0].kind === 'string'){
    // If string
    parseDataString(e.dataTransfer.getData('Text'));
  } else if (data[0].kind === 'file'){
    // If file, only :tags: format data is accepted
    importData(e.dataTransfer.files);
  }
  draggedData = null;
  dragenterBoxCounter = 0;
});


// --------------------------------------------------
// ---               TAGS FUNCTIONS               ---
// --------------------------------------------------

// Append TAGs by array
function appendTagsArr(arr){
  for (var i in arr) {
    var text = arr[i].text;
    var color = arr[i].color;
    var dupl = tagsBox.querySelector('.od-item[title="' + text + '"]');
    if (dupl) removeItem(dupl);
    addItem(text, color);
  }
  items = tagsBox.getElementsByTagName('div');
  saveData();
}
// Add and set a item in the BOX
function addItem(str, color, index){
  str = str || '';
  // Double check to ensure backward compatibility for the old HSL format of stored colors
  color = color ? color[0] === 'h' ? HSLToHex(color) : color : randomColor();
  index = index || tagsBox.childElementCount;

  var item = document.createElement('div');
  var label = document.createElement('i');

  item.classList.add('od-item');
  if (!str) item.classList.add('od-additem');

  label.dataset.title = str;
  label.style.backgroundColor = '#' + color;

  item.title = str || 'ADD TAG';
  item.dataset.title = str;
  item.dataset.color = color;
  item.draggable = true;

  item.appendChild(label);

  if (index < tagsBox.childElementCount) tagsBox.insertBefore(item, tagsBox.children[index]);
  else tagsBox.appendChild(item);


// --------------------------------------------------
// ---               ITEMS ACTIONS                ---
// --------------------------------------------------

  // ON CLICK
  item.addEventListener('click', str ?

    // TAG BUTTON - Add my TAG in the search field
    function (){
      if (typeof input.selectionStart !== 'undefined') {
        var startPos = input.selectionStart;
        var endPos = input.selectionEnd;
        var text = (startPos > 0 ? ' ' : '') + str + ' ';

        if (startPos > 0 && input.value[startPos-1] === ' ') startPos--;

        if (endPos < input.value.length && input.value[endPos] === ' ') endPos++;

        input.value = input.value.slice(0, startPos) + text + input.value.slice(endPos);
        input.focus();
        input.selectionStart = input.selectionEnd = startPos + text.length;
      } else {
        input.value = input.value.trim() + ' ' + str + ' ';
        input.focus();
      }
      input.click();
    } :

    // PLUS BUTTON (+) - Create a new TAG from the search field (highlighted) text
    function (){
      var text;
      if (input.selectionStart !== input.selectionEnd) {
        text = input.value.substring(input.selectionStart, input.selectionEnd).trim().toLowerCase();
      } else {
        text = input.value.trim().toLowerCase();
      }
      if (!text) {
        input.focus();
        return;
      }
      var newColor = randomColor();
      var dupl = tagsBox.querySelector('.od-item[title=\'' + text + '\']');
      if (dupl) removeItem(dupl);
      label.style.backgroundColor = '#' + newColor;
      item.dataset.color = newColor;
      var index = Array.from(tagsBox.children).indexOf(this) + 1;
      addItem(text, color, index);
      color = newColor;
      saveData();
    }
  );

  // DRAG-AND-DROP
  item.addEventListener('dragstart', itemDragstart);
  item.addEventListener('dragend', itemDragend);
  item.addEventListener('dragenter', itemDragenter);
  item.addEventListener('dragleave', itemDragleave);
  item.addEventListener('dragover', itemDragover);

  return item;
}
// Remove a TAG item
function removeItem(el){
  tagsBox.removeChild(el);
  saveData();
}
// Set the items width via a size key
function setTagsWidth(sizeKey){
  var classList = tagsBox.classList;
  sizeKey = ['', 'L','A'].includes(sizeKey) ? sizeKey : defaultSettings.tagsWidth || '';
  classList.remove('od-smallwidth', 'od-largewidth', 'od-autowidth');
  classList.add(sizeKey ? {'L': 'od-largewidth', 'A': 'od-autowidth'}[sizeKey] : 'od-smallwidth');
  settings.tagsWidth = sizeKey;

  // Set the checked item in the context menu
  var checkedItem = contextMenu.querySelector('.od-tagswidth[data-checked]');
  if (checkedItem) checkedItem.removeAttribute('data-checked');
  contextMenu.querySelector('.od-sizekey-' + sizeKey).dataset.checked = '';
}

// --------------------------------------------------
// ---              COLOR PROCESSING              ---
// --------------------------------------------------

function HSLToHex(hsl){
  if (!hsl) return;
  hsl = hsl.slice(4, -1).split(',');
  var h = hsl[0];
  var s = hsl[1].slice(0, -1) / 100;
  var l = hsl[2].slice(0, -1) / 100;
  var a = s * Math.min(l, 1 - l);
  var f = function(n){
    var k = (n + h / 30) % 12;
    var color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    return Math.round(255 * color).toString(16).padStart(2, '0');
  };
  return f(0) + f(8) + f(4);
}
function randomHSL(){
  return 'hsl(' + ~~(360 * Math.random()) + ',' + (40 + 30 * Math.random()) + '%,' + (40 + 20 * Math.random()) + '%)';
}
function randomColor(){
  return HSLToHex(randomHSL());
}
function rgbIsDark(rgb) {
    rgb = rgb.slice(4, -1).split(',');
    return ((rgb[0] * 299) + (rgb[1] * 587) + (rgb[2] * 114)) / 1000 < 155;
}

// --------------------------------------------------
// ---                  EFFECTS                   ---
// --------------------------------------------------

function fxGlow(el){
  el.classList.add('od-highlight');
  setTimeout(function(){el.classList.remove('od-highlight');}, 500);
}
function fxFadein(el, duration, delay){
  duration = duration == null ? 300 : +duration;
  delay = delay == null ? 0 : +delay;
  el.style.opacity = '0';
  el.style.transition = duration + 'ms ' + delay + 'ms ease-in-out';
  setTimeout(function(){
    el.style.removeProperty('opacity');
    setTimeout(function(){
      el.style.removeProperty('transition');
    }, duration + delay);
  }, 1);
}


// --------------------------------------------------
// ---                   MODAL                    ---
// --------------------------------------------------

function modal(msg, delay){
  if (typeof delay === 'undefined') delay = 10;
  if (typeof msg === 'number'){
    msg = modal.msgList[msg];
  }
  setTimeout(function(){
    alert(msg);
  }, delay);
}
modal.msgList = {
  10: '⚠️ Sorry!\nI don\'t understand the format of this data.\n\nNo tags have been added.',
  20: '⚠️ Oops!\nI can\'t open the file reader.💡 But...\nyou can open it elsewhere, then try the copy-paste functions.',
  21: '⚠️ Oops!\nI can\'t read this file.💡 Try picking it up and opening it again.',
  50: '⚠️ Oops!\nUnable to copy data to clipboard.\n\n💡 But...\nyou can copy the string from the search field.',
  60: '⚠️ Oops!\nI can\'t read data from the clipboard.\n\n💡 But... try with CTRL+V.\n– Close this modal first –',
};


// --------------------------------------------------
// ---                   START                    ---
// --------------------------------------------------

async function start(){

  // Retrieve data via GM APIs
  var data = !!GM && await GM.getValue('odtagsbox');
  // Maintain backward compatibility through localStorage
  if (!data) data = localStorage.getItem('odtagsbox');

  tagsBox.innerHTML = '';
  restoreData(data);
  parseData();
  items = tagsBox.getElementsByTagName('div');
  setTimeout(function(){ tagsBox.classList.remove('od-hidein');}, 2);
}


// --------------------------------------------------
// ---                   STYLE                    ---
// --------------------------------------------------

function addGlobalStyle(strCSS){
  var h = document.querySelector('head');
  if (!h) return;
  var s = document.createElement('style');
  s.type = 'text/css';
  s.innerHTML = strCSS;
  h.appendChild(s);
}
function getColorMode(mode){
  return {dark: mode === 'dark', light: mode !== 'dark'};
}
function css (colorMode){ return (
// RESET

'.ikrT4e { max-height: initial !important; }'+
'.e9EfHf { padding-top: 30px !important; }'+
'.Q3DXx.yIbDgf { margin-top: 16px !important; }'+
'#searchform > .sfbg { margin-top: 0 !important }'+

// CONTAINERS
'#od-tagsbox-wrapper *,'+
'#od-tagsbox-wrapper *::before,'+
'#od-tagsbox-wrapper *::after {'+
'  box-sizing: border-box;'+
'}'+
'#od-tagsbox {'+
'  position: absolute;'+
'  top: -34px;'+
'  max-width: 100%;'+
'  max-height: 32px;'+
'  margin-top: 2px;'+
'  border: 1px solid;'+
'  border-color: rgba(' + ( colorMode.dark ? '95, 99, 104' : '208, 211, 215' ) + ', 0);'+
'  border-radius: 16px;'+
'  outline: 2px solid transparent;'+
'  background: rgba(' + ( colorMode.dark ? '75, 75, 75' : '240, 240, 240' ) + ', 0);'+
'  box-shadow: 0 2px 5px 1px rgba(64, 60, 67, 0);'+
'  overflow: hidden;'+
'  transition: all .3s ease-out, outline 0s, outline-color .3s ease-out, max-height .4s ease;'+
'  z-index: 999;'+
'}'+
'#searchform #od-tagsbox {'+
'  top: -42px;'+
'  left: 30px;'+
'}'+
'#searchform.minidiv #od-tagsbox {'+
'  top: -33px;'+
'}'+
'#od-tagsbox:hover {'+
'  max-height: 200px;'+
'  border-color: rgba(' + ( colorMode.dark ? '95, 99, 104' : '208, 211, 215' ) + ', 1);'+
'  background: rgba(' + ( colorMode.dark ? '75, 75, 75' : '240, 240, 240' ) + ', .8);'+
'  box-shadow: 0 2px 5px 1px rgba(64, 60, 67, .3);'+
'}'+

// ITEMS

'.od-item {'+
'  float: left;'+
'  min-width: 30px;'+
'  max-width: 30px;'+
'  height: 30px;'+
'  cursor: pointer;'+
'  transition: all .3s ease-out, opacity .3s .1s ease-out;'+
'}'+

// ITEMS WIDTH PRESETS

'.od-smallwidth > .od-item { max-width: 30px; }'+
'.od-largewidth > .od-item { max-width: 60px; }'+
'.od-autowidth > .od-item { max-width: 180px; }'+

'.od-smallwidth > .od-item > i { min-width: 24px; }'+
'.od-largewidth > .od-item > i { min-width: 54px; }'+
'.od-autowidth > .od-item > i { text-overflow: ellipsis; }'+

// TAGS LABEL

'.od-item > i {'+
'  display: block;'+
'  top: 0;'+
'  left: 0;'+
'  min-width: 24px;'+
'  height: calc(100% - 6px);'+
'  margin: 3px;'+
'  border: 2px solid rgba(0, 0, 0, .2);'+
'  outline: 1px solid transparent;'+
'  border-radius: 15px;'+
'  text-align: center;'+
'  text-transform: uppercase;'+
'  white-space: nowrap;'+
'  font: normal 12px/20px Arial, sans-serif;'+
'  color: #fff;'+
'  overflow: hidden;'+
'  pointer-events: none;'+
'  transition: .2s ease-out, font-size 0s, font-weight 0s;'+
'}'+
'.od-item > i::before {'+
'  content: attr(data-title);'+
'}'+
'.od-item.od-additem > i{'+
'  font-size: 18px;'+
'  font-weight: bold;'+
'}'+
'.od-item.od-additem > i::before {'+
'  content: "+";'+
'}'+

// ITEMS HOVER

'#od-tagsbox > .od-item:not(.od-draggeditem):hover > i {'+
'  border-color: rgba(255, 255, 255, .4);'+
'  outline: 1px solid rgba(0, 0, 0, .4);'+
'  transition: 0s;'+
'}'+

// DRAG-AND-DROP

'.od-draggeditem > i {'+
'  opacity: 0;'+
'}'+
'.od-hintitem {'+
'  opacity: .6;'+
'  transition-delay: 0s;'+
'}'+
'.od-belowitem {'+
'}'+
'#od-deletingZone {'+
'  position: fixed;'+
'  top: 0;'+
'  right: 0;'+
'  bottom: 0;'+
'  left: 0;'+
'  z-index: 998;'+
'  background: rgba(255, 0, 0, .2);'+
'  opacity: 0;'+
'  visibility: hidden;'+
'  transition: .3s;'+
'}'+
'#od-deletingZone.od-dragging {'+
'  visibility: visible;'+
'}'+
'#od-deletingZone.od-dragging-hover {'+
'  opacity: 1;'+
'}'+
'#od-tagsbox.od-dragging-external-data {'+
'  outline: 2px dashed #45bfff;'+
'}'+
'#od-tagsbox.od-dragging-external-data > .od-item {'+
'  pointer-events: none;'+
'}'+

// CONTEXT MENU

'#od-contextMenu {'+
'  display: none;'+
'  position: fixed;'+
'  z-index: 999;'+
'  padding: 3px 0;'+
'  font: 400 12px/23px "Segoe UI", Calibri, Arial, sans-serif;'+
'  color: #000;'+
'  border: 1px #dadce0 solid;'+
'  background: #fff;'+
'  box-shadow: 5px 5px 4px -4px rgba(0, 0, 0, .9);'+
'  cursor: default;'+
'  user-select: none;'+
'}'+
'#od-contextMenu.open {'+
'  display: block;'+
'}'+
'#od-contextMenu > ul {'+
'  list-style-type: none;'+
'  margin: 0;'+
'  padding: 0;'+
'}'+
'#od-contextMenu > ul > li {'+
'  position: relative;'+
'  margin: 0;'+
'  padding: 0 22px 0 38px;'+
'  line-height: 23px;'+
'}'+
'#od-contextMenu > ul > li:empty {'+
'  margin: 4px 1px;'+
'  padding: 0;'+
'  border-top: 1px #dadce0 solid;'+
'}'+
'#od-contextMenu > ul > li > i:first-child {'+
'  position: absolute;'+
'  top: 0;'+
'  left: 0;'+
'  display: block;'+
'  width: 35px;'+
'  text-align: center;'+
'  font-size: 1.3em;'+
'  line-height: 23px;'+
'  font-style: normal;'+
'}'+
'#od-contextMenu > ul > li > kbd {'+
'  float: right;'+
'  display: block;'+
'  padding-left: 10px;'+
'  text-align: right;'+
'}'+
'#od-contextMenu > ul > li:not(:hover) > kbd {'+
'  color: #5f6368;'+
'}'+
'#od-contextMenu > ul > li:hover {'+
'  color: #000;'+
'  background: #e8e8e9;'+
'}'+
'#od-contextMenu > ul > li.od-checkable {'+
'  padding-left: 48px;'+
'}'+
'#od-contextMenu > ul > li.od-checkable[data-checked]::before {'+
'  content: "✓";'+
'  position: absolute;'+
'  left: 32px;'+
'}'+

// EFFECTS

// Glow
'#od-tagsbox.od-highlight {'+
'  outline: 3px solid #45bfff;'+
'  background: rgba(100, 180, 255, .6);'+
'  transition: 0s;'+
'}'+

// COLOR SCHEME

'@media (prefers-color-scheme: dark) {'+

// Dark-mode applies to the context menu according to the system color scheme
'  #od-contextMenu {'+
'    background: #292a2d;'+
'    color: #fff;'+
'    font-weight: 100;'+
'    border-color: #3c4043;'+
'  }'+
'  #od-contextMenu > ul > li:empty {'+
'    border-color: #3c4043;'+
'  }'+
'  #od-contextMenu > ul > li:hover {'+
'    color: #fff;'+
'    background: #3f4042;'+
'  }'+
'  #od-contextMenu > ul > li:not(:hover) > kbd {'+
'    color: #9aa0a6;'+
'  }'+
'}'
  );
}

// --------------------------------------------------
// ---               WE CAN START!                ---
// --------------------------------------------------

start();

});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址