Rllmuk Really Ignore Users

Really ignore ignored users, and ignore users in specific topics

目前为 2021-07-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Rllmuk Really Ignore Users
  3. // @description Really ignore ignored users, and ignore users in specific topics
  4. // @namespace https://github.com/insin/greasemonkey/
  5. // @version 10
  6. // @match https://rllmukforum.com/index.php*
  7. // @match https://www.rllmukforum.com/index.php*
  8. // ==/UserScript==
  9. function addStyle(css) {
  10. let $style = document.createElement('style')
  11. $style.appendChild(document.createTextNode(css))
  12. document.querySelector('head').appendChild($style)
  13. }
  14.  
  15. const USER_LINK_ID_RE = /profile\/(\d+)/
  16.  
  17. function TopicPage() {
  18. let topicId = document.body.dataset.pageid
  19. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  20. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  21. let topicIgnoredUserIds = []
  22. if (ignoredUsersInTopics[topicId]) {
  23. topicIgnoredUserIds = ignoredUsersInTopics[topicId].users.map(user => user.id)
  24. ignoredUserIds.push(...topicIgnoredUserIds)
  25. }
  26.  
  27. // Hide "You've chosen to ignore content by <ignored user>"
  28. addStyle(`
  29. .ipsComment_ignored {
  30. display: none;
  31. }
  32. `)
  33.  
  34. // Hide posts containing elements which have an ignored user id as a specified
  35. // data attribute.
  36. function hidePostsByDataAttribute(elements, dataAttribute) {
  37. elements.forEach(el => {
  38. if (!ignoredUserIds.includes(el.dataset[dataAttribute])) return
  39. let post = el.closest('article.ipsComment')
  40. if (post.style.display == 'none') return
  41. post.style.display = 'none'
  42. })
  43. }
  44.  
  45. // Hide posts which quote ignored users
  46. function processQuotes(context) {
  47. hidePostsByDataAttribute(
  48. context.querySelectorAll('[data-ipsquote-userid]'),
  49. 'ipsquoteUserid'
  50. )
  51. }
  52.  
  53. // Hide posts which @-mention ignored users
  54. function processMentions(context) {
  55. hidePostsByDataAttribute(
  56. context.querySelectorAll('[data-mentionid]'),
  57. 'mentionid'
  58. )
  59. }
  60.  
  61. // Hide posts by users ignored in this specific topic
  62. function processTopicIgnoredPosts(context = document) {
  63. if (topicIgnoredUserIds.length == 0) return
  64.  
  65. let postAvatarLinks = context.querySelectorAll('li.cAuthorPane_photo a')
  66. postAvatarLinks.forEach(el => {
  67. let userId = USER_LINK_ID_RE.exec(el.href)[1]
  68. if (!topicIgnoredUserIds.includes(userId)) return
  69. let post = el.closest('article.ipsComment')
  70. if (post.style.display == 'none') return
  71. post.style.display = 'none'
  72. })
  73. }
  74.  
  75. // Hide the unread comment separator if all subsequent posts are hidden
  76. function updateUnreadCommentSeparator() {
  77. let separator = document.querySelector('div.ipsUnreadBar')
  78. if (!separator) return
  79. let hasVisiblePost = false
  80. let sibling = separator.nextElementSibling
  81. while (sibling) {
  82. if (sibling.matches('article.ipsComment') &&
  83. !sibling.classList.contains('ipsHide') &&
  84. sibling.style.display != 'none') {
  85. hasVisiblePost = true
  86. break
  87. }
  88. sibling = sibling.nextElementSibling
  89. }
  90. separator.style.display = hasVisiblePost ? '' : 'none'
  91. }
  92.  
  93. // Process all posts on the current page
  94. function processPosts(context = document) {
  95. processQuotes(context)
  96. processMentions(context)
  97. processTopicIgnoredPosts(context)
  98. }
  99.  
  100. // Process initial posts
  101. processPosts()
  102. updateUnreadCommentSeparator()
  103.  
  104. // Add a new button to a user's hover card to ignore them in this topic
  105. function processHoverCard($el) {
  106. if (!$el.classList.contains('ipsHovercard')) return
  107.  
  108. // Create a new "Ignore In This Topic" button
  109. let $topicIgnore = document.createElement('div')
  110. $topicIgnore.className = 'ipsList_reset ipsFlex ipsFlex-ai:center ipsGap:3 ipsGap_row:0'
  111. $topicIgnore.style.marginTop = '12px'
  112. $topicIgnore.innerHTML = `<a href="#" class="ipsFlex-flex:11 ipsButton ipsButton_light ipsButton_verySmall">
  113. Ignore In This Topic
  114. </a>`
  115. let $ignoreLink = $topicIgnore.querySelector('a')
  116. $ignoreLink.addEventListener('click', (e) => {
  117. e.preventDefault()
  118.  
  119. let topicName = document.querySelector('.ipsType_pageTitle').innerText
  120. let user = {
  121. id: USER_LINK_ID_RE.exec($el.querySelector('a').href)[1],
  122. name: $el.querySelector('h2').innerText,
  123. avatar: $el.querySelector('img.ipsUserPhoto').src,
  124. }
  125.  
  126. // Add the user to the ignored users config for this topic
  127. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  128. if (ignoredUsersInTopics[topicId] == undefined) {
  129. ignoredUsersInTopics[topicId] = {
  130. name: topicName,
  131. users: [],
  132. }
  133. }
  134. ignoredUsersInTopics[topicId].name = topicName
  135. ignoredUsersInTopics[topicId].users.push(user)
  136. localStorage.ignoredUsersInTopics = JSON.stringify(ignoredUsersInTopics)
  137.  
  138. // Apply the new ignored user settings
  139. ignoredUserIds.push(user.id)
  140. topicIgnoredUserIds.push(user.id)
  141. processPosts()
  142. updateUnreadCommentSeparator()
  143.  
  144. // Hide the hover card
  145. $el.style.display = 'none'
  146. })
  147.  
  148. // Insert the new control into the hover card
  149. let $hoverCardButtons = $el.querySelector('.ipsList_reset')
  150. $hoverCardButtons.insertAdjacentElement('afterend', $topicIgnore)
  151. }
  152.  
  153. // Watch for posts being replaced when paging
  154. new MutationObserver(mutations =>
  155. mutations.forEach(mutation => {
  156. if (mutation.oldValue == 'true') {
  157. processPosts()
  158. updateUnreadCommentSeparator()
  159. }
  160. })
  161. ).observe(document.querySelector('div.cTopic'), {
  162. attributes: true,
  163. attributeFilter: ['animating'],
  164. attributeOldValue: true,
  165. })
  166.  
  167. // Watch for new posts being loaded into the current page
  168. new MutationObserver(mutations => {
  169. mutations.forEach(mutation =>
  170. mutation.addedNodes.forEach(processPosts)
  171. )
  172. updateUnreadCommentSeparator()
  173. }).observe(document.querySelector('#elPostFeed > form'), {
  174. childList: true,
  175. })
  176.  
  177. // Watch for user hover cards being added for display
  178. new MutationObserver(mutations => {
  179. mutations.forEach(mutation =>
  180. mutation.addedNodes.forEach(processHoverCard)
  181. )
  182. }).observe(document.body, {
  183. childList: true,
  184. })
  185. }
  186.  
  187. function IgnoredUsersPage() {
  188. // Sync ignored user ids
  189. localStorage.ignoredUserIds = JSON.stringify(
  190. Array.from(document.querySelectorAll('[data-ignoreuserid]')).map(el =>
  191. el.dataset.ignoreuserid
  192. )
  193. )
  194.  
  195. // Add a new section to manage users ignored in specific topics
  196. let $mainArea = document.querySelector('#ipsLayout_mainArea')
  197. $mainArea.appendChild(document.createElement('br'))
  198. let $div = document.createElement('div')
  199. $div.className = 'ipsBox'
  200.  
  201. function populateIgnoredUsersInTopics() {
  202. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  203.  
  204. $div.innerHTML = `
  205. <h2 class="ipsType_sectionTitle ipsType_reset ipsClear">Users currently being ignored in specific topics</h2>
  206. <ol class="ipsDataList ipsGrid ipsGrid_collapsePhone ipsClear" data-role="tableRows">
  207. </ol>`
  208. let $ol = $div.querySelector('ol')
  209.  
  210. if (Object.keys(ignoredUsersInTopics).length == 0) {
  211. $ol.innerHTML = `<li class="ipsDataItem">
  212. <div class="ipsType_light ipsType_center ipsPad">
  213. <br>
  214. <br>
  215. You're not currently ignoring any users in specific topics.
  216. </div>
  217. </li>`
  218. }
  219.  
  220. for (let [topicId, topicConfig] of Object.entries(ignoredUsersInTopics)) {
  221. for (let user of topicConfig.users) {
  222. let $li = document.createElement('li')
  223. $li.className = 'ipsDataItem ipsGrid_span6 ipsFaded_withHover'
  224. $li.innerHTML = `
  225. <p class="ipsType_reset ipsDataItem_icon">
  226. <a href="https://${location.host}/index.php?/profile/${user.id}" class="ipsUserPhoto ipsUserPhoto_tiny">
  227. <img src="${user.avatar}" alt="${user.name}">
  228. </a>
  229. </p>
  230. <div class="ipsDataItem_main">
  231. <h4 class="ipsDataItem_title"><strong>${user.name}</strong></h4>
  232. <ul class="ipsList_inline">
  233. <li class="ipsType_light">
  234. in <a href="https://${location.host}/index.php?/topic/${topicId}">
  235. ${topicConfig.name}
  236. </a>
  237. </li>
  238. <li>
  239. <a href="#" class="unignore ipsPos_middle ipsType_blendLinks">
  240. <i class="fa fa-times-circle"></i> Stop ignoring
  241. </a>
  242. </li>
  243. </ul>
  244. </div>`
  245. $li.querySelector('a.unignore').addEventListener('click', (e) => {
  246. e.preventDefault()
  247. let ignoredUsersInTopics = JSON.parse(localStorage.ignoredUsersInTopics || '{}')
  248. if (!ignoredUsersInTopics[topicId]) return populateIgnoredUsersInTopics()
  249. let index = ignoredUsersInTopics[topicId].users.findIndex(u => u.id == user.id)
  250. if (index == -1) return populateIgnoredUsersInTopics()
  251. ignoredUsersInTopics[topicId].users.splice(index, 1)
  252. if (ignoredUsersInTopics[topicId].users.length == 0) {
  253. delete ignoredUsersInTopics[topicId]
  254. }
  255. localStorage.ignoredUsersInTopics = JSON.stringify(ignoredUsersInTopics)
  256. populateIgnoredUsersInTopics()
  257. })
  258. $ol.appendChild($li)
  259. }
  260. }
  261.  
  262. $mainArea.appendChild($div)
  263. }
  264.  
  265. populateIgnoredUsersInTopics()
  266. }
  267.  
  268. function UnreadContentPage() {
  269. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  270. let view
  271.  
  272. function getView() {
  273. let $activeViewButton = document.querySelector('a.ipsButton_primary[data-action="switchView"]')
  274. return $activeViewButton ? $activeViewButton.textContent.trim() : null
  275. }
  276.  
  277. function processTopic($topic) {
  278. let $user = Array.from($topic.querySelectorAll('.ipsStreamItem_status a[href*="/profile/"]')).pop()
  279. if (!$user) return
  280. let userId = USER_LINK_ID_RE.exec($user.href)[1]
  281. if (ignoredUserIds.includes(userId)) {
  282. $topic.remove()
  283. }
  284. }
  285.  
  286. /**
  287. * Process topics within a topic container and watch for a new topic container being added.
  288. * When you click "Load more activity", a new <div> is added to the end of the topic container.
  289. */
  290. function processTopicContainer($el) {
  291. Array.from($el.querySelectorAll(':scope > li.ipsStreamItem'), processTopic)
  292.  
  293. new MutationObserver((mutations) => {
  294. mutations.forEach((mutation) => {
  295. if (view != getView()) {
  296. processView()
  297. }
  298. else if (mutation.addedNodes[0].tagName === 'DIV') {
  299. processTopicContainer(mutation.addedNodes[0])
  300. }
  301. })
  302. }).observe($el, {childList: true})
  303. }
  304.  
  305. /**
  306. * Process topics when the view changes between Condensed and Expanded.
  307. */
  308. function processView() {
  309. view = getView()
  310. processTopicContainer(document.querySelector('ol.ipsStream'))
  311. }
  312.  
  313. processView()
  314. }
  315.  
  316. function ForumPage() {
  317. let ignoredUserIds = JSON.parse(localStorage.ignoredUserIds || '[]')
  318.  
  319. function processTopic($topic) {
  320. let $user = $topic.querySelector('.ipsDataItem_meta a')
  321. if (!$user) return
  322. let userId = USER_LINK_ID_RE.exec($user.href)[1]
  323. if (ignoredUserIds.includes(userId)) {
  324. $topic.remove()
  325. }
  326. }
  327.  
  328. // Initial list of topics
  329. Array.from(document.querySelectorAll('ol.cTopicList > li.ipsDataItem[data-rowid]'), processTopic)
  330.  
  331. // Watch for topics being replaced when paging
  332. new MutationObserver(mutations =>
  333. mutations.forEach(mutation =>
  334. Array.from(mutation.addedNodes).filter(node => node.nodeType === Node.ELEMENT_NODE).map(processTopic)
  335. )
  336. ).observe(document.querySelector('ol.cTopicList'), {childList: true})
  337. }
  338.  
  339. let page
  340. if (location.href.includes('index.php?/topic/')) {
  341. page = TopicPage
  342. }
  343. else if (location.href.includes('index.php?/ignore/')) {
  344. page = IgnoredUsersPage
  345. }
  346. else if (location.href.includes('index.php?/discover/unread')) {
  347. page = UnreadContentPage
  348. }
  349. else if (location.href.includes('index.php?/forum/')) {
  350. page = ForumPage
  351. }
  352.  
  353. if (page) {
  354. page()
  355. }