Markdown Viewer

Automatically formats and displays .md files with a pleasant, readable theme and font settings. Turn your browser into the only Markdown viewer you need by giving your Tampermonkey access to local files.

  1. // ==UserScript==
  2. // @name Markdown Viewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0.2
  5. // @description Automatically formats and displays .md files with a pleasant, readable theme and font settings. Turn your browser into the only Markdown viewer you need by giving your Tampermonkey access to local files.
  6. // @description:en Automatically formats and displays .md files with a pleasant, readable theme and font settings. Turn your browser into the only Markdown viewer you need by giving your Tampermonkey access to local files.
  7. // @description:de Automatisch .md-Dateien formatieren und anzeigen mit einem angenehmen, lesbaren Thema und Schriftarten. Machen Sie Ihren Browser zum einzigen Markdown-Viewer, den Sie benötigen, indem Sie Tampermonkey Zugriff auf lokale Dateien gewähren.
  8. // @author https://github.com/anga83
  9. // @match *://*/*.md
  10. // @include file://*/*.md
  11. // @exclude https://github.com/*
  12. // @exclude http://github.com/*
  13. // @require https://cdn.jsdelivr.net/npm/marked@12.0.2/lib/marked.umd.min.js
  14. // @resource css_darkdown https://raw.githubusercontent.com/yrgoldteeth/darkdowncss/master/darkdown.css
  15. // @grant GM_getResourceText
  16. // @grant GM_setValue
  17. // @grant GM_getValue
  18. // @grant GM_registerMenuCommand
  19. // ==/UserScript==
  20.  
  21. (function() {
  22. 'use strict';
  23.  
  24. // --- SETTINGS IDENTIFIERS ---
  25. const FONT_STYLE_KEY = 'markdownViewer_fontStyle';
  26. const THEME_KEY = 'markdownViewer_theme';
  27. const STYLE_ELEMENT_ID_FONT = 'userscript-markdown-font-style';
  28. const STYLE_ELEMENT_ID_THEME = 'userscript-markdown-theme-style';
  29. const STYLE_ELEMENT_ID_BASE = 'userscript-markdown-base-style';
  30.  
  31. // --- FONT SETTINGS ---
  32. const FONT_SETTINGS = {
  33. 'serif': `Iowan Old Style, Apple Garamond, Baskerville, Georgia, Times New Roman, Droid Serif, Times, Source Serif Pro, serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol`,
  34. 'sans-serif': `"Segoe UI", "SF Pro Text", "Helvetica Neue", "Ubuntu", "Arial", sans-serif`
  35. };
  36.  
  37. function removeExistingStyleElement(id) {
  38. const existingStyle = document.getElementById(id);
  39. if (existingStyle) {
  40. existingStyle.remove();
  41. }
  42. }
  43.  
  44. function addStyleElement(id, css) {
  45. removeExistingStyleElement(id);
  46. const style = document.createElement('style');
  47. style.id = id;
  48. style.textContent = css;
  49. (document.head || document.documentElement).appendChild(style);
  50. }
  51.  
  52. function applyFontStyle() {
  53. const chosenFont = GM_getValue(FONT_STYLE_KEY, 'serif'); // Standard 'serif'
  54. const fontFamily = FONT_SETTINGS[chosenFont] || FONT_SETTINGS.serif;
  55. addStyleElement(STYLE_ELEMENT_ID_FONT, `.markdown-body { font-family: ${fontFamily} !important; }`);
  56. }
  57.  
  58. // --- THEME SETTINGS ---
  59. function applyThemeStyle() {
  60. const chosenTheme = GM_getValue(THEME_KEY, 'system'); // 'system', 'light', 'dark'
  61. const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
  62.  
  63. let useDarkTheme = false;
  64. if (chosenTheme === 'dark') {
  65. useDarkTheme = true;
  66. } else if (chosenTheme === 'system' && prefersDarkScheme) {
  67. useDarkTheme = true;
  68. }
  69.  
  70. let themeCss = '';
  71. if (useDarkTheme) {
  72. const darkdownCss = GM_getResourceText("css_darkdown");
  73. if (darkdownCss) {
  74. themeCss += darkdownCss;
  75. }
  76. // Dark Theme
  77. themeCss += `
  78. body {
  79. background-color: rgb(27, 28, 29) !important;
  80. color: rgb(220, 220, 220) !important;
  81. }
  82. .markdown-body {
  83. color: rgb(220, 220, 220) !important;
  84. }
  85. .markdown-body a {
  86. color: #79b8ff !important; /* Dezenter blau-türkis Farbton statt kräftiges Blau */
  87. text-decoration: none !important; /* Keine Unterstreichung standardmäßig */
  88. }
  89. .markdown-body a:hover {
  90. text-decoration: underline !important; /* Nur beim Hovern unterstreichen */
  91. }
  92. .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
  93. border-bottom-color: #30363d !important;
  94. color: rgb(220, 220, 220) !important;
  95. }
  96. .markdown-body hr { background-color: #30363d !important; }
  97. .markdown-body blockquote {
  98. color: #a0a0a0 !important;
  99. border-left-color: #30363d !important;
  100. }
  101. .markdown-body table th, .markdown-body table td { border-color: #484f58 !important; }
  102. .markdown-body code:not(pre code) { /* Inline code */
  103. background-color: rgb(50, 50, 50) !important;
  104. border: 1px solid rgb(70, 70, 70) !important;
  105. color: rgb(220, 220, 220) !important;
  106. }
  107. .markdown-body pre { /* Code block */
  108. background-color: rgb(40, 42, 44) !important;
  109. border: 1px solid rgb(60, 62, 64) !important;
  110. }
  111. .markdown-body pre code {
  112. color: rgb(220, 220, 220) !important;
  113. }
  114. .markdown-body kbd {
  115. background-color: rgb(50,50,50) !important;
  116. border: 1px solid rgb(70,70,70) !important;
  117. color: rgb(220,220,220) !important;
  118. border-bottom-color: rgb(80,80,80) !important;
  119. }
  120. .markdown-body img { filter: brightness(.8) contrast(1.2); }
  121. /* Dark Mode Button Styling */
  122. .custom-play-button {
  123. background-color: #444d56 !important; /* Dunklerer, weniger aufdringlicher Grauton */
  124. color: #e1e4e8 !important; /* Helle Schrift für dunklen Hintergrund */
  125. border: 1px solid #586069 !important; /* Dezenter Rand */
  126. }
  127. .custom-play-button:hover, .custom-play-button:focus {
  128. background-color: #586069 !important; /* Etwas heller beim Hover */
  129. color: #e1e4e8 !important;
  130. border-color: #6a737d !important;
  131. }
  132. .custom-play-button a {
  133. color: #e1e4e8 !important; /* Links erben die Button-Textfarbe */
  134. text-decoration: none !important;
  135. }
  136. .custom-play-button a:hover {
  137. color: #e1e4e8 !important; /* Auch beim Hover Button-Farbe beibehalten */
  138. text-decoration: none !important;
  139. }
  140. `;
  141. } else { // Light Theme
  142. themeCss += `
  143. body {
  144. background-color: #ffffff !important;
  145. color: #24292e !important;
  146. }
  147. .markdown-body {
  148. color: #24292e !important;
  149. }
  150. .markdown-body a {
  151. color: #0366d6 !important;
  152. text-decoration: none !important; /* Keine Unterstreichung standardmäßig */
  153. }
  154. .markdown-body a:hover {
  155. text-decoration: underline !important; /* Nur beim Hovern unterstreichen */
  156. }
  157. .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
  158. border-bottom-color: #eaecef !important;
  159. color: #24292e !important;
  160. }
  161. .markdown-body hr { background-color: #e1e4e8 !important; }
  162. .markdown-body blockquote {
  163. color: #6a737d !important;
  164. border-left-color: #dfe2e5 !important;
  165. }
  166. .markdown-body table th, .markdown-body table td { border: 1px solid #dfe2e5 !important; }
  167. .markdown-body code:not(pre code) { /* Inline code */
  168. background-color: rgba(27,31,35,.07) !important;
  169. border: 1px solid rgba(27,31,35,.1) !important;
  170. color: #24292e !important;
  171. }
  172. .markdown-body pre { /* Code block */
  173. background-color: #f6f8fa !important;
  174. border: 1px solid #eaecef !important;
  175. }
  176. .markdown-body pre code {
  177. color: #24292e !important;
  178. }
  179. .markdown-body kbd {
  180. background-color: #fafbfc !important;
  181. border: 1px solid #d1d5da !important;
  182. border-bottom-color: #c6cbd1 !important;
  183. color: #444d56 !important;
  184. }
  185. .markdown-body img { filter: none; }
  186. /* Light Mode Button Styling */
  187. .custom-play-button {
  188. background-color: #f6f8fa !important; /* Heller, subtiler Grauton */
  189. color: #24292e !important; /* Dunkle Schrift für hellen Hintergrund */
  190. border: 1px solid #d1d5da !important; /* Dezenter Rand */
  191. }
  192. .custom-play-button:hover, .custom-play-button:focus {
  193. background-color: #e1e4e8 !important; /* Etwas dunkler beim Hover */
  194. color: #24292e !important;
  195. border-color: #c6cbd1 !important;
  196. }
  197. .custom-play-button a {
  198. color: #24292e !important; /* Links erben die Button-Textfarbe */
  199. text-decoration: none !important;
  200. }
  201. .custom-play-button a:hover {
  202. color: #24292e !important; /* Auch beim Hover Button-Farbe beibehalten */
  203. text-decoration: none !important;
  204. }
  205. `;
  206. }
  207. addStyleElement(STYLE_ELEMENT_ID_THEME, themeCss);
  208. }
  209.  
  210. // --- MENU COMMANDS ---
  211. GM_registerMenuCommand('Font: Serif', () => {
  212. GM_setValue(FONT_STYLE_KEY, 'serif');
  213. applyFontStyle();
  214. });
  215.  
  216. GM_registerMenuCommand('Font: Sans-serif', () => {
  217. GM_setValue(FONT_STYLE_KEY, 'sans-serif');
  218. applyFontStyle();
  219. });
  220.  
  221. GM_registerMenuCommand('Theme: System', () => {
  222. GM_setValue(THEME_KEY, 'system');
  223. applyThemeStyle();
  224. });
  225.  
  226. GM_registerMenuCommand('Theme: Light', () => {
  227. GM_setValue(THEME_KEY, 'light');
  228. applyThemeStyle();
  229. });
  230.  
  231. GM_registerMenuCommand('Theme: Dark', () => {
  232. GM_setValue(THEME_KEY, 'dark');
  233. applyThemeStyle();
  234. });
  235.  
  236. // --- BASE STYLES ---
  237. function applyBaseStyles() {
  238. addStyleElement(STYLE_ELEMENT_ID_BASE, `
  239. body {
  240. margin: 0;
  241. }
  242. .markdown-body {
  243. box-sizing: border-box;
  244. min-width: 200px;
  245. max-width: 980px;
  246. margin: 0 auto;
  247. padding: 15px 30px 30px;
  248. }
  249. .markdown-body img {
  250. max-width: 100%; /* Korrigiert von 150% auf 100% */
  251. height: auto;
  252. display: block;
  253. margin-left: auto;
  254. margin-right: auto;
  255. border-radius: 3px;
  256. }
  257. .markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
  258. margin-top: 1.8em;
  259. margin-bottom: 0.7em;
  260. padding-bottom: 0.3em; /* Für die untere Linie */
  261. }
  262. .markdown-body h1:hover, .markdown-body h2:hover, .markdown-body h3:hover, .markdown-body h4:hover, .markdown-body h5:hover, .markdown-body h6:hover {
  263. text-decoration: underline; /* Direkte Unterstreichung der Überschriften beim Hovern */
  264. }
  265. .markdown-body h1 { font-size: 2.1em; }
  266. .markdown-body h2 { font-size: 1.7em; }
  267. .markdown-body h3 { font-size: 1.4em; }
  268. .markdown-body h4 { font-size: 1.2em; }
  269. .markdown-body h5 { font-size: 1.05em; }
  270. .markdown-body h6 { font-size: 0.9em; }
  271.  
  272. /* Code font family (colors/backgrounds are in theme) */
  273. .markdown-body code, .markdown-body kbd, .markdown-body samp, .markdown-body pre {
  274. font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
  275. font-size: 0.9em; /* Etwas kleiner für bessere Lesbarkeit im Fließtext */
  276. }
  277. .markdown-body pre {
  278. padding: 16px;
  279. overflow: auto;
  280. border-radius: 6px;
  281. line-height: 1.45;
  282. }
  283. .markdown-body code:not(pre code) { /* Inline code */
  284. padding: .2em .4em;
  285. margin: 0 .2em; /* Kleiner horizontaler Abstand */
  286. font-size: 88%; /* Relativ zur umgebenden Schriftgröße */
  287. border-radius: 3px;
  288. }
  289. .markdown-body kbd {
  290. padding: .2em .4em;
  291. margin: 0 .2em;
  292. font-size: 88%;
  293. border-radius: 3px;
  294. }
  295. .markdown-body ul, .markdown-body ol {
  296. padding-left: 2em; /* Standardeinzug für Listen */
  297. }
  298. .markdown-body table {
  299. display: block; /* Für Responsivität und Overflow */
  300. width: max-content; /* Passt sich dem Inhalt an, aber nicht breiter als Container */
  301. max-width: 100%;
  302. overflow: auto; /* Scrollbar bei Bedarf */
  303. border-spacing: 0;
  304. border-collapse: collapse;
  305. margin-top: 1em;
  306. margin-bottom: 1em;
  307. }
  308. .markdown-body table th, .markdown-body table td {
  309. padding: 6px 13px;
  310. }
  311. .markdown-body blockquote {
  312. margin-left: 0; /* Standard-Blockquote-Styling */
  313. margin-right: 0;
  314. padding: 0 1em; /* Innenabstand */
  315. }
  316. @media (max-width: 767px) {
  317. .markdown-body {
  318. padding: 20px 15px 15px;
  319. }
  320. .markdown-body h1 { font-size: 1.8em; }
  321. .markdown-body h2 { font-size: 1.5em; }
  322. .markdown-body h3 { font-size: 1.3em; }
  323. }
  324.  
  325. /* Custom Button Base Style */
  326. .custom-play-button {
  327. display: inline-block;
  328. padding: 10px 18px;
  329. text-decoration: none !important;
  330. border-radius: 5px;
  331. border: none;
  332. font-family: "Segoe UI", "SF Pro Text", "Helvetica Neue", "Ubuntu", "Arial", sans-serif;
  333. font-size: 0.95em;
  334. font-weight: 500;
  335. text-align: center;
  336. cursor: pointer;
  337. margin: 8px 0;
  338. transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
  339. }
  340. .custom-play-button a {
  341. color: inherit !important;
  342. text-decoration: none !important;
  343. }
  344. `);
  345. }
  346.  
  347. // --- MAIN SCRIPT EXECUTION ---
  348. function initializeViewer() {
  349. applyBaseStyles();
  350. applyThemeStyle();
  351. applyFontStyle();
  352.  
  353. // Listener für System-Theme-Änderungen
  354. if (GM_getValue(THEME_KEY, 'system') === 'system') {
  355. const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
  356. mediaQuery.removeEventListener('change', applyThemeStyle); // Vorsichtshalber entfernen
  357. mediaQuery.addEventListener('change', applyThemeStyle);
  358. }
  359.  
  360. const markdownBodyDiv = document.createElement('div');
  361. markdownBodyDiv.className = 'markdown-body';
  362.  
  363. // Überprüfen, ob marked durch @require geladen wurde
  364. if (typeof marked === 'undefined' || typeof marked.parse !== 'function') {
  365. console.error("Markdown Viewer: Marked.js library not loaded correctly via @require or 'parse' function is missing.");
  366. console.error("Markdown Viewer: typeof marked:", typeof marked);
  367. if (typeof marked !== 'undefined') {
  368. console.error("Markdown Viewer: marked properties:", Object.keys(marked));
  369. console.error("Markdown Viewer: typeof marked.parse:", typeof marked.parse);
  370. }
  371. markdownBodyDiv.innerHTML = `<p style="color:red; font-family:sans-serif;">Error: Marked.js library could not be loaded. Check console for details.</p>`;
  372. document.body.innerHTML = ''; // Vorhandenen Inhalt löschen
  373. document.body.appendChild(markdownBodyDiv);
  374. return;
  375. }
  376.  
  377. try {
  378. let markdownContentToParse = "";
  379. if (document.contentType === 'text/markdown' ||
  380. (location.protocol === 'file:' && document.body && document.body.children.length === 1 && document.body.firstChild.tagName === 'PRE')) {
  381. markdownContentToParse = document.body.firstChild.innerText;
  382. } else if (document.body && document.body.innerText) {
  383. markdownContentToParse = document.body.innerText;
  384. } else if (document.body && document.body.textContent) {
  385. markdownContentToParse = document.body.textContent;
  386. }
  387.  
  388. const renderer = new marked.Renderer();
  389. const originalLinkRenderer = renderer.link;
  390.  
  391. renderer.link = (href, title, text) => {
  392. const buttonPattern = /^\s*<kbd>\s*<br>\s*➡️ Play it right now in your browser\s*<br>\s*<\/kbd>\s*$/i;
  393.  
  394. if (buttonPattern.test(text)) {
  395. const buttonText = "➡️ Play it right now in your browser";
  396. return `<a href="${href}" ${title ? `title="${title}"` : ''} class="custom-play-button" target="_blank" rel="noopener noreferrer">${buttonText}</a>`;
  397. }
  398. return originalLinkRenderer.call(renderer, href, title, text);
  399. };
  400.  
  401. marked.use({ renderer });
  402.  
  403. const htmlContent = marked.parse(markdownContentToParse);
  404.  
  405. document.body.innerHTML = '';
  406. document.body.appendChild(markdownBodyDiv);
  407. markdownBodyDiv.innerHTML = htmlContent;
  408.  
  409. } catch (e) {
  410. console.error("Markdown Viewer: Error during Markdown parsing:", e);
  411. markdownBodyDiv.innerHTML = `<p style="color:red; font-family:sans-serif;">Error rendering Markdown: ${e.message}. Check console for details.</p>`;
  412. if (!document.body.contains(markdownBodyDiv)) {
  413. document.body.innerHTML = '';
  414. document.body.appendChild(markdownBodyDiv);
  415. }
  416. }
  417. }
  418.  
  419. // Stelle sicher, dass das DOM bereit ist, bevor es manipuliert wird
  420. if (document.readyState === "complete" || document.readyState === "interactive") {
  421. initializeViewer();
  422. } else {
  423. document.addEventListener("DOMContentLoaded", initializeViewer);
  424. }
  425.  
  426. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址