Greasy Fork is available in English.

GreasyFork Moderator Actions Log Viewer

to view GreasyFork Moderator Actions Log Table

Fra 24.05.2023. Se den seneste versjonen.

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