GreasyFork Code: Syntax Highlight by highlight.js

To syntax highlight GreasyFork Code by highlight.js

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