WaniKani Markdown Editor Notes (2023)

Write Markdown and HTML in the notes

当前为 2023-06-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WaniKani Markdown Editor Notes (2023)
  3. // @namespace wanikani
  4. // @description Write Markdown and HTML in the notes
  5. // @version 2.0.0
  6. // @require https://uicdn.toast.com/editor/latest/toastui-editor-all.min.js
  7. // @require https://unpkg.com/dexie@3/dist/dexie.js
  8. // @require https://greasyfork.org/scripts/430565-wanikani-item-info-injector/code/WaniKani%20Item%20Info%20Injector.user.js?version=1173815
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=markdownguide.org
  10. // @match *://www.wanikani.com/*
  11. // @match *://preview.wanikani.com/*
  12. // @license MIT
  13. // @source https://github.com/patarapolw/wanikani-userscript/blob/master/userscripts/markdown-notes.user.js
  14. // @supportURL https://community.wanikani.com/t/userscript-markdown-editor-notes-2023/62246
  15. // @grant none
  16. // ==/UserScript==
  17.  
  18. // @ts-check
  19. /// <reference path="./types/item-info.d.ts" />
  20. (function () {
  21. 'use strict';
  22.  
  23. const entryClazz = 'wk-markdown-notes';
  24.  
  25. // @ts-ignore
  26. const _Dexie = /** @type {typeof import('dexie').default} */ (Dexie);
  27. /**
  28. * @typedef {{ id: number; state: any; markdown: string }} EntryMarkdown
  29. */
  30.  
  31. class Database extends _Dexie {
  32. /** @type {import('dexie').Table<EntryMarkdown, number>} */
  33. markdown;
  34.  
  35. constructor() {
  36. super(entryClazz);
  37. this.version(1).stores({
  38. markdown: 'id',
  39. });
  40. }
  41. }
  42.  
  43. const db = new Database();
  44.  
  45. /** @type {HTMLElement} */
  46. let elEditor;
  47. /** @type {import('@toast-ui/editor').Editor} */
  48. let editor;
  49. /** @type {WKItemInfoState} */
  50. let state;
  51.  
  52. const injector = wkItemInfo
  53. .under('meaning,reading')
  54. .spoiling('nothing')
  55. .append('Markdown Notes', (o) => {
  56. if (editor) {
  57. save();
  58. }
  59.  
  60. state = o;
  61.  
  62. const onElLoaded = () => {
  63. db.markdown.get(state.id).then((entry) => {
  64. if (editor) {
  65. editor.setMarkdown(entry?.markdown || '');
  66. setTimeout(() => {
  67. editor.blur();
  68. });
  69. } else {
  70. /** @type {import('@toast-ui/editor').EditorOptions} */
  71. const opts = {
  72. el: elEditor,
  73. initialEditType: 'markdown',
  74. previewStyle: 'vertical',
  75. hideModeSwitch: true,
  76. linkAttributes: {
  77. target: '_blank',
  78. },
  79. previewHighlight: false,
  80. customHTMLSanitizer: (s) => {
  81. return s;
  82. },
  83. autofocus: false,
  84. initialValue: entry?.markdown,
  85. };
  86.  
  87. // @ts-ignore
  88. editor = new toastui.Editor(opts);
  89.  
  90. const elSave = document.createElement('button');
  91. elSave.type = 'button';
  92. elSave.className = 'fa fa-save save-button';
  93.  
  94. elSave.onclick = () => {
  95. elSave.classList.add(isClickedClass);
  96. save();
  97. setTimeout(() => {
  98. elSave.classList.remove(isClickedClass);
  99. }, 100);
  100. };
  101.  
  102. editor.insertToolbarItem(
  103. { groupIndex: -1, itemIndex: -1 },
  104. {
  105. name: 'Save',
  106. el: elSave,
  107. },
  108. );
  109.  
  110. editor.on('blur', () => {
  111. save();
  112. });
  113. }
  114. });
  115. };
  116.  
  117. if (!elEditor) {
  118. elEditor = document.createElement('div');
  119. elEditor.id = 'wk-markdown-editor';
  120. elEditor.lang = 'ja';
  121. elEditor.onkeydown = (ev) => {
  122. ev.stopImmediatePropagation();
  123. ev.stopPropagation();
  124. };
  125. }
  126.  
  127. onElLoaded();
  128.  
  129. return elEditor;
  130. });
  131.  
  132. window.addEventListener('willShowNextQuestion', () => {
  133. injector.renew();
  134. });
  135.  
  136. function save() {
  137. if (editor) {
  138. db.markdown.put(
  139. { id: state.id, state, markdown: editor.getMarkdown() },
  140. state.id,
  141. );
  142.  
  143. if (!elEditor) {
  144. editor.destroy();
  145. }
  146. }
  147. }
  148.  
  149. const isClickedClass = 'is-clicked';
  150.  
  151. add_css(/* css */ `
  152. @import url("https://uicdn.toast.com/editor/latest/toastui-editor.min.css");
  153.  
  154. .toastui-editor-defaultUI {
  155. background-color: #fff;
  156. }
  157.  
  158. .toastui-editor-defaultUI button.save-button {
  159. position: relative;
  160. background: transparent;
  161. font-size: 1em;
  162. bottom: 0.5em;
  163. }
  164.  
  165. .toastui-editor-defaultUI button.save-button.${isClickedClass} {
  166. background-color: gray;
  167. }
  168. `);
  169.  
  170. function add_css(css) {
  171. const style = document.createElement('style');
  172. style.append(document.createTextNode(css));
  173. document.head.append(style);
  174. }
  175. })();