Reddit - Remember RES tag action

Saves previously set tag and colour to use again.

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

  1. // ==UserScript==
  2. // @name Reddit - Remember RES tag action
  3. // @description Saves previously set tag and colour to use again.
  4. // @author James Skinner <spiralx@gmail.com> (http://github.com/spiralx)
  5. // @namespace http://spiralx.org/
  6. // @version 0.8.0
  7. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiAAABYgAWToQQYAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAAF3SURBVDhP1ZI/SAJhGMZN3WpKOAgsKGiIoKWg0K2LXIJWj6DAhpAgKMiW7irBqziHbhYE12sLGtWGKHBx0kHcQrBJXNIz9e35vvsQoj841g9+fHy8z/PyHZzrXzANNbgKF2ASHsOhOYGTUIb3cBSq4hyKa3Ey2GsMeMRvvzALd+AVvIPbcAb+yAq8hBehUOgxnU5TtVol27ap3W5TpVKhVCpFsiw/IHMusstwgOHxeDZM07T7/T5Rr0vUfacBvR7uHWIzwzBayK+zDm8KktFo9FXEifaXiHbnxAXEZKKtqcHSSCTygs6nBYuWZfEhR1eIzjbFBdzsYcma8xKQyWQInXmn6jCeSCQ6fDoEqqqyzxhzqgK/368Xi0UeKJfLpGka5fN5yuVyrEClUonPCoUCSZLE/oMvjGBwqut6q16vU6PRoGw2yxc0m02q1WoUj8fffD5fjGWdyvdMeL3ew2AweKsoynM4HH4KBAKW2+0+wExyIn8Hl+sDt5ENCrpr91QAAAAASUVORK5CYII=
  8. // @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
  9. // @match *://*.reddit.com/r/*/comments/*
  10. // @match *://*.reddit.com/user/*
  11. // @grant unsafeWindow
  12. // @run-at document-end
  13. // @require https://unpkg.com/jquery@3/dist/jquery.min.js
  14. // @require https://greasyfork.org/scripts/370255-console-message/code/consolemessage.js?version=612436
  15. // ==/UserScript==
  16.  
  17. // @require https://greasyfork.org/scripts/10443-datacache-simple-storage-wrapper/code/DataCache%20-%20Simple%20storage%20wrapper.js?version=56961
  18.  
  19. /* jshint asi: true, esnext: true, laxbreak: true */
  20. /* global jQuery, MutationSummary */
  21.  
  22. /**
  23. ==== 0.8.0 (2018.07.13) ====
  24. * Changed console.message require to use GreasyFork
  25.  
  26. ==== 0.7.0 (2018.02.13) ====
  27. * Changed the rendering of the tag preview to match how RES does it
  28. * Moved output to all use console.message
  29.  
  30. ==== 0.6.0 (2018.02.11) ====
  31. * Use unpkg.com for jQuery
  32. * Add console.message for logging
  33. * Use localstorage to save tags
  34. * Use new Tag class to store tag info
  35. * Updated ID for text field in tag popup
  36.  
  37. ==== 0.5.1 (2018.02.11) ====
  38. * Update icons to match other Reddit script
  39.  
  40. ==== 0.5.0 (26.08.2017) ====
  41. * Change to simple use of GM_getValue and GM_setValue for storage
  42.  
  43. ==== 0.4.0 (21.08.2017) ====
  44. * Update all other tags correctly when changing a user's tag
  45. * Handle removing all other tags when clearing a user's tag
  46.  
  47. ==== 0.3.0 (13.07.2017) ====
  48.  
  49. * Checks tag link to see if tag set, always overwrites if not
  50. * Updates other tags for same user on current page
  51.  
  52. ==== 0.2.1 (27.06.2017) ====
  53. * Changed timeout of field set function to 250ms
  54.  
  55. ==== 0.2.0 (31.05.2017) ====
  56. * Updated jQuery to v3.2.1
  57. * Added timeout before overriding tag/colour fields
  58. * Update preview when setting tag/colour
  59.  
  60. */
  61.  
  62. ; ($ => {
  63. 'use strict'
  64. const NORMAL = 'font-weight: normal; text-decoration: none; color: black'
  65. const ERROR = 'font-weight: bold; color: #f4f'
  66. const LINK = 'color: #05f; text-decoration: underline'
  67. const BOLD = 'font-weight: bold'
  68. const BLUE = [ 'color: #05f', 'color: #000' ]
  69. const GREEN = [ 'color: #c1007f', 'color: #000' ]
  70.  
  71. const qval = (v, n) => `${n}=%c${JSON.stringify(v)}%c`
  72.  
  73. const _BOLD = { fontWeight: 'bold' }
  74. const _GREEN = { color: '#c1007f' }
  75. const _BLUE = { color: '#05f' }
  76.  
  77. // --------------------------------------------------------------------
  78.  
  79. const bgToTextColorMap = {
  80. none: 'inherit',
  81. aqua: 'black',
  82. black: 'white',
  83. blue: 'white',
  84. cornflowerblue: 'white',
  85. fuchsia: 'white',
  86. gray: 'white',
  87. green: 'white',
  88. lime: 'black',
  89. maroon: 'white',
  90. navy: 'white',
  91. olive: 'white',
  92. orange: 'white',
  93. orangered: 'white',
  94. pink: 'black',
  95. purple: 'white',
  96. red: 'white',
  97. silver: 'black',
  98. teal: 'white',
  99. white: 'black',
  100. yellow: 'black'
  101. }
  102.  
  103.  
  104. // --------------------------------------------------------------------------
  105.  
  106. $.fn.outer = function() {
  107. const elem = this[0] || {}
  108. return elem.nodeType === Document.ELEMENT_NODE
  109. ? elem.outerHTML
  110. : null
  111. }
  112.  
  113. console.message.prototype.dump = function(obj) {
  114. if (obj && typeof obj.dump === 'function') {
  115. obj.dump.call(obj, this)
  116. } else if (typeof obj === 'function') {
  117. obj(this)
  118. }
  119. return this
  120. }
  121.  
  122. // --------------------------------------------------------------------------
  123.  
  124. class Tag {
  125. constructor(text = '', colour = 'none') {
  126. this.text = text
  127. this.colour = colour
  128. }
  129.  
  130. get css() {
  131. const color = bgToTextColorMap[ this.colour ] // + ` !important`
  132.  
  133. const backgroundColor = this.colour === 'none'
  134. ? 'transparent'
  135. : this.colour
  136.  
  137. return { color, backgroundColor }
  138. }
  139.  
  140. buildPreviewTag() {
  141. const $a = $(`<a>`, { href: '#', title: this.text })
  142. .addClass(`userTagLink hasTag truncateTag`)
  143. .css(this.css)
  144. .text(this.text)
  145.  
  146. return $(`<span>`)
  147. .addClass('RESUserTag')
  148. .append($a)
  149. }
  150.  
  151. apply($elem) {
  152. return $elem.text(this.text).css(this.css)
  153. }
  154.  
  155. equals(other) {
  156. return this.text === other.text && this.colour === other.colour
  157. }
  158.  
  159. json() {
  160. return JSON.stringify(this)
  161. }
  162.  
  163. dump(msg) {
  164. /* jshint ignore:start */
  165. msg.text(this.text, {
  166. ...this.css,
  167. fontSize: '0.9em',
  168. padding: '0 4px',
  169. border: 'solid 1px rgb(199, 199, 199)',
  170. borderRadius: 3,
  171. })
  172. /* jshint ignore:end */
  173. }
  174. }
  175.  
  176. Tag.parse = str => {
  177. const { text, colour } = JSON.parse(str)
  178. return new Tag(text, colour)
  179. }
  180.  
  181. Tag.default = () => new Tag($('.pagename a').text(), 'olive')
  182.  
  183. // --------------------------------------------------------------------------
  184.  
  185. const tags = JSON.parse(localStorage.getItem('resrem') || '[]').map(v => new Tag(v.text, v.colour))
  186.  
  187. function getLastTag() {
  188. return tags.length > 0 ? tags[0] : Tag.default()
  189. }
  190.  
  191. function saveTag(tag) {
  192. const idx = tags.findIndex(t => t.equals(tag))
  193.  
  194. if (idx !== -1) {
  195. tags.splice(idx, 1)
  196. }
  197.  
  198. tags.unshift(tag)
  199.  
  200. localStorage.setItem('resrem', JSON.stringify(tags))
  201.  
  202. return tag
  203. }
  204.  
  205. const msg = console.message().text('onScriptInit:', _BOLD)
  206. tags.forEach(tag => { msg.text(' ').dump(tag) })
  207. msg.print()
  208.  
  209. // --------------------------------------------------------------------------
  210.  
  211. function onTagModalOpened() {
  212. const $tagField = $('#userTaggerText')
  213. const $colourField = $('#userTaggerColor')
  214. const $previewField = $('#userTaggerPreview')
  215.  
  216. const previousTag = getLastTag()
  217.  
  218. if (previousTag) {
  219. $tagField.val(previousTag.text)
  220. $colourField.val(previousTag.colour)
  221.  
  222. const $previewTag = previousTag.buildPreviewTag()
  223. console.info($previewTag.outer())
  224.  
  225. $previewField
  226. .empty()
  227. .append($previewTag)
  228. }
  229. }
  230.  
  231.  
  232. // --------------------------------------------------------------------------
  233.  
  234. function onSaveTag() {
  235. const text = $('#userTaggerText').val()
  236. const colour = $('#userTaggerColor').val()
  237. const user = $('#userTaggerName').val()
  238.  
  239. if (text) {
  240. const newTag = saveTag(new Tag(text, colour))
  241.  
  242. const msg = console.message()
  243. .text('onSaveTag:', _BOLD)
  244. .text(' ')
  245. .dump(newTag)
  246. .text('* ')
  247. tags.slice(1).forEach(tag => { msg.text(' ').dump(tag) })
  248. msg.print()
  249.  
  250. $(`a.userTagLink[username=${user}]`)
  251. .text(newTag.text)
  252. .css(newTag.css)
  253. .addClass('hasTag')
  254. .removeClass('RESUserTagImage')
  255. } else {
  256. $(`a.userTagLink[username=${user}]`)
  257. .text('')
  258. .removeAttr('style')
  259. .removeClass('hasTag')
  260. .addClass('RESUserTagImage')
  261. }
  262. }
  263.  
  264. // --------------------------------------------------------------------------
  265.  
  266. $('body')
  267. .on('click.resrem', 'a.userTagLink:not(.hasTag)', () => {
  268. setTimeout(onTagModalOpened, 250)
  269. })
  270. .on('click.resrem', '#userTaggerSave', onSaveTag)
  271.  
  272. /*
  273. <div class="RESHover RESHoverInfoCard RESDialogSmall" style="top: 387.918px; left: 215.767px; width: 350px; display: block; opacity: 1;">
  274. <h3 class="RESHoverTitle" data-hover-element="0">
  275. <div>
  276. <span class="res-icon"></span>&nbsp;<span>Plan-Six</span>
  277. </div>
  278. </h3>
  279.  
  280. <div class="RESCloseButton">x</div>
  281.  
  282. <div class="RESHoverBody RESDialogContents" data-hover-element="1">
  283. <form id="userTaggerToolTip">
  284. <div class="fieldPair">
  285. <label class="fieldPair-label" for="userTaggerText">Text</label>
  286.  
  287. <input class="fieldPair-text" type="text" id="userTaggerText">
  288. </div>
  289.  
  290. <div class="fieldPair">
  291. <label class="fieldPair-label" for="userTaggerColor">Color</label>
  292.  
  293. <select id="userTaggerColor">
  294. <option style="color: inherit; background-color: none" value="none">none</option>
  295. <option style="color: black; background-color: aqua" value="aqua">aqua</option>
  296. <option style="color: white; background-color: black" value="black">black</option>
  297. <option style="color: white; background-color: blue" value="blue">blue</option>
  298. <option style="color: white; background-color: cornflowerblue" value="cornflowerblue">cornflowerblue</option>
  299. <option style="color: white; background-color: fuchsia" value="fuchsia">fuchsia</option>
  300. <option style="color: white; background-color: gray" value="gray">gray</option>
  301. <option style="color: white; background-color: green" value="green">green</option>
  302. <option style="color: black; background-color: lime" value="lime">lime</option>
  303. <option style="color: white; background-color: maroon" value="maroon">maroon</option>
  304. <option style="color: white; background-color: navy" value="navy">navy</option>
  305. <option style="color: white; background-color: olive" value="olive">olive</option>
  306. <option style="color: white; background-color: orange" value="orange">orange</option>
  307. <option style="color: white; background-color: orangered" value="orangered">orangered</option>
  308. <option style="color: black; background-color: pink" value="pink">pink</option>
  309. <option style="color: white; background-color: purple" value="purple">purple</option>
  310. <option style="color: white; background-color: red" value="red">red</option>
  311. <option style="color: black; background-color: silver" value="silver">silver</option>
  312. <option style="color: white; background-color: teal" value="teal">teal</option>
  313. <option style="color: black; background-color: white" value="white">white</option>
  314. <option style="color: black; background-color: yellow" value="yellow">yellow</option>
  315. </select>
  316. </div>
  317.  
  318. <div class="fieldPair">
  319. <label class="fieldPair-label" for="userTaggerPreview">Preview</label>
  320.  
  321. <span id="userTaggerPreview" style="color: white; background-color: olive;">
  322. <span class="RESUserTag">
  323. <a class="userTagLink hasTag truncateTag" style="background-color: olive; color: white !important;" title="Sexist" href="javascript:void 0">Sexist</a>
  324. </span>
  325. </span>
  326. </div>
  327. <a class="userTagLink hasTag truncateTag" style="background-color: olive; color: white !important;" title="Feminist" href="javascript:void 0">Feminist</a>
  328.  
  329. <div class="fieldPair res-usertag-ignore">
  330. <label class="fieldPair-label" for="userTaggerIgnore">Ignore</label>
  331.  
  332. <div id="userTaggerIgnoreContainer" class="toggleButton ">
  333. <span class="toggleThumb"></span>
  334. <div class="toggleLabel res-icon" data-enabled-text="" data-disabled-text=""></div>
  335. <input id="userTaggerIgnore" name="userTaggerIgnore" type="checkbox">
  336. </div>
  337.  
  338. <a class="gearIcon" href="#res:settings/userTagger/hardIgnore" title="RES Settings > User Tagger > hardIgnore"> configure </a>
  339. </div>
  340.  
  341. <div class="fieldPair">
  342. <label class="fieldPair-label" for="userTaggerLink">
  343. <span class="userTaggerOpenLink">
  344. <a title="open link" href="javascript:void 0">Source URL</a>
  345. </span>
  346. </label>
  347.  
  348. <input class="fieldPair-text" type="text" id="userTaggerLink" value="https://www.reddit.com/r/giantbomb/comments/7x251d/all_systems_goku_02/du5fpwr/">
  349. </div>
  350.  
  351. <div class="fieldPair">
  352. <label class="fieldPair-label" for="userTaggerVotesUp" title="Upvotes you have given this redditor">Upvotes</label>
  353.  
  354. <input type="number" style="width: 50px;" id="userTaggerVotesUp" value="0">
  355. </div>
  356.  
  357. <div class="fieldPair">
  358. <label class="fieldPair-label" for="userTaggerVotesDown" title="Downvotes you have given this redditor">Downvotes</label>
  359.  
  360. <input type="number" style="width: 50px;" id="userTaggerVotesDown" value="0">
  361. </div>
  362.  
  363. <div class="res-usertagger-footer">
  364. <a href="/r/dashboard#userTaggerContents" target="_blank" rel="noopener noreferer">View tagged users</a>
  365.  
  366. <input type="submit" id="userTaggerSave" value="✓ save tag">
  367. </div>
  368. </form>
  369. </div>
  370. </div>
  371.  
  372. */
  373.  
  374. })(jQuery)
  375.  
  376. jQuery.noConflict(true)