LINUX.DO Load More Topics Manually

Load more topics manually with enhanced UI and error handling.

Versione datata 14/08/2025. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name                 LINUX.DO Load More Topics Manually
// @name:zh-CN           LINUX.DO 手动加载更多话题
// @namespace            https://www.pipecraft.net/
// @homepageURL          https://github.com/utags/userscripts#readme
// @supportURL           https://github.com/utags/userscripts/issues
// @version              0.1.0
// @description          Load more topics manually with enhanced UI and error handling.
// @description:zh-CN    手动加载更多话题,具有增强的用户界面和错误处理。
// @author               Pipecraft
// @license              MIT
// @match                https://linux.do/*
// @icon                 https://www.google.com/s2/favicons?sz=64&domain=linux.do
// @grant                none
// ==/UserScript==

;(function () {
  'use strict'

  // Configuration constants
  const CONFIG = {
    BUTTON_ID: 'userscript-load-more-button',
    SENTINEL_SELECTOR: '.load-more-sentinel',
    LOAD_TIMEOUT: 1000,
    OBSERVER_DELAY: 100,
    DEBUG: false,
  }

  let isLoading = false

  /**
   * Log debug messages if debug mode is enabled
   * @param {string} message - The message to log
   * @param {any} data - Optional data to log
   */
  function debugLog(message, data = null) {
    if (CONFIG.DEBUG) {
      console.log(`[Load More Manually] ${message}`, data || '')
    }
  }

  /**
   * Create and style the load more button
   * @returns {HTMLButtonElement} The created button element
   */
  function createLoadMoreButton() {
    const button = document.createElement('button')
    button.id = CONFIG.BUTTON_ID
    button.textContent = 'Load More'
    button.className = 'userscript-load-more-btn'

    // Apply modern button styles
    Object.assign(button.style, {
      width: '100%',
      margin: '16px 0',
      padding: '12px 24px',
      border: 'none',
      borderRadius: '8px',
      backgroundColor: '#007bff',
      color: '#ffffff',
      fontSize: '14px',
      fontWeight: '500',
      cursor: 'pointer',
      transition: 'all 0.2s ease',
      boxShadow: '0 2px 4px rgba(0, 123, 255, 0.2)',
    })

    // Add hover and active states
    button.addEventListener('mouseenter', () => {
      if (!isLoading) {
        button.style.backgroundColor = '#0056b3'
        button.style.transform = 'translateY(-1px)'
        button.style.boxShadow = '0 4px 8px rgba(0, 123, 255, 0.3)'
      }
    })

    button.addEventListener('mouseleave', () => {
      if (!isLoading) {
        button.style.backgroundColor = '#007bff'
        button.style.transform = 'translateY(0)'
        button.style.boxShadow = '0 2px 4px rgba(0, 123, 255, 0.2)'
      }
    })

    return button
  }

  /**
   * Update button state during loading
   * @param {HTMLButtonElement} button - The button element
   * @param {boolean} loading - Whether the button is in loading state
   */
  function updateButtonState(button, loading) {
    isLoading = loading

    if (loading) {
      button.textContent = 'Loading...'
      button.style.backgroundColor = '#6c757d'
      button.style.cursor = 'not-allowed'
      button.style.transform = 'translateY(0)'
      button.disabled = true
    } else {
      button.textContent = 'Load More'
      button.style.backgroundColor = '#007bff'
      button.style.cursor = 'pointer'
      button.disabled = false
    }
  }

  /**
   * Handle the load more button click event
   * @param {HTMLElement} sentinel - The load more sentinel element
   * @param {HTMLButtonElement} button - The button element
   */
  function handleLoadMore(sentinel, button) {
    if (isLoading) {
      debugLog('Already loading, ignoring click')
      return
    }

    debugLog('Load more button clicked')
    updateButtonState(button, true)

    try {
      // Show the sentinel to trigger loading
      sentinel.style.display = 'block'

      // Hide the sentinel after timeout and reset button state
      setTimeout(() => {
        sentinel.style.display = 'none'
        updateButtonState(button, false)
        debugLog('Load more completed')
      }, CONFIG.LOAD_TIMEOUT)
    } catch (error) {
      debugLog('Error during load more:', error)
      updateButtonState(button, false)
    }
  }

  /**
   * Insert the button in the appropriate position relative to the sentinel
   * @param {HTMLElement} sentinel - The load more sentinel element
   * @param {HTMLButtonElement} button - The button element to insert
   */
  function insertButton(sentinel, button) {
    try {
      const parent = sentinel.parentNode
      if (!parent) {
        debugLog('Sentinel has no parent node')
        return false
      }

      if (sentinel.nextSibling) {
        parent.insertBefore(button, sentinel.nextSibling)
      } else {
        parent.appendChild(button)
      }

      debugLog('Button inserted successfully')
      return true
    } catch (error) {
      debugLog('Error inserting button:', error)
      return false
    }
  }

  /**
   * Initialize the load more functionality
   */
  function initLoadMore() {
    try {
      const sentinel = document.querySelector(CONFIG.SENTINEL_SELECTOR)
      if (!sentinel) {
        debugLog('Load more sentinel not found')
        return
      }

      // Hide the sentinel by default
      sentinel.style.display = 'none'

      // Check if button already exists
      const existingButton = document.querySelector(`#${CONFIG.BUTTON_ID}`)
      if (existingButton) {
        debugLog('Button already exists')
        return
      }

      // Create and configure the button
      const button = createLoadMoreButton()
      button.addEventListener('click', () => handleLoadMore(sentinel, button))

      // Insert the button
      if (insertButton(sentinel, button)) {
        debugLog('Load more functionality initialized')
      }
    } catch (error) {
      debugLog('Error initializing load more:', error)
    }
  }

  /**
   * Check if the mutation should trigger an update
   * @param {NodeList} addedNodes - The added nodes from mutation
   * @returns {boolean} Whether an update should be triggered
   */
  function shouldUpdateOnMutation(addedNodes) {
    if (!addedNodes || addedNodes.length === 0) {
      return false
    }

    // Check if any added node contains the sentinel or is the sentinel itself
    for (const node of addedNodes) {
      if (node.nodeType === Node.ELEMENT_NODE) {
        if (node.matches && node.matches(CONFIG.SENTINEL_SELECTOR)) {
          return true
        }
        if (
          node.querySelector &&
          node.querySelector(CONFIG.SENTINEL_SELECTOR)
        ) {
          return true
        }
      }
    }

    return false
  }

  // Initialize mutation observer to watch for DOM changes
  const observer = new MutationObserver((mutationsList) => {
    let shouldUpdate = false

    for (const mutation of mutationsList) {
      if (shouldUpdateOnMutation(mutation.addedNodes)) {
        shouldUpdate = true
        break
      }
    }

    if (shouldUpdate) {
      debugLog('DOM mutation detected, reinitializing')
      setTimeout(initLoadMore, CONFIG.OBSERVER_DELAY)
    }
  })

  // Start observing DOM changes
  observer.observe(document, {
    childList: true,
    subtree: true,
  })

  // Initialize on page load
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initLoadMore)
  } else {
    initLoadMore()
  }

  debugLog('Script initialized')
})()