You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
4 Commits
server-v2.
...
webpack_el
Author | SHA1 | Date | |
---|---|---|---|
|
4f5e52c363 | ||
|
4e7087b350 | ||
|
59e7c4933d | ||
|
621f83d3ce |
@@ -259,6 +259,7 @@ packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
packages/app-desktop/gui/NoteList/NoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/itemAnchorRef.js
|
||||
packages/app-desktop/gui/NoteList/types.js
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
@@ -311,6 +312,7 @@ packages/app-desktop/gui/TagItem.js
|
||||
packages/app-desktop/gui/TagList.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/styles/index.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/types.js
|
||||
packages/app-desktop/gui/ToolbarBase.js
|
||||
packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
@@ -483,7 +485,6 @@ packages/lib/SyncTargetOneDrive.js
|
||||
packages/lib/SyncTargetRegistry.js
|
||||
packages/lib/Synchronizer.js
|
||||
packages/lib/TaskQueue.js
|
||||
packages/lib/WelcomeUtils.js
|
||||
packages/lib/array.js
|
||||
packages/lib/callbackUrlUtils.js
|
||||
packages/lib/callbackUrlUtils.test.js
|
||||
@@ -519,7 +520,6 @@ packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.test.js
|
||||
packages/lib/import-enex.js
|
||||
packages/lib/locale.js
|
||||
packages/lib/locale.test.js
|
||||
packages/lib/markdownUtils.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils2.test.js
|
||||
@@ -551,6 +551,7 @@ packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/settings/types.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
@@ -685,8 +686,6 @@ packages/lib/services/plugins/utils/validatePluginVersion.test.js
|
||||
packages/lib/services/profileConfig/index.js
|
||||
packages/lib/services/profileConfig/index.test.js
|
||||
packages/lib/services/profileConfig/initProfile.js
|
||||
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js
|
||||
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js
|
||||
packages/lib/services/profileConfig/types.js
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.test.js
|
||||
@@ -813,6 +812,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
|
||||
@@ -846,7 +846,6 @@ packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.js
|
||||
packages/tools/build-welcome.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
|
24
.github/scripts/run_ci.sh
vendored
24
.github/scripts/run_ci.sh
vendored
@@ -183,32 +183,12 @@ if [[ $GIT_TAG_NAME = v* ]]; then
|
||||
# cd "$ROOT_DIR/packages/tools"
|
||||
# node bundleDefaultPlugins.js
|
||||
cd "$ROOT_DIR/packages/app-desktop"
|
||||
|
||||
if [ "$IS_MACOS" == "1" ]; then
|
||||
# This is to fix this error:
|
||||
#
|
||||
# Exit code: ENOENT. spawn /usr/bin/python ENOENT
|
||||
#
|
||||
# Ref: https://github.com/electron-userland/electron-builder/issues/6767#issuecomment-1096589528
|
||||
#
|
||||
# It can be removed once we upgrade to electron-builder@23, however we
|
||||
# cannot currently do this due to this error:
|
||||
# https://github.com/laurent22/joplin/issues/8149
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist
|
||||
else
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
fi
|
||||
USE_HARD_LINKS=false yarn run dist
|
||||
elif [[ $IS_LINUX = 1 ]] && [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
|
||||
echo "Step: Building Docker Image..."
|
||||
cd "$ROOT_DIR"
|
||||
yarn run buildServerDocker --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
|
||||
else
|
||||
echo "Step: Building but *not* publishing desktop application..."
|
||||
|
||||
if [ "$IS_MACOS" == "1" ]; then
|
||||
# See above why we need to specify Python
|
||||
PYTHON_PATH=$(which python) USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
else
|
||||
USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
fi
|
||||
USE_HARD_LINKS=false yarn run dist --publish=never
|
||||
fi
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@@ -245,6 +245,7 @@ packages/app-desktop/gui/NoteEditor/utils/useWindowCommandHandler.js
|
||||
packages/app-desktop/gui/NoteList/NoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/focusElementNoteList.js
|
||||
packages/app-desktop/gui/NoteList/commands/index.js
|
||||
packages/app-desktop/gui/NoteList/itemAnchorRef.js
|
||||
packages/app-desktop/gui/NoteList/types.js
|
||||
packages/app-desktop/gui/NoteListControls/NoteListControls.js
|
||||
packages/app-desktop/gui/NoteListControls/commands/focusSearch.js
|
||||
@@ -297,6 +298,7 @@ packages/app-desktop/gui/TagItem.js
|
||||
packages/app-desktop/gui/TagList.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/ToggleEditorsButton.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/styles/index.js
|
||||
packages/app-desktop/gui/ToggleEditorsButton/types.js
|
||||
packages/app-desktop/gui/ToolbarBase.js
|
||||
packages/app-desktop/gui/ToolbarButton/ToolbarButton.js
|
||||
packages/app-desktop/gui/ToolbarButton/styles/index.js
|
||||
@@ -469,7 +471,6 @@ packages/lib/SyncTargetOneDrive.js
|
||||
packages/lib/SyncTargetRegistry.js
|
||||
packages/lib/Synchronizer.js
|
||||
packages/lib/TaskQueue.js
|
||||
packages/lib/WelcomeUtils.js
|
||||
packages/lib/array.js
|
||||
packages/lib/callbackUrlUtils.js
|
||||
packages/lib/callbackUrlUtils.test.js
|
||||
@@ -505,7 +506,6 @@ packages/lib/import-enex-md-gen.js
|
||||
packages/lib/import-enex-md-gen.test.js
|
||||
packages/lib/import-enex.js
|
||||
packages/lib/locale.js
|
||||
packages/lib/locale.test.js
|
||||
packages/lib/markdownUtils.js
|
||||
packages/lib/markdownUtils.test.js
|
||||
packages/lib/markdownUtils2.test.js
|
||||
@@ -537,6 +537,7 @@ packages/lib/models/SmartFilter.js
|
||||
packages/lib/models/Tag.js
|
||||
packages/lib/models/dateTimeFormats.test.js
|
||||
packages/lib/models/settings/FileHandler.js
|
||||
packages/lib/models/settings/types.js
|
||||
packages/lib/models/utils/itemCanBeEncrypted.js
|
||||
packages/lib/models/utils/paginatedFeed.js
|
||||
packages/lib/models/utils/paginationToSql.js
|
||||
@@ -671,8 +672,6 @@ packages/lib/services/plugins/utils/validatePluginVersion.test.js
|
||||
packages/lib/services/profileConfig/index.js
|
||||
packages/lib/services/profileConfig/index.test.js
|
||||
packages/lib/services/profileConfig/initProfile.js
|
||||
packages/lib/services/profileConfig/mergeGlobalAndLocalSettings.js
|
||||
packages/lib/services/profileConfig/splitGlobalAndLocalSettings.js
|
||||
packages/lib/services/profileConfig/types.js
|
||||
packages/lib/services/rest/Api.js
|
||||
packages/lib/services/rest/Api.test.js
|
||||
@@ -799,6 +798,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
|
||||
@@ -832,7 +832,6 @@ packages/renderer/noteStyle.js
|
||||
packages/renderer/pathUtils.js
|
||||
packages/renderer/utils.js
|
||||
packages/tools/build-release-stats.js
|
||||
packages/tools/build-welcome.js
|
||||
packages/tools/buildServerDocker.js
|
||||
packages/tools/buildServerDocker.test.js
|
||||
packages/tools/bundleDefaultPlugins.js
|
||||
|
@@ -15,6 +15,7 @@
|
||||
"@joplin/tools",
|
||||
"@joplin/react-native-saf-x",
|
||||
"@joplin/react-native-alarm-notification",
|
||||
"@joplin/react-native-vosk",
|
||||
"@joplin/utils"
|
||||
]
|
||||
}
|
||||
|
@@ -9,9 +9,7 @@ function getOs() {
|
||||
function getFilename(path) {
|
||||
if (!path) return '';
|
||||
const s = path.split('/');
|
||||
const urlWithParams = s.pop();
|
||||
const s2 = urlWithParams.split('?');
|
||||
return s2[0];
|
||||
return s.pop();
|
||||
}
|
||||
|
||||
function getMobileOs() {
|
||||
|
@@ -134,16 +134,10 @@ else
|
||||
print "The latest version is ${RELEASE_VERSION}, but you have ${CURRENT_VERSION:-no version} installed."
|
||||
fi
|
||||
|
||||
# Check if it's an update or a new install
|
||||
DOWNLOAD_TYPE="New"
|
||||
if [[ -f ~/.joplin/Joplin.AppImage ]]; then
|
||||
DOWNLOAD_TYPE="Update"
|
||||
fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
print 'Downloading Joplin...'
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
wget -O "${TEMP_DIR}/Joplin.AppImage" "https://objects.joplinusercontent.com/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage?source=LinuxInstallScript&type=$DOWNLOAD_TYPE"
|
||||
wget -O "${TEMP_DIR}/Joplin.AppImage" "https://github.com/laurent22/joplin/releases/download/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage"
|
||||
wget -O "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png
|
||||
|
||||
#-----------------------------------------------------
|
||||
|
10
README.md
10
README.md
@@ -22,11 +22,11 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
|
||||
|
||||
Operating System | Download
|
||||
---|---
|
||||
Windows (32 and 64-bit) | <a href='https://objects.joplinusercontent.com/v2.10.18/Joplin-Setup-2.10.18.exe?source=JoplinWebsite&type=New'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
|
||||
macOS | <a href='https://objects.joplinusercontent.com/v2.10.18/Joplin-2.10.18.dmg?source=JoplinWebsite&type=New'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
|
||||
Linux | <a href='https://objects.joplinusercontent.com/v2.10.18/Joplin-2.10.18.AppImage?source=JoplinWebsite&type=New'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
|
||||
Windows (32 and 64-bit) | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-Setup-2.9.17.exe'><img alt='Get it on Windows' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeWindows.png'/></a>
|
||||
macOS | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-2.9.17.dmg'><img alt='Get it on macOS' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeMacOS.png'/></a>
|
||||
Linux | <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/Joplin-2.9.17.AppImage'><img alt='Get it on Linux' width="134px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeLinux.png'/></a>
|
||||
|
||||
**On Windows**, you may also use the <a href='https://objects.joplinusercontent.com/v2.10.18/JoplinPortable.exe?source=JoplinWebsite&type=New'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
|
||||
**On Windows**, you may also use the <a href='https://github.com/laurent22/joplin/releases/download/v2.9.17/JoplinPortable.exe'>Portable version</a>. The [portable application](https://en.wikipedia.org/wiki/Portable_application) allows installing the software on a portable device such as a USB key. Simply copy the file JoplinPortable.exe in any directory on that USB key ; the application will then create a directory called "JoplinProfile" next to the executable file.
|
||||
|
||||
**On Linux**, the recommended way is to use the following installation script as it will handle the desktop icon too:
|
||||
|
||||
@@ -36,7 +36,7 @@ Linux | <a href='https://objects.joplinusercontent.com/v2.10.18/Joplin-2.10.18.A
|
||||
|
||||
Operating System | Download | Alt. Download
|
||||
---|---|---
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8.apk?source=JoplinWebsite&type=New) [32-bit](https://objects.joplinusercontent.com/android-v2.9.8/joplin-v2.9.8-32bit.apk?source=JoplinWebsite&type=New)
|
||||
Android | <a href='https://play.google.com/store/apps/details?id=net.cozic.joplin&utm_source=GitHub&utm_campaign=README&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeAndroid.png'/></a> | or download the APK file: [64-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8.apk) [32-bit](https://github.com/laurent22/joplin-android/releases/download/android-v2.9.8/joplin-v2.9.8-32bit.apk)
|
||||
iOS | <a href='https://itunes.apple.com/us/app/joplin/id1315599797'><img alt='Get it on the App Store' height="40px" src='https://raw.githubusercontent.com/laurent22/joplin/dev/Assets/WebsiteAssets/images/BadgeIOS.png'/></a> | -
|
||||
|
||||
## Terminal application
|
||||
|
@@ -107,10 +107,7 @@
|
||||
".eslintignore": true,
|
||||
".gitignore": true,
|
||||
".vscode/*": true,
|
||||
".yarn/cache": true,
|
||||
".yarn/install-state.gz": true,
|
||||
".yarn/plugins": true,
|
||||
".yarn/releases": true,
|
||||
".yarn": true,
|
||||
"*.sublime-workspace": true,
|
||||
"**/_mydocs": true,
|
||||
"**/_mydocs/EnexSamples/*.enex": true,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import Setting, { SettingStorage } from '@joplin/lib/models/Setting';
|
||||
import { schemaUrl } from '@joplin/lib/models/settings/types';
|
||||
import { SettingItemType } from '@joplin/lib/services/plugins/api/types';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
@@ -38,7 +39,7 @@ class Command extends BaseCommand {
|
||||
public async action(args: any) {
|
||||
const schema: Record<string, any> = {
|
||||
title: 'JSON schema for Joplin setting files',
|
||||
'$id': Setting.schemaUrl,
|
||||
'$id': schemaUrl,
|
||||
'$schema': 'https://json-schema.org/draft-07/schema#',
|
||||
type: 'object',
|
||||
properties: {},
|
||||
|
@@ -1 +0,0 @@
|
||||
<div class="jop-noMdConv"><svg class="jop-noMdConv"><style class="jop-noMdConv"></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>
|
@@ -1 +0,0 @@
|
||||
<div><svg><style></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>
|
@@ -1 +0,0 @@
|
||||
<a href="#" class="jop-noMdConv">XSS</a>
|
@@ -1 +0,0 @@
|
||||
<a data-from-md="" href="javascript:top.require('child_process').execSync('open -a Calculator')">XSS</a>
|
2
packages/app-desktop/.gitignore
vendored
2
packages/app-desktop/.gitignore
vendored
@@ -14,3 +14,5 @@ style.min.css
|
||||
build/lib/
|
||||
vendor/*
|
||||
!vendor/loadEmojiLib.js
|
||||
main-html.bundle.js
|
||||
main.js
|
@@ -68,10 +68,6 @@ export class Bridge {
|
||||
return process.argv;
|
||||
}
|
||||
|
||||
public getLocale = () => {
|
||||
return this.electronApp().electronApp().getLocale();
|
||||
};
|
||||
|
||||
// Applies to electron-context-menu@3:
|
||||
//
|
||||
// For now we have to disable spell checking in non-editor text
|
||||
@@ -88,35 +84,35 @@ export class Bridge {
|
||||
// Perhaps the easiest would be to patch electron-context-menu to
|
||||
// support the renderer process again. Or possibly revert to an old
|
||||
// version of electron-context-menu.
|
||||
public setupContextMenu(_spellCheckerMenuItemsHandler: Function) {
|
||||
require('electron-context-menu')({
|
||||
allWindows: [this.window()],
|
||||
// public setupContextMenu(_spellCheckerMenuItemsHandler: Function) {
|
||||
// require('electron-context-menu')({
|
||||
// allWindows: [this.window()],
|
||||
|
||||
electronApp: this.electronApp(),
|
||||
// electronApp: this.electronApp(),
|
||||
|
||||
shouldShowMenu: (_event: any, params: any) => {
|
||||
// params.inputFieldType === 'none' when right-clicking the text
|
||||
// editor. This is a bit of a hack to detect it because in this
|
||||
// case we don't want to use the built-in context menu but a
|
||||
// custom one.
|
||||
return params.isEditable && params.inputFieldType !== 'none';
|
||||
},
|
||||
// shouldShowMenu: (_event: any, params: any) => {
|
||||
// // params.inputFieldType === 'none' when right-clicking the text
|
||||
// // editor. This is a bit of a hack to detect it because in this
|
||||
// // case we don't want to use the built-in context menu but a
|
||||
// // custom one.
|
||||
// return params.isEditable && params.inputFieldType !== 'none';
|
||||
// },
|
||||
|
||||
// menu: (actions: any, props: any) => {
|
||||
// const items = spellCheckerMenuItemsHandler(props.misspelledWord, props.dictionarySuggestions);
|
||||
// const spellCheckerMenuItems = items.map((item: any) => new MenuItem(item)); //SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions).map((item: any) => new MenuItem(item));
|
||||
// // menu: (actions: any, props: any) => {
|
||||
// // const items = spellCheckerMenuItemsHandler(props.misspelledWord, props.dictionarySuggestions);
|
||||
// // const spellCheckerMenuItems = items.map((item: any) => new MenuItem(item)); //SpellCheckerService.instance().contextMenuItems(props.misspelledWord, props.dictionarySuggestions).map((item: any) => new MenuItem(item));
|
||||
|
||||
// const output = [
|
||||
// actions.cut(),
|
||||
// actions.copy(),
|
||||
// actions.paste(),
|
||||
// ...spellCheckerMenuItems,
|
||||
// ];
|
||||
// // const output = [
|
||||
// // actions.cut(),
|
||||
// // actions.copy(),
|
||||
// // actions.paste(),
|
||||
// // ...spellCheckerMenuItems,
|
||||
// // ];
|
||||
|
||||
// return output;
|
||||
// },
|
||||
});
|
||||
}
|
||||
// // return output;
|
||||
// // },
|
||||
// });
|
||||
// }
|
||||
|
||||
public window() {
|
||||
return this.electronWrapper_.window();
|
||||
|
@@ -97,8 +97,7 @@ async function fetchLatestRelease(options: CheckForUpdateOptions) {
|
||||
}
|
||||
|
||||
if (found) {
|
||||
downloadUrl = asset.browser_download_url.replace('github.com/laurent22/joplin/releases/download', 'objects.joplinusercontent.com');
|
||||
downloadUrl.concat('?source=DesktopApp&type=Update');
|
||||
downloadUrl = asset.browser_download_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -32,52 +32,84 @@ import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
// import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
// import { reg } from '@joplin/lib/registry';
|
||||
|
||||
// Based on http://pypl.github.io/PYPL.html
|
||||
const topLanguages = [
|
||||
'python',
|
||||
'clike',
|
||||
'javascript',
|
||||
'jsx',
|
||||
'php',
|
||||
'r',
|
||||
'swift',
|
||||
'go',
|
||||
'vb',
|
||||
'vbscript',
|
||||
'ruby',
|
||||
'rust',
|
||||
'dart',
|
||||
'lua',
|
||||
'groovy',
|
||||
'perl',
|
||||
'cobol',
|
||||
'julia',
|
||||
'haskell',
|
||||
'pascal',
|
||||
'css',
|
||||
require('codemirror/mode/python/python');
|
||||
require('codemirror/mode/clike/clike');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
require('codemirror/mode/jsx/jsx');
|
||||
require('codemirror/mode/php/php');
|
||||
require('codemirror/mode/r/r');
|
||||
require('codemirror/mode/swift/swift');
|
||||
require('codemirror/mode/go/go');
|
||||
require('codemirror/mode/vb/vb');
|
||||
require('codemirror/mode/vbscript/vbscript');
|
||||
require('codemirror/mode/ruby/ruby');
|
||||
require('codemirror/mode/rust/rust');
|
||||
require('codemirror/mode/dart/dart');
|
||||
require('codemirror/mode/lua/lua');
|
||||
require('codemirror/mode/groovy/groovy');
|
||||
require('codemirror/mode/perl/perl');
|
||||
require('codemirror/mode/cobol/cobol');
|
||||
require('codemirror/mode/julia/julia');
|
||||
require('codemirror/mode/haskell/haskell');
|
||||
require('codemirror/mode/pascal/pascal');
|
||||
require('codemirror/mode/css/css');
|
||||
|
||||
// Additional languages, not in the PYPL list
|
||||
'xml', // For HTML too
|
||||
'markdown',
|
||||
'yaml',
|
||||
'shell',
|
||||
'dockerfile',
|
||||
'diff',
|
||||
'erlang',
|
||||
'sql',
|
||||
];
|
||||
// Load Top Modes
|
||||
for (let i = 0; i < topLanguages.length; i++) {
|
||||
const mode = topLanguages[i];
|
||||
// Additional languages, not in the PYPL list
|
||||
require('codemirror/mode/xml/xml'); // For HTML too
|
||||
require('codemirror/mode/markdown/markdown');
|
||||
require('codemirror/mode/yaml/yaml');
|
||||
require('codemirror/mode/shell/shell');
|
||||
require('codemirror/mode/dockerfile/dockerfile');
|
||||
require('codemirror/mode/diff/diff');
|
||||
require('codemirror/mode/erlang/erlang');
|
||||
require('codemirror/mode/sql/sql');
|
||||
|
||||
if (CodeMirror.modeInfo.find((m: any) => m.mode === mode)) {
|
||||
require(`codemirror/mode/${mode}/${mode}`);
|
||||
} else {
|
||||
reg.logger().error('Cannot find CodeMirror mode: ', mode);
|
||||
}
|
||||
}
|
||||
// // Based on http://pypl.github.io/PYPL.html
|
||||
// const topLanguages = [
|
||||
// 'python',
|
||||
// 'clike',
|
||||
// 'javascript',
|
||||
// 'jsx',
|
||||
// 'php',
|
||||
// 'r',
|
||||
// 'swift',
|
||||
// 'go',
|
||||
// 'vb',
|
||||
// 'vbscript',
|
||||
// 'ruby',
|
||||
// 'rust',
|
||||
// 'dart',
|
||||
// 'lua',
|
||||
// 'groovy',
|
||||
// 'perl',
|
||||
// 'cobol',
|
||||
// 'julia',
|
||||
// 'haskell',
|
||||
// 'pascal',
|
||||
// 'css',
|
||||
|
||||
// // Additional languages, not in the PYPL list
|
||||
// 'xml', // For HTML too
|
||||
// 'markdown',
|
||||
// 'yaml',
|
||||
// 'shell',
|
||||
// 'dockerfile',
|
||||
// 'diff',
|
||||
// 'erlang',
|
||||
// 'sql',
|
||||
// ];
|
||||
// // Load Top Modes
|
||||
// for (let i = 0; i < topLanguages.length; i++) {
|
||||
// const mode = topLanguages[i];
|
||||
|
||||
// if (CodeMirror.modeInfo.find((m: any) => m.mode === mode)) {
|
||||
// require(`codemirror/mode/${mode}/${mode}`);
|
||||
// } else {
|
||||
// reg.logger().error('Cannot find CodeMirror mode: ', mode);
|
||||
// }
|
||||
// }
|
||||
|
||||
export interface EditorProps {
|
||||
value: string;
|
||||
|
@@ -6,7 +6,8 @@ import useScroll from './utils/useScroll';
|
||||
import styles_ from './styles';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
import ToggleEditorsButton, { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/ToggleEditorsButton';
|
||||
import ToggleEditorsButton from '../../../ToggleEditorsButton/ToggleEditorsButton';
|
||||
import { Value as ToggleEditorsButtonValue } from '../../../ToggleEditorsButton/types';
|
||||
import ToolbarButton from '../../../../gui/ToolbarButton/ToolbarButton';
|
||||
import usePluginServiceRegistration from '../../utils/usePluginServiceRegistration';
|
||||
import { utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
|
@@ -19,6 +19,7 @@ import Note from '@joplin/lib/models/Note';
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import { Props } from './types';
|
||||
import usePrevious from '../hooks/usePrevious';
|
||||
import itemAnchorRef, { itemAnchorRefs_ } from './itemAnchorRef';
|
||||
|
||||
const commands = [
|
||||
require('./commands/focusElementNoteList'),
|
||||
@@ -31,15 +32,6 @@ const StyledRoot = styled.div`
|
||||
border-right: 1px solid ${(props: any) => props.theme.dividerColor};
|
||||
`;
|
||||
|
||||
const itemAnchorRefs_: any = {
|
||||
current: {},
|
||||
};
|
||||
|
||||
export const itemAnchorRef = (itemId: string) => {
|
||||
if (itemAnchorRefs_.current[itemId] && itemAnchorRefs_.current[itemId].current) return itemAnchorRefs_.current[itemId].current;
|
||||
return null;
|
||||
};
|
||||
|
||||
const NoteListComponent = (props: Props) => {
|
||||
const [dragOverTargetNoteIndex, setDragOverTargetNoteIndex] = useState(null);
|
||||
const [width, setWidth] = useState(0);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { stateUtils } from '@joplin/lib/reducer';
|
||||
import { itemAnchorRef } from '../NoteList';
|
||||
import itemAnchorRef from '../itemAnchorRef';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'focusElementNoteList',
|
||||
|
8
packages/app-desktop/gui/NoteList/itemAnchorRef.ts
Normal file
8
packages/app-desktop/gui/NoteList/itemAnchorRef.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export const itemAnchorRefs_: any = {
|
||||
current: {},
|
||||
};
|
||||
|
||||
export default (itemId: string) => {
|
||||
if (itemAnchorRefs_.current[itemId] && itemAnchorRefs_.current[itemId].current) return itemAnchorRefs_.current[itemId].current;
|
||||
return null;
|
||||
};
|
@@ -26,15 +26,11 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
private focusInput_: boolean;
|
||||
private styles_: any;
|
||||
private styleKey_: string;
|
||||
private menuIsOpened_: boolean = false;
|
||||
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.answerInput_ = React.createRef();
|
||||
|
||||
this.select_menuOpen = this.select_menuOpen.bind(this);
|
||||
this.select_menuClose = this.select_menuClose.bind(this);
|
||||
}
|
||||
|
||||
public UNSAFE_componentWillMount() {
|
||||
@@ -43,7 +39,6 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
answer: this.props.defaultValue ? this.props.defaultValue : '',
|
||||
});
|
||||
this.focusInput_ = true;
|
||||
this.menuIsOpened_ = false;
|
||||
}
|
||||
|
||||
public UNSAFE_componentWillReceiveProps(newProps: Props) {
|
||||
@@ -57,14 +52,6 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
}
|
||||
}
|
||||
|
||||
private select_menuOpen() {
|
||||
this.menuIsOpened_ = true;
|
||||
}
|
||||
|
||||
private select_menuClose() {
|
||||
this.menuIsOpened_ = false;
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.focusInput_ && this.answerInput_.current) this.answerInput_.current.focus();
|
||||
this.focusInput_ = false;
|
||||
@@ -237,14 +224,16 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
|
||||
const onKeyDown = (event: any) => {
|
||||
if (event.key === 'Enter') {
|
||||
// If the dropdown is open, we don't close the dialog - instead
|
||||
// the currently item will be selcted. If it is closed however
|
||||
// we confirm the dialog.
|
||||
if ((this.props.inputType === 'tags' || this.props.inputType === 'dropdown') && this.menuIsOpened_) {
|
||||
if (this.props.inputType === 'tags' || this.props.inputType === 'dropdown') {
|
||||
// Do nothing
|
||||
} else {
|
||||
onClose(true);
|
||||
}
|
||||
|
||||
// } else if (this.answerInput_.current && !this.answerInput_.current.state.menuIsOpen) {
|
||||
// // The menu will be open if the user is selecting a new item
|
||||
// onClose(true);
|
||||
// }
|
||||
} else if (event.key === 'Escape') {
|
||||
onClose(false);
|
||||
}
|
||||
@@ -257,9 +246,9 @@ export default class PromptDialog extends React.Component<Props, any> {
|
||||
if (this.props.inputType === 'datetime') {
|
||||
inputComp = <Datetime className="datetime-picker" value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={(momentObject: any) => onDateTimeChange(momentObject)} />;
|
||||
} else if (this.props.inputType === 'tags') {
|
||||
inputComp = <CreatableSelect className="tag-selector" onMenuOpen={this.select_menuOpen} onMenuClose={this.select_menuClose} styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
|
||||
inputComp = <CreatableSelect className="tag-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
|
||||
} else if (this.props.inputType === 'dropdown') {
|
||||
inputComp = <Select className="item-selector" onMenuOpen={this.select_menuOpen} onMenuClose={this.select_menuClose} styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
|
||||
inputComp = <Select className="item-selector" styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={(event: any) => onKeyDown(event)} />;
|
||||
} else {
|
||||
inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
|
||||
import ImportScreen from './ImportScreen';
|
||||
const { ResourceScreen } = require('./ResourceScreen.js');
|
||||
import Navigator from './Navigator';
|
||||
import WelcomeUtils from '@joplin/lib/WelcomeUtils';
|
||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||
const { ThemeProvider, StyleSheetManager, createGlobalStyle } = require('styled-components');
|
||||
const bridge = require('@electron/remote').require('./bridge').default;
|
||||
|
||||
@@ -141,7 +141,7 @@ class RootComponent extends React.Component<Props, any> {
|
||||
});
|
||||
}
|
||||
|
||||
await WelcomeUtils.install(Setting.value('locale'), this.props.dispatch);
|
||||
await WelcomeUtils.install(this.props.dispatch);
|
||||
}
|
||||
|
||||
private renderModalMessage(props: ModalDialogProps) {
|
||||
|
@@ -37,5 +37,5 @@ export default function(props: Props): any {
|
||||
};
|
||||
}, [styleSheetContent]);
|
||||
|
||||
return <div style={{ display: 'none' }}></div>;
|
||||
return null; // <div style={{ display: 'none' }}></div>;
|
||||
}
|
||||
|
@@ -1,17 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import styles_ from './styles';
|
||||
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
|
||||
export enum Value {
|
||||
Markdown = 'markdown',
|
||||
RichText = 'richText',
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
themeId: number;
|
||||
value: Value;
|
||||
toolbarButtonInfo: ToolbarButtonInfo;
|
||||
}
|
||||
import { Props } from './types';
|
||||
|
||||
export default function ToggleEditorsButton(props: Props) {
|
||||
const style = styles_(props);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Props, Value } from '../ToggleEditorsButton';
|
||||
import { Props, Value } from '../types';
|
||||
const { buildStyle } = require('@joplin/lib/theme');
|
||||
|
||||
export default function styles(props: Props) {
|
||||
|
12
packages/app-desktop/gui/ToggleEditorsButton/types.ts
Normal file
12
packages/app-desktop/gui/ToggleEditorsButton/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ToolbarButtonInfo } from '@joplin/lib/services/commands/ToolbarButtonUtils';
|
||||
|
||||
export enum Value {
|
||||
Markdown = 'markdown',
|
||||
RichText = 'richText',
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
themeId: number;
|
||||
value: Value;
|
||||
toolbarButtonInfo: ToolbarButtonInfo;
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import ToolbarButton from './ToolbarButton/ToolbarButton';
|
||||
import ToggleEditorsButton, { Value } from './ToggleEditorsButton/ToggleEditorsButton';
|
||||
import ToggleEditorsButton from './ToggleEditorsButton/ToggleEditorsButton';
|
||||
import { Value } from './ToggleEditorsButton/types';
|
||||
import ToolbarSpace from './ToolbarSpace';
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
@@ -42,7 +42,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-root"></div>
|
||||
<script src="main-html.js"></script>
|
||||
<script src="main-html.bundle.js"></script>
|
||||
<style>
|
||||
/* Disable dragging of links (which are often buttons) */
|
||||
a:not([draggable=true]), img:not([draggable=true]) {
|
||||
|
@@ -3,12 +3,15 @@
|
||||
// Disable React message in console "Download the React DevTools for a better development experience"
|
||||
// https://stackoverflow.com/questions/42196819/disable-hide-download-the-react-devtools#42196820
|
||||
// eslint-disable-next-line no-undef
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
supportsFiber: true,
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
onCommitFiberUnmount: function() {},
|
||||
};
|
||||
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined') {
|
||||
// eslint-disable-next-line no-undef
|
||||
__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
||||
supportsFiber: true,
|
||||
inject: function() {},
|
||||
onCommitFiberRoot: function() {},
|
||||
onCommitFiberUnmount: function() {},
|
||||
};
|
||||
}
|
||||
|
||||
const app = require('./app').default;
|
||||
const Folder = require('@joplin/lib/models/Folder').default;
|
||||
|
@@ -1,18 +1,20 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.11.2",
|
||||
"version": "2.11.1",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dist": "yarn run electronRebuild && npx electron-builder",
|
||||
"pack-html": "rollup --config rollup-main-html.config.js",
|
||||
"pack-main": "rollup --config rollup-main.config.js",
|
||||
"build": "gulp build",
|
||||
"postinstall": "yarn run build",
|
||||
"electronBuilder": "gulp electronBuilder",
|
||||
"electronRebuild": "gulp electronRebuild",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
|
||||
"start": "gulp build && electron . --env dev --log-level debug --open-dev-tools",
|
||||
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
|
||||
"test": "jest",
|
||||
"test-ci": "yarn test"
|
||||
},
|
||||
@@ -36,6 +38,9 @@
|
||||
"build/images/**",
|
||||
"build/defaultPlugins/**"
|
||||
],
|
||||
"files": [
|
||||
"!node_modules/**/*"
|
||||
],
|
||||
"afterAllArtifactBuild": "./generateSha512.js",
|
||||
"asar": true,
|
||||
"asarUnpack": "./node_modules/node-notifier/vendor/**",
|
||||
@@ -109,6 +114,9 @@
|
||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.11",
|
||||
"@rollup/plugin-commonjs": "24.1.0",
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.0.2",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.2.6",
|
||||
"@types/node": "18.11.18",
|
||||
@@ -116,7 +124,7 @@
|
||||
"@types/react-redux": "7.1.25",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"electron": "19.1.4",
|
||||
"electron-builder": "22.11.7",
|
||||
"electron-builder": "23.6.0",
|
||||
"electron-notarize": "1.2.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"glob": "8.1.0",
|
||||
@@ -126,6 +134,7 @@
|
||||
"js-sha512": "0.8.0",
|
||||
"nan": "2.17.0",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"rollup": "3.21.0",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -171,7 +180,7 @@
|
||||
"roboto-fontface": "0.10.0",
|
||||
"smalltalk": "2.5.1",
|
||||
"sqlite3": "5.1.6",
|
||||
"styled-components": "5.3.10",
|
||||
"styled-components": "5.3.9",
|
||||
"styled-system": "5.1.5",
|
||||
"taboverride": "4.0.3",
|
||||
"tinymce": "5.10.6"
|
||||
|
24
packages/app-desktop/rollup-main-html.config.js
Normal file
24
packages/app-desktop/rollup-main-html.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const nodeResolve = require('@rollup/plugin-node-resolve');
|
||||
const pluginJson = require('@rollup/plugin-json');
|
||||
|
||||
module.exports = {
|
||||
input: 'main-html.js',
|
||||
output: {
|
||||
file: 'main-html.bundle.js',
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
pluginJson(),
|
||||
commonjs({
|
||||
dynamicRequireTargets: [
|
||||
'codemirror/mode/python/python',
|
||||
],
|
||||
}),
|
||||
],
|
||||
external: [
|
||||
'keytar',
|
||||
'fsevents',
|
||||
],
|
||||
};
|
16
packages/app-desktop/rollup-main.config.js
Normal file
16
packages/app-desktop/rollup-main.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const commonjs = require('@rollup/plugin-commonjs');
|
||||
const nodeResolve = require('@rollup/plugin-node-resolve');
|
||||
const pluginJson = require('@rollup/plugin-json');
|
||||
|
||||
module.exports = {
|
||||
input: 'main.source.js',
|
||||
output: {
|
||||
file: 'main.js',
|
||||
format: 'cjs',
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
pluginJson(),
|
||||
commonjs(),
|
||||
],
|
||||
};
|
@@ -79,13 +79,9 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
// 2023-05-09: This seems to be optional, but it's not. Without it, the app
|
||||
// will crash on certain devices with this error:
|
||||
//
|
||||
// > java.lang.UnsatisfiedLinkError: couldn't find DSO to load: libhermes.so"
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/8144#issuecomment-1539629812
|
||||
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"
|
||||
@@ -156,8 +152,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097707
|
||||
versionName "2.11.22"
|
||||
versionCode 2097695
|
||||
versionName "2.11.10"
|
||||
// ndk {
|
||||
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
// }
|
||||
@@ -304,11 +300,8 @@ dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
||||
//noinspection GradleDynamicVersion
|
||||
// implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation ("com.facebook.react:react-native") version {
|
||||
strictly "0.70.6" // pass in your react-native version
|
||||
}
|
||||
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
|
@@ -751,12 +751,6 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
</View>
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="version_info_hermes" style={this.styles().settingContainer}>
|
||||
<Text style={this.styles().settingText}>{_('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0)}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={this.rootStyle(this.props.themeId).root}>
|
||||
<ScreenHeader title={_('Configuration')} showSaveButton={true} showSearchButton={false} showSideMenuButton={false} saveButtonDisabled={!this.state.changedSettingKeys.length} onSaveButtonPress={this.saveButton_press} />
|
||||
|
@@ -290,8 +290,6 @@ PODS:
|
||||
- glog
|
||||
- react-native-alarm-notification (2.11.0):
|
||||
- React
|
||||
- react-native-blob-util (0.17.3):
|
||||
- React-Core
|
||||
- react-native-camera (4.2.1):
|
||||
- React-Core
|
||||
- react-native-camera/RCT (= 4.2.1)
|
||||
@@ -400,6 +398,8 @@ PODS:
|
||||
- React-jsi (= 0.70.6)
|
||||
- React-logger (= 0.70.6)
|
||||
- React-perflogger (= 0.70.6)
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
- RNCClipboard (1.5.1):
|
||||
- React-Core
|
||||
- RNCPushNotificationIOS (1.10.1):
|
||||
@@ -472,7 +472,6 @@ DEPENDENCIES:
|
||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- "react-native-alarm-notification (from `../node_modules/@joplin/react-native-alarm-notification`)"
|
||||
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
|
||||
- react-native-camera (from `../node_modules/react-native-camera`)
|
||||
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
|
||||
- react-native-fingerprint-scanner (from `../node_modules/react-native-fingerprint-scanner`)
|
||||
@@ -501,6 +500,7 @@ DEPENDENCIES:
|
||||
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
|
||||
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
|
||||
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
|
||||
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
|
||||
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
|
||||
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
|
||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||
@@ -576,8 +576,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||
react-native-alarm-notification:
|
||||
:path: "../node_modules/@joplin/react-native-alarm-notification"
|
||||
react-native-blob-util:
|
||||
:path: "../node_modules/react-native-blob-util"
|
||||
react-native-camera:
|
||||
:path: "../node_modules/react-native-camera"
|
||||
react-native-document-picker:
|
||||
@@ -634,6 +632,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native/ReactCommon/runtimeexecutor"
|
||||
ReactCommon:
|
||||
:path: "../node_modules/react-native/ReactCommon"
|
||||
rn-fetch-blob:
|
||||
:path: "../node_modules/rn-fetch-blob"
|
||||
RNCClipboard:
|
||||
:path: "../node_modules/@react-native-community/clipboard"
|
||||
RNCPushNotificationIOS:
|
||||
@@ -693,7 +693,6 @@ SPEC CHECKSUMS:
|
||||
React-jsinspector: 60769e5a0a6d4b32294a2456077f59d0266f9a8b
|
||||
React-logger: 1623c216abaa88974afce404dc8f479406bbc3a0
|
||||
react-native-alarm-notification: 26527410a6162d07a9dc57f4bbc62e94ff48e65d
|
||||
react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6
|
||||
react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f
|
||||
react-native-document-picker: 495c444c0c773c6e83a5d91165890ecb1c0a399a
|
||||
react-native-fingerprint-scanner: ac6656f18c8e45a7459302b84da41a44ad96dbbe
|
||||
@@ -722,6 +721,7 @@ SPEC CHECKSUMS:
|
||||
React-RCTVibration: c75ceef7aa60a33b2d5731ebe5800ddde40cefc4
|
||||
React-runtimeexecutor: 15437b576139df27635400de0599d9844f1ab817
|
||||
ReactCommon: 349be31adeecffc7986a0de875d7fb0dcf4e251c
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
|
||||
RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd
|
||||
|
@@ -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 = [];
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"version": "2.11.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "BROWSERSLIST_IGNORE_OLD_DATA=true react-native start --reset-cache",
|
||||
"start": "react-native start --reset-cache",
|
||||
"android": "react-native run-android",
|
||||
"build": "gulp build",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
@@ -42,7 +42,6 @@
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.70.6",
|
||||
"react-native-action-button": "2.8.5",
|
||||
"react-native-blob-util": "0.17.3",
|
||||
"react-native-camera": "4.2.1",
|
||||
"react-native-dialogbox": "0.6.10",
|
||||
"react-native-document-picker": "8.2.0",
|
||||
@@ -55,7 +54,7 @@
|
||||
"react-native-image-picker": "5.3.1",
|
||||
"react-native-image-resizer": "1.4.5",
|
||||
"react-native-modal-datetime-picker": "14.0.1",
|
||||
"react-native-paper": "5.6.0",
|
||||
"react-native-paper": "5.5.2",
|
||||
"react-native-popup-menu": "0.16.1",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-rsa-native": "2.0.5",
|
||||
@@ -71,6 +70,7 @@
|
||||
"react-native-webview": "11.26.1",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.2.1",
|
||||
"rn-fetch-blob": "0.12.0",
|
||||
"stream": "0.0.2",
|
||||
"stream-browserify": "3.0.0",
|
||||
"string-natural-compare": "3.0.1",
|
||||
|
@@ -76,7 +76,7 @@ const { defaultState } = require('@joplin/lib/reducer');
|
||||
const { FileApiDriverLocal } = require('@joplin/lib/file-api-driver-local');
|
||||
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
|
||||
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
||||
import WelcomeUtils from '@joplin/lib/WelcomeUtils';
|
||||
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
|
||||
const { themeStyle } = require('./components/global-style.js');
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
const SyncTargetFilesystem = require('@joplin/lib/SyncTargetFilesystem.js');
|
||||
@@ -142,7 +142,7 @@ const generalMiddleware = (store: any) => (next: any) => async (action: any) =>
|
||||
if (action.type === 'NAV_GO') Keyboard.dismiss();
|
||||
|
||||
if (['NOTE_UPDATE_ONE', 'NOTE_DELETE', 'FOLDER_UPDATE_ONE', 'FOLDER_DELETE'].indexOf(action.type) >= 0) {
|
||||
if (!await reg.syncTarget().syncStarted()) void reg.scheduleSync(1000, { syncSteps: ['update_remote', 'delete_remote'] }, true);
|
||||
if (!await reg.syncTarget().syncStarted()) void reg.scheduleSync(5 * 1000, { syncSteps: ['update_remote', 'delete_remote'] }, true);
|
||||
SearchEngine.instance().scheduleSyncTables();
|
||||
}
|
||||
|
||||
@@ -659,7 +659,7 @@ async function initialize(dispatch: Function) {
|
||||
// doWifiConnectionCheck set to true so initial sync
|
||||
// doesn't happen on mobile data
|
||||
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
|
||||
void reg.scheduleSync(100, null, true).then(() => {
|
||||
void reg.scheduleSync(1000, null, true).then(() => {
|
||||
// Wait for the first sync before updating the notifications, since synchronisation
|
||||
// might change the notifications.
|
||||
void AlarmService.updateAllNotifications();
|
||||
@@ -667,7 +667,7 @@ async function initialize(dispatch: Function) {
|
||||
void DecryptionWorker.instance().scheduleStart();
|
||||
});
|
||||
|
||||
await WelcomeUtils.install(Setting.value('locale'), dispatch);
|
||||
await WelcomeUtils.install(dispatch);
|
||||
|
||||
// Collect revisions more frequently on mobile because it doesn't auto-save
|
||||
// and it cannot collect anything when the app is not active.
|
||||
|
@@ -4,7 +4,7 @@
|
||||
const RNExitApp = require('react-native-exit-app').default;
|
||||
import { Profile, ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||
import { loadProfileConfig as libLoadProfileConfig, saveProfileConfig as libSaveProfileConfig } from '@joplin/lib/services/profileConfig/index';
|
||||
import RNFetchBlob from 'react-native-blob-util';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
let dispatch_: Function = null;
|
||||
export const setDispatch = (dispatch: Function) => {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import FsDriverBase, { ReadDirStatsOptions } from '@joplin/lib/fs-driver-base';
|
||||
import RNFetchBlob, { Encoding as RNFetchBlobEncoding } from 'react-native-blob-util';
|
||||
const RNFetchBlob = require('rn-fetch-blob').default;
|
||||
const RNFS = require('react-native-fs');
|
||||
const DocumentPicker = require('react-native-document-picker').default;
|
||||
import { openDocument } from '@joplin/react-native-saf-x';
|
||||
@@ -32,7 +32,7 @@ export default class FsDriverRN extends FsDriverBase {
|
||||
}
|
||||
// We need to use rn-fetch-blob here due to this bug:
|
||||
// https://github.com/itinance/react-native-fs/issues/700
|
||||
return RNFetchBlob.fs.writeFile(path, content, encoding as RNFetchBlobEncoding);
|
||||
return RNFetchBlob.fs.writeFile(path, content, encoding);
|
||||
}
|
||||
|
||||
// same as rm -rf
|
||||
|
@@ -1,7 +1,7 @@
|
||||
const shim = require('@joplin/lib/shim').default;
|
||||
const { GeolocationReact } = require('./geolocation-react.js');
|
||||
const PoorManIntervals = require('@joplin/lib/PoorManIntervals').default;
|
||||
const RNFetchBlob = require('react-native-blob-util').default;
|
||||
const RNFetchBlob = require('rn-fetch-blob').default;
|
||||
const { generateSecureRandom } = require('react-native-securerandom');
|
||||
const FsDriverRN = require('./fs-driver-rn').default;
|
||||
const { Buffer } = require('buffer');
|
||||
|
@@ -840,8 +840,13 @@ export default class BaseApplication {
|
||||
}
|
||||
|
||||
if (Setting.value('firstStart')) {
|
||||
const locale = shim.detectAndSetLocale(Setting);
|
||||
reg.logger().info(`First start: detected locale as ${locale}`);
|
||||
// If it's a sub-profile, the locale must come from the root
|
||||
// profile.
|
||||
if (!Setting.value('isSubProfile')) {
|
||||
const locale = shim.detectAndSetLocale(Setting);
|
||||
reg.logger().info(`First start: detected locale as ${locale}`);
|
||||
}
|
||||
|
||||
Setting.skipDefaultMigrations();
|
||||
|
||||
if (Setting.value('env') === 'dev') {
|
||||
|
@@ -45,13 +45,12 @@ export default class JoplinServerApi {
|
||||
private options_: Options;
|
||||
private session_: Session;
|
||||
private debugRequests_: boolean = false;
|
||||
private debugRequestsShowPasswords_: boolean = false;
|
||||
|
||||
public constructor(options: Options) {
|
||||
this.options_ = options;
|
||||
|
||||
if (options.env !== Env.Dev) {
|
||||
this.debugRequestsShowPasswords_ = false;
|
||||
if (options.env === Env.Dev) {
|
||||
// this.debugRequests_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,15 +97,15 @@ export default class JoplinServerApi {
|
||||
try {
|
||||
const output = JSON.parse(o);
|
||||
if (!output) return o;
|
||||
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
|
||||
if (output.password) output.password = '******';
|
||||
return JSON.stringify(output);
|
||||
} catch (error) {
|
||||
return o;
|
||||
}
|
||||
} else {
|
||||
const output = { ...o };
|
||||
if (output.password && !this.debugRequestsShowPasswords_) output.password = '******';
|
||||
if (output['X-API-AUTH'] && !this.debugRequestsShowPasswords_) output['X-API-AUTH'] = '******';
|
||||
if (output.password) output.password = '******';
|
||||
if (output['X-API-AUTH']) output['X-API-AUTH'] = '******';
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
80
packages/lib/WelcomeUtils.js
Normal file
80
packages/lib/WelcomeUtils.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const welcomeAssets = require('./welcomeAssets');
|
||||
const Note = require('./models/Note').default;
|
||||
const Setting = require('./models/Setting').default;
|
||||
const Folder = require('./models/Folder').default;
|
||||
const Tag = require('./models/Tag').default;
|
||||
const shim = require('./shim').default;
|
||||
const uuid = require('./uuid').default;
|
||||
const { fileExtension, basename } = require('./path-utils');
|
||||
const { pregQuote } = require('./string-utils');
|
||||
|
||||
class WelcomeUtils {
|
||||
static async createWelcomeItems() {
|
||||
const output = {
|
||||
defaultFolderId: null,
|
||||
};
|
||||
|
||||
const folderAssets = welcomeAssets.folders;
|
||||
const tempDir = Setting.value('resourceDir');
|
||||
|
||||
for (let i = 0; i < folderAssets.length; i++) {
|
||||
const folderAsset = folderAssets[i];
|
||||
const folder = await Folder.save({ title: `${folderAsset.title} (${Setting.appTypeToLabel(Setting.value('appType'))})` });
|
||||
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
|
||||
}
|
||||
|
||||
const noteAssets = welcomeAssets.notes;
|
||||
|
||||
for (let i = noteAssets.length - 1; i >= 0; i--) {
|
||||
const noteAsset = noteAssets[i];
|
||||
|
||||
let noteBody = noteAsset.body;
|
||||
|
||||
for (const resourceUrl in noteAsset.resources) {
|
||||
if (!noteAsset.resources.hasOwnProperty(resourceUrl)) continue;
|
||||
const resourceAsset = noteAsset.resources[resourceUrl];
|
||||
const ext = fileExtension(resourceUrl);
|
||||
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
|
||||
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
|
||||
const resource = await shim.createResourceFromPath(tempFilePath, {
|
||||
title: basename(resourceUrl),
|
||||
});
|
||||
await shim.fsDriver().remove(tempFilePath);
|
||||
|
||||
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
|
||||
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
|
||||
}
|
||||
|
||||
const note = await Note.save({
|
||||
parent_id: output.defaultFolderId,
|
||||
title: noteAsset.title,
|
||||
body: noteBody,
|
||||
});
|
||||
|
||||
if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async install(dispatch) {
|
||||
if (!Setting.value('welcome.enabled')) {
|
||||
Setting.setValue('welcome.wasBuilt', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Setting.value('welcome.wasBuilt')) {
|
||||
const result = await WelcomeUtils.createWelcomeItems();
|
||||
Setting.setValue('welcome.wasBuilt', true);
|
||||
|
||||
dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: result.defaultFolderId,
|
||||
});
|
||||
|
||||
Setting.setValue('activeFolderId', result.defaultFolderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WelcomeUtils;
|
@@ -1,122 +0,0 @@
|
||||
const welcomeAssetsAny = require('./welcomeAssets');
|
||||
import Note from './models/Note';
|
||||
import Setting from './models/Setting';
|
||||
import Folder from './models/Folder';
|
||||
import shim from './shim';
|
||||
import uuid from './uuid';
|
||||
import { fileExtension, basename } from './path-utils';
|
||||
import { _ } from './locale';
|
||||
const { pregQuote } = require('./string-utils');
|
||||
|
||||
export interface ItemMetadatum {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type ItemMetadata = Record<string, ItemMetadatum>;
|
||||
|
||||
export interface CreateWelcomeItemsResult {
|
||||
defaultFolderId: string;
|
||||
}
|
||||
|
||||
export interface WelcomeAssetResource {
|
||||
id: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface WelcomeAssetNote {
|
||||
id: string;
|
||||
parent_id: string;
|
||||
title: string;
|
||||
body: string;
|
||||
resources: Record<string, WelcomeAssetResource>;
|
||||
}
|
||||
|
||||
export interface WelcomeAssetFolder {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface AssetContent {
|
||||
notes: WelcomeAssetNote[];
|
||||
folders: WelcomeAssetFolder[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type WelcomeAssets = Record<string, AssetContent>;
|
||||
|
||||
class WelcomeUtils {
|
||||
|
||||
public static async createWelcomeItems(locale: string): Promise<CreateWelcomeItemsResult> {
|
||||
const output: CreateWelcomeItemsResult = {
|
||||
defaultFolderId: null,
|
||||
};
|
||||
|
||||
const allWelcomeAssets = welcomeAssetsAny as WelcomeAssets;
|
||||
const welcomeAssets = locale in allWelcomeAssets ? allWelcomeAssets[locale] : allWelcomeAssets['en_GB'];
|
||||
const enGbWelcomeAssets = allWelcomeAssets['en_GB'];
|
||||
|
||||
const folderAssets = welcomeAssets.folders;
|
||||
const tempDir = Setting.value('resourceDir');
|
||||
|
||||
// Actually we don't really support mutiple folders at this point, because not needed
|
||||
for (let i = 0; i < folderAssets.length; i++) {
|
||||
const folder = await Folder.save({ title: _('Welcome!') });
|
||||
if (!output.defaultFolderId) output.defaultFolderId = folder.id;
|
||||
}
|
||||
|
||||
const noteAssets = welcomeAssets.notes;
|
||||
|
||||
for (let i = noteAssets.length - 1; i >= 0; i--) {
|
||||
const noteAsset = noteAssets[i];
|
||||
const enGbNoteAsset = enGbWelcomeAssets.notes[i];
|
||||
|
||||
let noteBody = noteAsset.body;
|
||||
|
||||
for (const resourceUrl in enGbNoteAsset.resources) {
|
||||
if (!enGbNoteAsset.resources.hasOwnProperty(resourceUrl)) continue;
|
||||
const resourceAsset = enGbNoteAsset.resources[resourceUrl];
|
||||
const ext = fileExtension(resourceUrl);
|
||||
const tempFilePath = `${tempDir}/${uuid.create()}.tmp.${ext}`;
|
||||
await shim.fsDriver().writeFile(tempFilePath, resourceAsset.body, 'base64');
|
||||
const resource = await shim.createResourceFromPath(tempFilePath, {
|
||||
title: basename(resourceUrl),
|
||||
});
|
||||
await shim.fsDriver().remove(tempFilePath);
|
||||
|
||||
const regex = new RegExp(pregQuote(`(${resourceUrl})`), 'g');
|
||||
noteBody = noteBody.replace(regex, `(:/${resource.id})`);
|
||||
}
|
||||
|
||||
await Note.save({
|
||||
parent_id: output.defaultFolderId,
|
||||
title: noteAsset.title,
|
||||
body: noteBody,
|
||||
});
|
||||
|
||||
// if (noteAsset.tags) await Tag.setNoteTagsByTitles(note.id, noteAsset.tags);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static async install(locale: string, dispatch: Function) {
|
||||
if (!Setting.value('welcome.enabled')) {
|
||||
Setting.setValue('welcome.wasBuilt', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Setting.value('welcome.wasBuilt')) {
|
||||
const result = await WelcomeUtils.createWelcomeItems(locale);
|
||||
Setting.setValue('welcome.wasBuilt', true);
|
||||
|
||||
dispatch({
|
||||
type: 'FOLDER_SELECT',
|
||||
id: result.defaultFolderId,
|
||||
});
|
||||
|
||||
Setting.setValue('activeFolderId', result.defaultFolderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WelcomeUtils;
|
@@ -9,11 +9,12 @@ import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchroniz
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import { reg } from '../../registry';
|
||||
import Setting from '../../models/Setting';
|
||||
const { useCallback, useEffect, useState } = shim.react();
|
||||
|
||||
type PasswordChecks = Record<string, boolean>;
|
||||
|
||||
export const useStats = () => {
|
||||
const { useEffect, useState } = shim.react();
|
||||
|
||||
const [stats, setStats] = useState<EncryptedItemsStats>({ encrypted: null, total: null });
|
||||
const [statsUpdateTime, setStatsUpdateTime] = useState<number>(0);
|
||||
|
||||
@@ -75,6 +76,7 @@ export const dontReencryptData = () => {
|
||||
};
|
||||
|
||||
export const useToggleShowDisabledMasterKeys = () => {
|
||||
const { useState } = shim.react();
|
||||
const [showDisabledMasterKeys, setShowDisabledMasterKeys] = useState<boolean>(false);
|
||||
|
||||
const toggleShowDisabledMasterKeys = () => {
|
||||
@@ -109,6 +111,8 @@ export const onMasterPasswordSave = (masterPasswordInput: string) => {
|
||||
};
|
||||
|
||||
export const useInputMasterPassword = (masterKeys: MasterKeyEntity[], activeMasterKeyId: string) => {
|
||||
const { useCallback, useState } = shim.react();
|
||||
|
||||
const [inputMasterPassword, setInputMasterPassword] = useState<string>('');
|
||||
|
||||
const onMasterPasswordSave = useCallback(async () => {
|
||||
@@ -128,6 +132,8 @@ export const useInputMasterPassword = (masterKeys: MasterKeyEntity[], activeMast
|
||||
};
|
||||
|
||||
export const useInputPasswords = (propsPasswords: Record<string, string>) => {
|
||||
const { useCallback, useEffect, useState } = shim.react();
|
||||
|
||||
const [inputPasswords, setInputPasswords] = useState<Record<string, string>>(propsPasswords);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -147,6 +153,8 @@ export const useInputPasswords = (propsPasswords: Record<string, string>) => {
|
||||
};
|
||||
|
||||
export const usePasswordChecker = (masterKeys: MasterKeyEntity[], activeMasterKeyId: string, masterPassword: string, passwords: Record<string, string>) => {
|
||||
const { useState } = shim.react();
|
||||
|
||||
const [passwordChecks, setPasswordChecks] = useState<PasswordChecks>({});
|
||||
|
||||
// "masterPasswordKeys" are the master key that can be decrypted with the
|
||||
|
@@ -175,10 +175,6 @@ export default class FileApiDriverJoplinServer {
|
||||
// they can have names such as ".resources/xxxxxxxxxx'
|
||||
}
|
||||
|
||||
private isRejectedBySyncTargetError(error: any) {
|
||||
return error.code === 413 || error.code === 409 || error.httpCode === 413 || error.httpCode === 409;
|
||||
}
|
||||
|
||||
public async put(path: string, content: any, options: any = null) {
|
||||
try {
|
||||
const output = await this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, options && options.shareId ? { share_id: options.shareId } : null, content, {
|
||||
@@ -186,7 +182,7 @@ export default class FileApiDriverJoplinServer {
|
||||
}, options);
|
||||
return output;
|
||||
} catch (error) {
|
||||
if (this.isRejectedBySyncTargetError(error)) {
|
||||
if (error.code === 413) {
|
||||
throw new JoplinError(error.message, 'rejectedByTarget');
|
||||
}
|
||||
throw error;
|
||||
@@ -194,15 +190,7 @@ export default class FileApiDriverJoplinServer {
|
||||
}
|
||||
|
||||
public async multiPut(items: MultiPutItem[], options: any = null) {
|
||||
const output = await this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
|
||||
|
||||
for (const [, response] of Object.entries<any>(output.items)) {
|
||||
if (response.error && this.isRejectedBySyncTargetError(response.error)) {
|
||||
response.error.code = 'rejectedByTarget';
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
return this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);
|
||||
}
|
||||
|
||||
public async delete(path: string) {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import shim from '../shim';
|
||||
const { useEffect } = shim.react();
|
||||
|
||||
export interface AsyncEffectEvent {
|
||||
cancelled: boolean;
|
||||
@@ -8,6 +7,8 @@ export interface AsyncEffectEvent {
|
||||
export type EffectFunction = (event: AsyncEffectEvent)=> Promise<void>;
|
||||
|
||||
export default function(effect: EffectFunction, dependencies: any[]) {
|
||||
const { useEffect } = shim.react();
|
||||
|
||||
useEffect(() => {
|
||||
const event: AsyncEffectEvent = { cancelled: false };
|
||||
void effect(event);
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import shim from '../shim';
|
||||
const { useCallback, useEffect, useState } = shim.react();
|
||||
import useEventListener from './useEventListener';
|
||||
|
||||
interface Size {
|
||||
@@ -8,6 +7,8 @@ interface Size {
|
||||
}
|
||||
|
||||
function useElementSize(elementRef: any): Size {
|
||||
const { useCallback, useEffect, useState } = shim.react();
|
||||
|
||||
const [size, setSize] = useState({
|
||||
width: 0,
|
||||
height: 0,
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import shim from '../shim';
|
||||
const { useEffect, useRef } = shim.react();
|
||||
|
||||
function useEventListener(
|
||||
eventName: any,
|
||||
handler: any,
|
||||
element?: any
|
||||
) {
|
||||
const { useEffect, useRef } = shim.react();
|
||||
|
||||
// Create a ref that stores handler
|
||||
const savedHandler = useRef();
|
||||
|
||||
|
@@ -1,18 +0,0 @@
|
||||
import { closestSupportedLocale } from './locale';
|
||||
|
||||
describe('locale', () => {
|
||||
|
||||
it('should find the closest matching locale', () => {
|
||||
const testCases: [string, string[], string][] = [
|
||||
['fr', ['fr_FR', 'en_GB'], 'fr_FR'],
|
||||
['pt-br', ['fr_FR', 'en_GB', 'pt_BR'], 'pt_BR'],
|
||||
['ro', ['fr_FR', 'en_GB', 'pt_BR'], 'en_GB'],
|
||||
];
|
||||
|
||||
for (const [input, locales, expected] of testCases) {
|
||||
const actual = closestSupportedLocale(input, true, locales);
|
||||
expect(actual).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@@ -584,16 +584,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
|
||||
}
|
||||
|
||||
function _(s: string, ...args: any[]): string {
|
||||
return stringByLocale(currentLocale_, s, ...args);
|
||||
}
|
||||
|
||||
function _n(singular: string, plural: string, n: number, ...args: any[]) {
|
||||
if (n > 1) return _(plural, ...args);
|
||||
return _(singular, ...args);
|
||||
}
|
||||
|
||||
const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
|
||||
const strings = localeStrings(locale);
|
||||
const strings = localeStrings(currentLocale_);
|
||||
let result = strings[s];
|
||||
if (result === '' || result === undefined) result = s;
|
||||
try {
|
||||
@@ -601,6 +592,11 @@ const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
|
||||
} catch (error) {
|
||||
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function _n(singular: string, plural: string, n: number, ...args: any[]) {
|
||||
if (n > 1) return _(plural, ...args);
|
||||
return _(singular, ...args);
|
||||
}
|
||||
|
||||
export { _, _n, supportedLocales, currentLocale, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import Setting, { SettingItemType, SettingSectionSource, SettingStorage } from '../models/Setting';
|
||||
import Setting, { SettingSectionSource, SettingStorage } from '../models/Setting';
|
||||
import { setupDatabaseAndSynchronizer, switchClient, expectThrow, expectNotThrow, msleep } from '../testing/test-utils';
|
||||
import { readFile, stat, mkdirp, writeFile, pathExists, readdir } from 'fs-extra';
|
||||
import Logger from '../Logger';
|
||||
@@ -61,11 +61,11 @@ describe('models/Setting', () => {
|
||||
await Setting.reset();
|
||||
|
||||
await expectNotThrow(async () => Setting.load());
|
||||
await expectThrow(async () => Setting.value('itsgone'), 'unknown_key');
|
||||
await expectThrow(async () => Setting.value('itsgone'));
|
||||
}));
|
||||
|
||||
it('should allow registering new settings dynamically', (async () => {
|
||||
await expectThrow(async () => Setting.setValue('myCustom', '123'), 'unknown_key');
|
||||
await expectThrow(async () => Setting.setValue('myCustom', '123'));
|
||||
|
||||
await Setting.registerSetting('myCustom', {
|
||||
public: true,
|
||||
@@ -297,21 +297,12 @@ describe('models/Setting', () => {
|
||||
expect(Setting.isSet('spellChecker.languages')).toBe(false);
|
||||
}));
|
||||
|
||||
it('should load sub-profile settings', async () => {
|
||||
it('should load sub-profile settings - 1', async () => {
|
||||
await Setting.reset();
|
||||
|
||||
await Setting.registerSetting('non_builtin', {
|
||||
public: true,
|
||||
storage: SettingStorage.File,
|
||||
isGlobal: true,
|
||||
type: SettingItemType.Bool,
|
||||
value: false,
|
||||
});
|
||||
|
||||
Setting.setValue('locale', 'fr_FR'); // Global setting
|
||||
Setting.setValue('theme', Setting.THEME_DARK); // Global setting
|
||||
Setting.setValue('sync.target', 9); // Local setting
|
||||
Setting.setValue('non_builtin', true); // Local setting
|
||||
await Setting.saveAll();
|
||||
|
||||
await switchToSubProfileSettings();
|
||||
@@ -320,9 +311,6 @@ describe('models/Setting', () => {
|
||||
expect(Setting.value('theme')).toBe(Setting.THEME_DARK); // Should come from the root profile
|
||||
expect(Setting.value('sync.target')).toBe(0); // Should come from the local profile
|
||||
|
||||
// Non-built-in variables are not copied
|
||||
expect(() => Setting.value('non_builtin')).toThrow();
|
||||
|
||||
// Also check that the special loadOne() function works as expected
|
||||
|
||||
expect((await Setting.loadOne('locale')).value).toBe('fr_FR');
|
||||
@@ -330,7 +318,7 @@ describe('models/Setting', () => {
|
||||
expect((await Setting.loadOne('sync.target')).value).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should save sub-profile settings', async () => {
|
||||
it('should save sub-profile settings - 2', async () => {
|
||||
await Setting.reset();
|
||||
Setting.setValue('locale', 'fr_FR'); // Global setting
|
||||
Setting.setValue('theme', Setting.THEME_DARK); // Global setting
|
||||
|
@@ -7,9 +7,6 @@ import SyncTargetRegistry from '../SyncTargetRegistry';
|
||||
import time from '../time';
|
||||
import FileHandler, { SettingValues } from './settings/FileHandler';
|
||||
import Logger from '../Logger';
|
||||
import mergeGlobalAndLocalSettings from '../services/profileConfig/mergeGlobalAndLocalSettings';
|
||||
import splitGlobalAndLocalSettings from '../services/profileConfig/splitGlobalAndLocalSettings';
|
||||
import JoplinError from '../JoplinError';
|
||||
const { sprintf } = require('sprintf-js');
|
||||
const ObjectUtils = require('../ObjectUtils');
|
||||
const { toTitleCase } = require('../string-utils.js');
|
||||
@@ -17,6 +14,44 @@ const { rtrimSlashes, toSystemSlashes } = require('../path-utils');
|
||||
|
||||
const logger = Logger.create('models/Setting');
|
||||
|
||||
const mergeGlobalAndLocalSettings = (rootSettings: Record<string, any>, subProfileSettings: Record<string, any>) => {
|
||||
const output: Record<string, any> = { ...subProfileSettings };
|
||||
|
||||
for (const k of Object.keys(output)) {
|
||||
const md = Setting.settingMetadata(k);
|
||||
if (md.isGlobal) {
|
||||
delete output[k];
|
||||
if (k in rootSettings) output[k] = rootSettings[k];
|
||||
}
|
||||
}
|
||||
|
||||
for (const k of Object.keys(rootSettings)) {
|
||||
const md = Setting.settingMetadata(k);
|
||||
if (md.isGlobal) {
|
||||
output[k] = rootSettings[k];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
const splitGlobalAndLocalSettings = (settings: SettingValues) => {
|
||||
const globalSettings: SettingValues = {};
|
||||
const localSettings: SettingValues = {};
|
||||
|
||||
for (const [k, v] of Object.entries(settings)) {
|
||||
const md = Setting.settingMetadata(k);
|
||||
|
||||
if (md.isGlobal) {
|
||||
globalSettings[k] = v;
|
||||
} else {
|
||||
localSettings[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return { globalSettings, localSettings };
|
||||
};
|
||||
|
||||
export enum SettingItemType {
|
||||
Int = 1,
|
||||
String = 2,
|
||||
@@ -220,8 +255,6 @@ const userSettingMigration: UserSettingMigration[] = [
|
||||
|
||||
class Setting extends BaseModel {
|
||||
|
||||
public static schemaUrl = 'https://joplinapp.org/schema/settings.json';
|
||||
|
||||
// For backward compatibility
|
||||
public static TYPE_INT = SettingItemType.Int;
|
||||
public static TYPE_STRING = SettingItemType.String;
|
||||
@@ -313,7 +346,6 @@ class Setting extends BaseModel {
|
||||
private static fileHandler_: FileHandler = null;
|
||||
private static rootFileHandler_: FileHandler = null;
|
||||
private static settingFilename_: string = 'settings.json';
|
||||
private static buildInMetadata_: SettingItems = null;
|
||||
|
||||
public static tableName() {
|
||||
return 'settings';
|
||||
@@ -408,7 +440,7 @@ class Setting extends BaseModel {
|
||||
return output;
|
||||
};
|
||||
|
||||
this.buildInMetadata_ = {
|
||||
this.metadata_ = {
|
||||
'clientId': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
@@ -1698,8 +1730,6 @@ class Setting extends BaseModel {
|
||||
|
||||
};
|
||||
|
||||
this.metadata_ = { ...this.buildInMetadata_ };
|
||||
|
||||
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
|
||||
|
||||
if (this.constants_.env === Env.Dev) this.validateMetadata(this.metadata_);
|
||||
@@ -1713,10 +1743,6 @@ class Setting extends BaseModel {
|
||||
}
|
||||
}
|
||||
|
||||
public static isBuiltinKey(key: string): boolean {
|
||||
return key in this.buildInMetadata_;
|
||||
}
|
||||
|
||||
public static customCssFilePath(filename: string): string {
|
||||
return `${this.value('rootProfileDir')}/${filename}`;
|
||||
}
|
||||
@@ -1818,7 +1844,7 @@ class Setting extends BaseModel {
|
||||
|
||||
public static settingMetadata(key: string): SettingItem {
|
||||
const metadata = this.metadata();
|
||||
if (!(key in metadata)) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
|
||||
if (!(key in metadata)) throw new Error(`Unknown key: ${key}`);
|
||||
const output = Object.assign({}, metadata[key]);
|
||||
output.key = key;
|
||||
return output;
|
||||
@@ -2263,7 +2289,7 @@ class Setting extends BaseModel {
|
||||
|
||||
public static enumOptions(key: string) {
|
||||
const metadata = this.metadata();
|
||||
if (!metadata[key]) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
|
||||
if (!metadata[key]) throw new Error(`Unknown key: ${key}`);
|
||||
if (!metadata[key].options) throw new Error(`No options for: ${key}`);
|
||||
return metadata[key].options();
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import Logger from '../../Logger';
|
||||
import shim from '../../shim';
|
||||
import Setting from '../Setting';
|
||||
import { schemaUrl } from './types';
|
||||
|
||||
const logger = Logger.create('Settings');
|
||||
|
||||
@@ -41,7 +41,7 @@ export default class FileHandler {
|
||||
|
||||
public async save(values: SettingValues) {
|
||||
const json = `${JSON.stringify({
|
||||
'$schema': Setting.schemaUrl,
|
||||
'$schema': schemaUrl,
|
||||
...values,
|
||||
}, null, '\t')}\n`;
|
||||
|
||||
|
2
packages/lib/models/settings/types.ts
Normal file
2
packages/lib/models/settings/types.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const schemaUrl = 'https://joplinapp.org/schema/settings.json';
|
@@ -2,29 +2,19 @@ import shim from './shim';
|
||||
import time from './time';
|
||||
const ntpClient_ = require('./vendor/ntp-client');
|
||||
|
||||
interface NtpServer {
|
||||
domain: string;
|
||||
port: number;
|
||||
}
|
||||
const server = {
|
||||
domain: 'pool.ntp.org',
|
||||
port: 123,
|
||||
};
|
||||
|
||||
function ntpClient() {
|
||||
ntpClient_.dgram = shim.dgram();
|
||||
return ntpClient_;
|
||||
}
|
||||
|
||||
const parseNtpServer = (ntpServer: string): NtpServer => {
|
||||
const s = ntpServer.split(':');
|
||||
if (s.length !== 2) throw new Error('NTP server URL must be in format `domain:port`');
|
||||
return {
|
||||
domain: s[0],
|
||||
port: Number(s[1]),
|
||||
};
|
||||
};
|
||||
|
||||
export async function getNetworkTime(ntpServer: string): Promise<Date> {
|
||||
export async function getNetworkTime(): Promise<Date> {
|
||||
return new Promise((resolve: Function, reject: Function) => {
|
||||
const s = parseNtpServer(ntpServer);
|
||||
ntpClient().getNetworkTime(s.domain, s.port, (error: any, date: Date) => {
|
||||
ntpClient().getNetworkTime(server.domain, server.port, (error: any, date: Date) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
@@ -35,7 +25,7 @@ export async function getNetworkTime(ntpServer: string): Promise<Date> {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDeviceTimeDrift(ntpServer: string): Promise<number> {
|
||||
export async function getDeviceTimeDrift(): Promise<number> {
|
||||
const maxTries = 3;
|
||||
let tryCount = 0;
|
||||
|
||||
@@ -44,12 +34,12 @@ export async function getDeviceTimeDrift(ntpServer: string): Promise<number> {
|
||||
while (true) {
|
||||
tryCount++;
|
||||
try {
|
||||
ntpTime = await getNetworkTime(ntpServer);
|
||||
ntpTime = await getNetworkTime();
|
||||
break;
|
||||
} catch (error) {
|
||||
if (tryCount >= maxTries) {
|
||||
const newError = typeof error === 'string' ? new Error(error) : error;
|
||||
newError.message = `Cannot retrieve the network time from ${ntpServer}: ${newError.message}`;
|
||||
newError.message = `Cannot retrieve the network time from ${server.domain}:${server.port}: ${newError.message}`;
|
||||
throw newError;
|
||||
} else {
|
||||
await time.msleep(tryCount * 1000);
|
||||
|
@@ -1,54 +0,0 @@
|
||||
import Logger from '../../Logger';
|
||||
import Setting from '../../models/Setting';
|
||||
|
||||
const logger = Logger.create('mergeGlobalAndLocalSettings');
|
||||
|
||||
export default (rootSettings: Record<string, any>, subProfileSettings: Record<string, any>) => {
|
||||
const output: Record<string, any> = { ...subProfileSettings };
|
||||
|
||||
for (const k of Object.keys(output)) {
|
||||
try {
|
||||
const md = Setting.settingMetadata(k);
|
||||
if (md.isGlobal) {
|
||||
delete output[k];
|
||||
if (k in rootSettings) output[k] = rootSettings[k];
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'unknown_key') {
|
||||
// The root settings may contain plugin parameters, but the
|
||||
// sub-profile won't necessarily have these plugins. In that
|
||||
// case, the app will throw an error, but we can ignore it since
|
||||
// we don't need this particular setting.
|
||||
// https://github.com/laurent22/joplin/issues/8143
|
||||
logger.info(`Ignoring unknown key in root settings: ${k}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const k of Object.keys(rootSettings)) {
|
||||
// We only copy built-in key and not, for example, plugin keys, because
|
||||
// those are plugin-specific
|
||||
if (!Setting.isBuiltinKey(k)) {
|
||||
logger.info(`Skipping non-built-in key: ${k}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const md = Setting.settingMetadata(k);
|
||||
if (md.isGlobal) {
|
||||
output[k] = rootSettings[k];
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'unknown_key') {
|
||||
// The root settings may contain plugin parameters, but the
|
||||
// sub-profile won't necessarily have these plugins. In that
|
||||
// case, the app will throw an error, but we can ignore it since
|
||||
// we don't need this particular setting.
|
||||
// https://github.com/laurent22/joplin/issues/8143
|
||||
logger.info(`Ignoring unknown key in root settings: ${k}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
@@ -1,19 +0,0 @@
|
||||
import Setting from '../../models/Setting';
|
||||
import { SettingValues } from '../../models/settings/FileHandler';
|
||||
|
||||
export default (settings: SettingValues) => {
|
||||
const globalSettings: SettingValues = {};
|
||||
const localSettings: SettingValues = {};
|
||||
|
||||
for (const [k, v] of Object.entries(settings)) {
|
||||
const md = Setting.settingMetadata(k);
|
||||
|
||||
if (md.isGlobal) {
|
||||
globalSettings[k] = v;
|
||||
} else {
|
||||
localSettings[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
return { globalSettings, localSettings };
|
||||
};
|
@@ -159,7 +159,7 @@ describe('synchronizer/ItemUploader', () => {
|
||||
await itemUploader.preUploadItems(notes);
|
||||
|
||||
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[0]), notes[0]));
|
||||
await expectThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]), null);
|
||||
await expectThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[1]), notes[1]));
|
||||
await expectNotThrow(async () => itemUploader.serializeAndUploadItem(Note, BaseItem.systemPath(notes[2]), notes[2]));
|
||||
}));
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { ModelType } from '../../BaseModel';
|
||||
import { FileApi, MultiPutItem } from '../../file-api';
|
||||
import JoplinError from '../../JoplinError';
|
||||
import Logger from '../../Logger';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
import { BaseItemEntity } from '../database/types';
|
||||
@@ -46,8 +45,7 @@ export default class ItemUploader {
|
||||
// the regular upload.
|
||||
logger.warn(`Pre-uploaded item updated_time has changed. It is going to be re-uploaded again: ${path} (From ${this.preUploadedItemUpdatedTimes_[path]} to ${local.updated_time})`);
|
||||
} else {
|
||||
const error = preUploadItem.error;
|
||||
if (error) throw new JoplinError(error.message ? error.message : 'Unknown pre-upload error', error.code);
|
||||
if (preUploadItem.error) throw new Error(preUploadItem.error.message ? preUploadItem.error.message : 'Unknown pre-upload error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSyn
|
||||
import Folder from '../../models/Folder';
|
||||
import Note from '../../models/Note';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
import WelcomeUtils from '../../WelcomeUtils';
|
||||
const WelcomeUtils = require('../../WelcomeUtils');
|
||||
|
||||
describe('Synchronizer.basics', () => {
|
||||
|
||||
@@ -332,12 +332,12 @@ describe('Synchronizer.basics', () => {
|
||||
it('should create a new Welcome notebook on each client', (async () => {
|
||||
// Create the Welcome items on two separate clients
|
||||
|
||||
await WelcomeUtils.createWelcomeItems('en_GB');
|
||||
await WelcomeUtils.createWelcomeItems();
|
||||
await synchronizerStart();
|
||||
|
||||
await switchClient(2);
|
||||
|
||||
await WelcomeUtils.createWelcomeItems('en_GB');
|
||||
await WelcomeUtils.createWelcomeItems();
|
||||
const beforeFolderCount = (await Folder.all()).length;
|
||||
const beforeNoteCount = (await Note.all()).length;
|
||||
expect(beforeFolderCount === 1).toBe(true);
|
||||
|
@@ -3,7 +3,6 @@ import MigrationHandler from '../MigrationHandler';
|
||||
import Setting from '../../../models/Setting';
|
||||
import { reg } from '../../../registry';
|
||||
import { appTypeToLockType } from '../LockHandler';
|
||||
const { useEffect, useState } = shim.react();
|
||||
|
||||
export interface SyncTargetUpgradeResult {
|
||||
done: boolean;
|
||||
@@ -11,6 +10,8 @@ export interface SyncTargetUpgradeResult {
|
||||
}
|
||||
|
||||
export default function useSyncTargetUpgrade(): SyncTargetUpgradeResult {
|
||||
const { useEffect, useState } = shim.react();
|
||||
|
||||
const [upgradeResult, setUpgradeResult] = useState({
|
||||
done: false,
|
||||
error: null,
|
||||
|
@@ -139,7 +139,7 @@ function shimInit(options = null) {
|
||||
};
|
||||
|
||||
shim.detectAndSetLocale = function(Setting) {
|
||||
let locale = shim.isElectron() ? shim.electronBridge().getLocale() : process.env.LANG;
|
||||
let locale = process.env.LANG;
|
||||
if (!locale) locale = defaultLocale();
|
||||
locale = locale.split('.');
|
||||
locale = locale[0];
|
||||
|
File diff suppressed because one or more lines are too long
@@ -44,6 +44,6 @@
|
||||
"pdfjs-dist": "2.16.105",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"styled-components": "5.3.10"
|
||||
"styled-components": "5.3.9"
|
||||
}
|
||||
}
|
||||
|
98
packages/react-native-vosk/.circleci/config.yml
Normal file
98
packages/react-native-vosk/.circleci/config.yml
Normal 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
|
4
packages/react-native-vosk/.gitattributes
vendored
Normal file
4
packages/react-native-vosk/.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pbxproj -text
|
||||
# specific for windows script files
|
||||
*.bat text eol=crlf
|
||||
*.a filter=lfs diff=lfs merge=lfs -text
|
22
packages/react-native-vosk/.github/workflows/npm-publish.yml
vendored
Normal file
22
packages/react-native-vosk/.github/workflows/npm-publish.yml
vendored
Normal 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
72
packages/react-native-vosk/.gitignore
vendored
Normal 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-*
|
1
packages/react-native-vosk/.watchmanconfig
Normal file
1
packages/react-native-vosk/.watchmanconfig
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
3
packages/react-native-vosk/.yarnrc
Normal file
3
packages/react-native-vosk/.yarnrc
Normal file
@@ -0,0 +1,3 @@
|
||||
# Override Yarn command so we can automatically setup the repo on running `yarn`
|
||||
|
||||
yarn-path "scripts/bootstrap.js"
|
195
packages/react-native-vosk/CONTRIBUTING.md
Normal file
195
packages/react-native-vosk/CONTRIBUTING.md
Normal 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.
|
20
packages/react-native-vosk/LICENSE
Normal file
20
packages/react-native-vosk/LICENSE
Normal 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.
|
125
packages/react-native-vosk/README.md
Normal file
125
packages/react-native-vosk/README.md
Normal 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.
|
||||

|
||||
|
||||
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:
|
||||

|
||||
|
||||
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]".
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
That's all. The model folder should appear in your project. When you click on it, your project target should be checked (see below).
|
||||
|
||||

|
||||
|
||||
## 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
|
107
packages/react-native-vosk/android/build.gradle
Normal file
107
packages/react-native-vosk/android/build.gradle
Normal 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"
|
||||
}
|
||||
}
|
5
packages/react-native-vosk/android/gradle.properties
Normal file
5
packages/react-native-vosk/android/gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
Vosk_kotlinVersion=1.7.0
|
||||
Vosk_minSdkVersion=21
|
||||
Vosk_targetSdkVersion=31
|
||||
Vosk_compileSdkVersion=31
|
||||
Vosk_ndkversion=21.4.7075529
|
@@ -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>
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
3
packages/react-native-vosk/babel.config.js
Normal file
3
packages/react-native-vosk/babel.config.js
Normal 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 |
BIN
packages/react-native-vosk/docs/xcode_add_files_to_folder.png
Normal file
BIN
packages/react-native-vosk/docs/xcode_add_files_to_folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
BIN
packages/react-native-vosk/docs/xcode_chose_model_folder.png
Normal file
BIN
packages/react-native-vosk/docs/xcode_chose_model_folder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
After Width: | Height: | Size: 304 KiB |
4
packages/react-native-vosk/ios/Vosk-Bridging-Header.h
Normal file
4
packages/react-native-vosk/ios/Vosk-Bridging-Header.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import "vosk_api.h"
|
21
packages/react-native-vosk/ios/Vosk.m
Normal file
21
packages/react-native-vosk/ios/Vosk.m
Normal 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
|
179
packages/react-native-vosk/ios/Vosk.swift
Normal file
179
packages/react-native-vosk/ios/Vosk.swift
Normal 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);
|
||||
}
|
||||
}
|
301
packages/react-native-vosk/ios/Vosk.xcodeproj/project.pbxproj
Normal file
301
packages/react-native-vosk/ios/Vosk.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
1
packages/react-native-vosk/ios/Vosk/README.md
Normal file
1
packages/react-native-vosk/ios/Vosk/README.md
Normal 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
|
51
packages/react-native-vosk/ios/VoskModel.swift
Normal file
51
packages/react-native-vosk/ios/VoskModel.swift
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
|
292
packages/react-native-vosk/ios/vosk_api.h
Normal file
292
packages/react-native-vosk/ios/vosk_api.h
Normal 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 */
|
11
packages/react-native-vosk/lefthook.yml
Normal file
11
packages/react-native-vosk/lefthook.yml
Normal 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
|
95
packages/react-native-vosk/lib/commonjs/index.js
vendored
Normal file
95
packages/react-native-vosk/lib/commonjs/index.js
vendored
Normal 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
|
88
packages/react-native-vosk/lib/module/index.js
vendored
Normal file
88
packages/react-native-vosk/lib/module/index.js
vendored
Normal 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
|
23
packages/react-native-vosk/lib/typescript/index.d.ts
vendored
Normal file
23
packages/react-native-vosk/lib/typescript/index.d.ts
vendored
Normal 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 {};
|
85
packages/react-native-vosk/package.json
Normal file
85
packages/react-native-vosk/package.json
Normal 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"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user