GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

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

QingJ © 2025

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