GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

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

  1. // ==UserScript==
  2. // @name GreasyFork Code: Syntax Highlight by PrismJS
  3. // @namespace Violentmonkey Scripts
  4. // @grant none
  5. // @version 0.2.7
  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. let byPass = true;
  20.  
  21. const cdn = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0';
  22. const resoruces = {
  23. 'prism-core.js': `${cdn}/components/prism-core.js`,
  24. 'prism-clike.js': `${cdn}/components/prism-clike.min.js`,
  25. 'prism-javascript.js': `${cdn}/components/prism-javascript.min.js`,
  26. 'prism-css.js': `${cdn}/components/prism-css.min.js`,
  27. 'prism-stylus.js': `${cdn}/components/prism-stylus.min.js`,
  28. 'prism.css': `${cdn}/themes/prism.min.css`,
  29. 'prism-dark.css': `${cdn}/themes/prism-dark.min.css`,
  30. }
  31.  
  32. function selectAllWithinElement(element) {
  33. // Clear any current selections
  34. window.getSelection().removeAllRanges();
  35.  
  36. // Create a new range
  37. let range = document.createRange();
  38.  
  39. // Check if the element exists
  40. if (element) {
  41. // Select all content within the element
  42. range.selectNodeContents(element);
  43.  
  44. // Add the range to the selection
  45. window.getSelection().addRange(range);
  46. } else {
  47. console.error('Element not found with ID:', element);
  48. }
  49. }
  50. document.addEventListener('keydown', (e) => {
  51. if (e && e.code === 'KeyA' && e.isTrusted && (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
  52.  
  53. const target = e.target;
  54. const container = target ? target.closest('div.code-container') : null;
  55. const code = container ? container.querySelector('code') : null;
  56.  
  57. if (container && code) {
  58.  
  59. e.preventDefault();
  60. e.stopPropagation();
  61. e.stopImmediatePropagation();
  62.  
  63. setTimeout(() => {
  64. selectAllWithinElement(code);
  65. }, 1)
  66.  
  67. }
  68.  
  69. }
  70. }, true)
  71.  
  72.  
  73. const Promise = (async function () { })().constructor;
  74.  
  75.  
  76. const PromiseExternal = ((resolve_, reject_) => {
  77. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  78. return class PromiseExternal extends Promise {
  79. constructor(cb = h) {
  80. super(cb);
  81. if (cb === h) {
  82. /** @type {(value: any) => void} */
  83. this.resolve = resolve_;
  84. /** @type {(reason?: any) => void} */
  85. this.reject = reject_;
  86. }
  87. }
  88. };
  89. })();
  90.  
  91. const documentReady = new Promise(resolve => {
  92. Promise.resolve().then(() => {
  93. if (document.readyState !== 'loading') {
  94. resolve();
  95. } else {
  96. window.addEventListener("DOMContentLoaded", resolve, false);
  97. }
  98. });
  99. });
  100.  
  101.  
  102. async function doAction() {
  103.  
  104. await new Promise(r => setTimeout(r, 1));
  105.  
  106. document.head.appendChild(document.createElement('style')).textContent = `
  107.  
  108. .code-container{
  109. height:100vh;
  110. }
  111. .code-container .CodeMirror, .code-container textarea{
  112. height:100%;
  113. }
  114. `;
  115.  
  116. if (window.requestIdleCallback) await new Promise(r => !!window.requestIdleCallback(r));
  117. else {
  118. await new Promise(r => !!window.requestAnimationFrame(r));
  119. await new Promise(r => !!window.setTimeout(r, 170));
  120. await new Promise(r => !!window.requestAnimationFrame(r));
  121. }
  122.  
  123. byPass = false;
  124.  
  125. }
  126.  
  127.  
  128. let mgg = 0;
  129. async function mTz() {
  130. if (mgg) return;
  131. mgg = 1;
  132. documentReady.then(doAction);
  133. }
  134.  
  135. function getElementsByTagName(tag) {
  136. if (byPass) {
  137. if (tag === 'pre' || tag === 'code' || tag === 'xmp') {
  138. if (location.pathname.endsWith('/code')) {
  139. setTimeout(mTz, 100)
  140. return [];
  141. }
  142. }
  143. }
  144. return this.getElementsByTagName331(tag);
  145. }
  146.  
  147. async function onBodyHeadReadyAsync() {
  148.  
  149. if (document.body && document.head) {
  150.  
  151.  
  152. } else {
  153.  
  154. const promiseBegin = new Promise(resolve => {
  155.  
  156. let mo = new MutationObserver(() => {
  157. if (document.body && document.head) {
  158. mo.disconnect();
  159. mo.takeRecords();
  160. mo = null;
  161. resolve();
  162. }
  163.  
  164. });
  165. mo.observe(document, { subtree: true, childList: true });
  166.  
  167. });
  168.  
  169. await promiseBegin.then();
  170.  
  171. }
  172.  
  173. }
  174.  
  175.  
  176. // Load CSS
  177. function loadJS(href) {
  178.  
  179. return new Promise(resolve => {
  180.  
  181. const script = document.createElement('script');
  182. script.src = href;
  183. script.onload = () => {
  184. resolve(script);
  185. };
  186. document.head.appendChild(script);
  187.  
  188. });
  189.  
  190. }
  191.  
  192. // Load CSS
  193. function loadCSS(href) {
  194. const link = document.createElement('link');
  195. link.rel = 'stylesheet';
  196. link.href = href;
  197. document.head.appendChild(link);
  198. return link;
  199. }
  200.  
  201. const global_css = `
  202.  
  203. html {
  204. line-height: 1.5;
  205. -webkit-text-size-adjust: 100%;
  206. -moz-tab-size: 4;
  207. -o-tab-size: 4;
  208. tab-size: 4;
  209. 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;
  210. font-feature-settings: normal;
  211. font-variation-settings: normal
  212. }
  213. /*
  214. body {
  215. margin: 0;
  216. line-height: inherit
  217. }
  218.  
  219. hr {
  220. height: 0;
  221. color: inherit;
  222. border-top-width: 1px
  223. }
  224.  
  225. abbr:where([title]) {
  226. -webkit-text-decoration: underline dotted;
  227. text-decoration: underline dotted
  228. }
  229.  
  230. h1,h2,h3,h4,h5,h6 {
  231. font-size: inherit;
  232. font-weight: inherit
  233. }
  234.  
  235.  
  236. b,strong {
  237. font-weight: bolder
  238. }
  239. */
  240. .code-container code, .code-container kbd, .code-container pre, .code-container samp {
  241. font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
  242. font-size: 1em
  243. }
  244. /*
  245. small {
  246. font-size: 80%
  247. }
  248.  
  249. sub,sup {
  250. font-size: 75%;
  251. line-height: 0;
  252. position: relative;
  253. vertical-align: baseline
  254. }
  255.  
  256. sub {
  257. bottom: -.25em
  258. }
  259.  
  260. sup {
  261. top: -.5em
  262. }
  263.  
  264. table {
  265. text-indent: 0;
  266. border-color: inherit;
  267. border-collapse: collapse
  268. }
  269.  
  270. button,input,optgroup,select,textarea {
  271. font-family: inherit;
  272. font-feature-settings: inherit;
  273. font-variation-settings: inherit;
  274. font-size: 100%;
  275. font-weight: inherit;
  276. line-height: inherit;
  277. color: inherit;
  278. margin: 0;
  279. padding: 0
  280. }
  281.  
  282. button,select {
  283. text-transform: none
  284. }
  285.  
  286. :-moz-focusring {
  287. outline: auto
  288. }
  289.  
  290. :-moz-ui-invalid {
  291. box-shadow: none
  292. }
  293.  
  294. progress {
  295. vertical-align: baseline
  296. }
  297.  
  298. ::-webkit-inner-spin-button,::-webkit-outer-spin-button {
  299. height: auto
  300. }
  301.  
  302. [type=search] {
  303. -webkit-appearance: textfield;
  304. outline-offset: -2px
  305. }
  306.  
  307. ::-webkit-search-decoration {
  308. -webkit-appearance: none
  309. }
  310.  
  311. ::-webkit-file-upload-button {
  312. -webkit-appearance: button;
  313. font: inherit
  314. }
  315.  
  316. summary {
  317. display: list-item
  318. }
  319.  
  320. blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre {
  321. margin: 0
  322. }
  323.  
  324. fieldset {
  325. margin: 0
  326. }
  327.  
  328. fieldset,legend {
  329. padding: 0
  330. }
  331.  
  332. menu,ol,ul {
  333. list-style: none;
  334. margin: 0;
  335. padding: 0
  336. }
  337. textarea {
  338. resize: vertical
  339. }
  340.  
  341. input::-moz-placeholder,textarea::-moz-placeholder {
  342. opacity: 1;
  343. color: #9ca3af
  344. }
  345.  
  346. input::placeholder,textarea::placeholder {
  347. opacity: 1;
  348. color: #9ca3af
  349. }
  350. */
  351.  
  352. #script-content > .code-container[class] {
  353. width: 100%;
  354. }
  355.  
  356. .code-container[class] {
  357. border-radius: 0;
  358. }
  359.  
  360. .code-container[class] {
  361. border-radius: 0;
  362. }
  363.  
  364. .code-container > pre:only-child{
  365. padding:0;
  366. }
  367.  
  368. code.syntax-highlighted[class] {
  369. /*
  370. font-family: ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace !important;
  371. font-size: 9pt;
  372. */
  373. font-family: monospace;
  374. font-size: 13px;
  375. font-variant-ligatures: contextual;
  376. line-height: 1.15rem;
  377. text-shadow: none !important;
  378. }
  379.  
  380.  
  381.  
  382.  
  383.  
  384. .hljs-comment[class], .hljs-quote[class] {
  385. font-style: inherit;
  386. color: #259789;
  387. }
  388.  
  389. .hljs-add-marker-width .marker-fixed-width[class] {
  390. user-select: none !important;
  391. width: calc(var(--hljs-marker-width, 0em) + 16px);
  392. background: #f4f4f4;
  393. padding-right: 6px;
  394. margin-right: 4px;
  395. contain: paint style;
  396. }
  397.  
  398.  
  399. [dark] .hljs-add-marker-width .marker-fixed-width[class] {
  400. background: #242424;
  401. color: #b6b2b2;
  402. }
  403.  
  404. .marker-fixed-width[marker-text]::before {
  405. content:attr(marker-text);
  406. }
  407.  
  408. `;
  409.  
  410.  
  411. const cssForCodePage = /\/scripts\/\d+[^\s\/\\]*\/code(\/|$)/.test(location.href) ? `
  412.  
  413. html:not([dkkfv]) div.code-container {
  414. /* position: absolute !important;*/
  415. /* visibility: collapse; */
  416. display:none;
  417. }
  418. /*
  419.  
  420. html:not([dkkfv]) div.code-container > pre{
  421. visibility: collapse;
  422. }
  423.  
  424.  
  425. html:not([dkkfv]) code:only-child {
  426. visibility: collapse;
  427. }
  428. */
  429.  
  430.  
  431. .code-container,
  432. .code-container pre:only-child,
  433. .code-container pre:only-child code:only-child {
  434. max-height: calc(100vh + 4px);
  435. max-width: calc(100vw + 4px);
  436. }
  437. ` : '';
  438.  
  439.  
  440. const cssAdd = `
  441.  
  442. ${global_css}
  443.  
  444. ${cssForCodePage}
  445.  
  446. .code-container {
  447. max-width: 100%;
  448. display: inline-flex;
  449. flex-direction: column;
  450. overflow: auto;
  451. border-radius: 8px;
  452. max-height: 100%;
  453. overflow: visible;
  454. }
  455. .code-container > pre:only-child {
  456. max-width: 100%;
  457. display: inline-flex;
  458. flex-direction: column;
  459. flex-grow: 1;
  460. height: 0;
  461. }
  462. .code-container > pre:only-child > code:only-child {
  463. max-width: 100%;
  464. flex-grow: 1;
  465. height: 0;
  466. }
  467. .code-container pre code {
  468. padding: 0;
  469. font-family: Consolas;
  470. cursor: text;
  471. overflow: auto;
  472. box-sizing: border-box;
  473. }
  474. .code-container pre code .marker {
  475. display: inline-block;
  476. color: #636d83;
  477. text-align: right;
  478. padding-right: 20px;
  479. user-select: none;
  480. cursor: auto;
  481. }
  482.  
  483. .code-container[contenteditable]{
  484. outline: 0 !important;
  485. contain: strict;
  486. box-sizing: border-box;
  487. }
  488.  
  489. .code-container[contenteditable]>pre[contenteditable="false"]{
  490. contain: strict;
  491. width: initial;
  492. box-sizing: border-box;
  493. }
  494.  
  495.  
  496.  
  497.  
  498. html {
  499.  
  500. --token-color-keyword: #07a;
  501. --token-color-punctuation: #1415ec;
  502. --token-color-comment: #259789;
  503. --token-color-function: #da204f;
  504. }
  505.  
  506. [dark] {
  507.  
  508. --token-color-keyword: #898af2;
  509. --token-color-punctuation: #fadbdb;
  510. --token-color-comment:#59c6b9;
  511. --token-color-function: #e98aa2;
  512.  
  513. }
  514.  
  515.  
  516. body .token.comment {
  517. color: var(--token-color-comment);
  518. }
  519.  
  520. body .token.atrule, body .token.attr-value, body .token.keyword {
  521. color: #1415ec;
  522. color: var(--token-color-keyword);
  523. }
  524.  
  525.  
  526. .language-stylus .token.atrule, .language-stylus .token.attr-value, .language-stylus .token.keyword {
  527. color: #700d0d;
  528. }
  529.  
  530.  
  531. body .token.punctuation{
  532. color: var(--token-color-punctuation);
  533. }
  534.  
  535.  
  536. body .token.variable-declaration,
  537. body .token.variable {
  538. color: #0d10cd;
  539. }
  540.  
  541. body .token.selector{
  542. color: #1373bb;
  543. }
  544.  
  545.  
  546. body .token.function {
  547. color:var(--token-color-function);
  548. }
  549.  
  550.  
  551.  
  552. .language-stylus .token.variable-declaration,
  553. .language-stylus .token.variable {
  554. color: #0d10cd;
  555. }
  556.  
  557. .language-stylus .token.selector{
  558. color: #1373bb;
  559. }
  560.  
  561. .language-stylus .token.punctuation{
  562. color:#700d0d;
  563. }
  564.  
  565.  
  566. .language-stylus .token.function {
  567. color:#da204f
  568. }
  569.  
  570.  
  571.  
  572.  
  573. `;
  574.  
  575.  
  576.  
  577.  
  578. HTMLElement.prototype.getElementsByTagName331 = HTMLElement.prototype.getElementsByTagName
  579. Document.prototype.getElementsByTagName331 = Document.prototype.getElementsByTagName
  580.  
  581. HTMLElement.prototype.getElementsByTagName = getElementsByTagName
  582. Document.prototype.getElementsByTagName = getElementsByTagName
  583.  
  584.  
  585. const pScript = new PromiseExternal();
  586.  
  587. onBodyHeadReadyAsync().then(async () => {
  588.  
  589. if (!location.pathname.endsWith('/code')) {
  590. return;
  591. }
  592.  
  593. document.head.appendChild(document.createElement('style')).textContent = `${cssAdd}`;
  594.  
  595. self.Prism = self.Prism || {};
  596. self.Prism.manual = true;
  597. await loadJS(resoruces['prism-core.js']);
  598. await loadJS(resoruces['prism-clike.js']);
  599. await loadJS(resoruces['prism-javascript.js']);
  600. await loadJS(resoruces['prism-css.js']);
  601. await loadJS(resoruces['prism-stylus.js']);
  602.  
  603. if (document.documentElement.hasAttribute('dark')) {
  604.  
  605. loadCSS(resoruces['prism-dark.css']);
  606. } else {
  607.  
  608. loadCSS(resoruces['prism.css']);
  609. }
  610.  
  611. pScript.resolve();
  612.  
  613.  
  614.  
  615.  
  616. });
  617.  
  618.  
  619. /** @param {HTMLElement} pre */
  620. async function prepareCodeAreaAsync(pre) {
  621.  
  622. for (const li of pre.querySelectorAll('li')) {
  623. li.append(document.createTextNode('\n'));
  624. }
  625.  
  626. const codeElement = document.createElement('code');
  627. // codeElement.classList.add('language-javascript');
  628. codeElement.innerHTML = pre.innerHTML;
  629.  
  630. // Clearing the original code container and appending the new one
  631. // pre.classList = '';
  632. pre.innerHTML = '';
  633. // pre.appendChild(codeElement);
  634.  
  635. // if (pre.querySelector('code')) return;
  636. const code = codeElement;
  637.  
  638. const codeContainer = pre.closest('.code-container');
  639. if (codeContainer && codeContainer.querySelector('.code-container>pre:only-child')) {
  640. // avoid selection to the outside by mouse dragging
  641. codeContainer.setAttribute('contenteditable', '');
  642. codeContainer.querySelector('.code-container>pre:only-child').setAttribute('contenteditable', 'false');
  643. }
  644.  
  645.  
  646. // let parentNode = code.parentNode;
  647. // let nextNode = code.nextSibling;
  648.  
  649. // code.remove();
  650. let parentNode = pre;
  651. let nextNode = null;
  652. await Promise.resolve().then();
  653.  
  654. // preset language
  655. /*
  656. const text = codeElement.textContent;
  657. if(/(^|\n)\s*\/\/\s+==UserScript==\s*\n/.test(text)){
  658. codeElement.classList.add('language-javascript');
  659. }else if(/(^|\n)\s*\/\*\s+==UserStyle==\s*\n/.test(text)){
  660. codeElement.classList.add('language-css');
  661. }
  662. */
  663. let className = '';
  664. if (pre.classList.contains('lang-js')) {
  665. className = 'language-javascript';
  666. codeElement.classList.add();
  667. } else if (pre.classList.contains('lang-css')) {
  668.  
  669. const text = codeElement.textContent;
  670. let m = /\n\@preprocessor\s+([-_a-zA-Z]{3,8})\s*\n/.exec(text);
  671. className = 'language-css'
  672. if (m) {
  673. const preprocessor = m[1];
  674. if (preprocessor === 'stylus') {
  675. className = 'language-stylus';
  676. } else if (preprocessor === 'uso') {
  677. className = 'language-stylus';
  678. } else if (preprocessor === 'less') {
  679. className = 'language-less';
  680. } else if (preprocessor === 'default') {
  681. className = 'language-stylus';
  682. } else {
  683. className = 'language-stylus';
  684. }
  685. }
  686.  
  687.  
  688. }
  689.  
  690.  
  691. if (!className) return;
  692.  
  693.  
  694.  
  695.  
  696. codeElement.classList.add(className);
  697. codeElement.classList.add('syntax-highlighted')
  698.  
  699. let htmlCode = '';
  700.  
  701. if (className === 'language-javascript') {
  702.  
  703.  
  704. htmlCode = Prism.highlight(code.textContent, Prism.languages.javascript, 'javascript');
  705. } else if (className === 'language-stylus') {
  706.  
  707. htmlCode = Prism.highlight(code.textContent, Prism.languages.stylus, 'stylus');
  708. } else if (className === 'language-less') {
  709.  
  710. htmlCode = Prism.highlight(code.textContent, Prism.languages.less, 'less');
  711.  
  712. } else {
  713.  
  714. htmlCode = Prism.highlight(code.textContent, Prism.languages.css, 'css');
  715. }
  716.  
  717.  
  718.  
  719. // Adding line numbers
  720. htmlCode = htmlCode || '';
  721. const htmlSplit = htmlCode ? htmlCode.split('\n') : [];
  722. const totalLines = htmlSplit.length;
  723.  
  724. let mwStyle = '';
  725.  
  726. if (totalLines >= 1) {
  727. code.classList.add('hljs-add-marker-width');
  728. const len = `${totalLines}`.length * 0.5;
  729. mwStyle = `${len}em`;
  730. htmlCode = htmlSplit.map((n, i) => `<span class="marker marker-fixed-width" marker-text="${i + 1}"></span>${n}`).join('\n');
  731.  
  732. } else {
  733.  
  734. code.classList.remove('hljs-add-marker-width');
  735. }
  736.  
  737. let targetCode = code;
  738.  
  739. if (htmlCode) {
  740.  
  741. const template = document.createElement('template');
  742.  
  743. template.innerHTML = `<code>${htmlCode}</code>`;
  744. const code2 = template.content.firstChild;
  745. for (const className of code.classList) {
  746. code2.classList.add(className);
  747. }
  748.  
  749. targetCode = code2;
  750. } else {
  751. code.innerHTML = "";
  752. targetCode = code;
  753. }
  754.  
  755. targetCode.style.setProperty('--hljs-marker-width', mwStyle);
  756.  
  757. parentNode.insertBefore(targetCode, nextNode);
  758.  
  759.  
  760.  
  761. }
  762. documentReady.then(async () => {
  763.  
  764. if (!location.pathname.endsWith('/code')) {
  765. byPass = false;
  766. return;
  767. }
  768.  
  769. await pScript.then();
  770.  
  771. // Code highlighting
  772. const promises = [...document.querySelectorAll('.code-container pre.lang-js, .code-container pre.lang-css')].map(prepareCodeAreaAsync)
  773. Promise.all(promises).then(() => {
  774. // setTimeout(() => {
  775. document.documentElement.setAttribute('dkkfv', '');
  776. // }, 1);
  777. })
  778.  
  779. });
  780.  
  781.  
  782. })();
  783.