LINUX.DO Load More Topics Manually

Load more topics manually with enhanced UI and error handling.

Tính đến 14-08-2025. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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')
})()