Reddit - Remember RES tag action

Saves previously set tag and colour to use again.

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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)