// ==UserScript== // @name Microsoft To-Do Markdown Preview Support - mstodo-md-preview // @namespace https://github.com/joisun // @version 1.0.1 // @author Zhongyi Sun // @description Microsoft To-Do Markdown Preview Support // @license MIT // @icon https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=https://to-do.live.com/&size=64 // @match https://to-do.live.com/* // @require https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.10.0/highlight.min.js // @require https://cdn.jsdelivr.net/npm/markdown-it@14.1.0/dist/markdown-it.min.js // @resource highlight.js/styles/tokyo-night-dark.min.css https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.10.0/styles/tokyo-night-dark.min.css // @grant GM_addStyle // @grant GM_getResourceText // @downloadURL none // ==/UserScript== (o=>{if(typeof GM_addStyle=="function"){GM_addStyle(o);return}const e=document.createElement("style");e.textContent=o,document.head.append(e)})(' .markdown-body{color:#fffc;font-size:1em;line-height:1.75}.markdown-body u{text-underline-offset:6px}.markdown-body hr{height:0;color:inherit;border-top-width:1px}.markdown-body abbr:where([title]){text-decoration:underline dotted}.markdown-body a{color:var(--fg-deeper);text-decoration:none;font-weight:500}.markdown-body b,.markdown-body strong{font-weight:bolder;margin:0 .2em;text-underline-offset:.2em;text-decoration:underline wavy;text-decoration-color:brown}.markdown-body code,.markdown-body kbd,.markdown-body samp,.markdown-body pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}.markdown-body sub{bottom:-.25em}.markdown-body sup{top:-.5em}.markdown-body blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}.markdown-body fieldset{margin:0;padding:0}.markdown-body legend{padding:0}.markdown-body ol,.markdown-body ul,.markdown-body menu{list-style:none;margin:0;padding:0}.markdown-body dialog{padding:0}.markdown-body textarea{resize:vertical}.markdown-body button,.markdown-body [role=button]{cursor:pointer}:disabled{cursor:default}.markdown-body [class~=lead]{color:#4b5563;font-size:1.25em;line-height:1.6;margin-top:1.2em;margin-bottom:1.2em}.markdown-body strong{color:var(--fg-deep);font-weight:600}.markdown-body ol>li,.markdown-body ul>li{position:relative;padding-left:1.75em}.markdown-body ol>li:before{content:counter(list-item,var(--list-counter-style, decimal)) ".";position:absolute;font-weight:400;color:#6b7280;left:0}.markdown-body ul>li:before{content:"";position:absolute;background-color:#d1d5db;border-radius:50%;width:.375em;height:.375em;top:.6875em;left:.25em}.markdown-body blockquote{font-weight:500;font-style:italic;color:inherit;border-left-width:.25rem;quotes:"\u201C" "\u201D" "\u2018" "\u2019";margin-top:1.6em;margin-bottom:1.6em;margin-left:auto;padding-left:1em}.markdown-body blockquote p:first-of-type:before{content:open-quote}.markdown-body blockquote p:last-of-type:after{content:close-quote}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{color:inherit;font-family:Menlo,Monaco,Consolas,Courier New,monospace;letter-spacing:.1em}.markdown-body h1{color:var(--fg-deeper);font-weight:800;font-size:2.25em;margin-top:0;margin-bottom:.8888889em;line-height:1.1111111}.markdown-body h2{color:var(--fg-deep);font-weight:700;font-size:1.6em;margin-top:0;margin-bottom:.5em;line-height:1.3333333}.markdown-body h3{color:inherit;font-weight:600;font-size:1.25em;margin-top:1.4em;margin-bottom:.6em;line-height:1.6}.markdown-body h4{color:inherit;font-weight:600;font-size:1.15em;margin-top:1.5em;margin-bottom:.5em;line-height:1.5}.markdown-body h5{color:inherit;font-weight:600;font-size:1.15em;margin-top:1.5em;opacity:.7;margin-bottom:.5em;line-height:1.5}.markdown-body figure figcaption{color:#6b7280;font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.markdown-body code{color:var(--fg-deep);font-weight:600;font-size:.875em}.markdown-body p code{font-weight:700;margin:0 .5em}.markdown-body p code:before{content:"`"}.markdown-body p code:after{content:"`"}.markdown-body a code{color:#111827}.markdown-body pre code:before,.markdown-body pre code:after{content:none}.markdown-body table{width:100%;table-layout:auto;text-align:left;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.7142857}.markdown-body thead{color:var(--fg-deep);font-weight:600;border-bottom-width:1px;border-bottom-color:#d1d5db}.markdown-body thead th{vertical-align:bottom;padding-right:.5714286em;padding-bottom:.5714286em;padding-left:.5714286em}.markdown-body tbody tr{border-bottom-width:1px;border-bottom-color:#e5e7eb}.markdown-body tbody tr:last-child{border-bottom-width:0}.markdown-body tbody td{vertical-align:top;padding:.5714286em}.markdown-body p{margin-top:1.25em;margin-bottom:1.25em;letter-spacing:.04em}.markdown-body img{width:100%;height:auto;margin-top:2em;margin-bottom:2em}.markdown-body video{margin-top:2em;margin-bottom:2em}.markdown-body figure{margin-top:2em;margin-bottom:2em}.markdown-body figure>*{margin-top:0;margin-bottom:0}.markdown-body h2 code{font-size:.875em}.markdown-body h3 code{font-size:.9em}.markdown-body ol,.markdown-body ul{margin-top:1.25em;margin-bottom:1.25em;list-style-type:none}.markdown-body li{margin-top:.5em;margin-bottom:.5em}.markdown-body pre{border-radius:.375em;border-width:1px;overflow:auto;padding:1.25em;background-color:#00000073;margin:.2em .4em}.markdown-body pre code{color:inherit;background:none;font-size:.975em;font-weight:400;overflow:visible;white-space:pre-wrap} '); (function (markdownit, hljs) { 'use strict'; function behindDebounce(func, duration) { let timer = null; return () => { timer && clearTimeout(timer); timer = setTimeout(() => { func(); }, duration); }; } function waitForElement(selector) { return new Promise((resolve) => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer2 = new MutationObserver(() => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer2.disconnect(); } }); observer2.observe(document.body, { childList: true, subtree: true }); }); } const cssLoader = (e) => { const t = GM_getResourceText(e); return GM_addStyle(t), t; }; cssLoader("highlight.js/styles/tokyo-night-dark.min.css"); const createBtnWithIcon = function(id, icon) { const btn = document.createElement("button"); btn.id = id; btn.innerHTML = icon; btn.style.width = "auto"; btn.style.height = "auto"; btn.style.color = "inherit"; btn.style.padding = ".2em .5em"; btn.style.backgroundColor = "#555"; btn.style.border = "none"; btn.style.borderRadius = "4px"; btn.style.display = "inline-flex"; btn.style.alignItems = "center"; btn.style.justifyContent = "center"; btn.style.cursor = "pointer"; btn.style.transition = "background-color 0.3s ease"; btn.addEventListener("mouseenter", () => { btn.style.backgroundColor = "#444"; }); btn.addEventListener("mouseleave", () => { btn.style.backgroundColor = "#555"; }); return btn; }; const md = markdownit({ // Enable HTML tags in source html: true, // Use '/' to close single tags (
) xhtmlOut: false, // Convert '\n' in paragraphs into
breaks: false, // CSS language prefix for fenced blocks langPrefix: "language-", // autoconvert URL-like texts to links linkify: true, // Enable smartypants and other sweet transforms typographer: true, highlight: function(str, lang) { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(str, { language: lang }).value; } catch (__) { } } return ""; } }); const EDIT_BTN_ID = "mstodo:editBtn"; const VIEW_BTN_ID = "mstodo:viewBtn"; waitForElement(".ql-editor").then(() => { observerHandler(); hideEditor(); observe(); clickListen(); }); let isEdit = false; function hideEditor() { if (isEdit) return; const editor = document.querySelector(".ql-editor"); editor && (editor.style.height = "0px"); const viewer = document.getElementById("tstodo:mdViewer"); viewer && (viewer.style.height = "auto"); } function showEditor() { isEdit = true; const editor = document.querySelector(".ql-editor"); editor && (editor.style.height = "auto"); const viewer = document.getElementById("tstodo:mdViewer"); viewer && (viewer.style.height = "0px"); } const initBtns = () => { var _a; if (document.getElementById("mstodo:btns")) return; const detailNote = document.querySelector(".detailNote"); if (!detailNote) return; const edit = createBtnWithIcon( EDIT_BTN_ID, `` ); const view = createBtnWithIcon( VIEW_BTN_ID, `` ); const btns = document.createElement("div"); btns.id = "mstodo:btns"; btns.style.display = "flex"; btns.style.gap = ".5em"; btns.style.justifyContent = "flex-end"; btns.style.padding = "0.5em 1em"; btns.appendChild(edit); btns.appendChild(view); detailNote.parentElement && ((_a = detailNote.parentElement) == null ? void 0 : _a.insertBefore(btns, detailNote)); edit.addEventListener("click", () => { showEditor(); }); view.addEventListener("click", () => { isEdit = false; hideEditor(); }); }; const createContainer = (qlEditor) => { var _a; if (!qlEditor) return; const container = document.createElement("div"); container.id = "tstodo:mdViewer"; container.classList.add("markdown-body"); container.style = ` overflow: hidden; background-color: inherit; `; container.addEventListener("click", (event) => { event.stopPropagation(); event.preventDefault(); }); qlEditor.parentElement && ((_a = qlEditor.parentElement) == null ? void 0 : _a.insertBefore(container, qlEditor)); return container; }; const observerHandler = behindDebounce(function() { const qlEditor = document.querySelector(".ql-editor"); console.log("mstodo:editor existed: ", !!qlEditor); const mdViewer = document.getElementById("tstodo:mdViewer") || createContainer(qlEditor); console.log("mstodo:mdviewer existed: ", !!mdViewer); initBtns(); if (!qlEditor) return; const mdContent = qlEditor.innerText; try { console.log("mstodo:parsing...."); let result = md.render(mdContent); mdViewer.innerHTML = result; if (!isEdit) hideEditor(); } catch (err) { console.error(err); } }, 100); const observer = new MutationObserver(observerHandler); function disconnect() { observer.disconnect(); } function observe() { disconnect(); observer.observe(document.querySelector(".ql-editor"), { characterData: true, // 观察目标节点内文本内容的变化 childList: true, // 观察目标节点中直接子节点的增删 subtree: true, // 观察所有后代节点 characterDataOldValue: true // 记录文本内容的变化前信息 }); } function clickListen() { document.addEventListener("click", function(e) { const target = e.target; const parent = document.querySelector(".tasks") || document.querySelector(".grid-body"); if (target.className === "taskItem-titleWrapper" || (parent == null ? void 0 : parent.contains(target))) { console.log("mstodo: click listener triggered"); observerHandler(); } }); } })(markdownit, hljs);