WordPress Reader Utilities

Common utilities for readers of WordPress-based sites

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

  1. // ==UserScript==
  2. // @name WordPress Reader Utilities
  3. // @namespace https://github.com/BobbyWibowo
  4. // @version 1.0.4
  5. // @description Common utilities for readers of WordPress-based sites
  6. // @author Bobby Wibowo
  7. // @license MIT
  8. // @run-at document-end
  9. // @match *://*/*
  10. // @grant GM_addStyle
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // Helper functions
  17.  
  18. const log = (msg, type = 'log') => console[type]('[WRU] ' + msg);
  19.  
  20. const getElement = (selectors = []) => {
  21. let element;
  22. let index;
  23. for (index = 0; index < selectors.length; index++) {
  24. element = document.querySelector(selectors[index]);
  25. if (element) break;
  26. }
  27. return { element, index };
  28. };
  29.  
  30. const capitalizeFirstLetter = string => {
  31. return string.charAt(0).toUpperCase() + string.slice(1);
  32. };
  33.  
  34. // Detect WordPress
  35.  
  36. const isWordPress = () => {
  37. const config = [
  38. {
  39. selector: 'meta[name="generator"]',
  40. test: element => /^WordPress/.test(element.getAttribute('content'))
  41. },
  42. { selector: '#footer .footer-wrap a[href*="wordpress.com"]' },
  43. { selector: '#footer2 a[href*="wordpress.org"]' },
  44. { selector: 'footer .site-info a[href*="wordpress.org"]' }
  45. ];
  46. const selectors = config.map(conf => conf.selector);
  47. const detect = getElement(selectors);
  48. if (detect.element) {
  49. if (typeof config[detect.index].test === 'function') { return config[detect.index].test(detect.element); }
  50. return true;
  51. }
  52. return false;
  53. };
  54.  
  55. if (!isWordPress()) return;
  56. log('Current page is a WordPress-based site.');
  57.  
  58. // Global style
  59.  
  60. let globalStyle = `
  61. #wcc-dates-container { text-align: center; margin-bottom: 25px }
  62. #wcc-toggle-container { text-align: center; margin-bottom: 25px }
  63. #wcc-toggle { width: 100% }
  64. `;
  65.  
  66. // Configurations
  67.  
  68. const dateLocale = undefined; // undefined => Browser's locale
  69. const dateOptions = {
  70. weekday: 'long',
  71. year: 'numeric',
  72. month: 'long',
  73. day: 'numeric',
  74. hour: 'numeric',
  75. minute: 'numeric',
  76. second: 'numeric',
  77. hour12: true
  78. };
  79.  
  80. // Add detailed published/modified dates at the top of post.
  81.  
  82. const getDates = () => {
  83. const tags = {
  84. 'article:published_time': 'published',
  85. 'article:modified_time': 'modified'
  86. };
  87. const results = {};
  88. const tagsKeys = Object.keys(tags);
  89. for (const key of tagsKeys) {
  90. const meta = document.head.querySelector('meta[property="' + key + '"]');
  91. if (!meta) continue;
  92. results[tags[key]] = new Date(meta.getAttribute('content'));
  93. }
  94. return results;
  95. };
  96.  
  97. const displayDates = () => {
  98. const dates = getDates();
  99. const datesKeys = Object.keys(dates);
  100. if (!datesKeys.length) { return log('Current page does not have date meta tags.'); }
  101.  
  102. const selectors = ['header.entry-header', 'h1.entry-title', 'h1.uk-article-title'];
  103. const title = getElement(selectors);
  104. if (!title.element) { return log('Current page does not have a title element.'); }
  105.  
  106. const datesContainer = document.createElement('div');
  107. datesContainer.id = 'wcc-dates-container';
  108. datesContainer.innerHTML = '';
  109. title.element.insertAdjacentElement('afterend', datesContainer);
  110. log('Dates container appended.');
  111.  
  112. for (const key of datesKeys) {
  113. const formattedDate = new Intl.DateTimeFormat(dateLocale, dateOptions).format(dates[key]);
  114. datesContainer.innerHTML += '<b>' + capitalizeFirstLetter(key) + ':</b> ' + formattedDate + '<br>';
  115. }
  116. };
  117.  
  118. displayDates();
  119.  
  120. // Add an expand/collapse button to comments container.
  121.  
  122. const appendToggler = () => {
  123. const selectors = [
  124. '.content-comments.container',
  125. '.comments-area',
  126. '#disqus_thread',
  127. '#fastcomments-widget'
  128. ];
  129.  
  130. const comments = getElement(selectors);
  131. if (!comments.element) { return log('Current page does not have a comments section.'); }
  132.  
  133. globalStyle += selectors[comments.index] + ':not([data-expanded="1"]) { height: 0; overflow: hidden }';
  134.  
  135. const commentHash = location.hash || '';
  136. const hasCommentHash = /^#comment(s|-\d+)$/.test(commentHash);
  137. if (hasCommentHash) {
  138. comments.element.dataset.expanded = '1';
  139. log('Comment hash detected, comments expanded.');
  140. }
  141.  
  142. const toggleContainer = document.createElement('div');
  143. toggleContainer.id = 'wcc-toggle-container';
  144. comments.element.insertAdjacentElement('beforebegin', toggleContainer);
  145. log('Toggle container appended.');
  146.  
  147. const toggle = document.createElement('button');
  148. toggle.id = 'wcc-toggle';
  149.  
  150. const updateBtn = () => {
  151. toggle.innerHTML = (comments.element.dataset.expanded ? 'Collapse' : 'Expand') + ' Comments';
  152. return toggle.innerHTML;
  153. };
  154. updateBtn();
  155.  
  156. toggle.addEventListener('click', () => {
  157. if (comments.element.dataset.expanded) delete comments.element.dataset.expanded;
  158. else comments.element.dataset.expanded = '1';
  159. updateBtn();
  160. });
  161.  
  162. toggleContainer.appendChild(toggle);
  163. log('Toggle appended.');
  164. };
  165. appendToggler();
  166.  
  167. // Add styling
  168.  
  169. GM_addStyle(globalStyle);
  170. log('Styling added.');
  171. })();