Rllmuk Topic Ignore List (Invision 4)

Hide topics you're not interested in

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name        Rllmuk Topic Ignore List (Invision 4)
// @description Hide topics you're not interested in
// @namespace   https://github.com/insin/greasemonkey/
// @version     13
// @match       https://rllmukforum.com/index.php*
// @match       https://www.rllmukforum.com/index.php*
// @grant       GM.registerMenuCommand
// ==/UserScript==

/**
 * @typedef {{updateClassNames(): void}} Topic
 */

/**
 * @typedef {{id: string}} IgnoredItem
 */

const IGNORED_TOPICS_STORAGE = 'rit_ignoredTopics'
const IGNORED_FORUMS_STORAGE = 'rit_ignoredForums'

/** @type {Topic[]} */
let topics = []
/** @type {IgnoredItem[]} */
let ignoredTopics
/** @type {string[]} */
let ignoredTopicIds
/** @type {IgnoredItem[]} */
let ignoredForums
/** @type {string[]} */
let ignoredForumIds

/** @type {import("./types").Config} */
let config = {
  hideFluidSidebar: false,
  showIgnoredTopics: false,
}

function isFluidForumPage() {
  return (
    location.href.includes('index.php?forumId=') ||
    (location.href.endsWith('index.php') && document.querySelector('a.ipsButton_primary[href*="setMethod&method=fluid"]') != null)
  )
}

function loadIgnoreConfig() {
  ignoredTopics = JSON.parse(localStorage[IGNORED_TOPICS_STORAGE] || '[]')
  ignoredTopicIds = ignoredTopics.map(topic => topic.id)
  ignoredForums = JSON.parse(localStorage[IGNORED_FORUMS_STORAGE] || '[]')
  ignoredForumIds = ignoredForums.map(forum => forum.id)
}

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

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

/**
 * @param {boolean} showIgnoredTopics
 */
function toggleShowIgnoredTopics(showIgnoredTopics) {
  config.showIgnoredTopics = showIgnoredTopics
  topics.forEach(topic => topic.updateClassNames())
}

/**
 * @param {string} css
 */
function addStyle(css) {
  let $style = document.createElement('style')
  $style.appendChild(document.createTextNode(css))
  document.querySelector('head').appendChild($style)
}

