@fiduswriter/editor 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +22 -0
  3. package/dist/bibliography/tools.js +53 -0
  4. package/dist/bibliography/tools.js.map +1 -0
  5. package/dist/citations/index.js +339 -0
  6. package/dist/citations/index.js.map +1 -0
  7. package/dist/citations/render.js +47 -0
  8. package/dist/citations/render.js.map +1 -0
  9. package/dist/clipboard/copy/index.js +2 -0
  10. package/dist/clipboard/copy/index.js.map +1 -0
  11. package/dist/clipboard/copy/schema.js +54 -0
  12. package/dist/clipboard/copy/schema.js.map +1 -0
  13. package/dist/clipboard/copy/serializers.js +132 -0
  14. package/dist/clipboard/copy/serializers.js.map +1 -0
  15. package/dist/clipboard/paste/fidus_writer.js +45 -0
  16. package/dist/clipboard/paste/fidus_writer.js.map +1 -0
  17. package/dist/clipboard/paste/general.js +76 -0
  18. package/dist/clipboard/paste/general.js.map +1 -0
  19. package/dist/clipboard/paste/google_docs.js +24 -0
  20. package/dist/clipboard/paste/google_docs.js.map +1 -0
  21. package/dist/clipboard/paste/html.js +68 -0
  22. package/dist/clipboard/paste/html.js.map +1 -0
  23. package/dist/clipboard/paste/index.js +3 -0
  24. package/dist/clipboard/paste/index.js.map +1 -0
  25. package/dist/clipboard/paste/libreoffice_writer.js +27 -0
  26. package/dist/clipboard/paste/libreoffice_writer.js.map +1 -0
  27. package/dist/clipboard/paste/microsoft_word.js +79 -0
  28. package/dist/clipboard/paste/microsoft_word.js.map +1 -0
  29. package/dist/clipboard/paste/text.js +41 -0
  30. package/dist/clipboard/paste/text.js.map +1 -0
  31. package/dist/collab/chat.js +154 -0
  32. package/dist/collab/chat.js.map +1 -0
  33. package/dist/collab/colors.js +62 -0
  34. package/dist/collab/colors.js.map +1 -0
  35. package/dist/collab/doc.js +1090 -0
  36. package/dist/collab/doc.js.map +1 -0
  37. package/dist/collab/index.js +75 -0
  38. package/dist/collab/index.js.map +1 -0
  39. package/dist/collab/merge/changeset.js +90 -0
  40. package/dist/collab/merge/changeset.js.map +1 -0
  41. package/dist/collab/merge/editor.js +708 -0
  42. package/dist/collab/merge/editor.js.map +1 -0
  43. package/dist/collab/merge/footnotes.js +149 -0
  44. package/dist/collab/merge/footnotes.js.map +1 -0
  45. package/dist/collab/merge/index.js +270 -0
  46. package/dist/collab/merge/index.js.map +1 -0
  47. package/dist/collab/merge/recreate_transform.js +259 -0
  48. package/dist/collab/merge/recreate_transform.js.map +1 -0
  49. package/dist/collab/merge/schema.js +117 -0
  50. package/dist/collab/merge/schema.js.map +1 -0
  51. package/dist/collab/merge/state_plugins/action.js +138 -0
  52. package/dist/collab/merge/state_plugins/action.js.map +1 -0
  53. package/dist/collab/merge/state_plugins/clipboard.js +32 -0
  54. package/dist/collab/merge/state_plugins/clipboard.js.map +1 -0
  55. package/dist/collab/merge/state_plugins/diff.js +541 -0
  56. package/dist/collab/merge/state_plugins/diff.js.map +1 -0
  57. package/dist/collab/merge/state_plugins/index.js +3 -0
  58. package/dist/collab/merge/state_plugins/index.js.map +1 -0
  59. package/dist/collab/merge/tools.js +175 -0
  60. package/dist/collab/merge/tools.js.map +1 -0
  61. package/dist/collab/templates.js +10 -0
  62. package/dist/collab/templates.js.map +1 -0
  63. package/dist/comments/comment.js +17 -0
  64. package/dist/comments/comment.js.map +1 -0
  65. package/dist/comments/editors/answer.js +43 -0
  66. package/dist/comments/editors/answer.js.map +1 -0
  67. package/dist/comments/editors/comment.js +278 -0
  68. package/dist/comments/editors/comment.js.map +1 -0
  69. package/dist/comments/editors/index.js +4 -0
  70. package/dist/comments/editors/index.js.map +1 -0
  71. package/dist/comments/editors/notify.js +13 -0
  72. package/dist/comments/editors/notify.js.map +1 -0
  73. package/dist/comments/editors/schema.js +60 -0
  74. package/dist/comments/editors/schema.js.map +1 -0
  75. package/dist/comments/index.js +11 -0
  76. package/dist/comments/index.js.map +1 -0
  77. package/dist/comments/interactions.js +379 -0
  78. package/dist/comments/interactions.js.map +1 -0
  79. package/dist/comments/store.js +438 -0
  80. package/dist/comments/store.js.map +1 -0
  81. package/dist/contacts/add_dialog.js +88 -0
  82. package/dist/contacts/add_dialog.js.map +1 -0
  83. package/dist/contacts/templates.js +23 -0
  84. package/dist/contacts/templates.js.map +1 -0
  85. package/dist/copyright_dialog/index.js +148 -0
  86. package/dist/copyright_dialog/index.js.map +1 -0
  87. package/dist/copyright_dialog/templates.js +48 -0
  88. package/dist/copyright_dialog/templates.js.map +1 -0
  89. package/dist/databases/bibliography.js +162 -0
  90. package/dist/databases/bibliography.js.map +1 -0
  91. package/dist/databases/images.js +260 -0
  92. package/dist/databases/images.js.map +1 -0
  93. package/dist/databases/index.js +55 -0
  94. package/dist/databases/index.js.map +1 -0
  95. package/dist/dialogs/citation.js +321 -0
  96. package/dist/dialogs/citation.js.map +1 -0
  97. package/dist/dialogs/code_block.js +192 -0
  98. package/dist/dialogs/code_block.js.map +1 -0
  99. package/dist/dialogs/contributor.js +132 -0
  100. package/dist/dialogs/contributor.js.map +1 -0
  101. package/dist/dialogs/figure.js +378 -0
  102. package/dist/dialogs/figure.js.map +1 -0
  103. package/dist/dialogs/index.js +11 -0
  104. package/dist/dialogs/index.js.map +1 -0
  105. package/dist/dialogs/language.js +47 -0
  106. package/dist/dialogs/language.js.map +1 -0
  107. package/dist/dialogs/link.js +243 -0
  108. package/dist/dialogs/link.js.map +1 -0
  109. package/dist/dialogs/math.js +121 -0
  110. package/dist/dialogs/math.js.map +1 -0
  111. package/dist/dialogs/ordered_list.js +67 -0
  112. package/dist/dialogs/ordered_list.js.map +1 -0
  113. package/dist/dialogs/revision.js +36 -0
  114. package/dist/dialogs/revision.js.map +1 -0
  115. package/dist/dialogs/subsup.js +109 -0
  116. package/dist/dialogs/subsup.js.map +1 -0
  117. package/dist/dialogs/table.js +217 -0
  118. package/dist/dialogs/table.js.map +1 -0
  119. package/dist/dialogs/templates.js +366 -0
  120. package/dist/dialogs/templates.js.map +1 -0
  121. package/dist/document_template/exporter.js +83 -0
  122. package/dist/document_template/exporter.js.map +1 -0
  123. package/dist/document_template/index.js +327 -0
  124. package/dist/document_template/index.js.map +1 -0
  125. package/dist/documents/access_rights/index.js +532 -0
  126. package/dist/documents/access_rights/index.js.map +1 -0
  127. package/dist/documents/access_rights/templates.js +150 -0
  128. package/dist/documents/access_rights/templates.js.map +1 -0
  129. package/dist/e2ee/encryptor.js +201 -0
  130. package/dist/e2ee/encryptor.js.map +1 -0
  131. package/dist/e2ee/key-manager.js +171 -0
  132. package/dist/e2ee/key-manager.js.map +1 -0
  133. package/dist/e2ee/passphrase-crypto.js +365 -0
  134. package/dist/e2ee/passphrase-crypto.js.map +1 -0
  135. package/dist/e2ee/passphrase-dialog.js +548 -0
  136. package/dist/e2ee/passphrase-dialog.js.map +1 -0
  137. package/dist/e2ee/passphrase-manager.js +350 -0
  138. package/dist/e2ee/passphrase-manager.js.map +1 -0
  139. package/dist/e2ee/password-dialog.js +552 -0
  140. package/dist/e2ee/password-dialog.js.map +1 -0
  141. package/dist/e2ee/snapshot-manager.js +238 -0
  142. package/dist/e2ee/snapshot-manager.js.map +1 -0
  143. package/dist/exporter/native/copy.js +29 -0
  144. package/dist/exporter/native/copy.js.map +1 -0
  145. package/dist/exporter/native/file.js +19 -0
  146. package/dist/exporter/native/file.js.map +1 -0
  147. package/dist/exporter/native/index.js +4 -0
  148. package/dist/exporter/native/index.js.map +1 -0
  149. package/dist/exporter/native/revision.js +54 -0
  150. package/dist/exporter/native/revision.js.map +1 -0
  151. package/dist/exporter/native/zip.js +2 -0
  152. package/dist/exporter/native/zip.js.map +1 -0
  153. package/dist/footnotes/editor.js +180 -0
  154. package/dist/footnotes/editor.js.map +1 -0
  155. package/dist/footnotes/index.js +15 -0
  156. package/dist/footnotes/index.js.map +1 -0
  157. package/dist/footnotes/layout.js +125 -0
  158. package/dist/footnotes/layout.js.map +1 -0
  159. package/dist/footnotes/state_plugins/access_rights.js +25 -0
  160. package/dist/footnotes/state_plugins/access_rights.js.map +1 -0
  161. package/dist/footnotes/state_plugins/index.js +2 -0
  162. package/dist/footnotes/state_plugins/index.js.map +1 -0
  163. package/dist/images/edit_dialog/edit_dialog/index.js +186 -0
  164. package/dist/images/edit_dialog/edit_dialog/index.js.map +1 -0
  165. package/dist/images/edit_dialog/edit_dialog/model.js +152 -0
  166. package/dist/images/edit_dialog/edit_dialog/model.js.map +1 -0
  167. package/dist/images/edit_dialog/edit_dialog/templates.js +33 -0
  168. package/dist/images/edit_dialog/edit_dialog/templates.js.map +1 -0
  169. package/dist/images/edit_dialog/model.js +6 -0
  170. package/dist/images/edit_dialog/model.js.map +1 -0
  171. package/dist/images/selection_dialog/index.js +149 -0
  172. package/dist/images/selection_dialog/index.js.map +1 -0
  173. package/dist/index.js +1002 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/keymap.js +73 -0
  176. package/dist/keymap.js.map +1 -0
  177. package/dist/marginboxes/index.js +674 -0
  178. package/dist/marginboxes/index.js.map +1 -0
  179. package/dist/marginboxes/templates.js +447 -0
  180. package/dist/marginboxes/templates.js.map +1 -0
  181. package/dist/menus/code_block/index.js +2 -0
  182. package/dist/menus/code_block/index.js.map +1 -0
  183. package/dist/menus/code_block/model.js +49 -0
  184. package/dist/menus/code_block/model.js.map +1 -0
  185. package/dist/menus/figure/index.js +3 -0
  186. package/dist/menus/figure/index.js.map +1 -0
  187. package/dist/menus/figure/model.js +80 -0
  188. package/dist/menus/figure/model.js.map +1 -0
  189. package/dist/menus/figure/utils.js +6 -0
  190. package/dist/menus/figure/utils.js.map +1 -0
  191. package/dist/menus/headerbar/index.js +3 -0
  192. package/dist/menus/headerbar/index.js.map +1 -0
  193. package/dist/menus/headerbar/model.js +743 -0
  194. package/dist/menus/headerbar/model.js.map +1 -0
  195. package/dist/menus/headerbar/view.js +579 -0
  196. package/dist/menus/headerbar/view.js.map +1 -0
  197. package/dist/menus/image/index.js +2 -0
  198. package/dist/menus/image/index.js.map +1 -0
  199. package/dist/menus/image/model.js +32 -0
  200. package/dist/menus/image/model.js.map +1 -0
  201. package/dist/menus/index.js +10 -0
  202. package/dist/menus/index.js.map +1 -0
  203. package/dist/menus/navigator/index.js +2 -0
  204. package/dist/menus/navigator/index.js.map +1 -0
  205. package/dist/menus/navigator/model.js +29 -0
  206. package/dist/menus/navigator/model.js.map +1 -0
  207. package/dist/menus/ordered_list/index.js +2 -0
  208. package/dist/menus/ordered_list/index.js.map +1 -0
  209. package/dist/menus/ordered_list/model.js +17 -0
  210. package/dist/menus/ordered_list/model.js.map +1 -0
  211. package/dist/menus/selection/index.js +3 -0
  212. package/dist/menus/selection/index.js.map +1 -0
  213. package/dist/menus/selection/model.js +108 -0
  214. package/dist/menus/selection/model.js.map +1 -0
  215. package/dist/menus/selection/view.js +125 -0
  216. package/dist/menus/selection/view.js.map +1 -0
  217. package/dist/menus/table/index.js +2 -0
  218. package/dist/menus/table/index.js.map +1 -0
  219. package/dist/menus/table/model.js +319 -0
  220. package/dist/menus/table/model.js.map +1 -0
  221. package/dist/menus/toolbar/index.js +3 -0
  222. package/dist/menus/toolbar/index.js.map +1 -0
  223. package/dist/menus/toolbar/model.js +747 -0
  224. package/dist/menus/toolbar/model.js.map +1 -0
  225. package/dist/menus/toolbar/view.js +314 -0
  226. package/dist/menus/toolbar/view.js.map +1 -0
  227. package/dist/navigator/index.js +435 -0
  228. package/dist/navigator/index.js.map +1 -0
  229. package/dist/no_collab_save/index.js +169 -0
  230. package/dist/no_collab_save/index.js.map +1 -0
  231. package/dist/plugins/citation_dialog/index.js +2 -0
  232. package/dist/plugins/citation_dialog/index.js.map +1 -0
  233. package/dist/plugins/editor/index.js +2 -0
  234. package/dist/plugins/editor/index.js.map +1 -0
  235. package/dist/state_plugins/access_rights.js +30 -0
  236. package/dist/state_plugins/access_rights.js.map +1 -0
  237. package/dist/state_plugins/citation_render.js +78 -0
  238. package/dist/state_plugins/citation_render.js.map +1 -0
  239. package/dist/state_plugins/clipboard.js +217 -0
  240. package/dist/state_plugins/clipboard.js.map +1 -0
  241. package/dist/state_plugins/code_block.js +165 -0
  242. package/dist/state_plugins/code_block.js.map +1 -0
  243. package/dist/state_plugins/collab_carets.js +115 -0
  244. package/dist/state_plugins/collab_carets.js.map +1 -0
  245. package/dist/state_plugins/comments.js +197 -0
  246. package/dist/state_plugins/comments.js.map +1 -0
  247. package/dist/state_plugins/contributor_input/add_button.js +101 -0
  248. package/dist/state_plugins/contributor_input/add_button.js.map +1 -0
  249. package/dist/state_plugins/contributor_input/dropup.js +273 -0
  250. package/dist/state_plugins/contributor_input/dropup.js.map +1 -0
  251. package/dist/state_plugins/contributor_input/helpers.js +23 -0
  252. package/dist/state_plugins/contributor_input/helpers.js.map +1 -0
  253. package/dist/state_plugins/contributor_input/index.js +3 -0
  254. package/dist/state_plugins/contributor_input/index.js.map +1 -0
  255. package/dist/state_plugins/contributor_input/node_view.js +76 -0
  256. package/dist/state_plugins/contributor_input/node_view.js.map +1 -0
  257. package/dist/state_plugins/contributor_input/plugin.js +236 -0
  258. package/dist/state_plugins/contributor_input/plugin.js.map +1 -0
  259. package/dist/state_plugins/document_template.js +213 -0
  260. package/dist/state_plugins/document_template.js.map +1 -0
  261. package/dist/state_plugins/figure.js +90 -0
  262. package/dist/state_plugins/figure.js.map +1 -0
  263. package/dist/state_plugins/footnote_markers.js +187 -0
  264. package/dist/state_plugins/footnote_markers.js.map +1 -0
  265. package/dist/state_plugins/headerbar.js +10 -0
  266. package/dist/state_plugins/headerbar.js.map +1 -0
  267. package/dist/state_plugins/index.js +27 -0
  268. package/dist/state_plugins/index.js.map +1 -0
  269. package/dist/state_plugins/inline_math.js +719 -0
  270. package/dist/state_plugins/inline_math.js.map +1 -0
  271. package/dist/state_plugins/inline_reference/dropup.js +182 -0
  272. package/dist/state_plugins/inline_reference/dropup.js.map +1 -0
  273. package/dist/state_plugins/inline_reference/index.js +2 -0
  274. package/dist/state_plugins/inline_reference/index.js.map +1 -0
  275. package/dist/state_plugins/inline_reference/plugin.js +1134 -0
  276. package/dist/state_plugins/inline_reference/plugin.js.map +1 -0
  277. package/dist/state_plugins/jump_hidden_nodes.js +47 -0
  278. package/dist/state_plugins/jump_hidden_nodes.js.map +1 -0
  279. package/dist/state_plugins/links.js +622 -0
  280. package/dist/state_plugins/links.js.map +1 -0
  281. package/dist/state_plugins/marginboxes.js +13 -0
  282. package/dist/state_plugins/marginboxes.js.map +1 -0
  283. package/dist/state_plugins/ordered_list_menu.js +72 -0
  284. package/dist/state_plugins/ordered_list_menu.js.map +1 -0
  285. package/dist/state_plugins/placeholders.js +98 -0
  286. package/dist/state_plugins/placeholders.js.map +1 -0
  287. package/dist/state_plugins/search.js +212 -0
  288. package/dist/state_plugins/search.js.map +1 -0
  289. package/dist/state_plugins/selection_menu.js +10 -0
  290. package/dist/state_plugins/selection_menu.js.map +1 -0
  291. package/dist/state_plugins/settings.js +191 -0
  292. package/dist/state_plugins/settings.js.map +1 -0
  293. package/dist/state_plugins/table.js +104 -0
  294. package/dist/state_plugins/table.js.map +1 -0
  295. package/dist/state_plugins/tag_input/helpers.js +55 -0
  296. package/dist/state_plugins/tag_input/helpers.js.map +1 -0
  297. package/dist/state_plugins/tag_input/index.js +3 -0
  298. package/dist/state_plugins/tag_input/index.js.map +1 -0
  299. package/dist/state_plugins/tag_input/node_view.js +77 -0
  300. package/dist/state_plugins/tag_input/node_view.js.map +1 -0
  301. package/dist/state_plugins/tag_input/plugin.js +199 -0
  302. package/dist/state_plugins/tag_input/plugin.js.map +1 -0
  303. package/dist/state_plugins/tag_input/tag_editor.js +159 -0
  304. package/dist/state_plugins/tag_input/tag_editor.js.map +1 -0
  305. package/dist/state_plugins/tag_input/tag_editor_plugins.js +61 -0
  306. package/dist/state_plugins/tag_input/tag_editor_plugins.js.map +1 -0
  307. package/dist/state_plugins/toc_render.js +142 -0
  308. package/dist/state_plugins/toc_render.js.map +1 -0
  309. package/dist/state_plugins/toolbar.js +10 -0
  310. package/dist/state_plugins/toolbar.js.map +1 -0
  311. package/dist/state_plugins/track/find_selected_changes.js +82 -0
  312. package/dist/state_plugins/track/find_selected_changes.js.map +1 -0
  313. package/dist/state_plugins/track/helpers.js +67 -0
  314. package/dist/state_plugins/track/helpers.js.map +1 -0
  315. package/dist/state_plugins/track/index.js +3 -0
  316. package/dist/state_plugins/track/index.js.map +1 -0
  317. package/dist/state_plugins/track/plugin.js +114 -0
  318. package/dist/state_plugins/track/plugin.js.map +1 -0
  319. package/dist/tools/index.js +4 -0
  320. package/dist/tools/index.js.map +1 -0
  321. package/dist/tools/key_bindings/index.js +20 -0
  322. package/dist/tools/key_bindings/index.js.map +1 -0
  323. package/dist/tools/key_bindings/templates.js +68 -0
  324. package/dist/tools/key_bindings/templates.js.map +1 -0
  325. package/dist/tools/search_replace/index.js +275 -0
  326. package/dist/tools/search_replace/index.js.map +1 -0
  327. package/dist/tools/search_replace/templates.js +18 -0
  328. package/dist/tools/search_replace/templates.js.map +1 -0
  329. package/dist/tools/word_count/index.js +40 -0
  330. package/dist/tools/word_count/index.js.map +1 -0
  331. package/dist/tools/word_count/templates.js +27 -0
  332. package/dist/tools/word_count/templates.js.map +1 -0
  333. package/dist/track/accept.js +66 -0
  334. package/dist/track/accept.js.map +1 -0
  335. package/dist/track/accept_all.js +51 -0
  336. package/dist/track/accept_all.js.map +1 -0
  337. package/dist/track/accept_all_no_insertions.js +63 -0
  338. package/dist/track/accept_all_no_insertions.js.map +1 -0
  339. package/dist/track/amend_transaction.js +390 -0
  340. package/dist/track/amend_transaction.js.map +1 -0
  341. package/dist/track/delete.js +62 -0
  342. package/dist/track/delete.js.map +1 -0
  343. package/dist/track/index.js +6 -0
  344. package/dist/track/index.js.map +1 -0
  345. package/dist/track/mod.js +86 -0
  346. package/dist/track/mod.js.map +1 -0
  347. package/dist/track/reject.js +67 -0
  348. package/dist/track/reject.js.map +1 -0
  349. package/dist/track/reject_all.js +56 -0
  350. package/dist/track/reject_all.js.map +1 -0
  351. package/package.json +80 -0
  352. package/src/bibliography/tools.js +58 -0
  353. package/src/citations/index.js +443 -0
  354. package/src/citations/render.js +68 -0
  355. package/src/clipboard/copy/index.js +1 -0
  356. package/src/clipboard/copy/schema.js +62 -0
  357. package/src/clipboard/copy/serializers.js +179 -0
  358. package/src/clipboard/paste/fidus_writer.js +55 -0
  359. package/src/clipboard/paste/general.js +80 -0
  360. package/src/clipboard/paste/google_docs.js +26 -0
  361. package/src/clipboard/paste/html.js +76 -0
  362. package/src/clipboard/paste/index.js +2 -0
  363. package/src/clipboard/paste/libreoffice_writer.js +32 -0
  364. package/src/clipboard/paste/microsoft_word.js +87 -0
  365. package/src/clipboard/paste/text.js +58 -0
  366. package/src/collab/chat.js +195 -0
  367. package/src/collab/colors.js +67 -0
  368. package/src/collab/doc.js +1436 -0
  369. package/src/collab/index.js +81 -0
  370. package/src/collab/merge/changeset.js +103 -0
  371. package/src/collab/merge/editor.js +1065 -0
  372. package/src/collab/merge/footnotes.js +175 -0
  373. package/src/collab/merge/index.js +401 -0
  374. package/src/collab/merge/recreate_transform.js +326 -0
  375. package/src/collab/merge/schema.js +135 -0
  376. package/src/collab/merge/state_plugins/action.js +171 -0
  377. package/src/collab/merge/state_plugins/clipboard.js +32 -0
  378. package/src/collab/merge/state_plugins/diff.js +780 -0
  379. package/src/collab/merge/state_plugins/index.js +2 -0
  380. package/src/collab/merge/tools.js +217 -0
  381. package/src/collab/templates.js +10 -0
  382. package/src/comments/comment.js +29 -0
  383. package/src/comments/editors/answer.js +55 -0
  384. package/src/comments/editors/comment.js +318 -0
  385. package/src/comments/editors/index.js +4 -0
  386. package/src/comments/editors/notify.js +13 -0
  387. package/src/comments/editors/schema.js +65 -0
  388. package/src/comments/index.js +11 -0
  389. package/src/comments/interactions.js +465 -0
  390. package/src/comments/store.js +513 -0
  391. package/src/contacts/add_dialog.js +110 -0
  392. package/src/contacts/templates.js +28 -0
  393. package/src/copyright_dialog/index.js +179 -0
  394. package/src/copyright_dialog/templates.js +56 -0
  395. package/src/databases/bibliography.js +179 -0
  396. package/src/databases/images.js +320 -0
  397. package/src/databases/index.js +69 -0
  398. package/src/dialogs/citation.js +428 -0
  399. package/src/dialogs/code_block.js +242 -0
  400. package/src/dialogs/contributor.js +173 -0
  401. package/src/dialogs/figure.js +479 -0
  402. package/src/dialogs/index.js +10 -0
  403. package/src/dialogs/language.js +58 -0
  404. package/src/dialogs/link.js +358 -0
  405. package/src/dialogs/math.js +148 -0
  406. package/src/dialogs/ordered_list.js +84 -0
  407. package/src/dialogs/revision.js +43 -0
  408. package/src/dialogs/subsup.js +116 -0
  409. package/src/dialogs/table.js +258 -0
  410. package/src/dialogs/templates.js +426 -0
  411. package/src/document_template/exporter.js +97 -0
  412. package/src/document_template/index.js +483 -0
  413. package/src/documents/access_rights/index.js +693 -0
  414. package/src/documents/access_rights/templates.js +178 -0
  415. package/src/e2ee/encryptor.js +228 -0
  416. package/src/e2ee/key-manager.js +202 -0
  417. package/src/e2ee/passphrase-crypto.js +606 -0
  418. package/src/e2ee/passphrase-dialog.js +655 -0
  419. package/src/e2ee/passphrase-manager.js +502 -0
  420. package/src/e2ee/password-dialog.js +652 -0
  421. package/src/e2ee/snapshot-manager.js +305 -0
  422. package/src/exporter/native/copy.js +54 -0
  423. package/src/exporter/native/file.js +37 -0
  424. package/src/exporter/native/index.js +3 -0
  425. package/src/exporter/native/revision.js +86 -0
  426. package/src/exporter/native/zip.js +1 -0
  427. package/src/footnotes/editor.js +227 -0
  428. package/src/footnotes/index.js +16 -0
  429. package/src/footnotes/layout.js +190 -0
  430. package/src/footnotes/state_plugins/access_rights.js +39 -0
  431. package/src/footnotes/state_plugins/index.js +1 -0
  432. package/src/images/edit_dialog/edit_dialog/index.js +222 -0
  433. package/src/images/edit_dialog/edit_dialog/model.js +183 -0
  434. package/src/images/edit_dialog/edit_dialog/templates.js +41 -0
  435. package/src/images/edit_dialog/model.js +5 -0
  436. package/src/images/selection_dialog/index.js +176 -0
  437. package/src/index.js +1252 -0
  438. package/src/keymap.js +102 -0
  439. package/src/marginboxes/index.js +946 -0
  440. package/src/marginboxes/templates.js +573 -0
  441. package/src/menus/code_block/index.js +1 -0
  442. package/src/menus/code_block/model.js +52 -0
  443. package/src/menus/figure/index.js +2 -0
  444. package/src/menus/figure/model.js +91 -0
  445. package/src/menus/figure/utils.js +5 -0
  446. package/src/menus/headerbar/index.js +2 -0
  447. package/src/menus/headerbar/model.js +1077 -0
  448. package/src/menus/headerbar/view.js +675 -0
  449. package/src/menus/image/index.js +1 -0
  450. package/src/menus/image/model.js +32 -0
  451. package/src/menus/index.js +9 -0
  452. package/src/menus/navigator/index.js +1 -0
  453. package/src/menus/navigator/model.js +28 -0
  454. package/src/menus/ordered_list/index.js +1 -0
  455. package/src/menus/ordered_list/model.js +19 -0
  456. package/src/menus/selection/index.js +2 -0
  457. package/src/menus/selection/model.js +134 -0
  458. package/src/menus/selection/view.js +154 -0
  459. package/src/menus/table/index.js +1 -0
  460. package/src/menus/table/model.js +414 -0
  461. package/src/menus/toolbar/index.js +2 -0
  462. package/src/menus/toolbar/model.js +920 -0
  463. package/src/menus/toolbar/view.js +354 -0
  464. package/src/navigator/index.js +515 -0
  465. package/src/no_collab_save/index.js +209 -0
  466. package/src/plugins/citation_dialog/index.js +1 -0
  467. package/src/plugins/editor/index.js +1 -0
  468. package/src/state_plugins/access_rights.js +47 -0
  469. package/src/state_plugins/citation_render.js +88 -0
  470. package/src/state_plugins/clipboard.js +289 -0
  471. package/src/state_plugins/code_block.js +201 -0
  472. package/src/state_plugins/collab_carets.js +152 -0
  473. package/src/state_plugins/comments.js +259 -0
  474. package/src/state_plugins/contributor_input/add_button.js +134 -0
  475. package/src/state_plugins/contributor_input/dropup.js +318 -0
  476. package/src/state_plugins/contributor_input/helpers.js +24 -0
  477. package/src/state_plugins/contributor_input/index.js +2 -0
  478. package/src/state_plugins/contributor_input/node_view.js +86 -0
  479. package/src/state_plugins/contributor_input/plugin.js +318 -0
  480. package/src/state_plugins/document_template.js +315 -0
  481. package/src/state_plugins/figure.js +108 -0
  482. package/src/state_plugins/footnote_markers.js +238 -0
  483. package/src/state_plugins/headerbar.js +12 -0
  484. package/src/state_plugins/index.js +73 -0
  485. package/src/state_plugins/inline_math.js +918 -0
  486. package/src/state_plugins/inline_reference/dropup.js +208 -0
  487. package/src/state_plugins/inline_reference/index.js +5 -0
  488. package/src/state_plugins/inline_reference/plugin.js +1429 -0
  489. package/src/state_plugins/jump_hidden_nodes.js +58 -0
  490. package/src/state_plugins/links.js +830 -0
  491. package/src/state_plugins/marginboxes.js +14 -0
  492. package/src/state_plugins/ordered_list_menu.js +84 -0
  493. package/src/state_plugins/placeholders.js +123 -0
  494. package/src/state_plugins/search.js +251 -0
  495. package/src/state_plugins/selection_menu.js +12 -0
  496. package/src/state_plugins/settings.js +223 -0
  497. package/src/state_plugins/table.js +127 -0
  498. package/src/state_plugins/tag_input/helpers.js +69 -0
  499. package/src/state_plugins/tag_input/index.js +2 -0
  500. package/src/state_plugins/tag_input/node_view.js +85 -0
  501. package/src/state_plugins/tag_input/plugin.js +278 -0
  502. package/src/state_plugins/tag_input/tag_editor.js +202 -0
  503. package/src/state_plugins/tag_input/tag_editor_plugins.js +74 -0
  504. package/src/state_plugins/toc_render.js +160 -0
  505. package/src/state_plugins/toolbar.js +12 -0
  506. package/src/state_plugins/track/find_selected_changes.js +107 -0
  507. package/src/state_plugins/track/helpers.js +108 -0
  508. package/src/state_plugins/track/index.js +6 -0
  509. package/src/state_plugins/track/plugin.js +140 -0
  510. package/src/tools/index.js +3 -0
  511. package/src/tools/key_bindings/index.js +21 -0
  512. package/src/tools/key_bindings/templates.js +68 -0
  513. package/src/tools/search_replace/index.js +421 -0
  514. package/src/tools/search_replace/templates.js +19 -0
  515. package/src/tools/word_count/index.js +59 -0
  516. package/src/tools/word_count/templates.js +34 -0
  517. package/src/track/accept.js +102 -0
  518. package/src/track/accept_all.js +103 -0
  519. package/src/track/accept_all_no_insertions.js +102 -0
  520. package/src/track/amend_transaction.js +562 -0
  521. package/src/track/delete.js +92 -0
  522. package/src/track/index.js +8 -0
  523. package/src/track/mod.js +116 -0
  524. package/src/track/reject.js +116 -0
  525. package/src/track/reject_all.js +121 -0
