bgm-wiki-rev-diff

show diff between bgm.tv wiki versions

目前為 2022-10-01 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name bgm-wiki-rev-diff
  3. // @name:zh 显示条目信息版本差异
  4. // @namespace https://trim21.me/
  5. // @version 0.2.4
  6. // @author Trim21 <i@trim21.me>
  7. // @source https://github.com/Trim21/bgm-wiki-rev-diff
  8. // @supportURL https://github.com/Trim21/bgm-wiki-rev-diff/issues
  9. // @license MIT
  10. // @match https://bgm.tv/subject/*/edit*
  11. // @match https://bangumi.tv/subject/*/edit*
  12. // @match https://chii.in/subject/*/edit*
  13. // @require https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js
  14. // @require https://cdn.jsdelivr.net/npm/diff2html@3.4.19/bundles/js/diff2html.min.js
  15. // @require https://cdn.jsdelivr.net/npm/diff@5.1.0/dist/diff.min.js
  16. // @require https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
  17. // @resource diff2html https://cdn.jsdelivr.net/npm/diff2html@3.4.19/bundles/css/diff2html.min.css
  18. // @grant GM.getResourceUrl
  19. // @grant GM.registerMenuCommand
  20. // @grant GM.setValue
  21. // @grant GM.getValue
  22. // @run-at document-end
  23. // @description show diff between bgm.tv wiki versions
  24. // ==/UserScript==
  25.  
  26.  
  27. /******/ (() => { // webpackBootstrap
  28. /******/ "use strict";
  29. var __webpack_exports__ = {};
  30.  
  31. ;// CONCATENATED MODULE: external "$"
  32. const external_$_namespaceObject = $;
  33. ;// CONCATENATED MODULE: external "_"
  34. const external_namespaceObject = _;
  35. ;// CONCATENATED MODULE: ./src/parser.ts
  36.  
  37.  
  38. function parseRevDetails(html) {
  39. var _a, _b, _c;
  40. const jq = external_$_namespaceObject(html);
  41. const rawInfo = ((_a = jq.find("#subject_infobox").val()) == null ? void 0 : _a.toString()) ?? "";
  42. const title = ((_b = jq.find('input[name="subject_title"]').val()) == null ? void 0 : _b.toString()) ?? "";
  43. const description = ((_c = jq.find("textarea#subject_summary").val()) == null ? void 0 : _c.toString()) ?? "";
  44. return {
  45. title,
  46. rawInfo,
  47. description
  48. };
  49. }
  50. function parseRevEl(el) {
  51. const date = el.find("a:not(.compare-previous-trim21-cn)").first().html();
  52. const revEL = el.find('a.l:contains("恢复")');
  53. const revCommentEl = el.find("span.comment");
  54. let comment = "";
  55. if (revCommentEl.length > 0) {
  56. comment = revCommentEl.html();
  57. comment = comment.substring(1, comment.length - 1);
  58. }
  59. const revHref = revEL.attr("href");
  60. if (!revHref) {
  61. return void 0;
  62. }
  63. const revID = revHref.split("/").pop();
  64. if (!revID) {
  65. throw new Error(`can't parse rev id from ${revHref}`);
  66. }
  67. return {
  68. id: revID,
  69. comment,
  70. date,
  71. url: revHref
  72. };
  73. }
  74. function getRevs() {
  75. const revs = [];
  76. external_$_namespaceObject("#pagehistory li").each(function() {
  77. const rev = parseRevEl(external_$_namespaceObject(this));
  78. if (rev != null) {
  79. revs.push(rev);
  80. }
  81. });
  82. return revs;
  83. }
  84. function getRevInfo(revID) {
  85. for (const rev of getRevs()) {
  86. if (rev.id === revID) {
  87. return rev;
  88. }
  89. }
  90. }
  91.  
  92. ;// CONCATENATED MODULE: external "Diff2Html"
  93. const external_Diff2Html_namespaceObject = Diff2Html;
  94. ;// CONCATENATED MODULE: ./src/config.ts
  95.  
  96. const configKey = "view-mode";
  97.  
  98. ;// CONCATENATED MODULE: external "Diff"
  99. const external_Diff_namespaceObject = Diff;
  100. ;// CONCATENATED MODULE: ./src/differ.ts
  101.  
  102.  
  103. function diff(revOld, revNew, style) {
  104. const options = { context: 100 };
  105. if (style === "line-by-line") {
  106. options.context = 4;
  107. }
  108. return [
  109. titleDiff(revOld, revNew, options),
  110. infoDiff(revOld, revNew, options),
  111. descriptionDiff(revOld, revNew, options)
  112. ].join("\n");
  113. }
  114. function titleDiff(rev1, rev2, options) {
  115. if (rev1.details.title === rev2.details.title) {
  116. return "";
  117. }
  118. return external_Diff_namespaceObject.createPatch(
  119. "条目名",
  120. rev1.details.title,
  121. rev2.details.title,
  122. rev1.rev.date,
  123. rev2.rev.date,
  124. options
  125. );
  126. }
  127. function infoDiff(rev1, rev2, options) {
  128. if (rev1.details.rawInfo === rev2.details.rawInfo) {
  129. return "";
  130. }
  131. return external_Diff_namespaceObject.createPatch(
  132. "相关信息",
  133. rev1.details.rawInfo,
  134. rev2.details.rawInfo,
  135. rev1.rev.date,
  136. rev2.rev.date,
  137. options
  138. );
  139. }
  140. function descriptionDiff(rev1, rev2, options) {
  141. if (rev1.details.description === rev2.details.description) {
  142. return "";
  143. }
  144. return external_Diff_namespaceObject.createPatch(
  145. "简介",
  146. rev1.details.description,
  147. rev2.details.description,
  148. rev1.rev.date,
  149. rev2.rev.date,
  150. options
  151. );
  152. }
  153.  
  154. ;// CONCATENATED MODULE: ./src/ui.ts
  155.  
  156.  
  157.  
  158.  
  159.  
  160. async function render(revOld, revNew) {
  161. var _a;
  162. let outputFormat = await GM.getValue(configKey);
  163. if (!outputFormat) {
  164. outputFormat = "line-by-line";
  165. }
  166. const patch = diff(revOld, revNew, outputFormat);
  167. const html = external_Diff2Html_namespaceObject.html(patch, { outputFormat });
  168. const elID = `show-diff-view-${outputFormat}`;
  169. show("");
  170. external_$_namespaceObject(`#${elID}`).html(html);
  171. (_a = document.getElementById(elID)) == null ? void 0 : _a.scrollIntoView({
  172. behavior: "smooth"
  173. });
  174. }
  175. function show(html) {
  176. external_$_namespaceObject("#show-diff-info").html(html);
  177. }
  178. function clear() {
  179. external_$_namespaceObject("#show-diff-view-line-by-line").html("");
  180. external_$_namespaceObject("#show-diff-view-side-by-side").html("");
  181. show("");
  182. }
  183.  
  184. ;// CONCATENATED MODULE: ./src/model.ts
  185.  
  186. class Commit {
  187. rev;
  188. details;
  189. constructor(rev, detail) {
  190. this.rev = rev;
  191. this.details = detail;
  192. }
  193. }
  194.  
  195. ;// CONCATENATED MODULE: ./src/compare.ts
  196.  
  197.  
  198.  
  199.  
  200. function compare(revID1, revID2) {
  201. clear();
  202. show("<h2>loading versions...</h2>");
  203. const rev1 = getRevInfo(revID1);
  204. const rev2 = getRevInfo(revID2);
  205. if (rev1 == null) {
  206. throw new Error(`error finding ${revID1}`);
  207. }
  208. const ps = [fetchRev(rev1), fetchRev(rev2)];
  209. Promise.all(ps).then(async (values) => {
  210. return await render(values[1], values[0]);
  211. }).catch((e) => {
  212. console.error(e);
  213. show('<div style="color: red">获取历史修改失败,请刷新页面后重试</div>');
  214. });
  215. }
  216. const _cache = {};
  217. async function fetchRev(rev) {
  218. if (rev == null) {
  219. return new Commit(
  220. {
  221. id: "0",
  222. comment: "",
  223. date: "",
  224. url: ""
  225. },
  226. {
  227. title: "",
  228. rawInfo: "",
  229. description: ""
  230. }
  231. );
  232. }
  233. if (!_cache[rev.id]) {
  234. const res = await fetch(rev.url);
  235. _cache[rev.id] = new Commit(rev, parseRevDetails(await res.text()));
  236. }
  237. return _cache[rev.id];
  238. }
  239.  
  240. ;// CONCATENATED MODULE: ./src/index.ts
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247. async function main() {
  248. console.log("start bgm-wiki-rev-diff UserScript");
  249. await initUI();
  250. }
  251. const style = `
  252. <style>
  253. #show-diff-view-side-by-side {
  254. margin:0 auto;
  255. max-width: 100em;
  256. }
  257.  
  258. .show-version-diff .d2h-code-line, .show-version-diff .d2h-code-side-line {
  259. width: calc(100% - 8em);
  260. padding-right: 0;
  261. }
  262.  
  263. .show-version-diff .d2h-code-line-ctn {
  264. width: calc(100% - 8em);
  265. }
  266.  
  267. #columnInSubjectA .rev-trim21-cn {
  268. margin: 0 0.2em;
  269. }
  270.  
  271. ul#pagehistory > li > * {
  272. vertical-align: middle;
  273. }
  274. </style>
  275. `;
  276. async function initUI() {
  277. GM.registerMenuCommand("切换diff视图", function() {
  278. void (async () => {
  279. let outputFormat = await GM.getValue(configKey);
  280. if (!outputFormat || outputFormat === "side-by-side") {
  281. outputFormat = "line-by-line";
  282. } else {
  283. outputFormat = "side-by-side";
  284. }
  285. await GM.setValue(configKey, outputFormat);
  286. })();
  287. });
  288. external_$_namespaceObject("#headerSubject").after(
  289. '<div id="show-diff-view-side-by-side" class="show-version-diff"></div>'
  290. );
  291. external_$_namespaceObject("#columnInSubjectA > hr.board").after(
  292. style + '<div id="show-diff-view-line-by-line" class="show-version-diff"></div>'
  293. );
  294. external_$_namespaceObject("#columnInSubjectA .subtitle").after('<div id="show-diff-info"></div>');
  295. const diff2htmlStyle = await GM.getResourceUrl("diff2html");
  296. external_$_namespaceObject("head").append(style).append(
  297. `<link rel='stylesheet' type='text/css' href='${diff2htmlStyle}' />`
  298. );
  299. const s = external_$_namespaceObject("#pagehistory li");
  300. const revs = Array.from(s).map(function(e) {
  301. var _a;
  302. return (_a = parseRevEl(external_$_namespaceObject(e))) == null ? void 0 : _a.id;
  303. });
  304. s.each(function(index) {
  305. const el = external_$_namespaceObject(this);
  306. const id = revs[index];
  307. if (!id) {
  308. el.prepend('<span style="padding-right: 1.4em"> 无法参与比较 </span>');
  309. return;
  310. }
  311. el.prepend(
  312. `<input type='radio' class='rev-trim21-cn' name='rev-right' label='select to compare' value='${id}'>`
  313. );
  314. el.prepend(
  315. `<input type='radio' class='rev-trim21-cn' name='rev-left' label='select to compare' value='${id}'>`
  316. );
  317. const previous = external_namespaceObject.find(revs, Boolean, index + 1) ?? "";
  318. el.prepend(
  319. `(<a href='#' data-rev='${id}' data-previous='${previous}' class='l compare-previous-trim21-cn'>显示修改</a>) `
  320. );
  321. });
  322. const typeRevert = {
  323. "rev-left": "rev-right",
  324. "rev-right": "rev-left"
  325. };
  326. external_$_namespaceObject('input[type="radio"]').on("change", function(e) {
  327. const name = e.target.getAttribute("name");
  328. if (!name) {
  329. return;
  330. }
  331. const selectName = typeRevert[name];
  332. const rev = e.target.getAttribute("value");
  333. if (rev) {
  334. external_$_namespaceObject(`input[name="${selectName}"][value="${rev}"]`).css(
  335. "visibility",
  336. "hidden"
  337. );
  338. external_$_namespaceObject(`input[name="${selectName}"][value!="${rev}"]`).css(
  339. "visibility",
  340. "visible"
  341. );
  342. }
  343. });
  344. external_$_namespaceObject(".compare-previous-trim21-cn").on("click", function() {
  345. const el = external_$_namespaceObject(this);
  346. const left = String(el.data("rev"));
  347. const right = String(el.data("previous"));
  348. compare(left, right);
  349. external_$_namespaceObject(`input[name="rev-left"][value="${left}"]`).prop("checked", true);
  350. external_$_namespaceObject(`input[name="rev-left"][value!="${left}"]`).prop("checked", null);
  351. external_$_namespaceObject(`input[name="rev-right"][value="${left}"]`).css("visibility", "hidden");
  352. external_$_namespaceObject(`input[name="rev-right"][value!="${left}"]`).css("visibility", "visible");
  353. external_$_namespaceObject('input[name="rev-left"]').css("visibility", "visible");
  354. external_$_namespaceObject('input[name="rev-right"]').prop("checked", null);
  355. if (right) {
  356. external_$_namespaceObject(`input[name="rev-right"][value="${right}"]`).prop("checked", true);
  357. external_$_namespaceObject(`input[name="rev-left"][value="${right}"]`).css("visibility", "hidden");
  358. }
  359. });
  360. external_$_namespaceObject("#columnInSubjectA span.text").append(
  361. '<a href="#" id="compare-trim21-cn" class="l"> > 比较选中的版本</a>'
  362. );
  363. external_$_namespaceObject("#compare-trim21-cn").on("click", function() {
  364. const selectedRevs = getSelectedVersion();
  365. compare(selectedRevs[0], selectedRevs[1]);
  366. });
  367. }
  368. function getSelectedVersion() {
  369. const selectedVersion = [];
  370. const selectedRev = external_$_namespaceObject(".rev-trim21-cn:checked");
  371. if (selectedRev.length < 2) {
  372. window.alert("请选中两个版本进行比较");
  373. throw new Error();
  374. }
  375. selectedRev.each(function() {
  376. const val = external_$_namespaceObject(this).val();
  377. selectedVersion.push(val);
  378. });
  379. selectedVersion.sort((a, b) => parseInt(b) - parseInt(a));
  380. return selectedVersion;
  381. }
  382. main().catch(console.error);
  383.  
  384. /******/ })()
  385. ;