function UnreadContentPage() {
  const TOPIC_LINK_ID_RE = /index\.php\?\/topic\/(\d+)/
  const FORUM_LINK_ID_RE = /index\.php\?(?:\/forum\/|forumId=)(\d+)/

  /** @type {string} */
  let view

  addStyle(`
    .rit_ignoreControl {
      visibility: hidden;
    }
    .rit_ignored {
      display: none;
    }
    .rit_ignored.rit_show {
      display: block;
      background-color: #fee;
    }
    .rit_ignored.rit_show::after {
      border-color: transparent #fee transparent transparent !important;
    }
    li.ipsStreamItem:hover .rit_ignoreControl {
      visibility: visible;
    }
    .rit_ignoreForumControl {
      opacity: 0.5;
    }
    .rit_ignoreForumControl:hover {
      opacity: 1;
    }
    .rit_ignoredForum .rit_ignoreTopicControl {
      display: none;
    }
    .rit_ignoredTopic .rit_ignoreForumControl {
      display: none;
    }
    .rit_ignoredTopic.rit_ignoredForum .rit_ignoreForumControl {
      display: inline;
    }
  `)

  function getView() {
    let $activeViewButton = document.querySelector('a.ipsButton_primary[data-action="switchView"]')
    return $activeViewButton ? $activeViewButton.textContent.trim() : null
  }

  /**
   * @param {HTMLElement} $topic
   * @returns {Topic}
   */
  function Topic($topic) {
    let $topicLink = /** @type {HTMLAnchorElement} */ ($topic.querySelector('a[href*="index.php?/topic/"][data-linktype="link"]'))
    let $forumLink = /** @type {HTMLAnchorElement} */ ($topic.querySelector('a[href*="index.php?/forum/"], a[href*="index.php?forumId"]'))
    if (!$topicLink) {
      return null
    }

    let topicId = TOPIC_LINK_ID_RE.exec($topicLink.href)[1]
    let forumId = FORUM_LINK_ID_RE.exec($forumLink.href)[1]

    let api = {
      updateClassNames() {
        let isTopicIgnored = ignoredTopicIds.includes(topicId)
        let isForumIgnored = ignoredForumIds.includes(forumId)
        $topic.classList.toggle('rit_ignoredTopic', isTopicIgnored)
        $topic.classList.toggle('rit_ignoredForum', isForumIgnored)
        $topic.classList.toggle('rit_ignored', isTopicIgnored || isForumIgnored)
        $topic.classList.toggle('rit_show', config.showIgnoredTopics && (isTopicIgnored || isForumIgnored))
      }
    }

    let $ignoreTopicContainer
    if (view == 'Condensed') {
      $ignoreTopicContainer = $topic.querySelector('ul.ipsStreamItem_stats')
      $ignoreTopicContainer.insertAdjacentHTML('beforeend', `
        <li class="rit_ignoreControl rit_ignoreTopicControl">
          <a style="cursor: pointer"><i class="fa fa-trash"></i></a>
        </li>
      `)
    }
    else {
      $ignoreTopicContainer = $topicLink.parentElement
      $ignoreTopicContainer.insertAdjacentHTML('beforeend', `
        <a style="cursor: pointer"class="rit_ignoreControl rit_ignoreTopicControl">
          <i class="fa fa-trash"></i>
        </a>
      `)
    }
    $ignoreTopicContainer.querySelector('i.fa-trash').addEventListener('click', () => {
      toggleIgnoreTopic(topicId, api)
    })

    $forumLink.parentElement.insertAdjacentHTML('beforeend', `
      <a style="cursor: pointer" class="rit_ignoreControl rit_ignoreForumControl"><i class="fa fa-trash"></i></a>
    `)
    $forumLink.parentElement.querySelector('i.fa-trash').addEventListener('click', () => {
      toggleIgnoreForum(forumId)
    })

    if (config.topicLinksLatestPost && !$topicLink.href.endsWith('&do=getNewComment')) {
      $topicLink.href += '&do=getNewComment'
    }

    return api
  }

  /**
   * Add ignore controls to a topic and hide it if it's in the ignored list.
   * @param {HTMLElement} $topic
   */
  function processTopic($topic) {
    let topic = Topic($topic)
    if (topic == null) {
      return
    }
    topics.push(topic)
    topic.updateClassNames()
  }

  /**
   * Process topics within a topic container and watch for a new topic container being added.
   * When you click "Load more activity", a new <div> is added to the end of the topic container.
   * @param {HTMLElement} $el
   */
  function processTopicContainer($el) {
    Array.from($el.querySelectorAll(':scope > li.ipsStreamItem'), processTopic)

    new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (view != getView()) {
          processView()
        }
        else if (mutation.addedNodes[0] instanceof HTMLElement &&
                 mutation.addedNodes[0].tagName === 'DIV') {
          processTopicContainer(mutation.addedNodes[0])
        }
      })
    }).observe($el, {childList: true})
  }

  /**
   * Reset handling of topics when the view changes between Condensed and Expanded.
   */
  function processView() {
    topics = []
    view = getView()
    processTopicContainer(document.querySelector('ol.ipsStream'))
  }

  processView()
}

