Cook'd and Bomb'd Ignore Topics

Ignore topics and forums, and other topic list tweaks

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Cook'd and Bomb'd Ignore Topics
// @description Ignore topics and forums, and other topic list tweaks
// @namespace   https://github.com/insin/greasemonkey/
// @version     11
// @match       https://www.cookdandbombd.co.uk/forums/index.php?board*
// @match       https://www.cookdandbombd.co.uk/forums/index.php?action=unread*
// @grant       GM.registerMenuCommand
// ==/UserScript==

const IGNORED_TOPICS_STORAGE = 'cab_ignoredTopics'
const IGNORED_FORUMS_STORAGE = 'cab_ignoredForums'

const TOPIC_ID_RE = /index\.php\?topic=(\d+)/
const FORUM_ID_RE = /index\.php\?board=(\d+)/

let topics = []

let ignoredTopicIds
let ignoredForumIds

let config = {
  hideRecentUnreadTopicsPageNumbers: true,
  // Set this to false if you're done hiding forums in Recent Unread Topics
  showIgnoreForumControl: true,
  showIgnoredTopics: false,
  topicLinksNewPost: true,
}

function loadIgnoreConfig() {
  let ignoredTopicsJson = localStorage[IGNORED_TOPICS_STORAGE]
  let ignoredForumsJson = localStorage[IGNORED_FORUMS_STORAGE]
  ignoredTopicIds = ignoredTopicsJson ? JSON.parse(ignoredTopicsJson) : []
  ignoredForumIds = ignoredForumsJson ? JSON.parse(ignoredForumsJson) : []
}

function toggleIgnoreTopic(id, topic) {
  if (!ignoredTopicIds.includes(id)) {
    ignoredTopicIds.unshift(id)
  }
  else {
    let index = ignoredTopicIds.indexOf(id)
    ignoredTopicIds.splice(index, 1)
  }
  localStorage[IGNORED_TOPICS_STORAGE] = JSON.stringify(ignoredTopicIds)
  topic.updateClassNames()
}

function toggleIgnoreForum(id) {
  if (!ignoredForumIds.includes(id)) {
    ignoredForumIds.unshift(id)
  }
  else {
    let index = ignoredForumIds.indexOf(id)
    ignoredForumIds.splice(index, 1)
  }
  localStorage[IGNORED_FORUMS_STORAGE] = JSON.stringify(ignoredForumIds)
  topics.forEach(topic => topic.updateClassNames())
}

function toggleShowIgnoredTopics(showIgnoredTopics) {
  config.showIgnoredTopics = showIgnoredTopics
  topics.forEach(topic => topic.updateClassNames())
}

function addStyle(css) {
  let $style = document.createElement('style')
  $style.appendChild(document.createTextNode(css))
  document.querySelector('head').appendChild($style)
}

