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