Greasy Fork 支持简体中文。

bgm-wiki-rev-diff

show diff between bgm.tv wiki versions

目前為 2021-11-22 提交的版本,檢視 最新版本

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