GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

当前为 2024-01-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork Code: Syntax Highlight by PrismJS
  3. // @namespace Violentmonkey Scripts
  4. // @grant none
  5. // @version 0.3.3
  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://greasyfork.org/*
  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. function getLangClassName(pre, textContent){
  373.  
  374.  
  375.  
  376. let className = '';
  377. if (pre.classList.contains('lang-js')) {
  378. className = 'language-javascript';
  379. } else if (pre.classList.contains('lang-css')) {
  380.  
  381. const text = textContent;
  382. let m = /\n@preprocessor\s+([-_a-zA-Z]{3,8})\s*\n/.exec(text);
  383. className = 'language-css'
  384. if (m) {
  385. const preprocessor = m[1];
  386. if (preprocessor === 'stylus') {
  387. className = 'language-stylus';
  388. } else if (preprocessor === 'uso') {
  389. className = 'language-stylus';
  390. } else if (preprocessor === 'less') {
  391. className = 'language-less';
  392. } else if (preprocessor === 'default') {
  393. className = 'language-stylus';
  394. } else {
  395. className = 'language-stylus';
  396. }
  397. }
  398.  
  399. }
  400.  
  401. return className;
  402.  
  403.  
  404. }
  405.  
  406. /** @param {HTMLElement} pre */
  407. function prepareCodeAreaAsync(pre) {
  408.  
  409. if (pre.isConnected === false) return;
  410. const preParentNode = pre.parentNode;
  411. const preNextNode = pre.nextSibling;
  412.  
  413. const codeContainer = pre.closest('.code-container');
  414. const doContentEditable = codeContainer && codeContainer.querySelector('.code-container>pre:only-child');
  415.  
  416. pre.remove();
  417.  
  418. for (const li of pre.querySelectorAll('li')) {
  419. li.append(document.createTextNode('\n'));
  420. }
  421.  
  422. const textContent = pre.textContent;
  423.  
  424. const className = getLangClassName(pre, textContent);
  425.  
  426. if (!className) return;
  427.  
  428. if (doContentEditable) {
  429. // avoid selection to the outside by mouse dragging
  430. codeContainer.setAttribute('contenteditable', '');
  431. pre.setAttribute('contenteditable', 'false');
  432. }
  433.  
  434. const code = document.createElement('code');
  435.  
  436. code.classList.add(className);
  437. code.classList.add('syntax-highlighted')
  438.  
  439. let htmlCode = '';
  440.  
  441. if (className === 'language-javascript') {
  442. htmlCode = Prism.highlight(textContent, Prism.languages.javascript, 'javascript');
  443. } else if (className === 'language-stylus') {
  444. htmlCode = Prism.highlight(textContent, Prism.languages.stylus, 'stylus');
  445. } else if (className === 'language-less') {
  446. htmlCode = Prism.highlight(textContent, Prism.languages.less, 'less');
  447. } else {
  448. htmlCode = Prism.highlight(textContent, Prism.languages.css, 'css');
  449. }
  450.  
  451. // Adding line numbers
  452. htmlCode = htmlCode || '';
  453. const htmlSplit = htmlCode ? htmlCode.split('\n') : [];
  454. const totalLines = htmlSplit.length;
  455.  
  456. let mwStyle = '';
  457.  
  458. if (totalLines >= 1) {
  459. code.classList.add('hljs-add-marker-width');
  460. const len = `${totalLines}`.length * 0.5;
  461. mwStyle = `${len}em`;
  462. htmlCode = htmlSplit.map((n, i) => `<span class="marker marker-fixed-width" marker-text="${i + 1}"></span>${n}`).join('\n') || '';
  463.  
  464. } else {
  465.  
  466. code.classList.remove('hljs-add-marker-width');
  467. }
  468.  
  469. code.innerHTML = htmlCode;
  470. code.style.setProperty('--hljs-marker-width', mwStyle);
  471.  
  472. if(pre.firstChild === pre.lastChild && pre.firstChild instanceof Node){
  473. pre.firstChild.replaceWith(code);
  474. }else{
  475. pre.innerHTML = '';
  476. pre.appendChild(code);
  477. }
  478. preParentNode.insertBefore(pre, preNextNode);
  479.  
  480. }
  481.  
  482. const documentBodyHeadReady = onBodyHeadReadyAsync();
  483.  
  484. documentBodyHeadReady.then(async () => {
  485.  
  486. if (!location.pathname.endsWith('/code')) {
  487. return;
  488. }
  489.  
  490. document.head.appendChild(document.createElement('style')).textContent = `${cssAdd()}`;
  491.  
  492. self.Prism = self.Prism || {};
  493. self.Prism.manual = true;
  494. await loadJS(resoruces['prism-core.js']);
  495. await loadJS(resoruces['prism-clike.js']);
  496. await loadJS(resoruces['prism-javascript.js']);
  497. await loadJS(resoruces['prism-css.js']);
  498. await loadJS(resoruces['prism-stylus.js']);
  499.  
  500. if (document.documentElement.hasAttribute('dark')) {
  501.  
  502. loadCSS(resoruces['prism-dark.css']);
  503. } else {
  504.  
  505. loadCSS(resoruces['prism.css']);
  506. }
  507.  
  508. pScript.resolve();
  509.  
  510.  
  511.  
  512.  
  513. });
  514.  
  515. let keydownActive = false;
  516.  
  517. documentReady.then(async () => {
  518.  
  519. if (!location.pathname.endsWith('/code')) {
  520. byPass = false;
  521. return;
  522. }
  523.  
  524. await pScript.then();
  525.  
  526. await Promise.race([pElementQuery, delayPn(800)]);
  527.  
  528. const targets = document.querySelectorAll('.code-container pre.lang-js, .code-container pre.lang-css');
  529.  
  530. if (targets.length === 0) return;
  531.  
  532. await delayPn(40);
  533.  
  534. document.head.appendChild(document.createElement('style')).textContent = doActionCSS();
  535.  
  536. await delayPn(40);
  537.  
  538. byPass = false;
  539.  
  540. // Code highlighting
  541. const promises = [...targets].map(prepareCodeAreaAsync)
  542. await Promise.all(promises);
  543.  
  544. await delayPn(40);
  545. document.documentElement.setAttribute('dkkfv', '');
  546. keydownActive = true;
  547.  
  548. });
  549.  
  550. function selectAllWithinElement(element) {
  551. window.getSelection().removeAllRanges();
  552. let range = document.createRange();
  553. if (element) {
  554. range.selectNodeContents(element);
  555. window.getSelection().addRange(range);
  556. } else {
  557. console.error('Element not found with ID:', element);
  558. }
  559. }
  560. document.addEventListener('keydown', (e) => {
  561. if (keydownActive && e && e.code === 'KeyA' && e.isTrusted && (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
  562.  
  563. const target = e.target;
  564. const container = target ? target.closest('div.code-container') : null;
  565. const code = container ? container.querySelector('code') : null;
  566.  
  567. if (container && code) {
  568.  
  569. e.preventDefault();
  570. e.stopPropagation();
  571. e.stopImmediatePropagation();
  572.  
  573. setTimeout(() => {
  574. selectAllWithinElement(code);
  575. }, 1)
  576.  
  577. }
  578.  
  579. }
  580. }, true);
  581.  
  582.  
  583. })();
  584.