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
  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. if (parent.getAttribute('style') === null || parent.getAttribute('style').indexOf('visibility') !== -1) {
  129. raw += node.textContent + '\n';
  130. }
  131.  
  132. }
  133.  
  134. return raw;
  135. }
  136.  
  137. /**
  138. * return the object of markdown-it
  139. * @returns {object}
  140. */
  141. function get_mdit() {
  142. const md = window.markdownit({
  143. breaks: true,
  144. highlight: function (str, lang) {
  145. if (lang && hljs.getLanguage(lang)) {
  146. try {
  147. return hljs.highlight(str, { language: lang }).value;
  148. } catch (__) { }
  149. }
  150.  
  151. return '';
  152. },
  153. html: true,
  154. linkify: true,
  155. });
  156.  
  157. return md;
  158. }
  159.  
  160.  
  161.  
  162. /**
  163. * watch the page
  164. */
  165. function watch_page() {
  166.  
  167. // wathe the page, so that the rendering is updated when new contents come in as the user edits or navigates
  168. const observer = new MutationObserver(function (mutationlist) {
  169. for (const { addedNodes } of mutationlist) {
  170. for (const node of addedNodes) {
  171. if (!node.tagName) continue; // not an element
  172.  
  173. if (node.classList.contains('innerContentContainer')) {
  174. update_preview();
  175. }
  176.  
  177. }
  178. }
  179. });
  180. observer.observe(document.body, { childList: true, subtree: true });
  181.  
  182. }
  183.  
  184.  
  185. /**
  186. * update preview if the preview box is shown
  187. */
  188. function update_preview() {
  189. // only update the preview content if the box is not hidden
  190. const page_container = document.querySelector('.pageContainer');
  191. if (!page_container.classList.contains('bmd-has-preview')) {
  192. return;
  193. }
  194.  
  195. const content = document.querySelector('.toastui-editor-contents');
  196. if (!content) {
  197. return;
  198. }
  199.  
  200. // update the preview
  201. const raw = get_raw_content();
  202. const md = get_mdit();
  203. const result = md.render(raw);
  204. content.innerHTML = result;
  205. }
  206.  
  207.  
  208. /**
  209. * load TUI Editor css
  210. */
  211. function load_css() {
  212. const tui_css = GM_getResourceText('TUI_CSS');
  213. GM.addStyle(tui_css);
  214.  
  215. const hl_css = GM_getResourceText('HL_CSS');
  216. GM.addStyle(hl_css);
  217.  
  218. // override WF's interfering styles
  219. GM.addStyle(`
  220. .toastui-editor-contents th, .toastui-editor-contents tr, .toastui-editor-contents td {
  221. vertical-align: middle;
  222. }
  223. .toastui-editor-contents {
  224. font-size: 15px;
  225. }
  226.  
  227. /* support dark mode */
  228. .toastui-editor-contents h1, .toastui-editor-contents h2, .toastui-editor-contents h3, .toastui-editor-contents h4, .toastui-editor-contents h5, .toastui-editor-contents h6
  229. , .toastui-editor-contents p
  230. , .toastui-editor-contents dir, .toastui-editor-contents menu, .toastui-editor-contents ol, .toastui-editor-contents ul
  231. , .toastui-editor-contents table
  232. {
  233. color: revert;
  234. }
  235. .toastui-editor-contents table td, .toastui-editor-contents table th {
  236. border: 1px solid #dadada;
  237. }
  238. .toastui-editor-contents pre code {
  239. color: #2a3135;
  240. }
  241.  
  242. `);
  243.  
  244. // style for the preview box
  245. GM.addStyle(`
  246. .bmd-preview-button {
  247. background: white;
  248. border: solid 1px;
  249. color: #2a3135;
  250. padding: 6px;
  251. position: absolute;
  252. right: 24px;
  253. top: 50px;
  254. }
  255. .bmd-preview-button:hover {
  256. background: lightgray;
  257. text-decoration: none;
  258. }
  259.  
  260. .bmd-preview-box {
  261. display: none;
  262. }
  263. .bmd-has-preview .bmd-preview-box {
  264. display: block;
  265. }
  266.  
  267. .bmd-has-preview {
  268. display: flex;
  269. }
  270. .bmd-has-preview .page.active {
  271. flex-basis: 50%;
  272. flex-grow: 1;
  273. padding-left: 24px;
  274. padding-right: 24px;
  275. word-break: break-word;
  276. }
  277. .bmd-preview-box {
  278. border: solid 1px lightgray;
  279. flex-basis: 50%;
  280. flex-grow: 1;
  281. margin-top: 72px;
  282. padding: 24px;
  283. user-select: text;
  284. }
  285. `);
  286.  
  287. }
  288.  
  289.  
  290. })();