Guest User

Untitled

a guest
Jul 21st, 2025
110
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.73 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Markdown Editor Enhancer for Mastodon glitch-soc v5.05 (DeepSeek ChatGPT)
  3. // @version 5.05
  4. // @namespace http://tampermonkey.net/
  5. // @description Improved Markdown preview with proper ESC clearing
  6. // @author Johan
  7. // @match *://cr8r.gg/*
  8. // @match *://tilde.zone/*
  9. // @match *://infosec.exchange/*
  10. // @match *://toot.cat/*
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // 1. Inline-разметка
  18. function inlineMarkdown(text) {
  19. return text
  20. // Ссылки [текст](URL) — улучшенный регексп
  21. .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener nofollow noreferrer" class="status-link unhandled-link">$1</a>')
  22. .replace(/#([а-яa-z0-9_]+)/gi, '<span class="mention hashtag status-link">#$1</span>')
  23. .replace(/@([a-zA-Z0-9_]+)@([a-zA-Z0-9\.\-]+)/g, '<a href="https://$2/@$1" class="u-url mention status-link" rel="nofollow noopener" target="_blank" title="@$1@$2">@<span>$1@$2</span></a>'
  24. )
  25. .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>')
  26. .replace(/`(.*?)`/g, '<code>$1</code>')
  27. .replace(/~~(.*?)~~/g, '<del>$1</del>')
  28. .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
  29. .replace(/__(.*?)__/g, '<strong>$1</strong>')
  30. .replace(/\*(.*?)\*/g, '<em>$1</em>')
  31. .replace(/_(.*?)_/g, '<em>$1</em>')
  32. }
  33.  
  34. // 2. Парсинг Markdown
  35. function parseMarkdown(raw) {
  36. const lines = raw.split('\n');
  37. let result = '';
  38. let inQuote = false;
  39. let inCodeBlock = false;
  40. let codeContent = [];
  41. let currentParagraph = [];
  42. let inList = false;
  43. let listType = '';
  44.  
  45. function flushParagraph() {
  46. if (currentParagraph.length === 0) return;
  47.  
  48. let content = currentParagraph.join('\n').trim();
  49. if (!content) {
  50. currentParagraph = [];
  51. return;
  52. }
  53.  
  54. if (inQuote) {
  55. content = content.replace(/^>\s?/gm, '');
  56. result += `<blockquote><p>${inlineMarkdown(content)}</p></blockquote>`;
  57. } else if (listType) {
  58. const listItems = content.split('\n');
  59. result += `<${listType === 'unordered' ? 'ul' : 'ol'}>`;
  60. listItems.forEach(item => {
  61. const text = item.replace(/^(?:-|\d+\.)\s+/, '').trim();
  62. if (text) result += `<li>${inlineMarkdown(text)}</li>`;
  63. });
  64. result += `</${listType === 'unordered' ? 'ul' : 'ol'}>`;
  65. } else {
  66. result += `<p>${inlineMarkdown(content)}</p>`;
  67. }
  68.  
  69. currentParagraph = [];
  70. }
  71.  
  72. function flushCodeBlock() {
  73. if (codeContent.length === 0) return;
  74. result += `<pre><code>${codeContent.join('\n')
  75. .replace(/&/g, '&amp;')
  76. .replace(/</g, '&lt;')
  77. .replace(/>/g, '&gt;')
  78. }</code></pre>`;
  79. codeContent = [];
  80. }
  81.  
  82. for (const line of lines) {
  83. const trimmedLine = line.trim();
  84.  
  85. if (trimmedLine.startsWith('```')) {
  86. if (inCodeBlock) {
  87. flushCodeBlock();
  88. inCodeBlock = false;
  89. } else {
  90. flushParagraph();
  91. inCodeBlock = true;
  92. }
  93. continue;
  94. }
  95.  
  96. if (inCodeBlock) {
  97. codeContent.push(line);
  98. continue;
  99. }
  100.  
  101. if (trimmedLine.startsWith('# ')) {
  102. flushParagraph();
  103. result += `<h1>${trimmedLine.substring(2)}</h1>`;
  104. continue;
  105. }
  106.  
  107. const isQuoteLine = line.startsWith('>');
  108. if (isQuoteLine && !inQuote) {
  109. flushParagraph();
  110. inQuote = true;
  111. } else if (!isQuoteLine && inQuote) {
  112. flushParagraph();
  113. inQuote = false;
  114. }
  115.  
  116. const isListLine = /^(-|\d+\.)\s+/.test(trimmedLine);
  117. if (isListLine && !inList) {
  118. flushParagraph();
  119. inList = true;
  120. listType = trimmedLine.startsWith('-') ? 'unordered' : 'ordered';
  121. } else if (!isListLine && inList) {
  122. flushParagraph();
  123. inList = false;
  124. listType = '';
  125. }
  126.  
  127. if (trimmedLine === '') {
  128. flushParagraph();
  129. continue;
  130. }
  131.  
  132. currentParagraph.push(line);
  133. }
  134.  
  135. flushParagraph();
  136. flushCodeBlock();
  137. return result;
  138. }
  139.  
  140. // 3. Создание превью
  141. function applyPreview(textarea) {
  142. const preview = textarea.nextElementSibling || document.createElement('div');
  143. if (!preview.classList.contains('md-preview')) {
  144. preview.className = 'md-preview status__content__text status__content__text--visible translate';
  145. preview.style.cssText = `
  146. border-top: 1px solid #ccc;
  147. padding: 10px;
  148. margin-top: 5px;
  149. white-space: normal;
  150. // раскомментируйте, если хотите со скроллбаром max-height: 300px;
  151. overflow-y: auto;
  152. `;
  153. textarea.parentNode.insertBefore(preview, textarea.nextSibling);
  154.  
  155. textarea.addEventListener('keydown', (e) => {
  156. if (e.key === 'Escape') {
  157. if (textarea.value === '') {
  158. preview.innerHTML = '';
  159. } else {
  160. textarea.value = '';
  161. preview.innerHTML = '';
  162. }
  163. e.preventDefault();
  164. }
  165. });
  166. }
  167. preview.innerHTML = parseMarkdown(textarea.value);
  168. }
  169.  
  170. // 4. Глобальный обработчик Escape
  171. function setupEscapeHandler() {
  172. document.addEventListener('keydown', (e) => {
  173. if (e.key === 'Escape') {
  174. const activeTextarea = document.activeElement;
  175. if (activeTextarea && activeTextarea.matches('textarea.autosuggest-textarea__textarea')) {
  176. const preview = activeTextarea.nextElementSibling;
  177. if (preview?.classList.contains('md-preview')) {
  178. if (activeTextarea.value === '') {
  179. preview.innerHTML = '';
  180. }
  181. activeTextarea.focus();
  182. }
  183. }
  184. }
  185. });
  186. }
  187.  
  188. // 5. Инициализация
  189. function init() {
  190. const textarea = document.querySelector('textarea.autosuggest-textarea__textarea');
  191. if (textarea) {
  192. textarea.addEventListener('input', () => applyPreview(textarea));
  193. applyPreview(textarea);
  194. }
  195. setupEscapeHandler();
  196. }
  197. /*
  198. // Собственные стили, пока не работает
  199. const style = document.createElement('style');
  200. style.textContent = `
  201. .md-preview {
  202. font-family: inherit;
  203. color: inherit;
  204. }
  205. .md-preview blockquote {
  206. border-left: 3px solid #4a90e2 !mportant;
  207. padding-left: 1em !mportant;
  208. margin: 0.8em 0 !mportant;
  209. color: #333 !mportant;
  210. }
  211. .md-preview p {
  212. margin: 0.5em 0 !mportant;
  213. line-height: 1. !mportant;
  214. }
  215. .md-preview code {
  216. background: #f0f0f0 !mportant;
  217. padding: 0.2em 0.4em !mportant;
  218. border-radius: 3px !mportant;
  219. font-family: monospace !mportant;
  220. }
  221. .md-preview pre {
  222. background: #f5f5f5 !mportant;
  223. padding: 1em !mportant;
  224. overflow: auto !mportant;
  225. border-radius: 3px !mportant;
  226. }
  227. .md-preview h1 {
  228. font-size: 1.2em !mportant;
  229. margin: 1em 0 0.5em 0 !mportant;
  230. font-weight: bold !mportant;
  231. }
  232. .md-preview ul, .md-preview ol {
  233. margin: 0.5em 0 !mportant;
  234. padding-left: 2em !mportant;
  235. }
  236. `;
  237. document.head.appendChild(style);
  238. */
  239. // Запуск
  240. if (document.readyState === 'complete') {
  241. init();
  242. } else {
  243. window.addEventListener('load', init);
  244. }
  245. })();
Advertisement
Add Comment
Please, Sign In to add comment