StackOverflow extended

Copy code to clipboard; hiding and saving the state of the "Blog", "Meta" blocks by clicking; adding links to all questions of the author and all questions only with tags of the current question to the user's card; stretching and restoring page content for better reading of code listings; redirecting from localized versions of the site to an English-language domain with a search for the current question.

当前为 2022-09-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name StackOverflow extended
  3. // @namespace https://github.com/XelaNimed
  4. // @version 0.10.1
  5. // @description Copy code to clipboard; hiding and saving the state of the "Blog", "Meta" blocks by clicking; adding links to all questions of the author and all questions only with tags of the current question to the user's card; stretching and restoring page content for better reading of code listings; redirecting from localized versions of the site to an English-language domain with a search for the current question.
  6. // @author XelaNimed
  7. // @copyright 2021, XelaNimed (https://github.com/XelaNimed)
  8. // @match https://*.stackoverflow.com/*
  9. // @match https://*.meta.stackoverflow.com/*
  10. // @grant GM_getResourceText
  11. // @grant GM_addStyle
  12. // @homepageURL https://github.com/XelaNimed/ruSO
  13. // @supportURL https://github.com/XelaNimed/ruSO/issues
  14. // @iconURL https://www.google.com/s2/favicons?domain=stackoverflow.com&sz=32
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js#sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/izimodal/1.6.1/js/iziModal.min.js#sha512-lR/2z/m/AunQdfBTSR8gp9bwkrjwMq1cP0BYRIZu8zd4ycLcpRYJopB+WsBGPDjlkJUwC6VHCmuAXwwPHlacww==
  17. // @resource IZI_MODAL https://cdnjs.cloudflare.com/ajax/libs/izimodal/1.6.1/css/iziModal.min.css
  18. // @license MIT
  19. // ==/UserScript==
  20.  
  21.  
  22. const $ = window.jQuery;
  23.  
  24. const ruSO = {
  25. $sidebar: $('#sidebar'),
  26. $content: $('#content'),
  27. $container: $('body>.container'),
  28. $fullWidthBtn: null,
  29. keys: {
  30. metaBlockVisibility: 'showMetaPosts',
  31. contentMaxWidth: 'contentMaxWidth',
  32. containerMaxWidth: 'containerMaxWidth',
  33. fooFullWidth: 'fooFullWidth',
  34. nativeLang: 'nativeLanguage',
  35. useSearchRedirectBtn: 'useSearchRedirectBtn',
  36. addLinkToMeta: 'addLinkToMeta',
  37. toggleMetaBlock: 'toggleMetaBlock'
  38. },
  39. strings: {
  40. clickToToggle: 'Скрыть/показать',
  41. setFullWidth: 'Растянуть',
  42. resetFullWidth: 'Восстановить',
  43. copy: 'Копировать',
  44. copied: 'Скопировано',
  45. canNotCopy: 'Упс, ошибка',
  46. intoClipboard: 'В буфер'
  47. },
  48.  
  49. // local storage access
  50. isLSNotInitForKey: function(key) { return localStorage[key] === undefined || localStorage[key] == null || localStorage[key] === ''; },
  51. setLSDefaults: function() {
  52.  
  53. if(this.isLSNotInitForKey(this.keys.nativeLang)) {
  54. const lang = navigator.language || navigator.userLanguage;
  55. if(this.getSupportedSubDomains().includes(lang)) {
  56. localStorage[this.keys.nativeLang] = lang;
  57. } else {
  58. localStorage[this.keys.useSearchRedirectBtn] = false;
  59. }
  60. }
  61. if(this.isLSNotInitForKey(this.keys.useSearchRedirectBtn)) {
  62. localStorage[this.keys.useSearchRedirectBtn] = true;
  63. }
  64. if(this.isLSNotInitForKey(this.keys.toggleMetaBlock)) {
  65. localStorage[this.keys.toggleMetaBlock] = true;
  66. }
  67. if(this.isLSNotInitForKey(this.keys.metaBlockVisibility)) {
  68. localStorage[this.keys.metaBlockVisibility] = true;
  69. }
  70. },
  71. isUseSearchRedirectBtn: function() { return localStorage[this.keys.useSearchRedirectBtn] == 'true'; },
  72. getNativeLang: function() { return localStorage[this.keys.nativeLang]; },
  73. isNativeLang: function(lang) { return localStorage[this.keys.nativeLang] === lang; },
  74. addLinkToMeta: function() { return localStorage[this.keys.addLinkToMeta] == 'true'; },
  75. toggleMetaBlock: function() { return localStorage[this.keys.toggleMetaBlock] == 'true'; },
  76. getSupportedSubDomains: function() { return ['ru', 'es', 'pt', 'ja']; },
  77. addSettingsModalDialog: function() {
  78.  
  79. let options = this.getSupportedSubDomains()
  80. .flatMap((l) => '<option value="' + l + '"' + (this.isNativeLang(l) ? ' selected="selected"' : '') + '>' + l + '</option>' )
  81. .join('');
  82.  
  83. $(document.body).append(`<div id="iziModal" style="display: none;">
  84.  
  85. <div class="izi-content">
  86.  
  87. <div class="d-flex ai-center jc-space-between p16">
  88. <label class="flex--item s-label p0" for="so-ext-search-btn-toggle">
  89. <div class="d-flex ai-center">Use redirect to enSO</div>
  90. <p class="s-description">When this option is enabled, a button redirecting the current search to the English-language StackOverflow site will be added at the end of the search field.</p>
  91. </label>
  92. <div class="flex--item s-toggle-switch">
  93. <input id="so-ext-search-btn-toggle" type="checkbox"${this.isUseSearchRedirectBtn() ? 'checked="checked"' : ''}>
  94. <div class="s-toggle-switch--indicator"></div>
  95. </div>
  96. </div>
  97.  
  98. <div id="so-ext-native-language-block" class="d-flex ai-center jc-space-between p16${this.isUseSearchRedirectBtn() ? '' : ' o50 pe-none'}">
  99. <label class="s-label flex--item" for="so-ext-native-language">Native language
  100. <p class="s-description">The two-letter code of your language, if it is different from English. Used when redirecting search queries from the localized site to the English version and back.</p>
  101. </label>
  102. <div class="d-flex">
  103. <select id="so-ext-native-language" class="flex--item s-input" style="width: 75px;" autofocus="true">
  104. ${options}
  105. </select>
  106. </div>
  107. </div>
  108.  
  109. <div class="d-flex ai-center jc-space-between p16">
  110. <label class="flex--item s-label p0" for="so-ext-add-meta-link">
  111. <div class="d-flex ai-center">Add link to Meta</div>
  112. <p class="s-description">If this option is enabled, a link to Meta will be added to the side menu.</p>
  113. </label>
  114. <div class="flex--item s-toggle-switch">
  115. <input id="so-ext-add-meta-link" type="checkbox"${this.addLinkToMeta() ? 'checked="checked"' : ''}>
  116. <div class="s-toggle-switch--indicator"></div>
  117. </div>
  118. </div>
  119.  
  120. <div class="d-flex ai-center jc-space-between p16">
  121. <label class="flex--item s-label p0" for="so-ext-toggle-meta-block">
  122. <div class="d-flex ai-center">Minimize the Meta block</div>
  123. <p class="s-description">If this option is enabled, the Meta block with popular questions can be minimized and maximized with the state saved in the local storage.</p>
  124. </label>
  125. <div class="flex--item s-toggle-switch">
  126. <input id="so-ext-toggle-meta-block" type="checkbox"${this.toggleMetaBlock() ? 'checked="checked"' : ''}>
  127. <div class="s-toggle-switch--indicator"></div>
  128. </div>
  129. </div>
  130.  
  131. <div class="d-flex ai-center p16 button-panel">
  132. <button class="flex--item s-btn s-btn__filled" role="button" value="cancel">Cancel</button>
  133. <button class="flex--item s-btn s-btn__filled" role="button" value="save">Save</button>
  134. <button class="flex--item s-btn s-btn__primary" role="button" value="save-reload">Save and reload</button>
  135. </div>
  136.  
  137. </div>
  138.  
  139. </div>`);
  140. },
  141.  
  142. init: function() {
  143.  
  144. 'use strict';
  145.  
  146. this.setLSDefaults();
  147. this.addSettingsModalDialog();
  148.  
  149. GM_addStyle(GM_getResourceText("IZI_MODAL"));
  150. GM_addStyle('#iziModal { background-color: var(--theme-content-background-color); box-shadow: 0px 0px 2px 0px var(--theme-button-color); }'+
  151. '#iziModal * { font-family: var(--theme-body-font-family); }' +
  152. '#iziModal .iziModal-header-title, #iziModal label { color: var(--theme-body-font-color); }' +
  153. '#iziModal .iziModal-header-subtitle { color: var(--theme-footer-link-color); }' +
  154. '#iziModal .iziModal-header .iziModal-button-close { background: ' +
  155. 'url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAAl' +
  156. 'CAYAAADFniADAAAACXBIWXMAAAsTAAALEwEAmpwYAAADIklEQVRYhe2XTWjUQ' +
  157. 'BSA1/qHP+A//oAgqCcVFA9W7WYSu1YPBfWwIFWbmSys+FM9qcfFiqggFMWDJ/' +
  158. 'EmFfWooCfRg4gXZRWUaruzSX8Ei9aTRbu+l0x2s9mNu8lmC0IehCTz8+Z7782' +
  159. 'bvMRikUQSyX8ouYS2xlC0Dq7QLk7UHp2ohw1Z3f1pf8/caQXBBfMyO6PL7DXc' +
  160. 'p+BecF95wn7kCe3XJW1n04G4xA7BYkOlxekfnbCsLtPHOqEP8jJ9AW2j5ZD00' +
  161. 'SBRV4UOU4jFZsCCvUXPEKZjuIbi6dWVYzMteaK2wvg74LHfAn6IE21zqFCg9K' +
  162. 'LD8j7empxXzzwu0S0A98EEk+lEaGBc1o6gh6yLHse2AsnMqjUPx6CHPyfSiyC' +
  163. '0zwXYF96RWtoQUK6tawkoGxchu4xtepyuResNoh70mvdme3o27iWYcxvB9PZj' +
  164. 'y+B5QITyVkNQoOCaCNu7QjI5EzesrRzuv6qBmUCEPXQkw3XTGFltF+Gf5CS1I' +
  165. 'RAQQoBHvqIiQ6YHSm3snmN/TWJGlgGhhxz9TnCY+9QKI7sUCIormiSUj+Ni5b' +
  166. 'CVYLWATCiFddueDwQFXrogFNx397lDhACQ/i+L7xha4V2nDLexFfaYkb1HF/i' +
  167. 'Ggoy5IRa4Wq2/0mPeHnIZO2FCEXWdbyiw/K61n9h5rzFGZ3q++wTH5PinXpkN' +
  168. 'in21IwCUlXlei1Tuobo99dPUK9GNvqF0op1r5p4aIycX+oYyJBqvln1eWVYtK' +
  169. '90egzbVgqZvfQOhlJ1TQnmttK8FBvqeic9NbyAo0zLIPBGObMMnOmEJe15O6V' +
  170. '4fGAogFoOrvwmrr5jK6/j2lTxG+/DbZ5D0cjvr4LoZGMgWrBJECk+BlSfMRX1' +
  171. 'UCWgYFn/CSwMNVwm2FMMoLK33NIaafStk5Ue7noKwbQoFCMUsP+CIMMtfK5TD' +
  172. 'sMhZ/HGoHJtp0RW6Cw9fezw856Be3xYakFPAY51YqBU3MRZ+hL6H5yelGp2Nu' +
  173. 'fr7R/ekVjYFyJZsMjnHIPQ0QLzy+puBvu+40QN9ShoV/HHgMt0HoaUAcwozEn' +
  174. '+rEHzaYSKJJJIQ5C/46/lP65NjdQAAAABJRU5ErkJggg==\') no-repeat 50% 50%;' +
  175. 'width: 48px; }' +
  176. '#iziModal .izi-content { background-color: var(--theme-content-background-color); }' +
  177. '#iziModal .izi-content > div { border-bottom: 1px solid var(--theme-content-border-color); }' +
  178. '#iziModal .izi-content > div:last-child { border-bottom: 0; }' +
  179. '#iziModal label { }' +
  180. '#iziModal .s-description { color: var(--theme-footer-link-color); }' +
  181. '#iziModal .button-panel { justify-content: flex-end; background-color: var(--theme-footer-background-color); }' +
  182. '#iziModal .button-panel button { margin-left: 10px; }' +
  183. 'body > div.iziModal-overlay { backdrop-filter: blur(2px); }');
  184.  
  185. return this;
  186. },
  187. initLocalStorage: function initLocalStorage() {
  188. localStorage[this.keys.containerMaxWidth] = this.$container.css('max-width');
  189. localStorage[this.keys.contentMaxWidth] = this.$content.css('max-width');
  190. localStorage[this.keys.fooFullWidth] = 'setFullWidth';
  191. return this;
  192. },
  193. addButtons: function () {
  194. let self = this,
  195. addScriptSettings = function() {
  196. $('<li><ol class="nav-links"><a href="#" class="nav-links--link">UserScript settings</a></li></ol></li>')
  197. .on('click', 'a', function(e) {
  198. e.preventDefault();
  199. e.stopPropagation();
  200. $("#iziModal").iziModal({
  201. title: 'Extended StackOverflow Settings',
  202. subtitle: 'All settings are saved in the local storage and will take effect when the page reloads',
  203. headerColor: 'var(--theme-footer-background-color)',
  204. // background: 'rgba(78, 78, 71, 1)',
  205. // icon: '.close-icon',
  206. // iconText: null,
  207. //iconColor: 'var(--theme-body-font-color)',
  208. width: 600,
  209. radius: 'var(--br-sm)',
  210. borderBottom: false,
  211. zindex: 9999,
  212. focusInput: true,
  213. bodyOverflow: false,
  214. // fullscreen: true,
  215. // openFullscreen: false,
  216. appendToOverlay: 'body', // or false
  217. overlay: true,
  218. overlayClose: true,
  219. // overlayColor: 'rgba(0, 0, 0, 0.3)'
  220. }).iziModal('open');
  221. })
  222. .insertAfter($('#left-sidebar nav > ol > li').last());
  223.  
  224. $('#iziModal')
  225. .on('click', 'button', function(e) {
  226. if(e.target.value.startsWith('save')) {
  227. localStorage[self.keys.nativeLang] = $('#so-ext-native-language option:selected').val();
  228. localStorage[self.keys.useSearchRedirectBtn] = $('#so-ext-search-btn-toggle').is(':checked');
  229. localStorage[self.keys.addLinkToMeta] = $('#so-ext-add-meta-link').is(':checked');
  230. localStorage[self.keys.toggleMetaBlock] = $('#so-ext-toggle-meta-block').is(':checked');
  231. }
  232. if(e.target.value.endsWith('reload')) {
  233. document.location.reload();
  234. }
  235. $('#iziModal').iziModal('close');
  236. })
  237. .on('change', 'input', function(e) {
  238. if(e.target.id == 'so-ext-search-btn-toggle') {
  239. $('#so-ext-native-language-block')[e.target.checked ? 'removeClass' : 'addClass']('o50 pe-none');
  240. }
  241. });
  242. },
  243. addWatchedTags = function () {
  244. let tags = [],
  245. urlPrefix = window.location.origin + '/questions/tagged/';
  246. $('.js-watched-tag-list a.user-tag').each(function (idx, itm) {
  247. let url = itm.href;
  248. tags.push(url.substring(url.lastIndexOf('/') + 1));
  249. });
  250. if (tags.length) {
  251. let url = urlPrefix + tags.join('+or+');
  252. let $header = self.$sidebar.find(".js-tag-preferences-container > div").first().find("h2");
  253. if ($header.length > 0) {
  254.  
  255. $header[0].innerHTML = '<a class="post-tag user-tag" href="' + url + '">' + $header.text() + '</a>';
  256. }
  257. }
  258. },
  259. addMetaToggles = function () {
  260. if(!self.toggleMetaBlock()) {
  261. return;
  262. }
  263. let showHideMetas = function ($elem) {
  264. let isVisible = localStorage[self.keys.metaBlockVisibility] === 'true';
  265. let $elems = $elem.parent().children('li.s-sidebarwidget--item');
  266. $elems.each(function(idx, itm){
  267. let $itm = $(itm);
  268. if(isVisible)
  269. {
  270. $itm.removeAttr('style');
  271. } else {
  272. $itm.attr('style', 'display: none !important');
  273. }
  274. });
  275. };
  276. self.$sidebar
  277. .find('div.s-sidebarwidget li.s-sidebarwidget--header')
  278. .each(function (idx, itm) {
  279. let $itm = $(itm);
  280. $itm
  281. .attr('title', ruSO.strings.clickToToggle)
  282. .css('cursor', 'pointer')
  283. .on('click', function (e) {
  284. let isVisible = localStorage.getItem(self.keys.metaBlockVisibility) === 'true';
  285. localStorage.setItem(self.keys.metaBlockVisibility, !isVisible);
  286. showHideMetas($(e.target));
  287. });
  288. showHideMetas($itm);
  289. });
  290. },
  291. addLinkToMeta = function () {
  292. if(!self.addLinkToMeta()) {
  293. return;
  294. }
  295. const isMeta = window.location.host.includes('meta.');
  296. const link = isMeta ? window.location.host.split('.').filter(part => part !== 'meta').join('.')
  297. : 'meta.' + window.location.host;
  298. const linkText = isMeta ? 'StackOverflow' : 'Meta'
  299. $('<li><ol class="nav-links"><a href="https://' + link + '" class="nav-links--link">' + linkText + '</a></ol></li>').insertAfter($('#left-sidebar nav > ol > li').last());
  300. },
  301. addFullWidth = function () {
  302. let $header = $('#question-header');
  303. self.$fullWidthBtn = $header.find('div').clone();
  304. self.$fullWidthBtn.attr('id', 'set-full-width-btn').find('a')
  305. .removeClass('s-btn__primary')
  306. .addClass('s-btn__filled')
  307. .attr('href', '#')
  308. .text(self.strings.setFullWidth)
  309. .on('click', function () {
  310. self[localStorage[self.keys.fooFullWidth]]();
  311. });
  312. $header.append(self.$fullWidthBtn);
  313. },
  314. addRedirectToSO = function () {
  315. if(!self.isUseSearchRedirectBtn()) {
  316. return;
  317. }
  318. let localPrefix = self.getNativeLang() + '.';
  319. let isLocalSO = location.host.substr(0, 3) === localPrefix;
  320. let btnText = isLocalSO ? 'en' : self.getNativeLang();
  321. let $btn = $('<div class="print:d-none"><a href="#" class="s-btn s-btn__filled s-btn__xs s-btn__icon ws-nowrap">' + btnText + '</a></div>');
  322. $btn.insertAfter($('#search'));
  323. $btn.on('click', function () {
  324. location.host = isLocalSO ? location.host.substr(localPrefix.length)
  325. : localPrefix + location.host;
  326. });
  327. };
  328. addWatchedTags();
  329. addMetaToggles();
  330. addLinkToMeta();
  331. addFullWidth();
  332. addRedirectToSO();
  333. addScriptSettings();
  334. return this;
  335. },
  336. addAuthorQuestionsLinks: function () {
  337. let $userDetails = $('div.owner > div.user-info > div.user-details');
  338. if ($userDetails.length > 0) {
  339. let $postTags = $('div.post-taglist').find('a.post-tag');
  340. let tags = [];
  341. for (const $postTag of $postTags) {
  342. tags.push('[' + $postTag.href.split('/').slice(-1).pop() + ']');
  343. }
  344. let tagsUrl = tags.join('+or+');
  345. for (const $userDetail of $userDetails) {
  346. const $userUrl = $userDetail.find('a');
  347. const userName = $userUrl.text();
  348. const userId = $userUrl[0].href.split('/')[4];
  349. const baseSearchUrl = 'https://ru.stackoverflow.com/search?tab=newest&q=user%3A' + userId + '+is%3Aq';
  350. let elem = '<span>? <a href="' + baseSearchUrl + '" title="Все вопросы ' + userName + '">все</a>';
  351. if (tags.length > 0) {
  352. elem += ', <a href="' + baseSearchUrl + '+' + tagsUrl + '" title="Вопросы ' + userName + ' с метками текущего вопроса">с такими-же метками</a>';
  353. }
  354. elem += '</span>';
  355. $(elem).insertAfter($userDetail);
  356. }
  357. }
  358. return this;
  359. },
  360. setFullWidth: function () {
  361. this.$container.add(this.$content).css({
  362. 'max-width': 'none'
  363. });
  364. this.$fullWidthBtn.find('a').text(this.strings.resetFullWidth);
  365. localStorage[this.keys.fooFullWidth] = 'resetFullWidth';
  366. return this;
  367. },
  368. resetFullWidth: function () {
  369. this.$container.css({
  370. 'max-width': localStorage[this.keys.containerMaxWidth]
  371. });
  372. this.$content.css({
  373. 'max-width': localStorage[this.keys.contentMaxWidth]
  374. });
  375. this.$fullWidthBtn.find('a').text(this.strings.setFullWidth);
  376. localStorage[this.keys.fooFullWidth] = 'setFullWidth';
  377. return this;
  378. },
  379. selectElemText: function(elem) {
  380. const range = document.createRange();
  381. range.selectNodeContents(elem);
  382. const sel = window.getSelection();
  383. sel.removeAllRanges();
  384. sel.addRange(range);
  385. },
  386. getSelectedText: function() {
  387. let text = '';
  388. if (window.getSelection) {
  389. text = window.getSelection();
  390. } else if (document.getSelection) {
  391. text = document.getSelection();
  392. } else if (document.selection) {
  393. text = document.selection.createRange().text;
  394. }
  395. return text;
  396. },
  397. copyToClipboard: function(text) {
  398. if (window.clipboardData && window.clipboardData.setData) {
  399. return window.clipboardData.setData("Text", text);
  400. } else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
  401. const textarea = document.createElement("textarea");
  402. textarea.textContent = text;
  403. textarea.style.position = "fixed";
  404. document.body.appendChild(textarea);
  405. textarea.select();
  406. try {
  407. return document.execCommand("copy");
  408. } catch (ex) {
  409. console.warn("Copy to clipboard failed", ex);
  410. return false;
  411. } finally {
  412. document.body.removeChild(textarea);
  413. }
  414. }
  415. },
  416. addCopyToClipboard: function() {
  417.  
  418. const self = this;
  419.  
  420. $('.snippet-ctas').each(function() {
  421. const $el = $(this);
  422. const $availableBtn = $el.find('.copySnippet');
  423. const $snipBtn = $availableBtn.clone();
  424. $snipBtn.val(self.strings.intoClipboard);
  425. $snipBtn.click(function() {
  426.  
  427. let code = "";
  428.  
  429. $snipBtn.closest('.snippet-code').find('pre > code').each(function() {
  430. self.selectElemText(this);
  431. let selectedText = self.getSelectedText();
  432. code += selectedText + '\n';
  433. window.getSelection().removeAllRanges();
  434. });
  435.  
  436. if(self.copyToClipboard(code)) {
  437. $snipBtn.val(self.strings.copied);
  438. } else {
  439. $snipBtn.val(self.strings.canNotCopy);
  440. }
  441.  
  442. setTimeout(function () {
  443. $snipBtn.val(self.strings.intoClipboard);
  444. }, 2000);
  445. });
  446. $availableBtn.after($snipBtn);
  447. });
  448.  
  449. $("pre").each(function () {
  450.  
  451. const $pre = $(this);
  452. const $parent = $pre.parent();
  453.  
  454. if($parent.hasClass('snippet-code')) {
  455. const padding = ($parent.innerWidth() - $parent.width()) / 2;
  456. $pre.wrapAll('<div style="position: relative; padding-bottom: ' + padding + 'px;"></div>');
  457. } else {
  458. $pre.wrapAll('<div style="position: relative;"></div>');
  459. }
  460.  
  461. const $btn = $("<button class='copy-code-button s-btn s-btn__filled s-btn__xs'>" + self.strings.copy + "</button>");
  462. $btn.css({
  463. "position": "absolute",
  464. "top": "6px",
  465. "right": "12px",
  466. "display": "none"
  467. });
  468. $pre.append($btn);
  469.  
  470. let $container = $btn.siblings("code");
  471. $pre.hover(function () {
  472. $btn.css("display", "block");
  473. }, function () {
  474. $btn.css("display", "none");
  475. });
  476.  
  477. setTimeout(function () {
  478. if ($container.length == 0) {
  479. $pre.contents().filter(function () {
  480. return this.className !== "copy-code-button";
  481. }).wrapAll('<code style= "overflow-x: auto; padding: 0px;"></code>');
  482. $container = $btn.siblings("code").get(0);
  483. } else {
  484. $container = $container.get(0);
  485. }
  486. }, 0);
  487.  
  488. $btn.click(function () {
  489. self.selectElemText($container);
  490. const selectedText = self.getSelectedText();
  491. let buttonNewText = "";
  492. if (self.copyToClipboard(selectedText)) {
  493. buttonNewText = self.strings.copied;
  494. } else {
  495. buttonNewText = self.strings.canNotCopy;
  496. }
  497. window.getSelection().removeAllRanges();
  498. $(this).text(buttonNewText);
  499. const that = this;
  500. setTimeout(function () {
  501. $(that).text(self.strings.copy);
  502. }, 400);
  503. });
  504. });
  505. return this;
  506. }
  507. };
  508.  
  509. ruSO.init();
  510.  
  511. window.addEventListener('load', function () {
  512. ruSO
  513. .initLocalStorage()
  514. .addButtons()
  515. .addAuthorQuestionsLinks()
  516. .addCopyToClipboard();
  517. }, false);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址