mebuki-catalog-text-popup

めぶきちゃんねるのカタログでスレ画像にマウスオーバーでスレ本文をポップアップ表示します

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         mebuki-catalog-text-popup
// @namespace    https://mebuki.moe/
// @version      0.1.2
// @description  めぶきちゃんねるのカタログでスレ画像にマウスオーバーでスレ本文をポップアップ表示します
// @author       ame-chan
// @match        https://mebuki.moe/app*
// @license      MIT
// @run-at       document-idle
// @require      https://update.greasyfork.org/scripts/552225/1688437/mebuki-page-state.js
// ==/UserScript==
(() => {
  'use strict';
  if (typeof window.USER_SCRIPT_MEBUKI_STATE === 'undefined') {
    return;
  }
  const CATALOG_LI_SELECTOR = '.catalog-container .catalog-item';
  const { subscribe, getState } = window.USER_SCRIPT_MEBUKI_STATE;
  const userjsStyle = `
    <style id="userjs-titlePopup-style">
    .userjs-titlePopup {
      position: absolute;
      padding: 8px 12px;
      color: inherit;
      font-size: 14px;
      background-color: var(--background, #333);
      border: 1px solid var(--border, #404040);
      border-radius: 4px;
      box-shadow: 0 2px 4px #333;
      z-index: 9999;
      opacity: 0;
      transition: opacity 0.2s;
      pointer-events: none;
    }
    </style>
  `;
  let observer;
  const createCatalogTitle = (catalogListElms) => {
    const popupWrapper = document.createElement('div');
    popupWrapper.id = 'userjs-titlePopup-wrapper';
    document.body.appendChild(popupWrapper);
    for (const catalogListElm of catalogListElms) {
      const textLineElm =
        catalogListElm.querySelector('.text-ellipsis') || catalogListElm.querySelector('.line-clamp-1');
      if (textLineElm instanceof HTMLElement && textLineElm.textContent) {
        const popup = document.createElement('div');
        popup.textContent = textLineElm.textContent;
        popup.classList.add('userjs-titlePopup');
        popupWrapper.appendChild(popup);
        catalogListElm.addEventListener('mouseover', () => {
          const { left, bottom } = catalogListElm.getBoundingClientRect();
          popup.style.left = `${left + window.scrollX}px`;
          popup.style.top = `${bottom + window.scrollY}px`;
          popup.style.opacity = '1';
        });
        catalogListElm.addEventListener('mouseout', () => {
          popup.style.opacity = '0';
        });
      }
    }
  };
  const observeBody = () => {
    const catalogListElms = document.querySelectorAll(CATALOG_LI_SELECTOR);
    if (catalogListElms.length && document.querySelector('#userjs-titlePopup-style') === null) {
      document.head.insertAdjacentHTML('beforeend', userjsStyle);
    }
    createCatalogTitle(catalogListElms);
    observer = new MutationObserver((mutations) => {
      let shouldUpdate = false;
      if (document.querySelector('#userjs-titlePopup-style') === null) {
        document.head.insertAdjacentHTML('beforeend', userjsStyle);
      }
      // wrapperが削除された場合の処理
      if (document.querySelector('#userjs-titlePopup-wrapper') === null) {
        const currentCatalogListElms = document.querySelectorAll(CATALOG_LI_SELECTOR);
        createCatalogTitle(currentCatalogListElms);
        return;
      }
      // 新しいli要素が追加されたかチェック
      for (const mutation of mutations) {
        if (mutation.type === 'childList') {
          for (const addedNode of mutation.addedNodes) {
            if (addedNode.nodeType === Node.ELEMENT_NODE) {
              const element = addedNode;
              // 新しいli要素、またはli要素を含む要素が追加された場合
              if (element.matches(CATALOG_LI_SELECTOR) || element.querySelector(CATALOG_LI_SELECTOR)) {
                shouldUpdate = true;
                break;
              }
            }
          }
        }
        if (shouldUpdate) break;
      }
      if (shouldUpdate) {
        // 既存のwrapperを削除して再作成
        const existingWrapper = document.querySelector('#userjs-titlePopup-wrapper');
        existingWrapper?.remove();
        const currentCatalogListElms = document.querySelectorAll(CATALOG_LI_SELECTOR);
        createCatalogTitle(currentCatalogListElms);
      }
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });
  };
  const deleteCatalogTitle = () => {
    const popupWrapper = document.querySelector('#userjs-titlePopup-wrapper');
    popupWrapper?.remove();
    observer && observer.disconnect();
  };
  subscribe((state) => {
    console.log('state:', state);
    if (state.isCatalogPage) {
      console.log('init2');
      observeBody();
    } else {
      deleteCatalogTitle();
    }
  });
  if (getState().isCatalogPage) {
    console.log('init');
    observeBody();
  }
})();