GreasyFork Code: Syntax Highlight by highlight.js

To syntax highlight GreasyFork Code by highlight.js

À partir de 2024-01-23. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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