function ForumPage() {
  let isFluid = isFluidForumPage()

  addStyle(`
    .rit_ignoreControl {
      display: table-cell;
      min-width: 24px;
      vertical-align: middle;
      visibility: hidden;
    }
    .rit_ignored {
      display: none;
    }
    body.rit_hideFluidSidebar #ipsLayout_sidebar {
      display: none;
    }
    .rit_ignored.rit_show {
      display: block;
      background-color: #fee !important;
    }
    @media screen and (max-width:979px) {
      .rit_ignoreControl {
        position: absolute;
        ${isFluid ? 'right' : 'left'}: 12px;
        ${isFluid ? 'top: 50%;' : 'bottom: 16px;'}
      }
      .rit_toggleFluidToolItem {
        display: none;
      }
    }
    li.ipsDataItem:hover .rit_ignoreControl {
      visibility: visible;
    }
  `)

  /**
   * @param {HTMLElement} $topic
   * @returns {Topic}
   */
  function Topic($topic) {
    let topicId = $topic.dataset.rowid
    if (!topicId) {
      return null
    }

    let api = {
      updateClassNames() {
        let isTopicIgnored = ignoredTopicIds.includes(topicId)
        $topic.classList.toggle('rit_ignored', isTopicIgnored)
        $topic.classList.toggle('rit_show', config.showIgnoredTopics && isTopicIgnored)
      }
    }

    $topic.insertAdjacentHTML('beforeend', `
      <div class="rit_ignoreControl ipsType_light ipsType_blendLinks">
        <a style="cursor: pointer"><i class="fa fa-trash"></i></a>
      <div>
    `)

    $topic.querySelector('i.fa-trash').addEventListener('click', () => {
      toggleIgnoreTopic(topicId, api)
    })

    return api
  }

  /**
   * Add ignore controls to a topic and hide it if it's in the ignored list.
   * @param {HTMLElement} $topic
   */
  function processTopic($topic) {
    let topic = Topic($topic)
    if (topic == null) {
      return
    }
    topics.push(topic)
    topic.updateClassNames()
  }

  if (isFluid) {
    let $toolList = document.querySelector('.ipsPageHeader .ipsToolList')
    $toolList.insertAdjacentHTML('afterbegin', `
      <li class="rit_toggleFluidToolItem">
        <ul class="ipsButton_split">
          <li>
            <a class="rit_toggleFluidButton ipsButton ipsButton_narrow ipsButton_medium" href="#toggleFluidSidebar">
              <i class="fa fa-chevron-down"></i>
            </a>
          </li>
        </ul>
      </li>
    `)

    let $toggleFluidControl = /** @type {HTMLAnchorElement} */ ($toolList.querySelector('.rit_toggleFluidButton'))
    let $toggleFluidIcon = $toggleFluidControl.firstElementChild

    function applyHideFluidSidebarConfig() {
      document.body.classList.toggle('rit_hideFluidSidebar', config.hideFluidSidebar)
      $toggleFluidIcon.classList.toggle('fa-chevron-down', !config.hideFluidSidebar)
      $toggleFluidIcon.classList.toggle('fa-chevron-left', config.hideFluidSidebar)
      $toggleFluidControl.title = `${config.hideFluidSidebar ? 'Show' : 'Hide'} sidebar`
    }

    $toggleFluidControl.addEventListener('click', (e) => {
      e.preventDefault()
      config.hideFluidSidebar = !config.hideFluidSidebar
      applyHideFluidSidebarConfig()
      if (typeof GM != 'undefined') {
        localStorage.rit_config = JSON.stringify(config)
      }
      else {
        chrome.storage.local.set({hideFluidSidebar: config.hideFluidSidebar})
      }
    })

    applyHideFluidSidebarConfig()
  }

  // Initial list of topics
  Array.from(document.querySelectorAll('ol.cTopicList > li.ipsDataItem[data-rowid]'), processTopic)

  // Watch for topics being replaced when paging
  new MutationObserver(mutations =>
    mutations.forEach(mutation =>
      Array.from(mutation.addedNodes).filter(node => node.nodeType === Node.ELEMENT_NODE).map(processTopic)
    )
  ).observe(document.querySelector('ol.cTopicList'), {childList: true})
}

let page
if (location.href.includes('index.php?/discover/unread')) {
  page = UnreadContentPage
}
else if (location.href.includes('index.php?/forum/') || isFluidForumPage()) {
  page = ForumPage
}

if (page) {
  if (typeof GM != 'undefined') {
    Object.assign(config, JSON.parse(localStorage.rit_config || '{}'))
    loadIgnoreConfig()
    page()
    GM.registerMenuCommand('Toggle Ignored Topic Display', () => {
      toggleShowIgnoredTopics(!config.showIgnoredTopics)
      localStorage.rit_config = JSON.stringify(config)
    })
  }
  else {
    chrome.storage.local.get((storedConfig) => {
      Object.assign(config, storedConfig)
      loadIgnoreConfig()
      page()
    })

    chrome.storage.onChanged.addListener((changes) => {
      if ('showIgnoredTopics' in changes) {
        toggleShowIgnoredTopics(changes['showIgnoredTopics'].newValue)
      }
    })
  }
}