Compare commits
63 Commits
android-v2
...
user_data
Author | SHA1 | Date | |
---|---|---|---|
|
051d628250 | ||
|
d8f365834b | ||
|
6dc6acc644 | ||
|
30e1164df9 | ||
|
3c0331eb6e | ||
|
a8821dfcb5 | ||
|
54b15a1917 | ||
|
e2d27275db | ||
|
b90cb89e0f | ||
|
a6516c5f14 | ||
|
249a744e9d | ||
|
1eeb5ab471 | ||
|
c77a0b4709 | ||
|
1b64688042 | ||
|
c082505f61 | ||
|
f555e528ba | ||
|
96ff76fd1d | ||
|
9ceb7b9c88 | ||
|
6dc8ad2ba6 | ||
|
152e354d87 | ||
|
dbef391855 | ||
|
78f3f1c01a | ||
|
e7409145b6 | ||
|
bf8e34a0be | ||
|
4aaea3fe71 | ||
|
e5b4332f17 | ||
|
72a0adb434 | ||
|
37b745c69e | ||
|
3338164f43 | ||
|
98440beffa | ||
|
0c6f779aab | ||
|
fb27ae991c | ||
|
a41c9b6819 | ||
|
625812f7e6 | ||
|
e8cee2907c | ||
|
da25d10900 | ||
|
9f01258341 | ||
|
8608032243 | ||
|
bdbb362644 | ||
|
30671e7a20 | ||
|
8b578c5dde | ||
|
3792a5fb94 | ||
|
fb28268582 | ||
|
be3c15c37c | ||
|
b34342f01e | ||
|
19992ffcf9 | ||
|
6c15940005 | ||
|
7f0f745af3 | ||
|
1d62003486 | ||
|
fd373baeae | ||
|
f880e12a26 | ||
|
e81332e6b9 | ||
|
bda8766564 | ||
|
6d7b856d1d | ||
|
90a8d704c1 | ||
|
8dfcec249f | ||
|
659f602a12 | ||
|
45877f8b26 | ||
|
77505e3c75 | ||
|
dde7acc70f | ||
|
531402cede | ||
|
56ca9d4125 | ||
|
60b3921808 |
@@ -75,6 +75,7 @@ packages/pdf-viewer/dist
|
||||
plugin_types/
|
||||
readme/
|
||||
packages/react-native-vosk/lib/
|
||||
packages/lib/countable/Countable.js
|
||||
|
||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||
packages/app-cli/app/LinkSelector.js
|
||||
@@ -556,6 +557,8 @@ packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
packages/lib/net-utils.js
|
||||
packages/lib/ntp.js
|
||||
packages/lib/onedrive-api.js
|
||||
|
2
.gitignore
vendored
@@ -542,6 +542,8 @@ packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
packages/lib/models/utils/types.js
|
||||
packages/lib/models/utils/userData.js
|
||||
packages/lib/models/utils/userData.test.js
|
||||
packages/lib/net-utils.js
|
||||
packages/lib/ntp.js
|
||||
packages/lib/onedrive-api.js
|
||||
|
@@ -1,30 +0,0 @@
|
||||
diff --git a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
|
||||
index a8abd71833879201e3438b2fa51d712a311c4551..ffe9c2c6dfa5c703ba76b65d94d5dd6784102c19 100644
|
||||
--- a/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
|
||||
+++ b/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java
|
||||
@@ -591,7 +591,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
|
||||
// ignored.printStackTrace();
|
||||
}
|
||||
|
||||
- RNFetchBlobFileResp rnFetchBlobFileResp = (RNFetchBlobFileResp) responseBody;
|
||||
+ RNFetchBlobFileResp rnFetchBlobFileResp = new RNFetchBlobFileResp(responseBody);
|
||||
|
||||
if(rnFetchBlobFileResp != null && !rnFetchBlobFileResp.isDownloadComplete()){
|
||||
callback.invoke("Download interrupted.", null);
|
||||
diff --git a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
|
||||
index 2470eef612308c15a89dfea5a1f16937469be29f..965f8becc195965907699182c764ec9e51811450 100644
|
||||
--- a/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
|
||||
+++ b/android/src/main/java/com/RNFetchBlob/Response/RNFetchBlobFileResp.java
|
||||
@@ -35,6 +35,12 @@ public class RNFetchBlobFileResp extends ResponseBody {
|
||||
FileOutputStream ofStream;
|
||||
boolean isEndMarkerReceived;
|
||||
|
||||
+ // ref: https://github.com/joltup/rn-fetch-blob/issues/490#issuecomment-990899440
|
||||
+ public RNFetchBlobFileResp(ResponseBody body) {
|
||||
+ super();
|
||||
+ this.originalBody = body;
|
||||
+ }
|
||||
+
|
||||
public RNFetchBlobFileResp(ReactApplicationContext ctx, String taskId, ResponseBody body, String path, boolean overwrite) throws IOException {
|
||||
super();
|
||||
this.rctContext = ctx;
|
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
viewBox="0 0 108 108"
|
||||
height="108"
|
||||
width="108"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
sodipodi:docname="JoplinLetter.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata23">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
|
||||
|
||||
|
||||
</cc:Work>
|
||||
|
||||
</rdf:RDF>
|
||||
|
||||
</metadata>
|
||||
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
inkscape:document-rotation="0"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1094"
|
||||
id="namedview21"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.8"
|
||||
inkscape:cx="-2.1428571"
|
||||
inkscape:cy="73.214286"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
|
||||
|
||||
<defs
|
||||
id="defs6" />
|
||||
|
||||
|
||||
<g
|
||||
id="g865"
|
||||
transform="translate(44, 40), scale(0.33,0.33)"
|
||||
style="fill:none;fill-opacity:1"
|
||||
inkscape:label="Letter container"><path
|
||||
id="path30"
|
||||
fill="#ffffff"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.17263;stroke-dasharray:none;stroke-opacity:1;stop-opacity:1"
|
||||
d="M 92.634508,-48.8373 H 28.444063 c -0.606678,0 -1.097079,0.490859 -1.097079,1.096592 v 20.383573 c 0,0.686004 0.555444,1.241912 1.241922,1.241912 h 8.800852 c 3.559682,0 6.44623,2.738013 6.755314,6.217395 v 13.9751869 7.78593 74.1585111 0.52545 h -0.0016 c 0.02333,0.77689 -0.0096,1.54086 -0.08442,2.29329 -0.0096,0.10981 -0.02333,0.21821 -0.03653,0.32709 -0.05078,0.43965 -0.107028,0.87699 -0.192454,1.30511 -0.04661,0.24405 -0.112627,0.48164 -0.171063,0.72199 -0.07882,0.3197 -0.142579,0.64448 -0.240789,0.95727 -0.580372,1.85779 -1.49796,3.58087 -2.767117,5.09773 -0.04647,0.054 -0.104299,0.10381 -0.150278,0.15732 -0.387038,0.44843 -0.793477,0.88531 -1.243283,1.2945 -0.453957,0.4129 -0.934667,0.79673 -1.439823,1.15335 -3.591033,2.54379 -8.424421,3.66298 -13.824789,3.14629 -6.885879,-0.64724 -13.717305,-3.77002 -19.2390127,-8.79395 -5.52077603,-5.02346 -8.953577,-11.23901 -9.664036,-17.50346 -0.638943,-5.60429 1.040305,-10.53595 4.72500997,-13.88846 0.0096,-0.007 0.01866,-0.0142 0.0258,-0.0222 0.142742,-0.12779 0.299405,-0.23989 0.446104,-0.36307 2.63699903,-2.209329 6.07439703,-3.548119 9.99711673,-3.91719 0.04421,-0.0042 0.08683,-0.0112 0.128653,-0.0147 0.412878,-0.03644 0.833625,-0.05307 1.258052,-0.06875 0.221801,-0.0075 0.441028,-0.02309 0.666165,-0.02489 0.07465,-10e-4 0.145635,-0.0089 0.220634,-0.0089 0.133761,0 0.272634,0.0175 0.406421,0.01936 0.618197,0.0098 1.240993,0.03506 1.876236,0.08951 0.08027,0.0065 0.156833,0.0037 0.239457,0.0112 0.05865,0.0061 0.116802,0.0189 0.175239,0.02494 3.598402,0.360764 7.161287,1.43521 10.542386,3.06325 0.0705,0.003 0.152191,0.0249 0.254215,0.0812 1.030611,0.56929 1.228062,-0.0415 1.258503,-0.44519 V 33.605817 20.558368 c 0,-0.878838 -0.611246,-1.656187 -1.468421,-1.844874 -18.2116207,-4.008068 -36.079989,0.163761 -48.68826,11.641302 -11.014353,10.01925 -16.342292,24.477454 -14.615976,39.665024 1.5404,13.5277 8.484385,26.52393 19.555018,36.599 10.7901423,9.81673 24.5286593,15.9557 38.700372,17.29085 1.952829,0.18076 3.914888,0.27393 5.829429,0.27393 13.534593,0 26.093507,-4.64092 35.363086,-13.07322 8.781041,-7.99493 14.040701,-18.97328 14.812501,-30.91676 l 0.0714,-78.3303471 h 0.006 V -19.079901 h 0.002 v -0.365378 c 0.0793,-3.696201 3.09325,-6.669963 6.80976,-6.669963 h 8.80085 c 0.68555,0 1.24192,-0.555907 1.24192,-1.241909 v -20.383571 c 0,-0.605733 -0.4904,-1.096592 -1.09705,-1.096592"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:label="Letter" /></g>
|
||||
|
||||
|
||||
</svg>
|
Before Width: | Height: | Size: 4.3 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="#fff" d="M5.841 0C2.63 0 0 2.628 0 5.841V18.16C0 21.372 2.629 24 5.841 24H18.16C21.372 24 24 21.372 24 18.159V5.84C24 2.628 21.372 0 18.159 0Zm6.207 3.21h6.522a.111.111 0 0 1 .111.111v2.071c0 .07-.056.127-.126.127h-.894a.692.692 0 0 0-.692.677v2.165h-.001l-.007 7.96a4.628 4.628 0 0 1-1.505 3.14c-.942.858-2.218 1.33-3.594 1.33-.194 0-.393-.01-.592-.029a6.843 6.843 0 0 1-3.932-1.757c-1.125-1.023-1.83-2.344-1.987-3.718-.175-1.544.366-3.013 1.485-4.03 1.281-1.167 3.097-1.59 4.947-1.183.087.019.15.098.15.187v3.117c-.004.041-.024.104-.128.046a.059.059 0 0 0-.026-.009 3.22 3.22 0 0 0-1.072-.31c-.005-.001-.011-.003-.017-.003-.009-.001-.016 0-.025-.002a2.753 2.753 0 0 0-.19-.009l-.042-.002-.022.001c-.023 0-.045.002-.068.003a2.53 2.53 0 0 0-.127.007l-.014.001c-.398.038-.747.174-1.015.398l-.046.037a.02.02 0 0 1-.002.002c-.375.341-.545.842-.48 1.412.072.636.42 1.268.981 1.778.562.51 1.256.828 1.955.894.55.052 1.04-.062 1.405-.32a1.52 1.52 0 0 0 .146-.117c.046-.042.087-.086.127-.132l.015-.016c.129-.154.222-.329.281-.518.01-.031.017-.064.025-.097l.017-.073c.009-.044.014-.088.02-.133l.003-.033a1.81 1.81 0 0 0 .009-.233v-9.8a.69.69 0 0 0-.686-.631h-.895a.126.126 0 0 1-.126-.127v-2.07a.111.111 0 0 1 .112-.112Z" style="stroke-width:.015625"/></svg>
|
Before Width: | Height: | Size: 1.3 KiB |
20
package.json
@@ -66,15 +66,15 @@
|
||||
"devDependencies": {
|
||||
"@joplin/utils": "~2.11",
|
||||
"@seiyab/eslint-plugin-react-hooks": "4.5.1-beta.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.48.2",
|
||||
"@typescript-eslint/parser": "5.48.2",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.0",
|
||||
"@typescript-eslint/parser": "5.59.0",
|
||||
"cspell": "5.21.2",
|
||||
"eslint": "8.31.0",
|
||||
"eslint-interactive": "10.3.0",
|
||||
"eslint-plugin-import": "2.27.4",
|
||||
"eslint": "8.39.0",
|
||||
"eslint-interactive": "10.7.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-jest": "27.2.1",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "7.32.0",
|
||||
"eslint-plugin-react": "7.32.2",
|
||||
"execa": "5.1.1",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "8.1.0",
|
||||
@@ -84,20 +84,18 @@
|
||||
"lint-staged": "13.2.2",
|
||||
"madge": "6.0.0",
|
||||
"npm-package-json-lint": "6.4.0",
|
||||
"svg2vectordrawable": "^2.9.1",
|
||||
"typedoc": "0.17.8",
|
||||
"typescript": "4.9.4"
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/fs-extra": "11.0.1",
|
||||
"http-server": "14.1.1",
|
||||
"node-gyp": "9.3.1",
|
||||
"nodemon": "2.0.22"
|
||||
},
|
||||
"packageManager": "yarn@3.3.1",
|
||||
"packageManager": "yarn@3.5.0",
|
||||
"resolutions": {
|
||||
"react-native-camera@4.2.1": "patch:react-native-camera@npm%3A4.2.1#./.yarn/patches/react-native-camera-npm-4.2.1-24b2600a7e.patch",
|
||||
"rn-fetch-blob@0.12.0": "patch:rn-fetch-blob@npm%3A0.12.0#./.yarn/patches/rn-fetch-blob-npm-0.12.0-cf02e3c544.patch",
|
||||
"react-native-vosk@0.1.12": "patch:react-native-vosk@npm%3A0.1.12#./.yarn/patches/react-native-vosk-npm-0.1.12-76b1caaae8.patch"
|
||||
}
|
||||
}
|
||||
|
@@ -71,13 +71,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.11",
|
||||
"@types/fs-extra": "9.0.13",
|
||||
"@types/jest": "29.2.6",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/fs-extra": "11.0.1",
|
||||
"@types/jest": "29.5.1",
|
||||
"@types/node": "18.15.13",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.4.3",
|
||||
"jest": "29.5.0",
|
||||
"temp": "0.9.4",
|
||||
"typescript": "4.9.4"
|
||||
"typescript": "5.0.2"
|
||||
}
|
||||
}
|
||||
|
1
packages/app-cli/tests/md_to_html/sanitize_17.html
Normal file
@@ -0,0 +1 @@
|
||||
<a href="#top" class="jop-noMdConv">⬆️</a>
|
1
packages/app-cli/tests/md_to_html/sanitize_17.md
Normal file
@@ -0,0 +1 @@
|
||||
<a href="#top">⬆️</a>
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -20,3 +20,4 @@ cd "$SCRIPT_DIR/withExternalModules/" && yo joplin --update --skip-install --sil
|
||||
cd "$SCRIPT_DIR/post_messages/" && yo joplin --update --skip-install --silent
|
||||
cd "$SCRIPT_DIR/nativeModule/" && yo joplin --update --skip-install --silent
|
||||
cd "$SCRIPT_DIR/external_assets/" && yo joplin --update --skip-install --silent
|
||||
cd "$SCRIPT_DIR/user_data/" && yo joplin --update --skip-install --silent
|
||||
|
3
packages/app-cli/tests/support/plugins/user_data/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
node_modules/
|
||||
publish/
|
@@ -0,0 +1,8 @@
|
||||
*.md
|
||||
!README.md
|
||||
/*.jpl
|
||||
/api
|
||||
/src
|
||||
/dist
|
||||
tsconfig.json
|
||||
webpack.config.js
|
@@ -0,0 +1,72 @@
|
||||
# generator-joplin
|
||||
|
||||
Scaffolds out a new Joplin plugin
|
||||
|
||||
## Installation
|
||||
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
Then generate your new project:
|
||||
|
||||
```bash
|
||||
yo joplin
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To test the generator for development purposes, follow the instructions there: https://yeoman.io/authoring/#running-the-generator
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
## Structure
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
The file `/plugin.config.json` could also be useful if you intend to use [external scripts](#external-script-files), such as content scripts or webview scripts.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Publishing the plugin
|
||||
|
||||
To publish the plugin, add it to npmjs.com by running `npm publish`. Later on, a script will pick up your plugin and add it automatically to the Joplin plugin repository as long as the package satisfies these conditions:
|
||||
|
||||
- In `package.json`, the name starts with "joplin-plugin-". For example, "joplin-plugin-toc".
|
||||
- In `package.json`, the keywords include "joplin-plugin".
|
||||
- In the `publish/` directory, there should be a .jpl and .json file (which are built by `npm run dist`)
|
||||
|
||||
In general all this is done automatically by the plugin generator, which will set the name and keywords of package.json, and will put the right files in the "publish" directory. But if something doesn't work and your plugin doesn't appear in the repository, double-check the above conditions.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `npm run update`.
|
||||
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
||||
|
||||
## External script files
|
||||
|
||||
By default, the compiler (webpack) is going to compile `src/index.ts` only (as well as any file it imports), and any other file will simply be copied to the plugin package. In some cases this is sufficient, however if you have [content scripts](https://joplinapp.org/api/references/plugin_api/classes/joplincontentscripts.html) or [webview scripts](https://joplinapp.org/api/references/plugin_api/classes/joplinviewspanels.html#addscript) you might want to compile them too, in particular in these two cases:
|
||||
|
||||
- The script is a TypeScript file - in which case it has to be compiled to JavaScript.
|
||||
|
||||
- The script requires modules you've added to package.json. In that case, the script, whether JS or TS, must be compiled so that the dependencies are bundled with the JPL file.
|
||||
|
||||
To get such an external script file to compile, you need to add it to the `extraScripts` array in `plugin.config.json`. The path you add should be relative to /src. For example, if you have a file in "/src/webviews/index.ts", the path should be set to "webviews/index.ts". Once compiled, the file will always be named with a .js extension. So you will get "webviews/index.js" in the plugin package, and that's the path you should use to reference the file.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Laurent Cozic
|
24
packages/app-cli/tests/support/plugins/user_data/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Joplin Plugin
|
||||
|
||||
This is a template to create a new Joplin plugin.
|
||||
|
||||
The main two files you will want to look at are:
|
||||
|
||||
- `/src/index.ts`, which contains the entry point for the plugin source code.
|
||||
- `/src/manifest.json`, which is the plugin manifest. It contains information such as the plugin a name, version, etc.
|
||||
|
||||
## Building the plugin
|
||||
|
||||
The plugin is built using Webpack, which creates the compiled code in `/dist`. A JPL archive will also be created at the root, which can use to distribute the plugin.
|
||||
|
||||
To build the plugin, simply run `npm run dist`.
|
||||
|
||||
The project is setup to use TypeScript, although you can change the configuration to use plain JavaScript.
|
||||
|
||||
## Updating the plugin framework
|
||||
|
||||
To update the plugin framework, run `npm run update`.
|
||||
|
||||
In general this command tries to do the right thing - in particular it's going to merge the changes in package.json and .gitignore instead of overwriting. It will also leave "/src" as well as README.md untouched.
|
||||
|
||||
The file that may cause problem is "webpack.config.js" because it's going to be overwritten. For that reason, if you want to change it, consider creating a separate JavaScript file and include it in webpack.config.js. That way, when you update, you only have to restore the line that include your file.
|
14
packages/app-cli/tests/support/plugins/user_data/api/Global.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
import Plugin from '../Plugin';
|
||||
import Joplin from './Joplin';
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export default class Global {
|
||||
private joplin_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
get joplin(): Joplin;
|
||||
get process(): any;
|
||||
}
|
68
packages/app-cli/tests/support/plugins/user_data/api/Joplin.d.ts
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import Plugin from '../Plugin';
|
||||
import JoplinData from './JoplinData';
|
||||
import JoplinPlugins from './JoplinPlugins';
|
||||
import JoplinWorkspace from './JoplinWorkspace';
|
||||
import JoplinFilters from './JoplinFilters';
|
||||
import JoplinCommands from './JoplinCommands';
|
||||
import JoplinViews from './JoplinViews';
|
||||
import JoplinInterop from './JoplinInterop';
|
||||
import JoplinSettings from './JoplinSettings';
|
||||
import JoplinContentScripts from './JoplinContentScripts';
|
||||
import JoplinClipboard from './JoplinClipboard';
|
||||
import JoplinWindow from './JoplinWindow';
|
||||
/**
|
||||
* This is the main entry point to the Joplin API. You can access various services using the provided accessors.
|
||||
*
|
||||
* The API is now relatively stable and in general maintaining backward compatibility is a top priority, so you shouldn't except much breakages.
|
||||
*
|
||||
* If a breaking change ever becomes needed, best effort will be done to:
|
||||
*
|
||||
* - Deprecate features instead of removing them, so as to give you time to fix the issue;
|
||||
* - Document breaking changes in the changelog;
|
||||
*
|
||||
* So if you are developing a plugin, please keep an eye on the changelog as everything will be in there with information about how to update your code.
|
||||
*/
|
||||
export default class Joplin {
|
||||
private data_;
|
||||
private plugins_;
|
||||
private workspace_;
|
||||
private filters_;
|
||||
private commands_;
|
||||
private views_;
|
||||
private interop_;
|
||||
private settings_;
|
||||
private contentScripts_;
|
||||
private clipboard_;
|
||||
private window_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
get data(): JoplinData;
|
||||
get clipboard(): JoplinClipboard;
|
||||
get window(): JoplinWindow;
|
||||
get plugins(): JoplinPlugins;
|
||||
get workspace(): JoplinWorkspace;
|
||||
get contentScripts(): JoplinContentScripts;
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Not sure if it's the best way to hook into the app
|
||||
* so for now disable filters.
|
||||
*/
|
||||
get filters(): JoplinFilters;
|
||||
get commands(): JoplinCommands;
|
||||
get views(): JoplinViews;
|
||||
get interop(): JoplinInterop;
|
||||
get settings(): JoplinSettings;
|
||||
/**
|
||||
* It is not possible to bundle native packages with a plugin, because they
|
||||
* need to work cross-platforms. Instead access to certain useful native
|
||||
* packages is provided using this function.
|
||||
*
|
||||
* Currently these packages are available:
|
||||
*
|
||||
* - [sqlite3](https://www.npmjs.com/package/sqlite3)
|
||||
* - [fs-extra](https://www.npmjs.com/package/fs-extra)
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/nativeModule)
|
||||
*/
|
||||
require(_path: string): any;
|
||||
}
|
23
packages/app-cli/tests/support/plugins/user_data/api/JoplinClipboard.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
export default class JoplinClipboard {
|
||||
private electronClipboard_;
|
||||
private electronNativeImage_;
|
||||
constructor(electronClipboard: any, electronNativeImage: any);
|
||||
readText(): Promise<string>;
|
||||
writeText(text: string): Promise<void>;
|
||||
readHtml(): Promise<string>;
|
||||
writeHtml(html: string): Promise<void>;
|
||||
/**
|
||||
* Returns the image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
|
||||
*/
|
||||
readImage(): Promise<string>;
|
||||
/**
|
||||
* Takes an image in [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) format.
|
||||
*/
|
||||
writeImage(dataUrl: string): Promise<void>;
|
||||
/**
|
||||
* Returns the list available formats (mime types).
|
||||
*
|
||||
* For example [ 'text/plain', 'text/html' ]
|
||||
*/
|
||||
availableFormats(): Promise<string[]>;
|
||||
}
|
89
packages/app-cli/tests/support/plugins/user_data/api/JoplinCommands.d.ts
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Command } from './types';
|
||||
/**
|
||||
* This class allows executing or registering new Joplin commands. Commands
|
||||
* can be executed or associated with
|
||||
* {@link JoplinViewsToolbarButtons | toolbar buttons} or
|
||||
* {@link JoplinViewsMenuItems | menu items}.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*
|
||||
* ## Executing Joplin's internal commands
|
||||
*
|
||||
* It is also possible to execute internal Joplin's commands which, as of
|
||||
* now, are not well documented. You can find the list directly on GitHub
|
||||
* though at the following locations:
|
||||
*
|
||||
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
|
||||
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
|
||||
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
|
||||
*
|
||||
* To view what arguments are supported, you can open any of these files
|
||||
* and look at the `execute()` command.
|
||||
*
|
||||
* ## Executing editor commands
|
||||
*
|
||||
* There might be a situation where you want to invoke editor commands
|
||||
* without using a {@link JoplinContentScripts | contentScript}. For this
|
||||
* reason Joplin provides the built in `editor.execCommand` command.
|
||||
*
|
||||
* `editor.execCommand` should work with any core command in both the
|
||||
* [CodeMirror](https://codemirror.net/doc/manual.html#execCommand) and
|
||||
* [TinyMCE](https://www.tiny.cloud/docs/api/tinymce/tinymce.editorcommands/#execcommand) editors,
|
||||
* as well as most functions calls directly on a CodeMirror editor object (extensions).
|
||||
*
|
||||
* * [CodeMirror commands](https://codemirror.net/doc/manual.html#commands)
|
||||
* * [TinyMCE core editor commands](https://www.tiny.cloud/docs/advanced/editor-command-identifiers/#coreeditorcommands)
|
||||
*
|
||||
* `editor.execCommand` supports adding arguments for the commands.
|
||||
*
|
||||
* ```typescript
|
||||
* await joplin.commands.execute('editor.execCommand', {
|
||||
* name: 'madeUpCommand', // CodeMirror and TinyMCE
|
||||
* args: [], // CodeMirror and TinyMCE
|
||||
* ui: false, // TinyMCE only
|
||||
* value: '', // TinyMCE only
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* [View the example using the CodeMirror editor](https://github.com/laurent22/joplin/blob/dev/packages/app-cli/tests/support/plugins/codemirror_content_script/src/index.ts)
|
||||
*
|
||||
*/
|
||||
export default class JoplinCommands {
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Executes the given
|
||||
* command.
|
||||
*
|
||||
* The command can take any number of arguments, and the supported
|
||||
* arguments will vary based on the command. For custom commands, this
|
||||
* is the `args` passed to the `execute()` function. For built-in
|
||||
* commands, you can find the supported arguments by checking the links
|
||||
* above.
|
||||
*
|
||||
* ```typescript
|
||||
* // Create a new note in the current notebook:
|
||||
* await joplin.commands.execute('newNote');
|
||||
*
|
||||
* // Create a new sub-notebook under the provided notebook
|
||||
* // Note: internally, notebooks are called "folders".
|
||||
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
||||
* ```
|
||||
*/
|
||||
execute(commandName: string, ...args: any[]): Promise<any | void>;
|
||||
/**
|
||||
* <span class="platform-desktop">desktop</span> Registers a new command.
|
||||
*
|
||||
* ```typescript
|
||||
* // Register a new commmand called "testCommand1"
|
||||
*
|
||||
* await joplin.commands.register({
|
||||
* name: 'testCommand1',
|
||||
* label: 'My Test Command 1',
|
||||
* iconName: 'fas fa-music',
|
||||
* execute: () => {
|
||||
* alert('Testing plugin command 1');
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
register(command: Command): Promise<void>;
|
||||
}
|
40
packages/app-cli/tests/support/plugins/user_data/api/JoplinContentScripts.d.ts
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ContentScriptType } from './types';
|
||||
export default class JoplinContentScripts {
|
||||
private plugin;
|
||||
constructor(plugin: Plugin);
|
||||
/**
|
||||
* Registers a new content script. Unlike regular plugin code, which runs in
|
||||
* a separate process, content scripts run within the main process code and
|
||||
* thus allow improved performances and more customisations in specific
|
||||
* cases. It can be used for example to load a Markdown or editor plugin.
|
||||
*
|
||||
* Note that registering a content script in itself will do nothing - it
|
||||
* will only be loaded in specific cases by the relevant app modules (eg.
|
||||
* the Markdown renderer or the code editor). So it is not a way to inject
|
||||
* and run arbitrary code in the app, which for safety and performance
|
||||
* reasons is not supported.
|
||||
*
|
||||
* The plugin generator provides a way to build any content script you might
|
||||
* want to package as well as its dependencies. See the [Plugin Generator
|
||||
* doc](https://github.com/laurent22/joplin/blob/dev/packages/generator-joplin/README.md)
|
||||
* for more information.
|
||||
*
|
||||
* * [View the renderer demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||
* * [View the editor demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||
*
|
||||
* See also the [postMessage demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
|
||||
*
|
||||
* @param type Defines how the script will be used. See the type definition for more information about each supported type.
|
||||
* @param id A unique ID for the content script.
|
||||
* @param scriptPath Must be a path relative to the plugin main script. For example, if your file content_script.js is next to your index.ts file, you would set `scriptPath` to `"./content_script.js`.
|
||||
*/
|
||||
register(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Listens to a messages sent from the content script using postMessage().
|
||||
* See {@link ContentScriptType} for more information as well as the
|
||||
* [postMessage
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages)
|
||||
*/
|
||||
onMessage(contentScriptId: string, callback: any): Promise<void>;
|
||||
}
|
50
packages/app-cli/tests/support/plugins/user_data/api/JoplinData.d.ts
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import { ModelType } from '../../../BaseModel';
|
||||
import { Path } from './types';
|
||||
/**
|
||||
* This module provides access to the Joplin data API: https://joplinapp.org/api/references/rest_api/
|
||||
* This is the main way to retrieve data, such as notes, notebooks, tags, etc.
|
||||
* or to update them or delete them.
|
||||
*
|
||||
* This is also what you would use to search notes, via the `search` endpoint.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/simple)
|
||||
*
|
||||
* In general you would use the methods in this class as if you were using a REST API. There are four methods that map to GET, POST, PUT and DELETE calls.
|
||||
* And each method takes these parameters:
|
||||
*
|
||||
* * `path`: This is an array that represents the path to the resource in the form `["resouceName", "resourceId", "resourceLink"]` (eg. ["tags", ":id", "notes"]). The "resources" segment is the name of the resources you want to access (eg. "notes", "folders", etc.). If not followed by anything, it will refer to all the resources in that collection. The optional "resourceId" points to a particular resources within the collection. Finally, an optional "link" can be present, which links the resource to a collection of resources. This can be used in the API for example to retrieve all the notes associated with a tag.
|
||||
* * `query`: (Optional) The query parameters. In a URL, this is the part after the question mark "?". In this case, it should be an object with key/value pairs.
|
||||
* * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
|
||||
* * `files`: (Optional) Used to create new resources and associate them with files.
|
||||
*
|
||||
* Please refer to the [Joplin API documentation](https://joplinapp.org/api/references/rest_api/) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```typescript
|
||||
* // Get a note ID, title and body
|
||||
* const noteId = 'some_note_id';
|
||||
* const note = await joplin.data.get(['notes', noteId], { fields: ['id', 'title', 'body'] });
|
||||
*
|
||||
* // Get all folders
|
||||
* const folders = await joplin.data.get(['folders']);
|
||||
*
|
||||
* // Set the note body
|
||||
* await joplin.data.put(['notes', noteId], null, { body: "New note body" });
|
||||
*
|
||||
* // Create a new note under one of the folders
|
||||
* await joplin.data.post(['notes'], null, { body: "my new note", title: "some title", parent_id: folders[0].id });
|
||||
* ```
|
||||
*/
|
||||
export default class JoplinData {
|
||||
private api_;
|
||||
private pathSegmentRegex_;
|
||||
private serializeApiBody;
|
||||
private pathToString;
|
||||
get(path: Path, query?: any): Promise<any>;
|
||||
post(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
|
||||
put(path: Path, query?: any, body?: any, files?: any[]): Promise<any>;
|
||||
delete(path: Path, query?: any): Promise<any>;
|
||||
itemType(itemId: string): Promise<ModelType>;
|
||||
resourcePath(resourceId: string): Promise<string>;
|
||||
}
|
10
packages/app-cli/tests/support/plugins/user_data/api/JoplinFilters.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* Not sure if it's the best way to hook into the app
|
||||
* so for now disable filters.
|
||||
*/
|
||||
export default class JoplinFilters {
|
||||
on(name: string, callback: Function): Promise<void>;
|
||||
off(name: string, callback: Function): Promise<void>;
|
||||
}
|
17
packages/app-cli/tests/support/plugins/user_data/api/JoplinInterop.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ExportModule, ImportModule } from './types';
|
||||
/**
|
||||
* Provides a way to create modules to import external data into Joplin or to export notes into any arbitrary format.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export)
|
||||
*
|
||||
* To implement an import or export module, you would simply define an object with various event handlers that are called
|
||||
* by the application during the import/export process.
|
||||
*
|
||||
* See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
|
||||
*
|
||||
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
|
||||
*/
|
||||
export default class JoplinInterop {
|
||||
registerExportModule(module: ExportModule): Promise<void>;
|
||||
registerImportModule(module: ImportModule): Promise<void>;
|
||||
}
|
43
packages/app-cli/tests/support/plugins/user_data/api/JoplinPlugins.d.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ContentScriptType, Script } from './types';
|
||||
/**
|
||||
* This class provides access to plugin-related features.
|
||||
*/
|
||||
export default class JoplinPlugins {
|
||||
private plugin;
|
||||
constructor(plugin: Plugin);
|
||||
/**
|
||||
* Registers a new plugin. This is the entry point when creating a plugin. You should pass a simple object with an `onStart` method to it.
|
||||
* That `onStart` method will be executed as soon as the plugin is loaded.
|
||||
*
|
||||
* ```typescript
|
||||
* joplin.plugins.register({
|
||||
* onStart: async function() {
|
||||
* // Run your plugin code here
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
register(script: Script): Promise<void>;
|
||||
/**
|
||||
* @deprecated Use joplin.contentScripts.register()
|
||||
*/
|
||||
registerContentScript(type: ContentScriptType, id: string, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Gets the plugin own data directory path. Use this to store any
|
||||
* plugin-related data. Unlike [[installationDir]], any data stored here
|
||||
* will be persisted.
|
||||
*/
|
||||
dataDir(): Promise<string>;
|
||||
/**
|
||||
* Gets the plugin installation directory. This can be used to access any
|
||||
* asset that was packaged with the plugin. This directory should be
|
||||
* considered read-only because any data you store here might be deleted or
|
||||
* re-created at any time. To store new persistent data, use [[dataDir]].
|
||||
*/
|
||||
installationDir(): Promise<string>;
|
||||
/**
|
||||
* @deprecated Use joplin.require()
|
||||
*/
|
||||
require(_path: string): any;
|
||||
}
|
64
packages/app-cli/tests/support/plugins/user_data/api/JoplinSettings.d.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { SettingItem, SettingSection } from './types';
|
||||
export interface ChangeEvent {
|
||||
/**
|
||||
* Setting keys that have been changed
|
||||
*/
|
||||
keys: string[];
|
||||
}
|
||||
export declare type ChangeHandler = (event: ChangeEvent) => void;
|
||||
/**
|
||||
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
|
||||
*
|
||||
* Settings are essentially key/value pairs.
|
||||
*
|
||||
* Note: Currently this API does **not** provide access to Joplin's built-in settings. This is by design as plugins that modify user settings could give unexpected results
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/settings)
|
||||
*/
|
||||
export default class JoplinSettings {
|
||||
private plugin_;
|
||||
constructor(plugin: Plugin);
|
||||
private get keyPrefix();
|
||||
private namespacedKey;
|
||||
/**
|
||||
* Registers new settings.
|
||||
* Note that registering a setting item is dynamic and will be gone next time Joplin starts.
|
||||
* What it means is that you need to register the setting every time the plugin starts (for example in the onStart event).
|
||||
* The setting value however will be preserved from one launch to the next so there is no risk that it will be lost even if for some
|
||||
* reason the plugin fails to start at some point.
|
||||
*/
|
||||
registerSettings(settings: Record<string, SettingItem>): Promise<void>;
|
||||
/**
|
||||
* @deprecated Use joplin.settings.registerSettings()
|
||||
*
|
||||
* Registers a new setting.
|
||||
*/
|
||||
registerSetting(key: string, settingItem: SettingItem): Promise<void>;
|
||||
/**
|
||||
* Registers a new setting section. Like for registerSetting, it is dynamic and needs to be done every time the plugin starts.
|
||||
*/
|
||||
registerSection(name: string, section: SettingSection): Promise<void>;
|
||||
/**
|
||||
* Gets a setting value (only applies to setting you registered from your plugin)
|
||||
*/
|
||||
value(key: string): Promise<any>;
|
||||
/**
|
||||
* Sets a setting value (only applies to setting you registered from your plugin)
|
||||
*/
|
||||
setValue(key: string, value: any): Promise<void>;
|
||||
/**
|
||||
* Gets a global setting value, including app-specific settings and those set by other plugins.
|
||||
*
|
||||
* The list of available settings is not documented yet, but can be found by looking at the source code:
|
||||
*
|
||||
* https://github.com/laurent22/joplin/blob/dev/packages/lib/models/Setting.ts#L142
|
||||
*/
|
||||
globalValue(key: string): Promise<any>;
|
||||
/**
|
||||
* Called when one or multiple settings of your plugin have been changed.
|
||||
* - For performance reasons, this event is triggered with a delay.
|
||||
* - You will only get events for your own plugin settings.
|
||||
*/
|
||||
onChange(handler: ChangeHandler): Promise<void>;
|
||||
}
|
28
packages/app-cli/tests/support/plugins/user_data/api/JoplinViews.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import Plugin from '../Plugin';
|
||||
import JoplinViewsDialogs from './JoplinViewsDialogs';
|
||||
import JoplinViewsMenuItems from './JoplinViewsMenuItems';
|
||||
import JoplinViewsMenus from './JoplinViewsMenus';
|
||||
import JoplinViewsToolbarButtons from './JoplinViewsToolbarButtons';
|
||||
import JoplinViewsPanels from './JoplinViewsPanels';
|
||||
/**
|
||||
* This namespace provides access to view-related services.
|
||||
*
|
||||
* All view services provide a `create()` method which you would use to create the view object, whether it's a dialog, a toolbar button or a menu item.
|
||||
* In some cases, the `create()` method will return a [[ViewHandle]], which you would use to act on the view, for example to set certain properties or call some methods.
|
||||
*/
|
||||
export default class JoplinViews {
|
||||
private store;
|
||||
private plugin;
|
||||
private dialogs_;
|
||||
private panels_;
|
||||
private menuItems_;
|
||||
private menus_;
|
||||
private toolbarButtons_;
|
||||
private implementation_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
get dialogs(): JoplinViewsDialogs;
|
||||
get panels(): JoplinViewsPanels;
|
||||
get menuItems(): JoplinViewsMenuItems;
|
||||
get menus(): JoplinViewsMenus;
|
||||
get toolbarButtons(): JoplinViewsToolbarButtons;
|
||||
}
|
68
packages/app-cli/tests/support/plugins/user_data/api/JoplinViewsDialogs.d.ts
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ButtonSpec, ViewHandle, DialogResult } from './types';
|
||||
/**
|
||||
* Allows creating and managing dialogs. A dialog is modal window that
|
||||
* contains a webview and a row of buttons. You can update the
|
||||
* webview using the `setHtml` method. Dialogs are hidden by default and
|
||||
* you need to call `open()` to open them. Once the user clicks on a
|
||||
* button, the `open` call will return an object indicating what button was
|
||||
* clicked on.
|
||||
*
|
||||
* ## Retrieving form values
|
||||
*
|
||||
* If your HTML content included one or more forms, a `formData` object
|
||||
* will also be included with the key/value for each form.
|
||||
*
|
||||
* ## Special button IDs
|
||||
*
|
||||
* The following buttons IDs have a special meaning:
|
||||
*
|
||||
* - `ok`, `yes`, `submit`, `confirm`: They are considered "submit" buttons
|
||||
* - `cancel`, `no`, `reject`: They are considered "dismiss" buttons
|
||||
*
|
||||
* This information is used by the application to determine what action
|
||||
* should be done when the user presses "Enter" or "Escape" within the
|
||||
* dialog. If they press "Enter", the first "submit" button will be
|
||||
* automatically clicked. If they press "Escape" the first "dismiss" button
|
||||
* will be automatically clicked.
|
||||
*
|
||||
* [View the demo
|
||||
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
|
||||
*/
|
||||
export default class JoplinViewsDialogs {
|
||||
private store;
|
||||
private plugin;
|
||||
private implementation_;
|
||||
constructor(implementation: any, plugin: Plugin, store: any);
|
||||
private controller;
|
||||
/**
|
||||
* Creates a new dialog
|
||||
*/
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Displays a message box with OK/Cancel buttons. Returns the button index that was clicked - "0" for OK and "1" for "Cancel"
|
||||
*/
|
||||
showMessageBox(message: string): Promise<number>;
|
||||
/**
|
||||
* Sets the dialog HTML content
|
||||
*/
|
||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||
/**
|
||||
* Adds and loads a new JS or CSS files into the dialog.
|
||||
*/
|
||||
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Sets the dialog buttons.
|
||||
*/
|
||||
setButtons(handle: ViewHandle, buttons: ButtonSpec[]): Promise<ButtonSpec[]>;
|
||||
/**
|
||||
* Opens the dialog
|
||||
*/
|
||||
open(handle: ViewHandle): Promise<DialogResult>;
|
||||
/**
|
||||
* Toggle on whether to fit the dialog size to the content or not.
|
||||
* When set to false, the dialog is set to 90vw and 80vh
|
||||
* @default true
|
||||
*/
|
||||
setFitToContent(handle: ViewHandle, status: boolean): Promise<boolean>;
|
||||
}
|
16
packages/app-cli/tests/support/plugins/user_data/api/JoplinViewsMenuItems.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { CreateMenuItemOptions, MenuItemLocation } from './types';
|
||||
import Plugin from '../Plugin';
|
||||
/**
|
||||
* Allows creating and managing menu items.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*/
|
||||
export default class JoplinViewsMenuItems {
|
||||
private store;
|
||||
private plugin;
|
||||
constructor(plugin: Plugin, store: any);
|
||||
/**
|
||||
* Creates a new menu item and associate it with the given command. You can specify under which menu the item should appear using the `location` parameter.
|
||||
*/
|
||||
create(id: string, commandName: string, location?: MenuItemLocation, options?: CreateMenuItemOptions): Promise<void>;
|
||||
}
|
18
packages/app-cli/tests/support/plugins/user_data/api/JoplinViewsMenus.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { MenuItem, MenuItemLocation } from './types';
|
||||
import Plugin from '../Plugin';
|
||||
/**
|
||||
* Allows creating menus.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/menu)
|
||||
*/
|
||||
export default class JoplinViewsMenus {
|
||||
private store;
|
||||
private plugin;
|
||||
constructor(plugin: Plugin, store: any);
|
||||
private registerCommandAccelerators;
|
||||
/**
|
||||
* Creates a new menu from the provided menu items and place it at the given location. As of now, it is only possible to place the
|
||||
* menu as a sub-menu of the application build-in menus.
|
||||
*/
|
||||
create(id: string, label: string, menuItems: MenuItem[], location?: MenuItemLocation): Promise<void>;
|
||||
}
|
78
packages/app-cli/tests/support/plugins/user_data/api/JoplinViewsPanels.d.ts
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
import Plugin from '../Plugin';
|
||||
import { ViewHandle } from './types';
|
||||
/**
|
||||
* Allows creating and managing view panels. View panels currently are
|
||||
* displayed at the right of the sidebar and allows displaying any HTML
|
||||
* content (within a webview) and update it in real-time. For example it
|
||||
* could be used to display a table of content for the active note, or
|
||||
* display various metadata or graph.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/toc)
|
||||
*/
|
||||
export default class JoplinViewsPanels {
|
||||
private store;
|
||||
private plugin;
|
||||
constructor(plugin: Plugin, store: any);
|
||||
private controller;
|
||||
/**
|
||||
* Creates a new panel
|
||||
*/
|
||||
create(id: string): Promise<ViewHandle>;
|
||||
/**
|
||||
* Sets the panel webview HTML
|
||||
*/
|
||||
setHtml(handle: ViewHandle, html: string): Promise<string>;
|
||||
/**
|
||||
* Adds and loads a new JS or CSS files into the panel.
|
||||
*/
|
||||
addScript(handle: ViewHandle, scriptPath: string): Promise<void>;
|
||||
/**
|
||||
* Called when a message is sent from the webview (using postMessage).
|
||||
*
|
||||
* To post a message from the webview to the plugin use:
|
||||
*
|
||||
* ```javascript
|
||||
* const response = await webviewApi.postMessage(message);
|
||||
* ```
|
||||
*
|
||||
* - `message` can be any JavaScript object, string or number
|
||||
* - `response` is whatever was returned by the `onMessage` handler
|
||||
*
|
||||
* Using this mechanism, you can have two-way communication between the
|
||||
* plugin and webview.
|
||||
*
|
||||
* See the [postMessage
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages) for more details.
|
||||
*
|
||||
*/
|
||||
onMessage(handle: ViewHandle, callback: Function): Promise<void>;
|
||||
/**
|
||||
* Sends a message to the webview.
|
||||
*
|
||||
* The webview must have registered a message handler prior, otherwise the message is ignored. Use;
|
||||
*
|
||||
* ```javascript
|
||||
* webviewApi.onMessage((message) => { ... });
|
||||
* ```
|
||||
*
|
||||
* - `message` can be any JavaScript object, string or number
|
||||
*
|
||||
* The view API may have only one onMessage handler defined.
|
||||
* This method is fire and forget so no response is returned.
|
||||
*
|
||||
* It is particularly useful when the webview needs to react to events emitted by the plugin or the joplin api.
|
||||
*/
|
||||
postMessage(handle: ViewHandle, message: any): void;
|
||||
/**
|
||||
* Shows the panel
|
||||
*/
|
||||
show(handle: ViewHandle, show?: boolean): Promise<void>;
|
||||
/**
|
||||
* Hides the panel
|
||||
*/
|
||||
hide(handle: ViewHandle): Promise<void>;
|
||||
/**
|
||||
* Tells whether the panel is visible or not
|
||||
*/
|
||||
visible(handle: ViewHandle): Promise<boolean>;
|
||||
}
|
16
packages/app-cli/tests/support/plugins/user_data/api/JoplinViewsToolbarButtons.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ToolbarButtonLocation } from './types';
|
||||
import Plugin from '../Plugin';
|
||||
/**
|
||||
* Allows creating and managing toolbar buttons.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/register_command)
|
||||
*/
|
||||
export default class JoplinViewsToolbarButtons {
|
||||
private store;
|
||||
private plugin;
|
||||
constructor(plugin: Plugin, store: any);
|
||||
/**
|
||||
* Creates a new toolbar button and associate it with the given command.
|
||||
*/
|
||||
create(id: string, commandName: string, location: ToolbarButtonLocation): Promise<void>;
|
||||
}
|
24
packages/app-cli/tests/support/plugins/user_data/api/JoplinWindow.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import Plugin from '../Plugin';
|
||||
export interface Implementation {
|
||||
injectCustomStyles(elementId: string, cssFilePath: string): Promise<void>;
|
||||
}
|
||||
export default class JoplinWindow {
|
||||
private plugin_;
|
||||
private store_;
|
||||
private implementation_;
|
||||
constructor(implementation: Implementation, plugin: Plugin, store: any);
|
||||
/**
|
||||
* Loads a chrome CSS file. It will apply to the window UI elements, except
|
||||
* for the note viewer. It is the same as the "Custom stylesheet for
|
||||
* Joplin-wide app styles" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
|
||||
* for an example.
|
||||
*/
|
||||
loadChromeCssFile(filePath: string): Promise<void>;
|
||||
/**
|
||||
* Loads a note CSS file. It will apply to the note viewer, as well as any
|
||||
* exported or printed note. It is the same as the "Custom stylesheet for
|
||||
* rendered Markdown" setting. See the [Load CSS Demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/load_css)
|
||||
* for an example.
|
||||
*/
|
||||
loadNoteCssFile(filePath: string): Promise<void>;
|
||||
}
|
87
packages/app-cli/tests/support/plugins/user_data/api/JoplinWorkspace.d.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import { FolderEntity } from '../../database/types';
|
||||
import { Disposable, MenuItem } from './types';
|
||||
export interface EditContextMenuFilterObject {
|
||||
items: MenuItem[];
|
||||
}
|
||||
declare type FilterHandler<T> = (object: T) => Promise<void>;
|
||||
declare enum ItemChangeEventType {
|
||||
Create = 1,
|
||||
Update = 2,
|
||||
Delete = 3
|
||||
}
|
||||
interface ItemChangeEvent {
|
||||
id: string;
|
||||
event: ItemChangeEventType;
|
||||
}
|
||||
interface SyncStartEvent {
|
||||
withErrors: boolean;
|
||||
}
|
||||
interface ResourceChangeEvent {
|
||||
id: string;
|
||||
}
|
||||
declare type ItemChangeHandler = (event: ItemChangeEvent) => void;
|
||||
declare type SyncStartHandler = (event: SyncStartEvent) => void;
|
||||
declare type ResourceChangeHandler = (event: ResourceChangeEvent) => void;
|
||||
/**
|
||||
* The workspace service provides access to all the parts of Joplin that
|
||||
* are being worked on - i.e. the currently selected notes or notebooks as
|
||||
* well as various related events, such as when a new note is selected, or
|
||||
* when the note content changes.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins)
|
||||
*/
|
||||
export default class JoplinWorkspace {
|
||||
private store;
|
||||
constructor(store: any);
|
||||
/**
|
||||
* Called when a new note or notes are selected.
|
||||
*/
|
||||
onNoteSelectionChange(callback: Function): Promise<Disposable>;
|
||||
/**
|
||||
* Called when the content of a note changes.
|
||||
* @deprecated Use `onNoteChange()` instead, which is reliably triggered whenever the note content, or any note property changes.
|
||||
*/
|
||||
onNoteContentChange(callback: Function): Promise<void>;
|
||||
/**
|
||||
* Called when the content of the current note changes.
|
||||
*/
|
||||
onNoteChange(handler: ItemChangeHandler): Promise<Disposable>;
|
||||
/**
|
||||
* Called when a resource is changed. Currently this handled will not be
|
||||
* called when a resource is added or deleted.
|
||||
*/
|
||||
onResourceChange(handler: ResourceChangeHandler): Promise<void>;
|
||||
/**
|
||||
* Called when an alarm associated with a to-do is triggered.
|
||||
*/
|
||||
onNoteAlarmTrigger(handler: Function): Promise<Disposable>;
|
||||
/**
|
||||
* Called when the synchronisation process is starting.
|
||||
*/
|
||||
onSyncStart(handler: SyncStartHandler): Promise<Disposable>;
|
||||
/**
|
||||
* Called when the synchronisation process has finished.
|
||||
*/
|
||||
onSyncComplete(callback: Function): Promise<Disposable>;
|
||||
/**
|
||||
* Called just before the editor context menu is about to open. Allows
|
||||
* adding items to it.
|
||||
*/
|
||||
filterEditorContextMenu(handler: FilterHandler<EditContextMenuFilterObject>): void;
|
||||
/**
|
||||
* Gets the currently selected note
|
||||
*/
|
||||
selectedNote(): Promise<any>;
|
||||
/**
|
||||
* Gets the currently selected folder. In some cases, for example during
|
||||
* search or when viewing a tag, no folder is actually selected in the user
|
||||
* interface. In that case, that function would return the last selected
|
||||
* folder.
|
||||
*/
|
||||
selectedFolder(): Promise<FolderEntity>;
|
||||
/**
|
||||
* Gets the IDs of the selected notes (can be zero, one, or many). Use the data API to retrieve information about these notes.
|
||||
*/
|
||||
selectedNoteIds(): Promise<string[]>;
|
||||
}
|
||||
export {};
|
@@ -0,0 +1,5 @@
|
||||
import type Joplin from './Joplin';
|
||||
|
||||
declare const joplin: Joplin;
|
||||
|
||||
export default joplin;
|
674
packages/app-cli/tests/support/plugins/user_data/api/types.ts
Normal file
@@ -0,0 +1,674 @@
|
||||
// =================================================================
|
||||
// Command API types
|
||||
// =================================================================
|
||||
|
||||
export interface Command {
|
||||
/**
|
||||
* Name of command - must be globally unique
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Label to be displayed on menu items or keyboard shortcut editor for example.
|
||||
* If it is missing, it's assumed it's a private command, to be called programmatically only.
|
||||
* In that case the command will not appear in the shortcut editor or command panel, and logically
|
||||
* should not be used as a menu item.
|
||||
*/
|
||||
label?: string;
|
||||
|
||||
/**
|
||||
* Icon to be used on toolbar buttons for example
|
||||
*/
|
||||
iconName?: string;
|
||||
|
||||
/**
|
||||
* Code to be ran when the command is executed. It may return a result.
|
||||
*/
|
||||
execute(...args: any[]): Promise<any | void>;
|
||||
|
||||
/**
|
||||
* Defines whether the command should be enabled or disabled, which in turns
|
||||
* affects the enabled state of any associated button or menu item.
|
||||
*
|
||||
* The condition should be expressed as a "when-clause" (as in Visual Studio
|
||||
* Code). It's a simple boolean expression that evaluates to `true` or
|
||||
* `false`. It supports the following operators:
|
||||
*
|
||||
* Operator | Symbol | Example
|
||||
* -- | -- | --
|
||||
* Equality | == | "editorType == markdown"
|
||||
* Inequality | != | "currentScreen != config"
|
||||
* Or | \|\| | "noteIsTodo \|\| noteTodoCompleted"
|
||||
* And | && | "oneNoteSelected && !inConflictFolder"
|
||||
*
|
||||
* Joplin, unlike VSCode, also supports parenthesis, which allows creating
|
||||
* more complex expressions such as `cond1 || (cond2 && cond3)`. Only one
|
||||
* level of parenthesis is possible (nested ones aren't supported).
|
||||
*
|
||||
* Currently the supported context variables aren't documented, but you can
|
||||
* find the list below:
|
||||
*
|
||||
* - [Global When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/lib/services/commands/stateToWhenClauseContext.ts)
|
||||
* - [Desktop app When Clauses](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/services/commands/stateToWhenClauseContext.ts)
|
||||
*
|
||||
* Note: Commands are enabled by default unless you use this property.
|
||||
*/
|
||||
enabledCondition?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Interop API types
|
||||
// =================================================================
|
||||
|
||||
export enum FileSystemItem {
|
||||
File = 'file',
|
||||
Directory = 'directory',
|
||||
}
|
||||
|
||||
export enum ImportModuleOutputFormat {
|
||||
Markdown = 'md',
|
||||
Html = 'html',
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to implement a module to export data from Joplin. [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/json_export) for an example.
|
||||
*
|
||||
* In general, all the event handlers you'll need to implement take a `context` object as a first argument. This object will contain the export or import path as well as various optional properties, such as which notes or notebooks need to be exported.
|
||||
*
|
||||
* To get a better sense of what it will contain it can be useful to print it using `console.info(context)`.
|
||||
*/
|
||||
export interface ExportModule {
|
||||
/**
|
||||
* The format to be exported, eg "enex", "jex", "json", etc.
|
||||
*/
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* The description that will appear in the UI, for example in the menu item.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Whether the module will export a single file or multiple files in a directory. It affects the open dialog that will be presented to the user when using your exporter.
|
||||
*/
|
||||
target: FileSystemItem;
|
||||
|
||||
/**
|
||||
* Only applies to single file exporters or importers
|
||||
* It tells whether the format can package multiple notes into one file.
|
||||
* For example JEX or ENEX can, but HTML cannot.
|
||||
*/
|
||||
isNoteArchive: boolean;
|
||||
|
||||
/**
|
||||
* The extensions of the files exported by your module. For example, it is `["htm", "html"]` for the HTML module, and just `["jex"]` for the JEX module.
|
||||
*/
|
||||
fileExtensions?: string[];
|
||||
|
||||
/**
|
||||
* Called when the export process starts.
|
||||
*/
|
||||
onInit(context: ExportContext): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when an item needs to be processed. An "item" can be any Joplin object, such as a note, a folder, a notebook, etc.
|
||||
*/
|
||||
onProcessItem(context: ExportContext, itemType: number, item: any): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when a resource file needs to be exported.
|
||||
*/
|
||||
onProcessResource(context: ExportContext, resource: any, filePath: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Called when the export process is done.
|
||||
*/
|
||||
onClose(context: ExportContext): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ImportModule {
|
||||
/**
|
||||
* The format to be exported, eg "enex", "jex", "json", etc.
|
||||
*/
|
||||
format: string;
|
||||
|
||||
/**
|
||||
* The description that will appear in the UI, for example in the menu item.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Only applies to single file exporters or importers
|
||||
* It tells whether the format can package multiple notes into one file.
|
||||
* For example JEX or ENEX can, but HTML cannot.
|
||||
*/
|
||||
isNoteArchive: boolean;
|
||||
|
||||
/**
|
||||
* The type of sources that are supported by the module. Tells whether the module can import files or directories or both.
|
||||
*/
|
||||
sources: FileSystemItem[];
|
||||
|
||||
/**
|
||||
* Tells the file extensions of the exported files.
|
||||
*/
|
||||
fileExtensions?: string[];
|
||||
|
||||
/**
|
||||
* Tells the type of notes that will be generated, either HTML or Markdown (default).
|
||||
*/
|
||||
outputFormat?: ImportModuleOutputFormat;
|
||||
|
||||
/**
|
||||
* Called when the import process starts. There is only one event handler within which you should import the complete data.
|
||||
*/
|
||||
onExec(context: ImportContext): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ExportOptions {
|
||||
format?: string;
|
||||
path?: string;
|
||||
sourceFolderIds?: string[];
|
||||
sourceNoteIds?: string[];
|
||||
// modulePath?: string;
|
||||
target?: FileSystemItem;
|
||||
}
|
||||
|
||||
export interface ExportContext {
|
||||
destPath: string;
|
||||
options: ExportOptions;
|
||||
|
||||
/**
|
||||
* You can attach your own custom data using this propery - it will then be passed to each event handler, allowing you to keep state from one event to the next.
|
||||
*/
|
||||
userData?: any;
|
||||
}
|
||||
|
||||
export interface ImportContext {
|
||||
sourcePath: string;
|
||||
options: any;
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Misc types
|
||||
// =================================================================
|
||||
|
||||
export interface Script {
|
||||
onStart?(event: any): Promise<void>;
|
||||
}
|
||||
|
||||
export interface Disposable {
|
||||
// dispose():void;
|
||||
}
|
||||
|
||||
export enum ModelType {
|
||||
Note = 1,
|
||||
Folder = 2,
|
||||
Setting = 3,
|
||||
Resource = 4,
|
||||
Tag = 5,
|
||||
NoteTag = 6,
|
||||
Search = 7,
|
||||
Alarm = 8,
|
||||
MasterKey = 9,
|
||||
ItemChange = 10,
|
||||
NoteResource = 11,
|
||||
ResourceLocalState = 12,
|
||||
Revision = 13,
|
||||
Migration = 14,
|
||||
SmartFilter = 15,
|
||||
Command = 16,
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Menu types
|
||||
// =================================================================
|
||||
|
||||
export interface CreateMenuItemOptions {
|
||||
accelerator: string;
|
||||
}
|
||||
|
||||
export enum MenuItemLocation {
|
||||
File = 'file',
|
||||
Edit = 'edit',
|
||||
View = 'view',
|
||||
Note = 'note',
|
||||
Tools = 'tools',
|
||||
Help = 'help',
|
||||
|
||||
/**
|
||||
* @deprecated Do not use - same as NoteListContextMenu
|
||||
*/
|
||||
Context = 'context',
|
||||
|
||||
// If adding an item here, don't forget to update isContextMenuItemLocation()
|
||||
|
||||
/**
|
||||
* When a command is called from the note list context menu, the
|
||||
* command will receive the following arguments:
|
||||
*
|
||||
* - `noteIds:string[]`: IDs of the notes that were right-clicked on.
|
||||
*/
|
||||
NoteListContextMenu = 'noteListContextMenu',
|
||||
|
||||
EditorContextMenu = 'editorContextMenu',
|
||||
|
||||
/**
|
||||
* When a command is called from a folder context menu, the
|
||||
* command will receive the following arguments:
|
||||
*
|
||||
* - `folderId:string`: ID of the folder that was right-clicked on
|
||||
*/
|
||||
FolderContextMenu = 'folderContextMenu',
|
||||
|
||||
/**
|
||||
* When a command is called from a tag context menu, the
|
||||
* command will receive the following arguments:
|
||||
*
|
||||
* - `tagId:string`: ID of the tag that was right-clicked on
|
||||
*/
|
||||
TagContextMenu = 'tagContextMenu',
|
||||
}
|
||||
|
||||
export function isContextMenuItemLocation(location: MenuItemLocation): boolean {
|
||||
return [
|
||||
MenuItemLocation.Context,
|
||||
MenuItemLocation.NoteListContextMenu,
|
||||
MenuItemLocation.EditorContextMenu,
|
||||
MenuItemLocation.FolderContextMenu,
|
||||
MenuItemLocation.TagContextMenu,
|
||||
].includes(location);
|
||||
}
|
||||
|
||||
export interface MenuItem {
|
||||
/**
|
||||
* Command that should be associated with the menu item. All menu item should
|
||||
* have a command associated with them unless they are a sub-menu.
|
||||
*/
|
||||
commandName?: string;
|
||||
|
||||
/**
|
||||
* Arguments that should be passed to the command. They will be as rest
|
||||
* parameters.
|
||||
*/
|
||||
commandArgs?: any[];
|
||||
|
||||
/**
|
||||
* Set to "separator" to create a divider line
|
||||
*/
|
||||
type?: ('normal' | 'separator' | 'submenu' | 'checkbox' | 'radio');
|
||||
|
||||
/**
|
||||
* Accelerator associated with the menu item
|
||||
*/
|
||||
accelerator?: string;
|
||||
|
||||
/**
|
||||
* Menu items that should appear below this menu item. Allows creating a menu tree.
|
||||
*/
|
||||
submenu?: MenuItem[];
|
||||
|
||||
/**
|
||||
* Menu item label. If not specified, the command label will be used instead.
|
||||
*/
|
||||
label?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// View API types
|
||||
// =================================================================
|
||||
|
||||
export interface ButtonSpec {
|
||||
id: ButtonId;
|
||||
title?: string;
|
||||
onClick?(): void;
|
||||
}
|
||||
|
||||
export type ButtonId = string;
|
||||
|
||||
export enum ToolbarButtonLocation {
|
||||
/**
|
||||
* This toolbar in the top right corner of the application. It applies to the note as a whole, including its metadata.
|
||||
*/
|
||||
NoteToolbar = 'noteToolbar',
|
||||
|
||||
/**
|
||||
* This toolbar is right above the text editor. It applies to the note body only.
|
||||
*/
|
||||
EditorToolbar = 'editorToolbar',
|
||||
}
|
||||
|
||||
export type ViewHandle = string;
|
||||
|
||||
export interface EditorCommand {
|
||||
name: string;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
export interface DialogResult {
|
||||
id: ButtonId;
|
||||
formData?: any;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Settings types
|
||||
// =================================================================
|
||||
|
||||
export enum SettingItemType {
|
||||
Int = 1,
|
||||
String = 2,
|
||||
Bool = 3,
|
||||
Array = 4,
|
||||
Object = 5,
|
||||
Button = 6,
|
||||
}
|
||||
|
||||
export enum SettingItemSubType {
|
||||
FilePathAndArgs = 'file_path_and_args',
|
||||
FilePath = 'file_path', // Not supported on mobile!
|
||||
DirectoryPath = 'directory_path', // Not supported on mobile!
|
||||
}
|
||||
|
||||
export enum AppType {
|
||||
Desktop = 'desktop',
|
||||
Mobile = 'mobile',
|
||||
Cli = 'cli',
|
||||
}
|
||||
|
||||
export enum SettingStorage {
|
||||
Database = 1,
|
||||
File = 2,
|
||||
}
|
||||
|
||||
// Redefine a simplified interface to mask internal details
|
||||
// and to remove function calls as they would have to be async.
|
||||
export interface SettingItem {
|
||||
value: any;
|
||||
type: SettingItemType;
|
||||
|
||||
/**
|
||||
* Currently only used to display a file or directory selector. Always set
|
||||
* `type` to `SettingItemType.String` when using this property.
|
||||
*/
|
||||
subType?: SettingItemSubType;
|
||||
|
||||
label: string;
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* A public setting will appear in the Configuration screen and will be
|
||||
* modifiable by the user. A private setting however will not appear there,
|
||||
* and can only be changed programmatically. You may use this to store some
|
||||
* values that you do not want to directly expose.
|
||||
*/
|
||||
public: boolean;
|
||||
|
||||
/**
|
||||
* You would usually set this to a section you would have created
|
||||
* specifically for the plugin.
|
||||
*/
|
||||
section?: string;
|
||||
|
||||
/**
|
||||
* To create a setting with multiple options, set this property to `true`.
|
||||
* That setting will render as a dropdown list in the configuration screen.
|
||||
*/
|
||||
isEnum?: boolean;
|
||||
|
||||
/**
|
||||
* This property is required when `isEnum` is `true`. In which case, it
|
||||
* should contain a map of value => label.
|
||||
*/
|
||||
options?: Record<any, any>;
|
||||
|
||||
/**
|
||||
* Reserved property. Not used at the moment.
|
||||
*/
|
||||
appTypes?: AppType[];
|
||||
|
||||
/**
|
||||
* Set this to `true` to store secure data, such as passwords. Any such
|
||||
* setting will be stored in the system keychain if one is available.
|
||||
*/
|
||||
secure?: boolean;
|
||||
|
||||
/**
|
||||
* An advanced setting will be moved under the "Advanced" button in the
|
||||
* config screen.
|
||||
*/
|
||||
advanced?: boolean;
|
||||
|
||||
/**
|
||||
* Set the min, max and step values if you want to restrict an int setting
|
||||
* to a particular range.
|
||||
*/
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
step?: number;
|
||||
|
||||
/**
|
||||
* Either store the setting in the database or in settings.json. Defaults to database.
|
||||
*/
|
||||
storage?: SettingStorage;
|
||||
}
|
||||
|
||||
export interface SettingSection {
|
||||
label: string;
|
||||
iconName?: string;
|
||||
description?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// Data API types
|
||||
// =================================================================
|
||||
|
||||
/**
|
||||
* An array of at least one element and at most three elements.
|
||||
*
|
||||
* - **[0]**: Resource name (eg. "notes", "folders", "tags", etc.)
|
||||
* - **[1]**: (Optional) Resource ID.
|
||||
* - **[2]**: (Optional) Resource link.
|
||||
*/
|
||||
export type Path = string[];
|
||||
|
||||
// =================================================================
|
||||
// Content Script types
|
||||
// =================================================================
|
||||
|
||||
export type PostMessageHandler = (message: any)=> Promise<any>;
|
||||
|
||||
/**
|
||||
* When a content script is initialised, it receives a `context` object.
|
||||
*/
|
||||
export interface ContentScriptContext {
|
||||
/**
|
||||
* The plugin ID that registered this content script
|
||||
*/
|
||||
pluginId: string;
|
||||
|
||||
/**
|
||||
* The content script ID, which may be necessary to post messages
|
||||
*/
|
||||
contentScriptId: string;
|
||||
|
||||
/**
|
||||
* Can be used by CodeMirror content scripts to post a message to the plugin
|
||||
*/
|
||||
postMessage: PostMessageHandler;
|
||||
}
|
||||
|
||||
export enum ContentScriptType {
|
||||
/**
|
||||
* Registers a new Markdown-It plugin, which should follow the template
|
||||
* below.
|
||||
*
|
||||
* ```javascript
|
||||
* module.exports = {
|
||||
* default: function(context) {
|
||||
* return {
|
||||
* plugin: function(markdownIt, options) {
|
||||
* // ...
|
||||
* },
|
||||
* assets: {
|
||||
* // ...
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* See [the
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||
* for a simple Markdown-it plugin example.
|
||||
*
|
||||
* ## Exported members
|
||||
*
|
||||
* - The `context` argument is currently unused but could be used later on
|
||||
* to provide access to your own plugin so that the content script and
|
||||
* plugin can communicate.
|
||||
*
|
||||
* - The **required** `plugin` key is the actual Markdown-It plugin - check
|
||||
* the [official doc](https://github.com/markdown-it/markdown-it) for more
|
||||
* information. The `options` parameter is of type
|
||||
* [RuleOptions](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml.ts),
|
||||
* which contains a number of options, mostly useful for Joplin's internal
|
||||
* code.
|
||||
*
|
||||
* - Using the **optional** `assets` key you may specify assets such as JS
|
||||
* or CSS that should be loaded in the rendered HTML document. Check for
|
||||
* example the Joplin [Mermaid
|
||||
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
|
||||
* to see how the data should be structured.
|
||||
*
|
||||
* ## Posting messages from the content script to your plugin
|
||||
*
|
||||
* The application provides the following function to allow executing
|
||||
* commands from the rendered HTML code:
|
||||
*
|
||||
* ```javascript
|
||||
* const response = await webviewApi.postMessage(contentScriptId, message);
|
||||
* ```
|
||||
*
|
||||
* - `contentScriptId` is the ID you've defined when you registered the
|
||||
* content script. You can retrieve it from the
|
||||
* {@link ContentScriptContext | context}.
|
||||
* - `message` can be any basic JavaScript type (number, string, plain
|
||||
* object), but it cannot be a function or class instance.
|
||||
*
|
||||
* When you post a message, the plugin can send back a `response` thus
|
||||
* allowing two-way communication:
|
||||
*
|
||||
* ```javascript
|
||||
* await joplin.contentScripts.onMessage(contentScriptId, (message) => {
|
||||
* // Process message
|
||||
* return response; // Can be any object, string or number
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See {@link JoplinContentScripts.onMessage} for more details, as well as
|
||||
* the [postMessage
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
|
||||
*
|
||||
* ## Registering an existing Markdown-it plugin
|
||||
*
|
||||
* To include a regular Markdown-It plugin, that doesn't make use of any
|
||||
* Joplin-specific features, you would simply create a file such as this:
|
||||
*
|
||||
* ```javascript
|
||||
* module.exports = {
|
||||
* default: function(context) {
|
||||
* return {
|
||||
* plugin: require('markdown-it-toc-done-right');
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
MarkdownItPlugin = 'markdownItPlugin',
|
||||
|
||||
/**
|
||||
* Registers a new CodeMirror plugin, which should follow the template
|
||||
* below.
|
||||
*
|
||||
* ```javascript
|
||||
* module.exports = {
|
||||
* default: function(context) {
|
||||
* return {
|
||||
* plugin: function(CodeMirror) {
|
||||
* // ...
|
||||
* },
|
||||
* codeMirrorResources: [],
|
||||
* codeMirrorOptions: {
|
||||
* // ...
|
||||
* },
|
||||
* assets: {
|
||||
* // ...
|
||||
* },
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* - The `context` argument is currently unused but could be used later on
|
||||
* to provide access to your own plugin so that the content script and
|
||||
* plugin can communicate.
|
||||
*
|
||||
* - The `plugin` key is your CodeMirror plugin. This is where you can
|
||||
* register new commands with CodeMirror or interact with the CodeMirror
|
||||
* instance as needed.
|
||||
*
|
||||
* - The `codeMirrorResources` key is an array of CodeMirror resources that
|
||||
* will be loaded and attached to the CodeMirror module. These are made up
|
||||
* of addons, keymaps, and modes. For example, for a plugin that want's to
|
||||
* enable clojure highlighting in code blocks. `codeMirrorResources` would
|
||||
* be set to `['mode/clojure/clojure']`.
|
||||
*
|
||||
* - The `codeMirrorOptions` key contains all the
|
||||
* [CodeMirror](https://codemirror.net/doc/manual.html#config) options
|
||||
* that will be set or changed by this plugin. New options can alse be
|
||||
* declared via
|
||||
* [`CodeMirror.defineOption`](https://codemirror.net/doc/manual.html#defineOption),
|
||||
* and then have their value set here. For example, a plugin that enables
|
||||
* line numbers would set `codeMirrorOptions` to `{'lineNumbers': true}`.
|
||||
*
|
||||
* - Using the **optional** `assets` key you may specify **only** CSS assets
|
||||
* that should be loaded in the rendered HTML document. Check for example
|
||||
* the Joplin [Mermaid
|
||||
* plugin](https://github.com/laurent22/joplin/blob/dev/packages/renderer/MdToHtml/rules/mermaid.ts)
|
||||
* to see how the data should be structured.
|
||||
*
|
||||
* One of the `plugin`, `codeMirrorResources`, or `codeMirrorOptions` keys
|
||||
* must be provided for the plugin to be valid. Having multiple or all
|
||||
* provided is also okay.
|
||||
*
|
||||
* See also the [demo
|
||||
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/codemirror_content_script)
|
||||
* for an example of all these keys being used in one plugin.
|
||||
*
|
||||
* ## Posting messages from the content script to your plugin
|
||||
*
|
||||
* In order to post messages to the plugin, you can use the postMessage
|
||||
* function passed to the {@link ContentScriptContext | context}.
|
||||
*
|
||||
* ```javascript
|
||||
* const response = await context.postMessage('messageFromCodeMirrorContentScript');
|
||||
* ```
|
||||
*
|
||||
* When you post a message, the plugin can send back a `response` thus
|
||||
* allowing two-way communication:
|
||||
*
|
||||
* ```javascript
|
||||
* await joplin.contentScripts.onMessage(contentScriptId, (message) => {
|
||||
* // Process message
|
||||
* return response; // Can be any object, string or number
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* See {@link JoplinContentScripts.onMessage} for more details, as well as
|
||||
* the [postMessage
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/post_messages).
|
||||
*
|
||||
*/
|
||||
CodeMirrorPlugin = 'codeMirrorPlugin',
|
||||
}
|
10687
packages/app-cli/tests/support/plugins/user_data/package-lock.json
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "joplin-plugin-userdata-demo",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
|
||||
"prepare": "npm run dist",
|
||||
"update": "npm install -g generator-joplin && yo joplin --update"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"joplin-plugin"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/node": "^14.0.14",
|
||||
"chalk": "^4.1.0",
|
||||
"copy-webpack-plugin": "^6.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"glob": "^7.1.6",
|
||||
"on-build-webpack": "^0.1.0",
|
||||
"tar": "^6.0.5",
|
||||
"ts-loader": "^7.0.5",
|
||||
"typescript": "^3.9.3",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"files": [
|
||||
"publish"
|
||||
]
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extraScripts": []
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
import joplin from 'api';
|
||||
import { ModelType } from 'api/types';
|
||||
|
||||
joplin.plugins.register({
|
||||
onStart: async function() {
|
||||
const folder = await joplin.data.post(['folders'], null, { title: "test" });
|
||||
const note = await joplin.data.post(['notes'], null, { title: "test", parent_id: folder.id });
|
||||
|
||||
await joplin.data.userDataSet(ModelType.Note, note.id, 'mykey', 'abcd');
|
||||
|
||||
console.info('Got back user data:', await joplin.data.userDataGet(ModelType.Note, note.id, 'mykey'));
|
||||
|
||||
await joplin.data.userDataDelete(ModelType.Note, note.id, 'mykey');
|
||||
|
||||
console.info('Got back user data:', await joplin.data.userDataGet(ModelType.Note, note.id, 'mykey'));
|
||||
},
|
||||
});
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"id": "org.joplinapp.plugins.UserDataDemo",
|
||||
"app_min_version": "2.0",
|
||||
"version": "1.0.0",
|
||||
"name": "User Data Demo",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"homepage_url": "",
|
||||
"repository_url": "",
|
||||
"keywords": []
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
@@ -0,0 +1,291 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// This file is used to build the plugin file (.jpl) and plugin info (.json). It
|
||||
// is recommended not to edit this file as it would be overwritten when updating
|
||||
// the plugin framework. If you do make some changes, consider using an external
|
||||
// JS file and requiring it here to minimize the changes. That way when you
|
||||
// update, you can easily restore the functionality you've added.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const WebpackOnBuildPlugin = require('on-build-webpack');
|
||||
const tar = require('tar');
|
||||
const glob = require('glob');
|
||||
const execSync = require('child_process').execSync;
|
||||
|
||||
const rootDir = path.resolve(__dirname);
|
||||
const userConfigFilename = './plugin.config.json';
|
||||
const userConfigPath = path.resolve(rootDir, userConfigFilename);
|
||||
const distDir = path.resolve(rootDir, 'dist');
|
||||
const srcDir = path.resolve(rootDir, 'src');
|
||||
const publishDir = path.resolve(rootDir, 'publish');
|
||||
|
||||
const userConfig = Object.assign({}, {
|
||||
extraScripts: [],
|
||||
}, fs.pathExistsSync(userConfigPath) ? require(userConfigFilename) : {});
|
||||
|
||||
const manifestPath = `${srcDir}/manifest.json`;
|
||||
const packageJsonPath = `${rootDir}/package.json`;
|
||||
const allPossibleCategories = ['appearance', 'developer tools', 'productivity', 'themes', 'integrations', 'viewer', 'search', 'tags', 'editor', 'files', 'personal knowledge management'];
|
||||
const manifest = readManifest(manifestPath);
|
||||
const pluginArchiveFilePath = path.resolve(publishDir, `${manifest.id}.jpl`);
|
||||
const pluginInfoFilePath = path.resolve(publishDir, `${manifest.id}.json`);
|
||||
|
||||
function validatePackageJson() {
|
||||
const content = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
if (!content.name || content.name.indexOf('joplin-plugin-') !== 0) {
|
||||
console.warn(chalk.yellow(`WARNING: To publish the plugin, the package name should start with "joplin-plugin-" (found "${content.name}") in ${packageJsonPath}`));
|
||||
}
|
||||
|
||||
if (!content.keywords || content.keywords.indexOf('joplin-plugin') < 0) {
|
||||
console.warn(chalk.yellow(`WARNING: To publish the plugin, the package keywords should include "joplin-plugin" (found "${JSON.stringify(content.keywords)}") in ${packageJsonPath}`));
|
||||
}
|
||||
|
||||
if (content.scripts && content.scripts.postinstall) {
|
||||
console.warn(chalk.yellow(`WARNING: package.json contains a "postinstall" script. It is recommended to use a "prepare" script instead so that it is executed before publish. In ${packageJsonPath}`));
|
||||
}
|
||||
}
|
||||
|
||||
function fileSha256(filePath) {
|
||||
const content = fs.readFileSync(filePath);
|
||||
return crypto.createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
|
||||
function currentGitInfo() {
|
||||
try {
|
||||
let branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
|
||||
const commit = execSync('git rev-parse HEAD', { stdio: 'pipe' }).toString().trim();
|
||||
if (branch === 'HEAD') branch = 'master';
|
||||
return `${branch}:${commit}`;
|
||||
} catch (error) {
|
||||
const messages = error.message ? error.message.split('\n') : [''];
|
||||
console.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));
|
||||
console.info(chalk.cyan('Git information will not be stored in plugin info file'));
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function validateCategories(categories) {
|
||||
if (!categories) return null;
|
||||
if ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');
|
||||
categories.forEach(category => {
|
||||
if (!allPossibleCategories.includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid Categories are: \n${allPossibleCategories}\n`);
|
||||
});
|
||||
}
|
||||
|
||||
function readManifest(manifestPath) {
|
||||
const content = fs.readFileSync(manifestPath, 'utf8');
|
||||
const output = JSON.parse(content);
|
||||
if (!output.id) throw new Error(`Manifest plugin ID is not set in ${manifestPath}`);
|
||||
validateCategories(output.categories);
|
||||
return output;
|
||||
}
|
||||
|
||||
function createPluginArchive(sourceDir, destPath) {
|
||||
const distFiles = glob.sync(`${sourceDir}/**/*`, { nodir: true })
|
||||
.map(f => f.substr(sourceDir.length + 1));
|
||||
|
||||
if (!distFiles.length) throw new Error('Plugin archive was not created because the "dist" directory is empty');
|
||||
fs.removeSync(destPath);
|
||||
|
||||
tar.create(
|
||||
{
|
||||
strict: true,
|
||||
portable: true,
|
||||
file: destPath,
|
||||
cwd: sourceDir,
|
||||
sync: true,
|
||||
},
|
||||
distFiles
|
||||
);
|
||||
|
||||
console.info(chalk.cyan(`Plugin archive has been created in ${destPath}`));
|
||||
}
|
||||
|
||||
function createPluginInfo(manifestPath, destPath, jplFilePath) {
|
||||
const contentText = fs.readFileSync(manifestPath, 'utf8');
|
||||
const content = JSON.parse(contentText);
|
||||
content._publish_hash = `sha256:${fileSha256(jplFilePath)}`;
|
||||
content._publish_commit = currentGitInfo();
|
||||
fs.writeFileSync(destPath, JSON.stringify(content, null, '\t'), 'utf8');
|
||||
}
|
||||
|
||||
function onBuildCompleted() {
|
||||
try {
|
||||
fs.removeSync(path.resolve(publishDir, 'index.js'));
|
||||
createPluginArchive(distDir, pluginArchiveFilePath);
|
||||
createPluginInfo(manifestPath, pluginInfoFilePath, pluginArchiveFilePath);
|
||||
validatePackageJson();
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const baseConfig = {
|
||||
mode: 'production',
|
||||
target: 'node',
|
||||
stats: 'errors-only',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const pluginConfig = Object.assign({}, baseConfig, {
|
||||
entry: './src/index.ts',
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
},
|
||||
// JSON files can also be required from scripts so we include this.
|
||||
// https://github.com/joplin/plugin-bibtex/pull/2
|
||||
extensions: ['.js', '.tsx', '.ts', '.json'],
|
||||
},
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
path: distDir,
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: '**/*',
|
||||
context: path.resolve(__dirname, 'src'),
|
||||
to: path.resolve(__dirname, 'dist'),
|
||||
globOptions: {
|
||||
ignore: [
|
||||
// All TypeScript files are compiled to JS and
|
||||
// already copied into /dist so we don't copy them.
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const extraScriptConfig = Object.assign({}, baseConfig, {
|
||||
resolve: {
|
||||
alias: {
|
||||
api: path.resolve(__dirname, 'api'),
|
||||
},
|
||||
extensions: ['.js', '.tsx', '.ts', '.json'],
|
||||
},
|
||||
});
|
||||
|
||||
const createArchiveConfig = {
|
||||
stats: 'errors-only',
|
||||
entry: './dist/index.js',
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
path: publishDir,
|
||||
},
|
||||
plugins: [new WebpackOnBuildPlugin(onBuildCompleted)],
|
||||
};
|
||||
|
||||
function resolveExtraScriptPath(name) {
|
||||
const relativePath = `./src/${name}`;
|
||||
|
||||
const fullPath = path.resolve(`${rootDir}/${relativePath}`);
|
||||
if (!fs.pathExistsSync(fullPath)) throw new Error(`Could not find extra script: "${name}" at "${fullPath}"`);
|
||||
|
||||
const s = name.split('.');
|
||||
s.pop();
|
||||
const nameNoExt = s.join('.');
|
||||
|
||||
return {
|
||||
entry: relativePath,
|
||||
output: {
|
||||
filename: `${nameNoExt}.js`,
|
||||
path: distDir,
|
||||
library: 'default',
|
||||
libraryTarget: 'commonjs',
|
||||
libraryExport: 'default',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildExtraScriptConfigs(userConfig) {
|
||||
if (!userConfig.extraScripts.length) return [];
|
||||
|
||||
const output = [];
|
||||
|
||||
for (const scriptName of userConfig.extraScripts) {
|
||||
const scriptPaths = resolveExtraScriptPath(scriptName);
|
||||
output.push(Object.assign({}, extraScriptConfig, {
|
||||
entry: scriptPaths.entry,
|
||||
output: scriptPaths.output,
|
||||
}));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function main(processArgv) {
|
||||
const yargs = require('yargs/yargs');
|
||||
const argv = yargs(processArgv).argv;
|
||||
|
||||
const configName = argv['joplin-plugin-config'];
|
||||
if (!configName) throw new Error('A config file must be specified via the --joplin-plugin-config flag');
|
||||
|
||||
// Webpack configurations run in parallel, while we need them to run in
|
||||
// sequence, and to do that it seems the only way is to run webpack multiple
|
||||
// times, with different config each time.
|
||||
|
||||
const configs = {
|
||||
// Builds the main src/index.ts and copy the extra content from /src to
|
||||
// /dist including scripts, CSS and any other asset.
|
||||
buildMain: [pluginConfig],
|
||||
|
||||
// Builds the extra scripts as defined in plugin.config.json. When doing
|
||||
// so, some JavaScript files that were copied in the previous might be
|
||||
// overwritten here by the compiled version. This is by design. The
|
||||
// result is that JS files that don't need compilation, are simply
|
||||
// copied to /dist, while those that do need it are correctly compiled.
|
||||
buildExtraScripts: buildExtraScriptConfigs(userConfig),
|
||||
|
||||
// Ths config is for creating the .jpl, which is done via the plugin, so
|
||||
// it doesn't actually need an entry and output, however webpack won't
|
||||
// run without this. So we give it an entry that we know is going to
|
||||
// exist and output in the publish dir. Then the plugin will delete this
|
||||
// temporary file before packaging the plugin.
|
||||
createArchive: [createArchiveConfig],
|
||||
};
|
||||
|
||||
// If we are running the first config step, we clean up and create the build
|
||||
// directories.
|
||||
if (configName === 'buildMain') {
|
||||
fs.removeSync(distDir);
|
||||
fs.removeSync(publishDir);
|
||||
fs.mkdirpSync(publishDir);
|
||||
}
|
||||
|
||||
return configs[configName];
|
||||
}
|
||||
|
||||
let exportedConfigs = [];
|
||||
|
||||
try {
|
||||
exportedConfigs = main(process.argv);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!exportedConfigs.length) {
|
||||
// Nothing to do - for example where there are no external scripts to
|
||||
// compile.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
module.exports = exportedConfigs;
|
@@ -5,6 +5,9 @@
|
||||
"target": "es2015",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,9 @@ import { defaultState, State } from '@joplin/lib/reducer';
|
||||
import iterateItems from './gui/ResizableLayout/utils/iterateItems';
|
||||
import { LayoutItem } from './gui/ResizableLayout/utils/types';
|
||||
import validateLayout from './gui/ResizableLayout/utils/validateLayout';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
|
||||
const logger = Logger.create('app.reducer');
|
||||
|
||||
export interface AppStateRoute {
|
||||
type: string;
|
||||
@@ -171,22 +174,31 @@ export default function(state: AppState, action: any) {
|
||||
case 'MAIN_LAYOUT_SET_ITEM_PROP':
|
||||
|
||||
{
|
||||
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
||||
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
if (item.key === action.itemKey) {
|
||||
(item as any)[action.propName] = action.propValue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (!state.mainLayout) {
|
||||
logger.warn('MAIN_LAYOUT_SET_ITEM_PROP: Trying to set an item prop on the layout, but layout is empty: ', JSON.stringify(action));
|
||||
} else {
|
||||
let newLayout = produce(state.mainLayout, (draftLayout: LayoutItem) => {
|
||||
iterateItems(draftLayout, (_itemIndex: number, item: LayoutItem, _parent: LayoutItem) => {
|
||||
if (!item) {
|
||||
logger.warn('MAIN_LAYOUT_SET_ITEM_PROP: Found an empty item in layout: ', JSON.stringify(state.mainLayout));
|
||||
} else {
|
||||
if (item.key === action.itemKey) {
|
||||
(item as any)[action.propName] = action.propValue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
||||
if (newLayout !== state.mainLayout) newLayout = validateLayout(newLayout);
|
||||
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: newLayout,
|
||||
};
|
||||
newState = {
|
||||
...state,
|
||||
mainLayout: newLayout,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@@ -80,6 +80,7 @@ const appDefaultState = createAppDefaultState(
|
||||
class Application extends BaseApplication {
|
||||
|
||||
private checkAllPluginStartedIID_: any = null;
|
||||
private initPluginServiceDone_: boolean = false;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
@@ -258,6 +259,9 @@ class Application extends BaseApplication {
|
||||
}
|
||||
|
||||
private async initPluginService() {
|
||||
if (this.initPluginServiceDone_) return;
|
||||
this.initPluginServiceDone_ = true;
|
||||
|
||||
const service = PluginService.instance();
|
||||
|
||||
const pluginRunner = new PluginRunner();
|
||||
|
@@ -20,7 +20,7 @@ export const runtime = (): CommandRuntime => {
|
||||
};
|
||||
|
||||
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
|
||||
await restart(false);
|
||||
await restart();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -1,7 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
|
||||
const logger = Logger.create('ItemList');
|
||||
|
||||
interface Props {
|
||||
style: any;
|
||||
@@ -50,15 +47,6 @@ class ItemList extends React.Component<Props, State> {
|
||||
let bottomItemIndex = topItemIndex + (visibleItemCount - 1);
|
||||
if (bottomItemIndex >= props.items.length) bottomItemIndex = props.items.length - 1;
|
||||
|
||||
// EDGE CASE:
|
||||
// ref: https://github.com/laurent22/joplin/issues/4124
|
||||
// when the note list is hidden, visibleItemCount is negative, and scroll top is positive when a note is selected
|
||||
if (visibleItemCount < 0 && this.scrollTop_ > 0) {
|
||||
logger.warn('Resetting scrollTop to 0. visibleItemCount is negative, scrollTop is positive.');
|
||||
// we will reset the scroll top so that there is no blank space at the top of note list
|
||||
this.scrollTop_ = 0;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
topItemIndex: topItemIndex,
|
||||
bottomItemIndex: bottomItemIndex,
|
||||
@@ -81,21 +69,6 @@ class ItemList extends React.Component<Props, State> {
|
||||
this.updateStateItemIndexes(newProps);
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
// EDGE CASE
|
||||
// scroll top is not updated when item list visibility is toggled
|
||||
// if the user was at the bottom of the item list before hiding, blank spaces are added at the bottom of the item list
|
||||
if (this.offsetScroll() !== this.listRef.current?.scrollTop) {
|
||||
logger.warn(`scrollTop mismatch. Updating scrollTop with current listRef scrollTop(${this.listRef.current.scrollTop})`);
|
||||
// update scroll postion once if there is a mismatch in scroll position after showing item list
|
||||
this.onScroll({
|
||||
target: {
|
||||
scrollTop: this.listRef.current.scrollTop,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onScroll(event: any) {
|
||||
this.scrollTop_ = event.target.scrollTop;
|
||||
this.updateStateItemIndexes();
|
||||
|
@@ -22,7 +22,7 @@ export const runtime = (comp: any): CommandRuntime => {
|
||||
const { newConfig, newProfile } = createNewProfile(context.state.profileConfig, answer);
|
||||
newConfig.currentProfileId = newProfile.id;
|
||||
await saveProfileConfig(`${Setting.value('rootProfileDir')}/profiles.json`, newConfig);
|
||||
await restart(false);
|
||||
await restart();
|
||||
}
|
||||
|
||||
comp.setState({ promptOptions: null });
|
||||
|
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import DialogButtonRow from './DialogButtonRow';
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const Countable = require('countable');
|
||||
const Countable = require('@joplin/lib/countable/Countable');
|
||||
import markupLanguageUtils from '../utils/markupLanguageUtils';
|
||||
|
||||
interface NoteContentPropertiesDialogProps {
|
||||
|
@@ -25,6 +25,7 @@ import { themeStyle } from '@joplin/lib/theme';
|
||||
import { loadScript } from '../../../utils/loadScript';
|
||||
import bridge from '../../../../services/bridge';
|
||||
import { TinyMceEditorEvents } from './utils/types';
|
||||
import type { Editor } from 'tinymce';
|
||||
const { clipboard } = require('electron');
|
||||
const supportedLocales = require('./supportedLocales');
|
||||
|
||||
@@ -557,7 +558,15 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
'bold', 'italic', 'joplinHighlight', 'joplinStrikethrough', 'formattingExtras', '|',
|
||||
'link', 'joplinInlineCode', 'joplinCodeBlock', 'joplinAttach', '|',
|
||||
'bullist', 'numlist', 'joplinChecklist', '|',
|
||||
'h1', 'h2', 'h3', 'hr', 'blockquote', 'table', `joplinInsertDateTime${toolbarPluginButtons}`,
|
||||
'h1', 'h2', 'h3', 'hr', 'blockquote', 'inserttable', `joplinInsertDateTime${toolbarPluginButtons}`,
|
||||
];
|
||||
|
||||
// Available table toolbar buttons:
|
||||
// https://www.tiny.cloud/docs/advanced/available-toolbar-buttons/#tableplugin
|
||||
const tableToolbar = [
|
||||
'tabledelete',
|
||||
'tableinsertrowafter tablecopyrow tablepasterowafter tabledeleterow',
|
||||
'tableinsertcolafter tablecopycol tablepastecolafter tabledeletecol',
|
||||
];
|
||||
|
||||
const editors = await (window as any).tinymce.init({
|
||||
@@ -576,6 +585,10 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
branding: false,
|
||||
statusbar: false,
|
||||
target_list: false,
|
||||
// Handle the first table row as table header.
|
||||
// https://www.tiny.cloud/docs/plugins/table/#table_header_type
|
||||
table_header_type: 'sectionCells',
|
||||
table_toolbar: tableToolbar.join(' | '),
|
||||
table_resize_bars: false,
|
||||
language_url: ['en_US', 'en_GB'].includes(language) ? undefined : `${bridge().vendorDir()}/lib/tinymce/langs/${language}`,
|
||||
toolbar: toolbar.join(' '),
|
||||
@@ -589,7 +602,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
joplinSub: { inline: 'sub', remove: 'all' },
|
||||
joplinSup: { inline: 'sup', remove: 'all' },
|
||||
},
|
||||
setup: (editor: any) => {
|
||||
setup: (editor: Editor) => {
|
||||
editor.ui.registry.addButton('joplinAttach', {
|
||||
tooltip: _('Attach file'),
|
||||
icon: 'paperclip',
|
||||
@@ -614,7 +627,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
onAction: function() {
|
||||
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' });
|
||||
},
|
||||
onSetup: function(api: any) {
|
||||
onSetup: function(api) {
|
||||
api.setActive(editor.formatter.match('code'));
|
||||
const unbind = editor.formatter.formatChanged('code', api.setActive).unbind;
|
||||
|
||||
@@ -624,6 +637,22 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
},
|
||||
});
|
||||
|
||||
editor.ui.registry.addMenuButton('inserttable', {
|
||||
icon: 'table',
|
||||
tooltip: 'Table',
|
||||
fetch: (callback) => {
|
||||
callback([
|
||||
{
|
||||
type: 'fancymenuitem',
|
||||
fancytype: 'inserttable',
|
||||
onAction: (data) => {
|
||||
editor.execCommand('mceInsertTable', false, { rows: data.numRows, columns: data.numColumns, options: { headerRows: 1 } });
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
});
|
||||
|
||||
editor.ui.registry.addButton('joplinInsertDateTime', {
|
||||
tooltip: _('Insert time'),
|
||||
icon: 'insert-time',
|
||||
@@ -647,13 +676,13 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
editor.addShortcut('Meta+Shift+9', '', () => editor.execCommand('InsertJoplinChecklist'));
|
||||
|
||||
// TODO: remove event on unmount?
|
||||
editor.on('DblClick', (event: any) => {
|
||||
editor.on('DblClick', (event) => {
|
||||
const editable = findEditableContainer(event.target);
|
||||
if (editable) openEditDialog(editor, markupToHtml, dispatchDidUpdate, editable);
|
||||
});
|
||||
|
||||
// This is triggered when an external file is dropped on the editor
|
||||
editor.on('drop', (event: any) => {
|
||||
editor.on('drop', (event) => {
|
||||
// Prevent the message "Dropped file type is not
|
||||
// supported" to show up. It was added in a recent
|
||||
// TinyMCE version and doesn't apply since we do support
|
||||
@@ -664,7 +693,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
props_onDrop.current(event);
|
||||
});
|
||||
|
||||
editor.on('ObjectResized', (event: any) => {
|
||||
editor.on('ObjectResized', (event) => {
|
||||
if (event.target.nodeName === 'IMG') {
|
||||
editor.fire(TinyMceEditorEvents.JoplinChange);
|
||||
dispatchDidUpdate(editor);
|
||||
|
@@ -181,7 +181,7 @@ function NoteListControls(props: Props) {
|
||||
useEffect(() => {
|
||||
if (breakpoint === dynamicBreakpoints.Xl) {
|
||||
noteControlsRef.current.style.flexDirection = 'row';
|
||||
searchAndSortRef.current.style.flex = '2 1 auto';
|
||||
searchAndSortRef.current.style.flex = '2 1 50%';
|
||||
props.onContentHeightChange(true);
|
||||
} else {
|
||||
noteControlsRef.current.style.flexDirection = 'column';
|
||||
|
@@ -100,9 +100,8 @@ export const StyledExpandLink = styled.a`
|
||||
`;
|
||||
|
||||
export const StyledNoteCount = styled.div`
|
||||
color: ${(props: any) => props.theme.color2};
|
||||
color: ${(props: any) => props.theme.colorFaded2};
|
||||
padding-left: 8px;
|
||||
opacity: 0.5;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.11.6",
|
||||
"version": "2.11.9",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -110,9 +110,9 @@
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.11",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.2.6",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/react": "16.14.35",
|
||||
"@types/jest": "29.5.1",
|
||||
"@types/node": "18.15.13",
|
||||
"@types/react": "16.14.41",
|
||||
"@types/react-redux": "7.1.25",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"electron": "19.1.4",
|
||||
@@ -121,12 +121,12 @@
|
||||
"electron-rebuild": "3.2.9",
|
||||
"glob": "8.1.0",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.4.3",
|
||||
"jest-environment-jsdom": "29.4.3",
|
||||
"jest": "29.5.0",
|
||||
"jest-environment-jsdom": "29.5.0",
|
||||
"js-sha512": "0.8.0",
|
||||
"nan": "2.17.0",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "4.9.4"
|
||||
"typescript": "5.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"7zip-bin-linux": "^1.0.1",
|
||||
@@ -148,7 +148,7 @@
|
||||
"electron-window-state": "5.0.3",
|
||||
"formatcoords": "1.1.3",
|
||||
"fs-extra": "11.1.1",
|
||||
"highlight.js": "11.7.0",
|
||||
"highlight.js": "11.8.0",
|
||||
"immer": "7.0.15",
|
||||
"keytar": "7.9.0",
|
||||
"mark.js": "8.11.1",
|
||||
|
@@ -129,7 +129,10 @@ export default class PluginRunner extends BasePluginRunner {
|
||||
|
||||
if (plugin.devMode) {
|
||||
pluginWindow.webContents.once('dom-ready', () => {
|
||||
pluginWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
// Need to open with a delay, otherwise it doesn't show up
|
||||
setTimeout(() => {
|
||||
pluginWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -4,5 +4,15 @@
|
||||
# It could be used to develop plugins too.
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
PLUGIN_PATH=~/src/joplin-rich-markdown
|
||||
npm install --prefix="$PLUGIN_PATH" && yarn start --dev-plugins "$PLUGIN_PATH"
|
||||
TEMP_PATH=~/src/plugin-tests
|
||||
PLUGIN_PATH=~/src/joplin/packages/app-cli/tests/support/plugins/user_data
|
||||
|
||||
mkdir -p "$TEMP_PATH"
|
||||
PLUGIN_NAME=$(echo "$PLUGIN_PATH" | awk -F/ '{print $NF}')
|
||||
TEMP_PLUGIN_PATH="$TEMP_PATH/$PLUGIN_NAME"
|
||||
|
||||
rsync -a --delete "$PLUGIN_PATH/" "$TEMP_PLUGIN_PATH/"
|
||||
|
||||
npm install --prefix="$TEMP_PLUGIN_PATH" && yarn start --dev-plugins "$TEMP_PLUGIN_PATH"
|
||||
|
||||
# Add eg "--profile $HOME/.config/joplindev-desktop-1" to test with a different profile
|
@@ -1,6 +0,0 @@
|
||||
|
||||
[android]
|
||||
target = Google Inc.:Google APIs:23
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
@@ -1,73 +0,0 @@
|
||||
[ignore]
|
||||
; We fork some components by platform
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore polyfills
|
||||
node_modules/react-native/Libraries/polyfills/.*
|
||||
|
||||
; These should not be required directly
|
||||
; require from fbjs/lib instead: require('fbjs/lib/warning')
|
||||
node_modules/warning/.*
|
||||
|
||||
; Flow doesn't support platforms
|
||||
.*/Libraries/Utilities/LoadingView.js
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/@react-native-community/cli/.*/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/interface.js
|
||||
node_modules/react-native/flow/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.json
|
||||
module.file_ext=.ios.js
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/node_modules/react-native/\1'
|
||||
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '<PROJECT_ROOT>/node_modules/react-native/Libraries/Image/RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[lints]
|
||||
sketchy-null-number=warn
|
||||
sketchy-null-mixed=warn
|
||||
sketchy-number=warn
|
||||
untyped-type-import=warn
|
||||
nonstrict-import=warn
|
||||
deprecated-type=warn
|
||||
unsafe-getters-setters=warn
|
||||
unnecessary-invariant=warn
|
||||
signature-verification-failure=warn
|
||||
deprecated-utility=error
|
||||
|
||||
[strict]
|
||||
deprecated-type
|
||||
nonstrict-import
|
||||
sketchy-null
|
||||
unclear-type
|
||||
unsafe-getters-setters
|
||||
untyped-import
|
||||
untyped-type-import
|
||||
|
||||
[version]
|
||||
^0.122.0
|
11
packages/app-mobile/.gitignore
vendored
@@ -31,6 +31,8 @@ local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
|
||||
# node.js
|
||||
#
|
||||
@@ -38,12 +40,6 @@ node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
@@ -63,6 +59,9 @@ buck-out/
|
||||
/ios/Pods/
|
||||
/vendor/bundle/
|
||||
|
||||
# Temporary files created by Metro to check the health of the file watcher
|
||||
.metro-health-check*
|
||||
|
||||
# Custom
|
||||
lib/csstojs/
|
||||
lib/rnInjectedJs/
|
||||
|
1
packages/app-mobile/.node-version
Normal file
@@ -0,0 +1 @@
|
||||
18
|
@@ -1 +0,0 @@
|
||||
2.7.5
|
@@ -1,4 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby '2.7.5'
|
||||
gem 'cocoapods', '~> 1.11', '>= 1.11.2'
|
||||
ruby '>= 2.6.10'
|
||||
gem 'cocoapods', '>= 1.11.3'
|
||||
|
@@ -1,119 +1,77 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
import com.android.build.OutputFile
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
* and bundleReleaseJsAndAssets).
|
||||
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||
* bundle directly from the development server. Below you can see all the possible configurations
|
||||
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||
*
|
||||
* project.ext.react = [
|
||||
* // the name of the generated asset file containing your JS bundle
|
||||
* bundleAssetName: "index.android.bundle",
|
||||
*
|
||||
* // the entry file for bundle generation. If none specified and
|
||||
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
|
||||
* // default. Can be overridden with ENTRY_FILE environment variable.
|
||||
* entryFile: "index.android.js",
|
||||
*
|
||||
* // https://reactnative.dev/docs/performance#enable-the-ram-format
|
||||
* bundleCommand: "ram-bundle",
|
||||
*
|
||||
* // whether to bundle JS and assets in debug mode
|
||||
* bundleInDebug: false,
|
||||
*
|
||||
* // whether to bundle JS and assets in release mode
|
||||
* bundleInRelease: true,
|
||||
*
|
||||
* // whether to bundle JS and assets in another build variant (if configured).
|
||||
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'bundleIn${productFlavor}${buildType}'
|
||||
* // 'bundleIn${buildType}'
|
||||
* // bundleInFreeDebug: true,
|
||||
* // bundleInPaidRelease: true,
|
||||
* // bundleInBeta: true,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // where to put the JS bundle asset in debug mode
|
||||
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||
*
|
||||
* // where to put the JS bundle asset in release mode
|
||||
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in debug mode
|
||||
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in release mode
|
||||
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||
*
|
||||
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||
* // for example, you might want to remove it from here.
|
||||
* inputExcludes: ["android/**", "ios/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
/* This is the configuration block to customize your React Native Android app.
|
||||
* By default you don't need to apply any configuration, just uncomment the lines you need.
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
// 2023-05-09: This seems to be optional, but it's not. Without it, the app
|
||||
// will crash on certain devices with this error:
|
||||
react {
|
||||
/* Folders */
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '..'
|
||||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/react-native-codegen
|
||||
// codegenDir = file("../node_modules/react-native-codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
/* Variants */
|
||||
// The list of variants to that are debuggable. For those we're going to
|
||||
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
|
||||
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
|
||||
// debuggableVariants = ["liteDebug", "prodDebug"]
|
||||
/* Bundling */
|
||||
// A list containing the node command and its flags. Default is just 'node'.
|
||||
// nodeExecutableAndArgs = ["node"]
|
||||
//
|
||||
// > java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so"
|
||||
// The command to run when bundling. By default is 'bundle'
|
||||
// bundleCommand = "ram-bundle"
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/8144#issuecomment-1539629812
|
||||
enableHermes: true, // clean and rebuild if changing
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
// The path to the CLI configuration file. Default is empty.
|
||||
// bundleConfig = file(../rn-cli.config.js)
|
||||
//
|
||||
// The name of the generated asset file containing your JS bundle
|
||||
// bundleAssetName = "MyApplication.android.bundle"
|
||||
//
|
||||
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
|
||||
// entryFile = file("../js/MyApplication.android.js")
|
||||
//
|
||||
// A list of extra flags to pass to the 'bundle' commands.
|
||||
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
|
||||
// extraPackagerArgs = []
|
||||
/* Hermes Commands */
|
||||
// The hermes compiler command to run. By default it is 'hermesc'
|
||||
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
* - An APK that only works on ARM devices
|
||||
* - An APK that only works on x86 devices
|
||||
* The advantage is the size of the APK is reduced by about 4MB.
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
* Set this to true to create four separate APKs instead of one,
|
||||
* one for each native architecture. This is useful if you don't
|
||||
* use App Bundles (https://developer.android.com/guide/app-bundle/)
|
||||
* and want to have separate APKs to upload to the Play Store.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore.
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
|
||||
@@ -122,16 +80,9 @@ def enableProguardInReleaseBuilds = false
|
||||
def jscFlavor = 'org.webkit:android-jsc-intl:+'
|
||||
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
*
|
||||
* This should be set on project.ext.react and that value will be read here. If it is not set
|
||||
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||
*/
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
/**
|
||||
* Architectures to build native code for.
|
||||
* Private function to get the list of Native Architectures you want to build.
|
||||
* This reads the value from reactNativeArchitectures in your gradle.properties
|
||||
* file and works together with the --active-arch-only flag of react-native run-android.
|
||||
*/
|
||||
def reactNativeArchitectures() {
|
||||
def value = project.getProperties().get("reactNativeArchitectures")
|
||||
@@ -139,6 +90,8 @@ def reactNativeArchitectures() {
|
||||
}
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
@@ -146,18 +99,19 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
dexOptions {
|
||||
// To fix "GC overhead limit exceeded"
|
||||
// https://stackoverflow.com/q/32133013/561309
|
||||
javaMaxHeapSize "4g"
|
||||
}
|
||||
// dexOptions {
|
||||
// // To fix "GC overhead limit exceeded"
|
||||
// // https://stackoverflow.com/q/32133013/561309
|
||||
// javaMaxHeapSize "4g"
|
||||
// }
|
||||
|
||||
namespace "net.cozic.joplin"
|
||||
defaultConfig {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097709
|
||||
versionName "2.11.24"
|
||||
versionCode 2097712
|
||||
versionName "2.11.27"
|
||||
// ndk {
|
||||
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
// }
|
||||
@@ -166,67 +120,7 @@ android {
|
||||
missingDimensionStrategy 'react-native-camera', 'general'
|
||||
|
||||
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
|
||||
multiDexEnabled true
|
||||
|
||||
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// We configure the CMake build only if you decide to opt-in for the New Architecture.
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DPROJECT_BUILD_DIR=$buildDir",
|
||||
"-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
|
||||
"-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
|
||||
"-DNODE_MODULES_DIR=$rootDir/../node_modules",
|
||||
"-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
if (!enableSeparateBuildPerCPUArchitecture) {
|
||||
ndk {
|
||||
abiFilters (*reactNativeArchitectures())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// We configure the NDK build only if you decide to opt-in for the New Architecture.
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "$projectDir/src/main/jni/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
def reactAndroidProjectDir = project(':ReactAndroid').projectDir
|
||||
def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
|
||||
dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
|
||||
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
|
||||
into("$buildDir/react-ndk/exported")
|
||||
}
|
||||
def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
|
||||
dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
|
||||
from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
|
||||
into("$buildDir/react-ndk/exported")
|
||||
}
|
||||
afterEvaluate {
|
||||
// If you wish to add a custom TurboModule or component locally,
|
||||
// you should uncomment this line.
|
||||
// preBuild.dependsOn("generateCodegenArtifactsFromSchema")
|
||||
preDebugBuild.dependsOn(packageReactNdkDebugLibs)
|
||||
preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
|
||||
// Due to a bug inside AGP, we have to explicitly set a dependency
|
||||
// between configureCMakeDebug* tasks and the preBuild tasks.
|
||||
// This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
|
||||
configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
|
||||
configureCMakeDebug.dependsOn(preDebugBuild)
|
||||
reactNativeArchitectures().each { architecture ->
|
||||
tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
|
||||
dependsOn("preDebugBuild")
|
||||
}
|
||||
tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
|
||||
dependsOn("preReleaseBuild")
|
||||
}
|
||||
}
|
||||
}
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
splits {
|
||||
@@ -288,10 +182,10 @@ android {
|
||||
// > Execution failed for task ':app:lintVitalRelease'
|
||||
//
|
||||
// https://stackoverflow.com/a/62603296/561309
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
checkReleaseBuilds false
|
||||
}
|
||||
// lintOptions {
|
||||
// disable 'InvalidPackage'
|
||||
// checkReleaseBuilds false
|
||||
// }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -301,72 +195,28 @@ dependencies {
|
||||
exclude group: 'com.google.android.gms', module: 'play-services-vision'
|
||||
}
|
||||
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
// implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
// implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation ("com.facebook.react:react-native") version {
|
||||
strictly "0.70.6" // pass in your react-native version
|
||||
}
|
||||
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
}
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
}
|
||||
|
||||
if (enableHermes) {
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
|
||||
exclude group:'com.facebook.fbjni'
|
||||
}
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
|
||||
implementation 'com.android.support:multidex:2.0.1'
|
||||
}
|
||||
|
||||
if (isNewArchitectureEnabled()) {
|
||||
// If new architecture is enabled, we let you build RN from source
|
||||
// Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
|
||||
// This will be applied to all the imported transtitive dependency.
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.facebook.react:react-native"))
|
||||
.using(project(":ReactAndroid"))
|
||||
.because("On New Architecture we're building React Native from source")
|
||||
substitute(module("com.facebook.react:hermes-engine"))
|
||||
.using(project(":ReactAndroid:hermes-engine"))
|
||||
.because("On New Architecture we're building Hermes from source")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.implementation
|
||||
into 'libs'
|
||||
// implementation 'com.android.support:multidex:2.0.1'
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
|
||||
|
||||
def isNewArchitectureEnabled() {
|
||||
// To opt-in for the New Architecture, you can either:
|
||||
// - Set `newArchEnabled` to true inside the `gradle.properties` file
|
||||
// - Invoke gradle with `-newArchEnabled=true`
|
||||
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
|
||||
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
||||
}
|
||||
|
@@ -1,19 +0,0 @@
|
||||
"""Helper definitions to glob .aar and .jar targets"""
|
||||
|
||||
def create_aar_targets(aarfiles):
|
||||
for aarfile in aarfiles:
|
||||
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
|
||||
lib_deps.append(":" + name)
|
||||
android_prebuilt_aar(
|
||||
name = name,
|
||||
aar = aarfile,
|
||||
)
|
||||
|
||||
def create_jar_targets(jarfiles):
|
||||
for jarfile in jarfiles:
|
||||
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
|
||||
lib_deps.append(":" + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
@@ -17,7 +17,6 @@ import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
||||
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||
import com.facebook.react.ReactInstanceEventListener;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
@@ -25,13 +24,16 @@ import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.modules.network.NetworkingModule;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the debug
|
||||
* flavor of it. Here you can add your own plugins and customize the Flipper setup.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (FlipperUtils.shouldEnableFlipper(context)) {
|
||||
final FlipperClient client = AndroidFlipperClient.getInstance(context);
|
||||
|
||||
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
|
||||
client.addPlugin(new ReactFlipperPlugin());
|
||||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
|
||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||
|
@@ -1,5 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.cozic.joplin"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@@ -8,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATION" />
|
||||
|
||||
<!-- Make these features optional to enable Chromebooks -->
|
||||
<!-- https://github.com/laurent22/joplin/issues/37 -->
|
||||
|
@@ -2,7 +2,8 @@ package net.cozic.joplin;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
@@ -16,31 +17,19 @@ public class MainActivity extends ReactActivity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
|
||||
* you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
|
||||
* (Paper).
|
||||
* Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link
|
||||
* DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React
|
||||
* (aka React 18) with two boolean flags.
|
||||
*/
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new MainActivityDelegate(this, getMainComponentName());
|
||||
}
|
||||
public static class MainActivityDelegate extends ReactActivityDelegate {
|
||||
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
|
||||
super(activity, mainComponentName);
|
||||
}
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
ReactRootView reactRootView = new ReactRootView(getContext());
|
||||
return new DefaultReactActivityDelegate(
|
||||
this,
|
||||
getMainComponentName(),
|
||||
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
|
||||
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
|
||||
return reactRootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isConcurrentRootEnabled() {
|
||||
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
|
||||
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
DefaultNewArchitectureEntryPoint.getFabricEnabled(), // fabricEnabled
|
||||
// If you opted-in for the New Architecture, we enable Concurrent React (i.e. React 18).
|
||||
DefaultNewArchitectureEntryPoint.getConcurrentReactEnabled() // concurrentRootEnabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,41 +1,38 @@
|
||||
package net.cozic.joplin;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.database.CursorWindow;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.multidex.MultiDex;
|
||||
// import androidx.multidex.MultiDex;
|
||||
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import net.cozic.joplin.newarchitecture.MainApplicationReactNativeHost;
|
||||
|
||||
import net.cozic.joplin.share.SharePackage;
|
||||
import net.cozic.joplin.ssl.SslPackage;
|
||||
import net.cozic.joplin.textinput.TextInputPackage;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
// Needed to fix: The number of method references in a .dex file cannot exceed 64K
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(base);
|
||||
MultiDex.install(this);
|
||||
}
|
||||
// @Override
|
||||
// protected void attachBaseContext(Context base) {
|
||||
// super.attachBaseContext(base);
|
||||
// MultiDex.install(this);
|
||||
// }
|
||||
|
||||
private final ReactNativeHost mReactNativeHost =
|
||||
new ReactNativeHost(this) {
|
||||
new DefaultReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
@@ -56,27 +53,26 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
};
|
||||
|
||||
private final ReactNativeHost mNewArchitectureNativeHost =
|
||||
new MainApplicationReactNativeHost(this);
|
||||
@Override
|
||||
protected boolean isNewArchEnabled() {
|
||||
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
}
|
||||
@Override
|
||||
protected Boolean isHermesEnabled() {
|
||||
return BuildConfig.IS_HERMES_ENABLED;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
return mNewArchitectureNativeHost;
|
||||
} else {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// If you opted-in for the New Architecture, we enable the TurboModule system
|
||||
ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
|
||||
|
||||
// To try to fix the error "Row too big to fit into CursorWindow"
|
||||
// https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-526423153
|
||||
// https://github.com/laurent22/joplin/issues/1767#issuecomment-515617991
|
||||
@@ -89,46 +85,10 @@ public class MainApplication extends Application implements ReactApplication {
|
||||
}
|
||||
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
|
||||
// To allow debugging the webview using the Chrome developer tools.
|
||||
// Open chrome://inspect/#devices to view the device and connect to it
|
||||
// IMPORTANT: USB debugging must be enabled on the device for it to work.
|
||||
// https://github.com/react-native-webview/react-native-webview/blob/master/docs/Debugging.md
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
|
||||
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
*
|
||||
* @param context
|
||||
* @param reactInstanceManager
|
||||
*/
|
||||
private static void initializeFlipper(
|
||||
Context context, ReactInstanceManager reactInstanceManager) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
try {
|
||||
/*
|
||||
We use reflection here to pick up the class that initializes Flipper,
|
||||
since Flipper library is not available in release mode
|
||||
*/
|
||||
Class<?> aClass = Class.forName("net.cozic.joplin.ReactNativeFlipper");
|
||||
aClass
|
||||
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
|
||||
.invoke(null, context, reactInstanceManager);
|
||||
} catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchMethodException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
DefaultNewArchitectureEntryPoint.load();
|
||||
}
|
||||
ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
|
||||
}
|
||||
}
|
||||
|
@@ -1,117 +0,0 @@
|
||||
package net.cozic.joplin.newarchitecture;
|
||||
|
||||
import android.app.Application;
|
||||
import androidx.annotation.NonNull;
|
||||
import com.facebook.react.PackageList;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
|
||||
import com.facebook.react.bridge.JSIModulePackage;
|
||||
import com.facebook.react.bridge.JSIModuleProvider;
|
||||
import com.facebook.react.bridge.JSIModuleSpec;
|
||||
import com.facebook.react.bridge.JSIModuleType;
|
||||
import com.facebook.react.bridge.JavaScriptContextHolder;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.UIManager;
|
||||
import com.facebook.react.fabric.ComponentFactory;
|
||||
import com.facebook.react.fabric.CoreComponentsRegistry;
|
||||
import com.facebook.react.fabric.EmptyReactNativeConfig;
|
||||
import com.facebook.react.fabric.FabricJSIModuleProvider;
|
||||
import com.facebook.react.fabric.ReactNativeConfig;
|
||||
import com.facebook.react.uimanager.ViewManagerRegistry;
|
||||
import net.cozic.joplin.BuildConfig;
|
||||
import net.cozic.joplin.newarchitecture.components.MainComponentsRegistry;
|
||||
import net.cozic.joplin.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both
|
||||
* TurboModule delegates and the Fabric Renderer.
|
||||
*
|
||||
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
|
||||
* `newArchEnabled` property). Is ignored otherwise.
|
||||
*/
|
||||
public class MainApplicationReactNativeHost extends ReactNativeHost {
|
||||
public MainApplicationReactNativeHost(Application application) {
|
||||
super(application);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
List<ReactPackage> packages = new PackageList(this).getPackages();
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
// TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
|
||||
// packages.add(new TurboReactPackage() { ... });
|
||||
// If you have custom Fabric Components, their ViewManagers should also be loaded here
|
||||
// inside a ReactPackage.
|
||||
return packages;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected ReactPackageTurboModuleManagerDelegate.Builder
|
||||
getReactPackageTurboModuleManagerDelegateBuilder() {
|
||||
// Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary
|
||||
// for the new architecture and to use TurboModules correctly.
|
||||
return new MainApplicationTurboModuleManagerDelegate.Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JSIModulePackage getJSIModulePackage() {
|
||||
return new JSIModulePackage() {
|
||||
@Override
|
||||
public List<JSIModuleSpec> getJSIModules(
|
||||
final ReactApplicationContext reactApplicationContext,
|
||||
final JavaScriptContextHolder jsContext) {
|
||||
final List<JSIModuleSpec> specs = new ArrayList<>();
|
||||
|
||||
// Here we provide a new JSIModuleSpec that will be responsible of providing the
|
||||
// custom Fabric Components.
|
||||
specs.add(
|
||||
new JSIModuleSpec() {
|
||||
@Override
|
||||
public JSIModuleType getJSIModuleType() {
|
||||
return JSIModuleType.UIManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSIModuleProvider<UIManager> getJSIModuleProvider() {
|
||||
final ComponentFactory componentFactory = new ComponentFactory();
|
||||
CoreComponentsRegistry.register(componentFactory);
|
||||
|
||||
// Here we register a Components Registry.
|
||||
// The one that is generated with the template contains no components
|
||||
// and just provides you the one from React Native core.
|
||||
MainComponentsRegistry.register(componentFactory);
|
||||
|
||||
final ReactInstanceManager reactInstanceManager = getReactInstanceManager();
|
||||
|
||||
ViewManagerRegistry viewManagerRegistry =
|
||||
new ViewManagerRegistry(
|
||||
reactInstanceManager.getOrCreateViewManagers(reactApplicationContext));
|
||||
|
||||
return new FabricJSIModuleProvider(
|
||||
reactApplicationContext,
|
||||
componentFactory,
|
||||
ReactNativeConfig.DEFAULT_CONFIG,
|
||||
viewManagerRegistry);
|
||||
}
|
||||
});
|
||||
return specs;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,36 +0,0 @@
|
||||
package net.cozic.joplin.newarchitecture.components;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.proguard.annotations.DoNotStrip;
|
||||
import com.facebook.react.fabric.ComponentFactory;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
/**
|
||||
* Class responsible to load the custom Fabric Components. This class has native methods and needs a
|
||||
* corresponding C++ implementation/header file to work correctly (already placed inside the jni/
|
||||
* folder for you).
|
||||
*
|
||||
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
|
||||
* `newArchEnabled` property). Is ignored otherwise.
|
||||
*/
|
||||
@DoNotStrip
|
||||
public class MainComponentsRegistry {
|
||||
static {
|
||||
SoLoader.loadLibrary("fabricjni");
|
||||
}
|
||||
|
||||
@DoNotStrip private final HybridData mHybridData;
|
||||
|
||||
@DoNotStrip
|
||||
private native HybridData initHybrid(ComponentFactory componentFactory);
|
||||
|
||||
@DoNotStrip
|
||||
private MainComponentsRegistry(ComponentFactory componentFactory) {
|
||||
mHybridData = initHybrid(componentFactory);
|
||||
}
|
||||
|
||||
@DoNotStrip
|
||||
public static MainComponentsRegistry register(ComponentFactory componentFactory) {
|
||||
return new MainComponentsRegistry(componentFactory);
|
||||
}
|
||||
}
|
@@ -1,48 +0,0 @@
|
||||
package net.cozic.joplin.newarchitecture.modules;
|
||||
|
||||
import com.facebook.jni.HybridData;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class responsible to load the TurboModules. This class has native methods and needs a
|
||||
* corresponding C++ implementation/header file to work correctly (already placed inside the jni/
|
||||
* folder for you).
|
||||
*
|
||||
* <p>Please note that this class is used ONLY if you opt-in for the New Architecture (see the
|
||||
* `newArchEnabled` property). Is ignored otherwise.
|
||||
*/
|
||||
public class MainApplicationTurboModuleManagerDelegate
|
||||
extends ReactPackageTurboModuleManagerDelegate {
|
||||
|
||||
private static volatile boolean sIsSoLibraryLoaded;
|
||||
|
||||
protected MainApplicationTurboModuleManagerDelegate(
|
||||
ReactApplicationContext reactApplicationContext, List<ReactPackage> packages) {
|
||||
super(reactApplicationContext, packages);
|
||||
}
|
||||
|
||||
protected native HybridData initHybrid();
|
||||
|
||||
native boolean canCreateTurboModule(String moduleName);
|
||||
|
||||
public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
|
||||
protected MainApplicationTurboModuleManagerDelegate build(
|
||||
ReactApplicationContext context, List<ReactPackage> packages) {
|
||||
return new MainApplicationTurboModuleManagerDelegate(context, packages);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void maybeLoadOtherSoLibraries() {
|
||||
if (!sIsSoLibraryLoaded) {
|
||||
// If you change the name of your application .so file in the Android.mk file,
|
||||
// make sure you update the name here as well.
|
||||
SoLoader.loadLibrary("joplin_appmodules");
|
||||
sIsSoLibraryLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
# Define the library name here.
|
||||
project(joplin_appmodules)
|
||||
# This file includes all the necessary to let you build your application with the New Architecture.
|
||||
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
|
@@ -1,31 +0,0 @@
|
||||
#include "MainApplicationModuleProvider.h"
|
||||
|
||||
#include <rncore.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
// Here you can provide your own module provider for TurboModules coming from
|
||||
// either your application or from external libraries. The approach to follow
|
||||
// is similar to the following (for a library called `samplelibrary`:
|
||||
//
|
||||
// auto module = samplelibrary_ModuleProvider(moduleName, params);
|
||||
// if (module != nullptr) {
|
||||
// return module;
|
||||
// }
|
||||
// return rncore_ModuleProvider(moduleName, params);
|
||||
|
||||
// Module providers autolinked by RN CLI
|
||||
auto rncli_module = rncli_ModuleProvider(moduleName, params);
|
||||
if (rncli_module != nullptr) {
|
||||
return rncli_module;
|
||||
}
|
||||
|
||||
return rncore_ModuleProvider(moduleName, params);
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,16 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ReactCommon/JavaTurboModule.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
std::shared_ptr<TurboModule> MainApplicationModuleProvider(
|
||||
const std::string &moduleName,
|
||||
const JavaTurboModule::InitParams ¶ms);
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,45 +0,0 @@
|
||||
#include "MainApplicationTurboModuleManagerDelegate.h"
|
||||
#include "MainApplicationModuleProvider.h"
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
jni::local_ref<MainApplicationTurboModuleManagerDelegate::jhybriddata>
|
||||
MainApplicationTurboModuleManagerDelegate::initHybrid(
|
||||
jni::alias_ref<jhybridobject>) {
|
||||
return makeCxxInstance();
|
||||
}
|
||||
|
||||
void MainApplicationTurboModuleManagerDelegate::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod(
|
||||
"initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid),
|
||||
makeNativeMethod(
|
||||
"canCreateTurboModule",
|
||||
MainApplicationTurboModuleManagerDelegate::canCreateTurboModule),
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) {
|
||||
// Not implemented yet: provide pure-C++ NativeModules here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule>
|
||||
MainApplicationTurboModuleManagerDelegate::getTurboModule(
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) {
|
||||
return MainApplicationModuleProvider(name, params);
|
||||
}
|
||||
|
||||
bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
|
||||
const std::string &name) {
|
||||
return getTurboModule(name, nullptr) != nullptr ||
|
||||
getTurboModule(name, {.moduleName = name}) != nullptr;
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,38 +0,0 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <ReactCommon/TurboModuleManagerDelegate.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class MainApplicationTurboModuleManagerDelegate
|
||||
: public jni::HybridClass<
|
||||
MainApplicationTurboModuleManagerDelegate,
|
||||
TurboModuleManagerDelegate> {
|
||||
public:
|
||||
// Adapt it to the package you used for your Java class.
|
||||
static constexpr auto kJavaDescriptor =
|
||||
"Lnet/cozic/joplin/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate;";
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject>);
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker) override;
|
||||
std::shared_ptr<TurboModule> getTurboModule(
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms) override;
|
||||
|
||||
/**
|
||||
* Test-only method. Allows user to verify whether a TurboModule can be
|
||||
* created by instances of this class.
|
||||
*/
|
||||
bool canCreateTurboModule(const std::string &name);
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,65 +0,0 @@
|
||||
#include "MainComponentsRegistry.h"
|
||||
|
||||
#include <CoreComponentsRegistry.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
||||
#include <react/renderer/components/rncore/ComponentDescriptors.h>
|
||||
#include <rncli.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {}
|
||||
|
||||
std::shared_ptr<ComponentDescriptorProviderRegistry const>
|
||||
MainComponentsRegistry::sharedProviderRegistry() {
|
||||
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
|
||||
|
||||
// Autolinked providers registered by RN CLI
|
||||
rncli_registerProviders(providerRegistry);
|
||||
|
||||
// Custom Fabric Components go here. You can register custom
|
||||
// components coming from your App or from 3rd party libraries here.
|
||||
//
|
||||
// providerRegistry->add(concreteComponentDescriptorProvider<
|
||||
// AocViewerComponentDescriptor>());
|
||||
return providerRegistry;
|
||||
}
|
||||
|
||||
jni::local_ref<MainComponentsRegistry::jhybriddata>
|
||||
MainComponentsRegistry::initHybrid(
|
||||
jni::alias_ref<jclass>,
|
||||
ComponentFactory *delegate) {
|
||||
auto instance = makeCxxInstance(delegate);
|
||||
|
||||
auto buildRegistryFunction =
|
||||
[](EventDispatcher::Weak const &eventDispatcher,
|
||||
ContextContainer::Shared const &contextContainer)
|
||||
-> ComponentDescriptorRegistry::Shared {
|
||||
auto registry = MainComponentsRegistry::sharedProviderRegistry()
|
||||
->createComponentDescriptorRegistry(
|
||||
{eventDispatcher, contextContainer});
|
||||
|
||||
auto mutableRegistry =
|
||||
std::const_pointer_cast<ComponentDescriptorRegistry>(registry);
|
||||
|
||||
mutableRegistry->setFallbackComponentDescriptor(
|
||||
std::make_shared<UnimplementedNativeViewComponentDescriptor>(
|
||||
ComponentDescriptorParameters{
|
||||
eventDispatcher, contextContainer, nullptr}));
|
||||
|
||||
return registry;
|
||||
};
|
||||
|
||||
delegate->buildRegistryFunction = buildRegistryFunction;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void MainComponentsRegistry::registerNatives() {
|
||||
registerHybrid({
|
||||
makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ComponentFactory.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorRegistry.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
class MainComponentsRegistry
|
||||
: public facebook::jni::HybridClass<MainComponentsRegistry> {
|
||||
public:
|
||||
// Adapt it to the package you used for your Java class.
|
||||
constexpr static auto kJavaDescriptor =
|
||||
"Lnet/cozic/joplin/newarchitecture/components/MainComponentsRegistry;";
|
||||
|
||||
static void registerNatives();
|
||||
|
||||
MainComponentsRegistry(ComponentFactory *delegate);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<ComponentDescriptorProviderRegistry const>
|
||||
sharedProviderRegistry();
|
||||
|
||||
static jni::local_ref<jhybriddata> initHybrid(
|
||||
jni::alias_ref<jclass>,
|
||||
ComponentFactory *delegate);
|
||||
};
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
@@ -1,11 +0,0 @@
|
||||
#include <fbjni/fbjni.h>
|
||||
#include "MainApplicationTurboModuleManagerDelegate.h"
|
||||
#include "MainComponentsRegistry.h"
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
||||
return facebook::jni::initialize(vm, [] {
|
||||
facebook::react::MainApplicationTurboModuleManagerDelegate::
|
||||
registerNatives();
|
||||
facebook::react::MainComponentsRegistry::registerNatives();
|
||||
});
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M5.84 0C2.63 0 0 2.63 0 5.84V18.16C0 21.37 2.63 24 5.84 24H18.16C21.37 24 24 21.37 24 18.16V5.84C24 2.63 21.37 0 18.16 0Zm6.21 3.21h6.52a0.11 0.11 0 0 1 0.11 0.11v2.07c0 0.07-0.06 0.13-0.12 0.13h-0.9a0.69 0.69 0 0 0-0.69 0.68v2.16h0l-0.01 7.96a4.63 4.63 0 0 1-1.5 3.14c-0.94 0.86-2.22 1.33-3.6 1.33-0.19 0-0.39-0.01-0.59-0.03a6.84 6.84 0 0 1-3.93-1.75c-1.13-1.02-1.83-2.34-1.99-3.72-0.17-1.54 0.37-3.01 1.49-4.03 1.28-1.17 3.1-1.59 4.94-1.19 0.09 0.02 0.15 0.1 0.15 0.19v3.12c0 0.04-0.02 0.1-0.12 0.04a0.06 0.06 0 0 0-0.03 0 3.22 3.22 0 0 0-1.07-0.31c-0.01 0-0.01 0-0.02-0.01-0.01 0-0.02 0-0.02 0a2.75 2.75 0 0 0-0.19-0.01l-0.05 0-0.02 0c-0.02 0-0.04 0-0.07 0a2.53 2.53 0 0 0-0.12 0.01l-0.02 0c-0.4 0.04-0.75 0.17-1.01 0.4l-0.05 0.04a0.02 0.02 0 0 1 0 0c-0.38 0.34-0.55 0.84-0.48 1.41 0.07 0.64 0.42 1.27 0.98 1.78 0.56 0.51 1.26 0.83 1.96 0.89 0.55 0.05 1.04-0.06 1.4-0.32a1.52 1.52 0 0 0 0.15-0.11c0.05-0.04 0.09-0.09 0.12-0.14l0.02-0.01c0.13-0.15 0.22-0.33 0.28-0.52 0.01-0.03 0.02-0.06 0.02-0.1l0.02-0.07c0.01-0.04 0.01-0.09 0.02-0.13l0-0.04a1.81 1.81 0 0 0 0.01-0.23v-9.8a0.69 0.69 0 0 0-0.68-0.63h-0.9a0.13 0.13 0 0 1-0.12-0.13v-2.07a0.11 0.11 0 0 1 0.11-0.11Z"/>
|
||||
</vector>
|
Before Width: | Height: | Size: 568 B |
Before Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 697 B |
Before Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 13 KiB |