1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

42 Commits

Author SHA1 Message Date
Laurent Cozic
a0e9da705c Android 2.11.9 2023-05-07 20:03:26 +01:00
Laurent Cozic
530a620d68 Chore: Fix Android buiold 2023-05-07 18:29:10 +01:00
Laurent Cozic
1f2ecd08eb Android 2.11.8 2023-05-07 18:26:16 +01:00
Laurent Cozic
d5eeb12ce7 Android: Fix voice typing 2023-05-07 17:53:19 +01:00
Laurent Cozic
e9e9986978 Mobile: Disable Hermes engine 2023-05-07 17:52:58 +01:00
Laurent Cozic
d44f212b95 Android 2.11.7 2023-05-07 15:29:58 +01:00
Laurent Cozic
47be6bcde3 Chore: Mobile: Fixed typo 2023-05-07 14:56:02 +01:00
Laurent Cozic
ef256a82ab Android 2.11.6 2023-05-07 14:53:51 +01:00
Laurent Cozic
b4d56f42f7 Chore: Disable Hermes to try to debug crash
Ref: https://github.com/facebook/react-native/issues/29165
2023-05-07 14:24:40 +01:00
Laurent Cozic
c1598c1a59 Doc: Update Android changelog 2023-05-07 14:24:26 +01:00
Laurent Cozic
3b6decfaf6 lock file 2023-05-07 12:48:30 +01:00
Laurent Cozic
5494e8c3dc Mobile: Improved Vosk support (beta, fr only) (#8131) 2023-05-07 12:05:41 +01:00
renovate[bot]
39059ae6bf Update dependency reselect to v4.1.8 (#8137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-07 02:10:24 +00:00
renovate[bot]
5773f63664 Update dependency react-native-share to v8.2.2 (#8130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-04 18:03:17 +00:00
renovate[bot]
5698c30d1a Update dependency sharp to v0.32.0 (#8124)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-04 12:12:27 +01:00
github-actions[bot]
218d69cf38 @cas-- has signed the CLA in laurent22/joplin#8126 2023-05-04 08:42:05 +00:00
renovate[bot]
00369fd613 Update dependency koa to v2.14.2 (#8111)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-03 16:11:52 +00:00
Laurent Cozic
ef86e01cd6 Android 2.11.4 2023-05-03 13:09:52 +01:00
Laurent Cozic
efd9f740ca Tools: Fixed git-changelog logic 2023-05-03 12:47:40 +01:00
Laurent Cozic
36c121523a Android: Add support for offline speech to text (Beta - FR only) (#8115) 2023-05-03 12:19:43 +01:00
Laurent Cozic
991c12025b Mobile: Fixes issue where the note body is not updated after attaching a file 2023-05-03 11:45:19 +01:00
renovate[bot]
f1d6945cd5 Update dependency sass to v1.60.0 (#8116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-03 09:35:15 +01:00
Laurent Cozic
2ebebd1dfa Doc: Fix image 2023-05-03 09:33:22 +01:00
Linkosred
d955ed464e Update faq_joplin_cloud.md (#8113) 2023-05-02 14:42:38 +01:00
github-actions[bot]
b7695115da @Linkosred has signed the CLA in laurent22/joplin#8110 2023-05-02 09:54:29 +00:00
renovate[bot]
f274678ad7 Update dependency react-native-document-picker to v8.2.0 (#8107)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-01 15:01:06 +01:00
renovate[bot]
5ff0309586 Update aws (#8106)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-01 07:36:51 +00:00
Joplin Bot
ffb6c25ce7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-01 00:39:43 +00:00
renovate[bot]
7237ae319a Update dependency gettext-extractor to v3.7.1 (#8102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-30 20:36:51 +00:00
Jonatan
cdd5a911a9 All: Translation: Update sv.po (#8103) 2023-04-30 11:01:25 -04:00
github-actions[bot]
66b53f013d @reportxx has signed the CLA in laurent22/joplin#8103 2023-04-30 11:19:00 +00:00
Laurent Cozic
4d9ffffe56 Doc: Update Paypal link 2023-04-30 10:50:14 +01:00
Laurent Cozic
c08f7de6bb Update architecture.md 2023-04-29 14:39:15 +01:00
Laurent Cozic
77789c0b17 Doc: Added architecture document 2023-04-29 11:40:13 +01:00
renovate[bot]
be7056ff59 Update dependency react-native-safe-area-context to v4.5.1 (#8097)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-28 22:30:07 +00:00
renovate[bot]
274a55a31f Update dependency lint-staged to v13.2.1 (#8091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-28 01:07:50 +00:00
Joplin Bot
32977c4e3f Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-27 12:21:00 +00:00
renovate[bot]
075b1626bd Update dependency @react-native-community/netinfo to v9.3.9 (#8086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-27 12:19:05 +00:00
Joplin Bot
fd6538fdc7 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-04-27 00:42:00 +00:00
Torgny Bjers
b897191cc8 Doc: add note about reference links (#8036)
Co-authored-by: Helmut K. C. Tessarek <tessarek@evermeet.cx>
2023-04-26 19:01:25 -04:00
Laurent Cozic
31897581d3 Desktop: Resolves #8028: Remove custom PDF viewer to reduce application size 2023-04-26 21:45:02 +01:00
Douglas Leão
6c1820edc0 All: Translation: Update pt_BR.po (#8077) 2023-04-26 09:53:09 -04:00
101 changed files with 9405 additions and 1478 deletions

View File

@@ -74,6 +74,7 @@ packages/turndown/
packages/pdf-viewer/dist
plugin_types/
readme/
packages/react-native-vosk/lib/
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
packages/app-cli/app/LinkSelector.js
@@ -417,12 +418,15 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/search.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.dummy.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/utils/ShareExtension.js
@@ -807,6 +811,7 @@ packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/types.js
packages/plugins/ToggleSidebars/src/index.js
packages/react-native-saf-x/src/index.js
packages/react-native-vosk/src/index.js
packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js

4
.gitignore vendored
View File

@@ -404,12 +404,15 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/search.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
packages/app-mobile/gulpfile.js
packages/app-mobile/root.js
packages/app-mobile/services/AlarmServiceDriver.android.js
packages/app-mobile/services/AlarmServiceDriver.ios.js
packages/app-mobile/services/e2ee/RSA.react-native.js
packages/app-mobile/services/profiles/index.js
packages/app-mobile/services/voiceTyping/vosk.dummy.js
packages/app-mobile/services/voiceTyping/vosk.js
packages/app-mobile/setupQuickActions.js
packages/app-mobile/tools/buildInjectedJs.js
packages/app-mobile/utils/ShareExtension.js
@@ -794,6 +797,7 @@ packages/plugins/ToggleSidebars/api/index.js
packages/plugins/ToggleSidebars/api/types.js
packages/plugins/ToggleSidebars/src/index.js
packages/react-native-saf-x/src/index.js
packages/react-native-vosk/src/index.js
packages/renderer/HtmlToHtml.js
packages/renderer/InMemoryCache.js
packages/renderer/MarkupToHtml.js

View File

@@ -15,6 +15,7 @@
"@joplin/tools",
"@joplin/react-native-saf-x",
"@joplin/react-native-alarm-notification",
"@joplin/react-native-vosk",
"@joplin/utils"
]
}

View File

@@ -0,0 +1,127 @@
diff --git a/android/build.gradle b/android/build.gradle
index 6afcbbf0cc8ca2d69dd78077d61e59a90b2136bb..9f8d72b4ec5b2b3d290975d6a255917c95300854 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -67,19 +67,19 @@ repositories {
}
// Generate UUIDs for each models contained in android/src/main/assets/
-tasks.register('genUUID') {
- doLast {
- fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
- if (fileDetails.directory) {
- def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
- def ofile = file("$odir/uuid")
- mkdir odir
- ofile.text = UUID.randomUUID().toString()
- }
- }
- }
-}
-preBuild.dependsOn genUUID
+// tasks.register('genUUID') {
+// doLast {
+// fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
+// if (fileDetails.directory) {
+// def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
+// def ofile = file("$odir/uuid")
+// mkdir odir
+// ofile.text = UUID.randomUUID().toString()
+// }
+// }
+// }
+// }
+// preBuild.dependsOn genUUID
def kotlin_version = getExtOrDefault('kotlinVersion')
diff --git a/android/src/main/java/com/reactnativevosk/VoskModule.kt b/android/src/main/java/com/reactnativevosk/VoskModule.kt
index 0e2b6595b1b2cf1ee01c6c64239c4b0ea37fce19..f3da440bc2863a59db6d2d1691c54d8d4870cb3f 100644
--- a/android/src/main/java/com/reactnativevosk/VoskModule.kt
+++ b/android/src/main/java/com/reactnativevosk/VoskModule.kt
@@ -19,13 +19,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
return "Vosk"
}
+ @ReactMethod
+ fun addListener(type: String?) {
+ // Keep: Required for RN built in Event Emitter Calls.
+ }
+
+ @ReactMethod
+ fun removeListeners(type: Int?) {
+ // Keep: Required for RN built in Event Emitter Calls.
+ }
+
override fun onResult(hypothesis: String) {
// Get text data from string object
val text = getHypothesisText(hypothesis)
// Stop recording if data found
if (text != null && text.isNotEmpty()) {
- cleanRecognizer();
+ // Don't auto-stop the recogniser - we want to do that when the user
+ // presses on "stop" only.
+ // cleanRecognizer();
sendEvent("onResult", text)
}
}
@@ -153,6 +165,25 @@ class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
cleanRecognizer();
}
+ @ReactMethod
+ fun stopOnly() {
+ if (speechService != null) {
+ speechService!!.stop()
+ }
+ }
+
+ @ReactMethod
+ fun cleanup() {
+ if (speechService != null) {
+ speechService!!.shutdown();
+ speechService = null
+ }
+ if (recognizer != null) {
+ recognizer!!.close();
+ recognizer = null;
+ }
+ }
+
@ReactMethod
fun unload() {
cleanRecognizer();
diff --git a/lib/typescript/index.d.ts b/lib/typescript/index.d.ts
index 441e41cc402cca3a60b34978ef4fea976076259c..a173acebb4b314402550442ad471e0f7c706e3c4 100644
--- a/lib/typescript/index.d.ts
+++ b/lib/typescript/index.d.ts
@@ -10,6 +10,8 @@ export default class Vosk {
currentRegisteredEvents: EmitterSubscription[];
start: (grammar?: string[] | null) => Promise<String>;
stop: () => void;
+ stopOnly: () => void;
+ cleanup: () => void;
unload: () => void;
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
diff --git a/src/index.tsx b/src/index.tsx
index d9f90c921d89b1b4d85e145443ed3376546a368a..29e4068dbd7500828a73145bd25497a52c9bf638 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -69,6 +69,15 @@ export default class Vosk {
VoskModule.stop();
};
+ stopOnly = () => {
+ VoskModule.stopOnly();
+ };
+
+ cleanup = () => {
+ this.cleanListeners();
+ VoskModule.cleanup();
+ };
+
unload = () => {
this.cleanListeners();
VoskModule.unload();

View File

@@ -0,0 +1,136 @@
<mxfile host="Electron" modified="2023-04-29T09:42:39.598Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.2.1 Chrome/112.0.5615.87 Electron/24.1.2 Safari/537.36" etag="apmX4QvXCQymGu7gtKJn" version="21.2.1" type="device">
<diagram name="Page-1" id="5f0bae14-7c28-e335-631c-24af17079c00">
<mxGraphModel dx="1244" dy="759" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="93vzSs2z7RmF_nCAYhdf-1" value="Front end" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="465" y="120" width="170" height="60" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-4" value="Service" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="280" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-5" value="Service" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="490" y="280" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-6" value="Service" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="670" y="280" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-10" value="Model" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="430" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-11" value="Model" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="490" y="430" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-12" value="Model" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="670" y="430" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-14" value="SQLite database" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="490" y="580" width="122.5" height="100" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-19" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-10" target="93vzSs2z7RmF_nCAYhdf-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-20" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-11" target="93vzSs2z7RmF_nCAYhdf-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-21" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-12" target="93vzSs2z7RmF_nCAYhdf-4">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-22" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-10" target="93vzSs2z7RmF_nCAYhdf-5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-23" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-11" target="93vzSs2z7RmF_nCAYhdf-5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-24" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-12" target="93vzSs2z7RmF_nCAYhdf-5">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-25" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-10" target="93vzSs2z7RmF_nCAYhdf-6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-26" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-11" target="93vzSs2z7RmF_nCAYhdf-6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-27" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-12" target="93vzSs2z7RmF_nCAYhdf-6">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-28" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-10" target="93vzSs2z7RmF_nCAYhdf-14">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-29" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-14" target="93vzSs2z7RmF_nCAYhdf-11">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-30" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-14" target="93vzSs2z7RmF_nCAYhdf-12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="700" y="440" as="sourcePoint" />
<mxPoint x="750" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-31" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-4" target="93vzSs2z7RmF_nCAYhdf-1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="620" y="440" as="sourcePoint" />
<mxPoint x="670" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-32" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-5" target="93vzSs2z7RmF_nCAYhdf-1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="620" y="440" as="sourcePoint" />
<mxPoint x="670" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-33" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-6" target="93vzSs2z7RmF_nCAYhdf-1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="620" y="440" as="sourcePoint" />
<mxPoint x="670" y="390" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-37" value="BACKEND" style="swimlane;whiteSpace=wrap;html=1;swimlaneFillColor=none;shadow=0;" vertex="1" parent="1">
<mxGeometry x="280" y="230" width="560" height="480" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-38" value="JSON config file" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;" vertex="1" parent="1">
<mxGeometry x="910" y="420" width="80" height="100" as="geometry" />
</mxCell>
<mxCell id="93vzSs2z7RmF_nCAYhdf-39" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="93vzSs2z7RmF_nCAYhdf-37" target="93vzSs2z7RmF_nCAYhdf-38">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="830" y="480" as="sourcePoint" />
<mxPoint x="800" y="580" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -0,0 +1,70 @@
<mxfile host="Electron" modified="2023-04-29T10:24:42.580Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.2.1 Chrome/112.0.5615.87 Electron/24.1.2 Safari/537.36" etag="kcPEKHJGaBvNGFhEOF2g" version="21.2.1" type="device">
<diagram name="Page-1" id="5f0bae14-7c28-e335-631c-24af17079c00">
<mxGraphModel dx="1306" dy="797" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="850" background="none" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="t8PL5avYcYxuv0YEq-6K-7" value="Joplin Server" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="40" width="630" height="465" as="geometry">
<mxRectangle x="350" y="300" width="120" height="30" as="alternateBounds" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-2" value="Server application" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="t8PL5avYcYxuv0YEq-6K-7">
<mxGeometry x="270" y="92.5" width="170" height="80" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-3" value="PostgreSQL" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="t8PL5avYcYxuv0YEq-6K-7">
<mxGeometry x="190" y="262.5" width="140" height="110" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-5" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="t8PL5avYcYxuv0YEq-6K-7" source="t8PL5avYcYxuv0YEq-6K-3" target="t8PL5avYcYxuv0YEq-6K-2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="280" y="232.5" as="sourcePoint" />
<mxPoint x="330" y="182.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-11" value="Note metadata,&lt;br&gt;user accounts, etc." style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="t8PL5avYcYxuv0YEq-6K-5">
<mxGeometry x="0.0586" y="1" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-4" value="AWS S3" style="shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;" vertex="1" parent="t8PL5avYcYxuv0YEq-6K-7">
<mxGeometry x="430" y="277.5" width="120" height="80" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-6" value="Note and attachment&lt;br&gt;content" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0;exitY=0;exitDx=50;exitDy=0;exitPerimeter=0;" edge="1" parent="t8PL5avYcYxuv0YEq-6K-7" source="t8PL5avYcYxuv0YEq-6K-4" target="t8PL5avYcYxuv0YEq-6K-2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="390" y="242.5" as="sourcePoint" />
<mxPoint x="440" y="192.5" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-12" value="Reverse proxy" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="t8PL5avYcYxuv0YEq-6K-7">
<mxGeometry x="70" y="97.5" width="75" height="75" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-13" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;" edge="1" parent="t8PL5avYcYxuv0YEq-6K-7" source="t8PL5avYcYxuv0YEq-6K-12" target="t8PL5avYcYxuv0YEq-6K-2">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="180" y="160" as="sourcePoint" />
<mxPoint x="230" y="110" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-18" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="t8PL5avYcYxuv0YEq-6K-7" source="t8PL5avYcYxuv0YEq-6K-14" target="t8PL5avYcYxuv0YEq-6K-2">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-14" value="Env file (config)" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;" vertex="1" parent="t8PL5avYcYxuv0YEq-6K-7">
<mxGeometry x="540" y="90" width="60" height="85" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-8" value="Joplin Application (mobile, desktop, ...)" style="whiteSpace=wrap;html=1;aspect=fixed;" vertex="1" parent="1">
<mxGeometry x="40" y="110" width="130" height="130" as="geometry" />
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-9" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="t8PL5avYcYxuv0YEq-6K-8" target="t8PL5avYcYxuv0YEq-6K-12">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="130" y="270" as="sourcePoint" />
<mxPoint x="310" y="230" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="t8PL5avYcYxuv0YEq-6K-10" value="HTTP REST requests" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="t8PL5avYcYxuv0YEq-6K-9">
<mxGeometry x="0.0435" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -1,5 +1,5 @@
<!-- DONATELINKS -->
[![Donate using PayPal](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [![Sponsor on GitHub](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg)](https://github.com/sponsors/laurent22/) [![Become a patron](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin) [![Donate using IBAN](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg)](https://joplinapp.org/donate/#donations)
[![Donate using PayPal](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&no_recurring=0&item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21&currency_code=EUR) [![Sponsor on GitHub](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg)](https://github.com/sponsors/laurent22/) [![Become a patron](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin) [![Donate using IBAN](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg)](https://joplinapp.org/donate/#donations)
<!-- DONATELINKS -->
<img width="64" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/LinuxIcons/256x256.png" align="left" /> **Joplin** is a free, open source note taking and to-do application, which can handle a large number of notes organised into notebooks. The notes are searchable, can be copied, tagged and modified either from the applications directly or from your own text editor. The notes are in [Markdown format](#markdown).
@@ -125,10 +125,11 @@ A community maintained list of these distributions can be found here: [Unofficia
- [How to build the apps](https://github.com/laurent22/joplin/blob/dev/BUILD.md)
- [Writing a technical spec](https://github.com/laurent22/joplin/blob/dev/readme/technical_spec.md)
- [Desktop application styling](https://github.com/laurent22/joplin/blob/dev/readme/spec/desktop_styling.md)
- [Note History spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Note history spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/history.md)
- [Synchronisation spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync.md)
- [Sync Lock spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_lock.md)
- [Synchronous Scroll spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/sync_scroll.md)
- [Overall Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/architecture.md)
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
- [Search Sorting spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/search_sorting.md)
- [E2EE: Technical spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)

View File

@@ -81,7 +81,7 @@
"gulp": "4.0.2",
"husky": "3.1.0",
"lerna": "3.22.1",
"lint-staged": "13.2.0",
"lint-staged": "13.2.1",
"madge": "6.0.0",
"npm-package-json-lint": "6.4.0",
"typedoc": "0.17.8",
@@ -96,6 +96,7 @@
"packageManager": "yarn@3.3.1",
"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"
"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"
}
}

View File

@@ -44,7 +44,7 @@
"@joplin/lib": "~2.11",
"@joplin/renderer": "~2.11",
"@joplin/utils": "~2.11",
"aws-sdk": "2.1290.0",
"aws-sdk": "2.1340.0",
"chalk": "4.1.2",
"compare-version": "0.1.2",
"fs-extra": "11.1.1",
@@ -57,7 +57,7 @@
"proper-lockfile": "4.1.2",
"read-chunk": "2.1.0",
"server-destroy": "1.0.1",
"sharp": "0.31.3",
"sharp": "0.32.0",
"sprintf-js": "1.1.2",
"sqlite3": "5.1.6",
"string-padding": "1.0.2",

View File

@@ -631,7 +631,7 @@ const mapStateToProps = (state: AppState) => {
], whenClauseContext)[0],
contentMaxWidth: state.settings['style.editor.contentMaxWidth'],
isSafeMode: state.settings.isSafeMode,
useCustomPdfViewer: state.settings.useCustomPdfViewer,
useCustomPdfViewer: false, // state.settings.useCustomPdfViewer,
};
};

View File

@@ -138,7 +138,6 @@
"@fortawesome/fontawesome-free": "5.15.4",
"@joeattardi/emoji-button": "4.6.4",
"@joplin/lib": "~2.11",
"@joplin/pdf-viewer": "~2.11",
"@joplin/renderer": "~2.11",
"async-mutex": "0.4.0",
"codemirror": "5.65.9",
@@ -168,7 +167,7 @@
"react-toggle-button": "2.2.0",
"react-tooltip": "4.5.1",
"redux": "4.2.1",
"reselect": "4.1.7",
"reselect": "4.1.8",
"roboto-fontface": "0.10.0",
"smalltalk": "2.5.1",
"sqlite3": "5.1.6",

View File

@@ -72,10 +72,10 @@ async function main() {
src: langSourceDir,
dest: `${buildLibDir}/tinymce/langs`,
},
{
src: resolve(__dirname, '../../pdf-viewer/dist'),
dest: `${buildLibDir}/@joplin/pdf-viewer`,
},
// {
// src: resolve(__dirname, '../../pdf-viewer/dist'),
// dest: `${buildLibDir}/@joplin/pdf-viewer`,
// },
];
const files = [
@@ -93,10 +93,10 @@ async function main() {
src: resolve(__dirname, '../../lib/services/plugins/sandboxProxy.js'),
dest: `${buildLibDir}/@joplin/lib/services/plugins/sandboxProxy.js`,
},
{
src: resolve(__dirname, '../../pdf-viewer/index.html'),
dest: `${buildLibDir}/@joplin/pdf-viewer/index.html`,
},
// {
// src: resolve(__dirname, '../../pdf-viewer/index.html'),
// dest: `${buildLibDir}/@joplin/pdf-viewer/index.html`,
// },
];
// First we delete all the destination directories, then we copy the files.

View File

@@ -72,3 +72,4 @@ components/NoteEditor/CodeMirror/CodeMirror.bundle.min.js
components/NoteEditor/**/*.bundle.js.md5
utils/fs-driver-android.js
android/app/build-*

View File

@@ -79,7 +79,9 @@ import org.apache.tools.ant.taskdefs.condition.Os
*/
project.ext.react = [
enableHermes: true, // clean and rebuild if changing
// 2023/05/07: Leave that to `false` for now because Hermes is rubbish at
// reporting errors, which it makes it impossible to investigate crashes.
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
@@ -150,8 +152,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097687
versionName "2.11.2"
versionCode 2097694
versionName "2.11.9"
// ndk {
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
// }

View File

@@ -0,0 +1,7 @@
French small model for Vosk
WER
%WER 23.95 [ 37203 / 155330, 5373 ins, 4427 del, 27403 sub ] exp/chain_a/tdnn/decode_test_cv/wer_12_0.0
%WER 19.30 [ 2975 / 15412, 683 ins, 672 del, 1620 sub ] exp/chain_a/tdnn/decode_test_mtedx/wer_10_0.0
%WER 27.25 [ 20208 / 74145, 2647 ins, 5852 del, 11709 sub ] exp/chain_a/tdnn/decode_test_podcast_reseg/wer_10_0.0

View File

@@ -0,0 +1,8 @@
--use-energy=false
--sample-frequency=16000
--num-mel-bins=40
--num-ceps=40
--low-freq=40
--high-freq=-200
--allow-upsample=true
--allow-downsample=true

View File

@@ -0,0 +1,10 @@
--min-active=200
--max-active=7000
--beam=13.0
--lattice-beam=4.0
--acoustic-scale=1.0
--frame-subsampling-factor=3
--endpoint.silence-phones=1:2:3:4:5:6:7:8:9:10
--endpoint.rule2.min-trailing-silence=0.5
--endpoint.rule3.min-trailing-silence=1.0
--endpoint.rule4.min-trailing-silence=2.0

View File

@@ -0,0 +1,76 @@
9365
9366
9367
9368
9369
9370
9371
9372
9373
9374
9375
9376
9377
9378
9379
9380
9381
9382
9383
9384
9385
9386
9387
9388
9389
9390
9391
9392
9393
9394
9395
9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408
9409
9410
9411
9412
9413
9414
9415
9416
9417
9418
9419
9420
9421
9422
9423
9424
9425
9426
9427
9428
9429
9430
9431
9432
9433
9434
9435
9436
9437
9438
9439
9440

View File

@@ -0,0 +1,154 @@
1 nonword
2 begin
3 end
4 internal
5 singleton
6 nonword
7 begin
8 end
9 internal
10 singleton
11 begin
12 end
13 internal
14 singleton
15 begin
16 end
17 internal
18 singleton
19 begin
20 end
21 internal
22 singleton
23 begin
24 end
25 internal
26 singleton
27 begin
28 end
29 internal
30 singleton
31 begin
32 end
33 internal
34 singleton
35 begin
36 end
37 internal
38 singleton
39 begin
40 end
41 internal
42 singleton
43 begin
44 end
45 internal
46 singleton
47 begin
48 end
49 internal
50 singleton
51 begin
52 end
53 internal
54 singleton
55 begin
56 end
57 internal
58 singleton
59 begin
60 end
61 internal
62 singleton
63 begin
64 end
65 internal
66 singleton
67 begin
68 end
69 internal
70 singleton
71 begin
72 end
73 internal
74 singleton
75 begin
76 end
77 internal
78 singleton
79 begin
80 end
81 internal
82 singleton
83 begin
84 end
85 internal
86 singleton
87 begin
88 end
89 internal
90 singleton
91 begin
92 end
93 internal
94 singleton
95 begin
96 end
97 internal
98 singleton
99 begin
100 end
101 internal
102 singleton
103 begin
104 end
105 internal
106 singleton
107 begin
108 end
109 internal
110 singleton
111 begin
112 end
113 internal
114 singleton
115 begin
116 end
117 internal
118 singleton
119 begin
120 end
121 internal
122 singleton
123 begin
124 end
125 internal
126 singleton
127 begin
128 end
129 internal
130 singleton
131 begin
132 end
133 internal
134 singleton
135 begin
136 end
137 internal
138 singleton
139 begin
140 end
141 internal
142 singleton
143 begin
144 end
145 internal
146 singleton
147 begin
148 end
149 internal
150 singleton
151 begin
152 end
153 internal
154 singleton

View File

@@ -0,0 +1,3 @@
[
1.022245e+11 -6.33291e+09 -2.480997e+09 8.290258e+09 -9.084483e+09 -8.092173e+09 -1.4735e+10 -7.041795e+09 -1.171205e+10 -2.976464e+08 -1.009425e+10 -6765179 -7.821326e+09 1.449499e+09 -6.413975e+09 -5.303802e+08 -4.998635e+09 9.521598e+07 -3.073041e+09 1.56756e+08 -1.287956e+09 1.738752e+08 -2.382392e+08 -2.716675e+07 4.404485e+08 -1.913359e+08 7.780919e+08 -4.006922e+08 7.895809e+08 -5.401082e+08 5.17605e+08 -6.227134e+08 6.58271e+08 -6.204593e+07 5.187754e+08 -4.497048e+08 4.219366e+07 -2.78742e+08 -1.797385e+07 -3.604475e+07 1.053647e+09
1.040194e+13 6.245521e+11 4.223293e+11 6.831219e+11 6.078478e+11 6.3425e+11 7.943839e+11 6.013323e+11 6.781652e+11 5.272091e+11 5.810814e+11 4.353831e+11 4.473305e+11 3.42063e+11 3.083377e+11 2.14257e+11 1.892057e+11 1.163827e+11 8.367058e+10 4.203224e+10 2.297476e+10 7.596307e+09 1.099877e+09 2.886651e+08 3.797438e+09 9.372847e+09 1.629059e+10 2.196351e+10 2.747149e+10 3.072878e+10 3.238528e+10 3.330232e+10 3.407238e+10 3.230687e+10 2.676914e+10 2.252055e+10 1.914305e+10 1.565974e+10 1.224627e+10 8.415393e+09 0 ]

View File

@@ -0,0 +1,2 @@
--left-context=3
--right-context=3

View File

@@ -0,0 +1 @@
1b7180e6-e500-4818-adc8-a41fe97a84ce

View File

@@ -3,8 +3,11 @@ import shim from '@joplin/lib/shim';
import Setting from '@joplin/lib/models/Setting';
const { themeStyle } = require('../../global-style.js');
import markupLanguageUtils from '@joplin/lib/markupLanguageUtils';
import Logger from '@joplin/lib/Logger';
const { assetsToHeaders } = require('@joplin/renderer');
const logger = Logger.create('NoteBodyViewer/useSource');
interface UseSourceResult {
// [html] can be null if the note is still being rendered.
html: string|null;
@@ -19,6 +22,23 @@ function usePrevious(value: any, initialValue: any = null): any {
return ref.current;
}
const onlyCheckboxHasChangedHack = (previousBody: string, newBody: string) => {
if (previousBody.length !== newBody.length) return false;
for (let i = 0; i < previousBody.length; i++) {
const c1 = previousBody.charAt(i);
const c2 = newBody.charAt(i);
if (c1 !== c2) {
if (c1 === ' ' && (c2 === 'x' || c2 === 'X')) continue;
if (c2 === ' ' && (c1 === 'x' || c1 === 'X')) continue;
return false;
}
}
return true;
};
export default function useSource(noteBody: string, noteMarkupLanguage: number, themeId: number, highlightedKeywords: string[], noteResources: any, paddingBottom: number, noteHash: string): UseSourceResult {
const [html, setHtml] = useState<string>('');
const [injectedJs, setInjectedJs] = useState<string[]>([]);
@@ -42,14 +62,20 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
// To address https://github.com/laurent22/joplin/issues/433
//
// If a checkbox in a note is ticked, the body changes, which normally
// would trigger a re-render of this component, which has the
// unfortunate side effect of making the view scroll back to the top.
// This re-rendering however is uncessary since the component is
// already visually updated via JS. So here, if the note has not
// changed, we prevent the component from updating. This fixes the
// above issue. A drawback of this is if the note is updated via sync,
// this change will not be displayed immediately.
// If a checkbox in a note is ticked, the body changes, which normally would
// trigger a re-render of this component, which has the unfortunate side
// effect of making the view scroll back to the top. This re-rendering
// however is uncessary since the component is already visually updated via
// JS. So here, if the note has not changed, we prevent the component from
// updating. This fixes the above issue. A drawback of this is if the note
// is updated via sync, this change will not be displayed immediately.
//
// 2022-05-03: However we sometimes need the HTML to be updated, even when
// only the body has changed - for example when attaching a resource, or
// when adding text via speech recognition. So the logic has been narrowed
// down so that updates are skipped only when checkbox has been changed.
// Checkboxes still work as expected, without making the note scroll, and
// other text added to the note is displayed correctly.
//
// IMPORTANT: KEEP noteBody AS THE FIRST dependency in the array as the
// below logic rely on this.
@@ -62,9 +88,13 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
return accum;
}, {});
const onlyNoteBodyHasChanged = Object.keys(changedDeps).length === 1 && changedDeps[0];
const onlyCheckboxesHaveChanged = previousDeps[0] && changedDeps[0] && onlyCheckboxHasChangedHack(previousDeps[0], noteBody);
useEffect(() => {
if (onlyNoteBodyHasChanged) return () => {};
if (onlyNoteBodyHasChanged && onlyCheckboxesHaveChanged) {
logger.info('Only a checkbox has changed - not updating HTML');
return () => {};
}
let cancelled = false;

View File

@@ -29,7 +29,7 @@ import ScreenHeader from '../ScreenHeader';
const NoteTagsDialog = require('./NoteTagsDialog');
import time from '@joplin/lib/time';
const { Checkbox } = require('../checkbox.js');
const { _ } = require('@joplin/lib/locale');
import { _, currentLocale } from '@joplin/lib/locale';
import { reg } from '@joplin/lib/registry';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
const { BaseScreenComponent } = require('../base-screen.js');
@@ -44,13 +44,18 @@ import ShareExtension from '../../utils/ShareExtension.js';
import CameraView from '../CameraView';
import { NoteEntity } from '@joplin/lib/services/database/types';
import Logger from '@joplin/lib/Logger';
import VoiceTypingDialog from '../voiceTyping/VoiceTypingDialog';
import { voskEnabled } from '../../services/voiceTyping/vosk';
const urlUtils = require('@joplin/lib/urlUtils');
// import Vosk from 'react-native-vosk';
const emptyArray: any[] = [];
const logger = Logger.create('screens/Note');
class NoteScreenComponent extends BaseScreenComponent {
public static navigationOptions(): any {
return { header: null };
}
@@ -84,6 +89,8 @@ class NoteScreenComponent extends BaseScreenComponent {
canUndo: false,
canRedo: false,
},
voiceTypingDialogShown: false,
};
this.saveActionQueues_ = {};
@@ -238,6 +245,8 @@ class NoteScreenComponent extends BaseScreenComponent {
this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onUndoRedoDepthChange = this.onUndoRedoDepthChange.bind(this);
this.voiceTypingDialog_onText = this.voiceTypingDialog_onText.bind(this);
this.voiceTypingDialog_onDismiss = this.voiceTypingDialog_onDismiss.bind(this);
}
private useEditorBeta(): boolean {
@@ -871,6 +880,69 @@ class NoteScreenComponent extends BaseScreenComponent {
if (buttonId === 'attachPhoto') void this.attachPhoto_onPress();
}
// private vosk_:Vosk;
// private async getVosk() {
// if (this.vosk_) return this.vosk_;
// this.vosk_ = new Vosk();
// await this.vosk_.loadModel('model-fr-fr');
// return this.vosk_;
// }
// private async voiceRecording_onPress() {
// logger.info('Vosk: Getting instance...');
// const vosk = await this.getVosk();
// this.voskResult_ = [];
// const eventHandlers: any[] = [];
// eventHandlers.push(vosk.onResult(e => {
// logger.info('Vosk: result', e.data);
// this.voskResult_.push(e.data);
// }));
// eventHandlers.push(vosk.onError(e => {
// logger.warn('Vosk: error', e.data);
// }));
// eventHandlers.push(vosk.onTimeout(e => {
// logger.warn('Vosk: timeout', e.data);
// }));
// eventHandlers.push(vosk.onFinalResult(e => {
// logger.info('Vosk: final result', e.data);
// }));
// logger.info('Vosk: Starting recording...');
// void vosk.start();
// const buttonId = await dialogs.pop(this, 'Voice recording in progress...', [
// { text: 'Stop recording', id: 'stop' },
// { text: _('Cancel'), id: 'cancel' },
// ]);
// logger.info('Vosk: Stopping recording...');
// vosk.stop();
// for (const eventHandler of eventHandlers) {
// eventHandler.remove();
// }
// logger.info('Vosk: Recording stopped:', this.voskResult_);
// if (buttonId === 'cancel') return;
// const newNote: NoteEntity = { ...this.state.note };
// newNote.body = `${newNote.body} ${this.voskResult_.join(' ')}`;
// this.setState({ note: newNote });
// this.scheduleSave();
// }
public menuOptions() {
const note = this.state.note;
const isTodo = note && !!note.is_todo;
@@ -914,6 +986,18 @@ class NoteScreenComponent extends BaseScreenComponent {
void this.share_onPress();
},
});
// Voice typing is enabled only for French language and on Android for now
if (voskEnabled && shim.mobilePlatform() === 'android' && currentLocale() === 'fr_FR') {
output.push({
title: _('Voice typing...'),
onPress: () => {
// this.voiceRecording_onPress();
this.setState({ voiceTypingDialogShown: true });
},
});
}
if (isSaved) {
output.push({
title: _('Tags'),
@@ -1033,6 +1117,25 @@ class NoteScreenComponent extends BaseScreenComponent {
void this.saveOneProperty('body', newBody);
}
private voiceTypingDialog_onText(text: string) {
if (this.state.mode === 'view') {
const newNote: NoteEntity = { ...this.state.note };
newNote.body = `${newNote.body} ${text}`;
this.setState({ note: newNote });
this.scheduleSave();
} else {
if (this.useEditorBeta()) {
this.editorRef.current.insertText(text);
} else {
logger.warn('Voice typing is not supported in plaintext editor');
}
}
}
private voiceTypingDialog_onDismiss() {
this.setState({ voiceTypingDialogShown: false });
}
public render() {
if (this.state.isLoading) {
return (
@@ -1188,6 +1291,11 @@ class NoteScreenComponent extends BaseScreenComponent {
const noteTagDialog = !this.state.noteTagDialogShown ? null : <NoteTagsDialog onCloseRequested={this.noteTagDialog_closeRequested} />;
const renderVoiceTypingDialog = () => {
if (!this.state.voiceTypingDialogShown) return null;
return <VoiceTypingDialog onText={this.voiceTypingDialog_onText} onDismiss={this.voiceTypingDialog_onDismiss}/>;
};
return (
<View style={this.rootStyle(this.props.themeId).root}>
<ScreenHeader
@@ -1216,6 +1324,7 @@ class NoteScreenComponent extends BaseScreenComponent {
}}
/>
{noteTagDialog}
{renderVoiceTypingDialog()}
</View>
);
}
@@ -1238,6 +1347,10 @@ const NoteScreen = connect((state: any) => {
showSideMenu: state.showSideMenu,
provisionalNoteIds: state.provisionalNoteIds,
highlightedWords: state.highlightedWords,
// What we call "beta editor" in this component is actually the (now
// default) CodeMirror editor. That should be refactored to make it less
// confusing.
useEditorBeta: !state.settings['editor.usePlainText'],
};
})(NoteScreenComponent);

View File

@@ -0,0 +1,99 @@
import * as React from 'react';
import { useState, useEffect, useCallback } from 'react';
import { Button, Dialog, Text } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
import { getVosk, Recorder, startRecording, Vosk } from '../../services/voiceTyping/vosk';
import { Alert } from 'react-native';
interface Props {
onDismiss: ()=> void;
onText: (text: string)=> void;
}
enum RecorderState {
Loading = 1,
Recording = 2,
Processing = 3,
}
const useVosk = (): Vosk|null => {
const [vosk, setVosk] = useState<Vosk>(null);
useAsyncEffect(async (event: AsyncEffectEvent) => {
const v = await getVosk();
if (event.cancelled) return;
setVosk(v);
}, []);
return vosk;
};
export default (props: Props) => {
const [recorder, setRecorder] = useState<Recorder>(null);
const [recorderState, setRecorderState] = useState<RecorderState>(RecorderState.Loading);
const vosk = useVosk();
useEffect(() => {
if (!vosk) return;
setRecorderState(RecorderState.Recording);
}, [vosk]);
useEffect(() => {
if (recorderState === RecorderState.Recording) {
setRecorder(startRecording(vosk));
}
}, [recorderState, vosk]);
const onDismiss = useCallback(() => {
recorder.cleanup();
props.onDismiss();
}, [recorder, props.onDismiss]);
const onStop = useCallback(async () => {
try {
setRecorderState(RecorderState.Processing);
const result = await recorder.stop();
props.onText(result);
} catch (error) {
Alert.alert(error.message);
}
onDismiss();
}, [recorder, onDismiss, props.onText]);
const renderContent = () => {
const components: Record<RecorderState, any> = {
[RecorderState.Loading]: <Text variant="bodyMedium">Loading...</Text>,
[RecorderState.Recording]: <Text variant="bodyMedium">Please record your voice...</Text>,
[RecorderState.Processing]: <Text variant="bodyMedium">Converting speech to text...</Text>,
};
return components[recorderState];
};
const renderActions = () => {
const components: Record<RecorderState, any> = {
[RecorderState.Loading]: null,
[RecorderState.Recording]: (
<Dialog.Actions>
<Button onPress={onDismiss}>Cancel</Button>
<Button onPress={onStop}>Done</Button>
</Dialog.Actions>
),
[RecorderState.Processing]: null,
};
return components[recorderState];
};
return (
<Dialog visible={true} onDismiss={props.onDismiss}>
<Dialog.Title>{_('Voice typing')}</Dialog.Title>
<Dialog.Content>
{renderContent()}
</Dialog.Content>
{renderActions()}
</Dialog>
);
};

View File

@@ -384,14 +384,12 @@
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-DoubleConversion/double-conversion.framework/double-conversion",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/Flipper-Glog/glog.framework/glog",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/hermes.framework/hermes",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/double-conversion.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/glog.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -422,6 +420,7 @@
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/react-native-vosk/Vosk.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
@@ -442,6 +441,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Vosk.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -598,7 +598,7 @@
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@@ -661,7 +661,7 @@
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@@ -20,10 +20,13 @@ target 'Joplin' do
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => true,
# 2023/05/07: Leave that to `false` for now because Hermes is rubbish at
# reporting errors, which it makes it impossible to investigate crashes.
:hermes_enabled => false,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#

View File

@@ -73,7 +73,6 @@ PODS:
- FlipperKit/FlipperKitNetworkPlugin
- fmt (6.2.1)
- glog (0.3.5)
- hermes-engine (0.70.6)
- JoplinCommonShareExtension (1.0.0)
- JoplinRNShareExtension (1.0.0):
- JoplinCommonShareExtension
@@ -91,12 +90,6 @@ PODS:
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- RCT-Folly/Futures (2021.07.22.00):
- boost
- DoubleConversion
- fmt (~> 6.2.1)
- glog
- libevent
- RCTRequired (0.70.6)
- RCTTypeSafety (0.70.6):
- FBLazyVector (= 0.70.6)
@@ -274,17 +267,6 @@ PODS:
- React-logger (= 0.70.6)
- React-perflogger (= 0.70.6)
- React-runtimeexecutor (= 0.70.6)
- React-hermes (0.70.6):
- DoubleConversion
- glog
- hermes-engine
- RCT-Folly (= 2021.07.22.00)
- RCT-Folly/Futures (= 2021.07.22.00)
- React-cxxreact (= 0.70.6)
- React-jsi (= 0.70.6)
- React-jsiexecutor (= 0.70.6)
- React-jsinspector (= 0.70.6)
- React-perflogger (= 0.70.6)
- React-jsi (0.70.6):
- boost (= 1.76.0)
- DoubleConversion
@@ -316,7 +298,7 @@ PODS:
- React-Core
- react-native-camera/RN (4.2.1):
- React-Core
- react-native-document-picker (8.1.4):
- react-native-document-picker (8.2.0):
- React-Core
- react-native-fingerprint-scanner (6.0.0):
- React
@@ -328,13 +310,13 @@ PODS:
- React-Core
- react-native-image-resizer (1.4.5):
- React-Core
- react-native-netinfo (9.3.8):
- react-native-netinfo (9.3.9):
- React-Core
- react-native-rsa-native (2.0.5):
- React
- react-native-saf-x (2.11.0):
- React-Core
- react-native-safe-area-context (4.5.0):
- react-native-safe-area-context (4.5.1):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
@@ -346,6 +328,8 @@ PODS:
- React-Core
- react-native-version-info (1.1.1):
- React-Core
- react-native-vosk (0.1.12):
- React-Core
- react-native-webview (11.26.1):
- React-Core
- React-perflogger (0.70.6)
@@ -432,7 +416,7 @@ PODS:
- React
- RNSecureRandom (1.0.1):
- React
- RNShare (8.2.1):
- RNShare (8.2.2):
- React-Core
- RNVectorIcons (9.2.0):
- React-Core
@@ -468,10 +452,8 @@ DEPENDENCIES:
- FlipperKit/FlipperKitUserDefaultsPlugin (= 0.125.0)
- FlipperKit/SKIOSNetworkPlugin (= 0.125.0)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes/hermes-engine.podspec`)
- JoplinCommonShareExtension (from `ShareExtension`)
- JoplinRNShareExtension (from `ShareExtension`)
- libevent (~> 2.1.12)
- OpenSSL-Universal (= 1.1.1100)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
@@ -485,7 +467,6 @@ DEPENDENCIES:
- React-Core/RCTWebSocket (from `../node_modules/react-native/`)
- React-CoreModules (from `../node_modules/react-native/React/CoreModules`)
- React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`)
- React-hermes (from `../node_modules/react-native/ReactCommon/hermes`)
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
@@ -505,6 +486,7 @@ DEPENDENCIES:
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- react-native-version-info (from `../node_modules/react-native-version-info`)
- react-native-vosk (from `../node_modules/react-native-vosk`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
@@ -560,8 +542,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/React/FBReactNativeSpec"
glog:
:podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec"
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes/hermes-engine.podspec"
JoplinCommonShareExtension:
:path: ShareExtension
JoplinRNShareExtension:
@@ -586,8 +566,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/React/CoreModules"
React-cxxreact:
:path: "../node_modules/react-native/ReactCommon/cxxreact"
React-hermes:
:path: "../node_modules/react-native/ReactCommon/hermes"
React-jsi:
:path: "../node_modules/react-native/ReactCommon/jsi"
React-jsiexecutor:
@@ -626,6 +604,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-sqlite-storage"
react-native-version-info:
:path: "../node_modules/react-native-version-info"
react-native-vosk:
:path: "../node_modules/react-native-vosk"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-perflogger:
@@ -694,7 +674,6 @@ SPEC CHECKSUMS:
FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
hermes-engine: 2af7b7a59128f250adfd86f15aa1d5a2ecd39995
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
JoplinRNShareExtension: 485f3e6dad83b7b77f1572eabc249f869ee55c02
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
@@ -709,26 +688,26 @@ SPEC CHECKSUMS:
React-Core: b587d0a624f9611b0e032505f3d6f25e8daa2bee
React-CoreModules: c6ff48b985e7aa622e82ca51c2c353c7803eb04e
React-cxxreact: ade3d9e63c599afdead3c35f8a8bd12b3da6730b
React-hermes: ed09ae33512bbb8d31b2411778f3af1a2eb681a1
React-jsi: 5a3952e0c6d57460ad9ee2c905025b4c28f71087
React-jsiexecutor: b4a65947391c658450151275aa406f2b8263178f
React-jsinspector: 60769e5a0a6d4b32294a2456077f59d0266f9a8b
React-logger: 1623c216abaa88974afce404dc8f479406bbc3a0
react-native-alarm-notification: 26527410a6162d07a9dc57f4bbc62e94ff48e65d
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
react-native-document-picker: a9bd26996d1b2e4f412dd186041714c79af381d0
react-native-document-picker: 495c444c0c773c6e83a5d91165890ecb1c0a399a
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
react-native-geolocation: 69f4fd37650b8e7fee91816d395e62dd16f5ab8d
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
react-native-image-picker: ec9b713e248760bfa0f879f0715391de4651a7cb
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
react-native-netinfo: fbc23bc2fe217155d85f2f7e0644b1654df8029b
react-native-netinfo: 22c082970cbd99071a4e5aa7a612ac20d66b08f0
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-saf-x: 9bd5238d3b43d76bbec64aa82c173ac20a4bce9f
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
react-native-safe-area-context: f5549f36508b1b7497434baa0cd97d7e470920d4
react-native-slider: 33b8d190b59d4f67a541061bb91775d53d617d9d
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
react-native-vosk: 33b8e82a46cc56f31bb4847a40efa2d160270e2e
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
React-perflogger: 8c79399b0500a30ee8152d0f9f11beae7fc36595
React-RCTActionSheet: 7316773acabb374642b926c19aef1c115df5c466
@@ -751,12 +730,12 @@ SPEC CHECKSUMS:
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
RNShare: eaee3dd5a06dad397c7d3b14762007035c5de405
RNShare: d82e10f6b7677f4b0048c23709bd04098d5aee6c
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
Yoga: 99caf8d5ab45e9d637ee6e0174ec16fbbb01bcfc
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 1f5ea1b29b693e847adf004360d019d064a024ca
PODFILE CHECKSUM: 0235ffbfa2e655de806a80d996148182dd493d8d
COCOAPODS: 1.11.3

View File

@@ -24,6 +24,7 @@ const localPackages = {
'@joplin/fork-uslug': path.resolve(__dirname, '../fork-uslug/'),
'@joplin/react-native-saf-x': path.resolve(__dirname, '../react-native-saf-x/'),
'@joplin/react-native-alarm-notification': path.resolve(__dirname, '../react-native-alarm-notification/'),
'@joplin/react-native-vosk': path.resolve(__dirname, '../react-native-vosk/'),
};
const watchedFolders = [];

View File

@@ -25,7 +25,7 @@
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/datetimepicker": "6.7.5",
"@react-native-community/geolocation": "2.1.0",
"@react-native-community/netinfo": "9.3.8",
"@react-native-community/netinfo": "9.3.9",
"@react-native-community/push-notification-ios": "1.10.1",
"@react-native-community/slider": "4.4.2",
"assert-browserify": "2.0.0",
@@ -44,7 +44,7 @@
"react-native-action-button": "2.8.5",
"react-native-camera": "4.2.1",
"react-native-dialogbox": "0.6.10",
"react-native-document-picker": "8.1.4",
"react-native-document-picker": "8.2.0",
"react-native-dropdownalert": "4.5.1",
"react-native-exit-app": "1.1.0",
"react-native-file-viewer": "2.1.5",
@@ -58,14 +58,15 @@
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "4.5.0",
"react-native-safe-area-context": "4.5.1",
"react-native-securerandom": "1.0.1",
"react-native-share": "8.2.1",
"react-native-share": "8.2.2",
"react-native-side-menu-updated": "1.3.2",
"react-native-sqlite-storage": "6.0.1",
"react-native-url-polyfill": "1.3.0",
"react-native-vector-icons": "9.2.0",
"react-native-version-info": "1.1.1",
"react-native-vosk": "0.1.12",
"react-native-webview": "11.26.1",
"react-redux": "7.2.9",
"redux": "4.2.1",

View File

@@ -0,0 +1,21 @@
type Vosk = any;
export { Vosk };
export interface Recorder {
stop: ()=> Promise<string>;
cleanup: ()=> void;
}
export const getVosk = async () => {
return {} as any;
};
export const startRecording = (_vosk: Vosk): Recorder => {
return {
stop: async () => { return ''; },
cleanup: () => {},
};
};
export const voskEnabled = false;

View File

@@ -0,0 +1,113 @@
import Logger from '@joplin/lib/Logger';
import Vosk from 'react-native-vosk';
const logger = Logger.create('voiceTyping/vosk');
enum State {
Idle = 0,
Recording,
}
let vosk_: Vosk|null = null;
let state_: State = State.Idle;
export const voskEnabled = true;
export { Vosk };
export interface Recorder {
stop: ()=> Promise<string>;
cleanup: ()=> void;
}
export const getVosk = async () => {
if (vosk_) return vosk_;
vosk_ = new Vosk();
await vosk_.loadModel('model-fr-fr');
return vosk_;
};
export const startRecording = (vosk: Vosk): Recorder => {
if (state_ !== State.Idle) throw new Error('Vosk is already recording');
state_ = State.Recording;
const result: string[] = [];
const eventHandlers: any[] = [];
let finalResultPromiseResolve: Function = null;
let finalResultPromiseReject: Function = null;
let finalResultTimeout = false;
const completeRecording = (finalResult: string, error: Error) => {
logger.info(`Complete recording. Final result: ${finalResult}. Error:`, error);
for (const eventHandler of eventHandlers) {
eventHandler.remove();
}
vosk.cleanup(),
state_ = State.Idle;
if (error) {
if (finalResultPromiseReject) finalResultPromiseReject(error);
} else {
if (finalResultPromiseResolve) finalResultPromiseResolve(finalResult);
}
};
eventHandlers.push(vosk.onResult(e => {
logger.info('Result', e.data);
result.push(e.data);
}));
eventHandlers.push(vosk.onError(e => {
logger.warn('Error', e.data);
}));
eventHandlers.push(vosk.onTimeout(e => {
logger.warn('Timeout', e.data);
}));
eventHandlers.push(vosk.onFinalResult(e => {
logger.info('Final result', e.data);
if (finalResultTimeout) {
logger.warn('Got final result - but already timed out. Not doing anything.');
return;
}
completeRecording(e.data, null);
}));
logger.info('Starting recording...');
void vosk.start();
return {
stop: (): Promise<string> => {
logger.info('Stopping recording...');
vosk.stopOnly();
logger.info('Waiting for final result...');
setTimeout(() => {
finalResultTimeout = true;
logger.warn('Timed out waiting for finalResult event');
completeRecording('', new Error('Could not process your message. Please try again.'));
}, 5000);
return new Promise((resolve: Function, reject: Function) => {
finalResultPromiseResolve = resolve;
finalResultPromiseReject = reject;
});
},
cleanup: () => {
if (state_ !== State.Idle) {
logger.info('Cancelling...');
vosk.stopOnly();
completeRecording('', null);
}
},
};
};

View File

@@ -1395,7 +1395,7 @@ class Setting extends BaseModel {
useCustomPdfViewer: {
value: false,
type: SettingItemType.Bool,
public: true,
public: false,
advanced: true,
appTypes: [AppType.Desktop],
label: () => 'Use custom PDF viewer (Beta)',

View File

@@ -25,12 +25,12 @@
"@types/uuid": "^9.0.0",
"clean-html": "1.5.0",
"jest": "29.4.3",
"sharp": "0.31.3",
"sharp": "0.32.0",
"typescript": "4.9.4"
},
"dependencies": {
"@aws-sdk/client-s3": "3.241.0",
"@aws-sdk/s3-request-presigner": "3.241.0",
"@aws-sdk/client-s3": "3.296.0",
"@aws-sdk/s3-request-presigner": "3.296.0",
"@joplin/fork-htmlparser2": "^4.1.43",
"@joplin/fork-sax": "^1.2.47",
"@joplin/fork-uslug": "^1.0.8",
@@ -80,7 +80,7 @@
"read-chunk": "2.1.0",
"redux": "4.2.1",
"relative": "3.0.2",
"reselect": "4.1.7",
"reselect": "4.1.8",
"server-destroy": "1.0.1",
"sprintf-js": "1.1.2",
"sqlite3": "5.1.6",

View File

@@ -9,12 +9,12 @@
"access": "restricted"
},
"scripts": {
"tsc": "tsc --project tsconfig.json",
"watch": "webpack --watch --config webpack.config.js --mode=development",
"build": "webpack --config webpack.config.js --mode=production",
"test": "jest",
"test-ci": "yarn test",
"postinstall": "yarn build"
"tsc_DISABLED": "tsc --project tsconfig.json",
"watch_DISABLED": "webpack --watch --config webpack.config.js --mode=development",
"build_DISABLED": "webpack --config webpack.config.js --mode=production",
"test_DISABLED": "jest",
"test-ci_DISABLED": "yarn test",
"postinstall_DISABLED": "yarn build"
},
"author": "Joplin",
"license": "AGPL-3.0-or-later",

View File

@@ -42,3 +42,5 @@ local.properties
buck-out/
\.buckd/
*.keystore
android/build-*

View File

@@ -63,4 +63,6 @@ android/keystores/debug.keystore
# generated by bob
lib/
.env
docs
docs
android/build-*

View File

@@ -0,0 +1,98 @@
version: 2.1
executors:
default:
docker:
- image: circleci/node:16
working_directory: ~/project
commands:
attach_project:
steps:
- attach_workspace:
at: ~/project
jobs:
install-dependencies:
executor: default
steps:
- checkout
- attach_project
- restore_cache:
keys:
- dependencies-{{ checksum "package.json" }}
- dependencies-
- restore_cache:
keys:
- dependencies-example-{{ checksum "example/package.json" }}
- dependencies-example-
- run:
name: Install dependencies
command: |
yarn install --cwd example --frozen-lockfile
yarn install --frozen-lockfile
- save_cache:
key: dependencies-{{ checksum "package.json" }}
paths: node_modules
- save_cache:
key: dependencies-example-{{ checksum "example/package.json" }}
paths: example/node_modules
- persist_to_workspace:
root: .
paths: .
lint:
executor: default
steps:
- attach_project
- run:
name: Lint files
command: |
yarn lint
typescript:
executor: default
steps:
- attach_project
- run:
name: Typecheck files
command: |
yarn typescript
unit-tests:
executor: default
steps:
- attach_project
- run:
name: Run unit tests
command: |
yarn test --coverage
- store_artifacts:
path: coverage
destination: coverage
build-package:
executor: default
steps:
- attach_project
- run:
name: Build package
command: |
yarn prepare
workflows:
build-and-test:
jobs:
- install-dependencies
- lint:
requires:
- install-dependencies
- typescript:
requires:
- install-dependencies
- unit-tests:
requires:
- install-dependencies
- build-package:
requires:
- install-dependencies

View File

@@ -0,0 +1,4 @@
*.pbxproj -text
# specific for windows script files
*.bat text eol=crlf
*.a filter=lfs diff=lfs merge=lfs -text

View File

@@ -0,0 +1,22 @@
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: Node.js Package
on:
release:
types: [created]
jobs:
publish-npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: yarn
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

72
packages/react-native-vosk/.gitignore vendored Normal file
View File

@@ -0,0 +1,72 @@
# OSX
#
.DS_Store
# XDE
.expo/
# VSCode
.vscode/
jsconfig.json
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local
project.xcworkspace
# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml
.cxx/
*.keystore
!debug.keystore
# Cocoapods
#
example/ios/Pods
# Ruby
example/vendor/
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# Expo
.expo/*
# generated by bob
# lib/
# Generated by UUID
example/android/app/src/main/assets/*/uuid
android/build-*

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,3 @@
# Override Yarn command so we can automatically setup the repo on running `yarn`
yarn-path "scripts/bootstrap.js"

View File

@@ -0,0 +1,195 @@
# Contributing
We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project.
## Development workflow
To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
yarn
```
> While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development.
While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app.
To start the packager:
```sh
yarn example start
```
To run the example app on Android:
```sh
yarn example android
```
To run the example app on iOS:
```sh
yarn example ios
```
Make sure your code passes TypeScript and ESLint. Run the following to verify:
```sh
yarn typescript
yarn lint
```
To fix formatting errors, run the following:
```sh
yarn lint --fix
```
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
To edit the Objective-C files, open `example/ios/VoskExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-vosk`.
To edit the Kotlin files, open `example/android` in Android studio and find the source files at `reactnativevosk` under `Android`.
### Commit message convention
We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
- `fix`: bug fixes, e.g. fix crash due to deprecated method.
- `feat`: new features, e.g. add new method to the module.
- `refactor`: code refactor, e.g. migrate from class components to hooks.
- `docs`: changes into documentation, e.g. add usage example for the module..
- `test`: adding or updating tests, e.g. add integration tests using detox.
- `chore`: tooling changes, e.g. change CI config.
Our pre-commit hooks verify that your commit message matches this format when committing.
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
Our pre-commit hooks verify that the linter and tests pass when committing.
### Publishing to npm
We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc.
To publish new versions, run the following:
```sh
yarn release
```
### Scripts
The `package.json` file contains various scripts for common tasks:
- `yarn bootstrap`: setup project by installing all dependencies and pods.
- `yarn typescript`: type-check files with TypeScript.
- `yarn lint`: lint files with ESLint.
- `yarn test`: run unit tests with Jest.
- `yarn example start`: start the Metro server for the example app.
- `yarn example android`: run the example app on Android.
- `yarn example ios`: run the example app on iOS.
### Sending a pull request
> **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Verify that linters and tests are passing.
- Review the documentation to make sure it looks good.
- Follow the pull request template when opening a pull request.
- For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
## Code of Conduct
### Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
### Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
### Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
### Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [INSERT CONTACT METHOD]. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
### Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
#### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
#### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
#### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
#### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

View File

@@ -0,0 +1,20 @@
MIT License
Copyright (c) 2022 Joris Gaudin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,125 @@
# react-native-vosk - React ASR (Automated Speech Recognition)
* * *
**NOTE:** For some reason this module doesn't work (events are not being fired), so for now we use the actual `react-native-vosk` module, but with a patch
**Joplin fork** of `react-native-vosk@0.1.12` with the following changes:
- The `onResult()` event doesn't automatically stop the recorder - because we need it to keep running so that it captures the whole text. The original package was designed to record one keyword, but we need whole sentences.
- Added the `stopOnly()` method. This is because the original `stop()` method wouldn't just stop, but clear everything, this preventing the useful `onFinalResult()` event from event from being emitted. And we need this event to get the final text.
- Also added `cleanup()` method. It should be called once the `onFinalResult()` event has been received, and does the same as the original `stop()` method.
- The folder in `ios/Vosk/vosk-model-spk-0.4` was deleted because unclear what it's for, and we don't build the iOS version anyway. If it's ever needed it can be restored from the original repo: https://github.com/riderodd/react-native-vosk
* * *
Speech recognition module for react native using [Vosk](https://github.com/alphacep/vosk-api) library
## Installation
### Library
```sh
npm install -S react-native-vosk
```
### Models
Vosk uses prebuilt models to perform speech recognition offline. You have to download the model(s) that you need on [Vosk official website](https://alphacephei.com/vosk/models)
Avoid using too heavy models, because the computation time required to load them into your app could lead to bad user experience.
Then, unzip the model in your app folder. If you just need to use the iOS version, put the model folder wherever you want, and import it as described below. If you need both iOS and Android to work, you can avoid to copy the model twice for both projects by importing the model from the Android assets folder in XCode. Just do as follow:
### Android
In Android Studio, open the project manager, right-click on your project folder and New > Folder > Assets folder.
![Android Studio assets folder creation](https://raw.githubusercontent.com/riderodd/react-native-vosk/main/docs/android_studio_assets_folder_creation.png)
Then put the model folder inside the assets folder created. In your file tree it should be located in android\app\src\main\assets. So, if you downloaded the french model named model-fr-fr, you should access the model by going to android\app\src\main\assets\model-fr-fr. In Android studio, your project structure should be like that:
![Android Studio final project structure](https://raw.githubusercontent.com/riderodd/react-native-vosk/main/docs/android_studio_project_structure.png)
You can import as many models as you want.
### iOS
In order to let the project work, you're going to need the iOS library. Mail contact@alphacephei.com to get the libraries. You're going to have a libvosk.xcframework file (or folder for not mac users), just copy it in the ios folder of the module (node_modules/react-native-vosk/ios/libvosk.xcframework). Then run in your root project:
```sh
npm run pods
```
In XCode, right-click on your project folder, and click on "Add files to [your project name]".
![XCode add files to project](https://raw.githubusercontent.com/riderodd/react-native-vosk/main/docs/xcode_add_files_to_folder.png)
Then navigate to your model folder. You can navigate to your Android assets folder as mentionned before, and chose your model here. It will avoid to have the model copied twice in your project. If you don't use the Android build, you can just put the model wherever you want, and select it.
![XCode chose model folder](https://raw.githubusercontent.com/riderodd/react-native-vosk/main/docs/xcode_chose_model_folder.png)
That's all. The model folder should appear in your project. When you click on it, your project target should be checked (see below).
![XCode full settings screenshot](https://raw.githubusercontent.com/riderodd/react-native-vosk/main/docs/xcode_full_settings_screenshot.png)
## Usage
```js
import VoiceRecognition from 'react-native-voice-recognition';
// ...
const voiceRecognition = new VoiceRecognition();
voiceRecognition.loadModel('model-fr-fr').then(() => {
// we can use promise...
voiceRecognition
.start()
.then((res: any) => {
console.log('Result is: ' + res);
})
// ... or events
const resultEvent = vosk.onResult((res) => {
console.log('A onResult event has been caught: ' + res.data);
});
// Don't forget to call resultEvent.remove(); when needed
}).catch(e => {
console.error(e);
})
```
Note that `start()` method will ask for audio record permission.
[Complete example...](https://github.com/riderodd/react-native-vosk/blob/main/example/src/App.tsx)
### Methods
| Method | Argument | Return | Description |
|---|---|---|---|
| `loadModel` | `path: string` | `Promise` | Loads the voice model used for recognition, it is required before using start method |
| `start` | `grammar: string[]` or `none` | `Promise` | Starts the voice recognition and returns the recognized text as a promised string, you can recognize specific words using the `grammar` argument (ex: ["left", "right"]) according to kaldi's documentation |
| `stop` | `none` | `none` | Stops the recognition |
### Events
| Method | Promise return | Description |
|---|---|---|
| `onResult` | The recognized word as a `string` | Triggers on voice recognition result |
| `onFinalResult` | The recognized word as a `string` | Triggers if stopped using `stop()` method |
| `onError` | The error that occured as a `string` or `exception` | Triggers if an error occured |
| `onTimeout` | "timeout" `string` | Triggers on timeout |
#### Example
```js
const resultEvent = voiceRecognition.onResult((res) => {
console.log('A onResult event has been caught: ' + res.data);
});
resultEvent.remove();
```
Don't forget to remove the event listener once you don't need it anymore.
## Contributing
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
## License
MIT

View File

@@ -0,0 +1,107 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["Vosk_kotlinVersion"]
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Vosk_" + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Vosk_" + name]).toInteger()
}
android {
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
mavenCentral()
google()
}
// Generate UUIDs for each models contained in android/src/main/assets/
// We don't want this because it's going to generate a different one on each
// build, even when nothing has changed.
// tasks.register('genUUID') {
// doLast {
// fileTree(dir: "$rootDir/app/src/main/assets", exclude: ['*/*']).visit { fileDetails ->
// if (fileDetails.directory) {
// def odir = file("$rootDir/app/src/main/assets/$fileDetails.relativePath")
// def ofile = file("$odir/uuid")
// mkdir odir
// ofile.text = UUID.randomUUID().toString()
// }
// }
// }
// }
// preBuild.dependsOn genUUID
def kotlin_version = getExtOrDefault('kotlinVersion')
dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// From node_modules
implementation 'net.java.dev.jna:jna:5.12.1@aar'
implementation 'com.alphacephei:vosk-android:0.3.46@aar'
}
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "Vosk"
codegenJavaPackageName = "com.reactnativevosk"
}
}

View File

@@ -0,0 +1,5 @@
Vosk_kotlinVersion=1.7.0
Vosk_minSdkVersion=21
Vosk_targetSdkVersion=31
Vosk_compileSdkVersion=31
Vosk_ndkversion=21.4.7075529

View File

@@ -0,0 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativevosk">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>

View File

@@ -0,0 +1,192 @@
package com.reactnativevosk
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import org.json.JSONObject
import org.vosk.Model
import org.vosk.Recognizer
import org.vosk.android.RecognitionListener
import org.vosk.android.SpeechService
import org.vosk.android.StorageService
import java.io.IOException
class VoskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), RecognitionListener {
private var model: Model? = null
private var speechService: SpeechService? = null
private var context: ReactApplicationContext? = reactContext
private var recognizer: Recognizer? = null
override fun getName(): String {
return "Vosk"
}
@ReactMethod
fun addListener(type: String?) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
fun removeListeners(type: Int?) {
// Keep: Required for RN built in Event Emitter Calls.
}
override fun onResult(hypothesis: String) {
// Get text data from string object
val text = getHypothesisText(hypothesis)
// Stop recording if data found
if (text != null && text.isNotEmpty()) {
// Don't auto-stop the recogniser - we want to do that when the user
// presses on "stop" only.
// cleanRecognizer();
sendEvent("onResult", text)
}
}
override fun onFinalResult(hypothesis: String) {
val text = getHypothesisText(hypothesis)
if (text!!.isNotEmpty()) sendEvent("onFinalResult", text)
}
override fun onPartialResult(hypothesis: String) {
sendEvent("onPartialResult", hypothesis)
}
override fun onError(e: Exception) {
sendEvent("onError", e.toString())
}
override fun onTimeout() {
sendEvent("onTimeout")
}
/**
* Converts hypothesis json text to the recognized text
* @return the recognized text or null if something went wrong
*/
private fun getHypothesisText(hypothesis: String): String? {
// Hypothesis is in the form: '{text: "recognized text"}'
return try {
val res = JSONObject(hypothesis)
res.getString("text")
} catch (tx: Throwable) {
null
}
}
/**
* Sends event to react native with associated data
*/
private fun sendEvent(eventName: String, data: String? = null) {
// Write event data if there is some
val event = Arguments.createMap().apply {
if (data != null) putString("data", data)
}
// Send event
context?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)?.emit(
eventName,
event
)
}
/**
* Translates array of string(s) to required kaldi string format
* @return the array of string(s) as a single string
*/
private fun makeGrammar(grammarArray: ReadableArray): String {
return grammarArray.toArrayList().joinToString(
prefix = "[",
separator = ", ",
transform = {"\"" + it + "\""},
postfix = "]"
)
}
@ReactMethod
fun loadModel(path: String, promise: Promise) {
cleanModel();
StorageService.unpack(context, path, "models",
{ model: Model? ->
this.model = model
promise.resolve("Model successfully loaded")
}
) { e: IOException ->
this.model = null
promise.reject(e)
}
}
@ReactMethod
fun start(grammar: ReadableArray? = null) {
if (model == null) {
sendEvent("onError", "Model is not loaded yet")
}
else if (speechService != null) {
sendEvent("onError", "Recognizer is already in use")
} else {
try {
recognizer =
if (grammar != null)
Recognizer(model, 16000.0f, makeGrammar(grammar))
else
Recognizer(model, 16000.0f)
speechService = SpeechService(recognizer, 16000.0f)
speechService!!.startListening(this)
sendEvent("onStart")
} catch (e: IOException) {
sendEvent("onError", e.toString())
}
}
}
private fun cleanRecognizer() {
if (speechService != null) {
speechService!!.stop()
speechService!!.shutdown();
speechService = null
}
if (recognizer != null) {
recognizer!!.close();
recognizer = null;
}
}
private fun cleanModel() {
if (this.model != null) {
this.model!!.close();
this.model = null;
}
}
@ReactMethod
fun stop() {
cleanRecognizer();
}
@ReactMethod
fun stopOnly() {
if (speechService != null) {
speechService!!.stop()
}
}
@ReactMethod
fun cleanup() {
if (speechService != null) {
speechService!!.shutdown();
speechService = null
}
if (recognizer != null) {
recognizer!!.close();
recognizer = null;
}
}
@ReactMethod
fun unload() {
cleanRecognizer();
cleanModel();
}
}

View File

@@ -0,0 +1,16 @@
package com.reactnativevosk
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class VoskPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(VoskModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

View File

@@ -0,0 +1,4 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTViewManager.h>
#import <React/RCTEventEmitter.h>
#import "vosk_api.h"

View File

@@ -0,0 +1,21 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(Vosk, RCTEventEmitter)
RCT_EXTERN_METHOD(loadModel:(NSString *)name
withResolver:(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(start:(NSArray)grammar)
RCT_EXTERN_METHOD(stop)
RCT_EXTERN_METHOD(unload)
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
@end

View File

@@ -0,0 +1,179 @@
import Foundation
import AVFoundation
// The representation of the JSON object returned by Vosk
struct VoskResult: Codable {
// Partial result
var partial: String?
// Complete result
var text: String?
}
@objc(Vosk)
class Vosk: RCTEventEmitter {
// Class properties
/// The current vosk model loaded
var currentModel: VoskModel?
/// The vosk recognizer
var recognizer : OpaquePointer?
/// The audioEngine used to pipe microphone to recognizer
let audioEngine = AVAudioEngine()
/// The audioEngine input
var inputNode: AVAudioInputNode!
/// The microphone input format
var formatInput: AVAudioFormat!
/// A queue to process datas
var processingQueue: DispatchQueue!
/// Keep the last processed result here
var lastRecognizedResult: VoskResult?
/// The timeout timer ref
var timeoutTimer: Timer?
/// React member: has any JS event listener
var hasListener: Bool = false
// Class methods
override init() {
super.init()
// Init the processing queue
processingQueue = DispatchQueue(label: "recognizerQueue")
// Create a new audio engine.
inputNode = audioEngine.inputNode
// Get the microphone default input format
formatInput = inputNode.inputFormat(forBus: 0)
}
deinit {
// free the recognizer
vosk_recognizer_free(recognizer);
}
/// Called when React adds an event observer
override func startObserving() {
hasListener = true
}
/// Called when no more event observers are running
override func stopObserving() {
hasListener = false
}
/// React method to define allowed events
@objc override func supportedEvents() -> [String]! {
return ["onError","onResult","onFinalResult","onPartialResult","onTimeout"];
}
/// Load a Vosk model
@objc(loadModel:withResolver:withRejecter:)
func loadModel(name: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
if (currentModel != nil) {
currentModel = nil; // deinit model
}
currentModel = VoskModel(name: name)
resolve(name)
}
/// Start speech recognition
@objc(start:)
func start(grammar: [String]?) -> Void {
let audioSession = AVAudioSession.sharedInstance()
do {
// Ask the user for permission to use the mic if required then start the engine.
try audioSession.setCategory(.record)
if (grammar != nil && grammar!.isEmpty == false) {
let jsonGrammar = try! JSONEncoder().encode(grammar)
recognizer = vosk_recognizer_new_grm(currentModel!.model, Float(formatInput.sampleRate), String(data: jsonGrammar, encoding: .utf8))
} else {
recognizer = vosk_recognizer_new_spk(currentModel!.model, Float(formatInput.sampleRate), currentModel!.spkModel)
}
let formatPcm = AVAudioFormat.init(commonFormat: AVAudioCommonFormat.pcmFormatInt16, sampleRate: formatInput.sampleRate, channels: 1, interleaved: true)
inputNode.installTap(onBus: 0,
bufferSize: UInt32(formatInput.sampleRate / 10),
format: formatPcm) { buffer, time in
self.processingQueue.async {
let res = self.recognizeData(buffer: buffer)
DispatchQueue.main.async {
let parsedResult = try! JSONDecoder().decode(VoskResult.self, from: res.result!.data(using: .utf8)!)
self.lastRecognizedResult = parsedResult
if (res.completed && self.hasListener && res.result != nil) {
self.sendEvent(withName: "onResult", body: ["data": parsedResult.text!])
self.stopInternal(withoutEvents: true);
} else if (!res.completed && self.hasListener && res.result != nil) {
self.sendEvent(withName: "onPartialResult", body: ["data": parsedResult.partial!])
}
}
}
}
// Start the stream of audio data.
audioEngine.prepare()
audioSession.requestRecordPermission { [weak self] success in
guard success, let self = self else { return }
try? self.audioEngine.start()
}
// and manage timeout
timeoutTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) {_ in
self.sendEvent(withName: "onTimeout", body: ["data": ""])
self.stopInternal(withoutEvents: true)
}
} catch {
if (hasListener) {
sendEvent(withName: "onError", body: ["data": "Unable to start AVAudioEngine " + error.localizedDescription])
} else {
debugPrint("Unable to start AVAudioEngine " + error.localizedDescription)
}
vosk_recognizer_free(recognizer);
}
}
/// Unload speech recognition and model
@objc(unload) func unload() -> Void {
stopInternal(withoutEvents: false)
if (currentModel != nil) {
currentModel = nil; // deinit model
}
}
/// Stop speech recognition if started
@objc(stop) func stop() -> Void {
// stop engines and send onFinalResult event
stopInternal(withoutEvents: false)
}
/// Do internal cleanup on stop recognition
func stopInternal(withoutEvents: Bool) {
inputNode.removeTap(onBus: 0)
if (audioEngine.isRunning) {
audioEngine.stop()
if (hasListener && !withoutEvents) {
sendEvent(withName: "onFinalResult", body: ["data": lastRecognizedResult!.partial])
}
lastRecognizedResult = nil
}
if (recognizer != nil) {
vosk_recognizer_free(recognizer);
recognizer = nil
}
if (timeoutTimer != nil) {
timeoutTimer?.invalidate()
timeoutTimer = nil
}
}
/// Process the audio buffer and do recognition with Vosk
func recognizeData(buffer : AVAudioPCMBuffer) -> (result: String?, completed: Bool) {
let dataLen = Int(buffer.frameLength * 2)
let channels = UnsafeBufferPointer(start: buffer.int16ChannelData, count: 1)
let endOfSpeech = channels[0].withMemoryRebound(to: Int8.self, capacity: dataLen) {
vosk_recognizer_accept_waveform(recognizer, $0, Int32(dataLen))
}
let res = endOfSpeech == 1 ? vosk_recognizer_result(recognizer) : vosk_recognizer_partial_result(recognizer)
return (String(validatingUTF8: res!), endOfSpeech == 1);
}
}

View File

@@ -0,0 +1,301 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objects = {
/* Begin PBXBuildFile section */
33B015BD288FEEE500EBEBCF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 33B015BC288FEEE400EBEBCF /* libc++.tbd */; };
33B015BF288FEF1300EBEBCF /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33B015BE288FEEE800EBEBCF /* Accelerate.framework */; };
33B015C3288FF3E400EBEBCF /* libvosk.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33B015C2288FF3E400EBEBCF /* libvosk.xcframework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libVosk.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libVosk.a; sourceTree = BUILT_PRODUCTS_DIR; };
33B015BC288FEEE400EBEBCF /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
33B015BE288FEEE800EBEBCF /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
33B015C0288FEF5E00EBEBCF /* VoskModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoskModel.swift; sourceTree = "<group>"; };
33B015C2288FF3E400EBEBCF /* libvosk.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = libvosk.xcframework; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* Vosk.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Vosk.m; sourceTree = "<group>"; };
F4FF95D5245B92E700C19C63 /* Vosk-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Vosk-Bridging-Header.h"; sourceTree = "<group>"; };
F4FF95D6245B92E800C19C63 /* Vosk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vosk.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
33B015BF288FEF1300EBEBCF /* Accelerate.framework in Frameworks */,
33B015BD288FEEE500EBEBCF /* libc++.tbd in Frameworks */,
33B015C3288FF3E400EBEBCF /* libvosk.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libVosk.a */,
);
name = Products;
sourceTree = "<group>";
};
33B015BA288FEE6600EBEBCF /* Frameworks */ = {
isa = PBXGroup;
children = (
33B015C2288FF3E400EBEBCF /* libvosk.xcframework */,
33B015BC288FEEE400EBEBCF /* libc++.tbd */,
33B015BE288FEEE800EBEBCF /* Accelerate.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
F4FF95D6245B92E800C19C63 /* Vosk.swift */,
33B015C0288FEF5E00EBEBCF /* VoskModel.swift */,
B3E7B5891CC2AC0600A0062D /* Vosk.m */,
F4FF95D5245B92E700C19C63 /* Vosk-Bridging-Header.h */,
134814211AA4EA7D00B7C361 /* Products */,
33B015BA288FEE6600EBEBCF /* Frameworks */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* Vosk */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Vosk" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = Vosk;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libVosk.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0920;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Vosk" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* Vosk */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=*]" = arm64;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = Vosk;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Vosk-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = Vosk;
SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Vosk-Bridging-Header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Vosk" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Vosk" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@@ -0,0 +1 @@
Here was the folder `vosk-model-spk-0.4`. You can restore it from the original repo: https://github.com/riderodd/react-native-vosk

View File

@@ -0,0 +1,51 @@
//
// Vosk.swift
// VoskApiTest
//
// Created by Niсkolay Shmyrev on 01.03.20.
// Copyright © 2020-2021 Alpha Cephei. All rights reserved.
//
import Foundation
public final class VoskModel {
var model : OpaquePointer!
var spkModel : OpaquePointer!
init(name: String) {
// Set to -1 to disable logs
vosk_set_log_level(0);
let appBundle = Bundle(for: Self.self)
// Load model from main app bundle
if let resourcePath = Bundle.main.resourcePath {
let modelPath = resourcePath + "/" + name
model = vosk_model_new(modelPath)
}
// Get the URL to the resource bundle within the bundle
// of the current class.
guard let resourceBundleURL = appBundle.url(
forResource: "Vosk", withExtension: "bundle")
else { fatalError("Vosk.bundle not found!") }
// Create a bundle object for the bundle found at that URL.
guard let resourceBundle = Bundle(url: resourceBundleURL)
else { fatalError("Cannot access Vosk.bundle!") }
if let resourcePath = resourceBundle.resourcePath {
let spkModelPath = resourcePath + "/vosk-model-spk-0.4"
spkModel = vosk_spk_model_new(spkModelPath)
}
}
deinit {
vosk_model_free(model)
vosk_spk_model_free(spkModel)
}
}

View File

@@ -0,0 +1,292 @@
// Copyright 2020-2021 Alpha Cephei Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/* This header contains the C API for Vosk speech recognition system */
#ifndef VOSK_API_H
#define VOSK_API_H
#ifdef __cplusplus
extern "C" {
#endif
/** Model stores all the data required for recognition
* it contains static data and can be shared across processing
* threads. */
typedef struct VoskModel VoskModel;
/** Speaker model is the same as model but contains the data
* for speaker identification. */
typedef struct VoskSpkModel VoskSpkModel;
/** Recognizer object is the main object which processes data.
* Each recognizer usually runs in own thread and takes audio as input.
* Once audio is processed recognizer returns JSON object as a string
* which represent decoded information - words, confidences, times, n-best lists,
* speaker information and so on */
typedef struct VoskRecognizer VoskRecognizer;
/** Loads model data from the file and returns the model object
*
* @param model_path: the path of the model on the filesystem
* @returns model object or NULL if problem occured */
VoskModel *vosk_model_new(const char *model_path);
/** Releases the model memory
*
* The model object is reference-counted so if some recognizer
* depends on this model, model might still stay alive. When
* last recognizer is released, model will be released too. */
void vosk_model_free(VoskModel *model);
/** Check if a word can be recognized by the model
* @param word: the word
* @returns the word symbol if @param word exists inside the model
* or -1 otherwise.
* Reminding that word symbol 0 is for <epsilon> */
int vosk_model_find_word(VoskModel *model, const char *word);
/** Loads speaker model data from the file and returns the model object
*
* @param model_path: the path of the model on the filesystem
* @returns model object or NULL if problem occured */
VoskSpkModel *vosk_spk_model_new(const char *model_path);
/** Releases the model memory
*
* The model object is reference-counted so if some recognizer
* depends on this model, model might still stay alive. When
* last recognizer is released, model will be released too. */
void vosk_spk_model_free(VoskSpkModel *model);
/** Creates the recognizer object
*
* The recognizers process the speech and return text using shared model data
* @param model VoskModel containing static data for recognizer. Model can be
* shared across recognizers, even running in different threads.
* @param sample_rate The sample rate of the audio you going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @returns recognizer object or NULL if problem occured */
VoskRecognizer *vosk_recognizer_new(VoskModel *model, float sample_rate);
/** Creates the recognizer object with speaker recognition
*
* With the speaker recognition mode the recognizer not just recognize
* text but also return speaker vectors one can use for speaker identification
*
* @param model VoskModel containing static data for recognizer. Model can be
* shared across recognizers, even running in different threads.
* @param sample_rate The sample rate of the audio you going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param spk_model speaker model for speaker identification
* @returns recognizer object or NULL if problem occured */
VoskRecognizer *vosk_recognizer_new_spk(VoskModel *model, float sample_rate, VoskSpkModel *spk_model);
/** Creates the recognizer object with the phrase list
*
* Sometimes when you want to improve recognition accuracy and when you don't need
* to recognize large vocabulary you can specify a list of phrases to recognize. This
* will improve recognizer speed and accuracy but might return [unk] if user said
* something different.
*
* Only recognizers with lookahead models support this type of quick configuration.
* Precompiled HCLG graph models are not supported.
*
* @param model VoskModel containing static data for recognizer. Model can be
* shared across recognizers, even running in different threads.
* @param sample_rate The sample rate of the audio you going to feed into the recognizer.
* Make sure this rate matches the audio content, it is a common
* issue causing accuracy problems.
* @param grammar The string with the list of phrases to recognize as JSON array of strings,
* for example "["one two three four five", "[unk]"]".
*
* @returns recognizer object or NULL if problem occured */
VoskRecognizer *vosk_recognizer_new_grm(VoskModel *model, float sample_rate, const char *grammar);
/** Adds speaker model to already initialized recognizer
*
* Can add speaker recognition model to already created recognizer. Helps to initialize
* speaker recognition for grammar-based recognizer.
*
* @param spk_model Speaker recognition model */
void vosk_recognizer_set_spk_model(VoskRecognizer *recognizer, VoskSpkModel *spk_model);
/** Configures recognizer to output n-best results
*
* <pre>
* {
* "alternatives": [
* { "text": "one two three four five", "confidence": 0.97 },
* { "text": "one two three for five", "confidence": 0.03 },
* ]
* }
* </pre>
*
* @param max_alternatives - maximum alternatives to return from recognition results
*/
void vosk_recognizer_set_max_alternatives(VoskRecognizer *recognizer, int max_alternatives);
/** Enables words with times in the output
*
* <pre>
* "result" : [{
* "conf" : 1.000000,
* "end" : 1.110000,
* "start" : 0.870000,
* "word" : "what"
* }, {
* "conf" : 1.000000,
* "end" : 1.530000,
* "start" : 1.110000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 1.950000,
* "start" : 1.530000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.340000,
* "start" : 1.950000,
* "word" : "zero"
* }, {
* "conf" : 1.000000,
* "end" : 2.610000,
* "start" : 2.340000,
* "word" : "one"
* }],
* </pre>
*
* @param words - boolean value
*/
void vosk_recognizer_set_words(VoskRecognizer *recognizer, int words);
/** Accept voice data
*
* accept and process new chunk of voice data
*
* @param data - audio data in PCM 16-bit mono format
* @param length - length of the audio data
* @returns 1 if silence is occured and you can retrieve a new utterance with result method
* 0 if decoding continues
* -1 if exception occured */
int vosk_recognizer_accept_waveform(VoskRecognizer *recognizer, const char *data, int length);
/** Same as above but the version with the short data for language bindings where you have
* audio as array of shorts */
int vosk_recognizer_accept_waveform_s(VoskRecognizer *recognizer, const short *data, int length);
/** Same as above but the version with the float data for language bindings where you have
* audio as array of floats */
int vosk_recognizer_accept_waveform_f(VoskRecognizer *recognizer, const float *data, int length);
/** Returns speech recognition result
*
* @returns the result in JSON format which contains decoded line, decoded
* words, times in seconds and confidences. You can parse this result
* with any json parser
*
* <pre>
* {
* "text" : "what zero zero zero one"
* }
* </pre>
*
* If alternatives enabled it returns result with alternatives, see also vosk_recognizer_set_alternatives().
*
* If word times enabled returns word time, see also vosk_recognizer_set_word_times().
*/
const char *vosk_recognizer_result(VoskRecognizer *recognizer);
/** Returns partial speech recognition
*
* @returns partial speech recognition text which is not yet finalized.
* result may change as recognizer process more data.
*
* <pre>
* {
* "partial" : "cyril one eight zero"
* }
* </pre>
*/
const char *vosk_recognizer_partial_result(VoskRecognizer *recognizer);
/** Returns speech recognition result. Same as result, but doesn't wait for silence
* You usually call it in the end of the stream to get final bits of audio. It
* flushes the feature pipeline, so all remaining audio chunks got processed.
*
* @returns speech result in JSON format.
*/
const char *vosk_recognizer_final_result(VoskRecognizer *recognizer);
/** Resets the recognizer
*
* Resets current results so the recognition can continue from scratch */
void vosk_recognizer_reset(VoskRecognizer *recognizer);
/** Releases recognizer object
*
* Underlying model is also unreferenced and if needed released */
void vosk_recognizer_free(VoskRecognizer *recognizer);
/** Set log level for Kaldi messages
*
* @param log_level the level
* 0 - default value to print info and error messages but no debug
* less than 0 - don't print info messages
* greather than 0 - more verbose mode
*/
void vosk_set_log_level(int log_level);
/**
* Init, automatically select a CUDA device and allow multithreading.
* Must be called once from the main thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
void vosk_gpu_init();
/**
* Init CUDA device in a multi-threaded environment.
* Must be called for each thread.
* Has no effect if HAVE_CUDA flag is not set.
*/
void vosk_gpu_thread_init();
#ifdef __cplusplus
}
#endif
#endif /* VOSK_API_H */

View File

@@ -0,0 +1,11 @@
pre-commit:
parallel: true
commands:
lint:
files: git diff --name-only @{push}
glob: "*.{js,ts,jsx,tsx}"
run: npx eslint {files}
types:
files: git diff --name-only @{push}
glob: "*.{js,ts, jsx, tsx}"
run: npx tsc --noEmit

View File

@@ -0,0 +1,95 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
const LINKING_ERROR = `The package 'react-native-vosk' doesn't seem to be linked. Make sure: \n\n${_reactNative.Platform.select({
ios: '- You have run \'pod install\'\n',
default: ''
})}- You rebuilt the app after installing the package\n` + '- You are not using Expo managed workflow\n';
const VoskModule = _reactNative.NativeModules.Vosk ? _reactNative.NativeModules.Vosk : new Proxy({}, {
get() {
throw new Error(LINKING_ERROR);
}
});
const eventEmitter = new _reactNative.NativeEventEmitter(VoskModule);
class Vosk {
constructor() {
var _this = this;
_defineProperty(this, "loadModel", path => VoskModule.loadModel(path));
_defineProperty(this, "currentRegisteredEvents", []);
_defineProperty(this, "start", function () {
let grammar = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
return new Promise((resolve, reject) => {
// Check for permission
_this.requestRecordPermission()
// eslint-disable-next-line promise/prefer-await-to-then
.then(granted => {
if (!granted) return reject('Audio record permission denied');
// Setup events
_this.currentRegisteredEvents.push(eventEmitter.addListener('onResult', e => resolve(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onFinalResult', e => resolve(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onError', e => reject(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onTimeout', () => reject('timeout')));
// Start recognition
VoskModule.start(grammar);
})
// eslint-disable-next-line promise/prefer-await-to-then
.catch(e => {
reject(e);
});
// eslint-disable-next-line promise/prefer-await-to-then
}).finally(() => {
_this.cleanListeners();
});
});
_defineProperty(this, "stop", () => {
this.cleanListeners();
VoskModule.stop();
});
_defineProperty(this, "stopOnly", () => {
VoskModule.stopOnly();
});
_defineProperty(this, "cleanup", () => {
this.cleanListeners();
VoskModule.cleanup();
});
_defineProperty(this, "unload", () => {
this.cleanListeners();
VoskModule.unload();
});
_defineProperty(this, "onResult", onResult => {
return eventEmitter.addListener('onResult', onResult);
});
_defineProperty(this, "onFinalResult", onFinalResult => {
return eventEmitter.addListener('onFinalResult', onFinalResult);
});
_defineProperty(this, "onError", onError => {
return eventEmitter.addListener('onError', onError);
});
_defineProperty(this, "onTimeout", onTimeout => {
return eventEmitter.addListener('onTimeout', onTimeout);
});
_defineProperty(this, "requestRecordPermission", async () => {
if (_reactNative.Platform.OS === 'ios') return true;
const granted = await _reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.RECORD_AUDIO);
return granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED;
});
_defineProperty(this, "cleanListeners", () => {
// Clean event listeners
this.currentRegisteredEvents.forEach(subscription => subscription.remove());
this.currentRegisteredEvents = [];
});
} // Public functions
// Event listeners builders
// Private functions
}
exports.default = Vosk;
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,88 @@
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
import { NativeEventEmitter, NativeModules, PermissionsAndroid, Platform } from 'react-native';
const LINKING_ERROR = `The package 'react-native-vosk' doesn't seem to be linked. Make sure: \n\n${Platform.select({
ios: '- You have run \'pod install\'\n',
default: ''
})}- You rebuilt the app after installing the package\n` + '- You are not using Expo managed workflow\n';
const VoskModule = NativeModules.Vosk ? NativeModules.Vosk : new Proxy({}, {
get() {
throw new Error(LINKING_ERROR);
}
});
const eventEmitter = new NativeEventEmitter(VoskModule);
export default class Vosk {
constructor() {
var _this = this;
_defineProperty(this, "loadModel", path => VoskModule.loadModel(path));
_defineProperty(this, "currentRegisteredEvents", []);
_defineProperty(this, "start", function () {
let grammar = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
return new Promise((resolve, reject) => {
// Check for permission
_this.requestRecordPermission()
// eslint-disable-next-line promise/prefer-await-to-then
.then(granted => {
if (!granted) return reject('Audio record permission denied');
// Setup events
_this.currentRegisteredEvents.push(eventEmitter.addListener('onResult', e => resolve(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onFinalResult', e => resolve(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onError', e => reject(e.data)));
_this.currentRegisteredEvents.push(eventEmitter.addListener('onTimeout', () => reject('timeout')));
// Start recognition
VoskModule.start(grammar);
})
// eslint-disable-next-line promise/prefer-await-to-then
.catch(e => {
reject(e);
});
// eslint-disable-next-line promise/prefer-await-to-then
}).finally(() => {
_this.cleanListeners();
});
});
_defineProperty(this, "stop", () => {
this.cleanListeners();
VoskModule.stop();
});
_defineProperty(this, "stopOnly", () => {
VoskModule.stopOnly();
});
_defineProperty(this, "cleanup", () => {
this.cleanListeners();
VoskModule.cleanup();
});
_defineProperty(this, "unload", () => {
this.cleanListeners();
VoskModule.unload();
});
_defineProperty(this, "onResult", onResult => {
return eventEmitter.addListener('onResult', onResult);
});
_defineProperty(this, "onFinalResult", onFinalResult => {
return eventEmitter.addListener('onFinalResult', onFinalResult);
});
_defineProperty(this, "onError", onError => {
return eventEmitter.addListener('onError', onError);
});
_defineProperty(this, "onTimeout", onTimeout => {
return eventEmitter.addListener('onTimeout', onTimeout);
});
_defineProperty(this, "requestRecordPermission", async () => {
if (Platform.OS === 'ios') return true;
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO);
return granted === PermissionsAndroid.RESULTS.GRANTED;
});
_defineProperty(this, "cleanListeners", () => {
// Clean event listeners
this.currentRegisteredEvents.forEach(subscription => subscription.remove());
this.currentRegisteredEvents = [];
});
} // Public functions
// Event listeners builders
// Private functions
}
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1,23 @@
import { EventSubscription } from 'react-native';
declare type VoskEvent = {
/**
* Event datas
*/
data: string;
};
export default class Vosk {
loadModel: (path: string) => any;
private currentRegisteredEvents;
start: (grammar?: string[] | null) => Promise<String>;
stop: () => void;
stopOnly: () => void;
cleanup: () => void;
unload: () => void;
onResult: (onResult: (e: VoskEvent) => void) => EventSubscription;
onFinalResult: (onFinalResult: (e: VoskEvent) => void) => EventSubscription;
onError: (onError: (e: VoskEvent) => void) => EventSubscription;
onTimeout: (onTimeout: (e: VoskEvent) => void) => EventSubscription;
private requestRecordPermission;
private cleanListeners;
}
export {};

View File

@@ -0,0 +1,85 @@
{
"name": "@joplin/react-native-vosk",
"version": "0.1.13",
"description": "Speech recognition module for react native using Vosk library",
"main": "lib/commonjs/index",
"module": "lib/module/index",
"types": "lib/typescript/index.d.ts",
"react-native": "src/index",
"source": "src/index",
"files": [
"src",
"lib",
"android",
"ios",
"cpp",
"react-native-vosk.podspec",
"!lib/typescript/example",
"!android/build",
"!ios/build",
"!**/__tests__",
"!**/__fixtures__",
"!**/__mocks__"
],
"scripts": {
"build": "tsc --project tsconfig.json && bob build",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"tsc": "tsc --project tsconfig.json"
},
"keywords": [
"react-native",
"ios",
"android"
],
"repository": "https://github.com/riderodd/react-native-vosk",
"author": "Joris Gaudin <contact@jg-web.site> (https://www.jg-web.site/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/riderodd/react-native-vosk/issues"
},
"homepage": "https://github.com/riderodd/react-native-vosk#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@babel/eslint-parser": "7.18.2",
"@react-native-community/eslint-config": "3.0.2",
"@release-it/conventional-changelog": "5.0.0",
"@types/jest": "28.1.2",
"@types/react": "~17.0.21",
"@types/react-native": "0.68.0",
"eslint": "8.4.1",
"jest": "28.1.1",
"pod-install": "0.1.0",
"react": "18.2.0",
"react-native": "0.71.4",
"react-native-builder-bob": "0.18.3",
"release-it": "15.0.0",
"typescript": "4.5.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"jest": {
"preset": "react-native",
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
]
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
}
}

View File

@@ -0,0 +1,41 @@
require "json"
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
folly_version = '2021.06.28.00-v2'
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
Pod::Spec.new do |s|
s.name = "react-native-vosk"
s.version = package["version"]
s.summary = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.authors = package["author"]
s.platforms = { :ios => "10.0" }
s.source = { :git => "https://github.com/riderodd/react-native-vosk.git", :tag => "#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.resource_bundles = { 'Vosk' => ['ios/Vosk/*'] }
s.dependency "React-Core"
s.frameworks = "Accelerate"
s.library = "c++"
s.vendored_frameworks = "ios/libvosk.xcframework"
s.requires_arc = true
# Don't install the dependencies when we run `pod install` in the old architecture.
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
s.dependency "React-Codegen"
s.dependency "RCT-Folly", folly_version
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
end
end

View File

@@ -0,0 +1,29 @@
const os = require('os');
const path = require('path');
const child_process = require('child_process');
const root = path.resolve(__dirname, '..');
const args = process.argv.slice(2);
const options = {
cwd: process.cwd(),
env: process.env,
stdio: 'inherit',
encoding: 'utf-8',
};
if (os.type() === 'Windows_NT') {
options.shell = true;
}
let result;
if (process.cwd() !== root || args.length) {
// We're not in the root of the project, or additional arguments were passed
// In this case, forward the command to `yarn`
result = child_process.spawnSync('yarn', args, options);
} else {
// If `yarn` is run without arguments, perform bootstrap
result = child_process.spawnSync('yarn', ['bootstrap'], options);
}
process.exitCode = result.status;

View File

@@ -0,0 +1,117 @@
import {
EmitterSubscription,
EventSubscription,
NativeEventEmitter,
NativeModules,
PermissionsAndroid,
Platform,
} from 'react-native';
const LINKING_ERROR =
`The package 'react-native-vosk' doesn't seem to be linked. Make sure: \n\n${
Platform.select({ ios: '- You have run \'pod install\'\n', default: '' })
}- You rebuilt the app after installing the package\n` +
'- You are not using Expo managed workflow\n';
const VoskModule = NativeModules.Vosk
? NativeModules.Vosk
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
},
}
);
type VoskEvent = {
/**
* Event datas
*/
data: string;
};
const eventEmitter = new NativeEventEmitter(VoskModule);
export default class Vosk {
// Public functions
public loadModel = (path: string) => VoskModule.loadModel(path);
private currentRegisteredEvents: EmitterSubscription[] = [];
public start = (grammar: string[] | null = null): Promise<String> => {
return new Promise<String>((resolve, reject) => {
// Check for permission
this.requestRecordPermission()
// eslint-disable-next-line promise/prefer-await-to-then
.then((granted) => {
if (!granted) return reject('Audio record permission denied');
// Setup events
this.currentRegisteredEvents.push(eventEmitter.addListener('onResult', (e: VoskEvent) => resolve(e.data)));
this.currentRegisteredEvents.push(eventEmitter.addListener('onFinalResult', (e: VoskEvent) => resolve(e.data)));
this.currentRegisteredEvents.push(eventEmitter.addListener('onError', (e: VoskEvent) => reject(e.data)));
this.currentRegisteredEvents.push(eventEmitter.addListener('onTimeout', () => reject('timeout')));
// Start recognition
VoskModule.start(grammar);
})
// eslint-disable-next-line promise/prefer-await-to-then
.catch((e) => {
reject(e);
});
// eslint-disable-next-line promise/prefer-await-to-then
}).finally(() => {
this.cleanListeners();
});
};
public stop = () => {
this.cleanListeners();
VoskModule.stop();
};
public stopOnly = () => {
VoskModule.stopOnly();
};
public cleanup = () => {
this.cleanListeners();
VoskModule.cleanup();
};
public unload = () => {
this.cleanListeners();
VoskModule.unload();
};
// Event listeners builders
public onResult = (onResult: (e: VoskEvent)=> void): EventSubscription => {
return eventEmitter.addListener('onResult', onResult);
};
public onFinalResult = (onFinalResult: (e: VoskEvent)=> void): EventSubscription => {
return eventEmitter.addListener('onFinalResult', onFinalResult);
};
public onError = (onError: (e: VoskEvent)=> void): EventSubscription => {
return eventEmitter.addListener('onError', onError);
};
public onTimeout = (onTimeout: (e: VoskEvent)=> void): EventSubscription => {
return eventEmitter.addListener('onTimeout', onTimeout);
};
// Private functions
private requestRecordPermission = async () => {
if (Platform.OS === 'ios') return true;
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO!
);
return granted === PermissionsAndroid.RESULTS.GRANTED;
};
private cleanListeners = () => {
// Clean event listeners
this.currentRegisteredEvents.forEach(subscription => subscription.remove());
this.currentRegisteredEvents = [];
};
}

View File

@@ -0,0 +1,5 @@
{
"extends": "./tsconfig",
"exclude": ["example"]
}

View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"react-native-vosk": ["./src/index"]
},
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"esModuleInterop": true,
"importsNotUsedAsValues": "error",
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["esnext"],
"module": "esnext",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noImplicitUseStrict": false,
"noStrictGenericChecks": false,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "esnext"
}
}

View File

@@ -21,7 +21,7 @@
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json"
},
"dependencies": {
"@aws-sdk/client-s3": "3.241.0",
"@aws-sdk/client-s3": "3.296.0",
"@fortawesome/fontawesome-free": "5.15.4",
"@joplin/lib": "~2.11",
"@joplin/renderer": "~2.11",
@@ -37,7 +37,7 @@
"html-entities": "1.4.0",
"jquery": "3.6.4",
"knex": "2.4.2",
"koa": "2.14.1",
"koa": "2.14.2",
"markdown-it": "13.0.1",
"mustache": "4.2.0",
"nanoid": "2.1.11",

View File

@@ -37,6 +37,7 @@ describe('git-changelog', () => {
['Update typescript-eslint monorepo to v5 (#7291)', 'typescript-eslint', 'v5'],
['Update aws-sdk-js-v3 monorepo to v3.215.0', 'aws-sdk-js-v3', 'v3.215.0'],
['Update dependency moment to v2.29.4 (#7087)', 'moment', 'v2.29.4'],
['Update aws (#8106)', 'aws', ''],
];
for (const testCase of testCases) {
@@ -59,8 +60,9 @@ describe('git-changelog', () => {
{ package: 'sas', version: 'v1.2' },
{ package: 'moment', version: 'v3.4' },
{ package: 'eslint', version: 'v1.2' },
{ package: 'aws', version: '' },
],
'Updated packages moment (v3.4), sas (v1.2)',
'Updated packages aws, moment (v3.4), sas (v1.2)',
],
[
[

View File

@@ -130,6 +130,7 @@ export const parseRenovateMessage = (message: string): RenovateMessage => {
const regexes = [
/^Update dependency ([^\s]+) to ([^\s]+)/,
/^Update ([^\s]+) monorepo to ([^\s]+)/,
/^Update ([^\s]+)/,
];
for (const regex of regexes) {
@@ -138,7 +139,7 @@ export const parseRenovateMessage = (message: string): RenovateMessage => {
if (m) {
return {
package: m[1],
version: m[2],
version: m.length >= 3 ? m[2] : '',
};
}
}
@@ -172,7 +173,7 @@ export const summarizeRenovateMessages = (messages: RenovateMessage[]): string =
const temp: Record<string, string> = {};
for (const message of messages) {
if (!temp[message.package]) {
if (!(message.package in temp)) {
temp[message.package] = message.version;
} else {
if (message.version > temp[message.package]) {
@@ -183,7 +184,8 @@ export const summarizeRenovateMessages = (messages: RenovateMessage[]): string =
const temp2: string[] = [];
for (const [pkg, version] of Object.entries(temp)) {
temp2.push(`${pkg} (${version})`);
const versionString = version ? ` (${version})` : '';
temp2.push(`${pkg}${versionString}`);
}
temp2.sort();

View File

@@ -11,13 +11,13 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Renato Nunes Bastos <rnbastos@gmail.com>\n"
"Last-Translator: Douglas Leão <djlsplays@gmail.com>\n"
"Language-Team: \n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"X-Generator: Poedit 3.0.1\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:657
@@ -1089,23 +1089,23 @@ msgstr "A versão atual está atualizada."
#: packages/lib/models/Note.ts:38
msgid "custom order"
msgstr "ordem customizada"
msgstr "ordem personalizada"
#: packages/app-desktop/gui/NoteList/NoteList.tsx:167
msgid "Custom order"
msgstr "Ordem customizada"
msgstr "Ordem personalizada"
#: packages/lib/models/Setting.ts:1298
msgid "Custom stylesheet for Joplin-wide app styles"
msgstr "Folha de estilos customizada para estilos do app Joplin"
msgstr "Folha de estilos personalizada para estilos do app Joplin"
#: packages/lib/models/Setting.ts:1281
msgid "Custom stylesheet for rendered Markdown"
msgstr "Folha de estilos customizada para Markdown renderizado"
msgstr "Folha de estilos personalizada para Markdown renderizado"
#: packages/lib/models/Setting.ts:1440
msgid "Custom TLS certificates"
msgstr "Certificados TLS customizados"
msgstr "Certificados TLS personalizados"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:18
#: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.tsx:804
@@ -1921,7 +1921,7 @@ msgstr ""
#: packages/app-cli/app/command-help.js:36
msgid "For information on how to customise the shortcuts please visit %s"
msgstr "Para informações sobre como customizar os atalhos, por favor visite %s"
msgstr "Para informações sobre como personalizar os atalhos, por favor visite %s"
#: packages/app-mobile/components/screens/encryption-config.tsx:291
msgid ""

View File

@@ -15,6 +15,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:657
msgid "- Camera: to allow taking a picture and attaching it to a note."
@@ -131,9 +133,8 @@ msgstr "%d anteckningar matchar det här mönstret. Ta bort dem?"
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:135
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:144
#, fuzzy
msgid "%s"
msgstr "(%s)"
msgstr "%s"
#: packages/app-desktop/gui/utils/NoteListUtils.ts:61
msgid "%s - Copy"
@@ -215,11 +216,11 @@ msgid ""
"target is a regular note it will be converted to a to-do). Use \"clear\" to "
"convert the to-do back to a regular note."
msgstr ""
"<todo-command> kan antingen vara \"toggle\" eller \"clear\". Använd "
"\"toggle\" för att växla mellan givna uppgifter mellan slutförda och inte "
"slutförda tillstånd (Om målet är en vanlig anteckning kommer den att "
"konverteras till en att-göra). Använd \"clear\" för att konvertera uppgiften "
"att-göra tillbaka till en vanlig anteckning."
"<todo-command> kan antingen vara \"toggle\" eller \"clear\". Använd \"toggle"
"\" för att växla mellan givna uppgifter mellan slutförda och inte slutförda "
"tillstånd (Om målet är en vanlig anteckning kommer den att konverteras till "
"en att-göra). Använd \"clear\" för att konvertera uppgiften att-göra "
"tillbaka till en vanlig anteckning."
#: packages/lib/models/Setting.ts:1371
msgid "A3"
@@ -326,6 +327,8 @@ msgstr "Avancerade verktyg"
msgid ""
"All data, including notes, notebooks and tags will be permanently deleted."
msgstr ""
"All data, inklusive anteckningar, anteckningsböcker och taggar kommer att "
"tas bort permanent."
#: packages/app-desktop/gui/Sidebar/Sidebar.tsx:484
#: packages/app-mobile/components/screens/Notes.tsx:203
@@ -361,8 +364,8 @@ msgstr ""
#: packages/app-cli/app/command-mkbook.ts:33
#: packages/app-cli/app/command-mv.js:29
msgid ""
"Ambiguous notebook \"%s\". Please use short notebook id instead - press "
"\"ti\" to see the short notebook id"
"Ambiguous notebook \"%s\". Please use short notebook id instead - press \"ti"
"\" to see the short notebook id"
msgstr ""
"Tvetydig anteckningsbok \"%s\". Använd kort anteckningsbok-id istället - "
"tryck på \"ti\" för att se det korta anteckningsbok-id:t"
@@ -392,6 +395,8 @@ msgid ""
"Are you sure you want to return to the default layout? The current layout "
"configuration will be lost."
msgstr ""
"Är du säker på att du vill återgå till standardlayouten? Den nuvarande "
"layoutkonfigurationen kommer att gå förlorad."
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:517
msgid "Arguments:"
@@ -600,9 +605,8 @@ msgid "Cannot find \"%s\"."
msgstr "Det går inte att hitta \"%s\"."
#: packages/app-cli/app/command-mkbook.ts:28
#, fuzzy
msgid "Cannot find: \"%s\""
msgstr "Det går inte att hitta \"%s\"."
msgstr "Det går inte att hitta: \"%s\"."
#: packages/app-cli/app/command-sync.ts:164
msgid "Cannot initialise synchroniser."
@@ -750,9 +754,8 @@ msgid "Close"
msgstr "Stäng"
#: packages/app-mobile/components/Dropdown.tsx:166
#, fuzzy
msgid "Close dropdown"
msgstr "Stäng fönster"
msgstr "Stäng rullgardinsmenyn"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24
#: packages/app-desktop/gui/MenuBar.tsx:596
@@ -864,7 +867,7 @@ msgstr "Konflikter (bilagor)"
#: packages/lib/utils/joplinCloud.ts:171
msgid "Consolidated billing"
msgstr ""
msgstr "Konsoliderad fakturering"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:110
msgid "Content provided by %s"
@@ -872,7 +875,7 @@ msgstr "Innehåll tillhandahålls av %s"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:64
msgid "Continue"
msgstr ""
msgstr "Fortsätt"
#: packages/app-mobile/components/screens/Note.tsx:930
msgid "Convert to note"
@@ -977,9 +980,8 @@ msgstr ""
"Felet var: \"%s\""
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:55
#, fuzzy
msgid "Could not switch profile: %s"
msgstr "Det gick inte att installera insticksmodul: %s"
msgstr "Det gick inte att byta profil: %s"
#: packages/lib/components/EncryptionConfigScreen/utils.ts:219
msgid "Could not upgrade master key: %s"
@@ -995,16 +997,15 @@ msgstr ""
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:29
msgid "Could not verify your identify"
msgstr ""
msgstr "Det gick inte att verifiera din identitet"
#: packages/app-desktop/gui/PromptDialog.tsx:260
msgid "Create"
msgstr "Skapa"
#: packages/app-cli/app/command-mkbook.ts:19
#, fuzzy
msgid "Create a new notebook under a parent notebook."
msgstr "Skapar en ny anteckningsbok."
msgstr "Skapa en ny anteckningsbok under en överordnad anteckningsbok."
#: packages/app-mobile/components/note-list.js:101
msgid "Create a notebook"
@@ -1228,9 +1229,8 @@ msgid "Delete plugin \"%s\"?"
msgstr "Ta bort insticksmodulen \"%s\"?"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:101
#, fuzzy
msgid "Delete profile \"%s\""
msgstr "Ta bort anteckningen \"%s\"?"
msgstr "Ta bort profilen \"%s\""
#: packages/app-mobile/components/ScreenHeader.tsx:420
msgid "Delete selected notes"
@@ -1249,9 +1249,8 @@ msgstr ""
"anteckningsboken."
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:97
#, fuzzy
msgid "Delete this profile?"
msgstr "Ta bort dessa %d anteckningar?"
msgstr "Ta bort denna profil?"
#: packages/lib/Synchronizer.ts:186
msgid "Deleted local items: %d."
@@ -1496,9 +1495,8 @@ msgid "Edit notebook"
msgstr "Redigera anteckningsbok"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:87
#, fuzzy
msgid "Edit profile"
msgstr "Exportera profil"
msgstr "Redigera profil"
#: packages/app-desktop/commands/editProfileConfig.ts:9
msgid "Edit profile configuration..."
@@ -1590,7 +1588,7 @@ msgstr "Aktivera ljudspelare"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:61
msgid "Enable biometrics authentication?"
msgstr ""
msgstr "Aktivera biometrisk autentisering?"
#: packages/lib/models/Setting.ts:1146
msgid "Enable deflist syntax"
@@ -1643,7 +1641,7 @@ msgstr "Aktivera mjuk radbrytning"
#: packages/lib/models/Setting.ts:1048
msgid "Enable spellcheck in the text editor"
msgstr ""
msgstr "Aktivera stavningskontroll i textredigeraren"
#: packages/lib/models/Setting.ts:1143
msgid "Enable table of contents extension"
@@ -2308,7 +2306,7 @@ msgstr "Objekt som inte kan synkroniseras"
#: packages/app-desktop/gui/MenuBar.tsx:792
msgid "Join us on Twitter"
msgstr ""
msgstr "Följ oss på Twitter"
#: packages/app-desktop/gui/SyncWizard/Dialog.tsx:330
msgid ""
@@ -2561,9 +2559,8 @@ msgid "Manage multiple users"
msgstr "Hantera flera användare"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:611
#, fuzzy
msgid "Manage profiles"
msgstr "Uppdatera profil"
msgstr "Hantera profiler"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:320
msgid "Manage your plugins"
@@ -2650,9 +2647,8 @@ msgid "Missing required argument: %s"
msgstr "Saknade obligatoriskt argument: %s"
#: packages/app-cli/app/cli-utils.js:135
#, fuzzy
msgid "Missing required flag value: %s"
msgstr "Saknade obligatoriskt argument: %s"
msgstr ""
#: packages/app-mobile/components/side-menu-content.tsx:457
msgid "Mobile data - auto-sync disabled"
@@ -2829,9 +2825,8 @@ msgid "Not generated"
msgstr "Inte skapad"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:70
#, fuzzy
msgid "Not now"
msgstr "Gör det nu"
msgstr "Inte nu"
#: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:110
#: packages/server/src/models/UserModel.ts:215
@@ -3021,7 +3016,7 @@ msgstr "Öppna PDF-visare"
#: packages/app-desktop/commands/openProfileDirectory.ts:8
msgid "Open profile directory"
msgstr "Öppna profilmappen"
msgstr "Öppna profilmapp"
#: packages/lib/models/Setting.ts:429
msgid "Open Sync Wizard..."
@@ -3095,7 +3090,7 @@ msgstr "Klistra in"
#: packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts:6
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:181
msgid "Paste as text"
msgstr ""
msgstr "Klistra in som text"
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:541
msgid "Path:"
@@ -3123,8 +3118,8 @@ msgid ""
"Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list "
"below."
msgstr ""
"Klicka på \"%s\" för att fortsätta, eller ställ in lösenorden i listan "
"\"%s\" nedan."
"Klicka på \"%s\" för att fortsätta, eller ställ in lösenorden i listan \"%s"
"\" nedan."
#: packages/lib/components/EncryptionConfigScreen/utils.ts:64
msgid ""
@@ -3296,9 +3291,8 @@ msgid "Profile"
msgstr "Profil"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:95
#, fuzzy
msgid "Profile name"
msgstr "Profilnamn:"
msgstr "Profilnamn"
#: packages/app-desktop/gui/MainScreen/commands/addProfile.ts:17
msgid "Profile name:"
@@ -3309,9 +3303,8 @@ msgid "Profile Version: %s"
msgstr "Profilversion: %s"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:155
#, fuzzy
msgid "Profiles"
msgstr "Profil"
msgstr "Profiler"
#: packages/app-mobile/components/screens/Note.tsx:944
msgid "Properties"
@@ -3462,9 +3455,8 @@ msgid "Replace: "
msgstr "Ersätt: "
#: packages/app-desktop/gui/MainScreen/commands/resetLayout.ts:7
#, fuzzy
msgid "Reset application layout"
msgstr "Ändra programmets layout"
msgstr "Återställ applikationslayout"
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:219
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:220
@@ -3942,9 +3934,8 @@ msgid "Stop external editing"
msgstr "Stoppa extern redigering"
#: packages/lib/utils/joplinCloud.ts:129
#, fuzzy
msgid "Storage space"
msgstr "%d GB lagringsutrymme"
msgstr "Lagringsutrymme"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:19
msgid "Strikethrough"
@@ -4144,6 +4135,8 @@ msgid ""
"The active profile cannot be deleted. Switch to a different profile and try "
"again."
msgstr ""
"Den aktiva profilen kan inte raderas. Byt till en annan profil och försök "
"igen."
#: packages/app-desktop/bridge.ts:280
msgid ""
@@ -4207,7 +4200,7 @@ msgstr "Standardkrypteringsmetoden har ändrats, du bör omkryptera dina data."
#: packages/lib/services/profileConfig/index.ts:104
msgid "The default profile cannot be deleted"
msgstr ""
msgstr "Standardprofilen kan inte tas bort"
#: packages/lib/models/Setting.ts:1366
msgid ""
@@ -4357,6 +4350,8 @@ msgid ""
"The WebDAV implementation of %s is incompatible with Joplin, and as such is "
"no longer supported. Please use a different sync method."
msgstr ""
"WebDAV-implementeringen av %s är inkompatibel med Joplin och stöds därför "
"inte längre. Använd en annan synkroniseringsmetod."
#: packages/lib/models/Setting.ts:827
msgid "Theme"
@@ -4370,7 +4365,7 @@ msgstr ""
#: packages/app-mobile/components/screens/ConfigScreen.tsx:341
msgid "There are unsaved changes."
msgstr ""
msgstr "Det finns ändringar som inte har sparats."
#: packages/app-desktop/gui/NoteList/NoteList.tsx:512
msgid ""
@@ -4596,6 +4591,7 @@ msgid ""
"To switch the profile, the app is going to close and you will need to "
"restart it."
msgstr ""
"För att byta profil kommer appen att stängas och du måste starta om den."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:651
msgid ""
@@ -4836,7 +4832,7 @@ msgstr "Användning: %s"
#: packages/lib/models/Setting.ts:1640
msgid "Use biometrics to secure access to the app"
msgstr ""
msgstr "Använd biometri för att säkra åtkomsten till appen"
#: packages/app-cli/app/command-ls.js:32 packages/app-cli/app/command-tag.js:18
msgid ""
@@ -4877,6 +4873,8 @@ msgid ""
"Use your biometrics to secure access to your application. You can always set "
"it up later in Settings."
msgstr ""
"Använd din biometri för att säkra åtkomsten till din applikation. Du kan "
"alltid ställa in det senare i Inställningar."
#: packages/lib/models/Setting.ts:1244
msgid ""
@@ -4912,7 +4910,7 @@ msgstr "Giltig"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:25
msgid "Verify your identity"
msgstr ""
msgstr "Verifiera din identitet"
#: packages/app-desktop/gui/NoteList/NoteList.tsx:167
msgid "View"

View File

@@ -35,7 +35,7 @@
"node-fetch": "2.6.7",
"relative": "3.0.2",
"request": "2.88.2",
"sharp": "0.31.3",
"sharp": "0.32.0",
"source-map-support": "0.5.21",
"uri-template": "2.0.0",
"yargs": "17.7.1"
@@ -49,12 +49,12 @@
"@types/node": "18.11.18",
"@types/node-fetch": "2.6.2",
"@types/yargs": "17.0.20",
"gettext-extractor": "3.7.0",
"gettext-extractor": "3.7.1",
"gulp": "4.0.2",
"html-entities": "1.4.0",
"jest": "29.4.3",
"rss": "1.2.2",
"sass": "1.59.3",
"sass": "1.60.0",
"sqlite3": "5.1.6",
"typescript": "4.9.4"
},

View File

@@ -69,6 +69,22 @@ async function createRelease(name: string, tagName: string, version: string): Pr
await fs.writeFile(filename, content);
}
if (name !== 'vosk') {
{
const filename = `${rnDir}/services/voiceTyping/vosk.ts`;
originalContents[filename] = await fs.readFile(filename, 'utf8');
const newContent = await fs.readFile(`${rnDir}/services/voiceTyping/vosk.dummy.ts`, 'utf8');
await fs.writeFile(filename, newContent);
}
{
const filename = `${rnDir}/package.json`;
let content = await fs.readFile(filename, 'utf8');
originalContents[filename] = content;
content = content.replace(/\s+"react-native-vosk": ".*",/, '');
await fs.writeFile(filename, content);
}
}
const apkFilename = `joplin-v${suffix}.apk`;
const apkFilePath = `${releaseDir}/${apkFilename}`;
const downloadUrl = `https://github.com/laurent22/${projectName}/releases/download/${tagName}/${apkFilename}`;
@@ -77,45 +93,32 @@ async function createRelease(name: string, tagName: string, version: string): Pr
console.info(`Running from: ${process.cwd()}`);
await execCommand('yarn install', { showStdout: false });
await execCommand('yarn run tsc', { showStdout: false });
console.info(`Building APK file v${suffix}...`);
const buildDirName = `build-${name}`;
const buildDirBasePath = `${rnDir}/android/app/${buildDirName}`;
await fs.remove(buildDirBasePath);
let restoreDir = null;
let apkBuildCmd = '';
const apkBuildCmdArgs = ['assembleRelease', '-PbuildDir=build'];
let apkCleanBuild = '';
const apkBuildCmdArgs = ['assembleRelease', `-PbuildDir=${buildDirName}`]; // TOOD: change build dir, delete before
if (await fileExists('/mnt/c/Windows/System32/cmd.exe')) {
// In recent versions (of Gradle? React Native?), running gradlew.bat from WSL throws the following error:
// Error: Command failed: /mnt/c/Windows/System32/cmd.exe /c "cd packages\app-mobile\android && gradlew.bat assembleRelease -PbuildDir=build"
// FAILURE: Build failed with an exception.
// * What went wrong:
// Could not determine if Stdout is a console: could not get handle file information (errno 1)
// So we need to manually run the command from DOS, and then coming back here to finish the process once it's done.
// console.info('Run this command from DOS:');
// console.info('');
// console.info(`cd "${wslToWinPath(rootDir)}\\packages\\app-mobile\\android" && gradlew.bat ${apkBuildCmd}"`);
// console.info('');
// await readline('Press Enter when done:');
// apkBuildCmd = ''; // Clear the command because we've already ran it
// process.chdir(`${rnDir}/android`);
// apkBuildCmd = `/mnt/c/Windows/System32/cmd.exe /c "cd packages\\app-mobile\\android && gradlew.bat ${apkBuildCmd}"`;
// restoreDir = rootDir;
// apkBuildCmd = `/mnt/c/Windows/System32/cmd.exe /c "cd packages\\app-mobile\\android && gradlew.bat ${apkBuildCmd}"`;
await execCommandWithPipes('/mnt/c/Windows/System32/cmd.exe', ['/c', `cd packages\\app-mobile\\android && gradlew.bat ${apkBuildCmd}`]);
apkBuildCmd = '';
throw new Error('TODO: apkCleanBuild must be set');
} else {
process.chdir(`${rnDir}/android`);
apkBuildCmd = './gradlew';
apkCleanBuild = `./gradlew clean -PbuildDir=${buildDirName}`;
restoreDir = rootDir;
}
if (apkBuildCmd) {
await execCommand(apkCleanBuild);
await execCommandVerbose(apkBuildCmd, apkBuildCmdArgs);
}
@@ -123,12 +126,18 @@ async function createRelease(name: string, tagName: string, version: string): Pr
await fs.mkdirp(releaseDir);
const builtApk = `${buildDirBasePath}/outputs/apk/release/app-release.apk`;
const builtApkStat = await fs.stat(builtApk);
console.info(`Built APK at ${builtApk}`);
console.info('APK size:', builtApkStat.size);
console.info(`Copying APK to ${apkFilePath}`);
await fs.copy(`${rnDir}/android/app/build/outputs/apk/release/app-release.apk`, apkFilePath);
await fs.copy(builtApk, apkFilePath);
if (name === 'main') {
console.info(`Copying APK to ${releaseDir}/joplin-latest.apk`);
await fs.copy(`${rnDir}/android/app/build/outputs/apk/release/app-release.apk`, `${releaseDir}/joplin-latest.apk`);
await fs.copy(builtApk, `${releaseDir}/joplin-latest.apk`);
}
for (const filename in originalContents) {
@@ -149,9 +158,9 @@ async function main() {
await gitPullTry(false);
const isPreRelease = !('type' in argv) || argv.type === 'prerelease';
const releaseNameOnly = argv['release-name'];
process.chdir(rnDir);
await execCommand('yarn run build', { showStdout: false });
if (isPreRelease) console.info('Creating pre-release');
console.info('Updating version numbers in build.gradle...');
@@ -159,22 +168,15 @@ async function main() {
const newContent = updateGradleConfig();
const version = gradleVersionName(newContent);
const tagName = `android-v${version}`;
const releaseNames = ['main', '32bit'];
// const releaseNames = ['main', '32bit', 'vosk'];
const releaseNames = ['main', 'vosk'];
const releaseFiles: Record<string, Release> = {};
for (const releaseName of releaseNames) {
if (releaseNameOnly && releaseName !== releaseNameOnly) continue;
releaseFiles[releaseName] = await createRelease(releaseName, tagName, version);
}
// NOT TESTED: These commands should not be necessary anymore since they are
// done in completeReleaseWithChangelog()
// await execCommandVerbose('git', ['add', '-A']);
// await execCommandVerbose('git', ['commit', '-m', `Android release v${version}`]);
// await execCommandVerbose('git', ['tag', tagName]);
// await execCommandVerbose('git', ['push']);
// await execCommandVerbose('git', ['push', '--tags']);
console.info(`Creating GitHub release ${tagName}...`);
const releaseOptions = { isPreRelease: isPreRelease };

View File

@@ -1,5 +1,5 @@
<!-- DONATELINKS -->
[! [Faire un don via PayPal] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) [! [Sponsor sur GitHub] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg)](https://github.com/sponsors/laurent22/) [! [Devenez mécène] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin) [! [Faire un don en utilisant un IBAN] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg)](https://joplinapp.org/donate/#donations)
[! [Faire un don via PayPal] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&no_recurring=0&item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21&currency_code=EUR) [! [Sponsor sur GitHub] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg)](https://github.com/sponsors/laurent22/) [! [Devenez mécène] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin) [! [Faire un don en utilisant un IBAN] (https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-IBAN.svg)](https://joplinapp.org/donate/#donations)
<!-- DONATELINKS -->
* * *

View File

@@ -1,5 +1,14 @@
# Joplin changelog
## [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) (Pre-release) - 2023-04-27T09:27:45Z
- Improved: Revert to "normal" package compression ([2e2feab](https://github.com/laurent22/joplin/commit/2e2feab))
## [v2.10.15](https://github.com/laurent22/joplin/releases/tag/v2.10.15) (Pre-release) - 2023-04-26T22:02:16Z
- Improved: Remove custom PDF viewer to reduce application size ([#8028](https://github.com/laurent22/joplin/issues/8028))
- Improved: Compress installer to reduce size ([#8068](https://github.com/laurent22/joplin/issues/8068)) ([#8028](https://github.com/laurent22/joplin/issues/8028))
## [v2.10.13](https://github.com/laurent22/joplin/releases/tag/v2.10.13) (Pre-release) - 2023-04-03T16:53:46Z
- Fixed: Encode the non-ASCII characters in OneDrive URI ([#7868](https://github.com/laurent22/joplin/issues/7868)) ([#7851](https://github.com/laurent22/joplin/issues/7851) by Self Not Found)

View File

@@ -1,5 +1,30 @@
# Joplin Android app changelog
## [android-v2.11.9](https://github.com/laurent22/joplin/releases/tag/android-v2.11.9) (Pre-release) - 2023-05-07T18:07:15Z
- Improved: Disable Hermes engine (e9e9986)
- Fixed: Fix voice typing (d5eeb12)
## [android-v2.11.7](https://github.com/laurent22/joplin/releases/tag/android-v2.11.7) (Pre-release) - 2023-05-07T14:29:08Z
- Fixed crash when starting voice typing.
## [android-v2.11.6](https://github.com/laurent22/joplin/releases/tag/android-v2.11.6) (Pre-release) - 2023-05-07T13:53:31Z
- Disabled Hermes engine
## [android-v2.11.5](https://github.com/laurent22/joplin/releases/tag/android-v2.11.5) (Pre-release) - 2023-05-07T12:14:21Z
- Improved: Improved Vosk support (beta, fr only) (#8131)
- Improved: Updated packages react-native-share (v8.2.2), reselect (v4.1.8), sharp (v0.32.0)
## [android-v2.11.4](https://github.com/laurent22/joplin/releases/tag/android-v2.11.4) (Pre-release) - 2023-05-03T11:57:27Z
- New: Add support for offline speech to text (Beta - FR only) (#8115)
- Improved: Updated packages @react-native-community/netinfo (v9.3.9), aws, react-native-document-picker (v8.2.0), react-native-paper (v5.5.2), react-native-safe-area-context (v4.5.1), sass (v1.60.0)
- Fixed: Fixed sync crash (#8056) (#8017 by Arun Kumar)
- Fixed: Fixes issue where the note body is not updated after attaching a file (991c120)
## [android-v2.11.2](https://github.com/laurent22/joplin/releases/tag/android-v2.11.2) (Pre-release) - 2023-04-09T12:04:06Z
- Improved: Resolve #8022: Editor syntax highlighting was broken (#8023) (#8022 by Henry Heino)

View File

@@ -471,6 +471,30 @@
"created_at": "2023-04-24T10:05:20Z",
"repoId": 79162682,
"pullRequestNo": 8079
},
{
"name": "reportxx",
"id": 84130654,
"comment_id": 1528999971,
"created_at": "2023-04-30T11:18:47Z",
"repoId": 79162682,
"pullRequestNo": 8103
},
{
"name": "Linkosred",
"id": 118282653,
"comment_id": 1531194331,
"created_at": "2023-05-02T09:54:12Z",
"repoId": 79162682,
"pullRequestNo": 8110
},
{
"name": "cas--",
"id": 606038,
"comment_id": 1534317297,
"created_at": "2023-05-04T08:41:49Z",
"repoId": 79162682,
"pullRequestNo": 8126
}
]
}

View File

@@ -8,7 +8,7 @@ Most of all, your donation will make it possible to keep up the current developm
Platform | Link
--- | ---
PayPal | [![Donate on PayPal](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=E8JMYD2LQ8MMA&lc=GB&item_name=Joplin+Development&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) |
PayPal | [![Donate on PayPal](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=E8JMYD2LQ8MMA&no_recurring=0&item_name=I+rely+on+donations+to+maintain+and+improve+the+Joplin+open+source+project.+Thank+you+for+your+help+-+it+makes+a+difference%21&currency_code=EUR) |
GitHub Sponsor | [![Sponsor on GitHub](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/GitHub-Badge.svg)](https://github.com/sponsors/laurent22/)
Patreon | [![Become a patron](https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/badges/Patreon-Badge.svg)](https://www.patreon.com/joplin)
Bank Transfer | **IBAN:** FR76 4061 8803 5200 0400 7415 938<br>**BIC/SWIFT:** BOUS FRPP XXX

View File

@@ -17,3 +17,8 @@ Moreover, by getting a subscription you are supporting the development of the pr
We offer a 50% Education Discount for students and teachers. To claim it, please [contact us](mailto:support@joplincloud.com) from your university or school email address. You will then receive a URL you can use to subscribe to Joplin Cloud while benefiting from the 50% discount. This is valid for a whole year and can be renewed for as long as you are in education by contacting us again.
We may also offer bulk discounts for companies, associations and nonprofit organisations. Please [contact us](mailto:support@joplincloud.com) for more details.
## All our services are carbon neutral
<img style="max-width: 100%; float:right;" src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/plans/CarbonNeutral.png" /> Because the protection of the environment is as important as the protection of your data, we made our services carbon neutral. We believe that technology should not come at the expense of the planet, and we are committed to reducing our carbon footprint.<br/><br/>To do this, we invest in various ecological projects such as planting trees, cleaning up polluted areas, cleaning the oceans, and more, through organizations, associations, or companies. We constantly monitor our emissions and offset them through these projects to ensure that we are doing our part in the fight against climate change.

View File

@@ -22,4 +22,6 @@ However **there is a catch**: in Joplin, notes, even when edited with this Rich
- If a note is of 'Markup - Markdown' and contains HTML formatting, this may be lost when editing in the Rich Text editor as it cannot be converted to Markdown. Notes of 'Markup - HTML' are not affected by edits in the Rich Text editor as this conversion does not take place.
- All reference links (`[title][link-name]`) are converted to inline links (`[title](https://example.com)`) when Joplin saves changes from the Rich Text editor.
Those are the known limitations but if you notice any other issue not listed here, please let us know [in the forum](https://discourse.joplinapp.org/).

View File

@@ -0,0 +1,72 @@
# Joplin architecture
Joplin as a project is organised around three main components:
- The user applications: For [desktop](https://github.com/laurent22/joplin/blob/dev/readme/desktop.md), [mobile](https://github.com/laurent22/joplin/blob/dev/readme/mobile.md) and [CLI](https://github.com/laurent22/joplin/blob/dev/readme/terminal.md))
- [Joplin Server](https://github.com/laurent22/joplin/blob/dev/packages/server/README.md)
- [Web Clipper](https://github.com/laurent22/joplin/blob/dev/readme/clipper.md)
## User applications
The desktop, mobile and CLI applications have the same architecture and mostly the same backend. The main difference is for the UI, where they each use a different framework, and for system integration (eg. notifications, importing or exporting files, etc.).
The overall architecture for each application is as such:
- Front end: The user facing part of the app. This is different for each applications (see below for the difference between applications)
- Back end: This is shared by all applications. It is made of:
- Services: Provide high-level functionalities, such as the [search engine](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/searchengine), [plugin system](https://github.com/laurent22/joplin/tree/dev/packages/lib/services/plugins) or [synchroniser](https://github.com/laurent22/joplin/blob/dev/packages/lib/Synchronizer.ts).
- Models: The model layer sits between the services and database. They provide a higher level abstraction than SQL and utility functions to easily save data, such as notes, notebooks, etc.
- Database: All applications use a local [SQLite database](https://sqlite.org/index.html) to store notes, settings, cache, etc. This is only a local database.
- Configuration: The application is configured using a `settings.json` file. Its schema is available online: https://joplinapp.org/schema/settings.json
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/architecture/Application.png" style="max-width: 100%;"></div>
### Desktop application
The desktop application is developed using [Electron](https://www.electronjs.org/), with a front end done in [React](https://react.dev/). The backend runs on [Node.js](https://nodejs.org/).
### Mobile application
The mobile application is developed using [React Native](https://reactnative.dev/). The backend runs on React Native's own [Hermes JavaScript engine](https://hermesengine.dev/).
### CLI application
This application is to use Joplin from the terminal. It is developed using [terminal-kit](https://github.com/cronvel/terminal-kit). The backend runs on Node.js.
## Joplin Server
Joplin Server is used to synchronise the application data between multiple devices. Thus, a user can have their notes on their laptop, and on the go, on their phone. Joplin Server also allows user to share notebooks with others, and publish notes to the internet. Because it is designed specifically for Joplin, it also offers improved performance, compared to other synchronisation targets.
A typical Joplin Server installation will use the following elements:
- The [Joplin Server application](https://github.com/laurent22/joplin/blob/dev/packages/server/README.md). This is a Node.js application. It exposes a REST API that is used by the Joplin clients to upload or download notes, notebooks, and other Joplin objects.
- [PostgreSQL](https://www.postgresql.org/): it is used to save the "item" metadata. An "item" can be a note, a notebook, a tag, etc. It is also used to save other informations, such as user accounts, access logs, etc.
- [AWS S3](https://aws.amazon.com/s3/): it is used to save the item content. In other words, the note body, the file attachments, etc.
- [Nginx](https://www.nginx.com/): It is used as a reverse proxy and for TLS termination.
- A configuration file: A `.env` file, which contains environement variables used to configure the server.
This is a typical Joplin Server installation, but many of its components can be configured - for example it is possible to use a different database engine, or to use the filesystem instead of AWS S3. Any reverse proxy would also work - using Nginx is not required.
<img src="https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/architecture/JoplinServer.png" style="max-width: 100%;"></div>
## Web Clipper
The Web Clipper is a browser extension for Firefox and Chrome. It is used to capture a web page, a part of a page, or a screenshot from the browser, and save it to Joplin.
It is developed using the [WebExtensions API](https://extensionworkshop.com/documentation/develop/about-the-webextensions-api/) with the popup being done using React.
# More information
- [Plugin Architecture spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/plugins.md)
- [E2EE: Technical spec](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee.md)
- [E2EE: Workflow](https://github.com/laurent22/joplin/blob/dev/readme/spec/e2ee/workflow.md)
- [All Joplin technical specifications](https://github.com/laurent22/joplin/tree/dev/readme/spec)

View File

@@ -1,14 +1,14 @@
---
updated: 2023-04-04T18:29:53Z
updated: 2023-05-01T00:39:26Z
---
# Joplin statistics
| Name | Value |
| ----- | ----- |
| Total Windows downloads | 3,163,976 |
| Total macOs downloads | 1,156,759 |
| Total Linux downloads | 951,743 |
| Total Windows downloads | 3,216,948 |
| Total macOs downloads | 1,170,454 |
| Total Linux downloads | 965,858 |
| Windows % | 60% |
| macOS % | 22% |
| Linux % | 18% |
@@ -17,277 +17,279 @@ updated: 2023-04-04T18:29:53Z
| Version | Date | Windows | macOS | Linux | Total |
| ----- | ----- | ----- | ----- | ----- | ----- |
| [v2.10.13](https://github.com/laurent22/joplin/releases/tag/v2.10.13) (p) | 2023-04-03T16:53:46Z | 591 | 150 | 88 | 829 |
| [v2.10.12](https://github.com/laurent22/joplin/releases/tag/v2.10.12) (p) | 2023-03-23T12:17:13Z | 2,251 | 497 | 582 | 3,330 |
| [v2.10.11](https://github.com/laurent22/joplin/releases/tag/v2.10.11) (p) | 2023-03-17T10:54:02Z | 1,643 | 362 | 358 | 2,363 |
| [v2.10.10](https://github.com/laurent22/joplin/releases/tag/v2.10.10) (p) | 2023-03-13T23:16:37Z | 1,130 | 254 | 230 | 1,614 |
| [v2.10.9](https://github.com/laurent22/joplin/releases/tag/v2.10.9) (p) | 2023-03-12T16:16:45Z | 673 | 195 | 211 | 1,079 |
| [v2.10.8](https://github.com/laurent22/joplin/releases/tag/v2.10.8) (p) | 2023-02-26T12:53:55Z | 3,131 | 556 | 683 | 4,370 |
| [v2.10.7](https://github.com/laurent22/joplin/releases/tag/v2.10.7) (p) | 2023-02-24T10:56:20Z | 836 | 173 | 257 | 1,266 |
| [v2.10.6](https://github.com/laurent22/joplin/releases/tag/v2.10.6) (p) | 2023-02-20T14:00:05Z | 1,635 | 324 | 269 | 2,228 |
| [v2.10.4](https://github.com/laurent22/joplin/releases/tag/v2.10.4) (p) | 2023-01-05T13:09:20Z | 6,798 | 1,273 | 1,723 | 9,794 |
| [v2.10.3](https://github.com/laurent22/joplin/releases/tag/v2.10.3) (p) | 2022-12-31T15:53:23Z | 1,424 | 297 | 331 | 2,052 |
| [v2.10.2](https://github.com/laurent22/joplin/releases/tag/v2.10.2) (p) | 2022-12-18T18:05:08Z | 2,831 | 546 | 602 | 3,979 |
| [v2.9.17](https://github.com/laurent22/joplin/releases/tag/v2.9.17) | 2022-11-15T10:28:37Z | 271,015 | 92,781 | 66,682 | 430,478 |
| [v2.9.12](https://github.com/laurent22/joplin/releases/tag/v2.9.12) (p) | 2022-11-01T17:06:05Z | 10,028 | 587 | 518 | 11,133 |
| [v2.9.11](https://github.com/laurent22/joplin/releases/tag/v2.9.11) (p) | 2022-10-23T16:09:58Z | 2,189 | 510 | 694 | 3,393 |
| [v2.9.4](https://github.com/laurent22/joplin/releases/tag/v2.9.4) (p) | 2022-08-18T16:52:26Z | 7,212 | 1,844 | 2,170 | 11,226 |
| [v2.9.3](https://github.com/laurent22/joplin/releases/tag/v2.9.3) (p) | 2022-08-18T13:11:09Z | 289 | 74 | 249 | 612 |
| [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) (p) | 2023-04-27T09:27:45Z | 1,010 | 250 | 216 | 1,476 |
| [v2.10.15](https://github.com/laurent22/joplin/releases/tag/v2.10.15) (p) | 2023-04-26T22:02:16Z | 318 | 109 | 35 | 462 |
| [v2.10.13](https://github.com/laurent22/joplin/releases/tag/v2.10.13) (p) | 2023-04-03T16:53:46Z | 3,244 | 806 | 1,059 | 5,109 |
| [v2.10.12](https://github.com/laurent22/joplin/releases/tag/v2.10.12) (p) | 2023-03-23T12:17:13Z | 2,316 | 499 | 588 | 3,403 |
| [v2.10.11](https://github.com/laurent22/joplin/releases/tag/v2.10.11) (p) | 2023-03-17T10:54:02Z | 1,693 | 365 | 365 | 2,423 |
| [v2.10.10](https://github.com/laurent22/joplin/releases/tag/v2.10.10) (p) | 2023-03-13T23:16:37Z | 1,189 | 257 | 237 | 1,683 |
| [v2.10.9](https://github.com/laurent22/joplin/releases/tag/v2.10.9) (p) | 2023-03-12T16:16:45Z | 725 | 198 | 254 | 1,177 |
| [v2.10.8](https://github.com/laurent22/joplin/releases/tag/v2.10.8) (p) | 2023-02-26T12:53:55Z | 3,169 | 556 | 741 | 4,466 |
| [v2.10.7](https://github.com/laurent22/joplin/releases/tag/v2.10.7) (p) | 2023-02-24T10:56:20Z | 880 | 174 | 260 | 1,314 |
| [v2.10.6](https://github.com/laurent22/joplin/releases/tag/v2.10.6) (p) | 2023-02-20T14:00:05Z | 1,681 | 324 | 270 | 2,275 |
| [v2.10.4](https://github.com/laurent22/joplin/releases/tag/v2.10.4) (p) | 2023-01-05T13:09:20Z | 6,843 | 1,273 | 1,728 | 9,844 |
| [v2.10.3](https://github.com/laurent22/joplin/releases/tag/v2.10.3) (p) | 2022-12-31T15:53:23Z | 1,463 | 297 | 336 | 2,096 |
| [v2.10.2](https://github.com/laurent22/joplin/releases/tag/v2.10.2) (p) | 2022-12-18T18:05:08Z | 2,869 | 546 | 604 | 4,019 |
| [v2.9.17](https://github.com/laurent22/joplin/releases/tag/v2.9.17) | 2022-11-15T10:28:37Z | 315,476 | 105,214 | 79,286 | 499,976 |
| [v2.9.12](https://github.com/laurent22/joplin/releases/tag/v2.9.12) (p) | 2022-11-01T17:06:05Z | 10,070 | 589 | 518 | 11,177 |
| [v2.9.11](https://github.com/laurent22/joplin/releases/tag/v2.9.11) (p) | 2022-10-23T16:09:58Z | 2,228 | 510 | 695 | 3,433 |
| [v2.9.4](https://github.com/laurent22/joplin/releases/tag/v2.9.4) (p) | 2022-08-18T16:52:26Z | 7,258 | 1,845 | 2,170 | 11,273 |
| [v2.9.3](https://github.com/laurent22/joplin/releases/tag/v2.9.3) (p) | 2022-08-18T13:11:09Z | 290 | 74 | 249 | 613 |
| [v2.9.2](https://github.com/laurent22/joplin/releases/tag/v2.9.2) (p) | 2022-08-12T18:12:12Z | 1,470 | 430 | 0 | 1,900 |
| [v2.9.1](https://github.com/laurent22/joplin/releases/tag/v2.9.1) (p) | 2022-07-11T09:59:32Z | 6,632 | 1,322 | 1,378 | 9,332 |
| [v2.8.8](https://github.com/laurent22/joplin/releases/tag/v2.8.8) | 2022-05-17T14:48:06Z | 346,538 | 113,526 | 113,348 | 573,412 |
| [v2.8.7](https://github.com/laurent22/joplin/releases/tag/v2.8.7) (p) | 2022-05-06T11:34:27Z | 2,841 | 345 | 373 | 3,559 |
| [v2.8.6](https://github.com/laurent22/joplin/releases/tag/v2.8.6) (p) | 2022-05-03T10:08:25Z | 2,472 | 384 | 307 | 3,163 |
| [v2.8.5](https://github.com/laurent22/joplin/releases/tag/v2.8.5) (p) | 2022-04-27T13:51:50Z | 2,495 | 344 | 323 | 3,162 |
| [v2.8.4](https://github.com/laurent22/joplin/releases/tag/v2.8.4) (p) | 2022-04-19T18:00:09Z | 3,002 | 562 | 305 | 3,869 |
| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 2,421 | 259 | 241 | 2,921 |
| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 153,851 | 56,676 | 51,167 | 261,694 |
| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 32,782 | 16,752 | 4,775 | 54,309 |
| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 53,179 | 25,698 | 11,686 | 90,563 |
| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 3,182 | 446 | 439 | 4,067 |
| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 2,295 | 179 | 142 | 2,616 |
| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 1,852 | 109 | 65 | 2,026 |
| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 4,097 | 752 | 803 | 5,652 |
| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 2,129 | 139 | 116 | 2,384 |
| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 2,138 | 166 | 96 | 2,400 |
| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 133,095 | 51,156 | 49,215 | 233,466 |
| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 17,233 | 9,471 | 3,165 | 29,869 |
| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 2,301 | 148 | 84 | 2,533 |
| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 2,325 | 239 | 148 | 2,712 |
| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 1,646 | 37 | 15 | 1,698 |
| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 2,372 | 271 | 182 | 2,825 |
| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 4,157 | 776 | 684 | 5,617 |
| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 80,462 | 32,474 | 25,189 | 138,125 |
| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 45,346 | 19,006 | 10,056 | 74,408 |
| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 14,140 | 6,545 | 2,295 | 22,980 |
| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 1,955 | 190 | 150 | 2,295 |
| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 1,981 | 163 | 91 | 2,235 |
| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 3,463 | 553 | 555 | 4,571 |
| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 45,074 | 19,953 | 9,765 | 74,792 |
| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 4,439 | 888 | 928 | 6,255 |
| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 57,319 | 23,225 | 15,864 | 96,408 |
| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 8,307 | 1,757 | 516 | 10,580 |
| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 2,405 | 236 | 191 | 2,832 |
| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 3,171 | 444 | 502 | 4,117 |
| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 2,437 | 253 | 208 | 2,898 |
| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 2,656 | 362 | 343 | 3,361 |
| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 2,189 | 190 | 161 | 2,540 |
| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 1,910 | 134 | 76 | 2,120 |
| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 2,788 | 356 | 320 | 3,464 |
| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 82,757 | 31,387 | 33,102 | 147,246 |
| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 16,184 | 6,863 | 4,043 | 27,090 |
| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 16,373 | 7,498 | 2,578 | 26,449 |
| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 9,115 | 4,604 | 942 | 14,661 |
| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 2,520 | 261 | 191 | 2,972 |
| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 2,199 | 191 | 117 | 2,507 |
| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 4,096 | 721 | 630 | 5,447 |
| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 47,639 | 18,783 | 16,752 | 83,174 |
| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 3,738 | 400 | 376 | 4,514 |
| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 31,465 | 12,176 | 12,714 | 56,355 |
| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 15,100 | 6,387 | 3,614 | 25,101 |
| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 2,562 | 233 | 185 | 2,980 |
| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 2,662 | 294 | 199 | 3,155 |
| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 24,783 | 9,243 | 9,828 | 43,854 |
| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 3,805 | 920 | 377 | 5,102 |
| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 2,658 | 289 | 875 | 3,822 |
| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 2,179 | 225 | 574 | 2,978 |
| [v2.9.1](https://github.com/laurent22/joplin/releases/tag/v2.9.1) (p) | 2022-07-11T09:59:32Z | 6,676 | 1,323 | 1,378 | 9,377 |
| [v2.8.8](https://github.com/laurent22/joplin/releases/tag/v2.8.8) | 2022-05-17T14:48:06Z | 346,684 | 113,617 | 113,367 | 573,668 |
| [v2.8.7](https://github.com/laurent22/joplin/releases/tag/v2.8.7) (p) | 2022-05-06T11:34:27Z | 2,883 | 345 | 374 | 3,602 |
| [v2.8.6](https://github.com/laurent22/joplin/releases/tag/v2.8.6) (p) | 2022-05-03T10:08:25Z | 2,511 | 384 | 307 | 3,202 |
| [v2.8.5](https://github.com/laurent22/joplin/releases/tag/v2.8.5) (p) | 2022-04-27T13:51:50Z | 2,542 | 344 | 323 | 3,209 |
| [v2.8.4](https://github.com/laurent22/joplin/releases/tag/v2.8.4) (p) | 2022-04-19T18:00:09Z | 3,049 | 566 | 306 | 3,921 |
| [v2.8.2](https://github.com/laurent22/joplin/releases/tag/v2.8.2) (p) | 2022-04-14T11:35:45Z | 2,469 | 259 | 241 | 2,969 |
| [v2.7.15](https://github.com/laurent22/joplin/releases/tag/v2.7.15) | 2022-03-17T13:03:23Z | 153,923 | 56,678 | 51,177 | 261,778 |
| [v2.7.14](https://github.com/laurent22/joplin/releases/tag/v2.7.14) | 2022-02-27T11:30:53Z | 32,824 | 16,752 | 4,775 | 54,351 |
| [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) | 2022-02-24T17:42:12Z | 53,223 | 25,698 | 11,686 | 90,607 |
| [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (p) | 2022-02-14T15:06:14Z | 3,229 | 446 | 440 | 4,115 |
| [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (p) | 2022-02-12T13:00:02Z | 2,344 | 179 | 142 | 2,665 |
| [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (p) | 2022-02-11T18:19:09Z | 1,901 | 110 | 66 | 2,077 |
| [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (p) | 2022-01-19T09:35:27Z | 4,141 | 752 | 803 | 5,696 |
| [v2.7.7](https://github.com/laurent22/joplin/releases/tag/v2.7.7) (p) | 2022-01-18T14:05:07Z | 2,180 | 139 | 116 | 2,435 |
| [v2.7.6](https://github.com/laurent22/joplin/releases/tag/v2.7.6) (p) | 2022-01-17T17:08:28Z | 2,182 | 166 | 96 | 2,444 |
| [v2.6.10](https://github.com/laurent22/joplin/releases/tag/v2.6.10) | 2021-12-19T11:31:16Z | 133,165 | 51,156 | 49,221 | 233,542 |
| [v2.6.9](https://github.com/laurent22/joplin/releases/tag/v2.6.9) | 2021-12-17T11:57:32Z | 17,277 | 9,471 | 3,166 | 29,914 |
| [v2.6.7](https://github.com/laurent22/joplin/releases/tag/v2.6.7) (p) | 2021-12-16T10:47:23Z | 2,349 | 148 | 84 | 2,581 |
| [v2.6.6](https://github.com/laurent22/joplin/releases/tag/v2.6.6) (p) | 2021-12-13T12:31:43Z | 2,366 | 240 | 148 | 2,754 |
| [v2.6.5](https://github.com/laurent22/joplin/releases/tag/v2.6.5) (p) | 2021-12-13T10:07:04Z | 1,694 | 37 | 15 | 1,746 |
| [v2.6.4](https://github.com/laurent22/joplin/releases/tag/v2.6.4) (p) | 2021-12-09T19:53:43Z | 2,414 | 271 | 183 | 2,868 |
| [v2.6.2](https://github.com/laurent22/joplin/releases/tag/v2.6.2) (p) | 2021-11-18T12:19:12Z | 4,207 | 776 | 684 | 5,667 |
| [v2.5.12](https://github.com/laurent22/joplin/releases/tag/v2.5.12) | 2021-11-08T11:07:11Z | 80,524 | 32,479 | 25,197 | 138,200 |
| [v2.5.10](https://github.com/laurent22/joplin/releases/tag/v2.5.10) | 2021-11-01T08:22:42Z | 45,403 | 19,008 | 10,056 | 74,467 |
| [v2.5.8](https://github.com/laurent22/joplin/releases/tag/v2.5.8) | 2021-10-31T11:38:03Z | 14,178 | 6,545 | 2,295 | 23,018 |
| [v2.5.7](https://github.com/laurent22/joplin/releases/tag/v2.5.7) (p) | 2021-10-29T14:47:33Z | 1,996 | 190 | 150 | 2,336 |
| [v2.5.6](https://github.com/laurent22/joplin/releases/tag/v2.5.6) (p) | 2021-10-28T22:03:09Z | 2,027 | 163 | 91 | 2,281 |
| [v2.5.4](https://github.com/laurent22/joplin/releases/tag/v2.5.4) (p) | 2021-10-19T10:10:54Z | 3,510 | 553 | 556 | 4,619 |
| [v2.4.12](https://github.com/laurent22/joplin/releases/tag/v2.4.12) | 2021-10-13T17:24:34Z | 45,134 | 19,953 | 9,765 | 74,852 |
| [v2.5.1](https://github.com/laurent22/joplin/releases/tag/v2.5.1) (p) | 2021-10-02T09:51:58Z | 4,486 | 888 | 928 | 6,302 |
| [v2.4.9](https://github.com/laurent22/joplin/releases/tag/v2.4.9) | 2021-09-29T19:08:58Z | 57,367 | 23,225 | 15,867 | 96,459 |
| [v2.4.8](https://github.com/laurent22/joplin/releases/tag/v2.4.8) (p) | 2021-09-22T19:01:46Z | 8,352 | 1,758 | 517 | 10,627 |
| [v2.4.7](https://github.com/laurent22/joplin/releases/tag/v2.4.7) (p) | 2021-09-19T12:53:22Z | 2,458 | 237 | 191 | 2,886 |
| [v2.4.6](https://github.com/laurent22/joplin/releases/tag/v2.4.6) (p) | 2021-09-09T18:57:17Z | 3,224 | 444 | 502 | 4,170 |
| [v2.4.5](https://github.com/laurent22/joplin/releases/tag/v2.4.5) (p) | 2021-09-06T18:03:28Z | 2,481 | 254 | 208 | 2,943 |
| [v2.4.4](https://github.com/laurent22/joplin/releases/tag/v2.4.4) (p) | 2021-08-30T16:02:51Z | 2,699 | 362 | 343 | 3,404 |
| [v2.4.3](https://github.com/laurent22/joplin/releases/tag/v2.4.3) (p) | 2021-08-28T15:27:32Z | 2,229 | 190 | 162 | 2,581 |
| [v2.4.2](https://github.com/laurent22/joplin/releases/tag/v2.4.2) (p) | 2021-08-27T17:13:21Z | 1,953 | 134 | 76 | 2,163 |
| [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (p) | 2021-08-21T11:52:30Z | 2,832 | 356 | 320 | 3,508 |
| [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) | 2021-08-17T06:43:30Z | 82,812 | 31,388 | 33,104 | 147,304 |
| [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) | 2021-08-14T09:19:40Z | 16,241 | 6,864 | 4,043 | 27,148 |
| [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) | 2021-08-11T11:03:26Z | 16,415 | 7,498 | 2,580 | 26,493 |
| [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (p) | 2021-08-09T19:29:20Z | 9,168 | 4,604 | 942 | 14,714 |
| [v2.2.5](https://github.com/laurent22/joplin/releases/tag/v2.2.5) (p) | 2021-08-07T10:35:24Z | 2,563 | 261 | 191 | 3,015 |
| [v2.2.4](https://github.com/laurent22/joplin/releases/tag/v2.2.4) (p) | 2021-08-05T16:42:48Z | 2,244 | 192 | 117 | 2,553 |
| [v2.2.2](https://github.com/laurent22/joplin/releases/tag/v2.2.2) (p) | 2021-07-19T10:28:35Z | 4,137 | 721 | 630 | 5,488 |
| [v2.1.9](https://github.com/laurent22/joplin/releases/tag/v2.1.9) | 2021-07-19T10:28:43Z | 47,689 | 18,783 | 16,754 | 83,226 |
| [v2.2.1](https://github.com/laurent22/joplin/releases/tag/v2.2.1) (p) | 2021-07-09T17:38:25Z | 3,781 | 400 | 376 | 4,557 |
| [v2.1.8](https://github.com/laurent22/joplin/releases/tag/v2.1.8) | 2021-07-03T08:25:16Z | 31,515 | 12,177 | 12,714 | 56,406 |
| [v2.1.7](https://github.com/laurent22/joplin/releases/tag/v2.1.7) | 2021-06-26T19:48:55Z | 15,154 | 6,387 | 3,614 | 25,155 |
| [v2.1.5](https://github.com/laurent22/joplin/releases/tag/v2.1.5) (p) | 2021-06-23T15:08:52Z | 2,609 | 233 | 185 | 3,027 |
| [v2.1.3](https://github.com/laurent22/joplin/releases/tag/v2.1.3) (p) | 2021-06-19T16:32:51Z | 2,704 | 294 | 199 | 3,197 |
| [v2.0.11](https://github.com/laurent22/joplin/releases/tag/v2.0.11) | 2021-06-16T17:55:49Z | 24,876 | 9,249 | 9,832 | 43,957 |
| [v2.0.10](https://github.com/laurent22/joplin/releases/tag/v2.0.10) | 2021-06-16T07:58:29Z | 3,855 | 920 | 377 | 5,152 |
| [v2.0.9](https://github.com/laurent22/joplin/releases/tag/v2.0.9) (p) | 2021-06-12T09:30:30Z | 2,697 | 289 | 875 | 3,861 |
| [v2.0.8](https://github.com/laurent22/joplin/releases/tag/v2.0.8) (p) | 2021-06-10T16:15:08Z | 2,224 | 225 | 574 | 3,023 |
| [v2.0.4](https://github.com/laurent22/joplin/releases/tag/v2.0.4) (p) | 2021-06-02T12:54:17Z | 1,607 | 388 | 374 | 2,369 |
| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 3,947 | 486 | 1,664 | 6,097 |
| [v2.0.2](https://github.com/laurent22/joplin/releases/tag/v2.0.2) (p) | 2021-05-21T18:07:48Z | 3,994 | 486 | 1,664 | 6,144 |
| [v2.0.1](https://github.com/laurent22/joplin/releases/tag/v2.0.1) (p) | 2021-05-15T13:22:58Z | 874 | 269 | 1,018 | 2,161 |
| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 39,392 | 16,268 | 19,403 | 75,063 |
| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 2,160 | 135 | 454 | 2,749 |
| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 3,161 | 303 | 934 | 4,398 |
| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 3,427 | 434 | 1,282 | 5,143 |
| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 4,482 | 824 | 2,450 | 7,756 |
| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 118,342 | 42,858 | 64,351 | 225,551 |
| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,248 | 4,856 | 4,490 | 23,594 |
| [v1.8.5](https://github.com/laurent22/joplin/releases/tag/v1.8.5) | 2021-05-10T11:58:14Z | 39,443 | 16,269 | 19,406 | 75,118 |
| [v1.8.4](https://github.com/laurent22/joplin/releases/tag/v1.8.4) (p) | 2021-05-09T18:05:05Z | 2,195 | 135 | 454 | 2,784 |
| [v1.8.3](https://github.com/laurent22/joplin/releases/tag/v1.8.3) (p) | 2021-05-04T10:38:16Z | 3,201 | 303 | 934 | 4,438 |
| [v1.8.2](https://github.com/laurent22/joplin/releases/tag/v1.8.2) (p) | 2021-04-25T10:50:51Z | 3,476 | 434 | 1,283 | 5,193 |
| [v1.8.1](https://github.com/laurent22/joplin/releases/tag/v1.8.1) (p) | 2021-03-29T10:46:41Z | 4,524 | 824 | 2,450 | 7,798 |
| [v1.7.11](https://github.com/laurent22/joplin/releases/tag/v1.7.11) | 2021-02-03T12:50:01Z | 118,421 | 42,863 | 64,359 | 225,643 |
| [v1.7.10](https://github.com/laurent22/joplin/releases/tag/v1.7.10) | 2021-01-30T13:25:29Z | 14,253 | 4,856 | 4,493 | 23,602 |
| [v1.7.9](https://github.com/laurent22/joplin/releases/tag/v1.7.9) (p) | 2021-01-28T09:50:21Z | 502 | 133 | 498 | 1,133 |
| [v1.7.6](https://github.com/laurent22/joplin/releases/tag/v1.7.6) (p) | 2021-01-27T10:36:05Z | 316 | 93 | 288 | 697 |
| [v1.7.5](https://github.com/laurent22/joplin/releases/tag/v1.7.5) (p) | 2021-01-26T09:53:05Z | 409 | 205 | 454 | 1,068 |
| [v1.7.4](https://github.com/laurent22/joplin/releases/tag/v1.7.4) (p) | 2021-01-22T17:58:38Z | 695 | 204 | 625 | 1,524 |
| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 21,011 | 7,704 | 7,607 | 36,322 |
| [v1.6.8](https://github.com/laurent22/joplin/releases/tag/v1.6.8) | 2021-01-20T18:11:34Z | 21,067 | 7,704 | 7,607 | 36,378 |
| [v1.7.3](https://github.com/laurent22/joplin/releases/tag/v1.7.3) (p) | 2021-01-20T11:23:50Z | 349 | 77 | 442 | 868 |
| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 12,818 | 4,639 | 4,545 | 22,002 |
| [v1.6.7](https://github.com/laurent22/joplin/releases/tag/v1.6.7) | 2021-01-11T23:20:33Z | 12,870 | 4,639 | 4,549 | 22,058 |
| [v1.6.6](https://github.com/laurent22/joplin/releases/tag/v1.6.6) | 2021-01-09T16:15:31Z | 12,712 | 3,419 | 4,797 | 20,928 |
| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 2,426 | 77 | 308 | 2,811 |
| [v1.6.5](https://github.com/laurent22/joplin/releases/tag/v1.6.5) (p) | 2021-01-09T01:24:32Z | 2,467 | 77 | 308 | 2,852 |
| [v1.6.4](https://github.com/laurent22/joplin/releases/tag/v1.6.4) (p) | 2021-01-07T19:11:32Z | 392 | 78 | 204 | 674 |
| [v1.6.2](https://github.com/laurent22/joplin/releases/tag/v1.6.2) (p) | 2021-01-04T22:34:55Z | 673 | 228 | 590 | 1,491 |
| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 13,300 | 5,210 | 5,531 | 24,041 |
| [v1.5.14](https://github.com/laurent22/joplin/releases/tag/v1.5.14) | 2020-12-30T01:48:46Z | 13,350 | 5,210 | 5,531 | 24,091 |
| [v1.6.1](https://github.com/laurent22/joplin/releases/tag/v1.6.1) (p) | 2020-12-29T19:37:45Z | 171 | 38 | 168 | 377 |
| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 652 | 222 | 205 | 1,079 |
| [v1.5.13](https://github.com/laurent22/joplin/releases/tag/v1.5.13) | 2020-12-29T18:29:15Z | 652 | 222 | 208 | 1,082 |
| [v1.5.12](https://github.com/laurent22/joplin/releases/tag/v1.5.12) | 2020-12-28T15:14:08Z | 2,424 | 1,774 | 925 | 5,123 |
| [v1.5.11](https://github.com/laurent22/joplin/releases/tag/v1.5.11) | 2020-12-27T19:54:07Z | 14,182 | 4,634 | 4,282 | 23,098 |
| [v1.5.11](https://github.com/laurent22/joplin/releases/tag/v1.5.11) | 2020-12-27T19:54:07Z | 14,182 | 4,635 | 4,282 | 23,099 |
| [v1.5.10](https://github.com/laurent22/joplin/releases/tag/v1.5.10) (p) | 2020-12-26T12:35:36Z | 294 | 107 | 270 | 671 |
| [v1.5.9](https://github.com/laurent22/joplin/releases/tag/v1.5.9) (p) | 2020-12-23T18:01:08Z | 327 | 372 | 411 | 1,110 |
| [v1.5.8](https://github.com/laurent22/joplin/releases/tag/v1.5.8) (p) | 2020-12-20T09:45:19Z | 566 | 165 | 644 | 1,375 |
| [v1.5.7](https://github.com/laurent22/joplin/releases/tag/v1.5.7) (p) | 2020-12-10T12:58:33Z | 889 | 254 | 994 | 2,137 |
| [v1.5.4](https://github.com/laurent22/joplin/releases/tag/v1.5.4) (p) | 2020-12-05T12:07:49Z | 693 | 167 | 635 | 1,495 |
| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 28,051 | 13,536 | 11,682 | 53,269 |
| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,556 | 3,887 | 3,144 | 18,587 |
| [v1.4.19](https://github.com/laurent22/joplin/releases/tag/v1.4.19) | 2020-12-01T11:11:16Z | 28,101 | 13,538 | 11,684 | 53,323 |
| [v1.4.18](https://github.com/laurent22/joplin/releases/tag/v1.4.18) | 2020-11-28T12:21:41Z | 11,566 | 3,887 | 3,144 | 18,597 |
| [v1.4.16](https://github.com/laurent22/joplin/releases/tag/v1.4.16) | 2020-11-27T19:40:16Z | 1,500 | 836 | 603 | 2,939 |
| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 919 | 489 | 278 | 1,686 |
| [v1.4.15](https://github.com/laurent22/joplin/releases/tag/v1.4.15) | 2020-11-27T13:25:43Z | 920 | 490 | 278 | 1,688 |
| [v1.4.12](https://github.com/laurent22/joplin/releases/tag/v1.4.12) | 2020-11-23T18:58:07Z | 3,066 | 1,336 | 1,310 | 5,712 |
| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 3,189 | 161 | 595 | 3,945 |
| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 648 | 198 | 686 | 1,532 |
| [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 839 | 144 | 404 | 1,387 |
| [v1.4.11](https://github.com/laurent22/joplin/releases/tag/v1.4.11) (p) | 2020-11-19T23:06:51Z | 3,240 | 161 | 595 | 3,996 |
| [v1.4.10](https://github.com/laurent22/joplin/releases/tag/v1.4.10) (p) | 2020-11-14T09:53:15Z | 648 | 199 | 686 | 1,533 |
| [v1.4.9](https://github.com/laurent22/joplin/releases/tag/v1.4.9) (p) | 2020-11-11T14:23:17Z | 839 | 145 | 404 | 1,388 |
| [v1.4.7](https://github.com/laurent22/joplin/releases/tag/v1.4.7) (p) | 2020-11-07T18:23:29Z | 526 | 176 | 517 | 1,219 |
| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 33,316 | 11,345 | 10,522 | 55,183 |
| [v1.3.17](https://github.com/laurent22/joplin/releases/tag/v1.3.17) (p) | 2020-11-06T11:35:15Z | 52 | 28 | 26 | 106 |
| [v1.3.18](https://github.com/laurent22/joplin/releases/tag/v1.3.18) | 2020-11-06T12:07:02Z | 33,366 | 11,345 | 10,522 | 55,233 |
| [v1.3.17](https://github.com/laurent22/joplin/releases/tag/v1.3.17) (p) | 2020-11-06T11:35:15Z | 52 | 29 | 26 | 107 |
| [v1.4.6](https://github.com/laurent22/joplin/releases/tag/v1.4.6) (p) | 2020-11-05T22:44:12Z | 668 | 97 | 55 | 820 |
| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,597 | 1,306 | 851 | 4,754 |
| [v1.3.15](https://github.com/laurent22/joplin/releases/tag/v1.3.15) | 2020-11-04T12:22:50Z | 2,598 | 1,307 | 853 | 4,758 |
| [v1.3.11](https://github.com/laurent22/joplin/releases/tag/v1.3.11) (p) | 2020-10-31T13:22:20Z | 703 | 190 | 484 | 1,377 |
| [v1.3.10](https://github.com/laurent22/joplin/releases/tag/v1.3.10) (p) | 2020-10-29T13:27:14Z | 380 | 119 | 319 | 818 |
| [v1.3.9](https://github.com/laurent22/joplin/releases/tag/v1.3.9) (p) | 2020-10-23T16:04:26Z | 843 | 247 | 636 | 1,726 |
| [v1.3.8](https://github.com/laurent22/joplin/releases/tag/v1.3.8) (p) | 2020-10-21T18:46:29Z | 525 | 121 | 333 | 979 |
| [v1.3.7](https://github.com/laurent22/joplin/releases/tag/v1.3.7) (p) | 2020-10-20T11:35:55Z | 298 | 89 | 345 | 732 |
| [v1.3.5](https://github.com/laurent22/joplin/releases/tag/v1.3.5) (p) | 2020-10-17T14:26:35Z | 475 | 137 | 409 | 1,021 |
| [v1.3.7](https://github.com/laurent22/joplin/releases/tag/v1.3.7) (p) | 2020-10-20T11:35:55Z | 299 | 89 | 345 | 733 |
| [v1.3.5](https://github.com/laurent22/joplin/releases/tag/v1.3.5) (p) | 2020-10-17T14:26:35Z | 476 | 137 | 409 | 1,022 |
| [v1.3.3](https://github.com/laurent22/joplin/releases/tag/v1.3.3) (p) | 2020-10-17T10:56:57Z | 123 | 50 | 36 | 209 |
| [v1.3.2](https://github.com/laurent22/joplin/releases/tag/v1.3.2) (p) | 2020-10-11T20:39:49Z | 672 | 187 | 571 | 1,430 |
| [v1.3.2](https://github.com/laurent22/joplin/releases/tag/v1.3.2) (p) | 2020-10-11T20:39:49Z | 673 | 187 | 571 | 1,431 |
| [v1.3.1](https://github.com/laurent22/joplin/releases/tag/v1.3.1) (p) | 2020-10-11T15:10:18Z | 87 | 56 | 47 | 190 |
| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 47,134 | 17,750 | 14,053 | 78,937 |
| [v1.2.4](https://github.com/laurent22/joplin/releases/tag/v1.2.4) (p) | 2020-09-30T07:34:29Z | 823 | 252 | 802 | 1,877 |
| [v1.2.6](https://github.com/laurent22/joplin/releases/tag/v1.2.6) | 2020-10-09T13:56:59Z | 47,202 | 17,750 | 14,056 | 79,008 |
| [v1.2.4](https://github.com/laurent22/joplin/releases/tag/v1.2.4) (p) | 2020-09-30T07:34:29Z | 824 | 253 | 802 | 1,879 |
| [v1.2.3](https://github.com/laurent22/joplin/releases/tag/v1.2.3) (p) | 2020-09-29T15:13:02Z | 223 | 69 | 84 | 376 |
| [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 1,113 | 211 | 642 | 1,966 |
| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 27,996 | 13,516 | 7,758 | 49,270 |
| [v1.2.2](https://github.com/laurent22/joplin/releases/tag/v1.2.2) (p) | 2020-09-22T20:31:55Z | 1,113 | 212 | 642 | 1,967 |
| [v1.1.4](https://github.com/laurent22/joplin/releases/tag/v1.1.4) | 2020-09-21T11:20:09Z | 27,999 | 13,516 | 7,761 | 49,276 |
| [v1.1.3](https://github.com/laurent22/joplin/releases/tag/v1.1.3) (p) | 2020-09-17T10:30:37Z | 577 | 156 | 468 | 1,201 |
| [v1.1.2](https://github.com/laurent22/joplin/releases/tag/v1.1.2) (p) | 2020-09-15T12:58:38Z | 383 | 123 | 256 | 762 |
| [v1.1.2](https://github.com/laurent22/joplin/releases/tag/v1.1.2) (p) | 2020-09-15T12:58:38Z | 387 | 123 | 256 | 766 |
| [v1.1.1](https://github.com/laurent22/joplin/releases/tag/v1.1.1) (p) | 2020-09-11T23:32:47Z | 539 | 203 | 355 | 1,097 |
| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 22,428 | 10,021 | 5,650 | 38,099 |
| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,844 | 6,429 | 3,027 | 22,300 |
| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 26,296 | 5,944 | 5,117 | 37,357 |
| [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 931 | 234 | 407 | 1,572 |
| [v1.0.237](https://github.com/laurent22/joplin/releases/tag/v1.0.237) (p) | 2020-08-29T15:38:04Z | 596 | 933 | 345 | 1,874 |
| [v1.0.245](https://github.com/laurent22/joplin/releases/tag/v1.0.245) | 2020-09-09T12:56:10Z | 22,437 | 10,021 | 5,652 | 38,110 |
| [v1.0.242](https://github.com/laurent22/joplin/releases/tag/v1.0.242) | 2020-09-04T22:00:34Z | 12,844 | 6,430 | 3,027 | 22,301 |
| [v1.0.241](https://github.com/laurent22/joplin/releases/tag/v1.0.241) | 2020-09-04T18:06:00Z | 26,304 | 5,944 | 5,117 | 37,365 |
| [v1.0.239](https://github.com/laurent22/joplin/releases/tag/v1.0.239) (p) | 2020-09-01T21:56:36Z | 932 | 235 | 407 | 1,574 |
| [v1.0.237](https://github.com/laurent22/joplin/releases/tag/v1.0.237) (p) | 2020-08-29T15:38:04Z | 597 | 933 | 345 | 1,875 |
| [v1.0.236](https://github.com/laurent22/joplin/releases/tag/v1.0.236) (p) | 2020-08-28T09:16:54Z | 322 | 120 | 110 | 552 |
| [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 2,005 | 499 | 928 | 3,432 |
| [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 622 | 134 | 107 | 863 |
| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 45,875 | 18,209 | 12,367 | 76,451 |
| [v1.0.232](https://github.com/laurent22/joplin/releases/tag/v1.0.232) (p) | 2020-07-28T22:34:40Z | 661 | 231 | 186 | 1,078 |
| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 41,337 | 15,290 | 9,647 | 66,274 |
| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,929 | 2,261 | 694 | 7,884 |
| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 24,992 | 11,015 | 6,014 | 42,021 |
| [v1.0.223](https://github.com/laurent22/joplin/releases/tag/v1.0.223) (p) | 2020-06-20T11:51:27Z | 194 | 122 | 85 | 401 |
| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 862 | 216 | 218 | 1,296 |
| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,636 | 9,935 | 6,424 | 48,995 |
| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,564 | 6,983 | 3,132 | 24,679 |
| [v1.0.235](https://github.com/laurent22/joplin/releases/tag/v1.0.235) (p) | 2020-08-18T22:08:01Z | 2,006 | 499 | 928 | 3,433 |
| [v1.0.234](https://github.com/laurent22/joplin/releases/tag/v1.0.234) (p) | 2020-08-17T23:13:02Z | 624 | 134 | 107 | 865 |
| [v1.0.233](https://github.com/laurent22/joplin/releases/tag/v1.0.233) | 2020-08-01T14:51:15Z | 45,922 | 18,209 | 12,367 | 76,498 |
| [v1.0.232](https://github.com/laurent22/joplin/releases/tag/v1.0.232) (p) | 2020-07-28T22:34:40Z | 662 | 231 | 186 | 1,079 |
| [v1.0.227](https://github.com/laurent22/joplin/releases/tag/v1.0.227) | 2020-07-07T20:44:54Z | 41,345 | 15,290 | 9,647 | 66,282 |
| [v1.0.226](https://github.com/laurent22/joplin/releases/tag/v1.0.226) (p) | 2020-07-04T10:21:26Z | 4,929 | 2,262 | 694 | 7,885 |
| [v1.0.224](https://github.com/laurent22/joplin/releases/tag/v1.0.224) | 2020-06-20T22:26:08Z | 24,998 | 11,016 | 6,014 | 42,028 |
| [v1.0.223](https://github.com/laurent22/joplin/releases/tag/v1.0.223) (p) | 2020-06-20T11:51:27Z | 196 | 122 | 85 | 403 |
| [v1.0.221](https://github.com/laurent22/joplin/releases/tag/v1.0.221) (p) | 2020-06-20T01:44:20Z | 863 | 216 | 218 | 1,297 |
| [v1.0.220](https://github.com/laurent22/joplin/releases/tag/v1.0.220) | 2020-06-13T18:26:22Z | 32,648 | 9,935 | 6,424 | 49,007 |
| [v1.0.218](https://github.com/laurent22/joplin/releases/tag/v1.0.218) | 2020-06-07T10:43:34Z | 14,572 | 6,983 | 3,133 | 24,688 |
| [v1.0.217](https://github.com/laurent22/joplin/releases/tag/v1.0.217) (p) | 2020-06-06T15:17:27Z | 234 | 105 | 62 | 401 |
| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 39,639 | 14,307 | 10,191 | 64,137 |
| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 6,748 | 3,479 | 771 | 10,998 |
| [v1.0.212](https://github.com/laurent22/joplin/releases/tag/v1.0.212) (p) | 2020-05-21T07:48:39Z | 220 | 77 | 55 | 352 |
| [v1.0.216](https://github.com/laurent22/joplin/releases/tag/v1.0.216) | 2020-05-24T14:21:01Z | 39,679 | 14,307 | 10,191 | 64,177 |
| [v1.0.214](https://github.com/laurent22/joplin/releases/tag/v1.0.214) (p) | 2020-05-21T17:15:15Z | 6,754 | 3,479 | 771 | 11,004 |
| [v1.0.212](https://github.com/laurent22/joplin/releases/tag/v1.0.212) (p) | 2020-05-21T07:48:39Z | 220 | 79 | 55 | 354 |
| [v1.0.211](https://github.com/laurent22/joplin/releases/tag/v1.0.211) (p) | 2020-05-20T08:59:16Z | 309 | 142 | 94 | 545 |
| [v1.0.209](https://github.com/laurent22/joplin/releases/tag/v1.0.209) (p) | 2020-05-17T18:32:51Z | 1,400 | 861 | 155 | 2,416 |
| [v1.0.207](https://github.com/laurent22/joplin/releases/tag/v1.0.207) (p) | 2020-05-10T16:37:35Z | 1,206 | 272 | 1,024 | 2,502 |
| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,264 | 20,059 | 18,188 | 92,511 |
| [v1.0.209](https://github.com/laurent22/joplin/releases/tag/v1.0.209) (p) | 2020-05-17T18:32:51Z | 1,401 | 861 | 155 | 2,417 |
| [v1.0.207](https://github.com/laurent22/joplin/releases/tag/v1.0.207) (p) | 2020-05-10T16:37:35Z | 1,206 | 273 | 1,024 | 2,503 |
| [v1.0.201](https://github.com/laurent22/joplin/releases/tag/v1.0.201) | 2020-04-15T22:55:13Z | 54,265 | 20,060 | 18,188 | 92,513 |
| [v1.0.200](https://github.com/laurent22/joplin/releases/tag/v1.0.200) | 2020-04-12T12:17:46Z | 9,576 | 4,899 | 1,909 | 16,384 |
| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,616 | 5,896 | 3,798 | 29,310 |
| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 23,123 | 9,733 | 6,235 | 39,091 |
| [v1.0.199](https://github.com/laurent22/joplin/releases/tag/v1.0.199) | 2020-04-10T18:41:58Z | 19,620 | 5,896 | 3,798 | 29,314 |
| [v1.0.197](https://github.com/laurent22/joplin/releases/tag/v1.0.197) | 2020-03-30T17:21:22Z | 23,146 | 9,745 | 6,257 | 39,148 |
| [v1.0.195](https://github.com/laurent22/joplin/releases/tag/v1.0.195) | 2020-03-22T19:56:12Z | 19,115 | 7,957 | 4,512 | 31,584 |
| [v1.0.194](https://github.com/laurent22/joplin/releases/tag/v1.0.194) (p) | 2020-03-14T00:00:32Z | 1,292 | 1,391 | 524 | 3,207 |
| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,746 | 10,924 | 7,421 | 47,091 |
| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 488 | 130 | 95 | 713 |
| [v1.0.193](https://github.com/laurent22/joplin/releases/tag/v1.0.193) | 2020-03-08T08:58:53Z | 28,746 | 10,925 | 7,421 | 47,092 |
| [v1.0.192](https://github.com/laurent22/joplin/releases/tag/v1.0.192) (p) | 2020-03-06T23:27:52Z | 488 | 130 | 96 | 714 |
| [v1.0.190](https://github.com/laurent22/joplin/releases/tag/v1.0.190) (p) | 2020-03-06T01:22:22Z | 390 | 99 | 92 | 581 |
| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 360 | 102 | 103 | 565 |
| [v1.0.187](https://github.com/laurent22/joplin/releases/tag/v1.0.187) (p) | 2020-03-01T12:31:06Z | 931 | 243 | 279 | 1,453 |
| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,456 | 28,814 | 22,566 | 122,836 |
| [v1.0.178](https://github.com/laurent22/joplin/releases/tag/v1.0.178) | 2020-01-20T19:06:45Z | 17,606 | 5,971 | 2,598 | 26,175 |
| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 1,961 | 445 | 714 | 3,120 |
| [v1.0.189](https://github.com/laurent22/joplin/releases/tag/v1.0.189) (p) | 2020-03-04T17:27:15Z | 361 | 102 | 103 | 566 |
| [v1.0.187](https://github.com/laurent22/joplin/releases/tag/v1.0.187) (p) | 2020-03-01T12:31:06Z | 932 | 243 | 279 | 1,454 |
| [v1.0.179](https://github.com/laurent22/joplin/releases/tag/v1.0.179) | 2020-01-24T22:42:41Z | 71,457 | 28,826 | 22,567 | 122,850 |
| [v1.0.178](https://github.com/laurent22/joplin/releases/tag/v1.0.178) | 2020-01-20T19:06:45Z | 17,606 | 5,972 | 2,598 | 26,176 |
| [v1.0.177](https://github.com/laurent22/joplin/releases/tag/v1.0.177) (p) | 2019-12-30T14:40:40Z | 1,961 | 446 | 717 | 3,124 |
| [v1.0.176](https://github.com/laurent22/joplin/releases/tag/v1.0.176) (p) | 2019-12-14T10:36:44Z | 3,130 | 2,541 | 476 | 6,147 |
| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,527 | 16,968 | 16,589 | 107,084 |
| [v1.0.175](https://github.com/laurent22/joplin/releases/tag/v1.0.175) | 2019-12-08T11:48:47Z | 73,530 | 16,972 | 16,589 | 107,091 |
| [v1.0.174](https://github.com/laurent22/joplin/releases/tag/v1.0.174) | 2019-11-12T18:20:58Z | 30,596 | 11,756 | 8,234 | 50,586 |
| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,106 | 2,088 | 753 | 7,947 |
| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,687 | 8,782 | 7,689 | 44,158 |
| [v1.0.173](https://github.com/laurent22/joplin/releases/tag/v1.0.173) | 2019-11-11T08:33:35Z | 5,107 | 2,088 | 753 | 7,948 |
| [v1.0.170](https://github.com/laurent22/joplin/releases/tag/v1.0.170) | 2019-10-13T22:13:04Z | 27,688 | 8,782 | 7,689 | 44,159 |
| [v1.0.169](https://github.com/laurent22/joplin/releases/tag/v1.0.169) | 2019-09-27T18:35:13Z | 17,184 | 5,929 | 3,759 | 26,872 |
| [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,339 | 2,281 | 722 | 8,342 |
| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 16,839 | 5,712 | 3,710 | 26,261 |
| [v1.0.166](https://github.com/laurent22/joplin/releases/tag/v1.0.166) | 2019-09-09T17:35:54Z | 1,965 | 568 | 240 | 2,773 |
| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,052 | 6,988 | 5,471 | 31,511 |
| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,327 | 6,358 | 4,142 | 29,827 |
| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,686 | 7,764 | 8,110 | 46,560 |
| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,205 | 2,186 | 1,162 | 8,553 |
| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,831 | 3,555 | 1,942 | 15,328 |
| [v1.0.168](https://github.com/laurent22/joplin/releases/tag/v1.0.168) | 2019-09-25T21:21:38Z | 5,340 | 2,281 | 722 | 8,343 |
| [v1.0.167](https://github.com/laurent22/joplin/releases/tag/v1.0.167) | 2019-09-10T08:48:37Z | 16,842 | 5,712 | 3,710 | 26,264 |
| [v1.0.166](https://github.com/laurent22/joplin/releases/tag/v1.0.166) | 2019-09-09T17:35:54Z | 1,965 | 568 | 241 | 2,774 |
| [v1.0.165](https://github.com/laurent22/joplin/releases/tag/v1.0.165) | 2019-08-14T21:46:29Z | 19,052 | 6,989 | 5,471 | 31,512 |
| [v1.0.161](https://github.com/laurent22/joplin/releases/tag/v1.0.161) | 2019-07-13T18:30:00Z | 19,328 | 6,358 | 4,142 | 29,828 |
| [v1.0.160](https://github.com/laurent22/joplin/releases/tag/v1.0.160) | 2019-06-15T00:21:40Z | 30,689 | 7,764 | 8,110 | 46,563 |
| [v1.0.159](https://github.com/laurent22/joplin/releases/tag/v1.0.159) | 2019-06-08T00:00:19Z | 5,207 | 2,186 | 1,168 | 8,561 |
| [v1.0.158](https://github.com/laurent22/joplin/releases/tag/v1.0.158) | 2019-05-27T19:01:18Z | 9,833 | 3,555 | 1,943 | 15,331 |
| [v1.0.157](https://github.com/laurent22/joplin/releases/tag/v1.0.157) | 2019-05-26T17:55:53Z | 2,188 | 851 | 297 | 3,336 |
| [v1.0.153](https://github.com/laurent22/joplin/releases/tag/v1.0.153) (p) | 2019-05-15T06:27:29Z | 858 | 110 | 112 | 1,080 |
| [v1.0.152](https://github.com/laurent22/joplin/releases/tag/v1.0.152) | 2019-05-13T09:08:07Z | 13,885 | 4,442 | 4,067 | 22,394 |
| [v1.0.151](https://github.com/laurent22/joplin/releases/tag/v1.0.151) | 2019-05-12T15:14:32Z | 1,962 | 542 | 964 | 3,468 |
| [v1.0.153](https://github.com/laurent22/joplin/releases/tag/v1.0.153) (p) | 2019-05-15T06:27:29Z | 859 | 110 | 113 | 1,082 |
| [v1.0.152](https://github.com/laurent22/joplin/releases/tag/v1.0.152) | 2019-05-13T09:08:07Z | 13,886 | 4,442 | 4,067 | 22,395 |
| [v1.0.151](https://github.com/laurent22/joplin/releases/tag/v1.0.151) | 2019-05-12T15:14:32Z | 1,963 | 542 | 964 | 3,469 |
| [v1.0.150](https://github.com/laurent22/joplin/releases/tag/v1.0.150) | 2019-05-12T11:27:48Z | 433 | 144 | 77 | 654 |
| [v1.0.148](https://github.com/laurent22/joplin/releases/tag/v1.0.148) (p) | 2019-05-08T19:12:24Z | 136 | 62 | 100 | 298 |
| [v1.0.145](https://github.com/laurent22/joplin/releases/tag/v1.0.145) | 2019-05-03T09:16:53Z | 7,017 | 2,868 | 1,443 | 11,328 |
| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,926 | 3,560 | 2,788 | 18,274 |
| [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,766 | 4,574 | 4,736 | 24,076 |
| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,645 | 4,181 | 3,384 | 21,210 |
| [v1.0.139](https://github.com/laurent22/joplin/releases/tag/v1.0.139) (p) | 2019-03-09T10:06:48Z | 129 | 71 | 53 | 253 |
| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 159 | 96 | 89 | 344 |
| [v1.0.137](https://github.com/laurent22/joplin/releases/tag/v1.0.137) (p) | 2019-03-03T01:12:51Z | 597 | 64 | 89 | 750 |
| [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,636 | 3,965 | 4,084 | 20,685 |
| [v1.0.134](https://github.com/laurent22/joplin/releases/tag/v1.0.134) | 2019-02-27T10:21:44Z | 1,476 | 576 | 224 | 2,276 |
| [v1.0.132](https://github.com/laurent22/joplin/releases/tag/v1.0.132) | 2019-02-26T23:02:05Z | 1,095 | 459 | 100 | 1,654 |
| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 9,886 | 3,179 | 2,937 | 16,002 |
| [v1.0.126](https://github.com/laurent22/joplin/releases/tag/v1.0.126) (p) | 2019-02-09T19:46:16Z | 940 | 80 | 122 | 1,142 |
| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,299 | 3,565 | 1,709 | 15,573 |
| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,626 | 5,214 | 6,525 | 27,365 |
| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 8,913 | 3,270 | 2,020 | 14,203 |
| [v1.0.118](https://github.com/laurent22/joplin/releases/tag/v1.0.118) | 2019-01-11T08:34:13Z | 723 | 256 | 94 | 1,073 |
| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,270 | 4,904 | 6,387 | 27,561 |
| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 3,876 | 1,130 | 719 | 5,725 |
| [v1.0.115](https://github.com/laurent22/joplin/releases/tag/v1.0.115) | 2018-11-16T16:52:02Z | 3,663 | 1,310 | 807 | 5,780 |
| [v1.0.114](https://github.com/laurent22/joplin/releases/tag/v1.0.114) | 2018-10-24T20:14:10Z | 11,402 | 3,510 | 3,836 | 18,748 |
| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,185 | 3,361 | 3,688 | 19,234 |
| [v1.0.110](https://github.com/laurent22/joplin/releases/tag/v1.0.110) | 2018-09-29T12:29:21Z | 967 | 419 | 124 | 1,510 |
| [v1.0.109](https://github.com/laurent22/joplin/releases/tag/v1.0.109) | 2018-09-27T18:01:41Z | 2,110 | 713 | 337 | 3,160 |
| [v1.0.108](https://github.com/laurent22/joplin/releases/tag/v1.0.108) (p) | 2018-09-29T18:49:29Z | 37 | 29 | 22 | 88 |
| [v1.0.148](https://github.com/laurent22/joplin/releases/tag/v1.0.148) (p) | 2019-05-08T19:12:24Z | 137 | 64 | 100 | 301 |
| [v1.0.145](https://github.com/laurent22/joplin/releases/tag/v1.0.145) | 2019-05-03T09:16:53Z | 7,017 | 2,869 | 1,444 | 11,330 |
| [v1.0.143](https://github.com/laurent22/joplin/releases/tag/v1.0.143) | 2019-04-22T10:51:38Z | 11,928 | 3,560 | 2,788 | 18,276 |
| [v1.0.142](https://github.com/laurent22/joplin/releases/tag/v1.0.142) | 2019-04-02T16:44:51Z | 14,766 | 4,575 | 4,736 | 24,077 |
| [v1.0.140](https://github.com/laurent22/joplin/releases/tag/v1.0.140) | 2019-03-10T20:59:58Z | 13,647 | 4,181 | 3,384 | 21,212 |
| [v1.0.139](https://github.com/laurent22/joplin/releases/tag/v1.0.139) (p) | 2019-03-09T10:06:48Z | 129 | 72 | 53 | 254 |
| [v1.0.138](https://github.com/laurent22/joplin/releases/tag/v1.0.138) (p) | 2019-03-03T17:23:00Z | 160 | 97 | 89 | 346 |
| [v1.0.137](https://github.com/laurent22/joplin/releases/tag/v1.0.137) (p) | 2019-03-03T01:12:51Z | 597 | 65 | 89 | 751 |
| [v1.0.135](https://github.com/laurent22/joplin/releases/tag/v1.0.135) | 2019-02-27T23:36:57Z | 12,637 | 3,966 | 4,085 | 20,688 |
| [v1.0.134](https://github.com/laurent22/joplin/releases/tag/v1.0.134) | 2019-02-27T10:21:44Z | 1,476 | 576 | 225 | 2,277 |
| [v1.0.132](https://github.com/laurent22/joplin/releases/tag/v1.0.132) | 2019-02-26T23:02:05Z | 1,096 | 460 | 101 | 1,657 |
| [v1.0.127](https://github.com/laurent22/joplin/releases/tag/v1.0.127) | 2019-02-14T23:12:48Z | 9,888 | 3,180 | 2,937 | 16,005 |
| [v1.0.126](https://github.com/laurent22/joplin/releases/tag/v1.0.126) (p) | 2019-02-09T19:46:16Z | 941 | 80 | 122 | 1,143 |
| [v1.0.125](https://github.com/laurent22/joplin/releases/tag/v1.0.125) | 2019-01-26T18:14:33Z | 10,300 | 3,566 | 1,709 | 15,575 |
| [v1.0.120](https://github.com/laurent22/joplin/releases/tag/v1.0.120) | 2019-01-10T21:42:53Z | 15,628 | 5,215 | 6,525 | 27,368 |
| [v1.0.119](https://github.com/laurent22/joplin/releases/tag/v1.0.119) | 2018-12-18T12:40:22Z | 8,925 | 3,270 | 2,020 | 14,215 |
| [v1.0.118](https://github.com/laurent22/joplin/releases/tag/v1.0.118) | 2019-01-11T08:34:13Z | 724 | 256 | 94 | 1,074 |
| [v1.0.117](https://github.com/laurent22/joplin/releases/tag/v1.0.117) | 2018-11-24T12:05:24Z | 16,270 | 4,905 | 6,387 | 27,562 |
| [v1.0.116](https://github.com/laurent22/joplin/releases/tag/v1.0.116) | 2018-11-20T19:09:24Z | 3,890 | 1,132 | 720 | 5,742 |
| [v1.0.115](https://github.com/laurent22/joplin/releases/tag/v1.0.115) | 2018-11-16T16:52:02Z | 3,664 | 1,311 | 808 | 5,783 |
| [v1.0.114](https://github.com/laurent22/joplin/releases/tag/v1.0.114) | 2018-10-24T20:14:10Z | 11,404 | 3,510 | 3,836 | 18,750 |
| [v1.0.111](https://github.com/laurent22/joplin/releases/tag/v1.0.111) | 2018-09-30T20:15:09Z | 12,192 | 3,365 | 3,689 | 19,246 |
| [v1.0.110](https://github.com/laurent22/joplin/releases/tag/v1.0.110) | 2018-09-29T12:29:21Z | 967 | 419 | 125 | 1,511 |
| [v1.0.109](https://github.com/laurent22/joplin/releases/tag/v1.0.109) | 2018-09-27T18:01:41Z | 2,110 | 714 | 337 | 3,161 |
| [v1.0.108](https://github.com/laurent22/joplin/releases/tag/v1.0.108) (p) | 2018-09-29T18:49:29Z | 37 | 30 | 22 | 89 |
| [v1.0.107](https://github.com/laurent22/joplin/releases/tag/v1.0.107) | 2018-09-16T19:51:07Z | 7,161 | 2,145 | 1,717 | 11,023 |
| [v1.0.106](https://github.com/laurent22/joplin/releases/tag/v1.0.106) | 2018-09-08T15:23:40Z | 4,565 | 1,464 | 324 | 6,353 |
| [v1.0.105](https://github.com/laurent22/joplin/releases/tag/v1.0.105) | 2018-09-05T11:29:36Z | 4,663 | 1,598 | 1,463 | 7,724 |
| [v1.0.104](https://github.com/laurent22/joplin/releases/tag/v1.0.104) | 2018-06-28T20:25:36Z | 15,078 | 4,710 | 7,371 | 27,159 |
| [v1.0.103](https://github.com/laurent22/joplin/releases/tag/v1.0.103) | 2018-06-21T19:38:13Z | 2,060 | 896 | 685 | 3,641 |
| [v1.0.101](https://github.com/laurent22/joplin/releases/tag/v1.0.101) | 2018-06-17T18:35:11Z | 1,316 | 615 | 415 | 2,346 |
| [v1.0.100](https://github.com/laurent22/joplin/releases/tag/v1.0.100) | 2018-06-14T17:41:43Z | 893 | 440 | 252 | 1,585 |
| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,262 | 605 | 386 | 2,253 |
| [v1.0.106](https://github.com/laurent22/joplin/releases/tag/v1.0.106) | 2018-09-08T15:23:40Z | 4,565 | 1,465 | 324 | 6,354 |
| [v1.0.105](https://github.com/laurent22/joplin/releases/tag/v1.0.105) | 2018-09-05T11:29:36Z | 4,664 | 1,600 | 1,463 | 7,727 |
| [v1.0.104](https://github.com/laurent22/joplin/releases/tag/v1.0.104) | 2018-06-28T20:25:36Z | 15,079 | 4,711 | 7,372 | 27,162 |
| [v1.0.103](https://github.com/laurent22/joplin/releases/tag/v1.0.103) | 2018-06-21T19:38:13Z | 2,061 | 896 | 685 | 3,642 |
| [v1.0.101](https://github.com/laurent22/joplin/releases/tag/v1.0.101) | 2018-06-17T18:35:11Z | 1,317 | 616 | 416 | 2,349 |
| [v1.0.100](https://github.com/laurent22/joplin/releases/tag/v1.0.100) | 2018-06-14T17:41:43Z | 893 | 441 | 252 | 1,586 |
| [v1.0.99](https://github.com/laurent22/joplin/releases/tag/v1.0.99) | 2018-06-10T13:18:23Z | 1,262 | 605 | 387 | 2,254 |
| [v1.0.97](https://github.com/laurent22/joplin/releases/tag/v1.0.97) | 2018-06-09T19:23:34Z | 321 | 163 | 66 | 550 |
| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,729 | 1,232 | 1,708 | 5,669 |
| [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 426 | 226 | 130 | 782 |
| [v1.0.94](https://github.com/laurent22/joplin/releases/tag/v1.0.94) | 2018-05-21T20:52:59Z | 1,139 | 595 | 407 | 2,141 |
| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,798 | 1,257 | 768 | 3,823 |
| [v1.0.96](https://github.com/laurent22/joplin/releases/tag/v1.0.96) | 2018-05-26T16:36:39Z | 2,731 | 1,232 | 1,708 | 5,671 |
| [v1.0.95](https://github.com/laurent22/joplin/releases/tag/v1.0.95) | 2018-05-25T13:04:30Z | 427 | 226 | 130 | 783 |
| [v1.0.94](https://github.com/laurent22/joplin/releases/tag/v1.0.94) | 2018-05-21T20:52:59Z | 1,140 | 595 | 408 | 2,143 |
| [v1.0.93](https://github.com/laurent22/joplin/releases/tag/v1.0.93) | 2018-05-14T11:36:01Z | 1,798 | 1,259 | 768 | 3,825 |
| [v1.0.91](https://github.com/laurent22/joplin/releases/tag/v1.0.91) | 2018-05-10T14:48:04Z | 833 | 562 | 319 | 1,714 |
| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 501 | 243 | 122 | 866 |
| [v1.0.85](https://github.com/laurent22/joplin/releases/tag/v1.0.85) | 2018-05-01T21:08:24Z | 1,658 | 960 | 643 | 3,261 |
| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,466 | 2,540 | 2,668 | 10,674 |
| [v1.0.89](https://github.com/laurent22/joplin/releases/tag/v1.0.89) | 2018-05-09T13:05:05Z | 501 | 244 | 123 | 868 |
| [v1.0.85](https://github.com/laurent22/joplin/releases/tag/v1.0.85) | 2018-05-01T21:08:24Z | 1,658 | 961 | 643 | 3,262 |
| [v1.0.83](https://github.com/laurent22/joplin/releases/tag/v1.0.83) | 2018-04-04T19:43:58Z | 5,469 | 2,541 | 2,668 | 10,678 |
| [v1.0.82](https://github.com/laurent22/joplin/releases/tag/v1.0.82) | 2018-03-31T19:16:31Z | 710 | 415 | 132 | 1,257 |
| [v1.0.81](https://github.com/laurent22/joplin/releases/tag/v1.0.81) | 2018-03-28T08:13:58Z | 1,006 | 607 | 793 | 2,406 |
| [v1.0.79](https://github.com/laurent22/joplin/releases/tag/v1.0.79) | 2018-03-23T18:00:11Z | 935 | 547 | 394 | 1,876 |
| [v1.0.81](https://github.com/laurent22/joplin/releases/tag/v1.0.81) | 2018-03-28T08:13:58Z | 1,007 | 608 | 793 | 2,408 |
| [v1.0.79](https://github.com/laurent22/joplin/releases/tag/v1.0.79) | 2018-03-23T18:00:11Z | 935 | 548 | 394 | 1,877 |
| [v1.0.78](https://github.com/laurent22/joplin/releases/tag/v1.0.78) | 2018-03-17T15:27:18Z | 1,316 | 905 | 881 | 3,102 |
| [v1.0.77](https://github.com/laurent22/joplin/releases/tag/v1.0.77) | 2018-03-16T15:12:35Z | 182 | 111 | 55 | 348 |
| [v1.0.77](https://github.com/laurent22/joplin/releases/tag/v1.0.77) | 2018-03-16T15:12:35Z | 182 | 112 | 55 | 349 |
| [v1.0.72](https://github.com/laurent22/joplin/releases/tag/v1.0.72) | 2018-03-14T09:44:35Z | 415 | 266 | 65 | 746 |
| [v1.0.70](https://github.com/laurent22/joplin/releases/tag/v1.0.70) | 2018-02-28T20:04:30Z | 1,863 | 1,059 | 1,263 | 4,185 |
| [v1.0.67](https://github.com/laurent22/joplin/releases/tag/v1.0.67) | 2018-02-19T22:51:08Z | 1,822 | 615 | 0 | 2,437 |
| [v1.0.70](https://github.com/laurent22/joplin/releases/tag/v1.0.70) | 2018-02-28T20:04:30Z | 1,863 | 1,061 | 1,263 | 4,187 |
| [v1.0.67](https://github.com/laurent22/joplin/releases/tag/v1.0.67) | 2018-02-19T22:51:08Z | 1,823 | 615 | 0 | 2,438 |
| [v1.0.66](https://github.com/laurent22/joplin/releases/tag/v1.0.66) | 2018-02-18T23:09:09Z | 333 | 143 | 92 | 568 |
| [v1.0.65](https://github.com/laurent22/joplin/releases/tag/v1.0.65) | 2018-02-17T20:02:25Z | 198 | 137 | 139 | 474 |
| [v1.0.64](https://github.com/laurent22/joplin/releases/tag/v1.0.64) | 2018-02-16T00:58:20Z | 1,090 | 551 | 1,130 | 2,771 |
| [v1.0.65](https://github.com/laurent22/joplin/releases/tag/v1.0.65) | 2018-02-17T20:02:25Z | 199 | 137 | 139 | 475 |
| [v1.0.64](https://github.com/laurent22/joplin/releases/tag/v1.0.64) | 2018-02-16T00:58:20Z | 1,090 | 552 | 1,130 | 2,772 |
| [v1.0.63](https://github.com/laurent22/joplin/releases/tag/v1.0.63) | 2018-02-14T19:40:36Z | 309 | 168 | 98 | 575 |
| [v1.0.62](https://github.com/laurent22/joplin/releases/tag/v1.0.62) | 2018-02-12T20:19:58Z | 573 | 309 | 378 | 1,260 |
| [v0.10.61](https://github.com/laurent22/joplin/releases/tag/v0.10.61) | 2018-02-08T18:27:39Z | 977 | 644 | 969 | 2,590 |
| [v0.10.60](https://github.com/laurent22/joplin/releases/tag/v0.10.60) | 2018-02-06T13:09:56Z | 727 | 530 | 558 | 1,815 |
| [v0.10.54](https://github.com/laurent22/joplin/releases/tag/v0.10.54) | 2018-01-31T20:21:30Z | 1,826 | 1,468 | 330 | 3,624 |
| [v0.10.61](https://github.com/laurent22/joplin/releases/tag/v0.10.61) | 2018-02-08T18:27:39Z | 978 | 644 | 969 | 2,591 |
| [v0.10.60](https://github.com/laurent22/joplin/releases/tag/v0.10.60) | 2018-02-06T13:09:56Z | 728 | 530 | 558 | 1,816 |
| [v0.10.54](https://github.com/laurent22/joplin/releases/tag/v0.10.54) | 2018-01-31T20:21:30Z | 1,826 | 1,469 | 330 | 3,625 |
| [v0.10.52](https://github.com/laurent22/joplin/releases/tag/v0.10.52) | 2018-01-31T19:25:18Z | 53 | 642 | 23 | 718 |
| [v0.10.51](https://github.com/laurent22/joplin/releases/tag/v0.10.51) | 2018-01-28T18:47:02Z | 1,334 | 1,609 | 334 | 3,277 |
| [v0.10.48](https://github.com/laurent22/joplin/releases/tag/v0.10.48) | 2018-01-23T11:19:51Z | 1,969 | 1,760 | 38 | 3,767 |
| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,236 | 1,280 | 73 | 2,589 |
| [v0.10.51](https://github.com/laurent22/joplin/releases/tag/v0.10.51) | 2018-01-28T18:47:02Z | 1,335 | 1,609 | 334 | 3,278 |
| [v0.10.48](https://github.com/laurent22/joplin/releases/tag/v0.10.48) | 2018-01-23T11:19:51Z | 1,970 | 1,760 | 38 | 3,768 |
| [v0.10.47](https://github.com/laurent22/joplin/releases/tag/v0.10.47) | 2018-01-16T17:27:17Z | 1,236 | 1,281 | 73 | 2,590 |
| [v0.10.43](https://github.com/laurent22/joplin/releases/tag/v0.10.43) | 2018-01-08T10:12:10Z | 3,446 | 2,365 | 1,215 | 7,026 |
| [v0.10.41](https://github.com/laurent22/joplin/releases/tag/v0.10.41) | 2018-01-05T20:38:12Z | 1,044 | 1,558 | 249 | 2,851 |
| [v0.10.40](https://github.com/laurent22/joplin/releases/tag/v0.10.40) | 2018-01-02T23:16:57Z | 1,599 | 1,797 | 344 | 3,740 |
| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,930 | 4,399 | 3,295 | 13,624 |
| [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,055 | 1,240 | 314 | 2,609 |
| [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 272 | 858 | 91 | 1,221 |
| [v0.10.39](https://github.com/laurent22/joplin/releases/tag/v0.10.39) | 2017-12-11T21:19:44Z | 5,931 | 4,399 | 3,295 | 13,625 |
| [v0.10.38](https://github.com/laurent22/joplin/releases/tag/v0.10.38) | 2017-12-08T10:12:06Z | 1,055 | 1,242 | 314 | 2,611 |
| [v0.10.37](https://github.com/laurent22/joplin/releases/tag/v0.10.37) | 2017-12-07T19:38:05Z | 272 | 859 | 91 | 1,222 |
| [v0.10.36](https://github.com/laurent22/joplin/releases/tag/v0.10.36) | 2017-12-05T09:34:40Z | 1,023 | 1,370 | 446 | 2,839 |
| [v0.10.35](https://github.com/laurent22/joplin/releases/tag/v0.10.35) | 2017-12-02T15:56:08Z | 1,583 | 1,559 | 752 | 3,894 |
| [v0.10.34](https://github.com/laurent22/joplin/releases/tag/v0.10.34) | 2017-12-02T14:50:28Z | 97 | 682 | 68 | 847 |
| [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 68 | 674 | 33 | 775 |
| [v0.10.35](https://github.com/laurent22/joplin/releases/tag/v0.10.35) | 2017-12-02T15:56:08Z | 1,584 | 1,560 | 752 | 3,896 |
| [v0.10.34](https://github.com/laurent22/joplin/releases/tag/v0.10.34) | 2017-12-02T14:50:28Z | 98 | 682 | 68 | 848 |
| [v0.10.33](https://github.com/laurent22/joplin/releases/tag/v0.10.33) | 2017-12-02T13:20:39Z | 68 | 675 | 33 | 776 |
| [v0.10.31](https://github.com/laurent22/joplin/releases/tag/v0.10.31) | 2017-12-01T09:56:44Z | 899 | 1,466 | 416 | 2,781 |
| [v0.10.30](https://github.com/laurent22/joplin/releases/tag/v0.10.30) | 2017-11-30T20:28:16Z | 731 | 1,385 | 434 | 2,550 |
| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,365 | 1,718 | 888 | 3,971 |
| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 199 | 717 | 273 | 1,189 |
| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 157 | 711 | 6,656 | 7,524 |
| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 144 | 678 | 47 | 869 |
| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 95 | 663 | 33 | 791 |
| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 62 | 656 | 28 | 746 |
| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 51 | 666 | 37 | 754 |
| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 40 | 667 | 33 | 740 |
| [v0.10.28](https://github.com/laurent22/joplin/releases/tag/v0.10.28) | 2017-11-30T01:07:46Z | 1,365 | 1,721 | 888 | 3,974 |
| [v0.10.26](https://github.com/laurent22/joplin/releases/tag/v0.10.26) | 2017-11-29T16:02:17Z | 200 | 718 | 273 | 1,191 |
| [v0.10.25](https://github.com/laurent22/joplin/releases/tag/v0.10.25) | 2017-11-24T14:27:49Z | 157 | 713 | 6,657 | 7,527 |
| [v0.10.23](https://github.com/laurent22/joplin/releases/tag/v0.10.23) | 2017-11-21T19:38:41Z | 145 | 678 | 47 | 870 |
| [v0.10.22](https://github.com/laurent22/joplin/releases/tag/v0.10.22) | 2017-11-20T21:45:57Z | 95 | 664 | 33 | 792 |
| [v0.10.21](https://github.com/laurent22/joplin/releases/tag/v0.10.21) | 2017-11-18T00:53:15Z | 62 | 657 | 28 | 747 |
| [v0.10.20](https://github.com/laurent22/joplin/releases/tag/v0.10.20) | 2017-11-17T17:18:25Z | 51 | 667 | 37 | 755 |
| [v0.10.19](https://github.com/laurent22/joplin/releases/tag/v0.10.19) | 2017-11-20T18:59:48Z | 40 | 668 | 34 | 742 |

Some files were not shown because too many files have changed in this diff Show More