GreasyFork Code: Syntax Highlight by PrismJS

To syntax highlight GreasyFork Code by PrismJS

目前为 2023-12-17 提交的版本。查看 最新版本

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