Markdown-WorkFlowy

Supports Markdown in WorkFlowy

目前為 2022-03-14 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Markdown-WorkFlowy
  3. // @namespace https://github.com/BettyJJ
  4. // @version 0.2.1
  5. // @description Supports Markdown in WorkFlowy
  6. // @author Betty
  7. // @match https://workflowy.com/*
  8. // @match https://*.workflowy.com/*
  9. // @run-at document-idle
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.3.2/markdown-it.min.js
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js
  12. // @grant GM.addStyle
  13. // @grant GM_getResourceText
  14. // @resource TUI_CSS https://cdn.jsdelivr.net/npm/@toast-ui/editor@3.1.3/dist/toastui-editor-viewer.min.css
  15. // @resource HL_CSS https://unpkg.com/@highlightjs/cdn-assets@11.5.0/styles/github.min.css
  16. // ==/UserScript==
  17.  
  18. (function () {
  19. 'use strict';
  20.  
  21.  
  22. init();
  23.  
  24.  
  25. /**
  26. * initialize
  27. */
  28. function init() {
  29.  
  30. wait_for_page_load();
  31.  
  32. watch_page();
  33.  
  34. load_css();
  35.  
  36. // hide_note_raw();
  37. }
  38.  
  39.  
  40. /**
  41. * wait till the page is loaded
  42. */
  43. function wait_for_page_load() {
  44.  
  45. const observer = new MutationObserver(function (mutation_list) {
  46. for (const { addedNodes } of mutation_list) {
  47. for (const node of addedNodes) {
  48. if (!node.tagName) continue; // not an element
  49.  
  50. // this element appears when the page is loaded
  51. if (node.classList.contains('pageContainer')) {
  52. show_preview_button();
  53. }
  54.  
  55. }
  56. }
  57. });
  58. observer.observe(document.body, { childList: true, subtree: true });
  59.  
  60. }
  61.  
  62.  
  63. /**
  64. * show preview button
  65. */
  66. function show_preview_button() {
  67. // show preview button
  68. const btn = document.createElement('a');
  69. btn.className = 'bmd-preview-button';
  70. btn.textContent = 'Preview MD';
  71. const active_page = document.querySelector('.page.active');
  72. active_page.insertAdjacentElement('afterend', btn);
  73.  
  74. // insert preview box
  75. const preview = document.createElement('div');
  76. preview.className = 'bmd-preview-box';
  77. active_page.insertAdjacentElement('afterend', preview);
  78.  
  79. // bind click event
  80. btn.addEventListener('click', function () {
  81. // toggle class for page container
  82. const page_container = document.querySelector('.pageContainer');
  83. page_container.classList.toggle('bmd-has-preview');
  84.  
  85. // show the preview content if the box is not hidden
  86. if (page_container.classList.contains('bmd-has-preview')) {
  87. show_preview_content();
  88. }
  89. });
  90. }
  91.  
  92.  
  93. /**
  94. * show the preview content
  95. */
  96. function show_preview_content() {
  97. const raw = get_raw_content();
  98.  
  99. const preview = document.querySelector('.bmd-preview-box');
  100.  
  101. // use tui editor's style
  102. let content = document.querySelector('.toastui-editor-contents');
  103. if (content === null) {
  104. content = document.createElement('div');
  105. content.className = 'toastui-editor-contents';
  106. preview.appendChild(content);
  107. }
  108.  
  109. const md = get_mdit();
  110. const result = md.render(raw);
  111. content.innerHTML = result;
  112. }
  113.  
  114.  
  115. /**
  116. * get raw content
  117. * @returns {string}
  118. */
  119. function get_raw_content() {
  120. const node_list = document.getElementsByClassName('innerContentContainer');
  121.  
  122. let raw = '';
  123. for (let i = 0; i < node_list.length; i++) {
  124. const node = node_list[i];
  125.  
  126. // sometimes there is repetition. don't know why, but we need to check and exclude it first
  127. const parent = node.parentElement;
  128. const style = parent.getAttribute('style');
  129. if (style === null || style === '' || style.indexOf('visibility') !== -1) {
  130. raw += node.textContent + '\n';
  131. }
  132.  
  133. }
  134.  
  135. return raw;
  136. }
  137.  
  138. /**
  139. * return the object of markdown-it
  140. * @returns {object}
  141. */
  142. function get_mdit() {
  143. const md = window.markdownit({
  144. breaks: true,
  145. highlight: function (str, lang) {
  146. if (lang && hljs.getLanguage(lang)) {
  147. try {
  148. return hljs.highlight(str, { language: lang }).value;
  149. } catch (__) { }
  150. }
  151.  
  152. return '';
  153. },
  154. html: true,
  155. linkify: true,
  156. });
  157.  
  158. return md;
  159. }
  160.  
  161.  
  162.  
  163. /**
  164. * watch the page
  165. */
  166. function watch_page() {
  167.  
  168. // wathe the page, so that the rendering is updated when new contents come in as the user edits or navigates
  169. const observer = new MutationObserver(function (mutationlist) {
  170. for (const { addedNodes } of mutationlist) {
  171. for (const node of addedNodes) {
  172. if (!node.tagName) continue; // not an element
  173.  
  174. if (node.classList.contains('innerContentContainer')) {
  175. update_preview();
  176. }
  177.  
  178. }
  179. }
  180. });
  181. observer.observe(document.body, { childList: true, subtree: true });
  182.  
  183. }
  184.  
  185.  
  186. /**
  187. * update preview if the preview box is shown
  188. */
  189. function update_preview() {
  190. // only update the preview content if the box is not hidden
  191. const page_container = document.querySelector('.pageContainer');
  192. if (!page_container.classList.contains('bmd-has-preview')) {
  193. return;
  194. }
  195.  
  196. const content = document.querySelector('.toastui-editor-contents');
  197. if (!content) {
  198. return;
  199. }
  200.  
  201. // update the preview
  202. const raw = get_raw_content();
  203. const md = get_mdit();
  204. const result = md.render(raw);
  205. content.innerHTML = result;
  206. }
  207.  
  208.  
  209. /**
  210. * load TUI Editor css
  211. */
  212. function load_css() {
  213. const tui_css = GM_getResourceText('TUI_CSS');
  214. GM.addStyle(tui_css);
  215.  
  216. const hl_css = GM_getResourceText('HL_CSS');
  217. GM.addStyle(hl_css);
  218.  
  219. // override WF's interfering styles
  220. GM.addStyle(`
  221. .toastui-editor-contents th, .toastui-editor-contents tr, .toastui-editor-contents td {
  222. vertical-align: middle;
  223. }
  224. .toastui-editor-contents {
  225. font-size: 15px;
  226. }
  227.  
  228. /* support dark mode */
  229. .toastui-editor-contents h1, .toastui-editor-contents h2, .toastui-editor-contents h3, .toastui-editor-contents h4, .toastui-editor-contents h5, .toastui-editor-contents h6
  230. , .toastui-editor-contents p
  231. , .toastui-editor-contents dir, .toastui-editor-contents menu, .toastui-editor-contents ol, .toastui-editor-contents ul
  232. , .toastui-editor-contents table
  233. {
  234. color: revert;
  235. }
  236. .toastui-editor-contents table td, .toastui-editor-contents table th {
  237. border: 1px solid #dadada;
  238. }
  239. .toastui-editor-contents pre code {
  240. color: #2a3135;
  241. }
  242.  
  243. `);
  244.  
  245. // style for the preview box
  246. GM.addStyle(`
  247. .bmd-preview-button {
  248. background: white;
  249. border: solid 1px;
  250. color: #2a3135;
  251. padding: 6px;
  252. position: absolute;
  253. right: 24px;
  254. top: 50px;
  255. }
  256. .bmd-preview-button:hover {
  257. background: lightgray;
  258. text-decoration: none;
  259. }
  260.  
  261. .bmd-preview-box {
  262. display: none;
  263. }
  264. .bmd-has-preview .bmd-preview-box {
  265. display: block;
  266. }
  267.  
  268. .bmd-has-preview {
  269. display: flex;
  270. }
  271. .bmd-has-preview .page.active {
  272. flex-basis: 50%;
  273. flex-grow: 1;
  274. padding-left: 24px;
  275. padding-right: 24px;
  276. word-break: break-word;
  277. }
  278. .bmd-preview-box {
  279. border: solid 1px lightgray;
  280. flex-basis: 50%;
  281. flex-grow: 1;
  282. margin-top: 72px;
  283. padding: 24px;
  284. user-select: text;
  285. }
  286. `);
  287.  
  288. }
  289.  
  290.  
  291. })();