GreasyFork Code: Syntax Highlight by highlight.js

To syntax highlight GreasyFork Code by highlight.js

As of 2023-12-12. See the latest version.

  1. // ==UserScript==
  2. // @name GreasyFork Code: Syntax Highlight by highlight.js
  3. // @namespace Violentmonkey Scripts
  4. // @grant none
  5. // @version 0.3.6
  6. // @author CY Fung
  7. // @description To syntax highlight GreasyFork Code by highlight.js
  8. // @run-at document-start
  9. // @inject-into page
  10. // @unwrap
  11. // @license MIT
  12. // @match https://greatest.deepsurf.us/*
  13. // @match https://sleazyfork.org/*
  14. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js
  16. // ==/UserScript==
  17.  
  18.  
  19. (() => {
  20. let byPass = true;
  21.  
  22. const resoruces = {
  23. 'nnfx-light.css': 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/nnfx-light.min.css'
  24. }
  25.  
  26. function selectAllWithinElement(element) {
  27. // Clear any current selections
  28. window.getSelection().removeAllRanges();
  29.  
  30. // Create a new range
  31. let range = document.createRange();
  32.  
  33.  
  34. // Check if the element exists
  35. if (element) {
  36. // Select all content within the element
  37. range.selectNodeContents(element);
  38.  
  39. // Add the range to the selection
  40. window.getSelection().addRange(range);
  41. } else {
  42. console.error('Element not found with ID:', element);
  43. }
  44. }
  45. document.addEventListener('keydown', (e) => {
  46. if (e && e.code === 'KeyA' && e.isTrusted && (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
  47.  
  48. const target = e.target;
  49. const container = target ? target.closest('div.code-container') : null;
  50. const code = container ? container.querySelector('code') : null;
  51.  
  52. if (container && code) {
  53.  
  54.  
  55. e.preventDefault();
  56. e.stopPropagation();
  57. e.stopImmediatePropagation();
  58.  
  59.  
  60.  
  61.  
  62.  
  63. setTimeout(() => {
  64.  
  65. selectAllWithinElement(code)
  66.  
  67.  
  68. }, 1)
  69.  
  70. }
  71.  
  72.  
  73. }
  74. }, true)
  75.  
  76.  
  77. const Promise = (async function () { })().constructor;
  78.  
  79.  
  80. const documentReady = new Promise(resolve => {
  81. Promise.resolve().then(() => {
  82. if (document.readyState !== 'loading') {
  83. resolve();
  84. } else {
  85. window.addEventListener("DOMContentLoaded", resolve, false);
  86. }
  87. });
  88. });
  89.  
  90.  
  91. async function doAction() {
  92.  
  93. await new Promise(r => setTimeout(r, 1));
  94.  
  95. document.head.appendChild(document.createElement('style')).textContent = `
  96.  
  97. .code-container{
  98. height:100vh;
  99. }
  100. .code-container .CodeMirror, .code-container textarea{
  101. height:100%;
  102. }
  103. `;
  104.  
  105.  
  106.  
  107. if (window.requestIdleCallback) await new Promise(r => !!window.requestIdleCallback(r));
  108. else {
  109. await new Promise(r => !!window.requestAnimationFrame(r));
  110. await new Promise(r => !!window.setTimeout(r, 170));
  111. await new Promise(r => !!window.requestAnimationFrame(r));
  112. }
  113.  
  114. byPass = false;
  115.  
  116.  
  117.  
  118.  
  119.  
  120. }
  121.  
  122.  
  123. let mgg = 0;
  124. async function mTz() {
  125. if (mgg) return;
  126. mgg = 1;
  127. documentReady.then(doAction);
  128. }
  129.  
  130.  
  131.  
  132. function getElementsByTagName(tag) {
  133.  
  134. if (byPass) {
  135. if (tag === 'pre' || tag === 'code' || tag === 'xmp') {
  136. if (location.pathname.endsWith('/code')) {
  137. setTimeout(mTz, 100)
  138. return [];
  139. }
  140. }
  141. }
  142. return this.getElementsByTagName331(tag);
  143. }
  144.  
  145.  
  146.  
  147. async function onBodyHeadReadyAsync() {
  148.  
  149. if (document.body && document.head) {
  150.  
  151.  
  152. } else {
  153.  
  154. const promiseBegin = new Promise(resolve => {
  155.  
  156. let mo = new MutationObserver(() => {
  157. if (document.body && document.head) {
  158. mo.disconnect();
  159. mo.takeRecords();
  160. mo = null;
  161. resolve();
  162. }
  163.  
  164. });
  165. mo.observe(document, { subtree: true, childList: true });
  166.  
  167. });
  168.  
  169. await promiseBegin.then();
  170.  
  171. }
  172.  
  173. }
  174.  
  175.  
  176. // Load CSS
  177. function loadCSS(href) {
  178. const link = document.createElement('link');
  179. link.rel = 'stylesheet';
  180. link.href = href;
  181. document.head.appendChild(link);
  182. }
  183.  
  184. const global_css = `
  185.  
  186. html {
  187. line-height: 1.5;
  188. -webkit-text-size-adjust: 100%;
  189. -moz-tab-size: 4;
  190. -o-tab-size: 4;
  191. tab-size: 4;
  192. font-family: ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
  193. font-feature-settings: normal;
  194. font-variation-settings: normal
  195. }
  196. /*
  197. body {
  198. margin: 0;
  199. line-height: inherit
  200. }
  201.  
  202. hr {
  203. height: 0;
  204. color: inherit;
  205. border-top-width: 1px
  206. }
  207.  
  208. abbr:where([title]) {
  209. -webkit-text-decoration: underline dotted;
  210. text-decoration: underline dotted
  211. }
  212.  
  213. h1,h2,h3,h4,h5,h6 {
  214. font-size: inherit;
  215. font-weight: inherit
  216. }
  217.  
  218.  
  219. b,strong {
  220. font-weight: bolder
  221. }
  222. */
  223. .code-container code, .code-container kbd, .code-container pre, .code-container samp {
  224. font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
  225. font-size: 1em
  226. }
  227. /*
  228. small {
  229. font-size: 80%
  230. }
  231.  
  232. sub,sup {
  233. font-size: 75%;
  234. line-height: 0;
  235. position: relative;
  236. vertical-align: baseline
  237. }
  238.  
  239. sub {
  240. bottom: -.25em
  241. }
  242.  
  243. sup {
  244. top: -.5em
  245. }
  246.  
  247. table {
  248. text-indent: 0;
  249. border-color: inherit;
  250. border-collapse: collapse
  251. }
  252.  
  253. button,input,optgroup,select,textarea {
  254. font-family: inherit;
  255. font-feature-settings: inherit;
  256. font-variation-settings: inherit;
  257. font-size: 100%;
  258. font-weight: inherit;
  259. line-height: inherit;
  260. color: inherit;
  261. margin: 0;
  262. padding: 0
  263. }
  264.  
  265. button,select {
  266. text-transform: none
  267. }
  268.  
  269. :-moz-focusring {
  270. outline: auto
  271. }
  272.  
  273. :-moz-ui-invalid {
  274. box-shadow: none
  275. }
  276.  
  277. progress {
  278. vertical-align: baseline
  279. }
  280.  
  281. ::-webkit-inner-spin-button,::-webkit-outer-spin-button {
  282. height: auto
  283. }
  284.  
  285. [type=search] {
  286. -webkit-appearance: textfield;
  287. outline-offset: -2px
  288. }
  289.  
  290. ::-webkit-search-decoration {
  291. -webkit-appearance: none
  292. }
  293.  
  294. ::-webkit-file-upload-button {
  295. -webkit-appearance: button;
  296. font: inherit
  297. }
  298.  
  299. summary {
  300. display: list-item
  301. }
  302.  
  303. blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre {
  304. margin: 0
  305. }
  306.  
  307. fieldset {
  308. margin: 0
  309. }
  310.  
  311. fieldset,legend {
  312. padding: 0
  313. }
  314.  
  315. menu,ol,ul {
  316. list-style: none;
  317. margin: 0;
  318. padding: 0
  319. }
  320. textarea {
  321. resize: vertical
  322. }
  323.  
  324. input::-moz-placeholder,textarea::-moz-placeholder {
  325. opacity: 1;
  326. color: #9ca3af
  327. }
  328.  
  329. input::placeholder,textarea::placeholder {
  330. opacity: 1;
  331. color: #9ca3af
  332. }
  333. */
  334.  
  335. #script-content > .code-container[class] {
  336. width: 100%;
  337. }
  338.  
  339. .code-container[class] {
  340. border-radius: 0;
  341. }
  342.  
  343. .code-container[class] {
  344. border-radius: 0;
  345. }
  346.  
  347. .code-container > pre:only-child{
  348. padding:0;
  349. }
  350.  
  351. code.language-javascript.hljs[class] {
  352. /*
  353. font-family: ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace !important;
  354. font-size: 9pt;
  355. */
  356. font-family: monospace;
  357. font-size: 13px;
  358. font-variant-ligatures: contextual;
  359. line-height: 1.15rem;
  360. }
  361.  
  362. .hljs-comment[class], .hljs-quote[class] {
  363. font-style: inherit;
  364. color: #259789;
  365. }
  366.  
  367. .hljs-add-marker-width .marker-fixed-width[class] {
  368. user-select: none !important;
  369. width: calc(var(--hljs-marker-width, 0em) + 16px);
  370. background: #f4f4f4;
  371. padding-right: 6px;
  372. margin-right: 4px;
  373. contain: paint style;
  374. }
  375.  
  376. `;
  377.  
  378.  
  379. const cssForCodePage = /\/scripts\/\d+[^\s\/\\]*\/code(\/|$)/.test(location.href) ? `
  380.  
  381. html:not([dkkfv]) div.code-container {
  382. position: absolute !important;
  383. display: none;
  384. }
  385.  
  386. html:not([dkkfv]) div.code-container > pre{
  387. display:none;
  388. }
  389.  
  390.  
  391. html:not([dkkfv]) code:only-child {
  392. display:none;
  393. }
  394.  
  395.  
  396. .code-container,
  397. .code-container pre:only-child,
  398. .code-container pre:only-child code:only-child {
  399. max-height: calc(100vh + 4px);
  400. max-width: calc(100vw + 4px);
  401. }
  402. ` : '';
  403.  
  404.  
  405. const cssAdd = `
  406.  
  407. ${global_css}
  408.  
  409. ${cssForCodePage}
  410.  
  411. .code-container {
  412. max-width: 100%;
  413. display: inline-flex;
  414. flex-direction: column;
  415. overflow: auto;
  416. border-radius: 8px;
  417. max-height: 100%;
  418. overflow: visible;
  419. }
  420. .code-container > pre:only-child {
  421. max-width: 100%;
  422. display: inline-flex;
  423. flex-direction: column;
  424. flex-grow: 1;
  425. height: 0;
  426. }
  427. .code-container > pre:only-child > code:only-child {
  428. max-width: 100%;
  429. flex-grow: 1;
  430. height: 0;
  431. }
  432. .code-container pre code {
  433. padding: 0;
  434. font-family: Consolas;
  435. cursor: text;
  436. overflow: auto;
  437. }
  438. .code-container pre code .marker {
  439. display: inline-block;
  440. color: #636d83;
  441. text-align: right;
  442. padding-right: 20px;
  443. user-select: none;
  444. cursor: auto;
  445. }
  446.  
  447. .code-container[contenteditable]{
  448. outline: 0 !important;
  449. contain: strict;
  450. }
  451.  
  452. .code-container[contenteditable]>pre[contenteditable="false"]{
  453. contain: strict;
  454. }
  455.  
  456. `;
  457.  
  458.  
  459.  
  460.  
  461. HTMLElement.prototype.getElementsByTagName331 = HTMLElement.prototype.getElementsByTagName
  462. Document.prototype.getElementsByTagName331 = Document.prototype.getElementsByTagName
  463.  
  464. HTMLElement.prototype.getElementsByTagName = getElementsByTagName
  465. Document.prototype.getElementsByTagName = getElementsByTagName
  466.  
  467.  
  468. onBodyHeadReadyAsync().then(() => {
  469.  
  470. loadCSS(resoruces['nnfx-light.css'])
  471.  
  472. document.head.appendChild(document.createElement('style')).textContent = `${cssAdd}`;
  473.  
  474.  
  475.  
  476. });
  477.  
  478.  
  479. documentReady.then(() => {
  480.  
  481. if (!location.pathname.endsWith('/code')) {
  482. byPass = false;
  483. }
  484.  
  485. // Code highlighting
  486. for (const pre of document.querySelectorAll('pre.lang-js')) {
  487.  
  488.  
  489. for (const li of pre.querySelectorAll('li')) {
  490. li.append(document.createTextNode('\n'));
  491. }
  492.  
  493. const codeElement = document.createElement('code');
  494. codeElement.classList.add('language-javascript');
  495. codeElement.innerHTML = pre.innerHTML;
  496.  
  497. // Clearing the original code container and appending the new one
  498. pre.classList = '';
  499. pre.innerHTML = '';
  500. pre.appendChild(codeElement);
  501.  
  502. const code = pre.querySelector('code:only-child');
  503. if (!code) continue;
  504.  
  505. const codeContainer = code.closest('.code-container');
  506. if (codeContainer && codeContainer.querySelector('.code-container>pre:only-child')) {
  507. codeContainer.setAttribute('contenteditable', '');
  508. codeContainer.querySelector('.code-container>pre:only-child').setAttribute('contenteditable', 'false');
  509. codeContainer.addEventListener('keypress', () => {
  510.  
  511. })
  512. }
  513.  
  514. // Highlighting
  515. hljs.highlightElement(code);
  516.  
  517. // Adding line numbers
  518. const html = code.innerHTML || '';
  519. const htmlSplit = html ? html.split('\n') : [];
  520. const totalLines = htmlSplit.length;
  521.  
  522. if (totalLines >= 1) {
  523. code.classList.add('hljs-add-marker-width');
  524. code.style.setProperty('--hljs-marker-width', `${String(totalLines).length * 0.5}em`);
  525. code.innerHTML = htmlSplit.map((n, i) => `<span class="marker marker-fixed-width">${i + 1}</span>${n}`).join('\n');
  526. } else {
  527. code.classList.remove('hljs-add-marker-width');
  528. code.style.setProperty('--hljs-marker-width', '');
  529. }
  530.  
  531.  
  532. }
  533.  
  534. setTimeout(() => {
  535. document.documentElement.setAttribute('dkkfv', '');
  536. }, 1);
  537.  
  538.  
  539. });
  540.  
  541.  
  542. })();
  543.