GreasyFork Code: Syntax Highlight by CodeMirror

To syntax highlight GreasyFork Code by CodeMirror

Fra 23.01.2024. Se den seneste versjonen.

  1. // ==UserScript==
  2. // @name GreasyFork Code: Syntax Highlight by CodeMirror
  3. // @namespace Violentmonkey Scripts
  4. // @grant none
  5. // @version 0.3.0
  6. // @author CY Fung
  7. // @description To syntax highlight GreasyFork Code by CodeMirror
  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://cdn.jsdelivr.net/npm/codemirror@5.65.16';
  21. const resoruces = {
  22. 'codemirror.min.js': `${cdn}/lib/codemirror.min.js`,
  23. 'javascript.min.js': `${cdn}/mode/javascript/javascript.min.js`,
  24. 'active-line.min.js': `${cdn}/addon/selection/active-line.min.js`,
  25. 'search.js': `${cdn}/addon/search/search.js`,
  26. 'searchcursor.js': `${cdn}/addon/search/searchcursor.js`,
  27. 'jump-to-line.js': `${cdn}/addon/search/jump-to-line.js`,
  28. 'dialog.js': `${cdn}/addon/dialog/dialog.js`,
  29. 'codemirror.min.css': `${cdn}/lib/codemirror.min.css`,
  30. 'dialog.css': `${cdn}/addon/dialog/dialog.css`,
  31. 'material.css': `${cdn}/theme/material.css`,
  32. }
  33.  
  34. const doActionCSS = () => `
  35.  
  36. .code-container{
  37. height:100vh;
  38. }
  39. .code-container .CodeMirror, .code-container textarea{
  40. height:100%;
  41. }
  42. `;
  43.  
  44.  
  45. const global_css = () => `
  46.  
  47. html {
  48. line-height: 1.5;
  49. -webkit-text-size-adjust: 100%;
  50. -moz-tab-size: 4;
  51. -o-tab-size: 4;
  52. tab-size: 4;
  53. 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;
  54. font-feature-settings: normal;
  55. font-variation-settings: normal
  56. }
  57.  
  58. .code-container code, .code-container kbd, .code-container pre, .code-container samp {
  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] {
  68. border-radius: 0;
  69. }
  70.  
  71. .code-container[class] {
  72. border-radius: 0;
  73. }
  74.  
  75. .code-container > pre:only-child{
  76. padding:0;
  77. }
  78.  
  79. code.syntax-highlighted[class] {
  80. font-family: monospace;
  81. font-size: 13px;
  82. font-variant-ligatures: contextual;
  83. line-height: 1.15rem;
  84. text-shadow: none !important;
  85. }
  86.  
  87. .hljs-comment[class], .hljs-quote[class] {
  88. font-style: inherit;
  89. color: #259789;
  90. }
  91.  
  92. .hljs-add-marker-width .marker-fixed-width[class] {
  93. user-select: none !important;
  94. width: calc(var(--hljs-marker-width, 0em) + 16px);
  95. background: #f4f4f4;
  96. padding-right: 6px;
  97. margin-right: 4px;
  98. contain: paint style;
  99. }
  100.  
  101. [dark] .hljs-add-marker-width .marker-fixed-width[class] {
  102. background: #242424;
  103. color: #b6b2b2;
  104. }
  105.  
  106. .marker-fixed-width[marker-text]::before {
  107. content: attr(marker-text);
  108. }
  109.  
  110. `;
  111.  
  112.  
  113. const cssForCodePage = () => /\/scripts\/\d+[^\s\/\\]*\/code(\/|$)/.test(location.href) ? `
  114.  
  115. html:not([dkkfv]) div.code-container {
  116. visibility: collapse;
  117. }
  118.  
  119. .code-container,
  120. .code-container pre:only-child,
  121. .code-container pre:only-child code:only-child {
  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 {
  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 {
  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 {
  151. max-width: 100%;
  152. flex-grow: 1;
  153. height: 0;
  154. }
  155. .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 {
  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. html {
  184. --token-color-comment: #259789;
  185.  
  186. }
  187.  
  188. [dark] {
  189. --token-color-comment:#59c6b9;
  190. }
  191.  
  192.  
  193. .CodeMirror .cm-comment[class] {
  194. color: var(--token-color-comment);
  195. }
  196.  
  197.  
  198. html .CodeMirror .CodeMirror-linenumber[class] {
  199. background: #f4f4f4;
  200. color: #636d83;
  201. padding-right: 6px;
  202. margin-right: 4px;
  203. contain: paint style;
  204. user-select: none !important;
  205. }
  206.  
  207. [dark] .CodeMirror .CodeMirror-linenumber[class] {
  208. background: #242424;
  209. color: #b6b2b2;
  210. }
  211.  
  212. html .CodeMirror[class] {
  213. background-color: inherit;
  214. }
  215. html .CodeMirror .CodeMirror-activeline-background[class] {
  216. background: inherit;
  217. background-color: #8fd9da17;
  218. }
  219.  
  220. div.code-container .CodeMirror .CodeMirror-lines {
  221. padding: 0;
  222. }
  223.  
  224. div.code-container .CodeMirror {
  225. font-family: monospace;
  226. font-size: 13px;
  227. font-variant-ligatures: contextual;
  228. line-height: 1.15rem;
  229. text-shadow: none !important;
  230. }
  231.  
  232. `;
  233.  
  234. const Promise = (async function () { })().constructor;
  235.  
  236. const delayPn = delay => new Promise((fn => setTimeout(fn, delay)));
  237.  
  238. const PromiseExternal = ((resolve_, reject_) => {
  239. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  240. return class PromiseExternal extends Promise {
  241. constructor(cb = h) {
  242. super(cb);
  243. if (cb === h) {
  244. /** @type {(value: any) => void} */
  245. this.resolve = resolve_;
  246. /** @type {(reason?: any) => void} */
  247. this.reject = reject_;
  248. }
  249. }
  250. };
  251. })();
  252.  
  253. const pScript = new PromiseExternal();
  254. const pElementQuery = new PromiseExternal();
  255.  
  256. HTMLElement.prototype.getElementsByTagName331 = HTMLElement.prototype.getElementsByTagName;
  257. Document.prototype.getElementsByTagName331 = Document.prototype.getElementsByTagName;
  258.  
  259. HTMLElement.prototype.getElementsByTagName = getElementsByTagName;
  260. Document.prototype.getElementsByTagName = getElementsByTagName;
  261.  
  262. let byPass = true;
  263.  
  264. const observablePromise = (proc, timeoutPromise) => {
  265. let promise = null;
  266. return {
  267. obtain() {
  268. if (!promise) {
  269. promise = new Promise(resolve => {
  270. let mo = null;
  271. const f = () => {
  272. let t = proc();
  273. if (t) {
  274. mo.disconnect();
  275. mo.takeRecords();
  276. mo = null;
  277. resolve(t);
  278. }
  279. }
  280. mo = new MutationObserver(f);
  281. mo.observe(document, { subtree: true, childList: true })
  282. f();
  283. timeoutPromise && timeoutPromise.then(() => {
  284. resolve(null)
  285. });
  286. });
  287. }
  288. return promise
  289. }
  290. }
  291. }
  292.  
  293. const documentReady = new Promise(resolve => {
  294. Promise.resolve().then(() => {
  295. if (document.readyState !== 'loading') {
  296. resolve();
  297. } else {
  298. window.addEventListener("DOMContentLoaded", resolve, false);
  299. }
  300. });
  301. });
  302.  
  303. function getElementsByTagName(tag) {
  304. if (byPass) {
  305. if (tag === 'pre' || tag === 'code' || tag === 'xmp') {
  306. if (location.pathname.endsWith('/code')) {
  307. pElementQuery.resolve();
  308. return [];
  309. }
  310. }
  311. }
  312. return this.getElementsByTagName331(tag);
  313. }
  314.  
  315. async function onBodyHeadReadyAsync() {
  316. await observablePromise(() => document.body && document.head).obtain();
  317. }
  318.  
  319.  
  320. // Load CSS
  321. function loadJS(href) {
  322.  
  323. return new Promise(resolve => {
  324.  
  325. const script = document.createElement('script');
  326. script.src = href;
  327. script.onload = () => {
  328. resolve(script);
  329. };
  330. document.head.appendChild(script);
  331.  
  332. });
  333.  
  334. }
  335.  
  336. // Load CSS
  337. function loadCSS(href) {
  338. const link = document.createElement('link');
  339. link.rel = 'stylesheet';
  340. link.href = href;
  341. document.head.appendChild(link);
  342. return link;
  343. }
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350. /** @param {HTMLElement} pre */
  351. async function prepareCodeAreaAsync(pre) {
  352.  
  353. if (pre.isConnected === false) return;
  354.  
  355. for (const li of pre.querySelectorAll('li')) {
  356. li.append(document.createTextNode('\n'));
  357. }
  358.  
  359. const codeElement = document.createElement('code');
  360. // codeElement.classList.add('language-javascript');
  361. codeElement.innerHTML = pre.innerHTML;
  362.  
  363. // Clearing the original code container and appending the new one
  364. // pre.classList = '';
  365. pre.innerHTML = '';
  366. // pre.appendChild(codeElement);
  367.  
  368. // if (pre.querySelector('code')) return;
  369. const code = codeElement;
  370.  
  371. const codeContainer = pre.closest('.code-container');
  372. if (codeContainer && codeContainer.querySelector('.code-container>pre:only-child')) {
  373. // avoid selection to the outside by mouse dragging
  374. codeContainer.setAttribute('contenteditable', '');
  375. codeContainer.querySelector('.code-container>pre:only-child').setAttribute('contenteditable', 'false');
  376. }
  377.  
  378.  
  379. // let parentNode = code.parentNode;
  380. // let nextNode = code.nextSibling;
  381.  
  382. // code.remove();
  383. let parentNode = pre;
  384. let nextNode = null;
  385. await Promise.resolve().then();
  386.  
  387. // preset language
  388. /*
  389. const text = codeElement.textContent;
  390. if(/(^|\n)\s*\/\/\s+==UserScript==\s*\n/.test(text)){
  391. codeElement.classList.add('language-javascript');
  392. }else if(/(^|\n)\s*\/\*\s+==UserStyle==\s*\n/.test(text)){
  393. codeElement.classList.add('language-css');
  394. }
  395. */
  396. let className = '';
  397. if (pre.classList.contains('lang-js')) {
  398. className = 'language-javascript';
  399. } else if (pre.classList.contains('lang-css')) {
  400.  
  401. const text = codeElement.textContent;
  402. let m = /\n@preprocessor\s+([-_a-zA-Z]{3,8})\s*\n/.exec(text);
  403. className = 'language-css'
  404. if (m) {
  405. const preprocessor = m[1];
  406. if (preprocessor === 'stylus') {
  407. className = 'language-stylus';
  408. } else if (preprocessor === 'uso') {
  409. className = 'language-stylus';
  410. } else if (preprocessor === 'less') {
  411. className = 'language-less';
  412. } else if (preprocessor === 'default') {
  413. className = 'language-stylus';
  414. } else {
  415. className = 'language-stylus';
  416. }
  417. }
  418.  
  419.  
  420. }
  421.  
  422.  
  423. if (!className) return;
  424.  
  425. let mode = '';
  426. if (className === 'language-javascript') mode = 'javascript';
  427. if (className === 'language-stylus') mode = 'stylus';
  428. if (className === 'language-less') mode = 'less';
  429. if (className === 'language-css') mode = 'css';
  430.  
  431. if (!mode) return;
  432.  
  433.  
  434.  
  435. let textarea = document.createElement('textarea');
  436. textarea.value = `${code.textContent}`;
  437. textarea.readOnly = true;
  438. textarea.id = 'editor651';
  439.  
  440.  
  441. // textarea.classList.add('code-container')
  442.  
  443. textarea.style.width = '100%';
  444. textarea.style.height = '100vh';
  445.  
  446. parentNode.insertBefore(textarea, nextNode);
  447.  
  448.  
  449. let editor651 = CodeMirror.fromTextArea(document.querySelector('#editor651'), {
  450.  
  451. mode: mode,
  452. theme: document.documentElement.hasAttribute('dark') ? 'material' : '',
  453.  
  454. readOnly: true,
  455. styleActiveLine: true,
  456. lineNumbers: true,
  457. extraKeys: { "Alt-F": "findPersistent" }
  458. });
  459. editor651.save();
  460.  
  461.  
  462.  
  463. }
  464.  
  465. const documentBodyHeadReady = onBodyHeadReadyAsync();
  466.  
  467. documentBodyHeadReady.then(async () => {
  468.  
  469. if (!location.pathname.endsWith('/code')) {
  470. return;
  471. }
  472.  
  473. document.head.appendChild(document.createElement('style')).textContent = `${cssAdd()}`;
  474.  
  475. await loadJS(resoruces['codemirror.min.js']);
  476.  
  477. await Promise.all([
  478. loadJS(resoruces['javascript.min.js']),
  479. loadJS(resoruces['active-line.min.js']),
  480. loadJS(resoruces['search.js']),
  481. loadJS(resoruces['searchcursor.js']),
  482. loadJS(resoruces['jump-to-line.js']),
  483. loadJS(resoruces['dialog.js'])
  484. ]);
  485.  
  486. if (document.documentElement.hasAttribute('dark')) {
  487.  
  488. // TBC
  489. loadCSS(resoruces['codemirror.min.css']);
  490. loadCSS(resoruces['dialog.css']);
  491. loadCSS(resoruces['material.css']);
  492.  
  493. } else {
  494.  
  495. loadCSS(resoruces['codemirror.min.css']);
  496. loadCSS(resoruces['dialog.css']);
  497. }
  498.  
  499.  
  500. pScript.resolve();
  501.  
  502.  
  503.  
  504.  
  505. });
  506.  
  507. let keydownActive = false;
  508.  
  509. documentReady.then(async () => {
  510.  
  511. if (!location.pathname.endsWith('/code')) {
  512. byPass = false;
  513. return;
  514. }
  515.  
  516. await pScript.then();
  517.  
  518. await Promise.race([pElementQuery, delayPn(800)]);
  519.  
  520. const targets = document.querySelectorAll('.code-container pre.lang-js, .code-container pre.lang-css');
  521.  
  522. if (targets.length === 0) return;
  523.  
  524. await delayPn(40);
  525.  
  526. document.head.appendChild(document.createElement('style')).textContent = doActionCSS();
  527.  
  528. await delayPn(40);
  529.  
  530. byPass = false;
  531.  
  532. // Code highlighting
  533. const promises = [...targets].map(prepareCodeAreaAsync)
  534. await Promise.all(promises);
  535.  
  536. await delayPn(40);
  537. document.documentElement.setAttribute('dkkfv', '');
  538. keydownActive = true;
  539.  
  540. });
  541.  
  542. function selectAllWithinElement(element) {
  543. window.getSelection().removeAllRanges();
  544. let range = document.createRange();
  545. if (element) {
  546. range.selectNodeContents(element);
  547. window.getSelection().addRange(range);
  548. } else {
  549. console.error('Element not found with ID:', element);
  550. }
  551. }
  552. document.addEventListener('keydown', (e) => {
  553. if (keydownActive && e && e.code === 'KeyA' && e.isTrusted && (e.metaKey || e.ctrlKey) && !e.shiftKey && !e.altKey) {
  554.  
  555. const target = e.target;
  556. const container = target ? target.closest('div.code-container') : null;
  557. const code = container ? container.querySelector('code') : null;
  558.  
  559. if (container && code) {
  560.  
  561. e.preventDefault();
  562. e.stopPropagation();
  563. e.stopImmediatePropagation();
  564.  
  565. setTimeout(() => {
  566. selectAllWithinElement(code);
  567. }, 1)
  568.  
  569. }
  570.  
  571. }
  572. }, true);
  573.  
  574.  
  575. })();