function ForumPage() {
  function Topic($topicRow) {
    let $topicLink = $topicRow.querySelector('.info :is(.recent_title, .message_index_title) .preview a')
    // Only in Recent Unread Topics
    let $forumLink = $topicRow.querySelector('.floatleft em a')
    let $lastPostLink = $topicRow.querySelector('.lastpost a')

    let topicIdMatch = TOPIC_ID_RE.exec($lastPostLink.href)
    if (!topicIdMatch) {
      return null
    }
    let topicId = topicIdMatch[1]

    let forumId = null
    if ($forumLink) {
      let forumIdMatch = FORUM_ID_RE.exec($forumLink.href)
      if (forumIdMatch) {
        forumId = forumIdMatch[1]
      }
    }

    let api = {
      $el: $topicRow,
      isIgnored() {
        return ignoredTopicIds.includes(topicId) || (forumId ? ignoredForumIds.includes(forumId) : false)
      },
      updateClassNames() {
        let isTopicIgnored = ignoredTopicIds.includes(topicId)
        let isForumIgnored = forumId ? ignoredForumIds.includes(forumId) : false
        $topicRow.classList.toggle('cab_ignoredTopic', isTopicIgnored)
        $topicRow.classList.toggle('cab_ignoredForum', isForumIgnored)
        $topicRow.classList.toggle('cab_ignored', isTopicIgnored || isForumIgnored)
        $topicRow.classList.toggle('cab_show', config.showIgnoredTopics && (isTopicIgnored || isForumIgnored))
      }
    }

    $lastPostLink.insertAdjacentHTML('afterend', `
      <a href="#" class="cab_ignoreControl cab_ignoreTopic" title="Ignore topic">
        <span class="main_icons ignore"></span>
      </a>
    `)

    $topicRow.querySelector('a.cab_ignoreTopic').addEventListener('click', (e) => {
      e.preventDefault()
      toggleIgnoreTopic(topicId, api)
      reStripeTopics()
    })

    if (config.showIgnoreForumControl && forumId) {
      $forumLink.parentElement.insertAdjacentHTML('afterend', `
        <a href="#" class="cab_ignoreControl cab_ignoreForum" title="Ignore forum">
          <span class="main_icons ignore"></span>
        </a>
      `)
      $topicRow.querySelector('a.cab_ignoreForum').addEventListener('click', (e) => {
        e.preventDefault()
        toggleIgnoreForum(forumId)
        reStripeTopics()
      })
    }

    if (config.topicLinksNewPost) {
      let $newPostLink = $topicRow.querySelector('a[id^=newicon]')
      if ($newPostLink) {
        $topicLink.href = $newPostLink.href
      }
    }

    return api
  }

  /**
   * Add ignore controls to a topic and hide it if it's being ignored.
   */
  function processTopicRow($topicRow) {
    let topic = Topic($topicRow)
    if (topic == null) {
      return
    }
    topics.push(topic)
    topic.updateClassNames()
  }

  /**
   * Topics being hidden breaks the CSS nth-of-type striping.
   */
  function reStripeTopics() {
    let odd = true
    topics.forEach(topic => {
      if (!topic.isIgnored()) {
        topic.$el.classList.toggle('odd', odd)
        topic.$el.classList.toggle('even', !odd)
        odd = !odd
      } else {
        topic.$el.classList.remove('odd')
        topic.$el.classList.remove('even')
      }
    })
  }

  let topicElements = Array.from(document.querySelectorAll('#topic_container > div'))
  let oddBg = topicElements[0] ? getComputedStyle(topicElements[0]).backgroundColor : null
  let evenBg = topicElements[1] ? getComputedStyle(topicElements[1]).backgroundColor : null
  let isRecentUnreadTopicsPage = location.search.includes('action=unread')

  addStyle(`
    .cab_ignoreControl {
      visibility: hidden;
    }
    #topic_container .windowbg.cab_ignored {
      display: none;
    }
    #topic_container .windowbg.cab_ignored.cab_show {
      display: flex;
    }
    ${oddBg ? `#topic_container .windowbg.odd {
      background-color: ${oddBg};
    }` : ''}
    ${evenBg ? `#topic_container .windowbg.even {
      background-color: ${evenBg};
    }` : ''}
    #topic_container .windowbg.cab_ignored.cab_show {
      background-color: #ddd !important;
    }
    #topic_container > div:hover .cab_ignoreControl {
      visibility: visible;
    }
    .cab_ignoredForum .cab_ignoreTopic {
      display: none;
    }
    .cab_ignoredTopic .cab_ignoreForum {
      display: none;
    }
    .cab_ignoredTopic.cab_ignoredForum .cab_ignoreForum {
      display: inline;
    }
    ${isRecentUnreadTopicsPage && config.hideRecentUnreadTopicsPageNumbers ? '.topic_pages { display: none; }' : ''}
  `)

  topicElements.forEach(processTopicRow)
  reStripeTopics()
}

// Already-processed pages seem to be getting cached on back navigation... sometimes
if (!document.querySelector('a.cab_ignoreTopic')) {
  if (typeof GM != 'undefined') {
    loadIgnoreConfig()
    ForumPage()
    GM.registerMenuCommand('Toggle ignored topic display', () => {
      toggleShowIgnoredTopics(!config.showIgnoredTopics)
    })
  }
  else {
    chrome.storage.local.get((storedConfig) => {
      Object.assign(config, storedConfig)
      loadIgnoreConfig()
      ForumPage()
    })
    chrome.storage.onChanged.addListener((changes) => {
      if ('showIgnoredTopics' in changes) {
        toggleShowIgnoredTopics(changes['showIgnoredTopics'].newValue)
      }
    })
  }
}