Reddit - Remember RES tag action

Saves previously set tag and colour to use again.

目前為 2018-07-13 提交的版本,檢視 最新版本

// ==UserScript==
// @name           Reddit - Remember RES tag action
// @description    Saves previously set tag and colour to use again.
// @author         James Skinner <[email protected]> (http://github.com/spiralx)
// @namespace      http://spiralx.org/
// @version        0.8.0
// @icon           data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiAAABYgAWToQQYAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAAF3SURBVDhP1ZI/SAJhGMZN3WpKOAgsKGiIoKWg0K2LXIJWj6DAhpAgKMiW7irBqziHbhYE12sLGtWGKHBx0kHcQrBJXNIz9e35vvsQoj841g9+fHy8z/PyHZzrXzANNbgKF2ASHsOhOYGTUIb3cBSq4hyKa3Ey2GsMeMRvvzALd+AVvIPbcAb+yAq8hBehUOgxnU5TtVol27ap3W5TpVKhVCpFsiw/IHMusstwgOHxeDZM07T7/T5Rr0vUfacBvR7uHWIzwzBayK+zDm8KktFo9FXEifaXiHbnxAXEZKKtqcHSSCTygs6nBYuWZfEhR1eIzjbFBdzsYcma8xKQyWQInXmn6jCeSCQ6fDoEqqqyzxhzqgK/368Xi0UeKJfLpGka5fN5yuVyrEClUonPCoUCSZLE/oMvjGBwqut6q16vU6PRoGw2yxc0m02q1WoUj8fffD5fjGWdyvdMeL3ew2AweKsoynM4HH4KBAKW2+0+wExyIn8Hl+sDt5ENCrpr91QAAAAASUVORK5CYII=
// @icon64         data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2029%2025%22%3E%3Cg%20transform=%22translate(1%201)%22%20stroke-width=%221.1%22%20fill-rule=%22evenodd%22%20fill=%22none%22%3E%3Ccircle%20stroke=%22#000%22%20cx=%222.7%22%20r=%222.7%22%20cy=%2210.7%22%20fill=%22#fff%22/%3E%3Ccircle%20stroke=%22#000%22%20cx=%2224.7%22%20r=%222.7%22%20cy=%2210.7%22%20fill=%22#fff%22/%3E%3Cpath%20stroke-linejoin=%22round%22%20stroke=%22#000%22%20stroke-linecap=%22round%22%20d=%22M21.23%201.35L15.83.08l-2%207.28%22/%3E%3Ccircle%20stroke=%22#000%22%20cx=%2223.13%22%20r=%222.13%22%20cy=%222.13%22%20fill=%22#fff%22/%3E%3Cellipse%20cy=%2214.99%22%20rx=%2212.24%22%20ry=%227.99%22%20stroke=%22#000%22%20cx=%2213.24%22%20fill=%22#fff%22/%3E%3Cg%20transform=%22translate(8%2012)%22%3E%3Ccircle%20stroke=%22#FF4500%22%20cx=%221%22%20r=%221.43%22%20cy=%221.43%22%20fill=%22#FF4500%22/%3E%3Ccircle%20stroke=%22#FF4500%22%20cx=%2210%22%20r=%221.43%22%20cy=%221.43%22%20fill=%22#FF4500%22/%3E%3Cpath%20stroke=%22#000%22%20d=%22M1.5%206.23C2.58%207.3%204.3%207.5%205.73%207.5m4.24-1.27C8.9%207.3%207.17%207.5%205.77%207.5%22/%3E%3C/g%3E%3C/g%3E%3C/svg%3E
// @match          *://*.reddit.com/r/*/comments/*
// @match          *://*.reddit.com/user/*
// @grant          unsafeWindow
// @run-at         document-end
// @require        https://unpkg.com/jquery@3/dist/jquery.min.js
// @require        https://greasyfork.org/scripts/370255-console-message/code/consolemessage.js?version=612436
// ==/UserScript==

// @require        https://greasyfork.org/scripts/10443-datacache-simple-storage-wrapper/code/DataCache%20-%20Simple%20storage%20wrapper.js?version=56961

/* jshint asi: true, esnext: true, laxbreak: true */
/* global jQuery, MutationSummary */

/**
==== 0.8.0 (2018.07.13) ====
* Changed console.message require to use GreasyFork

==== 0.7.0 (2018.02.13) ====
* Changed the rendering of the tag preview to match how RES does it
* Moved output to all use console.message

==== 0.6.0 (2018.02.11) ====
* Use unpkg.com for jQuery
* Add console.message for logging
* Use localstorage to save tags
* Use new Tag class to store tag info
* Updated ID for text field in tag popup

==== 0.5.1 (2018.02.11) ====
* Update icons to match other Reddit script

==== 0.5.0 (26.08.2017) ====
* Change to simple use of GM_getValue and GM_setValue for storage

==== 0.4.0 (21.08.2017) ====
* Update all other tags correctly when changing a user's tag
* Handle removing all other tags when clearing a user's tag

==== 0.3.0 (13.07.2017) ====

* Checks tag link to see if tag set, always overwrites if not
* Updates other tags for same user on current page

==== 0.2.1 (27.06.2017) ====
* Changed timeout of field set function to 250ms

==== 0.2.0 (31.05.2017) ====
* Updated jQuery to v3.2.1
* Added timeout before overriding tag/colour fields
* Update preview when setting tag/colour

*/

