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