Markdown-WorkFlowy

Supports Markdown in WorkFlowy

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

  1. // ==UserScript==
  2. // @name Markdown-WorkFlowy
  3. // @namespace https://github.com/BettyJJ
  4. // @version 0.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. const content = document.createElement('div');
  103. content.className = 'toastui-editor-contents';
  104. preview.appendChild(content);
  105.  
  106. const md = get_mdit();
  107. const result = md.render(raw);
  108. content.innerHTML = result;
  109. }
  110.  
  111.  
  112. /**
  113. * get raw content
  114. * @returns {string}
  115. */
  116. function get_raw_content() {
  117. const node_list = document.getElementsByClassName('innerContentContainer');
  118.  
  119. let raw = '';
  120. for (let i = 0; i < node_list.length; i++) {
  121. const node = node_list[i];
  122.  
  123. // sometimes there is repetition. don't know why, but we need to check and exclude it first
  124. const parent = node.parentElement;
  125. if (parent.getAttribute('style') === null || parent.getAttribute('style').indexOf('visibility') !== -1) {
  126. raw += node.textContent + '\n';
  127. }
  128.  
  129. }
  130.  
  131. return raw;
  132. }
  133.  
  134. /**
  135. * return the object of markdown-it
  136. * @returns {object}
  137. */
  138. function get_mdit() {
  139. const md = window.markdownit({
  140. breaks: true,
  141. highlight: function (str, lang) {
  142. if (lang && hljs.getLanguage(lang)) {
  143. try {
  144. return hljs.highlight(str, { language: lang }).value;
  145. } catch (__) { }
  146. }
  147.  
  148. return '';
  149. },
  150. html: true,
  151. linkify: true,
  152. });
  153.  
  154. return md;
  155. }
  156.  
  157.  
  158.  
  159. /**
  160. * watch the page
  161. */
  162. function watch_page() {
  163.  
  164. // wathe the page, so that the rendering is updated when new contents come in as the user edits or navigates
  165. const observer = new MutationObserver(function (mutationlist) {
  166. for (const { addedNodes } of mutationlist) {
  167. for (const node of addedNodes) {
  168. if (!node.tagName) continue; // not an element
  169.  
  170. if (node.classList.contains('innerContentContainer')) {
  171. update_preview();
  172. }
  173.  
  174. }
  175. }
  176. });
  177. observer.observe(document.body, { childList: true, subtree: true });
  178.  
  179. }
  180.  
  181.  
  182. /**
  183. * update preview if the preview box is shown
  184. */
  185. function update_preview() {
  186. // only update the preview content if the box is not hidden
  187. const page_container = document.querySelector('.pageContainer');
  188. if (!page_container.classList.contains('bmd-has-preview')) {
  189. return;
  190. }
  191.  
  192. const content = document.querySelector('.toastui-editor-contents');
  193. if (!content) {
  194. return;
  195. }
  196.  
  197. // update the preview
  198. const raw = get_raw_content();
  199. const md = get_mdit();
  200. const result = md.render(raw);
  201. content.innerHTML = result;
  202. }
  203.  
  204.  
  205. /**
  206. * load TUI Editor css
  207. */
  208. function load_css() {
  209. const tui_css = GM_getResourceText('TUI_CSS');
  210. GM.addStyle(tui_css);
  211.  
  212. const hl_css = GM_getResourceText('HL_CSS');
  213. GM.addStyle(hl_css);
  214.  
  215. // override WF's interfering styles
  216. GM.addStyle(`
  217. .toastui-editor-contents th, .toastui-editor-contents tr, .toastui-editor-contents td {
  218. vertical-align: middle;
  219. }
  220. .toastui-editor-contents {
  221. font-size: 15px;
  222. }
  223. `);
  224.  
  225. // style for the preview box
  226. GM.addStyle(`
  227. .bmd-preview-button {
  228. background: white;
  229. border: solid 1px;
  230. padding: 6px;
  231. position: absolute;
  232. right: 24px;
  233. top: 50px;
  234. }
  235. .bmd-preview-button:hover {
  236. background: lightgray;
  237. text-decoration: none;
  238. }
  239.  
  240. .bmd-preview-box {
  241. display: none;
  242. }
  243. .bmd-has-preview .bmd-preview-box {
  244. display: block;
  245. }
  246.  
  247. .bmd-has-preview {
  248. display: flex;
  249. }
  250. .bmd-has-preview .page.active {
  251. flex-basis: 50%;
  252. flex-grow: 1;
  253. padding-left: 24px;
  254. padding-right: 24px;
  255. word-break: break-word;
  256. }
  257. .bmd-preview-box {
  258. border: solid 1px lightgray;
  259. flex-basis: 50%;
  260. flex-grow: 1;
  261. margin-top: 72px;
  262. padding: 24px;
  263. }
  264. `);
  265.  
  266. }
  267.  
  268.  
  269. })();