kbin enhancement script

Few small changes to the kbin UI while they still develop some features

目前為 2023-06-16 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name kbin enhancement script
  3. // @description Few small changes to the kbin UI while they still develop some features
  4. // @namespace com.sirpsychomantis
  5. // @license MIT
  6. // @version 1.6
  7. // @grant none
  8. // @run-at document-end
  9. // @match https://fedia.io/*
  10. // @match https://kbin.social/*
  11. // ==/UserScript==
  12.  
  13.  
  14. (function(){
  15. const version = "1.6";
  16. const allSettings = [
  17. {name: "Show domains", value:"show-domains"},
  18. {name: "Show collapse comment", value:"show-collapse"},
  19. {name: "Show collapse replies", value:"show-collapse-replies"},
  20. {name: "Replies start collapsed", value:"start-collapse-replies", default: "false"},
  21. {name: "Move comment box to top", value:"comment-box-top"},
  22. {name: "Reply box improvements", value:"comment-cancel"}
  23. ];
  24. allSettings.forEach(setting => {
  25. if (setting.default === "false" && localStorage.getItem("setting-" + setting.value) === null) {
  26. localStorage.setItem("setting-" + setting.value, "false");
  27. }
  28. });
  29. function getSetting(setting) {
  30. let value = localStorage.getItem("setting-" + setting);
  31. if (value === null)
  32. value = "true";
  33. return value === "true";
  34. }
  35. function setSetting(setting, value) {
  36. localStorage.setItem("setting-" + setting, value);
  37. location.reload();
  38. }
  39. function addDomain(link) {
  40. const parts = link.title.split("@");
  41. if (parts[2] !== location.hostname && !link.innerText.includes("@" + parts[2])) {
  42. const linkText = link.childNodes[link.childNodes.length-1];
  43. linkText.nodeValue += "@" + parts[2];
  44. }
  45. }
  46. function addDomains() {
  47. document.querySelectorAll(".magazine-inline, .user-inline").forEach(link => {
  48. addDomain(link);
  49. });
  50. }
  51. function getComments(comment, allComments) {
  52. const id = comment.id.split('-')[2];
  53. allComments.push(comment);
  54. const subComments = comment.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
  55. subComments.forEach(blockquote => { getComments(blockquote, allComments); });
  56. }
  57. function getCollapsos(comment, allCollapsos) {
  58. const id = comment.id.split('-')[2];
  59. if (comment.classList.contains('kes-expand'))
  60. allCollapsos.push(comment);
  61. const subComments = comment.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
  62. subComments.forEach(blockquote => { getCollapsos(blockquote, allCollapsos); });
  63. }
  64. function removeAllCollapsos(blockquote) {
  65. // Just remove all these for now, don't want to figure out how to do this cleanly right now.
  66. const allCollapsos = [];
  67. getCollapsos(blockquote, allCollapsos);
  68. allCollapsos.forEach(comment => { comment.remove() });
  69. }
  70. function expandComment(blockquote) {
  71. const allComments = [];
  72. getComments(blockquote, allComments);
  73. allComments.forEach(comment => { comment.style.display="" });
  74. removeAllCollapsos(blockquote);
  75. }
  76. function collapseComment(blockquote) {
  77. const id = blockquote.id.split('-')[2];
  78. let commentLevel = "1";
  79. blockquote.classList.forEach(classItem => {
  80. if (classItem.includes("comment-level"))
  81. commentLevel = classItem.split("--")[1];
  82. });
  83. const allComments = [];
  84. getComments(blockquote, allComments);
  85. allComments.forEach(comment => { comment.style.display="none" });
  86.  
  87. const username = blockquote.querySelector("header a").innerText;
  88. const time = blockquote.querySelector("header time").innerText;
  89.  
  90. const newBlockquote = document.createElement('blockquote');
  91. newBlockquote.className = 'kes-expand section comment entry-comment comment-level--' + commentLevel;
  92. newBlockquote.style = 'grid-gap: 0; padding-left:55px';
  93. newBlockquote.dataset.subjectParentValue = id;
  94. newBlockquote.innerHTML = '<header><a href="javascript:;">' + username + ', ' + time + ' [+]</a></header>';
  95. newBlockquote.querySelector('a').addEventListener("click", () => {expandComment(blockquote)});
  96. blockquote.parentNode.insertBefore(newBlockquote, blockquote);
  97. }
  98. function getTotalReplyCount(blockquote) {
  99. const allComments = [];
  100. getComments(blockquote, allComments);
  101. return allComments.length - 1;
  102. }
  103. function addCollapseLinks() {
  104. if (location.pathname.startsWith('/m')) {
  105. const comments = document.querySelectorAll("blockquote.comment");
  106. comments.forEach(blockquote => {
  107. const menu = blockquote.querySelector("header");
  108. if (!menu.innerText.includes('[-]')) {
  109. const newA = document.createElement('a');
  110. newA.href = "javascript:;";
  111. newA.className = "kes-collapse";
  112. newA.innerHTML = '[-]';
  113. menu.appendChild(newA);
  114. }
  115. });
  116. document.querySelectorAll(".kes-collapse").forEach(link => {link.addEventListener("click", () => {
  117. const blockquote = link.closest("blockquote.comment");
  118. collapseComment(blockquote);
  119. })});
  120. }
  121. }
  122. function getAllReplies(blockquote) {
  123. const allComments = [];
  124. getComments(blockquote, allComments);
  125. allComments.splice(allComments.indexOf(blockquote), 1);
  126. return allComments;
  127. }
  128. function toggleReplies(blockquote, display) {
  129. const id = blockquote.id.split('-')[2];
  130. const allReplies = getAllReplies(blockquote);
  131. let anyHidden = false;
  132. allReplies.forEach(reply => {
  133. if (reply.style.display == 'none')
  134. anyHidden = true;
  135. });
  136. allReplies.forEach(comment => { comment.style.display = anyHidden ? '' : 'none' });
  137. removeAllCollapsos(blockquote);
  138. }
  139. function addCollapseRepliesLinks() {
  140. if (location.pathname.startsWith('/m')) {
  141. const comments = document.querySelectorAll("blockquote.comment-level--1");
  142. comments.forEach(blockquote => {
  143. const id = blockquote.id.split('-')[2];
  144. const subComments = blockquote.parentElement.querySelectorAll('blockquote[data-subject-parent-value="'+id+'"]');
  145. if (subComments.length > 0) {
  146. const menu = blockquote.querySelector("footer menu");
  147. const newLi = document.createElement('li');
  148. newLi.innerHTML = '<a href="javascript:;" class="kes-collapse-replies">toggle replies ('+getTotalReplyCount(blockquote)+')</a>';
  149. menu.appendChild(newLi);
  150. }
  151. });
  152. document.querySelectorAll(".kes-collapse-replies").forEach(link => {link.addEventListener("click", () => {
  153. const blockquote = link.closest("blockquote.comment");
  154. toggleReplies(blockquote);
  155. })});
  156. }
  157. }
  158. function collapseAllReplies() {
  159. const comments = document.querySelectorAll("blockquote.comment-level--2");
  160. comments.forEach(blockquote => {
  161. collapseComment(blockquote);
  162. });
  163. }
  164. function moveCommentBox() {
  165. const commentAdd = document.querySelector('#comment-add');
  166. if (commentAdd)
  167. commentAdd.parentNode.insertBefore(commentAdd, document.querySelector('#comments'));
  168. }
  169. function removeReplyBox(container) {
  170. container.innerHTML = '';
  171. container.style = '';
  172. }
  173. function addCommentCancelButton(container) {
  174. const list = container.querySelector('div.actions ul');
  175. const newLi = document.createElement('li');
  176. newLi.innerHTML = '<div><button class="btn btn__primary">Cancel</button></div>';
  177. list.appendChild(newLi);
  178. newLi.querySelector('button').addEventListener("click", () => { removeReplyBox(container) });
  179. }
  180. function fixMarkdownButtons(form) {
  181. const formActionSplit = form.action.split('/');
  182. const newId = 'entry_comment_body_' + formActionSplit[formActionSplit.length-1];
  183. form.querySelector('#entry_comment_body').id = newId;
  184. form.querySelector('markdown-toolbar').setAttribute('for', newId);
  185. }
  186. function observeReplyAdded() {
  187. const config = { childList: true };
  188. const callback = (mutationList, observer) => {
  189. for (const mutation of mutationList) {
  190. const container = mutation.target;
  191. const form = container.querySelector('form.comment-add');
  192. if (form !== null) {
  193. fixMarkdownButtons(form);
  194. addCommentCancelButton(container);
  195. }
  196. }
  197. };
  198.  
  199. const observer = new MutationObserver(callback);
  200. document.querySelectorAll('blockquote.comment footer div.js-container').forEach(container => { observer.observe(container, config) })
  201. }
  202. function generateSettingDiv(settingDisplay, setting) {
  203. const settingValue = getSetting(setting);
  204. const newDiv = document.createElement('div');
  205. newDiv.className = "row";
  206. newDiv.innerHTML = `<span>${settingDisplay}:</span>
  207. <div>
  208. <a class="kes-setting-yes link-muted ${settingValue ? 'active' : ''}" href="javascript:;" data-setting="${setting}">
  209. Yes
  210. </a>
  211. |
  212. <a class="kes-setting-no link-muted ${settingValue ? '' : 'active'}" href="javascript:;" data-setting="${setting}">
  213. No
  214. </a>
  215. </div>`;
  216. return newDiv;
  217. }
  218. function addHTMLSettings() {
  219. const settingsList = document.querySelector(".settings-list");
  220. const header = document.createElement('strong');
  221. header.textContent = "kbin enhancement script";
  222. settingsList.appendChild(header);
  223. allSettings.forEach(setting => { settingsList.appendChild(generateSettingDiv(setting.name, setting.value)) });
  224. document.querySelectorAll(".kes-setting-yes").forEach(link => { link.addEventListener("click", () => {setSetting(link.dataset.setting, true) })});
  225. document.querySelectorAll(".kes-setting-no").forEach(link => { link.addEventListener("click", () => {setSetting(link.dataset.setting, false) })});
  226. }
  227. addHTMLSettings();
  228. if (getSetting("show-domains"))
  229. addDomains();
  230. if (getSetting("show-collapse"))
  231. addCollapseLinks();
  232. if (getSetting("show-collapse-replies"))
  233. addCollapseRepliesLinks();
  234. if (getSetting("start-collapse-replies"))
  235. collapseAllReplies();
  236. if (getSetting("comment-box-top"))
  237. moveCommentBox();
  238. if (getSetting("comment-cancel"))
  239. observeReplyAdded();
  240. if (localStorage.getItem("setting-changelog-version") != version) {
  241. const message = `<strong>kbin enhancement script version: ${version}</strong><br>
  242. Thanks for downloading! You can always toggle on and off features in the kbin sidebar settings.<br>Recent changes:
  243. <ul>
  244. <li>Fixed markdown buttons and added "Cancel" when replying</li>
  245. <br>
  246. <li>Added move comment box to top</li>
  247. <li>Adjusted collapse functionality</li>
  248. <li>Added hide all replies</li>
  249. <li>Added hide all replies by default</li>
  250. <li>Fixed bugs</li>
  251. </ul>
  252. `
  253. const versionDiv = document.createElement('div');
  254. versionDiv.id = 'kes-version-dialog';
  255. versionDiv.style = 'position: fixed; width: 100vw; height: 100vh; top: 0; left: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0,0,0,.3); z-index: 9999999';
  256. versionDiv.innerHTML = '<div style="background: #ddd; color: #444; position: relative; padding: 40px">'+message+'<br><button>Close</button></div>';
  257. document.body.appendChild(versionDiv);
  258. document.querySelector('#kes-version-dialog button').addEventListener("click", () => {
  259. document.querySelector('#kes-version-dialog').remove();
  260. localStorage.setItem("setting-changelog-version", version);
  261. });
  262. }
  263. })();