WordPress Reader Utilities

Common utilities for readers of WordPress-based sites

当前为 2025-04-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         WordPress Reader Utilities
// @namespace    https://github.com/BobbyWibowo
// @version      1.0.4
// @description  Common utilities for readers of WordPress-based sites
// @author       Bobby Wibowo
// @license      MIT
// @run-at       document-end
// @match        *://*/*
// @grant        GM_addStyle
// ==/UserScript==

(function () {
  'use strict';

  // Helper functions

  const log = (msg, type = 'log') => console[type]('[WRU] ' + msg);

  const getElement = (selectors = []) => {
    let element;
    let index;
    for (index = 0; index < selectors.length; index++) {
      element = document.querySelector(selectors[index]);
      if (element) break;
    }
    return { element, index };
  };

  const capitalizeFirstLetter = string => {
    return string.charAt(0).toUpperCase() + string.slice(1);
  };

  // Detect WordPress

  const isWordPress = () => {
    const config = [
      {
        selector: 'meta[name="generator"]',
        test: element => /^WordPress/.test(element.getAttribute('content'))
      },
      { selector: '#footer .footer-wrap a[href*="wordpress.com"]' },
      { selector: '#footer2 a[href*="wordpress.org"]' },
      { selector: 'footer .site-info a[href*="wordpress.org"]' }
    ];
    const selectors = config.map(conf => conf.selector);
    const detect = getElement(selectors);
    if (detect.element) {
      if (typeof config[detect.index].test === 'function') { return config[detect.index].test(detect.element); }
      return true;
    }
    return false;
  };

  if (!isWordPress()) return;
  log('Current page is a WordPress-based site.');

  // Global style

  let globalStyle = `
    #wcc-dates-container { text-align: center; margin-bottom: 25px }
    #wcc-toggle-container { text-align: center; margin-bottom: 25px }
    #wcc-toggle { width: 100% }
  `;

  // Configurations

  const dateLocale = undefined; // undefined => Browser's locale
  const dateOptions = {
    weekday: 'long',
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
    hour12: true
  };

  // Add detailed published/modified dates at the top of post.

  const getDates = () => {
    const tags = {
      'article:published_time': 'published',
      'article:modified_time': 'modified'
    };
    const results = {};
    const tagsKeys = Object.keys(tags);
    for (const key of tagsKeys) {
      const meta = document.head.querySelector('meta[property="' + key + '"]');
      if (!meta) continue;
      results[tags[key]] = new Date(meta.getAttribute('content'));
    }
    return results;
  };

  const displayDates = () => {
    const dates = getDates();
    const datesKeys = Object.keys(dates);
    if (!datesKeys.length) { return log('Current page does not have date meta tags.'); }

    const selectors = ['header.entry-header', 'h1.entry-title', 'h1.uk-article-title'];
    const title = getElement(selectors);
    if (!title.element) { return log('Current page does not have a title element.'); }

    const datesContainer = document.createElement('div');
    datesContainer.id = 'wcc-dates-container';
    datesContainer.innerHTML = '';
    title.element.insertAdjacentElement('afterend', datesContainer);
    log('Dates container appended.');

    for (const key of datesKeys) {
      const formattedDate = new Intl.DateTimeFormat(dateLocale, dateOptions).format(dates[key]);
      datesContainer.innerHTML += '<b>' + capitalizeFirstLetter(key) + ':</b> ' + formattedDate + '<br>';
    }
  };

  displayDates();

  // Add an expand/collapse button to comments container.

  const appendToggler = () => {
    const selectors = [
      '.content-comments.container',
      '.comments-area',
      '#disqus_thread',
      '#fastcomments-widget'
    ];

    const comments = getElement(selectors);
    if (!comments.element) { return log('Current page does not have a comments section.'); }

    globalStyle += selectors[comments.index] + ':not([data-expanded="1"]) { height: 0; overflow: hidden }';

    const commentHash = location.hash || '';
    const hasCommentHash = /^#comment(s|-\d+)$/.test(commentHash);
    if (hasCommentHash) {
      comments.element.dataset.expanded = '1';
      log('Comment hash detected, comments expanded.');
    }

    const toggleContainer = document.createElement('div');
    toggleContainer.id = 'wcc-toggle-container';
    comments.element.insertAdjacentElement('beforebegin', toggleContainer);
    log('Toggle container appended.');

    const toggle = document.createElement('button');
    toggle.id = 'wcc-toggle';

    const updateBtn = () => {
      toggle.innerHTML = (comments.element.dataset.expanded ? 'Collapse' : 'Expand') + ' Comments';
      return toggle.innerHTML;
    };
    updateBtn();

    toggle.addEventListener('click', () => {
      if (comments.element.dataset.expanded) delete comments.element.dataset.expanded;
      else comments.element.dataset.expanded = '1';
      updateBtn();
    });

    toggleContainer.appendChild(toggle);
    log('Toggle appended.');
  };
  appendToggler();

  // Add styling

  GM_addStyle(globalStyle);
  log('Styling added.');
})();