; ($ => {
  'use strict'
  
  const NORMAL = 'font-weight: normal; text-decoration: none; color: black'
  const ERROR = 'font-weight: bold; color: #f4f'
  const LINK = 'color: #05f; text-decoration: underline'
  const BOLD = 'font-weight: bold'
  const BLUE = [ 'color: #05f', 'color: #000' ]
  const GREEN = [ 'color: #c1007f', 'color: #000' ]

  const qval = (v, n) => `${n}=%c${JSON.stringify(v)}%c`

  const _BOLD = { fontWeight: 'bold' }
  const _GREEN = { color: '#c1007f' }
  const _BLUE = { color: '#05f' }

  // --------------------------------------------------------------------

  const bgToTextColorMap = {
	none: 'inherit',
	aqua: 'black',
	black: 'white',
	blue: 'white',
	cornflowerblue: 'white',
	fuchsia: 'white',
	gray: 'white',
	green: 'white',
	lime: 'black',
	maroon: 'white',
	navy: 'white',
	olive: 'white',
	orange: 'white',
	orangered: 'white',
	pink: 'black',
	purple: 'white',
	red: 'white',
	silver: 'black',
	teal: 'white',
	white: 'black',
	yellow: 'black'
  }


  // --------------------------------------------------------------------------

  $.fn.outer = function() {
    const elem = this[0] || {}
    return elem.nodeType === Document.ELEMENT_NODE
      ? elem.outerHTML
      : null
  }

  console.message.prototype.dump = function(obj) {
    if (obj && typeof obj.dump === 'function') {
      obj.dump.call(obj, this)
    } else if (typeof obj === 'function') {
      obj(this)
    }
    return this
  }

  // --------------------------------------------------------------------------

  class Tag {
    constructor(text = '', colour = 'none') {
      this.text = text
      this.colour = colour
    }

    get css() {
      const color = bgToTextColorMap[ this.colour ] // + ` !important`

      const backgroundColor = this.colour === 'none'
        ? 'transparent'
        : this.colour

      return { color, backgroundColor }
    }

    buildPreviewTag() {
      const $a = $(`<a>`, { href: '#', title: this.text })
        .addClass(`userTagLink hasTag truncateTag`)
        .css(this.css)
        .text(this.text)

      return $(`<span>`)
        .addClass('RESUserTag')
        .append($a)
    }

    apply($elem) {
      return $elem.text(this.text).css(this.css)
    }

    equals(other) {
      return this.text === other.text && this.colour === other.colour
    }

    json() {
      return JSON.stringify(this)
    }

    dump(msg) {
      /* jshint ignore:start */
      msg.text(this.text, {
        ...this.css,
        fontSize: '0.9em',
        padding: '0 4px',
        border: 'solid 1px rgb(199, 199, 199)',
        borderRadius: 3,
      })
      /* jshint ignore:end */
    }
  }

  Tag.parse = str => {
    const { text, colour } = JSON.parse(str)
    return new Tag(text, colour)
  }

  Tag.default = () => new Tag($('.pagename a').text(), 'olive')

  // --------------------------------------------------------------------------

  const tags = JSON.parse(localStorage.getItem('resrem') || '[]').map(v => new Tag(v.text, v.colour))

  function getLastTag() {
    return tags.length > 0 ? tags[0] : Tag.default()
  }

  function saveTag(tag) {
    const idx = tags.findIndex(t => t.equals(tag))

    if (idx !== -1) {
      tags.splice(idx, 1)
    }

    tags.unshift(tag)

    localStorage.setItem('resrem', JSON.stringify(tags))

    return tag
  }

  const msg = console.message().text('onScriptInit:', _BOLD)
  tags.forEach(tag => { msg.text(' ').dump(tag) })
  msg.print()

  // --------------------------------------------------------------------------

  function onTagModalOpened() {
    const $tagField = $('#userTaggerText')
    const $colourField = $('#userTaggerColor')
    const $previewField = $('#userTaggerPreview')

    const previousTag = getLastTag()

    if (previousTag) {
      $tagField.val(previousTag.text)
      $colourField.val(previousTag.colour)

      const $previewTag = previousTag.buildPreviewTag()
      console.info($previewTag.outer())

      $previewField
        .empty()
        .append($previewTag)
    }
  }  


  // --------------------------------------------------------------------------

  function onSaveTag() {
    const text = $('#userTaggerText').val()
    const colour = $('#userTaggerColor').val()
    const user = $('#userTaggerName').val()

    if (text) {
      const newTag = saveTag(new Tag(text, colour))

      const msg = console.message()
        .text('onSaveTag:', _BOLD)
        .text(' ')
        .dump(newTag)
        .text('* ')
      tags.slice(1).forEach(tag => { msg.text(' ').dump(tag) })
      msg.print()

      $(`a.userTagLink[username=${user}]`)
        .text(newTag.text)
        .css(newTag.css)
        .addClass('hasTag')
        .removeClass('RESUserTagImage')
    } else {
      $(`a.userTagLink[username=${user}]`)
        .text('')
        .removeAttr('style')
        .removeClass('hasTag')
        .addClass('RESUserTagImage')
    }
  }
  

  // --------------------------------------------------------------------------

  $('body')
    .on('click.resrem', 'a.userTagLink:not(.hasTag)', () => {
      setTimeout(onTagModalOpened, 250)
    })
    .on('click.resrem', '#userTaggerSave', onSaveTag)

  /*
    <div class="RESHover RESHoverInfoCard RESDialogSmall" style="top: 387.918px; left: 215.767px; width: 350px; display: block; opacity: 1;">
      <h3 class="RESHoverTitle" data-hover-element="0">
        <div>
          <span class="res-icon"></span>&nbsp;<span>Plan-Six</span>
        </div>
      </h3>

      <div class="RESCloseButton">x</div>

      <div class="RESHoverBody RESDialogContents" data-hover-element="1">
        <form id="userTaggerToolTip">
          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerText">Text</label>

            <input class="fieldPair-text" type="text" id="userTaggerText">
          </div>

          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerColor">Color</label>

            <select id="userTaggerColor">
              <option style="color: inherit; background-color: none" value="none">none</option>
              <option style="color: black; background-color: aqua" value="aqua">aqua</option>
              <option style="color: white; background-color: black" value="black">black</option>
              <option style="color: white; background-color: blue" value="blue">blue</option>
              <option style="color: white; background-color: cornflowerblue" value="cornflowerblue">cornflowerblue</option>
              <option style="color: white; background-color: fuchsia" value="fuchsia">fuchsia</option>
              <option style="color: white; background-color: gray" value="gray">gray</option>
              <option style="color: white; background-color: green" value="green">green</option>
              <option style="color: black; background-color: lime" value="lime">lime</option>
              <option style="color: white; background-color: maroon" value="maroon">maroon</option>
              <option style="color: white; background-color: navy" value="navy">navy</option>
              <option style="color: white; background-color: olive" value="olive">olive</option>
              <option style="color: white; background-color: orange" value="orange">orange</option>
              <option style="color: white; background-color: orangered" value="orangered">orangered</option>
              <option style="color: black; background-color: pink" value="pink">pink</option>
              <option style="color: white; background-color: purple" value="purple">purple</option>
              <option style="color: white; background-color: red" value="red">red</option>
              <option style="color: black; background-color: silver" value="silver">silver</option>
              <option style="color: white; background-color: teal" value="teal">teal</option>
              <option style="color: black; background-color: white" value="white">white</option>
              <option style="color: black; background-color: yellow" value="yellow">yellow</option>
            </select>
          </div>

          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerPreview">Preview</label>

            <span id="userTaggerPreview" style="color: white; background-color: olive;">
              <span class="RESUserTag">
                <a class="userTagLink hasTag truncateTag" style="background-color: olive; color: white !important;" title="Sexist" href="javascript:void 0">Sexist</a>
              </span>
            </span>
          </div>
          <a class="userTagLink hasTag truncateTag" style="background-color: olive; color: white !important;" title="Feminist" href="javascript:void 0">Feminist</a>

          <div class="fieldPair res-usertag-ignore">
            <label class="fieldPair-label" for="userTaggerIgnore">Ignore</label>

            <div id="userTaggerIgnoreContainer" class="toggleButton ">
              <span class="toggleThumb"></span>
              <div class="toggleLabel res-icon" data-enabled-text="" data-disabled-text=""></div>
              <input id="userTaggerIgnore" name="userTaggerIgnore" type="checkbox">
            </div>

            <a class="gearIcon" href="#res:settings/userTagger/hardIgnore" title="RES Settings > User Tagger > hardIgnore"> configure </a>
          </div>

          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerLink">
              <span class="userTaggerOpenLink">
                <a title="open link" href="javascript:void 0">Source URL</a>
              </span>
            </label>

            <input class="fieldPair-text" type="text" id="userTaggerLink" value="https://www.reddit.com/r/giantbomb/comments/7x251d/all_systems_goku_02/du5fpwr/">
          </div>

          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerVotesUp" title="Upvotes you have given this redditor">Upvotes</label>

            <input type="number" style="width: 50px;" id="userTaggerVotesUp" value="0">
          </div>

          <div class="fieldPair">
            <label class="fieldPair-label" for="userTaggerVotesDown" title="Downvotes you have given this redditor">Downvotes</label>

            <input type="number" style="width: 50px;" id="userTaggerVotesDown" value="0">
          </div>

          <div class="res-usertagger-footer">
            <a href="/r/dashboard#userTaggerContents" target="_blank" rel="noopener noreferer">View tagged users</a>

            <input type="submit" id="userTaggerSave" value="✓ save tag">
          </div>
        </form>
      </div>
    </div>

  */

})(jQuery)

jQuery.noConflict(true)