GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

As of 2024-01-23. See the latest version.

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