bgm-wiki-rev-diff

show diff between bgm.tv wiki versions

当前为 2021-10-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name bgm-wiki-rev-diff
  3. // @name:zh 显示条目信息版本差异
  4. // @namespace https://trim21.me/
  5. // @version 0.1.5
  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.0/dist/jquery.min.js
  14. // @require https://cdn.jsdelivr.net/npm/diff2html@3.4.11/bundles/js/diff2html.min.js
  15. // @require https://cdn.jsdelivr.net/npm/diff@5.0.0/dist/diff.min.js
  16. // @connect bgm.tv
  17. // @connect bangumi.tv
  18. // @run-at document-end
  19. // @description show diff between bgm.tv wiki versions
  20. // ==/UserScript==
  21.  
  22.  
  23. /******/ (function() { // webpackBootstrap
  24. /******/ "use strict";
  25. /******/ var __webpack_modules__ = ({
  26.  
  27. /***/ "./src/compare.ts":
  28. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  29.  
  30.  
  31. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  32. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  33. return new (P || (P = Promise))(function (resolve, reject) {
  34. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  35. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  36. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  37. step((generator = generator.apply(thisArg, _arguments || [])).next());
  38. });
  39. };
  40. Object.defineProperty(exports, "__esModule", ({ value: true }));
  41. exports.compare = void 0;
  42. const parser_1 = __webpack_require__("./src/parser.ts");
  43. const differ_1 = __webpack_require__("./src/differ.ts");
  44. const ui_1 = __webpack_require__("./src/ui.ts");
  45. const model_1 = __webpack_require__("./src/model.ts");
  46. function compare(revID1, revID2) {
  47. (0, ui_1.clear)();
  48. (0, ui_1.show)('<h2>loading versions...</h2>');
  49. const rev1 = (0, parser_1.getRevInfo)(revID1);
  50. const rev2 = (0, parser_1.getRevInfo)(revID2);
  51. if (!rev1) {
  52. throw new Error(`error finding ${revID1}`);
  53. }
  54. const ps = [fetchRev(rev1), fetchRev(rev2)];
  55. Promise.all(ps)
  56. .then((values) => {
  57. const d = (0, differ_1.diff)(values[1], values[0]);
  58. const rendered = (0, ui_1.render)(d);
  59. return (0, ui_1.show)(rendered);
  60. })
  61. .catch((e) => {
  62. console.log(e);
  63. (0, ui_1.show)('<h2 style="color:red">loading versions error</h2>');
  64. })
  65. .finally(() => {
  66. var _a;
  67. (_a = document.getElementById('show-trim21-cn')) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
  68. behavior: 'smooth',
  69. });
  70. });
  71. }
  72. exports.compare = compare;
  73. const _cache = {};
  74. function fetchRev(rev) {
  75. return __awaiter(this, void 0, void 0, function* () {
  76. if (!rev) {
  77. return new model_1.Commit({
  78. id: '0',
  79. comment: '',
  80. date: '',
  81. url: '',
  82. }, {
  83. title: '',
  84. rawInfo: '',
  85. description: '',
  86. });
  87. }
  88. if (!_cache[rev.id]) {
  89. const res = yield fetch(rev.url);
  90. _cache[rev.id] = new model_1.Commit(rev, (0, parser_1.parseRevDetails)(yield res.text()));
  91. }
  92. return _cache[rev.id];
  93. });
  94. }
  95.  
  96.  
  97. /***/ }),
  98.  
  99. /***/ "./src/differ.ts":
  100. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  101.  
  102.  
  103. Object.defineProperty(exports, "__esModule", ({ value: true }));
  104. exports.diff = void 0;
  105. const Diff = __webpack_require__("diff");
  106. function diff(revOld, revNew) {
  107. return [
  108. titleDiff(revOld, revNew),
  109. infoDiff(revOld, revNew),
  110. descriptionDiff(revOld, revNew),
  111. ].join('\n');
  112. }
  113. exports.diff = diff;
  114. function titleDiff(rev1, rev2) {
  115. if (rev1.details.title === rev2.details.title) {
  116. return '';
  117. }
  118. return Diff.createPatch('条目名', rev1.details.title, rev2.details.title, rev1.rev.date, rev2.rev.date);
  119. }
  120. function infoDiff(rev1, rev2) {
  121. if (rev1.details.rawInfo === rev2.details.rawInfo) {
  122. return '';
  123. }
  124. return Diff.createPatch('相关信息', rev1.details.rawInfo, rev2.details.rawInfo, rev1.rev.date, rev2.rev.date);
  125. }
  126. function descriptionDiff(rev1, rev2) {
  127. if (rev1.details.description === rev2.details.description) {
  128. return '';
  129. }
  130. return Diff.createPatch('简介', rev1.details.description, rev2.details.description, rev1.rev.date, rev2.rev.date);
  131. }
  132.  
  133.  
  134. /***/ }),
  135.  
  136. /***/ "./src/index.ts":
  137. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  138.  
  139.  
  140. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  141. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  142. return new (P || (P = Promise))(function (resolve, reject) {
  143. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  144. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  145. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  146. step((generator = generator.apply(thisArg, _arguments || [])).next());
  147. });
  148. };
  149. Object.defineProperty(exports, "__esModule", ({ value: true }));
  150. const $ = __webpack_require__("jquery");
  151. const parser_1 = __webpack_require__("./src/parser.ts");
  152. const compare_1 = __webpack_require__("./src/compare.ts");
  153. function main() {
  154. return __awaiter(this, void 0, void 0, function* () {
  155. console.log('start bgm-wiki-rev-diff UserScript');
  156. yield initUI();
  157. });
  158. }
  159. const style = `
  160. <style>
  161. #show-trim21-cn .d2h-code-line {
  162. width: calc(100% - 8em);
  163. padding-right: 0;
  164. }
  165.  
  166. #show-trim21-cn .d2h-code-line-ctn {
  167. width: calc(100% - 8em);
  168. }
  169.  
  170. #columnInSubjectA .rev-trim21-cn {
  171. margin: 0 0.2em;
  172. }
  173.  
  174. ul#pagehistory > li > * {
  175. vertical-align: middle;
  176. }
  177. </style>
  178. `;
  179. function initUI() {
  180. return __awaiter(this, void 0, void 0, function* () {
  181. $('#columnInSubjectA > hr.board').after(style + '<div id="show-trim21-cn"></div>');
  182. const s = $('#pagehistory li');
  183. const revs = Array.from(s).map(function (e) {
  184. var _a;
  185. return (_a = (0, parser_1.parseRevEl)($(e))) === null || _a === void 0 ? void 0 : _a.id;
  186. });
  187. s.each(function (index) {
  188. var _a, _b;
  189. const el = $(this);
  190. const id = revs[index];
  191. if (!id) {
  192. el.prepend('<span style="padding-right: 1.4em"> 无法用于比较 </span>');
  193. return;
  194. }
  195. el.prepend(`<input type="radio" class="rev-trim21-cn" name="rev-right" label="select to compare" value="${id}">`);
  196. el.prepend(`<input type="radio" class="rev-trim21-cn" name="rev-left" label="select to compare" value="${id}">`);
  197. const previous = (_b = (_a = revs[index + 1]) !== null && _a !== void 0 ? _a : revs[index + 2]) !== null && _b !== void 0 ? _b : '';
  198. el.prepend(`(<a href="#" data-rev="${id}" data-previous="${previous}" class="l compare-previous-trim21-cn">显示修改</a>) `);
  199. });
  200. const typeRevert = {
  201. 'rev-left': 'rev-right',
  202. 'rev-right': 'rev-left',
  203. };
  204. $('input[type="radio"]').on('change', function (e) {
  205. const name = e.target.getAttribute('name');
  206. if (!name) {
  207. return;
  208. }
  209. const selectName = typeRevert[name];
  210. const rev = e.target.getAttribute('value');
  211. if (rev) {
  212. $(`input[name="${selectName}"][value="${rev}"]`).css('visibility', 'hidden');
  213. $(`input[name="${selectName}"][value!="${rev}"]`).css('visibility', 'visible');
  214. }
  215. });
  216. $('.compare-previous-trim21-cn').on('click', function () {
  217. const el = $(this);
  218. const left = String(el.data('rev'));
  219. const right = String(el.data('previous'));
  220. $('input[name="rev-left"]').attr('checked', null);
  221. $(`input[name="rev-left"][value="${left}"]`)
  222. .attr('checked', 'true')
  223. .trigger('change');
  224. $('input[name="rev-right"]').attr('checked', null);
  225. $(`input[name="rev-right"][value="${right}"]`)
  226. .attr('checked', 'true')
  227. .trigger('change');
  228. (0, compare_1.compare)(left, right);
  229. });
  230. $('#columnInSubjectA span.text').append('<a href="#" id="compare-trim21-cn" class="l"> > 比较选中的版本</a>');
  231. $('#compare-trim21-cn').on('click', function () {
  232. const selectedRevs = getSelectedVersion();
  233. (0, compare_1.compare)(selectedRevs[0], selectedRevs[1]);
  234. });
  235. $('head').append('<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />');
  236. });
  237. }
  238. function getSelectedVersion() {
  239. const selectedVersion = [];
  240. const selectedRev = $('.rev-trim21-cn:checked');
  241. if (selectedRev.length < 2) {
  242. window.alert('请选中两个版本进行比较');
  243. throw new Error();
  244. }
  245. selectedRev.each(function () {
  246. const val = $(this).val();
  247. selectedVersion.push(val);
  248. });
  249. selectedVersion.sort((a, b) => parseInt(b) - parseInt(a));
  250. return selectedVersion;
  251. }
  252. main().catch(console.error);
  253.  
  254.  
  255. /***/ }),
  256.  
  257. /***/ "./src/model.ts":
  258. /***/ (function(__unused_webpack_module, exports) {
  259.  
  260.  
  261. Object.defineProperty(exports, "__esModule", ({ value: true }));
  262. exports.Commit = void 0;
  263. class Commit {
  264. constructor(rev, detail) {
  265. this.rev = rev;
  266. this.details = detail;
  267. }
  268. }
  269. exports.Commit = Commit;
  270.  
  271.  
  272. /***/ }),
  273.  
  274. /***/ "./src/parser.ts":
  275. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  276.  
  277.  
  278. Object.defineProperty(exports, "__esModule", ({ value: true }));
  279. exports.getRevInfo = exports.parseRevEl = exports.parseRevDetails = void 0;
  280. const $ = __webpack_require__("jquery");
  281. function parseRevDetails(html) {
  282. var _a, _b, _c, _d, _e, _f;
  283. const jq = $(html);
  284. const rawInfo = (_b = (_a = jq.find('#subject_infobox').val()) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '';
  285. const title = (_d = (_c = jq.find('input[name="subject_title"]').val()) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '';
  286. const description = (_f = (_e = jq.find('textarea#subject_summary').val()) === null || _e === void 0 ? void 0 : _e.toString()) !== null && _f !== void 0 ? _f : '';
  287. return {
  288. title,
  289. rawInfo,
  290. description,
  291. };
  292. }
  293. exports.parseRevDetails = parseRevDetails;
  294. function parseRevEl(el) {
  295. const date = el.find('a:not(.compare-previous-trim21-cn)').first().html();
  296. const revEL = el.find('a.l:contains("恢复")');
  297. const revCommentEl = el.find('span.comment');
  298. let comment = '';
  299. if (revCommentEl.length > 0) {
  300. comment = revCommentEl.html();
  301. comment = comment.substring(1, comment.length - 1);
  302. }
  303. const revHref = revEL.attr('href');
  304. if (!revHref) {
  305. // this is a merge commit, can't know what's really info
  306. return undefined;
  307. }
  308. const revID = revHref.split('/').pop();
  309. if (!revID) {
  310. throw new Error(`can't parse rev id from ${revHref}`);
  311. }
  312. return {
  313. id: revID,
  314. comment,
  315. date,
  316. url: revHref,
  317. };
  318. }
  319. exports.parseRevEl = parseRevEl;
  320. function getRevs() {
  321. const revs = [];
  322. $('#pagehistory li').each(function () {
  323. const rev = parseRevEl($(this));
  324. if (rev) {
  325. revs.push(rev);
  326. }
  327. });
  328. return revs;
  329. }
  330. function getRevInfo(revID) {
  331. for (const rev of getRevs()) {
  332. if (rev.id === revID) {
  333. return rev;
  334. }
  335. }
  336. }
  337. exports.getRevInfo = getRevInfo;
  338.  
  339.  
  340. /***/ }),
  341.  
  342. /***/ "./src/ui.ts":
  343. /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
  344.  
  345.  
  346. Object.defineProperty(exports, "__esModule", ({ value: true }));
  347. exports.clear = exports.show = exports.render = void 0;
  348. const Diff2Html = __webpack_require__("diff2html");
  349. const $ = __webpack_require__("jquery");
  350. function render(diff) {
  351. return Diff2Html.html(diff);
  352. }
  353. exports.render = render;
  354. function show(html) {
  355. $('#show-trim21-cn').html(html);
  356. }
  357. exports.show = show;
  358. function clear() {
  359. $('#show-trim21-cn').html('');
  360. }
  361. exports.clear = clear;
  362.  
  363.  
  364. /***/ }),
  365.  
  366. /***/ "jquery":
  367. /***/ (function(module) {
  368.  
  369. module.exports = $;
  370.  
  371. /***/ }),
  372.  
  373. /***/ "diff":
  374. /***/ (function(module) {
  375.  
  376. module.exports = Diff;
  377.  
  378. /***/ }),
  379.  
  380. /***/ "diff2html":
  381. /***/ (function(module) {
  382.  
  383. module.exports = Diff2Html;
  384.  
  385. /***/ })
  386.  
  387. /******/ });
  388. /************************************************************************/
  389. /******/ // The module cache
  390. /******/ var __webpack_module_cache__ = {};
  391. /******/
  392. /******/ // The require function
  393. /******/ function __webpack_require__(moduleId) {
  394. /******/ // Check if module is in cache
  395. /******/ var cachedModule = __webpack_module_cache__[moduleId];
  396. /******/ if (cachedModule !== undefined) {
  397. /******/ return cachedModule.exports;
  398. /******/ }
  399. /******/ // Create a new module (and put it into the cache)
  400. /******/ var module = __webpack_module_cache__[moduleId] = {
  401. /******/ // no module.id needed
  402. /******/ // no module.loaded needed
  403. /******/ exports: {}
  404. /******/ };
  405. /******/
  406. /******/ // Execute the module function
  407. /******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  408. /******/
  409. /******/ // Return the exports of the module
  410. /******/ return module.exports;
  411. /******/ }
  412. /******/
  413. /************************************************************************/
  414. /******/
  415. /******/ // startup
  416. /******/ // Load entry module and return exports
  417. /******/ // This entry module is referenced by other modules so it can't be inlined
  418. /******/ var __webpack_exports__ = __webpack_require__("./src/index.ts");
  419. /******/
  420. /******/ })()
  421. ;