Google Searching Tags Box

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

נכון ליום 09-05-2022. ראה הגרסה האחרונה.

// ==UserScript==
// @name         Google Searching Tags Box
// @version      1.0
// @description  Make your searches easier by adding tags to your search queries with one click
// @author       OpenDec
// @license      MIT
// @match        https://www.google.com/
// @match        https://www.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @grant        none
// @run-at       document-start
// @namespace https://greasyfork.org/users/873547
// ==/UserScript==

window.addEventListener('DOMContentLoaded', function stageReady() {

  // INIT
  var input = document.querySelector('input.gLFyf.gsfi');
  var container = document.querySelector('.RNNXgb');
  var tagsBox = document.createElement('div');
  var items;
  var validTarget;
  var data = [];
  var draggedItem = null;

  tagsBox.id = 'od-tagsbox';
  tagsBox.draggable = true;
  container.parentNode.insertBefore(tagsBox, container.nextSibling);

  // tagsBox is a valid target
  tagsBox.addEventListener('dragover', function (e) {
    e.preventDefault();
  });
  tagsBox.addEventListener('drop', function (e) {
    e.preventDefault();
    validTarget = true;
  });

  // DATA STORAGE
  function loadData() {
    data = JSON.parse(localStorage.getItem('odtagsbox') || '[]');
  }
  function saveData() {
    updateData();
    localStorage.setItem('odtagsbox', JSON.stringify(data));
  }

  // PARSING
  function parseData() {
    addItem();
    for (var i in data) {
      addItem(data[i].text, data[i].color);
    }
  }
  function updateData() {
    data = Array.from(items).flatMap(function(e){return e.classList.contains('od-additem') ? [] : [{ text: e.dataset.title, color: e.dataset.color }]; });
  }

  // ADD ITEM
  function addItem(str, color, index) {
    str = str || '';
    color = color || randomHSL();
    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.title = str;
    label.style.backgroundColor = color;

    item.dataset.title = str;
    item.dataset.color = color;
    item.draggable = true;

    item.appendChild(label);

    (index < tagsBox.childElementCount) ? tagsBox.insertBefore(item, tagsBox.children[index]) : tagsBox.appendChild(item);

    // :: ACTIONS ::

    // ON CLICK
    item.addEventListener('click', str ?
    // 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();
      } :
    // 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 = randomHSL();

        var dupl = tagsBox.querySelector('.od-item[data-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 START
    item.addEventListener('dragstart', function (e) {
      e.dataTransfer.effectAllowed = "move";
      draggedItem = this;
      tagsBox.classList.add('od-grabbing');
      for (var i = 0; i < items.length; i++) {
        items.item(i).classList.add(items.item(i) == draggedItem ? 'od-draggeditem' : 'od-hintitem');
      }
    });

    // DRAG ENTER
    item.addEventListener('dragenter', function () {
      if (this != draggedItem) { this.classList.add('od-activeitem'); }
    });

    // DRAG LEAVE
    item.addEventListener('dragleave', function () {
      this.classList.remove('od-activeitem');
    });

    // DRAG END
    item.addEventListener('dragend', function () {
      if (!validTarget && !this.classList.contains('od-additem')) {
        removeItem(this);
      }
      validTarget = false;

      tagsBox.classList.remove('od-grabbing')
      for (var i = 0; i < items.length; i++) {
        items.item(i).classList.remove('od-hintitem');
        items.item(i).classList.remove('od-activeitem');
        items.item(i).classList.remove('od-draggeditem');
      }
    });

    // DRAG OVER - PREVENT THE DEFAULT 'DROP', SO WE CAN DO OUR OWN
    item.addEventListener('dragover', function (e) {
      e.preventDefault();
    });

    // ON DROP
    item.addEventListener('drop', function (e) {
      e.dataTransfer.dropEffect = 'move';
      e.preventDefault();
      validTarget = true;

      if (this != draggedItem) {
        var currentpos = 0, droppedpos = 0;
        for (var it = 0; it < items.length; it++) {
          if (draggedItem == items[it]) { currentpos = it; }
          if (this == items[it]) { droppedpos = it; }
        }
        if (currentpos < droppedpos) {
          this.parentNode.insertBefore(draggedItem, this.nextSibling);
        } else {
          this.parentNode.insertBefore(draggedItem, this);
        }
        saveData();
      }
    });
  }

  // REMOVE ITEM
  function removeItem(el){
    var text = el.dataset.title;
    var removeIndex = data.findIndex(function(i){return i.text === text;});
    data.splice(removeIndex, 1);
    tagsBox.removeChild(el);
    saveData();
  }

  // START
  function start(){
    addGlobalStyle(css);
    tagsBox.innerHTML = '';
    loadData();
    parseData();
    items = tagsBox.getElementsByTagName('div');
  }






// STYLE

var css = '/* RESET */';

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

css+= '/* CONTAINER */';

css+= '#od-tagsbox {';
css+= '    position: absolute;';
css+= '    top: -34px;';
css+= '    max-width: 100%;';
css+= '    max-height: 30px;';
css+= '    margin-top: 2px;';
css+= '    border: 1px solid transparent;';
css+= '    border-radius: 20px;';
css+= '    background: rgba(255,255,255, 0);';
css+= '    overflow: hidden;';
css+= '    transition: .3s ease-out;';
css+= '    z-index: 999;';
css+= '}';
css+= '#od-tagsbox * {';
css+= '    box-sizing: border-box;';
css+= '}';
css+= '#searchform #od-tagsbox {';
css+= '    top: -42px;';
css+= '    left: 30px;';
css+= '}';
css+= '#searchform.minidiv #od-tagsbox {';
css+= '    top: -33px;';
css+= '}';
css+= '#od-tagsbox:hover {';
css+= '    max-height: 100px;';
css+= '		border-color: rgba(200,200,200, .2);';
css+= '		box-shadow: 0 2px 5px 1px rgba(64,60,67,.3);';
css+= '		background: rgba(255,255,255, .2);';
css+= '}';

css+= '/* ITEMS */';

css+= '.od-item {';
css+= '    float: left;';
css+= '    padding: 3px;';
css+= '    transition: opacity .3s .1s ease-out;';
css+= '    cursor: pointer;';
css+= '}';
css+= '.od-item > i {';
css+= '    position: relative;';
css+= '    top: 0;';
css+= '    display: block;';
css+= '    width: 24px;';
css+= '    height: 24px;';
css+= '    border: 2px solid #0002;';
css+= '    text-align: center;';
css+= '    text-transform: uppercase;';
css+= '    white-space: nowrap;';
css+= '    font: normal 12px/20px Arial, sans-serif;';
css+= '    color: #fff;';
css+= '    border-radius: 50%;';
css+= '    overflow: hidden;';
css+= '    cursor: pointer;';
css+= '    transition: .2s ease-out, font-size 0s, font-weight 0s;';
css+= '}';
css+= '.od-item > i::before {';
css+= '    content: attr(title);';
css+= '}';
css+= '.od-item.od-additem > i{';
css+= '    font-size: 18px;';
css+= '    font-weight: bold;';
css+= '}';
css+= '.od-item.od-additem > i::before {';
css+= '    content: "+";';
css+= '}';

css+= '/* DRAGGING */';

css+= '#od-tagsbox.od-grabbing {';
css+= '    cursor: move;';
css+= '}';
css+= '#od-tagsbox.od-grabbing > .od-item {';
css+= '    transition-delay: 0;';
css+= '}';
css+= '.od-hintitem {';
css+= '    opacity: .4;';
css+= '}';
css+= '.od-hintitem > i {';
css+= '    pointer-events: none;';
css+= '}';
css+= '.od-draggeditem {';
css+= '    opacity: 0;';
css+= '}';
css+= '.od-activeitem {';
css+= '    opacity: .25;';
css+= '}';
css+= '.od-activeitem > i {';
css+= '    top: -3px;';
css+= '}';

  function randomHSL() {
    return 'hsl(' + ~~(360 * Math.random()) + ',50%,50%)';
  }

  function addGlobalStyle(css) {
    var h = document.querySelector('head');
    if (!h) return;
    var s = document.createElement('style');
    s.type = 'text/css';
    s.innerHTML = css;
    h.appendChild(s);
  }

  start();

});