diff --git a/.eslintignore b/.eslintignore
index 0a9d67706c..277e1f46a3 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -55,6 +55,7 @@ packages/app-desktop/vendor/lib/
packages/app-mobile/packageInfo.js
packages/app-mobile/android
packages/app-mobile/**/*.bundle.js
+packages/app-mobile/**/*.bundle.css
packages/app-mobile/web/public/pluginAssets/**/*
packages/app-mobile/ios
packages/app-mobile/lib/rnInjectedJs/
@@ -74,6 +75,7 @@ packages/lib/services/database/types.ts
packages/lib/vendor/
packages/lib/vendor/fountain.min.js
packages/lib/welcomeAssets.js
+packages/editor/*/vendor/
packages/plugins/**/api
packages/plugins/**/dist
packages/server/dist/
@@ -663,6 +665,7 @@ packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
+packages/app-mobile/components/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
@@ -671,42 +674,28 @@ packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
-packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
-packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
+packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
+packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
+packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
+packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
+packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
@@ -861,6 +850,36 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
+packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
+packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
+packages/app-mobile/contentScripts/markdownEditorBundle/types.js
+packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
+packages/app-mobile/contentScripts/rendererBundle/types.js
+packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
+packages/app-mobile/contentScripts/richTextEditorBundle/contentScript.js
+packages/app-mobile/contentScripts/richTextEditorBundle/types.js
+packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/types.js
+packages/app-mobile/contentScripts/utils/polyfills.js
+packages/app-mobile/contentScripts/utils/readFileToBase64.js
+packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@@ -883,7 +902,7 @@ packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
-packages/app-mobile/tools/buildInjectedJs/copyJs.js
+packages/app-mobile/tools/buildInjectedJs/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@@ -920,7 +939,6 @@ packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
-packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
@@ -990,12 +1008,12 @@ packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
+packages/editor/CodeMirror/index.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
-packages/editor/CodeMirror/testing/createEditorSettings.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
@@ -1031,9 +1049,43 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/setupVim.js
+packages/editor/ProseMirror/commands.test.js
+packages/editor/ProseMirror/commands.js
+packages/editor/ProseMirror/createEditor.js
+packages/editor/ProseMirror/index.js
+packages/editor/ProseMirror/plugins/inputRulesPlugin.js
+packages/editor/ProseMirror/plugins/joplinEditablePlugin.js
+packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
+packages/editor/ProseMirror/plugins/keymapPlugin.js
+packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
+packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
+packages/editor/ProseMirror/plugins/listPlugin.js
+packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
+packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
+packages/editor/ProseMirror/plugins/searchPlugin.js
+packages/editor/ProseMirror/schema.js
+packages/editor/ProseMirror/styles.js
+packages/editor/ProseMirror/testing/createTestEditor.js
+packages/editor/ProseMirror/types.js
+packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
+packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
+packages/editor/ProseMirror/utils/computeSelectionFormatting.js
+packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
+packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
+packages/editor/ProseMirror/utils/jumpToHash.js
+packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
+packages/editor/ProseMirror/utils/preprocessEditorInput.js
+packages/editor/ProseMirror/utils/sanitizeHtml.js
+packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
+packages/editor/ProseMirror/vendor/changedDescendants.js
+packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
+packages/editor/polyfills.js
+packages/editor/testing/createEditorSettings.js
+packages/editor/testing/setUpLogger.js
packages/editor/types.js
+packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@@ -1113,6 +1165,8 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
+packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
+packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
diff --git a/.eslintrc.js b/.eslintrc.js
index 6ea2562675..5c5722e63e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -23,6 +23,7 @@ module.exports = {
'FileSystemCreateWritableOptions': 'readonly',
'FileSystemHandle': 'readonly',
'IDBTransactionMode': 'readonly',
+ 'FlatArray': 'readonly',
'BigInt': 'readonly',
'globalThis': 'readonly',
diff --git a/.gitignore b/.gitignore
index 8f8653d68b..2fdad3445b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -638,6 +638,7 @@ packages/app-mobile/components/ExtendedWebView/index.jest.js
packages/app-mobile/components/ExtendedWebView/index.js
packages/app-mobile/components/ExtendedWebView/index.web.js
packages/app-mobile/components/ExtendedWebView/types.js
+packages/app-mobile/components/ExtendedWebView/utils/useCss.js
packages/app-mobile/components/FolderPicker.js
packages/app-mobile/components/Icon.js
packages/app-mobile/components/IconButton.js
@@ -646,42 +647,28 @@ packages/app-mobile/components/ModalDialog.js
packages/app-mobile/components/NestableFlatList.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.test.js
packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.test.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/types.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/addPluginAssets.js
-packages/app-mobile/components/NoteBodyViewer/bundledJs/utils/makeResourceModel.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useContentScripts.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnMessage.js
packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js
-packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.js
packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.js
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
packages/app-mobile/components/NoteBodyViewer/types.js
-packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js
packages/app-mobile/components/NoteEditor/EditLinkDialog.js
packages/app-mobile/components/NoteEditor/ImageEditor/ImageEditor.js
packages/app-mobile/components/NoteEditor/ImageEditor/autosave.js
packages/app-mobile/components/NoteEditor/ImageEditor/isEditableResource.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/applyTemplateToEditor.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.test.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/polyfills.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/startAutosaveLoop.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/types.js
-packages/app-mobile/components/NoteEditor/ImageEditor/js-draw/watchEditorForTemplateChanges.js
packages/app-mobile/components/NoteEditor/ImageEditor/promptRestoreAutosave.js
-packages/app-mobile/components/NoteEditor/ImageEditor/utils/useEditorMessenger.js
+packages/app-mobile/components/NoteEditor/MarkdownEditor.js
packages/app-mobile/components/NoteEditor/NoteEditor.test.js
packages/app-mobile/components/NoteEditor/NoteEditor.js
+packages/app-mobile/components/NoteEditor/RichTextEditor.test.js
+packages/app-mobile/components/NoteEditor/RichTextEditor.js
packages/app-mobile/components/NoteEditor/SearchPanel.js
+packages/app-mobile/components/NoteEditor/WarningBanner.js
packages/app-mobile/components/NoteEditor/commandDeclarations.js
packages/app-mobile/components/NoteEditor/hooks/useCodeMirrorPlugins.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.test.js
packages/app-mobile/components/NoteEditor/hooks/useEditorCommandHandler.js
+packages/app-mobile/components/NoteEditor/testing/createTestEditorProps.js
packages/app-mobile/components/NoteEditor/types.js
packages/app-mobile/components/NoteItem.js
packages/app-mobile/components/NoteList.js
@@ -836,6 +823,36 @@ packages/app-mobile/components/voiceTyping/AudioRecordingBanner.js
packages/app-mobile/components/voiceTyping/RecordingControls.js
packages/app-mobile/components/voiceTyping/SpeechToTextBanner.js
packages/app-mobile/components/voiceTyping/types.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/applyTemplateToEditor.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.test.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/index.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/startAutosaveLoop.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/types.js
+packages/app-mobile/contentScripts/imageEditorBundle/contentScript/watchEditorForTemplateChanges.js
+packages/app-mobile/contentScripts/imageEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/imageEditorBundle/utils/useEditorMessenger.js
+packages/app-mobile/contentScripts/markdownEditorBundle/contentScript.js
+packages/app-mobile/contentScripts/markdownEditorBundle/types.js
+packages/app-mobile/contentScripts/markdownEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.test.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/Renderer.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/index.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/types.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/addPluginAssets.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/afterFullPageRender.js
+packages/app-mobile/contentScripts/rendererBundle/contentScript/utils/makeResourceModel.js
+packages/app-mobile/contentScripts/rendererBundle/types.js
+packages/app-mobile/contentScripts/rendererBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useContentScripts.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.test.js
+packages/app-mobile/contentScripts/rendererBundle/utils/useEditPopup.js
+packages/app-mobile/contentScripts/richTextEditorBundle/contentScript.js
+packages/app-mobile/contentScripts/richTextEditorBundle/types.js
+packages/app-mobile/contentScripts/richTextEditorBundle/useWebViewSetup.js
+packages/app-mobile/contentScripts/types.js
+packages/app-mobile/contentScripts/utils/polyfills.js
+packages/app-mobile/contentScripts/utils/readFileToBase64.js
+packages/app-mobile/contentScripts/utils/setUpLogger.js
packages/app-mobile/gulpfile.js
packages/app-mobile/index.web.js
packages/app-mobile/root.js
@@ -858,7 +875,7 @@ packages/app-mobile/services/voiceTyping/whisper.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
packages/app-mobile/tools/buildInjectedJs/constants.js
-packages/app-mobile/tools/buildInjectedJs/copyJs.js
+packages/app-mobile/tools/buildInjectedJs/copyAssets.js
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
packages/app-mobile/tools/copyAssets.js
packages/app-mobile/utils/ShareExtension.js
@@ -895,7 +912,6 @@ packages/app-mobile/utils/image/fileToImage.web.js
packages/app-mobile/utils/image/getImageDimensions.js
packages/app-mobile/utils/image/resizeImage.js
packages/app-mobile/utils/initializeCommandService.js
-packages/app-mobile/utils/injectedJs.js
packages/app-mobile/utils/ipc/RNToWebViewMessenger.js
packages/app-mobile/utils/ipc/WebViewToRNMessenger.js
packages/app-mobile/utils/lockToSingleInstance.js
@@ -965,12 +981,12 @@ packages/editor/CodeMirror/extensions/overwriteModeExtension.js
packages/editor/CodeMirror/extensions/searchExtension.js
packages/editor/CodeMirror/extensions/selectedNoteIdExtension.js
packages/editor/CodeMirror/getScrollFraction.js
+packages/editor/CodeMirror/index.js
packages/editor/CodeMirror/pluginApi/PluginLoader.js
packages/editor/CodeMirror/pluginApi/codeMirrorRequire.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.test.js
packages/editor/CodeMirror/pluginApi/customEditorCompletion.js
packages/editor/CodeMirror/testing/createEditorControl.js
-packages/editor/CodeMirror/testing/createEditorSettings.js
packages/editor/CodeMirror/testing/createTestEditor.js
packages/editor/CodeMirror/testing/findNodesWithName.js
packages/editor/CodeMirror/testing/forceFullParse.js
@@ -1006,9 +1022,43 @@ packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.test.js
packages/editor/CodeMirror/utils/markdown/renumberSelectedLists.js
packages/editor/CodeMirror/utils/markdown/stripBlockquote.js
packages/editor/CodeMirror/utils/setupVim.js
+packages/editor/ProseMirror/commands.test.js
+packages/editor/ProseMirror/commands.js
+packages/editor/ProseMirror/createEditor.js
+packages/editor/ProseMirror/index.js
+packages/editor/ProseMirror/plugins/inputRulesPlugin.js
+packages/editor/ProseMirror/plugins/joplinEditablePlugin.js
+packages/editor/ProseMirror/plugins/joplinEditorApiPlugin.js
+packages/editor/ProseMirror/plugins/keymapPlugin.js
+packages/editor/ProseMirror/plugins/linkTooltipPlugin.test.js
+packages/editor/ProseMirror/plugins/linkTooltipPlugin.js
+packages/editor/ProseMirror/plugins/listPlugin.js
+packages/editor/ProseMirror/plugins/originalMarkupPlugin.js
+packages/editor/ProseMirror/plugins/resourcePlaceholderPlugin.js
+packages/editor/ProseMirror/plugins/searchPlugin.js
+packages/editor/ProseMirror/schema.js
+packages/editor/ProseMirror/styles.js
+packages/editor/ProseMirror/testing/createTestEditor.js
+packages/editor/ProseMirror/types.js
+packages/editor/ProseMirror/utils/UndoStackSynchronizer.js
+packages/editor/ProseMirror/utils/canReplaceSelectionWith.js
+packages/editor/ProseMirror/utils/computeSelectionFormatting.js
+packages/editor/ProseMirror/utils/extractSelectedLinesTo.test.js
+packages/editor/ProseMirror/utils/extractSelectedLinesTo.js
+packages/editor/ProseMirror/utils/jumpToHash.js
+packages/editor/ProseMirror/utils/preprocessEditorInput.test.js
+packages/editor/ProseMirror/utils/preprocessEditorInput.js
+packages/editor/ProseMirror/utils/sanitizeHtml.js
+packages/editor/ProseMirror/utils/trimEmptyParagraphs.js
+packages/editor/ProseMirror/vendor/changedDescendants.js
+packages/editor/ProseMirror/vendor/splitBlockAs.js
packages/editor/SelectionFormatting.js
packages/editor/events.js
+packages/editor/polyfills.js
+packages/editor/testing/createEditorSettings.js
+packages/editor/testing/setUpLogger.js
packages/editor/types.js
+packages/editor/utils/getFileFromPasteEvent.js
packages/fork-htmlparser2/src/CollectingHandler.js
packages/fork-htmlparser2/src/FeedHandler.spec.js
packages/fork-htmlparser2/src/FeedHandler.js
@@ -1088,6 +1138,8 @@ packages/lib/commands/toggleAllFolders.js
packages/lib/commands/toggleEditorPlugin.js
packages/lib/components/EncryptionConfigScreen/utils.test.js
packages/lib/components/EncryptionConfigScreen/utils.js
+packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick.js
+packages/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick.js
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
packages/lib/components/shared/NoteRevisionViewer/getHelpMessage.js
packages/lib/components/shared/NoteRevisionViewer/useDeleteHistoryClick.js
diff --git a/packages/app-cli/tests/html_to_md/task_list_block_content.html b/packages/app-cli/tests/html_to_md/task_list_block_content.html
new file mode 100644
index 0000000000..34ed57e6bb
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/task_list_block_content.html
@@ -0,0 +1,13 @@
+
A task list created by the TipTap editor:
+
\ No newline at end of file
diff --git a/packages/app-cli/tests/html_to_md/task_list_block_content.md b/packages/app-cli/tests/html_to_md/task_list_block_content.md
new file mode 100644
index 0000000000..bbc2c892ca
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/task_list_block_content.md
@@ -0,0 +1,5 @@
+A task list created by the TipTap editor:
+
+- [ ] Testing...
+
+- [ ] testing
\ No newline at end of file
diff --git a/packages/app-cli/tests/html_to_md/task_lists.html b/packages/app-cli/tests/html_to_md/task_lists.html
new file mode 100644
index 0000000000..5e69ddbd32
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/task_lists.html
@@ -0,0 +1,26 @@
+List 1:
+
+List 2:
+
+List 3:
+
\ No newline at end of file
diff --git a/packages/app-cli/tests/html_to_md/task_lists.md b/packages/app-cli/tests/html_to_md/task_lists.md
new file mode 100644
index 0000000000..929283d62b
--- /dev/null
+++ b/packages/app-cli/tests/html_to_md/task_lists.md
@@ -0,0 +1,15 @@
+List 1:
+
+- [ ] This
+- [x] is a test.
+
+List 2:
+
+- [ ] This
+- [x] is another test.
+
+List 3:
+
+- [ ] This
+- [x] is another test.
+- [x]
\ No newline at end of file
diff --git a/packages/app-cli/tests/md_to_html/checkbox_alternative.html b/packages/app-cli/tests/md_to_html/checkbox_alternative.html
index 40da561bd5..8bfc1e1d5c 100644
--- a/packages/app-cli/tests/md_to_html/checkbox_alternative.html
+++ b/packages/app-cli/tests/md_to_html/checkbox_alternative.html
@@ -1,7 +1,7 @@
-
+
- Not checked
- Checked!!
-
+
- Indented, with bold
- Indented, not checked
diff --git a/packages/app-cli/tests/md_to_html/resource_nonexistent_image.html b/packages/app-cli/tests/md_to_html/resource_nonexistent_image.html
index 19789acfa6..b5e6b23938 100644
--- a/packages/app-cli/tests/md_to_html/resource_nonexistent_image.html
+++ b/packages/app-cli/tests/md_to_html/resource_nonexistent_image.html
@@ -1,15 +1,15 @@
-
-
+

-
+

\ No newline at end of file
+	"/>
\ No newline at end of file
diff --git a/packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx b/packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx
index 2d96d5ee1d..537ced95ca 100644
--- a/packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx
+++ b/packages/app-desktop/gui/NoteEditor/WarningBanner/WarningBanner.tsx
@@ -4,7 +4,8 @@ import { AppState } from '../../../app.reducer';
import Setting from '@joplin/lib/models/Setting';
import BannerContent from './BannerContent';
import { _ } from '@joplin/lib/locale';
-import bridge from '../../../services/bridge';
+import onRichTextReadMoreLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextReadMoreLinkClick';
+import onRichTextDismissLinkClick from '@joplin/lib/components/shared/NoteEditor/WarningBanner/onRichTextDismissLinkClick';
import { useMemo } from 'react';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
import PluginService from '@joplin/lib/services/plugins/PluginService';
@@ -16,14 +17,6 @@ interface Props {
plugins: PluginStates;
}
-const onRichTextDismissLinkClick = () => {
- Setting.setValue('richTextBannerDismissed', true);
-};
-
-const onRichTextReadMoreLinkClick = () => {
- void bridge().openExternal('https://joplinapp.org/help/apps/rich_text_editor');
-};
-
const onSwitchToLegacyEditor = () => {
Setting.setValue('editor.legacyMarkdown', true);
};
diff --git a/packages/app-mobile/.gitignore b/packages/app-mobile/.gitignore
index 634beaadbc..dc9270e0a6 100644
--- a/packages/app-mobile/.gitignore
+++ b/packages/app-mobile/.gitignore
@@ -67,7 +67,8 @@ yarn-error.log
lib/csstojs/
lib/rnInjectedJs/
dist/
-components/**/*.bundle.js
+/**/*.bundle.js
+/**/*.bundle.css
components/**/*.bundle.js.LICENSE.txt
components/**/*.bundle.js.md5
components/**/*.bundle.min.js
diff --git a/packages/app-mobile/components/ExtendedWebView/index.jest.tsx b/packages/app-mobile/components/ExtendedWebView/index.jest.tsx
index 359138361b..98838c4b96 100644
--- a/packages/app-mobile/components/ExtendedWebView/index.jest.tsx
+++ b/packages/app-mobile/components/ExtendedWebView/index.jest.tsx
@@ -1,13 +1,14 @@
import * as React from 'react';
import {
- forwardRef, Ref, useEffect, useImperativeHandle, useMemo, useRef,
+ forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useRef,
} from 'react';
import { View } from 'react-native';
import Logger from '@joplin/utils/Logger';
import { Props, WebViewControl } from './types';
import { JSDOM } from 'jsdom';
+import useCss from './utils/useCss';
const logger = Logger.create('ExtendedWebView');
@@ -18,11 +19,13 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
return new JSDOM(props.html, { runScripts: 'dangerously', pretendToBeVisual: true });
}, [props.html]);
+ const injectJs = useCallback((js: string) => {
+ return dom.window.eval(js);
+ }, [dom]);
+
useImperativeHandle(ref, (): WebViewControl => {
const result = {
- injectJS(js: string) {
- return dom.window.eval(js);
- },
+ injectJS: injectJs,
postMessage(message: unknown) {
const messageEventContent = {
data: message,
@@ -36,33 +39,24 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
},
};
return result;
- }, [dom]);
+ }, [dom, injectJs]);
const onMessageRef = useRef(props.onMessage);
onMessageRef.current = props.onMessage;
+ const { injectedJs: cssInjectedJavaScript } = useCss(
+ injectJs,
+ props.css,
+ );
+
// Don't re-load when injected JS changes. This should match the behavior of the native webview.
const injectedJavaScriptRef = useRef(props.injectedJavaScript);
- injectedJavaScriptRef.current = props.injectedJavaScript;
+ injectedJavaScriptRef.current = props.injectedJavaScript + cssInjectedJavaScript;
useEffect(() => {
// JSDOM polyfills
dom.window.eval(`
- // Prevents the CodeMirror error "getClientRects is undefined".
- // See https://github.com/jsdom/jsdom/issues/3002#issue-652790925
- document.createRange = () => {
- const range = new Range();
- range.getBoundingClientRect = () => {};
- range.getClientRects = () => {
- return {
- length: 0,
- item: () => null,
- [Symbol.iterator]: () => {},
- };
- };
-
- return range;
- };
+ window.scrollBy = (_amount) => { };
`);
dom.window.eval(`
@@ -80,7 +74,6 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
dom.window.eval(injectedJavaScriptRef.current);
}, [dom]);
-
const onLoadEndRef = useRef(props.onLoadEnd);
onLoadEndRef.current = props.onLoadEnd;
const onLoadStartRef = useRef(props.onLoadStart);
diff --git a/packages/app-mobile/components/ExtendedWebView/index.tsx b/packages/app-mobile/components/ExtendedWebView/index.tsx
index e5ff0e2709..052518293e 100644
--- a/packages/app-mobile/components/ExtendedWebView/index.tsx
+++ b/packages/app-mobile/components/ExtendedWebView/index.tsx
@@ -12,6 +12,7 @@ import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
import Logger from '@joplin/utils/Logger';
import { Props, WebViewControl } from './types';
+import useCss from './utils/useCss';
const logger = Logger.create('ExtendedWebView');
@@ -98,6 +99,9 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
}, 250);
}, []);
+ const { injectedJs: cssInjectedJs } = useCss(webviewRef.current?.injectJavaScript, props.css);
+ const injectedJavaScript = props.injectedJavaScript + cssInjectedJs;
+
// - `setSupportMultipleWindows` must be `true` for security reasons:
// https://github.com/react-native-webview/react-native-webview/releases/tag/v11.0.0
@@ -131,7 +135,7 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
allowFileAccess={true}
allowFileAccessFromFileURLs={props.allowFileAccessFromJs}
webviewDebuggingEnabled={allowWebviewDebugging}
- injectedJavaScript={props.injectedJavaScript}
+ injectedJavaScript={injectedJavaScript}
onMessage={props.onMessage}
onError={props.onError ?? onError}
onLoadEnd={props.onLoadEnd}
diff --git a/packages/app-mobile/components/ExtendedWebView/index.web.tsx b/packages/app-mobile/components/ExtendedWebView/index.web.tsx
index 8383a2828e..d99ea57455 100644
--- a/packages/app-mobile/components/ExtendedWebView/index.web.tsx
+++ b/packages/app-mobile/components/ExtendedWebView/index.web.tsx
@@ -1,13 +1,14 @@
import * as React from 'react';
import {
- forwardRef, Ref, useEffect, useImperativeHandle, useRef, useState,
+ forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useRef, useState,
} from 'react';
import { Props, WebViewControl } from './types';
import { View, ViewStyle } from 'react-native';
import makeSandboxedIframe from '@joplin/lib/utils/dom/makeSandboxedIframe';
import Logger from '@joplin/utils/Logger';
+import useCss from './utils/useCss';
const logger = Logger.create('ExtendedWebView');
@@ -20,24 +21,26 @@ const wrapperStyle: ViewStyle = { height: '100%', width: '100%', flex: 1 };
const ExtendedWebView = (props: Props, ref: Ref) => {
const iframeRef = useRef(null);
+ const injectJs = useCallback((js: string) => {
+ if (!iframeRef.current) {
+ logger.warn(`WebView(${props.webviewInstanceId}): Tried to inject JavaScript after the iframe has unloaded.`);
+ return;
+ }
+
+ // react-native-webview doesn't seem to show a warning in the case where JavaScript
+ // is injected before the first page loads.
+ if (!iframeRef.current.contentWindow) {
+ return;
+ }
+
+ iframeRef.current.contentWindow.postMessage({
+ injectJs: js,
+ }, '*');
+ }, [props.webviewInstanceId]);
+
useImperativeHandle(ref, (): WebViewControl => {
return {
- injectJS(js: string) {
- if (!iframeRef.current) {
- logger.warn(`WebView(${props.webviewInstanceId}): Tried to inject JavaScript after the iframe has unloaded.`);
- return;
- }
-
- // react-native-webview doesn't seem to show a warning in the case where JavaScript
- // is injected before the first page loads.
- if (!iframeRef.current.contentWindow) {
- return;
- }
-
- iframeRef.current.contentWindow.postMessage({
- injectJs: js,
- }, '*');
- },
+ injectJS: injectJs,
postMessage(message: unknown) {
if (!iframeRef.current || !iframeRef.current.contentWindow) {
logger.warn(`WebView(${props.webviewInstanceId}): Tried to post a message to an unloaded iframe.`);
@@ -49,7 +52,7 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
}, '*');
},
};
- }, [props.webviewInstanceId]);
+ }, [props.webviewInstanceId, injectJs]);
const [containerElement, setContainerElement] = useState();
const containerRef = useRef(containerElement);
@@ -62,9 +65,15 @@ const ExtendedWebView = (props: Props, ref: Ref) => {
const onLoadStartRef = useRef(props.onLoadStart);
onLoadStartRef.current = props.onLoadStart;
+ const { injectedJs: cssInjectedJs } = useCss(
+ iframeRef.current ? injectJs : null,
+ props.css,
+ );
+ const injectedJavaScript = props.injectedJavaScript + cssInjectedJs;
+
// Don't re-load when injected JS changes. This should match the behavior of the native webview.
- const injectedJavaScriptRef = useRef(props.injectedJavaScript);
- injectedJavaScriptRef.current = props.injectedJavaScript;
+ const injectedJavaScriptRef = useRef(injectedJavaScript);
+ injectedJavaScriptRef.current = injectedJavaScript;
useEffect(() => {
const headHtml = `
diff --git a/packages/app-mobile/components/ExtendedWebView/types.ts b/packages/app-mobile/components/ExtendedWebView/types.ts
index e8ac090f6e..5531c0723d 100644
--- a/packages/app-mobile/components/ExtendedWebView/types.ts
+++ b/packages/app-mobile/components/ExtendedWebView/types.ts
@@ -31,6 +31,7 @@ export interface Props {
// If HTML is still being loaded, [html] should be an empty string.
html: string;
+ css?: string;
// Initial javascript. Must evaluate to true.
injectedJavaScript: string;
diff --git a/packages/app-mobile/components/ExtendedWebView/utils/useCss.ts b/packages/app-mobile/components/ExtendedWebView/utils/useCss.ts
new file mode 100644
index 0000000000..de31583b0b
--- /dev/null
+++ b/packages/app-mobile/components/ExtendedWebView/utils/useCss.ts
@@ -0,0 +1,38 @@
+import { useEffect } from 'react';
+
+type OnInjectJs = (js: string)=> void;
+
+const webViewCssClassName = 'extended-webview-css';
+
+const applyCssJs = (css: string) => `
+(function() {
+ const styleId = ${JSON.stringify(webViewCssClassName)};
+
+ const oldStyle = document.getElementById(styleId);
+ if (oldStyle) {
+ oldStyle.remove();
+ }
+
+ const style = document.createElement('style');
+ style.setAttribute('id', styleId);
+
+ style.appendChild(document.createTextNode(${JSON.stringify(css)}));
+ document.head.appendChild(style);
+})();
+
+true;
+`;
+
+const useCss = (injectJs: OnInjectJs|null, css: string) => {
+ useEffect(() => {
+ if (injectJs && css) {
+ injectJs(applyCssJs(css));
+ }
+ }, [injectJs, css]);
+
+ return {
+ injectedJs: css ? applyCssJs(css) : '',
+ };
+};
+
+export default useCss;
diff --git a/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.tsx b/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.tsx
index 08c7487dd0..cbd576b41f 100644
--- a/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.tsx
+++ b/packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.tsx
@@ -1,24 +1,20 @@
import * as React from 'react';
import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
-import { useRef, useCallback, useState, useMemo } from 'react';
+import { useRef, useCallback } from 'react';
import { View, ViewStyle } from 'react-native';
import ExtendedWebView from '../ExtendedWebView';
import { WebViewControl } from '../ExtendedWebView/types';
import useOnResourceLongPress from './hooks/useOnResourceLongPress';
-import useRenderer from './hooks/useRenderer';
-import { OnWebViewMessageHandler } from './types';
import useRerenderHandler, { ResourceInfo } from './hooks/useRerenderHandler';
import useSource from './hooks/useSource';
-import Setting from '@joplin/lib/models/Setting';
-import uuid from '@joplin/lib/uuid';
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
-import useContentScripts from './hooks/useContentScripts';
import { MarkupLanguage } from '@joplin/renderer';
import shim from '@joplin/lib/shim';
import CommandService from '@joplin/lib/services/CommandService';
import { AppState } from '../../utils/types';
import { connect } from 'react-redux';
+import useWebViewSetup from '../../contentScripts/rendererBundle/useWebViewSetup';
interface Props {
themeId: number;
@@ -69,27 +65,14 @@ function NoteBodyViewer(props: Props) {
onResourceLongPress,
});
- const [webViewLoaded, setWebViewLoaded] = useState(false);
- const [onWebViewMessage, setOnWebViewMessage] = useState(()=>()=>{});
-
-
- // The renderer can write to whichever temporary directory we choose. As such,
- // we use a subdirectory of the main temporary directory for security reasons.
- const tempDir = useMemo(() => {
- return `${Setting.value('tempDir')}/${uuid.createNano()}`;
- }, []);
-
- const renderer = useRenderer({
- webViewLoaded,
- onScroll,
+ const { api: renderer, pageSetup, webViewEventHandlers } = useWebViewSetup({
webviewRef,
+ onBodyScroll: onScroll,
onPostMessage,
- setOnWebViewMessage,
- tempDir,
+ pluginStates: props.pluginStates,
+ themeId: props.themeId,
});
- const contentScripts = useContentScripts(props.pluginStates);
-
useRerenderHandler({
renderer,
fontSize: props.fontSize,
@@ -102,16 +85,14 @@ function NoteBodyViewer(props: Props) {
initialScroll: props.initialScroll,
paddingBottom: props.paddingBottom,
-
- contentScripts,
});
const onLoadEnd = useCallback(() => {
- setWebViewLoaded(true);
+ webViewEventHandlers.onLoadEnd();
if (props.onLoadEnd) props.onLoadEnd();
- }, [props.onLoadEnd]);
+ }, [props.onLoadEnd, webViewEventHandlers]);
- const { html, injectedJs } = useSource(tempDir, props.themeId);
+ const { html, js } = useSource(pageSetup, props.themeId);
return (
@@ -121,10 +102,10 @@ function NoteBodyViewer(props: Props) {
testID='NoteBodyViewer'
html={html}
allowFileAccessFromJs={true}
- injectedJavaScript={injectedJs}
+ injectedJavaScript={js}
mixedContentMode="always"
onLoadEnd={onLoadEnd}
- onMessage={onWebViewMessage}
+ onMessage={webViewEventHandlers.onMessage}
/>
);
diff --git a/packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.ts b/packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.ts
deleted file mode 100644
index 3114390f27..0000000000
--- a/packages/app-mobile/components/NoteBodyViewer/bundledJs/Renderer.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-import { MarkupLanguage, MarkupToHtml } from '@joplin/renderer';
-import type { MarkupToHtmlConverter, RenderOptions, RenderResultPluginAsset, FsDriver as RendererFsDriver } from '@joplin/renderer/types';
-import makeResourceModel from './utils/makeResourceModel';
-import addPluginAssets from './utils/addPluginAssets';
-import { ExtraContentScriptSource } from './types';
-import { ExtraContentScript } from '@joplin/lib/services/plugins/utils/loadContentScripts';
-
-export interface RendererSetupOptions {
- settings: {
- safeMode: boolean;
- tempDir: string;
- resourceDir: string;
- resourceDownloadMode: string;
- };
- // True if asset and resource files should be transferred to the WebView before rendering.
- // This must be true on web, where asset and resource files are virtual and can't be accessed
- // without transferring.
- useTransferredFiles: boolean;
-
- fsDriver: RendererFsDriver;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- pluginOptions: Record;
-}
-
-export interface RendererSettings {
- theme: string;
- onResourceLoaded: ()=> void;
- highlightedKeywords: string[];
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- resources: Record;
- codeTheme: string;
- noteHash: string;
- initialScroll: number;
-
- createEditPopupSyntax: string;
- destroyEditPopupSyntax: string;
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- pluginSettings: Record;
- requestPluginSetting: (pluginId: string, settingKey: string)=> void;
- readAssetBlob: (assetPath: string)=> Promise;
-}
-
-export interface MarkupRecord {
- language: MarkupLanguage;
- markup: string;
-}
-
-export default class Renderer {
- private markupToHtml: MarkupToHtmlConverter;
- private lastSettings: RendererSettings|null = null;
- private extraContentScripts: ExtraContentScript[] = [];
- private lastRenderMarkup: MarkupRecord|null = null;
- private resourcePathOverrides: Record = Object.create(null);
-
- public constructor(private setupOptions: RendererSetupOptions) {
- this.recreateMarkupToHtml();
- }
-
- private recreateMarkupToHtml() {
- this.markupToHtml = new MarkupToHtml({
- extraRendererRules: this.extraContentScripts,
- fsDriver: this.setupOptions.fsDriver,
- isSafeMode: this.setupOptions.settings.safeMode,
- tempDir: this.setupOptions.settings.tempDir,
- ResourceModel: makeResourceModel(this.setupOptions.settings.resourceDir),
- pluginOptions: this.setupOptions.pluginOptions,
- });
- }
-
- // Intended for web, where resources can't be linked to normally.
- public async setResourceFile(id: string, file: Blob) {
- this.resourcePathOverrides[id] = URL.createObjectURL(file);
- }
-
- public getResourcePathOverride(resourceId: string) {
- if (Object.prototype.hasOwnProperty.call(this.resourcePathOverrides, resourceId)) {
- return this.resourcePathOverrides[resourceId];
- }
- return null;
- }
-
- public async setExtraContentScriptsAndRerender(
- extraContentScripts: ExtraContentScriptSource[],
- ) {
- this.extraContentScripts = extraContentScripts.map(script => {
- const scriptModule = (eval(script.js))({
- pluginId: script.pluginId,
- contentScriptId: script.id,
- });
-
- if (!scriptModule.plugin) {
- throw new Error(`
- Expected content script ${script.id} to export a function that returns an object with a "plugin" property.
- Found: ${scriptModule}, which has keys ${Object.keys(scriptModule)}.
- `);
- }
-
- return {
- ...script,
- module: scriptModule,
- };
- });
- this.recreateMarkupToHtml();
-
- // If possible, rerenders with the last rendering settings. The goal
- // of this is to reduce the number of IPC calls between the viewer and
- // React Native. We want the first render to be as fast as possible.
- if (this.lastRenderMarkup) {
- await this.rerender(this.lastRenderMarkup, this.lastSettings);
- }
- }
-
- public async rerender(markup: MarkupRecord, settings: RendererSettings) {
- this.lastSettings = settings;
- this.lastRenderMarkup = markup;
-
- const options: RenderOptions = {
- onResourceLoaded: settings.onResourceLoaded,
- highlightedKeywords: settings.highlightedKeywords,
- resources: settings.resources,
- codeTheme: settings.codeTheme,
- postMessageSyntax: 'window.joplinPostMessage_',
- enableLongPress: true,
-
- // Show an 'edit' popup over SVG images
- editPopupFiletypes: ['image/svg+xml'],
- createEditPopupSyntax: settings.createEditPopupSyntax,
- destroyEditPopupSyntax: settings.destroyEditPopupSyntax,
- itemIdToUrl: this.setupOptions.useTransferredFiles ? (id: string) => this.getResourcePathOverride(id) : undefined,
-
- settingValue: (pluginId: string, settingName: string) => {
- const settingKey = `${pluginId}.${settingName}`;
-
- if (!(settingKey in settings.pluginSettings)) {
- // This should make the setting available on future renders.
- settings.requestPluginSetting(pluginId, settingName);
- return undefined;
- }
-
- return settings.pluginSettings[settingKey];
- },
- whiteBackgroundNoteRendering: markup.language === MarkupLanguage.Html,
- };
-
- this.markupToHtml.clearCache(markup.language);
-
- const contentContainer = document.getElementById('joplin-container-content');
-
- let html = '';
- let pluginAssets: RenderResultPluginAsset[] = [];
- try {
- const result = await this.markupToHtml.render(
- markup.language,
- markup.markup,
- JSON.parse(settings.theme),
- options,
- );
- html = result.html;
- pluginAssets = result.pluginAssets;
- } catch (error) {
- if (!contentContainer) {
- alert(`Renderer error: ${error}`);
- } else {
- contentContainer.innerText = `
- Error: ${error}
-
- ${error.stack ?? ''}
- `;
- }
- throw error;
- }
-
- contentContainer.innerHTML = html;
-
- // Adding plugin assets can be slow -- run it asynchronously.
- void (async () => {
- await addPluginAssets(pluginAssets, {
- inlineAssets: this.setupOptions.useTransferredFiles,
- readAssetBlob: settings.readAssetBlob,
- });
-
- // Some plugins require this event to be dispatched just after being added.
- document.dispatchEvent(new Event('joplin-noteDidUpdate'));
- })();
-
- this.afterRender(settings);
- }
-
- private afterRender(renderSettings: RendererSettings) {
- const readyStateCheckInterval = setInterval(() => {
- if (document.readyState === 'complete') {
- clearInterval(readyStateCheckInterval);
- if (this.setupOptions.settings.resourceDownloadMode === 'manual') {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- (window as any).webviewLib.setupResourceManualDownload();
- }
-
- const hash = renderSettings.noteHash;
- const initialScroll = renderSettings.initialScroll;
-
- // Don't scroll to a hash if we're given initial scroll (initial scroll
- // overrides scrolling to a hash).
- if ((initialScroll ?? null) !== null) {
- const scrollingElement = document.scrollingElement ?? document.documentElement;
- scrollingElement.scrollTop = initialScroll;
- } else if (hash) {
- // Gives it a bit of time before scrolling to the anchor
- // so that images are loaded.
- setTimeout(() => {
- const e = document.getElementById(hash);
- if (!e) {
- console.warn('Cannot find hash', hash);
- return;
- }
- e.scrollIntoView();
- }, 500);
- }
- }
- }, 10);
- }
-
- public clearCache(markupLanguage: MarkupLanguage) {
- this.markupToHtml.clearCache(markupLanguage);
- }
-
- private extraCssElements: Record = {};
- public setExtraCss(key: string, css: string) {
- if (this.extraCssElements.hasOwnProperty(key)) {
- this.extraCssElements[key].remove();
- }
-
- const extraCssElement = document.createElement('style');
- extraCssElement.appendChild(document.createTextNode(css));
- document.head.appendChild(extraCssElement);
-
- this.extraCssElements[key] = extraCssElement;
- }
-}
diff --git a/packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.ts b/packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.ts
deleted file mode 100644
index 93b5b9d0cb..0000000000
--- a/packages/app-mobile/components/NoteBodyViewer/bundledJs/noteBodyViewerBundle.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-
-import WebViewToRNMessenger from '../../../utils/ipc/WebViewToRNMessenger';
-import { NoteViewerLocalApi, NoteViewerRemoteApi, RendererWebViewOptions, WebViewLib } from './types';
-import Renderer from './Renderer';
-
-declare global {
- interface Window {
- rendererWebViewOptions: RendererWebViewOptions;
- webviewLib: WebViewLib;
- }
-}
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
-declare const webviewLib: WebViewLib;
-
-const messenger = new WebViewToRNMessenger(
- 'note-viewer',
- null,
-);
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
-(window as any).joplinPostMessage_ = (message: string, _args: any) => {
- return messenger.remoteApi.onPostMessage(message);
-};
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
-(window as any).webviewApi = {
- postMessage: messenger.remoteApi.onPostPluginMessage,
-};
-
-webviewLib.initialize({
- postMessage: (message: string) => {
- messenger.remoteApi.onPostMessage(message);
- },
-});
-// Share the webview library globally so that the renderer can access it.
-window.webviewLib = webviewLib;
-
-window.webviewLib = webviewLib;
-
-const renderer = new Renderer({
- ...window.rendererWebViewOptions,
- fsDriver: messenger.remoteApi.fsDriver,
-});
-
-messenger.setLocalInterface({
- renderer,
- jumpToHash: (hash: string) => {
- location.hash = `#${hash}`;
- },
-});
-
-const lastScrollTop: number|null = null;
-const onMainContentScroll = () => {
- const newScrollTop = document.scrollingElement.scrollTop;
- if (lastScrollTop !== newScrollTop) {
- messenger.remoteApi.onScroll(newScrollTop);
- }
-};
-
-// Listen for events on both scrollingElement and window
-// - On Android, scrollingElement.addEventListener('scroll', callback) doesn't call callback on
-// scroll. However, window.addEventListener('scroll', callback) does.
-// - iOS needs a listener to be added to scrollingElement -- events aren't received when
-// the listener is added to window with window.addEventListener('scroll', ...).
-document.scrollingElement?.addEventListener('scroll', onMainContentScroll);
-window.addEventListener('scroll', onMainContentScroll);
diff --git a/packages/app-mobile/components/NoteBodyViewer/bundledJs/types.ts b/packages/app-mobile/components/NoteBodyViewer/bundledJs/types.ts
deleted file mode 100644
index e7c08709ad..0000000000
--- a/packages/app-mobile/components/NoteBodyViewer/bundledJs/types.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import type { FsDriver as RendererFsDriver } from '@joplin/renderer/types';
-import Renderer from './Renderer';
-
-export interface RendererWebViewOptions {
- settings: {
- safeMode: boolean;
- tempDir: string;
- resourceDir: string;
- resourceDownloadMode: string;
- };
- useTransferredFiles: boolean;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- pluginOptions: Record;
-}
-
-export interface ExtraContentScriptSource {
- id: string;
- js: string;
- assetPath: string;
- pluginId: string;
-}
-
-export interface NoteViewerLocalApi {
- renderer: Renderer;
- jumpToHash: (hash: string)=> void;
-}
-
-export interface NoteViewerRemoteApi {
- onScroll(scrollTop: number): void;
- onPostMessage(message: string): void;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- onPostPluginMessage(contentScriptId: string, message: any): Promise;
- fsDriver: RendererFsDriver;
-}
-
-export interface WebViewLib {
- initialize(config: unknown): void;
-}
-
diff --git a/packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.ts b/packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.ts
deleted file mode 100644
index aefbb225d3..0000000000
--- a/packages/app-mobile/components/NoteBodyViewer/hooks/useRenderer.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Dispatch, RefObject, SetStateAction, useEffect, useMemo, useRef } from 'react';
-import { WebViewControl } from '../../ExtendedWebView/types';
-import { OnScrollCallback, OnWebViewMessageHandler } from '../types';
-import RNToWebViewMessenger from '../../../utils/ipc/RNToWebViewMessenger';
-import { NoteViewerLocalApi, NoteViewerRemoteApi } from '../bundledJs/types';
-import shim from '@joplin/lib/shim';
-import { WebViewMessageEvent } from 'react-native-webview';
-import PluginService from '@joplin/lib/services/plugins/PluginService';
-import Logger from '@joplin/utils/Logger';
-
-const logger = Logger.create('useRenderer');
-
-interface Props {
- webviewRef: RefObject;
- onScroll: OnScrollCallback;
- onPostMessage: (message: string)=> void;
- setOnWebViewMessage: Dispatch>;
- webViewLoaded: boolean;
-
- tempDir: string;
-}
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
-const onPostPluginMessage = async (contentScriptId: string, message: any) => {
- logger.debug(`Handling message from content script: ${contentScriptId}:`, message);
-
- const pluginService = PluginService.instance();
- const pluginId = pluginService.pluginIdByContentScriptId(contentScriptId);
- if (!pluginId) {
- throw new Error(`Plugin not found for content script with ID ${contentScriptId}`);
- }
-
- const plugin = pluginService.pluginById(pluginId);
- return plugin.emitContentScriptMessage(contentScriptId, message);
-};
-
-const useRenderer = (props: Props) => {
- const onScrollRef = useRef(props.onScroll);
- onScrollRef.current = props.onScroll;
-
- const onPostMessageRef = useRef(props.onPostMessage);
- onPostMessageRef.current = props.onPostMessage;
-
- const messenger = useMemo(() => {
- const fsDriver = shim.fsDriver();
- const localApi = {
- onScroll: (fraction: number) => onScrollRef.current?.(fraction),
- onPostMessage: (message: string) => onPostMessageRef.current?.(message),
- onPostPluginMessage,
- fsDriver: {
- writeFile: async (path: string, content: string, encoding?: string) => {
- if (!await fsDriver.exists(props.tempDir)) {
- await fsDriver.mkdir(props.tempDir);
- }
- // To avoid giving the WebView access to the entire main tempDir,
- // we use props.tempDir (which should be different).
- path = fsDriver.resolveRelativePathWithinDir(props.tempDir, path);
- return await fsDriver.writeFile(path, content, encoding);
- },
- exists: fsDriver.exists,
- cacheCssToFile: fsDriver.cacheCssToFile,
- },
- };
- return new RNToWebViewMessenger(
- 'note-viewer', props.webviewRef, localApi,
- );
- }, [props.webviewRef, props.tempDir]);
-
- useEffect(() => {
- props.setOnWebViewMessage(() => (event: WebViewMessageEvent) => {
- messenger.onWebViewMessage(event);
- });
- }, [messenger, props.setOnWebViewMessage]);
-
- useEffect(() => {
- if (props.webViewLoaded) {
- messenger.onWebViewLoaded();
- }
- }, [messenger, props.webViewLoaded]);
-
- return useMemo(() => {
- return messenger.remoteApi.renderer;
- }, [messenger]);
-};
-
-export default useRenderer;
diff --git a/packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.ts b/packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.ts
index 354a1807ef..0e7b136910 100644
--- a/packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.ts
+++ b/packages/app-mobile/components/NoteBodyViewer/hooks/useRerenderHandler.ts
@@ -1,26 +1,20 @@
-import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
import usePrevious from '@joplin/lib/hooks/usePrevious';
-import { themeStyle } from '@joplin/lib/theme';
import { MarkupLanguage } from '@joplin/renderer';
-import useEditPopup from './useEditPopup';
-import Renderer from '../bundledJs/Renderer';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import Logger from '@joplin/utils/Logger';
-import { ExtraContentScriptSource } from '../bundledJs/types';
-import Setting from '@joplin/lib/models/Setting';
-import shim from '@joplin/lib/shim';
+import { ResourceEntity, ResourceLocalStateEntity } from '@joplin/lib/services/database/types';
+import { RendererControl, RenderOptions } from '../../../contentScripts/rendererBundle/types';
import Resource from '@joplin/lib/models/Resource';
-import { ResourceEntity } from '@joplin/lib/services/database/types';
-import resolvePathWithinDir from '@joplin/lib/utils/resolvePathWithinDir';
+import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
export interface ResourceInfo {
- localState: unknown;
+ localState: ResourceLocalStateEntity;
item: ResourceEntity;
}
interface Props {
- renderer: Renderer;
+ renderer: RendererControl;
noteBody: string;
noteMarkupLanguage: MarkupLanguage;
@@ -33,8 +27,6 @@ interface Props {
initialScroll: number|undefined;
paddingBottom: number;
-
- contentScripts: ExtraContentScriptSource[];
}
const onlyCheckboxHasChangedHack = (previousBody: string, newBody: string) => {
@@ -56,10 +48,35 @@ const onlyCheckboxHasChangedHack = (previousBody: string, newBody: string) => {
const logger = Logger.create('useRerenderHandler');
-const useRerenderHandler = (props: Props) => {
- const { createEditPopupSyntax, destroyEditPopupSyntax, editPopupCss } = useEditPopup(props.themeId);
+const useResourceLoadCounter = (noteResources: Record) => {
const [lastResourceLoadCounter, setLastResourceLoadCounter] = useState(0);
- const [pluginSettingKeys, setPluginSettingKeys] = useState>({});
+ const lastDownloadCount = useRef(-1);
+ useEffect(() => {
+ let downloadedCount = 0;
+ for (const resource of Object.values(noteResources)) {
+ if (resource.localState.fetch_status === Resource.FETCH_STATUS_DONE) {
+ downloadedCount ++;
+ }
+ }
+
+ if (lastDownloadCount.current !== -1 && lastDownloadCount.current < downloadedCount) {
+ setLastResourceLoadCounter(counter => counter + 1);
+ }
+ lastDownloadCount.current = downloadedCount;
+ }, [noteResources]);
+
+ return lastResourceLoadCounter;
+};
+
+const useRerenderHandler = (props: Props) => {
+ const resourceDownloadRerenderCounter = useResourceLoadCounter(props.noteResources);
+ useEffect(() => {
+ // Whenever a resource state changes, for example when it goes from "not downloaded" to "downloaded", the "noteResources"
+ // props changes, thus triggering a render. The **content** of this noteResources array however is not changed because
+ // it doesn't contain info about the resource download state. Because of that, if we were to use the markupToHtml() cache
+ // it wouldn't re-render at all.
+ props.renderer.clearCache(props.noteMarkupLanguage);
+ }, [resourceDownloadRerenderCounter, props.renderer, props.noteMarkupLanguage]);
// To address https://github.com/laurent22/joplin/issues/433
//
@@ -82,8 +99,8 @@ const useRerenderHandler = (props: Props) => {
// below logic rely on this.
const effectDependencies = [
props.noteBody, props.noteMarkupLanguage, props.renderer, props.highlightedKeywords,
- props.noteHash, props.noteResources, props.themeId, props.paddingBottom, lastResourceLoadCounter,
- createEditPopupSyntax, destroyEditPopupSyntax, pluginSettingKeys, props.fontSize,
+ props.noteHash, props.noteResources, props.themeId, props.paddingBottom, resourceDownloadRerenderCounter,
+ props.fontSize,
];
const previousDeps = usePrevious(effectDependencies, []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -99,125 +116,42 @@ const useRerenderHandler = (props: Props) => {
const previousHash = usePrevious(props.noteHash, '');
const hashChanged = previousHash !== props.noteHash;
- useEffect(() => {
- // Whenever a resource state changes, for example when it goes from "not downloaded" to "downloaded", the "noteResources"
- // props changes, thus triggering a render. The **content** of this noteResources array however is not changed because
- // it doesn't contain info about the resource download state. Because of that, if we were to use the markupToHtml() cache
- // it wouldn't re-render at all.
- props.renderer.clearCache(props.noteMarkupLanguage);
- }, [lastResourceLoadCounter, props.renderer, props.noteMarkupLanguage]);
-
- useEffect(() => {
- void props.renderer.setExtraContentScriptsAndRerender(props.contentScripts);
- }, [props.contentScripts, props.renderer]);
-
- useAsyncEffect(async event => {
+ useAsyncEffect(async (event) => {
if (onlyNoteBodyHasChanged && onlyCheckboxesHaveChanged) {
logger.info('Only a checkbox has changed - not updating HTML');
return;
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- const pluginSettings: Record = { };
- for (const key in pluginSettingKeys) {
- pluginSettings[key] = Setting.value(`plugin-${key}`);
- }
- let newPluginSettingKeys = pluginSettingKeys;
-
- // On web, resources are virtual files and thus need to be transferred to the WebView.
- if (shim.mobilePlatform() === 'web') {
- for (const [resourceId, resource] of Object.entries(props.noteResources)) {
- try {
- await props.renderer.setResourceFile(
- resourceId,
- await shim.fsDriver().fileAtPath(Resource.fullPath(resource.item)),
- );
- } catch (error) {
- if (error.code !== 'ENOENT') {
- throw error;
- }
-
- // This can happen if a resource hasn't been downloaded yet
- logger.warn('Error: Resource file not found (ENOENT)', Resource.fullPath(resource.item), 'for ID', resource.item.id);
- }
- }
- }
-
- const theme = themeStyle(props.themeId);
- const config = {
- // We .stringify the theme to avoid a JSON serialization error involving
- // the color package.
- theme: JSON.stringify({
+ const config: RenderOptions = {
+ themeId: props.themeId,
+ themeOverrides: {
bodyPaddingTop: '0.8em',
bodyPaddingBottom: props.paddingBottom,
- ...theme,
-
noteViewerFontSize: props.fontSize,
- }),
- codeTheme: theme.codeThemeCss,
-
- onResourceLoaded: () => {
- // Force a rerender when a resource loads
- setLastResourceLoadCounter(lastResourceLoadCounter + 1);
},
highlightedKeywords: props.highlightedKeywords,
resources: props.noteResources,
+ pluginAssetContainerSelector: '#joplin-container-pluginAssetsContainer',
// If the hash changed, we don't set initial scroll -- we want to scroll to the hash
// instead.
initialScroll: (previousHash && hashChanged) ? undefined : props.initialScroll,
noteHash: props.noteHash,
-
- pluginSettings,
- requestPluginSetting: (pluginId: string, settingKey: string) => {
- // Don't trigger additional renders
- if (event.cancelled) return;
-
- const key = `${pluginId}.${settingKey}`;
- logger.debug(`Request plugin setting: plugin-${key}`);
-
- if (!(key in newPluginSettingKeys)) {
- newPluginSettingKeys = { ...newPluginSettingKeys, [`${pluginId}.${settingKey}`]: true };
- setPluginSettingKeys(newPluginSettingKeys);
- }
- },
- readAssetBlob: (assetPath: string) => {
- // Built-in assets are in resourceDir, external plugin assets are in cacheDir.
- const assetsDirs = [Setting.value('resourceDir'), Setting.value('cacheDir')];
-
- let resolvedPath = null;
- for (const assetDir of assetsDirs) {
- resolvedPath ??= resolvePathWithinDir(assetDir, assetPath);
- if (resolvedPath) break;
- }
-
- if (!resolvedPath) {
- throw new Error(`Failed to load asset at ${assetPath} -- not in any of the allowed asset directories: ${assetsDirs.join(',')}.`);
- }
- return shim.fsDriver().fileAtPath(resolvedPath);
- },
-
- createEditPopupSyntax,
- destroyEditPopupSyntax,
};
try {
logger.debug('Starting render...');
- await props.renderer.rerender({
+ await props.renderer.rerenderToBody({
language: props.noteMarkupLanguage,
markup: props.noteBody,
- }, config);
+ }, config, event);
logger.debug('Render complete.');
} catch (error) {
logger.error('Render failed:', error);
}
}, effectDependencies);
-
- useEffect(() => {
- props.renderer.setExtraCss('edit-popup', editPopupCss);
- }, [editPopupCss, props.renderer]);
};
export default useRerenderHandler;
diff --git a/packages/app-mobile/components/NoteBodyViewer/hooks/useSource.ts b/packages/app-mobile/components/NoteBodyViewer/hooks/useSource.ts
index f595137ed2..90c8595344 100644
--- a/packages/app-mobile/components/NoteBodyViewer/hooks/useSource.ts
+++ b/packages/app-mobile/components/NoteBodyViewer/hooks/useSource.ts
@@ -1,49 +1,15 @@
import { useMemo } from 'react';
import shim from '@joplin/lib/shim';
-import Setting from '@joplin/lib/models/Setting';
-import { RendererWebViewOptions } from '../bundledJs/types';
import { themeStyle } from '../../global-style';
-import { Platform } from 'react-native';
-
-const useSource = (tempDirPath: string, themeId: number) => {
- const injectedJs = useMemo(() => {
- const subValues = Setting.subValues('markdown.plugin', Setting.toPlainObject());
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
- const pluginOptions: any = {};
- for (const n in subValues) {
- pluginOptions[n] = { enabled: subValues[n] };
- }
-
- const rendererWebViewOptions: RendererWebViewOptions = {
- settings: {
- safeMode: Setting.value('isSafeMode'),
- tempDir: tempDirPath,
- resourceDir: Setting.value('resourceDir'),
- resourceDownloadMode: Setting.value('sync.resourceDownloadMode'),
- },
- // Web needs files to be transferred manually, since image SRCs can't reference
- // the Origin Private File System.
- useTransferredFiles: Platform.OS === 'web',
- pluginOptions,
- };
-
- return `
- window.rendererWebViewOptions = ${JSON.stringify(rendererWebViewOptions)};
-
- if (!window.injectedJsLoaded) {
- window.injectedJsLoaded = true;
-
- ${shim.injectedJs('webviewLib')}
- ${shim.injectedJs('noteBodyViewerBundle')}
- }
- `;
- }, [tempDirPath]);
+import { PageSetupSources } from '../../../contentScripts/types';
+const useSource = (rendererSource: PageSetupSources, themeId: number) => {
const [paddingLeft, paddingRight] = useMemo(() => {
const theme = themeStyle(themeId);
return [theme.marginLeft, theme.marginRight];
}, [themeId]);
+ const rendererBaseCss = rendererSource.css;
const html = useMemo(() => {
// iOS doesn't automatically adjust the WebView's font size to match users'
// accessibility settings. To do this, we need to tell it to match the system font.
@@ -75,6 +41,7 @@ const useSource = (tempDirPath: string, themeId: number) => {
@@ -84,9 +51,9 @@ const useSource = (tempDirPath: string, themeId: number) => {