vnotex.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. /*
  2. The main object that will be provided to all scripts in VNoteX.
  3. Maintain a list of workers for different tasks.
  4. Main:
  5. - initialized()
  6. - ready()
  7. Markdown scenario:
  8. - markdownTextUpdated(p_text)
  9. - basicMarkdownRendered()
  10. - fullMarkdownRendered()
  11. */
  12. class VNoteX extends EventEmitter {
  13. constructor() {
  14. super();
  15. this.kickedOff = false;
  16. this.initialized = false;
  17. // Registered workers.
  18. // name -> worker.
  19. this.workers = new Map();
  20. this.numOfOngoingWorkers = 0;
  21. this.pendingData = {
  22. text: null,
  23. lineNumber: -1,
  24. anchor: null
  25. }
  26. this.numOfMuteScroll = 0;
  27. this.os = VNoteX.detectOS();
  28. this.turndown = null;
  29. this.sectionNumberBaseLevel = 2;
  30. // Dict mapping from {id, index} to callback for renderGraph().
  31. this.renderGraphCallbacks = {}
  32. window.addEventListener('load', () => {
  33. console.log('window load finished');
  34. // Init DOM nodes.
  35. this.contentContainer = document.getElementById('vx-content');
  36. this.inplacePreviewContainer = document.getElementById('vx-inplace-preview');
  37. this.nodeLineMapper = new NodeLineMapper(this, this.contentContainer);
  38. this.graphPreviewer = new GraphPreviewer(this, this.inplacePreviewContainer);
  39. this.crossCopyer = new CrossCopy(this);
  40. this.searcher = new MarkJs(this, this.contentContainer);
  41. this.sectionNumberBaseLevel = window.vxOptions.sectionNumberBaseLevel;
  42. if (this.sectionNumberBaseLevel > 3) {
  43. console.warn('only support section number base level less than 3', this.sectionNumberBaseLevel);
  44. this.sectionNumberBaseLevel = 3;
  45. }
  46. this.setContentContainerOption('vx-constrain-image-width',
  47. window.vxOptions.constrainImageWidthEnabled || !window.vxOptions.scrollable);
  48. this.setContentContainerOption('vx-image-align-center',
  49. window.vxOptions.imageAlignCenterEnabled);
  50. this.setContentContainerOption('vx-indent-first-line',
  51. window.vxOptions.indentFirstLineEnabled);
  52. this.setContentContainerOption('line-numbers',
  53. window.vxOptions.codeBlockLineNumberEnabled);
  54. this.setBodyOption('vx-transparent-background',
  55. window.vxOptions.transparentBackgroundEnabled);
  56. this.setContentContainerOption('vx-nonscrollable',
  57. !window.vxOptions.scrollable);
  58. this.setBodySize(window.vxOptions.bodyWidth, window.vxOptions.bodyHeight);
  59. document.body.style.height = '800';
  60. this.initialized = true;
  61. // Signal out.
  62. this.emit('initialized');
  63. this.emit('ready');
  64. });
  65. }
  66. setContentContainerOption(p_class, p_enabled) {
  67. if (p_enabled) {
  68. this.contentContainer.classList.add(p_class);
  69. } else {
  70. this.contentContainer.classList.remove(p_class);
  71. }
  72. }
  73. setBodyOption(p_class, p_enabled) {
  74. if (p_enabled) {
  75. document.body.classList.add(p_class);
  76. } else {
  77. document.body.classList.remove(p_class);
  78. }
  79. }
  80. registerWorker(p_worker) {
  81. this.workers.set(p_worker.name, p_worker);
  82. p_worker.register(this);
  83. }
  84. finishWorker(p_name) {
  85. --this.numOfOngoingWorkers;
  86. if (this.numOfOngoingWorkers == 0) {
  87. // Signal out anyway.
  88. this.emit('fullMarkdownRendered');
  89. window.vxMarkdownAdapter.setWorkFinished();
  90. // Check pending work.
  91. if (this.pendingData.text) {
  92. this.setMarkdownText(this.pendingData.text);
  93. } else if (this.pendingData.lineNumber > -1) {
  94. this.scrollToLine(this.pendingData.lineNumber);
  95. }
  96. }
  97. }
  98. getWorker(p_name) {
  99. return this.workers.get(p_name);
  100. }
  101. kickOffMarkdown() {
  102. if (this.kickedOff) {
  103. return;
  104. }
  105. console.log('viewer is ready now, kick off Markdown');
  106. this.kickedOff = true;
  107. window.vxMarkdownAdapter.setReady(true);
  108. }
  109. setMarkdownText(p_text) {
  110. if (this.numOfOngoingWorkers > 0) {
  111. this.pendingData.text = p_text;
  112. console.info('wait for last render finish with remaing workers',
  113. this.numOfOngoingWorkers);
  114. } else {
  115. this.numOfOngoingWorkers = this.workers.size;
  116. this.pendingData.text = null;
  117. console.log('start new round with ' + this.numOfOngoingWorkers + ' workers');
  118. this.emit('markdownTextUpdated', p_text);
  119. }
  120. }
  121. scrollToLine(p_lineNumber) {
  122. if (p_lineNumber < 0) {
  123. return;
  124. }
  125. if (this.numOfOngoingWorkers > 0) {
  126. this.pendingData.lineNumber = p_lineNumber;
  127. console.log('wait for render finish before scroll');
  128. } else {
  129. this.pendingData.lineNumber = -1;
  130. this.nodeLineMapper.scrollToLine(p_lineNumber);
  131. }
  132. }
  133. scrollToAnchor(p_anchor) {
  134. if (!p_anchor) {
  135. return;
  136. }
  137. if (this.numOfOngoingWorkers > 0) {
  138. this.pendingData.anchor = p_anchor;
  139. console.log('wait for render finish before scroll');
  140. } else {
  141. this.pendingData.anchor = '';
  142. this.nodeLineMapper.scrollToAnchor(p_anchor);
  143. }
  144. }
  145. setBasicMarkdownRendered() {
  146. this.emit('basicMarkdownRendered');
  147. }
  148. muteScroll() {
  149. ++this.numOfMuteScroll;
  150. }
  151. unmuteScroll() {
  152. window.setTimeout(() => {
  153. if (this.numOfMuteScroll > 0) {
  154. --this.numOfMuteScroll;
  155. if (this.numOfMuteScroll == 0) {
  156. this.nodeLineMapper.updateAfterScrollUnmuted();
  157. }
  158. }
  159. }, 1000);
  160. }
  161. isScrollMuted() {
  162. return this.numOfMuteScroll > 0;
  163. }
  164. setTopLineNumber(p_lineNumber) {
  165. window.vxMarkdownAdapter.setTopLineNumber(p_lineNumber);
  166. }
  167. previewGraph(p_id, p_timeStamp, p_lang, p_text) {
  168. if (this.graphPreviewer) {
  169. this.graphPreviewer.previewGraph(p_id, p_timeStamp, p_lang, p_text);
  170. }
  171. }
  172. previewMath(p_id, p_timeStamp, p_text) {
  173. if (this.graphPreviewer) {
  174. this.graphPreviewer.previewMath(p_id, p_timeStamp, p_text);
  175. }
  176. }
  177. setGraphPreviewData(p_data) {
  178. window.vxMarkdownAdapter.setGraphPreviewData(p_data.id,
  179. p_data.timeStamp,
  180. p_data.format,
  181. p_data.data,
  182. p_data.base64,
  183. p_data.needScale);
  184. }
  185. setMathPreviewData(p_data) {
  186. window.vxMarkdownAdapter.setMathPreviewData(p_data.id,
  187. p_data.timeStamp,
  188. p_data.format,
  189. p_data.data,
  190. p_data.base64,
  191. p_data.needScale);
  192. }
  193. setHeadings(p_headings) {
  194. window.vxMarkdownAdapter.setHeadings(p_headings);
  195. }
  196. setCurrentHeadingAnchor(p_idx, p_anchor) {
  197. window.vxMarkdownAdapter.setCurrentHeadingAnchor(p_idx, p_anchor);
  198. }
  199. setSectionNumberEnabled(p_enabled) {
  200. let sectionClass = 'vx-section-number';
  201. let sectionLevelClass = 'vx-section-number-' + this.sectionNumberBaseLevel;
  202. this.setContentContainerOption(sectionClass, p_enabled);
  203. this.setContentContainerOption(sectionLevelClass, p_enabled);
  204. }
  205. scroll(p_up) {
  206. EasyAccess.scroll(p_up);
  207. }
  208. setKeyPress(p_key, p_ctrl, p_shift, p_meta) {
  209. window.vxMarkdownAdapter.setKeyPress(p_key, p_ctrl, p_shift, p_meta);
  210. }
  211. zoom(p_zoomIn) {
  212. window.vxMarkdownAdapter.zoom(p_zoomIn);
  213. }
  214. htmlToMarkdown(p_id, p_timeStamp, p_html) {
  215. if (!this.turndown) {
  216. this.turndown = new TurndownConverter(this);
  217. }
  218. let markdown = this.turndown.turndown(p_html);
  219. window.vxMarkdownAdapter.setMarkdownFromHtml(p_id, p_timeStamp, markdown);
  220. }
  221. highlightCodeBlock(p_idx, p_timeStamp, p_text) {
  222. let match = /^```[^\S\n]*(\S+)?\s*\n([\s\S]+)\n```\s*$/.exec(p_text);
  223. if (!match || !match[1] || !match[2]) {
  224. window.vxMarkdownAdapter.setCodeBlockHighlightHtml(p_idx, p_timeStamp, '');
  225. return;
  226. }
  227. let lang = match[1];
  228. let body = match[2];
  229. if (Prism && Prism.languages[lang]) {
  230. let html = Prism.highlight(body, Prism.languages[lang], lang);
  231. window.vxMarkdownAdapter.setCodeBlockHighlightHtml(p_idx, p_timeStamp, html);
  232. } else {
  233. window.vxMarkdownAdapter.setCodeBlockHighlightHtml(p_idx, p_timeStamp, '');
  234. }
  235. }
  236. parseStyleSheet(p_id, p_styleSheet) {
  237. let doc = document.implementation.createHTMLDocument('');
  238. let styleEle = document.createElement('style');
  239. styleEle.textContent = p_styleSheet;
  240. doc.body.appendChild(styleEle);
  241. let styles = [];
  242. for (let i = 0; i < styleEle.sheet.cssRules.length; ++i) {
  243. let rule = styleEle.sheet.cssRules[i];
  244. if (rule.type != CSSRule.STYLE_RULE) {
  245. continue;
  246. }
  247. styles.push({
  248. selector: rule.selectorText,
  249. color: rule.style.color,
  250. backgroundColor: rule.style.backgroundColor,
  251. fontWeight: rule.style.fontWeight,
  252. fontStyle: rule.style.fontStyle
  253. });
  254. }
  255. window.vxMarkdownAdapter.setStyleSheetStyles(p_id, styles);
  256. }
  257. setCrossCopyTargets(p_targets) {
  258. window.vxMarkdownAdapter.setCrossCopyTargets(p_targets);
  259. }
  260. crossCopy(p_id, p_timeStamp, p_target, p_baseUrl, p_html) {
  261. this.crossCopyer.crossCopy(p_id, p_timeStamp, p_target, p_baseUrl, p_html);
  262. }
  263. setCrossCopyResult(p_id, p_timeStamp, p_html) {
  264. window.vxMarkdownAdapter.setCrossCopyResult(p_id, p_timeStamp, p_html);
  265. }
  266. findText(p_texts, p_options, p_currentMatchLine) {
  267. this.searcher.findText(p_texts, p_options, p_currentMatchLine);
  268. }
  269. showFindResult(p_texts, p_totalMatches, p_currentMatchIndex) {
  270. window.vxMarkdownAdapter.setFindText(p_texts, p_totalMatches, p_currentMatchIndex);
  271. }
  272. saveContent() {
  273. if (!this.initialized) {
  274. console.warn('saveContent() called before initialization');
  275. window.vxMarkdownAdapter.setSavedContent('', '', '');
  276. return;
  277. }
  278. window.vxMarkdownAdapter.setSavedContent("",
  279. Utils.fetchStyleContent(),
  280. this.contentContainer.outerHTML,
  281. document.body.classList.value);
  282. }
  283. setBodySize(p_width, p_height) {
  284. if (p_width > 0) {
  285. document.body.style.width = p_width + 'px';
  286. }
  287. if (p_height > 0) {
  288. document.body.style.height = p_height + 'px';
  289. }
  290. }
  291. renderGraph(p_id, p_index, p_format, p_lang, p_text, p_callback) {
  292. this.renderGraphCallbacks[p_id + '_' + p_index] = p_callback;
  293. window.vxMarkdownAdapter.renderGraph(p_id, p_index, p_format, p_lang, p_text);
  294. }
  295. graphRenderDataReady(p_id, p_index, p_format, p_data) {
  296. let key = p_id + '_' + p_index;
  297. if (key in this.renderGraphCallbacks) {
  298. this.renderGraphCallbacks[key](p_id, p_index, p_format, p_data);
  299. delete this.renderGraphCallbacks[key];
  300. }
  301. }
  302. static detectOS() {
  303. let osName="Unknown OS";
  304. if (navigator.appVersion.indexOf("Win")!=-1) {
  305. osName="Windows";
  306. } else if (navigator.appVersion.indexOf("Mac")!=-1) {
  307. osName="MacOS";
  308. } else if (navigator.appVersion.indexOf("X11")!=-1) {
  309. osName="UNIX";
  310. } else if (navigator.appVersion.indexOf("Linux")!=-1) {
  311. osName="Linux";
  312. }
  313. return osName
  314. }
  315. }
  316. window.vnotex = new VNoteX();