package/src/index.js ADDED
@@ -0,0 +1,1252 @@
1
+ import {
2
+ Dialog,
3
+ WebSocketConnector,
4
+ activateWait,
5
+ addAlert,
6
+ deactivateWait,
7
+ ensureCSS,
8
+ postJson,
9
+ showSystemMessage,
10
+ whenReady
11
+ } from "fwtoolkit"
12
+ import {collab, sendableSteps} from "prosemirror-collab"
13
+ import {baseKeymap} from "prosemirror-commands"
14
+ import {dropCursor} from "prosemirror-dropcursor"
15
+ import {buildKeymap} from "prosemirror-example-setup"
16
+ import {gapCursor} from "prosemirror-gapcursor"
17
+ import {history} from "prosemirror-history"
18
+ import {keymap} from "prosemirror-keymap"
19
+ import {EditorState, TextSelection} from "prosemirror-state"
20
+ import {tableEditing} from "prosemirror-tables"
21
+ import {EditorView} from "prosemirror-view"
22
+ import {FeedbackTab} from "@fiduswriter/common"
23
+ import {E2EEEncryptor} from "./e2ee/encryptor"
24
+ import {E2EEKeyManager} from "./e2ee/key-manager"
25
+ import {
26
+ enterPassphraseDialog,
27
+ setupPassphraseDialog,
28
+ showRecoveryKeyDialog
29
+ } from "./e2ee/passphrase-dialog"
30
+ import {PassphraseManager} from "./e2ee/passphrase-manager"
31
+ import {
32
+ changePasswordDialog,
33
+ createPasswordDialog
34
+ } from "./e2ee/password-dialog"
35
+ import {E2EESnapshotManager} from "./e2ee/snapshot-manager"
36
+
37
+ import {getSettings} from "@fiduswriter/document/schema/convert"
38
+ import {docSchema} from "@fiduswriter/document/schema/document/index"
39
+ import {plugins} from "../../plugins/editor"
40
+ import {ModCitations} from "./citations"
41
+ import {ModCollab} from "./collab"
42
+ import {ModComments} from "./comments"
43
+ import {ModDB} from "./databases"
44
+ import {ModDocumentTemplate} from "./document_template"
45
+ import {ModFootnotes} from "./footnotes"
46
+ import {ModMarginboxes} from "./marginboxes"
47
+ import {
48
+ codeBlockMenuModel,
49
+ figureMenuModel,
50
+ figureWidthMenuModel,
51
+ headerbarModel,
52
+ imageMenuModel,
53
+ navigatorFilterModel,
54
+ orderedListMenuModel,
55
+ selectionMenuModel,
56
+ tableMenuModel,
57
+ toolbarModel
58
+ } from "./menus"
59
+ import {ModNavigator} from "./navigator"
60
+ import {NoCollabSave} from "./no_collab_save"
61
+ import {ModTrack, acceptAllNoInsertions, amendTransaction} from "./track"
62
+
63
+ import {ExportFidusFile} from "../exporter/native/file"
64
+ import {imageEditModel} from "../images/edit_dialog/model"
65
+ import {buildEditorKeymap} from "./keymap"
66
+ import {
67
+ accessRightsPlugin,
68
+ citationRenderPlugin,
69
+ clipboardPlugin,
70
+ codeBlockPlugin,
71
+ collabCaretsPlugin,
72
+ commentsPlugin,
73
+ contributorInputPlugin,
74
+ documentTemplatePlugin,
75
+ figurePlugin,
76
+ footnoteMarkersPlugin,
77
+ headerbarPlugin,
78
+ inlineMathPlugin,
79
+ inlineReferencePlugin,
80
+ jumpHiddenNodesPlugin,
81
+ linksPlugin,
82
+ marginboxesPlugin,
83
+ orderedListMenuPlugin,
84
+ placeholdersPlugin,
85
+ searchPlugin,
86
+ selectionMenuPlugin,
87
+ settingsPlugin,
88
+ tablePlugin,
89
+ tagInputPlugin,
90
+ tocRenderPlugin,
91
+ toolbarPlugin,
92
+ trackPlugin
93
+ } from "./state_plugins"
94
+
95
+ // UUID v4 pattern for share tokens
96
+ const uuid4Pattern =
97
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
98
+
99
+ export const COMMENT_ONLY_ROLES = ["review", "comment"]
100
+ export const READ_ONLY_ROLES = ["read", "read-without-comments"]
101
+ export const REVIEW_ROLES = ["review", "review-tracked"]
102
+ export const WRITE_ROLES = ["write", "write-tracked", "review-tracked"]
103
+
104
+ export class Editor {
105
+ // A class that contains everything that happens on the editor page.
106
+ // It is currently not possible to initialize more than one editor class, as it
107
+ // contains bindings to menu items, etc. that are uniquely defined.
108
+ constructor({app, user}, path, idString) {
109
+ this.app = app
110
+ // For unauthenticated guests, replace the bare config object with a
111
+ // proper placeholder that has all the fields templates expect.
112
+ this.user = user.is_authenticated
113
+ ? user
114
+ : {id: undefined, username: "", name: "", is_authenticated: false}
115
+ this.mod = {}
116
+ // Whether the editor is currently waiting for a document update. Set to true
117
+ // initially so that diffs that arrive before document has been loaded are not
118
+ // dealt with.
119
+ this.waitingForDocument = true
120
+
121
+ this.docInfo = {
122
+ rights: "",
123
+ owner: undefined,
124
+ is_owner: false,
125
+ confirmedDoc: false, // The latest doc as confirmed by the server.
126
+ updated: false, // Latest update time stamp
127
+ dir: "ltr", // standard direction, used in input fields, etc.
128
+ path // Default doc path.
129
+ }
130
+ if (uuid4Pattern.test(idString)) {
131
+ // Share token — store the raw UUID; init() will resolve it to a real doc id
132
+ this.docInfo.id = idString
133
+ this.docInfo.token = idString
134
+ } else {
135
+ let id = Number.parseInt(idString)
136
+ if (isNaN(id)) {
137
+ id = 0
138
+ let templateId = Number.parseInt(idString.slice(1))
139
+ if (isNaN(templateId)) {
140
+ templateId = 1
141
+ }
142
+ this.docInfo.templateId = templateId
143
+ }
144
+ this.docInfo.id = id
145
+ }
146
+ this.schema = docSchema
147
+ // Provide a handler for decrypting end-to-end encrypted images. The
148
+ // reusable document schema no longer includes E2EE code, so the main
149
+ // app injects the decryption logic here after loading the schema.
150
+ this.schema.cached.decryptImage = (imageEntry, _dom) => {
151
+ const key = this.e2ee?.key
152
+ if (!key) {
153
+ return Promise.reject(new Error("No E2EE key available"))
154
+ }
155
+ return E2EEEncryptor.decryptImageToUrl(
156
+ imageEntry.image,
157
+ key,
158
+ imageEntry.original_file_type || "image/png"
159
+ )
160
+ }
161
+
162
+ this.menu = {
163
+ headerbarModel: headerbarModel(),
164
+ imageMenuModel: imageMenuModel(),
165
+ navigatorFilterModel: navigatorFilterModel(),
166
+ orderedListMenuModel: orderedListMenuModel(),
167
+ selectionMenuModel: selectionMenuModel(),
168
+ imageEditModel: imageEditModel(),
169
+ tableMenuModel: tableMenuModel(),
170
+ figureMenuModel: figureMenuModel(),
171
+ toolbarModel: toolbarModel(),
172
+ figureWidthMenuModel: figureWidthMenuModel(),
173
+ codeBlockMenuModel: codeBlockMenuModel()
174
+ }
175
+ this.client_id = Math.floor(Math.random() * 0xffffffff)
176
+ this.clientTimeAdjustment = 0
177
+
178
+ // E2EE state: holds the encryption key and snapshot manager
179
+ // for end-to-end encrypted documents. Set to an object with
180
+ // { key, snapshotManager, encrypted, encryptionSalt, encryptionIterations }
181
+ // when the document is encrypted and the user has entered the password.
182
+ this.e2ee = null
183
+
184
+ this.pathEditable = true // Set to false through plugin to disable path editing.
185
+
186
+ this.statePlugins = [
187
+ [inlineMathPlugin, () => ({editor: this})],
188
+ [inlineReferencePlugin, () => ({editor: this})],
189
+ [linksPlugin, () => ({editor: this})],
190
+ [keymap, () => buildEditorKeymap(this.schema)],
191
+ [keymap, () => buildKeymap(this.schema)],
192
+ [keymap, () => baseKeymap],
193
+ [collab, () => ({clientID: this.client_id})],
194
+ [history],
195
+ [dropCursor],
196
+ [gapCursor],
197
+ [tableEditing],
198
+ [jumpHiddenNodesPlugin],
199
+ [placeholdersPlugin, () => ({editor: this})],
200
+ [citationRenderPlugin, () => ({editor: this})],
201
+ [headerbarPlugin, () => ({editor: this})],
202
+ [toolbarPlugin, () => ({editor: this})],
203
+ [selectionMenuPlugin, () => ({editor: this})],
204
+ [collabCaretsPlugin, () => ({editor: this})],
205
+ [footnoteMarkersPlugin, () => ({editor: this})],
206
+ [commentsPlugin, () => ({editor: this})],
207
+ [marginboxesPlugin, () => ({editor: this})],
208
+ [tagInputPlugin, () => ({editor: this})],
209
+ [contributorInputPlugin, () => ({editor: this})],
210
+ [clipboardPlugin, () => ({editor: this, viewType: "main"})],
211
+ [accessRightsPlugin, () => ({editor: this})],
212
+ [settingsPlugin, () => ({editor: this})],
213
+ [documentTemplatePlugin, () => ({editor: this})],
214
+ [trackPlugin, () => ({editor: this})],
215
+ [tablePlugin, () => ({editor: this})],
216
+ [orderedListMenuPlugin, () => ({editor: this})],
217
+ [figurePlugin, () => ({editor: this})],
218
+ [codeBlockPlugin, () => ({editor: this})],
219
+ [tocRenderPlugin, () => ({editor: this})],
220
+ [searchPlugin]
221
+ ]
222
+ }
223
+
224
+ init() {
225
+ ensureCSS([
226
+ staticUrl("css/libs/mathlive/mathlive.css"),
227
+ staticUrl("css/editor.css"),
228
+ staticUrl("css/tags.css"),
229
+ staticUrl("css/contributors.css"),
230
+ staticUrl("css/document.css"),
231
+ staticUrl("css/carets.css"),
232
+ staticUrl("css/tracking.css"),
233
+ staticUrl("css/margin_boxes.css"),
234
+ staticUrl("css/prosemirror.css"),
235
+ staticUrl("css/footnotes.css"),
236
+ staticUrl("css/chat.css"),
237
+ staticUrl("css/access_rights_dialog.css"),
238
+ staticUrl("css/citation_dialog.css"),
239
+ staticUrl("css/review.css"),
240
+ staticUrl("css/add_remove_dialog.css"),
241
+ staticUrl("css/bibliography.css"),
242
+ staticUrl("css/dot_menu.css"),
243
+ staticUrl("css/cropper.min.css"),
244
+ staticUrl("css/inline_tools.css"),
245
+ staticUrl("css/e2ee.css")
246
+ ])
247
+ new ModDocumentTemplate(this)
248
+ const initPromises = [
249
+ whenReady(),
250
+ this.mod.documentTemplate.getCitationStyles()
251
+ ]
252
+ if (this.docInfo.hasOwnProperty("templateId")) {
253
+ // Check if this should be an E2EE document.
254
+ // The E2EE mode can be indicated via:
255
+ // 1. URL query parameter ?e2ee=true (from overview page)
256
+ // 2. Global E2EE_MODE variable set by the settings
257
+ const e2eeParam = new URLSearchParams(window.location.search).get(
258
+ "e2ee"
259
+ )
260
+ const isE2EERequired = this.app.settings.E2EE_MODE === "required"
261
+ const isE2EERequested = e2eeParam === "true" || e2eeParam === "1"
262
+ const shouldEncrypt = isE2EERequired || isE2EERequested
263
+
264
+ if (shouldEncrypt) {
265
+ initPromises.push(this._createE2EEDocument())
266
+ } else {
267
+ initPromises.push(
268
+ postJson("/api/document/create_doc/", {
269
+ template_id: this.docInfo.templateId,
270
+ path: this.docInfo.path
271
+ }).then(({json}) => {
272
+ this.docInfo.id = json.id
273
+ window.history.replaceState(
274
+ "",
275
+ "",
276
+ `/document/${this.docInfo.id}/`
277
+ )
278
+ delete this.docInfo.templateId
279
+ return Promise.resolve()
280
+ })
281
+ )
282
+ }
283
+ }
284
+ // Handle token-based access (share links)
285
+ if (uuid4Pattern.test(this.docInfo.id)) {
286
+ this.docInfo.token = this.docInfo.id
287
+ initPromises.push(
288
+ postJson(
289
+ `/api/document/share_token/validate/${this.docInfo.token}/`
290
+ ).then(({json, status}) => {
291
+ if (status === 200 && json.document_id) {
292
+ this.docInfo.id = json.document_id
293
+ this.docInfo.access_rights = json.rights
294
+ this.docInfo.wsBase = json.ws_base
295
+ return Promise.resolve()
296
+ } else {
297
+ // Token is invalid or expired
298
+ return Promise.reject(
299
+ new Error("Invalid or expired share link")
300
+ )
301
+ }
302
+ })
303
+ )
304
+ }
305
+ return Promise.all(initPromises)
306
+ .then(() => {
307
+ new ModCitations(this)
308
+ new ModFootnotes(this)
309
+ return this.activateFidusPlugins()
310
+ })
311
+ .then(() => {
312
+ activateWait(true)
313
+ const stylesPayload = {id: this.docInfo.id}
314
+ if (this.docInfo.token) {
315
+ stylesPayload.token = this.docInfo.token
316
+ }
317
+ const editorSaveMode = this.app.settings.EDITOR_SAVE_MODE
318
+ const collaborativeEditing = editorSaveMode === "collaborative"
319
+ const wsBasePromise = collaborativeEditing
320
+ ? this.docInfo.wsBase
321
+ ? Promise.resolve({
322
+ json: {ws_base: this.docInfo.wsBase}
323
+ })
324
+ : postJson("/api/document/get_ws_base/", {
325
+ id: this.docInfo.id
326
+ })
327
+ : Promise.resolve({json: {ws_base: ""}})
328
+ const stylesPromise = postJson(
329
+ "/api/document/get_doc_styles/",
330
+ stylesPayload
331
+ )
332
+ const docDataPromise = postJson(
333
+ "/api/document/get_doc_data/",
334
+ stylesPayload
335
+ )
336
+ return Promise.all([
337
+ wsBasePromise,
338
+ stylesPromise,
339
+ docDataPromise
340
+ ])
341
+ })
342
+ .catch(error => {
343
+ // Only show "Invalid Share Link" for token validation errors.
344
+ // Other errors (REST failures, etc.) should not show this dialog.
345
+ if (error.message === "Invalid or expired share link") {
346
+ deactivateWait()
347
+ const errorDialog = new Dialog({
348
+ title: gettext("Invalid Share Link"),
349
+ id: "invalid_share_link_dialog",
350
+ body: gettext(
351
+ "This share link has expired or is invalid. Please ask the document owner for a new link."
352
+ ),
353
+ buttons: [
354
+ {
355
+ text: gettext("OK"),
356
+ classes: "fw-dark",
357
+ click: () => {
358
+ window.location.href = "/"
359
+ }
360
+ }
361
+ ],
362
+ canClose: false
363
+ })
364
+ errorDialog.open()
365
+ } else {
366
+ deactivateWait()
367
+ console.error("Editor initialization failed:", error)
368
+ }
369
+ return Promise.reject(error)
370
+ })
371
+ .then(([wsResult, stylesResult, docResult]) => {
372
+ let resubScribed = false
373
+ this.render()
374
+ this.initEditor()
375
+ const editorSaveMode = this.app.settings.EDITOR_SAVE_MODE
376
+ const collaborativeEditing = editorSaveMode === "collaborative"
377
+ if (collaborativeEditing) {
378
+ // Include token in WebSocket path if present
379
+ let wsPath = `/document/${this.docInfo.id}/`
380
+ if (this.docInfo.token) {
381
+ wsPath += `?token=${this.docInfo.token}`
382
+ }
383
+ this.ws = new WebSocketConnector({
384
+ base: wsResult.json.ws_base,
385
+ path: wsPath,
386
+ appLoaded: () => this.view.state.plugins.length,
387
+ anythingToSend: () => sendableSteps(this.view.state),
388
+ initialMessage: () => {
389
+ const message = {
390
+ type: "subscribe",
391
+ v: this.docInfo.version
392
+ }
393
+
394
+ if (this.ws.connectionCount) {
395
+ message.connection = this.ws.connectionCount
396
+ }
397
+ return message
398
+ },
399
+ resubScribed: () => {
400
+ if (
401
+ sendableSteps(
402
+ this.mod.footnotes.fnEditor.view.state
403
+ )
404
+ ) {
405
+ this.mod.collab.doc.footnoteRender = true
406
+ }
407
+ resubScribed = true
408
+ this.mod.footnotes.fnEditor.renderAllFootnotes()
409
+ this.mod.collab.doc.awaitingDiffResponse = true // wait sending diffs till the version is confirmed
410
+ },
411
+ restartMessage: () => ({
412
+ type: "check_version",
413
+ v: this.docInfo.version
414
+ }),
415
+ messagesElement: () =>
416
+ this.dom.querySelector("#unobtrusive-messages"),
417
+ warningNotAllSent: gettext(
418
+ "Warning! Not all your changes have been saved! You could suffer data loss. Attempting to reconnect..."
419
+ ),
420
+ infoDisconnected: gettext(
421
+ "Disconnected. Attempting to reconnect..."
422
+ ),
423
+ receiveData: data => {
424
+ if (document.body !== this.dom) {
425
+ return // user navigated away.
426
+ }
427
+ switch (data.type) {
428
+ case "chat":
429
+ this.mod.collab.chat.newMessage(data)
430
+ break
431
+ case "connections":
432
+ this.mod.collab.updateParticipantList(
433
+ data.participant_list
434
+ )
435
+ if (resubScribed) {
436
+ // Reconnecting after offline with
437
+ // local edits: ask the server to
438
+ // save first so that the REST
439
+ // refetch reflects the definitive
440
+ // server state and adjustDocument
441
+ // can run the tracked-changes merge.
442
+ const missingSteps = sendableSteps(
443
+ this.view.state
444
+ )
445
+ this.mod.collab.doc.checkVersion(
446
+ missingSteps
447
+ )
448
+ resubScribed = false
449
+ }
450
+ break
451
+ case "session_info":
452
+ this.docInfo.session_id = data.session_id
453
+ // Update access rights from server
454
+ if (data.access_right) {
455
+ this.docInfo.access_rights =
456
+ data.access_right
457
+ }
458
+ // Update guest user identity with session_id
459
+ if (!this.user.is_authenticated) {
460
+ this.user = {
461
+ id: this.docInfo.token,
462
+ username: `guest${data.session_id}`,
463
+ name: `Guest ${data.session_id}`,
464
+ is_authenticated: false
465
+ }
466
+ }
467
+ // Handle E2EE encryption parameters from server
468
+ if (data.e2ee) {
469
+ if (!this.e2ee) {
470
+ this.e2ee = {
471
+ encrypted: true,
472
+ encryptionSalt: data.e2ee_salt,
473
+ encryptionIterations:
474
+ data.e2ee_iterations,
475
+ key: null,
476
+ snapshotManager:
477
+ new E2EESnapshotManager(
478
+ this
479
+ )
480
+ }
481
+ } else {
482
+ this.e2ee.encrypted = true
483
+ this.e2ee.encryptionSalt =
484
+ data.e2ee_salt
485
+ this.e2ee.encryptionIterations =
486
+ data.e2ee_iterations
487
+ }
488
+ }
489
+ break
490
+ case "refetch_doc":
491
+ // The server has forced a DB save before
492
+ // sending this message, so the REST response
493
+ // will be at the current server version. Passing
494
+ // our version (v) lets the endpoint include the
495
+ // covering diffs as `m` so adjustDocument can
496
+ // do a precise merge.
497
+ postJson("/api/document/get_doc_data/", {
498
+ id: this.docInfo.id,
499
+ token: this.docInfo.token,
500
+ v: this.docInfo.version
501
+ }).then(({json}) => {
502
+ this.mod.collab.doc.receiveDocument(
503
+ json
504
+ )
505
+ })
506
+ break
507
+ case "confirm_version":
508
+ this.mod.collab.doc.cancelCurrentlyCheckingVersion()
509
+ if (data["v"] !== this.docInfo.version) {
510
+ this.mod.collab.doc.checkVersion()
511
+ return
512
+ }
513
+ this.mod.collab.doc.confirmVersion(
514
+ data["v"]
515
+ )
516
+ this.mod.collab.doc.enableDiffSending()
517
+ break
518
+ case "selection_change":
519
+ this.mod.collab.doc.cancelCurrentlyCheckingVersion()
520
+ if (data["v"] !== this.docInfo.version) {
521
+ this.mod.collab.doc.checkVersion()
522
+ return
523
+ }
524
+ this.mod.collab.doc.receiveSelectionChange(
525
+ data
526
+ )
527
+ break
528
+ case "path_change":
529
+ this.docInfo.path = data["path"]
530
+ this.menu.headerView.update()
531
+ break
532
+ case "diff":
533
+ if (data["cid"] === this.client_id) {
534
+ // The diff origins from the local user.
535
+ this.mod.collab.doc.confirmDiff(
536
+ data["rid"]
537
+ )
538
+ return
539
+ }
540
+ if (data["v"] !== this.docInfo.version) {
541
+ this.mod.collab.doc.checkVersion()
542
+ return
543
+ }
544
+ this.mod.collab.doc.receiveDiff(data)
545
+ break
546
+ case "confirm_diff":
547
+ this.mod.collab.doc.confirmDiff(data["rid"])
548
+ break
549
+ case "reject_diff":
550
+ this.mod.collab.doc.rejectDiff(data["rid"])
551
+ break
552
+ case "patch_error":
553
+ showSystemMessage(
554
+ gettext(
555
+ "Your document was out of sync and has been reset."
556
+ )
557
+ )
558
+ break
559
+ case "access_right":
560
+ if (
561
+ data.access_right !==
562
+ this.docInfo.access_rights
563
+ ) {
564
+ if (
565
+ sendableSteps(this.view.state) &&
566
+ !WRITE_ROLES.includes(
567
+ data.access_right
568
+ )
569
+ ) {
570
+ // If the user's new rights does not allow him to update document , then download a copy of the
571
+ // same and ask him to re-open the document.
572
+ this.handleAccessRightModification()
573
+ } else {
574
+ addAlert(
575
+ "info",
576
+ interpolate(
577
+ gettext(
578
+ "Your Access rights have been modified. You now have %(accessRight)s access to this document."
579
+ ),
580
+ {
581
+ accessRight:
582
+ data.access_right
583
+ },
584
+ true
585
+ )
586
+ )
587
+ this.docInfo.access_rights =
588
+ data.access_right
589
+ }
590
+ }
591
+ break
592
+ case "request_snapshot":
593
+ // Server is asking a write-capable client to
594
+ // send an encrypted snapshot for persistence.
595
+ if (
596
+ this.e2ee &&
597
+ this.e2ee.snapshotManager
598
+ ) {
599
+ this.e2ee.snapshotManager.handleRequestSnapshot(
600
+ data
601
+ )
602
+ }
603
+ break
604
+ case "e2ee_snapshot_received":
605
+ // Server confirmed that an encrypted snapshot
606
+ // was saved. Informational — no action needed.
607
+ if (
608
+ this.e2ee &&
609
+ this.e2ee.snapshotManager
610
+ ) {
611
+ this.e2ee.snapshotManager.handleSnapshotReceived(
612
+ data
613
+ )
614
+ }
615
+ break
616
+ default:
617
+ break
618
+ }
619
+ },
620
+ failedAuth: () => {
621
+ // Clear all E2EE keys on session expiration
622
+ E2EEKeyManager.clearAllKeysFromSession()
623
+ PassphraseManager.clearKeysFromSession()
624
+ if (this.docInfo.token) {
625
+ // Token-based access failed
626
+ const tokenDialog = new Dialog({
627
+ title: gettext("Invalid Share Link"),
628
+ id: "invalid_token_dialog",
629
+ body: gettext(
630
+ "This share link has expired or is invalid. Please ask the document owner for a new link."
631
+ ),
632
+ buttons: [
633
+ {
634
+ text: gettext("OK"),
635
+ classes: "fw-dark",
636
+ click: () => {
637
+ window.location.href = "/"
638
+ }
639
+ }
640
+ ],
641
+ canClose: false
642
+ })
643
+ tokenDialog.open()
644
+ return
645
+ }
646
+ if (
647
+ this.view.state.plugins.length &&
648
+ sendableSteps(this.view.state) &&
649
+ this.ws.connectionCount > 0
650
+ ) {
651
+ this.ws.online = false // To avoid Websocket trying to reconnect.
652
+ const sessionDialog = new Dialog({
653
+ title: gettext("Session Expired"),
654
+ id: "session_expiration_dialog",
655
+ body: gettext(
656
+ "Your session expired while you were offline, so we cannot save your work to the server any longer, but you can download it to your computer instead. Please consider importing it as a new document after logging in."
657
+ ),
658
+ buttons: [
659
+ {
660
+ text: gettext("Download Document"),
661
+ click: () => {
662
+ const doc = this.getDoc({
663
+ use_current_view: true
664
+ })
665
+ new ExportFidusFile(
666
+ doc,
667
+ this.mod.db.bibDB,
668
+ this.mod.db.imageDB,
669
+ false
670
+ )
671
+ }
672
+ },
673
+ {
674
+ text: gettext(
675
+ "Proceed to Login page"
676
+ ),
677
+ classes: "fw-dark",
678
+ click: () => {
679
+ window.location.href = "/"
680
+ }
681
+ }
682
+ ],
683
+ canClose: false
684
+ })
685
+ sessionDialog.open()
686
+ } else {
687
+ window.location.href = "/"
688
+ }
689
+ }
690
+ })
691
+ }
692
+ this.mod.documentTemplate.setStyles(stylesResult.json)
693
+ const docIsE2EE = docResult.json.doc_info?.e2ee === true
694
+ this.mod.collab.doc.receiveDocument(docResult.json)
695
+ // For E2EE documents, delay the WebSocket connection until
696
+ // after the password dialog and decryption are complete (see
697
+ // _decryptAndLoadDoc). This ensures the key is available when
698
+ // catch-up diffs arrive from the server.
699
+ if (collaborativeEditing && !docIsE2EE) {
700
+ this.startWebSocket()
701
+ } else if (!docIsE2EE) {
702
+ // In non-collaborative modes there is no server
703
+ // confirm_version message, so mark it locally.
704
+ this.mod.collab.doc.confirmVersion(this.docInfo.version)
705
+ }
706
+ if (editorSaveMode === "direct") {
707
+ if (!this.noCollabSave) {
708
+ this.noCollabSave = new NoCollabSave(this)
709
+ }
710
+ this.noCollabSave.start()
711
+ }
712
+ })
713
+ }
714
+
715
+ /**
716
+ * Open the WebSocket connection and attach the close listener.
717
+ * Called immediately for non-E2EE documents and deferred until after
718
+ * password entry + decryption for E2EE documents.
719
+ */
720
+ startWebSocket() {
721
+ this.ws.init()
722
+ this.ws.ws.addEventListener("close", () => {
723
+ if (this.menu.toolbarViews) {
724
+ this.menu.toolbarViews.forEach(view => view.update())
725
+ }
726
+ if (this.menu.headerView) {
727
+ this.menu.headerView.update()
728
+ }
729
+ })
730
+ }
731
+
732
+ handleAccessRightModification() {
733
+ // This function when invoked creates a copy of document in FW format and closes editor operation.
734
+ new ExportFidusFile(
735
+ this.getDoc({use_current_view: true}),
736
+ this.mod.db.bibDB,
737
+ this.mod.db.imageDB
738
+ )
739
+ const accessRightModifiedDialog = new Dialog({
740
+ title: gettext("Access rights modified"),
741
+ id: "access_rights_modified",
742
+ body: gettext(
743
+ "Your access rights were modified while you were offline, so we cannot save your work to the server any longer, and it is downloaded to your computer instead. Please consider importing it into a new document."
744
+ ),
745
+ buttons: [
746
+ {
747
+ text: gettext("Leave editor"),
748
+ classes: "fw-dark",
749
+ click: () => {
750
+ window.location.href = "/"
751
+ }
752
+ }
753
+ ],
754
+ canClose: false
755
+ })
756
+ accessRightModifiedDialog.open()
757
+ this.close() // Close the editor operations.
758
+ }
759
+
760
+ /**
761
+ * Create a new E2EE document.
762
+ *
763
+ * If the user has set up a personal passphrase, generates a random
764
+ * document encryption key (DEK) and encrypts it with the master key.
765
+ * Otherwise, prompts for a per-document password.
766
+ *
767
+ * @returns {Promise<void>}
768
+ * @private
769
+ */
770
+ async _createE2EEDocument() {
771
+ let password, key, saltBase64, iterations
772
+
773
+ // Check if the user has a personal passphrase set up
774
+ const hasPassphraseKeys = await PassphraseManager.hasEncryptionKeys()
775
+
776
+ if (hasPassphraseKeys) {
777
+ // Check if keys are unlocked in sessionStorage
778
+ if (!PassphraseManager.hasKeysInSession()) {
779
+ // Prompt for passphrase to unlock
780
+ const result = await new Promise(resolve => {
781
+ enterPassphraseDialog(
782
+ pwd => resolve({action: "unlock", passphrase: pwd}),
783
+ () => resolve({action: "recover"})
784
+ )
785
+ })
786
+ if (result.action === "unlock" && result.passphrase) {
787
+ try {
788
+ await PassphraseManager.unlockWithPassphrase(
789
+ result.passphrase
790
+ )
791
+ } catch (_e) {
792
+ addAlert("error", gettext("Incorrect passphrase."))
793
+ }
794
+ } else if (result.action === "recover") {
795
+ // Recovery flow
796
+ const {recoverWithKeyDialog} = await import(
797
+ "./e2ee/passphrase-dialog.js"
798
+ )
799
+ const recoverResult = await new Promise(resolve => {
800
+ recoverWithKeyDialog(resolve)
801
+ })
802
+ if (recoverResult) {
803
+ try {
804
+ const {newRecoveryKey} =
805
+ await PassphraseManager.recoverWithRecoveryKey(
806
+ recoverResult.recoveryKey,
807
+ recoverResult.newPassphrase
808
+ )
809
+ const {showRecoveryKeyDialog} = await import(
810
+ "./e2ee/passphrase-dialog.js"
811
+ )
812
+ await new Promise(resolve => {
813
+ showRecoveryKeyDialog(newRecoveryKey, resolve)
814
+ })
815
+ } catch (e) {
816
+ addAlert(
817
+ "error",
818
+ gettext("Recovery failed: ") + e.message
819
+ )
820
+ }
821
+ }
822
+ }
823
+ }
824
+
825
+ if (PassphraseManager.hasKeysInSession()) {
826
+ // Generate a random document password that is itself a raw DEK
827
+ password = await PassphraseManager.generateDocumentPassword()
828
+ }
829
+ // Fall through to per-document password if passphrase unlock failed
830
+ }
831
+
832
+ if (!password) {
833
+ // Prompt for per-document password
834
+ password = await new Promise(resolve => {
835
+ createPasswordDialog(pwd => resolve(pwd))
836
+ })
837
+ }
838
+
839
+ // Generate a random 16-byte salt
840
+ const salt = E2EEKeyManager.generateSalt()
841
+ saltBase64 = btoa(String.fromCharCode(...salt))
842
+ iterations = 600000
843
+
844
+ // Resolve password to encryption key
845
+ key = await PassphraseManager.resolvePasswordToKey(
846
+ password,
847
+ salt,
848
+ iterations
849
+ )
850
+
851
+ // Create the document on the server with E2EE parameters
852
+ const {json, status} = await postJson("/api/document/create_doc/", {
853
+ template_id: this.docInfo.templateId,
854
+ path: this.docInfo.path,
855
+ e2ee: true,
856
+ e2ee_salt: saltBase64,
857
+ e2ee_iterations: iterations
858
+ })
859
+
860
+ if (status === 403) {
861
+ addAlert(
862
+ "error",
863
+ json.error || gettext("E2EE is not enabled on this server.")
864
+ )
865
+ window.location.href = "/"
866
+ return
867
+ }
868
+
869
+ this.docInfo.id = json.id
870
+ window.history.replaceState("", "", `/document/${this.docInfo.id}/`)
871
+ delete this.docInfo.templateId
872
+
873
+ // Store password in sessionStorage
874
+ E2EEKeyManager.storePasswordInSession(this.docInfo.id, password)
875
+
876
+ // If passphrase user, also save encrypted password on server
877
+ if (hasPassphraseKeys && PassphraseManager.hasKeysInSession()) {
878
+ try {
879
+ await PassphraseManager.saveDocumentPassword(
880
+ this.docInfo.id,
881
+ password,
882
+ null,
883
+ "user",
884
+ true
885
+ )
886
+ } catch (_e) {
887
+ console.error("Failed to save document password:", _e)
888
+ }
889
+ } else {
890
+ // Non-passphrase user: cache the key in sessionStorage
891
+ await E2EEKeyManager.storeKeyInSession(this.docInfo.id, key)
892
+ }
893
+
894
+ // Set up E2EE state on the editor
895
+ this.e2ee = {
896
+ encrypted: true,
897
+ encryptionSalt: saltBase64,
898
+ encryptionIterations: iterations,
899
+ key: key,
900
+ password: password,
901
+ snapshotManager: new E2EESnapshotManager(this),
902
+ usesPassphrase:
903
+ hasPassphraseKeys && PassphraseManager.hasKeysInSession()
904
+ }
905
+ this.e2ee.snapshotManager.setKey(key)
906
+ }
907
+
908
+ close() {
909
+ if (this.menu.toolbarViews) {
910
+ this.menu.toolbarViews.forEach(view => view.destroy())
911
+ }
912
+ if (this.menu.selectionMenuViews) {
913
+ this.menu.selectionMenuViews.forEach(view => view.destroy())
914
+ }
915
+ if (this.menu.headerView) {
916
+ this.menu.headerView.destroy()
917
+ }
918
+ // For E2EE documents, update the sessionStorage title cache with the
919
+ // current ProseMirror title so the overview shows the correct title
920
+ // immediately, even before the encrypted snapshot has been saved to
921
+ // the DB.
922
+ if (this.e2ee && this.view) {
923
+ const titleNode = this.view.state.doc.firstChild
924
+ if (titleNode) {
925
+ let title = ""
926
+ titleNode.forEach(child => {
927
+ if (
928
+ !child.marks.find(mark => mark.type.name === "deletion")
929
+ ) {
930
+ title += child.textContent
931
+ }
932
+ })
933
+ sessionStorage.setItem(`e2ee_title_${this.docInfo.id}`, title)
934
+ }
935
+ }
936
+ if (this.noCollabSave) {
937
+ this.noCollabSave.stop()
938
+ if (this.docInfo.access_rights === "write") {
939
+ // Fire a final save before closing. For SPA navigation the
940
+ // promise usually has enough time to complete; for actual page
941
+ // unload the beforeunload/pagehide handler covers us.
942
+ this.noCollabSave.save()
943
+ }
944
+ }
945
+ if (this.ws) {
946
+ // For E2EE documents, try to send one last encrypted snapshot before
947
+ // closing so the title (and any last-minute edits) are persisted.
948
+ // The WebSocket connector has an 80ms throttle between messages, so
949
+ // we delay ws.close() to give the snapshot time to be queued and
950
+ // sent. For SPA navigation 300ms is usually enough; for tab-close
951
+ // the browser may still kill us early.
952
+ // For non-E2EE documents we close immediately so the server's
953
+ // disconnect handler saves the document to the DB before the
954
+ // overview page loads its data (avoiding a race where the overview
955
+ // would still see the old/empty title).
956
+ if (
957
+ this.e2ee &&
958
+ this.e2ee.key &&
959
+ this.e2ee.snapshotManager &&
960
+ this.docInfo.access_rights === "write"
961
+ ) {
962
+ this.e2ee.snapshotManager.handleRequestSnapshot({})
963
+ window.setTimeout(() => this.ws.close(), 300)
964
+ } else {
965
+ this.ws.close()
966
+ }
967
+ }
968
+ }
969
+
970
+ render() {
971
+ this.dom = document.createElement("body")
972
+ document.body = this.dom
973
+ this.dom.classList.add("editor")
974
+ this.dom.classList.add("fw-scrollable")
975
+ this.dom.innerHTML = `<div id="editor">
976
+ <div id="fw-wait"><i class="fa-solid fa-spinner fa-pulse"></i></div>
977
+ <header>
978
+ <nav id="headerbar"><div></div></nav>
979
+ <nav id="toolbar"><div></div></nav>
980
+ </header>
981
+ <div id="navigator"></div>
982
+ <div id="editor-content">
983
+ <div id="flow">
984
+ <div id="paper-editable">
985
+ <div id="document-editable" class="user-contents"></div>
986
+ <div id="footnote-box-container" class="user-contents">
987
+ <div id="citation-footnote-box-container"></div>
988
+ </div>
989
+ </div>
990
+ <div id="bibliography" class="doc-bibliography user-contents"></div>
991
+ <div id="global-comments">
992
+ <div id="global-comment-container"><div></div></div>
993
+ <button id="new-global-comment" class="fw-button fw-light" type="button">
994
+ ${gettext("Add document comment")}
995
+ </button>
996
+ </div>
997
+ </div>
998
+ <nav id="selection-menu"><div></div></nav>
999
+ <div id="margin-box-column">
1000
+ <div id="margin-box-filter"></div>
1001
+ <div id="margin-box-container"><div></div></div>
1002
+ </div>
1003
+ </div>
1004
+ <div id="chat">
1005
+ <i class="resize-button fa-solid fa-angle-double-down"></i>
1006
+ <div id="chat-container"></div>
1007
+ <div id="messageform" contentEditable="true" class="empty"></div>
1008
+ <audio id="chat-notification">
1009
+ <source src="${staticUrl("ogg/chat_notification.ogg")}" type="audio/ogg">
1010
+ </audio>
1011
+ </div>
1012
+ </div>
1013
+ <div id="unobtrusive-messages"></div>`
1014
+ const feedbackTab = new FeedbackTab()
1015
+ feedbackTab.init()
1016
+ }
1017
+
1018
+ onResize() {
1019
+ if (!this.view || !this.mod.marginboxes) {
1020
+ // Editor not yet set up
1021
+ return
1022
+ }
1023
+ this.mod.marginboxes.updateDOM()
1024
+ this.mod.footnotes.layout.updateDOM()
1025
+ this.menu.toolbarViews.forEach(view => view.onResize())
1026
+ }
1027
+
1028
+ onBeforeUnload() {
1029
+ if (this.app.isOffline()) {
1030
+ showSystemMessage(
1031
+ gettext(
1032
+ "Changes you made to the document since going offline will be lost, if you choose to close/refresh the tab or close the browser."
1033
+ )
1034
+ )
1035
+ return true
1036
+ }
1037
+ this.close()
1038
+ }
1039
+
1040
+ initEditor() {
1041
+ let setFocus = false
1042
+ this.view = new EditorView(
1043
+ this.dom.querySelector("#document-editable"),
1044
+ {
1045
+ state: EditorState.create({
1046
+ schema: this.schema
1047
+ }),
1048
+ handleDOMEvents: {
1049
+ mousedown: (view, _event) => {
1050
+ this.currentView = this.view
1051
+ view.focus()
1052
+ return false
1053
+ },
1054
+ focus: (view, _event) => {
1055
+ if (!setFocus) {
1056
+ this.currentView = this.view
1057
+ // We focus once more, as focus may have disappeared due to
1058
+ // disappearing placeholders.
1059
+ setFocus = true
1060
+ view.focus()
1061
+ setFocus = false
1062
+ }
1063
+ }
1064
+ },
1065
+ dispatchTransaction: tr => {
1066
+ const trackedTr = amendTransaction(
1067
+ tr,
1068
+ this.view.state,
1069
+ this
1070
+ )
1071
+ const {state: newState, transactions} =
1072
+ this.view.state.applyTransaction(trackedTr)
1073
+ this.view.updateState(newState)
1074
+ transactions.forEach(subTr => {
1075
+ const footTr = subTr.getMeta("footTr")
1076
+ if (footTr && footTr.steps.length) {
1077
+ this.mod.footnotes.fnEditor.view.dispatch(footTr)
1078
+ }
1079
+ })
1080
+ if (tr.steps.length) {
1081
+ this.docInfo.updated = new Date()
1082
+ }
1083
+ // Update the header bar to reflect any title changes
1084
+ if (this.menu.headerView) {
1085
+ this.menu.headerView.update()
1086
+ }
1087
+
1088
+ this.mod.collab.doc.sendToCollaborators()
1089
+ }
1090
+ }
1091
+ )
1092
+ // The editor that is currently being edited in -- main or footnote editor
1093
+ this.currentView = this.view
1094
+ this.mod.citations.init()
1095
+ this.mod.footnotes.init()
1096
+ new ModDB(this)
1097
+ new ModCollab(this)
1098
+ new ModTrack(this)
1099
+ new ModMarginboxes(this)
1100
+ this.mod.marginboxes.init()
1101
+ new ModComments(this)
1102
+ new ModNavigator(this)
1103
+ this.mod.navigator.init()
1104
+ }
1105
+
1106
+ activateFidusPlugins() {
1107
+ if (this.plugins) {
1108
+ // Plugins have been activated already
1109
+ return
1110
+ }
1111
+ // Add plugins.
1112
+ this.plugins = {}
1113
+
1114
+ return Promise.all(
1115
+ plugins.map(([app, plugin]) => {
1116
+ if (!this.app.settings.APPS.includes(app)) {
1117
+ return Promise.resolve()
1118
+ }
1119
+ return Promise.all(
1120
+ Object.values(plugin).map(pluginExport => {
1121
+ if (typeof pluginExport === "function") {
1122
+ this.plugins[pluginExport.name] = new pluginExport(
1123
+ this
1124
+ )
1125
+ return (
1126
+ this.plugins[pluginExport.name].init() ||
1127
+ Promise.resolve()
1128
+ )
1129
+ }
1130
+ return Promise.resolve()
1131
+ })
1132
+ )
1133
+ })
1134
+ )
1135
+ }
1136
+
1137
+ // Collect all components of the current doc. Needed for saving and export
1138
+ // filters
1139
+ getDoc(options = {}) {
1140
+ const doc =
1141
+ this.app.isOffline() || Boolean(options.use_current_view)
1142
+ ? this.view.docView.node
1143
+ : this.docInfo.confirmedDoc
1144
+ const pmDoc =
1145
+ options.changes === "acceptAllNoInsertions"
1146
+ ? acceptAllNoInsertions(doc)
1147
+ : doc
1148
+
1149
+ let title = ""
1150
+ pmDoc.firstChild.forEach(child => {
1151
+ if (!child.marks.find(mark => mark.type.name === "deletion")) {
1152
+ title += child.textContent
1153
+ }
1154
+ })
1155
+ return {
1156
+ content: pmDoc.toJSON(),
1157
+ settings: getSettings(pmDoc),
1158
+ title: title.substring(0, 255),
1159
+ path: this.docInfo.path,
1160
+ version: this.docInfo.version,
1161
+ comments: this.mod.comments.store.comments,
1162
+ id: this.docInfo.id,
1163
+ updated: this.docInfo.updated
1164
+ }
1165
+ }
1166
+
1167
+ // Use PMs scrollIntoView function and adjust for top menu
1168
+ scrollIdIntoView(id) {
1169
+ let foundPos = false,
1170
+ view
1171
+
1172
+ this.view.state.doc.descendants((node, pos) => {
1173
+ if (foundPos) {
1174
+ return
1175
+ } else if (
1176
+ (node.type.groups.includes("heading") ||
1177
+ node.type.name === "figure") &&
1178
+ node.attrs.id === id
1179
+ ) {
1180
+ foundPos = node.type.name === "figure" ? pos : pos + 1
1181
+ view = this.view
1182
+ } else {
1183
+ const anchorMark = node.marks.find(
1184
+ mark => mark.type.name === "anchor"
1185
+ )
1186
+ if (anchorMark?.attrs.id === id) {
1187
+ foundPos = pos + 1
1188
+ view = this.view
1189
+ }
1190
+ }
1191
+ })
1192
+
1193
+ if (!foundPos) {
1194
+ this.mod.footnotes.fnEditor.view.state.doc.descendants(
1195
+ (node, pos) => {
1196
+ if (foundPos) {
1197
+ return
1198
+ } else if (
1199
+ (node.type.groups.includes("heading") ||
1200
+ node.type.name === "figure") &&
1201
+ node.attrs.id === id
1202
+ ) {
1203
+ foundPos = node.type.name === "figure" ? pos : pos + 1
1204
+ view = this.mod.footnotes.fnEditor.view
1205
+ } else {
1206
+ const anchorMark = node.marks.find(
1207
+ mark => mark.type.name === "anchor"
1208
+ )
1209
+ if (anchorMark?.attrs.id === id) {
1210
+ foundPos = pos + 1
1211
+ view = this.mod.footnotes.fnEditor.view
1212
+ }
1213
+ }
1214
+ }
1215
+ )
1216
+ }
1217
+ if (foundPos) {
1218
+ this.scrollPosIntoView(foundPos, view)
1219
+ }
1220
+ }
1221
+
1222
+ scrollPosIntoView(pos, view) {
1223
+ const topMenuHeight = this.dom.querySelector("header").offsetHeight + 10
1224
+ const $pos = view.state.doc.resolve(pos)
1225
+ view.dispatch(view.state.tr.setSelection(new TextSelection($pos, $pos)))
1226
+ view.focus()
1227
+ const distanceFromTop = view.coordsAtPos(pos).top - topMenuHeight
1228
+ window.scrollBy({
1229
+ left: 0,
1230
+ top: distanceFromTop,
1231
+ behavior: "smooth",
1232
+ block: "center"
1233
+ })
1234
+ return
1235
+ }
1236
+
1237
+ scrollBibliographyIntoView() {
1238
+ const topMenuHeight = this.dom.querySelector("header").offsetHeight + 10
1239
+ const bibliographyHeaderEl = document.querySelector(
1240
+ "h1.doc-bibliography-header"
1241
+ )
1242
+ const distanceFromTop =
1243
+ bibliographyHeaderEl.getBoundingClientRect().top - topMenuHeight
1244
+ window.scrollBy({
1245
+ left: 0,
1246
+ top: distanceFromTop,
1247
+ behavior: "smooth",
1248
+ block: "center"
1249
+ })
1250
+ return
1251
+ }
1252
+ }