diff --git a/BUILD.md b/BUILD.md
index a2a108690..5b32bc7dd 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -20,25 +20,25 @@ Then you can test the various applications:
## Testing the desktop application
cd ElectronClient
- npm run start
+ npm start
If you'd like to auto-reload the app on changes rather than having to quit and restart it manually each time, you can use [watchman-make](https://facebook.github.io/watchman/docs/watchman-make.html):
```sh
cd ElectronClient
-watchman-make -p '**/*.js' '**/*.jsx' --run "npm run start"
+watchman-make -p '**/*.js' '**/*.jsx' --run "npm start"
```
-It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm run start"` each time. Here's what the workflow loop looks like in practice:
+It still requires you to quit the application each time you want it to rebuild, but at least you don't have to re-run `"npm start"` each time. Here's what the workflow loop looks like in practice:
1. Edit and save files in your text editor.
2. Switch to the Electron app and cmd+Q to quit it.
-3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm run start"`, and hit enter).
+3. `watchman` immediately restarts the app for you (whereas usually you'd have to switch back to the terminal, type `"npm start"`, and hit enter).
## Testing the Terminal application
cd CliClient
- npm run start
+ npm start
## Testing the Mobile application
@@ -47,12 +47,12 @@ First you need to setup React Native to build projects with native code. For thi
Then:
cd ReactNativeClient
- npm run start-android
- # Or: npm run start-ios
+ npm start-android
+ # Or: npm start-ios
To run the iOS application, it might be easier to open the file `ios/Joplin.xcworkspace` on XCode and run the app from there.
-Normally the bundler should start automatically with the application. If it doesn't run `npm run start`.
+Normally the bundler should start automatically with the application. If it doesn't run `npm start`.
## Building the clipper
@@ -72,9 +72,9 @@ Running `npm run build` would have the same effect, but without watching.
## Running an application with additional parameters
-You can specify additional parameters when running the desktop or CLI application. To do so, add `--` to the `npm run start` command, followed by your flags. For example:
+You can specify additional parameters when running the desktop or CLI application. To do so, add `--` to the `npm start` command, followed by your flags. For example:
- npm run start -- --profile ~/MyTestProfile
+ npm start -- --profile ~/MyTestProfile
## TypeScript
diff --git a/CliClient/package-lock.json b/CliClient/package-lock.json
index 57c507c7a..f728f4c3d 100644
--- a/CliClient/package-lock.json
+++ b/CliClient/package-lock.json
@@ -726,10 +726,68 @@
"minimist": "^1.1.1"
},
"dependencies": {
+ "domelementtype": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+ },
+ "domhandler": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "htmlparser2": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
+ "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+ "requires": {
+ "domelementtype": "^1.3.1",
+ "domhandler": "^2.3.0",
+ "domutils": "^1.5.1",
+ "entities": "^1.1.1",
+ "inherits": "^2.0.1",
+ "readable-stream": "^3.1.1"
+ }
+ },
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "readable-stream": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
+ "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "requires": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
+ "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
+ },
+ "string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "requires": {
+ "safe-buffer": "~5.2.0"
+ }
}
}
},
@@ -1311,19 +1369,14 @@
"integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
},
"dom-serializer": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz",
- "integrity": "sha512-sK3ujri04WyjwQXVoK4PU3y8ula1stq10GJZpqHIUgoGZdsGzAGu65BnU3d08aTVSvO7mGPZUc0wTEDL+qGE0Q==",
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
"requires": {
"domelementtype": "^2.0.1",
"entities": "^2.0.0"
},
"dependencies": {
- "domelementtype": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
- "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
- },
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
@@ -1332,9 +1385,9 @@
}
},
"domelementtype": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
- "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+ "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
},
"domexception": {
"version": "1.0.1",
@@ -1345,20 +1398,21 @@
}
},
"domhandler": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
- "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
+ "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
"requires": {
- "domelementtype": "1"
+ "domelementtype": "^2.0.1"
}
},
"domutils": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
- "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
+ "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
"requires": {
- "dom-serializer": "0",
- "domelementtype": "1"
+ "dom-serializer": "^0.2.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0"
}
},
"dot-prop": {
@@ -3042,40 +3096,20 @@
}
},
"htmlparser2": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
- "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
+ "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
"requires": {
- "domelementtype": "^1.3.1",
- "domhandler": "^2.3.0",
- "domutils": "^1.5.1",
- "entities": "^1.1.1",
- "inherits": "^2.0.1",
- "readable-stream": "^3.1.1"
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0",
+ "domutils": "^2.0.0",
+ "entities": "^2.0.0"
},
"dependencies": {
- "readable-stream": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
- "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- },
- "safe-buffer": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
- "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
- },
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "requires": {
- "safe-buffer": "~5.2.0"
- }
+ "entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
+ "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
}
}
},
@@ -4538,14 +4572,6 @@
"is-stream": "^1.0.1"
}
},
- "node-html-parser": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
- "integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
- "requires": {
- "he": "1.1.1"
- }
- },
"node-persist": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/node-persist/-/node-persist-2.1.0.tgz",
diff --git a/CliClient/package.json b/CliClient/package.json
index d72f6481e..cad32eabf 100644
--- a/CliClient/package.json
+++ b/CliClient/package.json
@@ -5,7 +5,7 @@
"author": "Laurent Cozic",
"scripts": {
"test": "gulp buildTests -L && jasmine --config=tests/support/jasmine.json",
- "postinstall": "patch-package && npm run build",
+ "postinstall": "patch-package --patch-dir ../patches && npm run build",
"build": "gulp build",
"start": "gulp build -L && node 'build/main.js' --profile ~/Temp/TestNotes2 --stack-trace-enabled --log-level debug --env dev"
},
@@ -52,6 +52,7 @@
"highlight.js": "^9.17.1",
"html-entities": "^1.2.1",
"html-minifier": "^3.5.15",
+ "htmlparser2": "^4.1.0",
"image-data-uri": "^2.0.0",
"image-type": "^3.0.0",
"joplin-turndown": "^4.0.19",
@@ -108,8 +109,7 @@
"valid-url": "^1.0.9",
"word-wrap": "^1.2.3",
"xml2js": "^0.4.19",
- "yargs-parser": "^7.0.0",
- "node-html-parser": "^1.2.4"
+ "yargs-parser": "^7.0.0"
},
"devDependencies": {
"gulp": "^4.0.2",
diff --git a/CliClient/tests/MdToHtml.js b/CliClient/tests/MdToHtml.js
index e53ea4917..8ab3adfff 100644
--- a/CliClient/tests/MdToHtml.js
+++ b/CliClient/tests/MdToHtml.js
@@ -30,7 +30,11 @@ describe('MdToHtml', function() {
it('should convert from Markdown to Html', asyncTest(async () => {
const basePath = `${__dirname}/md_to_html`;
const files = await shim.fsDriver().readDirStats(basePath);
- const mdToHtml = new MdToHtml();
+ const mdToHtml = new MdToHtml({
+ ResourceModel: {
+ isResourceUrl: () => false,
+ },
+ });
for (let i = 0; i < files.length; i++) {
const mdFilename = files[i].path;
@@ -39,7 +43,7 @@ describe('MdToHtml', function() {
const mdFilePath = `${basePath}/${mdFilename}`;
const htmlPath = `${basePath}/${filename(mdFilePath)}.html`;
- // if (mdFilename !== 'table_with_header.html') continue;
+ // if (mdFilename !== 'sanitize_9.md') continue;
const mdToHtmlOptions = {
bodyOnly: true,
diff --git a/CliClient/tests/html_to_html/sanitize.dest.html b/CliClient/tests/html_to_html/sanitize.dest.html
index b0f104132..7f871c790 100644
--- a/CliClient/tests/html_to_html/sanitize.dest.html
+++ b/CliClient/tests/html_to_html/sanitize.dest.html
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize.html b/CliClient/tests/md_to_html/sanitize.html
index b0f104132..7f871c790 100644
--- a/CliClient/tests/md_to_html/sanitize.html
+++ b/CliClient/tests/md_to_html/sanitize.html
@@ -1,2 +1,2 @@
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_2.html b/CliClient/tests/md_to_html/sanitize_2.html
index 7b0e5c380..e095d7d48 100644
--- a/CliClient/tests/md_to_html/sanitize_2.html
+++ b/CliClient/tests/md_to_html/sanitize_2.html
@@ -1 +1 @@
-
Testing inline text
+Testing inline text
diff --git a/CliClient/tests/md_to_html/sanitize_3.html b/CliClient/tests/md_to_html/sanitize_3.html
new file mode 100644
index 000000000..fd5681576
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_3.html
@@ -0,0 +1 @@
+Should be red.
diff --git a/CliClient/tests/md_to_html/sanitize_3.md b/CliClient/tests/md_to_html/sanitize_3.md
new file mode 100644
index 000000000..25a717145
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_3.md
@@ -0,0 +1 @@
+Should be red.
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_4.html b/CliClient/tests/md_to_html/sanitize_4.html
new file mode 100644
index 000000000..fbafec90e
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_4.html
@@ -0,0 +1,4 @@
+
+
H5
+H6
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_4.md b/CliClient/tests/md_to_html/sanitize_4.md
new file mode 100644
index 000000000..a137daf4b
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_4.md
@@ -0,0 +1,6 @@
+
+
+##### H5
+###### H6
+
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_5.html b/CliClient/tests/md_to_html/sanitize_5.html
new file mode 100644
index 000000000..819bccc5e
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_5.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_5.md b/CliClient/tests/md_to_html/sanitize_5.md
new file mode 100644
index 000000000..819bccc5e
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_5.md
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_6.html b/CliClient/tests/md_to_html/sanitize_6.html
new file mode 100644
index 000000000..e627988b4
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_6.html
@@ -0,0 +1 @@
+bla
diff --git a/CliClient/tests/md_to_html/sanitize_6.md b/CliClient/tests/md_to_html/sanitize_6.md
new file mode 100644
index 000000000..75f1f64bd
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_6.md
@@ -0,0 +1 @@
+bla
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_7.html b/CliClient/tests/md_to_html/sanitize_7.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/CliClient/tests/md_to_html/sanitize_7.md b/CliClient/tests/md_to_html/sanitize_7.md
new file mode 100644
index 000000000..51839dfd2
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_7.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_8.html b/CliClient/tests/md_to_html/sanitize_8.html
new file mode 100644
index 000000000..775977ea1
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_8.html
@@ -0,0 +1,2 @@
+<a href="#" onclick="leavethisalone">testing fence</a>
+
diff --git a/CliClient/tests/md_to_html/sanitize_8.md b/CliClient/tests/md_to_html/sanitize_8.md
new file mode 100644
index 000000000..c2460c2f0
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_8.md
@@ -0,0 +1,3 @@
+```html
+testing fence
+```
diff --git a/CliClient/tests/md_to_html/sanitize_9.html b/CliClient/tests/md_to_html/sanitize_9.html
new file mode 100644
index 000000000..59fd9881e
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_9.html
@@ -0,0 +1 @@
+M&Ms
\ No newline at end of file
diff --git a/CliClient/tests/md_to_html/sanitize_9.md b/CliClient/tests/md_to_html/sanitize_9.md
new file mode 100644
index 000000000..a191d394d
--- /dev/null
+++ b/CliClient/tests/md_to_html/sanitize_9.md
@@ -0,0 +1 @@
+M&Ms
\ No newline at end of file
diff --git a/ElectronClient/package-lock.json b/ElectronClient/package-lock.json
index f80001b8a..9d3b10981 100644
--- a/ElectronClient/package-lock.json
+++ b/ElectronClient/package-lock.json
@@ -3629,6 +3629,24 @@
"webidl-conversions": "^4.0.2"
}
},
+ "domhandler": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
+ "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
+ "requires": {
+ "domelementtype": "^2.0.1"
+ }
+ },
+ "domutils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
+ "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
+ "requires": {
+ "dom-serializer": "^0.2.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0"
+ }
+ },
"dot-prop": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz",
@@ -6257,6 +6275,24 @@
}
}
},
+ "htmlparser2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
+ "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0",
+ "domutils": "^2.0.0",
+ "entities": "^2.0.0"
+ },
+ "dependencies": {
+ "entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
+ "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
+ }
+ }
+ },
"http-cache-semantics": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz",
@@ -7967,21 +8003,6 @@
}
}
},
- "node-html-parser": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
- "integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
- "requires": {
- "he": "1.1.1"
- },
- "dependencies": {
- "he": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
- "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
- }
- }
- },
"node-notifier": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz",
diff --git a/ElectronClient/package.json b/ElectronClient/package.json
index 694009bc1..3a0159ac0 100644
--- a/ElectronClient/package.json
+++ b/ElectronClient/package.json
@@ -5,7 +5,7 @@
"main": "main.js",
"scripts": {
"dist": "node_modules/.bin/electron-builder",
- "build": "patch-package && gulp build",
+ "build": "patch-package --patch-dir ../patches && gulp build",
"postinstall": "npm run build && gulp electronRebuild",
"start": "gulp build -L && electron . --env dev --log-level debug --no-welcome --open-dev-tools"
},
@@ -107,6 +107,7 @@
"highlight.js": "^9.17.1",
"html-entities": "^1.2.1",
"html-minifier": "^4.0.0",
+ "htmlparser2": "^4.1.0",
"image-type": "^3.0.0",
"joplin-turndown": "^4.0.19",
"joplin-turndown-plugin-gfm": "^1.0.12",
@@ -137,7 +138,6 @@
"multiparty": "^4.2.1",
"mustache": "^3.0.1",
"node-fetch": "^1.7.3",
- "node-html-parser": "^1.2.4",
"node-notifier": "^6.0.0",
"pretty-bytes": "^5.3.0",
"promise": "^8.0.1",
diff --git a/ElectronClient/patches/sax+1.2.4.patch b/ElectronClient/patches/sax+1.2.4.patch
deleted file mode 100644
index 2ea8282cf..000000000
--- a/ElectronClient/patches/sax+1.2.4.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-diff --git a/node_modules/sax/lib/sax.js b/node_modules/sax/lib/sax.js
-index 795d607..ccad5d8 100644
---- a/node_modules/sax/lib/sax.js
-+++ b/node_modules/sax/lib/sax.js
-@@ -1040,6 +1040,13 @@
- parser.textNode += c
- }
- }
-+
-+ // Sax is kind of buggy when handling large text node. It has a function to check that
-+ // the buffer doesn't run out of space but it doesn't seem to call it for text node.
-+ // The result is that parser.textNode reaches 1GB and then the app crashes. So here
-+ // we call checkBufferLength to make sure the buffer is cleared and the "text" event
-+ // emitted so that the caller can handle memory properly.
-+ checkBufferLength(parser);
- continue
-
- case S.SCRIPT:
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
index 2fe2bbc3c..db348af46 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml.js
@@ -201,6 +201,7 @@ class MdToHtml {
// Using the `context` object, a plugin can define what additional assets they need (css, fonts, etc.) using context.pluginAssets.
// The calling application will need to handle loading these assets.
+ markdownIt.use(rules.sanitize_html(context, ruleOptions));
markdownIt.use(rules.image(context, ruleOptions));
markdownIt.use(rules.checkbox(context, ruleOptions));
markdownIt.use(rules.link_open(context, ruleOptions));
@@ -208,7 +209,6 @@ class MdToHtml {
if (this.pluginEnabled('katex')) markdownIt.use(rules.katex(context, ruleOptions));
if (this.pluginEnabled('fountain')) markdownIt.use(rules.fountain(context, ruleOptions));
if (this.pluginEnabled('mermaid')) markdownIt.use(rules.mermaid(context, ruleOptions));
- markdownIt.use(rules.sanitize_html(context, ruleOptions));
markdownIt.use(rules.highlight_keywords(context, ruleOptions));
markdownIt.use(rules.code_inline(context, ruleOptions));
markdownIt.use(markdownItAnchor, { slugify: uslugify });
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
index aa94c4658..d3e41a9a2 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/html_image.js
@@ -34,7 +34,7 @@ function installRule(markdownIt, mdOptions, ruleOptions) {
if (!content.match(imageRegex)) return defaultRender(tokens, idx, options, env, self);
return content.replace(imageRegex, (v, before, src, after) => {
- if (!Resource.isResourceUrl(src)) return defaultRender(tokens, idx, options, env, self);
+ if (!Resource.isResourceUrl(src)) return ``;
return renderImageHtml(before, src, after, ruleOptions);
});
};
diff --git a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts
index cc92bbe11..d221fa236 100644
--- a/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts
+++ b/ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/sanitize_html.ts
@@ -1,20 +1,6 @@
const md5 = require('md5');
const htmlUtils = require('../../htmlUtils');
-function getOpenTagName(html:string):string {
- const m = html.toLowerCase().match(/<([a-z]+)(\s|>)/);
- if (!m || m.length < 2) return null;
- return m[1];
-}
-
-function isSelfClosedTag(html:string):boolean {
- return html.substr(-2) === '/>';
-}
-
-function stripOffClosingTag(html:string, tagName:string):string {
- return html.substr(0, html.length - tagName.length - 3);
-}
-
// @ts-ignore: Keep the function signature as-is despite unusued arguments
function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any) {
markdownIt.core.ruler.push('sanitize_html', (state:any) => {
@@ -43,40 +29,10 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
// text: Testing
// html_inline:
//
- // The problem for us is that when we pass this HTML fragment to the sanitize function
- // it is going to turn it into valid HTML. Thus:
- //
- // "" becomes ""
- // "" becomes ""
- //
- // So the result would be "Testing"
- //
- // Because of this, we need to be careful with html_inline:
- //
- // 0. Check if it's an opening or closing tag - only opening ones need to be processed
- // 1. Sanitize the fragment
- // 2. Strip off the closing tag that was added
- //
- // Also self-closing tags need to be handled.
- //
- // html_block is not a problem as the whole content is valid HTML.
+ // So the sanitizeHtml function must handle this kind of non-valid HTML.
if (!sanitizedContent) {
- if (token.type === 'html_inline') {
- const openTagName = getOpenTagName(token.content);
- const isSelfClosed = isSelfClosedTag(token.content);
-
- if (!openTagName) {
- sanitizedContent = token.content;
- } else {
- sanitizedContent = htmlUtils.sanitizeHtml(token.content);
- if (!isSelfClosed) {
- sanitizedContent = stripOffClosingTag(sanitizedContent, openTagName);
- }
- }
- } else { // html_block
- sanitizedContent = htmlUtils.sanitizeHtml(token.content);
- }
+ sanitizedContent = htmlUtils.sanitizeHtml(token.content);
}
token.content = sanitizedContent;
diff --git a/ReactNativeClient/lib/joplin-renderer/htmlUtils.js b/ReactNativeClient/lib/joplin-renderer/htmlUtils.js
index dc78c4a96..338af5d21 100644
--- a/ReactNativeClient/lib/joplin-renderer/htmlUtils.js
+++ b/ReactNativeClient/lib/joplin-renderer/htmlUtils.js
@@ -2,12 +2,32 @@ const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode;
// [\s\S] instead of . for multiline matching
-const NodeHtmlParser = require('node-html-parser');
-
// https://stackoverflow.com/a/16119722/561309
const imageRegex = //gi;
const JS_EVENT_NAMES = ['onabort', 'onafterprint', 'onbeforeprint', 'onbeforeunload', 'onblur', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick', 'oncontextmenu', 'oncopy', 'oncuechange', 'oncut', 'ondblclick', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror', 'onfocus', 'onhashchange', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onmessage', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpaste', 'onpause', 'onplay', 'onplaying', 'onpopstate', 'onprogress', 'onratechange', 'onreset', 'onresize', 'onscroll', 'onsearch', 'onseeked', 'onseeking', 'onselect', 'onstalled', 'onstorage', 'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onunload', 'onvolumechange', 'onwaiting', 'onwheel'];
+const selfClosingElements = [
+ 'area',
+ 'base',
+ 'basefont',
+ 'br',
+ 'col',
+ 'command',
+ 'embed',
+ 'frame',
+ 'hr',
+ 'img',
+ 'input',
+ 'isindex',
+ 'keygen',
+ 'link',
+ 'meta',
+ 'param',
+ 'source',
+ 'track',
+ 'wbr',
+];
+
class HtmlUtils {
attributesHtml(attr) {
@@ -46,31 +66,64 @@ class HtmlUtils {
});
}
- sanitizeHtml(html) {
- const walkHtmlNodes = (nodes) => {
- if (!nodes || !nodes.length) return;
+ isSelfClosingTag(tagName) {
+ return selfClosingElements.includes(tagName.toLowerCase());
+ }
- for (const node of nodes) {
- for (const attr in node.attributes) {
- if (!node.attributes.hasOwnProperty(attr)) continue;
- if (JS_EVENT_NAMES.includes(attr)) node.setAttribute(attr, '');
- }
- walkHtmlNodes(node.childNodes);
- }
+ sanitizeHtml(html) {
+ const htmlparser2 = require('htmlparser2');
+
+ const output = [];
+
+ const tagStack = [];
+
+ const currentTag = () => {
+ if (!tagStack.length) return '';
+ return tagStack[tagStack.length - 1];
};
- // Need to wrap in div, otherwise elements at the root will be skipped
- // The DIV tags are removed below
- const dom = NodeHtmlParser.parse(`${html}
`, {
- script: false,
- style: true,
- pre: true,
- comment: false,
- });
+ const disallowedTags = ['script', 'iframe', 'frameset', 'frame', 'object'];
- walkHtmlNodes([dom]);
- const output = dom.toString();
- return output.substr(5, output.length - 11);
+ const parser = new htmlparser2.Parser({
+
+ onopentag: (name, attrs) => {
+ tagStack.push(name.toLowerCase());
+
+ if (disallowedTags.includes(currentTag())) return;
+
+ attrs = Object.assign({}, attrs);
+ for (const eventName of JS_EVENT_NAMES) {
+ delete attrs[eventName];
+ }
+ let attrHtml = this.attributesHtml(attrs);
+ if (attrHtml) attrHtml = ` ${attrHtml}`;
+ const closingSign = this.isSelfClosingTag(name) ? '/>' : '>';
+ output.push(`<${name}${attrHtml}${closingSign}`);
+ },
+
+ ontext: (decodedText) => {
+ if (disallowedTags.includes(currentTag())) return;
+
+ output.push(htmlentities(decodedText));
+ },
+
+ onclosetag: (name) => {
+ const current = currentTag();
+
+ if (current === name.toLowerCase()) tagStack.pop();
+
+ if (disallowedTags.includes(current)) return;
+
+ if (this.isSelfClosingTag(name)) return;
+ output.push(`${name}>`);
+ },
+
+ }, { decodeEntities: true });
+
+ parser.write(html);
+ parser.end();
+
+ return output.join('');
}
diff --git a/ReactNativeClient/lib/joplin-renderer/package.json b/ReactNativeClient/lib/joplin-renderer/package.json
index a3daa8533..7d964314b 100644
--- a/ReactNativeClient/lib/joplin-renderer/package.json
+++ b/ReactNativeClient/lib/joplin-renderer/package.json
@@ -38,7 +38,6 @@
"md5": "^2.2.1",
"mermaid": "^8.4.6",
"memory-cache": "^0.2.0",
- "node-html-parser": "^1.2.4",
"uslug": "^1.0.4"
}
}
diff --git a/ReactNativeClient/package-lock.json b/ReactNativeClient/package-lock.json
index b1d805b71..5b690bbd3 100644
--- a/ReactNativeClient/package-lock.json
+++ b/ReactNativeClient/package-lock.json
@@ -2171,6 +2171,12 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
},
+ "@yarnpkg/lockfile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+ "dev": true
+ },
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -3853,12 +3859,44 @@
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz",
"integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
},
+ "dom-serializer": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
+ "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "entities": "^2.0.0"
+ }
+ },
"dom-walk": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
"integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=",
"dev": true
},
+ "domelementtype": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+ "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
+ },
+ "domhandler": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
+ "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
+ "requires": {
+ "domelementtype": "^2.0.1"
+ }
+ },
+ "domutils": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz",
+ "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==",
+ "requires": {
+ "dom-serializer": "^0.2.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0"
+ }
+ },
"duplexify": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
@@ -4417,6 +4455,29 @@
}
}
},
+ "find-yarn-workspace-root": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz",
+ "integrity": "sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==",
+ "dev": true,
+ "requires": {
+ "fs-extra": "^4.0.3",
+ "micromatch": "^3.1.4"
+ },
+ "dependencies": {
+ "fs-extra": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+ "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ }
+ }
+ },
"findup-sync": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz",
@@ -5520,6 +5581,17 @@
}
}
},
+ "htmlparser2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
+ "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0",
+ "domutils": "^2.0.0",
+ "entities": "^2.0.0"
+ }
+ },
"http-errors": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
@@ -6254,6 +6326,15 @@
"graceful-fs": "^4.1.9"
}
},
+ "klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
"last-run": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz",
@@ -7485,21 +7566,6 @@
"is-stream": "^1.0.1"
}
},
- "node-html-parser": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.2.4.tgz",
- "integrity": "sha512-qHwPdGyGr9pOZBoSgUOuNPG20QYZVN00lFcxKQgjPUODSxVH7obQeLVVawa3B4cfSNtLIeczSzoy/xYA8XG5WQ==",
- "requires": {
- "he": "1.1.1"
- },
- "dependencies": {
- "he": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
- "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0="
- }
- }
- },
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@@ -7928,6 +7994,93 @@
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
+ "patch-package": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.1.tgz",
+ "integrity": "sha512-dfCtQor63PPij6DDYtCzBRoO5nNAcMSg7Cmh+DLhR+s3t0OLQBdvFxJksZHBe1J2MjsSWDjTF4+oQKFbdkssIg==",
+ "dev": true,
+ "requires": {
+ "@yarnpkg/lockfile": "^1.1.0",
+ "chalk": "^2.4.2",
+ "cross-spawn": "^6.0.5",
+ "find-yarn-workspace-root": "^1.2.1",
+ "fs-extra": "^7.0.1",
+ "is-ci": "^2.0.0",
+ "klaw-sync": "^6.0.0",
+ "minimist": "^1.2.0",
+ "rimraf": "^2.6.3",
+ "semver": "^5.6.0",
+ "slash": "^2.0.0",
+ "tmp": "^0.0.33"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "semver": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "dev": true
+ },
+ "slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ }
+ }
+ },
"path-dirname": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
diff --git a/ReactNativeClient/package.json b/ReactNativeClient/package.json
index d3fea9040..5fbc5f153 100644
--- a/ReactNativeClient/package.json
+++ b/ReactNativeClient/package.json
@@ -8,7 +8,7 @@
"start": "node node_modules/react-native/local-cli/cli.js start --reset-cache",
"start-ios": "react-native run-ios",
"start-android": "react-native run-android",
- "postinstall": "jetify && npm run build",
+ "postinstall": "patch-package --patch-dir ../patches && jetify && npm run build",
"build": "gulp build",
"log-ios": "react-native-log-ios \"Joplin\"",
"log-android": "adb logcat *:S ReactNative:V ReactNativeJS:V"
@@ -26,6 +26,7 @@
"form-data": "^2.1.4",
"highlight.js": "^9.17.1",
"html-entities": "^1.2.1",
+ "htmlparser2": "^4.1.0",
"jsc-android": "241213.1.0",
"json-stringify-safe": "^5.0.1",
"katex": "^0.11.1",
@@ -46,7 +47,6 @@
"memory-cache": "^0.2.0",
"mermaid": "^8.4.6",
"moment": "^2.24.0",
- "node-html-parser": "^1.2.4",
"prop-types": "^15.6.0",
"punycode": "^2.1.1",
"query-string": "4.3.4",
@@ -101,6 +101,7 @@
"gulp": "^4.0.2",
"jetifier": "^1.6.5",
"metro-react-native-babel-preset": "^0.54.1",
+ "patch-package": "^6.2.1",
"react-test-renderer": "^16.8.3"
}
}
diff --git a/patches/htmlparser2+4.1.0.patch b/patches/htmlparser2+4.1.0.patch
new file mode 100644
index 000000000..87075ae84
--- /dev/null
+++ b/patches/htmlparser2+4.1.0.patch
@@ -0,0 +1,47 @@
+diff --git a/node_modules/htmlparser2/lib/Parser.js b/node_modules/htmlparser2/lib/Parser.js
+index 44b4371..bcd7cc2 100644
+--- a/node_modules/htmlparser2/lib/Parser.js
++++ b/node_modules/htmlparser2/lib/Parser.js
+@@ -212,6 +212,13 @@ var Parser = /** @class */ (function (_super) {
+ this._tagname = "";
+ };
+ Parser.prototype.onclosetag = function (name) {
++ // When this is true, the onclosetag event will always be emitted
++ // for closing tags (eg ) even if that tag was not previously
++ // open. This is needed because we reconstruct the HTML based on
++ // fragments that don't necessarily contain the opening tag.
++ // Without this patch, onopentagname would not be emitted, and
++ // so the closing tag would disappear from the output.
++ var alwaysClose = true;
+ this._updatePosition(1);
+ if (this._lowerCaseTagNames) {
+ name = name.toLowerCase();
+@@ -236,11 +243,15 @@ var Parser = /** @class */ (function (_super) {
+ else if (name === "p" && !this._options.xmlMode) {
+ this.onopentagname(name);
+ this._closeCurrentTag();
++ } else if (!this._stack.length && alwaysClose) {
++ this._cbs.onclosetag(name);
+ }
+ }
+ else if (!this._options.xmlMode && (name === "br" || name === "p")) {
+ this.onopentagname(name);
+ this._closeCurrentTag();
++ } else if (!this._stack.length && alwaysClose) {
++ this._cbs.onclosetag(name);
+ }
+ };
+ Parser.prototype.onselfclosingtag = function () {
+@@ -331,7 +342,11 @@ var Parser = /** @class */ (function (_super) {
+ };
+ Parser.prototype.onend = function () {
+ if (this._cbs.onclosetag) {
+- for (var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i]))
++ // Prevent the parser from auto-closing tags. Since we deal with fragments that
++ // maybe contain the opening tag but not the closing one, we don't want that
++ // closing tag to be auto-added.
++ //
++ // for (var i = this._stack.length; i > 0; this._cbs.onclosetag(this._stack[--i]))
+ ;
+ }
+ if (this._cbs.onend)
diff --git a/CliClient/patches/sax+1.2.4.patch b/patches/sax+1.2.4.patch
similarity index 100%
rename from CliClient/patches/sax+1.2.4.patch
rename to patches/sax+1.2.4.patch