GreasyFork Moderator Actions Log Viewer

to view GreasyFork Moderator Actions Log Table

目前为 2023-05-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork Moderator Actions Log Viewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.0
  5. // @description to view GreasyFork Moderator Actions Log Table
  6. // @author CY Fung
  7. // @match https://greasyfork.org/*/moderator_actions*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
  9. // @grant none
  10. // @run-at document-idle
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. function formatDateToCustomFormat(date) {
  18. var year = date.getFullYear();
  19. var month = padZero(date.getMonth() + 1);
  20. var day = padZero(date.getDate());
  21. var hours = padZero(date.getHours());
  22. var minutes = padZero(date.getMinutes());
  23. var timeZoneOffset = getTimeZoneOffsetString();
  24.  
  25. return year + '.' + month + '.' + day + ' ' + hours + ':' + minutes + ' (GMT' + timeZoneOffset + ')';
  26. }
  27.  
  28. function padZero(value) {
  29. return value.toString().padStart(2, '0');
  30. }
  31.  
  32. function getTimeZoneOffsetString() {
  33. var offsetMinutes = new Date().getTimezoneOffset();
  34. var sign = offsetMinutes > 0 ? '-' : '+';
  35. var offsetHours = Math.floor(Math.abs(offsetMinutes) / 60);
  36.  
  37. return sign + offsetHours;
  38. }
  39.  
  40.  
  41. function setupTableContent() {
  42.  
  43. for (const s of document.querySelectorAll('.log-table td:nth-child(1) relative-time:not(.jsm)')) {
  44.  
  45. s.classList.add('jsm')
  46.  
  47. let date = s.date;
  48. if (date) {
  49.  
  50. let e = document.createElement('div');
  51. let q = formatDateToCustomFormat(date);
  52. q = q.split(' ');
  53. // e.textContent = formatDateToCustomFormat(date);
  54. e.className = 'date-entry';
  55. s.classList.add('jsm-hidden')
  56. s.after(e)
  57.  
  58. e.appendChild(Object.assign(document.createElement('span'), {
  59. className: 'date-entry-date',
  60.  
  61. textContent: q[0]
  62. }));
  63.  
  64. e.appendChild(Object.assign(document.createElement('span'), {
  65. className: 'date-entry-time',
  66.  
  67. textContent: q[1]
  68. }));
  69.  
  70. e.appendChild(Object.assign(document.createElement('span'), {
  71. className: 'date-entry-gmt',
  72. textContent: q[2]
  73. }));
  74. }
  75.  
  76.  
  77. }
  78.  
  79.  
  80. for (const s of document.querySelectorAll('.log-table td:nth-child(3) a[href*="/scripts/"]:not(.jsm)')) {
  81.  
  82.  
  83.  
  84. s.classList.add('jsm')
  85. let m = /\/scripts\/(\d+)/.exec(s.href);
  86. if (m) {
  87. let e = document.createElement('div');
  88. e.className = 'script-entry';
  89. s.replaceWith(e);
  90. e.appendChild(s);
  91.  
  92. let span = document.createElement('span');
  93. span.className = 'entry-rid';
  94. span.textContent = m[1]
  95. e.prepend(span)
  96. }
  97.  
  98.  
  99. }
  100.  
  101.  
  102. for (const s of document.querySelectorAll('.log-table td:nth-child(3) a[href*="/users/"]:not(.jsm)')) {
  103.  
  104.  
  105.  
  106. s.classList.add('jsm')
  107. let m = /\/users\/(\d+)/.exec(s.href);
  108. if (m) {
  109. let e = document.createElement('div');
  110. e.className = 'user-entry';
  111. s.replaceWith(e);
  112. e.appendChild(s);
  113.  
  114. let span = document.createElement('span');
  115. span.className = 'entry-rid';
  116. span.textContent = m[1]
  117. e.prepend(span)
  118. }
  119.  
  120.  
  121. }
  122.  
  123.  
  124. for (const s of document.querySelectorAll('.log-table td:nth-child(4)')) {
  125.  
  126. convertToBadges(s);
  127.  
  128.  
  129. }
  130.  
  131.  
  132. for (const s of document.querySelectorAll('.log-table td:nth-child(5)')) {
  133.  
  134. convertHyperlinks(s);
  135.  
  136.  
  137. }
  138.  
  139. }
  140. function convertHyperlinks(elm) {
  141. var walker = document.createTreeWalker(elm, NodeFilter.SHOW_TEXT, null, false);
  142.  
  143. while (walker.nextNode()) {
  144. var textNode = walker.currentNode;
  145. var parentNode = textNode.parentNode;
  146.  
  147. var text = textNode.nodeValue.trim();
  148. if (text.length > 0 && parentNode.tagName !== 'A') {
  149. var match = text.match(/(https?:\/\/[^\s]+)/);
  150.  
  151. if (match) {
  152. var link = document.createElement('a');
  153. link.href = match[0];
  154. link.textContent = match[0].replace('https://greasyfork.org/scripts/', 'scripts/');
  155.  
  156. var before = document.createTextNode(text.substring(0, match.index));
  157. var after = document.createTextNode(text.substring(match.index + match[0].length));
  158.  
  159. parentNode.insertBefore(before, textNode);
  160. parentNode.insertBefore(link, textNode);
  161. parentNode.insertBefore(after, textNode);
  162.  
  163. parentNode.removeChild(textNode);
  164. }
  165. }
  166. }
  167. }
  168.  
  169.  
  170. function convertToBadges(elm) {
  171.  
  172. const converts = {
  173. 'Ban': () => Object.assign(document.createElement('img'), {
  174. src: `https://img.shields.io/badge/action-ban-FF5C5C`
  175. }),
  176. 'Delete and lock': () => Object.assign(document.createElement('img'), {
  177. src: `https://img.shields.io/badge/action-delete-FF9933`
  178. }),
  179. 'Undelete': () => Object.assign(document.createElement('img'), {
  180. src: `https://img.shields.io/badge/action-undelete-66CC66`
  181. }),
  182.  
  183. }
  184.  
  185. var walker = document.createTreeWalker(elm, NodeFilter.SHOW_TEXT, null, false);
  186.  
  187. while (walker.nextNode()) {
  188. var textNode = walker.currentNode;
  189. var parentNode = textNode.parentNode;
  190.  
  191. var text = textNode.nodeValue.trim();
  192. if (text.length > 0 && parentNode.tagName !== 'A' && parentNode.tagName !== 'IMG') {
  193. let t = text.trim();
  194. if (converts[t]) textNode.replaceWith(converts[t]());
  195. }
  196. }
  197. }
  198.  
  199.  
  200.  
  201. function convertToAdvancedTable(tableSelector) {
  202.  
  203. setupTableContent();
  204. // Get the table element
  205. var table = document.querySelector(tableSelector);
  206.  
  207. // Add classes to the table and its components
  208. table.classList.add('advanced-table');
  209. table.tHead.classList.add('advanced-table-head');
  210. table.tBodies[0].classList.add('advanced-table-body');
  211.  
  212. // Get the table headers
  213. var headers = Array.from(table.tHead.rows[0].cells);
  214.  
  215. var sortOrder = []; // Track sort order for each column
  216.  
  217. // Add classes and event listeners to enable sorting
  218. headers.forEach(function (header, index) {
  219. header.classList.add('sortable');
  220. header.addEventListener('click', function (event) {
  221. if (!event.target.classList.contains('search-input')) {
  222. sortTable(table, index, sortOrder);
  223. sortOrder[index] = !sortOrder[index]; // Toggle sort order
  224. }
  225. });
  226.  
  227. // Create search input element
  228. var searchInput = document.createElement('input');
  229. searchInput.setAttribute('type', 'text');
  230. searchInput.setAttribute('placeholder', 'Search');
  231. searchInput.classList.add('search-input');
  232. searchInput.addEventListener('input', function () {
  233. filterTable(table, index);
  234. });
  235. header.appendChild(searchInput);
  236.  
  237. // Create sort icon element
  238. var sortIcon = document.createElement('span');
  239. sortIcon.classList.add('sort-icon');
  240. header.appendChild(sortIcon);
  241. });
  242. }
  243.  
  244. // Function to sort the table by column index
  245. function sortTable(table, columnIndex, sortOrder) {
  246. var rows = Array.from(table.tBodies[0].rows);
  247.  
  248. rows.sort(function (a, b) {
  249. var cellA = a.cells[columnIndex].textContent.toLowerCase();
  250. var cellB = b.cells[columnIndex].textContent.toLowerCase();
  251.  
  252. if (sortOrder[columnIndex]) {
  253. // Sort in descending order
  254. if (cellA < cellB) return 1;
  255. if (cellA > cellB) return -1;
  256. return 0;
  257. } else {
  258. // Sort in ascending order
  259. if (cellA < cellB) return -1;
  260. if (cellA > cellB) return 1;
  261. return 0;
  262. }
  263. });
  264.  
  265. table.tBodies[0].innerHTML = '';
  266. rows.forEach(function (row) {
  267. table.tBodies[0].appendChild(row);
  268. });
  269. }
  270.  
  271. // Function to filter the table by column index
  272. function filterTable(table, columnIndex) {
  273. var filterValue = table.tHead.rows[0].cells[columnIndex].querySelector('.search-input').value.toLowerCase();
  274. var rows = Array.from(table.tBodies[0].rows);
  275.  
  276. rows.forEach(function (row) {
  277. var cellValue = row.cells[columnIndex].textContent.toLowerCase();
  278. row.style.display = cellValue.includes(filterValue) ? '' : 'none';
  279. });
  280. }
  281.  
  282.  
  283. const colsize = (idx) => `.log-table th:nth-child(${idx}), .log-table td:nth-child(${idx}){width:${colsizes[idx - 1]}; max-width:${colsizes[idx - 1]};}`
  284.  
  285. let colsizes = [36, 32, 120, 32, 64];
  286. let colsizeSum = colsizes.reduce((a, b) => a + b, 0);
  287. colsizes = colsizes.map(t => (t / colsizeSum * 100).toFixed(2) + '%');
  288.  
  289. document.head.appendChild(document.createElement('style')).textContent = `
  290.  
  291. .log-table.advanced-table td img{
  292. display:block;
  293. }
  294.  
  295. .advanced-table-head th {
  296. position: relative;
  297. padding: 8px;
  298. }
  299.  
  300. .sortable {
  301. cursor: pointer;
  302. }
  303.  
  304. .sort-icon {
  305. position: absolute;
  306. top: 50%;
  307. right: 8px;
  308. transform: translateY(-50%);
  309. width: 8px;
  310. height: 8px;
  311. border-left: 4px solid transparent;
  312. border-right: 4px solid transparent;
  313. transition: transform 0.2s ease;
  314. }
  315.  
  316. .sortable.asc .sort-icon {
  317. border-bottom: 4px solid #000;
  318. }
  319.  
  320. .sortable.desc .sort-icon {
  321. border-top: 4px solid #000;
  322. }
  323.  
  324. .search-input {
  325. width: 100%;
  326. box-sizing: border-box;
  327. padding: 4px;
  328. border: 1px solid #ccc;
  329. border-radius: 4px;
  330. }
  331.  
  332.  
  333.  
  334. ${colsize(1)}
  335. ${colsize(2)}
  336. ${colsize(3)}
  337. ${colsize(4)}
  338. ${colsize(5)}
  339.  
  340. .entry-rid{
  341. font-size:80%;
  342. font-family: 'Open Sans',sans-serif,"Segoe UI Emoji";
  343. }
  344. .date-entry{
  345. font-family: 'Open Sans',sans-serif,"Segoe UI Emoji";
  346.  
  347. }
  348.  
  349. .user-entry, .script-entry{
  350. display: flex;
  351. column-gap: 4px;
  352. place-items: center;
  353. }
  354.  
  355. .script-entry a[href]{
  356. overflow: hidden;
  357. white-space: nowrap;
  358. width: 24em;
  359. text-overflow: ellipsis;
  360. }
  361.  
  362.  
  363. /* Shared styles for both ".user-entry > .entry-rid" and ".script-entry > .entry-rid" */
  364. .user-entry > .entry-rid,
  365. .script-entry > .entry-rid {
  366.  
  367. display: inline-flex;
  368. place-content: center;
  369. padding: 4px 8px;
  370. color: #fff; /* Set an appropriate white text color */
  371. border-radius: 8px; /* Set the desired border radius */
  372. transition: background-color 0.3s; /* Add transition effect */
  373. min-width: 4em;
  374. }
  375.  
  376. /* Styles for ".user-entry > .entry-rid" */
  377. .user-entry > .entry-rid {
  378. background-color: #4A90E2; /* Set your desired background color */
  379. }
  380.  
  381. .user-entry > .entry-rid:hover {
  382. background-color: #77B5FF; /* Set your desired hover background color */
  383. }
  384.  
  385. /* Styles for ".script-entry > .entry-rid" */
  386. .script-entry > .entry-rid {
  387. background-color: #B146C2; /* Set your desired background color */
  388. }
  389.  
  390. .script-entry > .entry-rid:hover {
  391. background-color: #D27BFF; /* Set your desired hover background color */
  392. }
  393.  
  394. relative-time.jsm-hidden {
  395. display:none;
  396. }
  397.  
  398. .date-entry-date{
  399.  
  400. display: inline-block;
  401. padding: 4px 8px;
  402. color: #fff; /* Set an appropriate white text color */
  403. border-radius: 8px; /* Set the desired border radius */
  404. transition: background-color 0.3s; /* Add transition effect */
  405. font-size:70%;
  406. background-color: #336699;
  407. }
  408.  
  409.  
  410. .date-entry-time{
  411.  
  412. display: inline-block;
  413. padding: 4px 8px;
  414. color: #fff; /* Set an appropriate white text color */
  415. border-radius: 8px; /* Set the desired border radius */
  416. transition: background-color 0.3s; /* Add transition effect */
  417. font-size:70%;
  418. background-color: #663366;
  419. }
  420.  
  421. .date-entry-gmt{
  422.  
  423. display: inline-block;
  424. padding: 4px 8px;
  425. color: #fff; /* Set an appropriate white text color */
  426. border-radius: 8px; /* Set the desired border radius */
  427. transition: background-color 0.3s; /* Add transition effect */
  428. font-size:40%;
  429. background-color: #336633;
  430.  
  431. }
  432.  
  433. `
  434.  
  435. setInterval(() => {
  436.  
  437. let table = document.querySelector('table.log-table:not(.advanced-table)')
  438. if (table) {
  439. requestAnimationFrame(() => {
  440. if (table.classList.contains('advanced-table')) return;
  441. table = null;
  442. convertToAdvancedTable('table.log-table')
  443. });
  444. }
  445.  
  446. }, 100);
  447.  
  448.  
  449.  
  450. // Your code here...
  451. })();