bgm-wiki-rev-diff

show diff between bgm.tv wiki versions

目前为 2023-12-31 提交的版本。查看 最新版本

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