1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-24 20:19:10 +02:00

Compare commits

...

55 Commits

Author SHA1 Message Date
Laurent Cozic
9160f0e2a2 Android 2.11.22 2023-05-14 14:49:59 +01:00
Laurent Cozic
92272533e5 Merge branch 'dev' of github.com:laurent22/joplin into dev 2023-05-14 14:34:27 +01:00
Laurent Cozic
648e091523 Chore: Fix Android release script 2023-05-14 14:33:13 +01:00
Laurent Cozic
b023f58497 Mobile: Fix "Download interrupted" error 2023-05-14 14:31:55 +01:00
Laurent Cozic
c639791d4f Include yarn patches 2023-05-14 14:24:58 +01:00
Joplin Bot
6d52288e28 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-14 12:19:54 +00:00
Laurent Cozic
3e2f4b163b Android 2.11.21 2023-05-14 12:20:01 +01:00
renovate[bot]
e0dbd198d8 Update dependency react-native-paper to v5.6.0 (#8181)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 18:40:18 +01:00
Laurent Cozic
a76c5c8746 Tools: Allow publishing varients 2023-05-13 18:25:29 +01:00
Laurent Cozic
20b43ce56e fix 2023-05-13 16:31:24 +01:00
Laurent Cozic
8215ce14c6 Tools: Allow different Android variants 2023-05-13 15:54:17 +01:00
renovate[bot]
3fead0a8a7 Update dependency styled-components to v5.3.10 (#8178)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-13 05:50:55 +00:00
Jonatan
c8bf3e8583 All: Translation: Update sv.po (#8169) 2023-05-12 13:57:39 -04:00
Dmitriy Q
42dee6c275 All: Translation: Update ru_RU.po (#8161) 2023-05-12 13:56:10 -04:00
Laurent Cozic
6f3f866f78 Android 2.11.16 2023-05-12 13:44:15 +01:00
Laurent Cozic
adf2e7322d fix 2023-05-12 13:18:29 +01:00
Laurent Cozic
0df170926a Tools: Restore content after patching Android app 2023-05-12 13:18:28 +01:00
Laurent Cozic
20a26732a9 Tools: Restore content after patching Android app 2023-05-12 13:18:27 +01:00
Laurent Cozic
0da3e91a29 Tools: Exclude Vosk model files when building regular app 2023-05-12 13:17:01 +01:00
renovate[bot]
9ad56dc373 Update dependency gettext-extractor to v3.7.2 (#8167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-12 05:32:43 +00:00
Arda Kılıçdağı
29dab26dce All: Translation: Update tr_TR.po (#8162)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2023-05-11 18:22:36 -04:00
github-actions[bot]
10e8fbcdab @Ardakilic has signed the CLA in laurent22/joplin#8162 2023-05-11 19:11:11 +00:00
Laurent Cozic
6c4f566765 Doc: Fix download links 2023-05-11 19:12:39 +01:00
Joplin Bot
78df302e86 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-11 17:55:45 +00:00
Laurent Cozic
f52dd4f098 Doc: Update download links 2023-05-11 18:44:50 +01:00
github-actions[bot]
dbab786c7b @marph91 has signed the CLA in laurent22/joplin#8163 2023-05-11 14:17:59 +00:00
Laurent Cozic
3eb44d27b2 Mobile: Sync as soon as the app starts, and immediately after changing a note 2023-05-11 14:58:19 +01:00
Laurent Cozic
52bea115ac Tools: Allow uploading Android version to different repo 2023-05-11 14:48:33 +01:00
Laurent Cozic
19bdda25c6 Desktop: Security: Prevent XSS and potential RCE when using a special HTML tag 2023-05-11 14:17:37 +01:00
Laurent Cozic
b26bc9ed5f Desktop: Security: Fixed possible XSS injection 2023-05-10 16:27:16 +01:00
Laurent Cozic
865cedc24f Android 2.11.14 2023-05-10 13:28:43 +01:00
Laurent Cozic
33f0811ad2 Server: Resolves #8153: Allow setting NTP server using NTP_SERVER env variable 2023-05-10 12:50:48 +01:00
Laurent Cozic
8cedf27fea Desktop, Mobile: Resolves #8154: Translate Welcome notes 2023-05-10 12:20:04 +01:00
Laurent Cozic
052a829167 Desktop: Auto-detect locale on startup 2023-05-10 12:20:03 +01:00
Laurent Cozic
5371c97ccd Chore: Refactor build-welcome script 2023-05-10 12:20:02 +01:00
Joplin Bot
c53b957293 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-09 18:17:54 +00:00
Laurent Cozic
e6f8dc96df Fixed CI 2023-05-09 17:21:40 +01:00
Laurent Cozic
21648b1b1b Desktop: Fixes #8149: Application cannot be installed on Windows 10 in some cases 2023-05-09 12:56:23 +01:00
Laurent Cozic
83db6f6596 Desktop release v2.11.2 2023-05-09 11:13:38 +01:00
Laurent Cozic
3adfa574c0 Desktop: Fixes #8149: Application cannot be installed on Windows 10 in some cases 2023-05-09 11:13:23 +01:00
Laurent Cozic
4d0ffc5beb clean up 2023-05-09 11:12:53 +01:00
Laurent Cozic
69f9b160dd Chore: Cleanup Android release script 2023-05-09 11:01:43 +01:00
Laurent Cozic
c17b02cfb5 Android 2.11.13 2023-05-09 08:49:00 +01:00
Laurent Cozic
6dd57b63a6 Chore: Enable Hermes again on Android to try to fix crash 2023-05-08 21:03:11 +01:00
Laurent Cozic
248c8014c8 Android 2.11.12 2023-05-08 21:02:28 +01:00
Joplin Bot
5fe2766a6b Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-08 18:16:06 +00:00
Laurent Cozic
000e0ad517 Chore: Android: Remove non longer needed joplin/react-native-vosk package 2023-05-08 18:54:23 +01:00
Laurent Cozic
c047375143 Chore: Android: Trying to fix random crash 2023-05-08 18:44:52 +01:00
Laurent Cozic
bd9e62cbd2 Android 2.11.11 2023-05-08 18:27:18 +01:00
Laurent Cozic
5ecae17538 Mobile: Tells whether Hermes engine is enabled or not 2023-05-08 17:50:19 +01:00
Laurent Cozic
35037e2dc9 fix tests 2023-05-08 17:30:30 +01:00
Laurent Cozic
059202be09 Desktop: Fixes #8072: Enter Key No Longer Saves and Closes The Tag Dialog 2023-05-08 17:07:55 +01:00
Laurent Cozic
6672f63981 Desktop: Fixes #8143: Fixes crash when using multiple profiles along with certain plugins 2023-05-08 16:45:18 +01:00
Laurent Cozic
f390eca4de Desktop: Fixes #8143: Fixes crash when using multiple profiles along with certain plugins 2023-05-08 15:34:38 +01:00
Joplin Bot
edc5e33559 Doc: Auto-update documentation
Auto-updated using release-website.sh
2023-05-08 13:12:39 +00:00
97 changed files with 1567 additions and 7749 deletions

View File

@@ -483,6 +483,7 @@ 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
@@ -518,6 +519,7 @@ 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
@@ -811,7 +813,6 @@ 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
@@ -845,6 +846,7 @@ 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

View File

@@ -183,12 +183,32 @@ if [[ $GIT_TAG_NAME = v* ]]; then
# cd "$ROOT_DIR/packages/tools"
# node bundleDefaultPlugins.js
cd "$ROOT_DIR/packages/app-desktop"
USE_HARD_LINKS=false yarn run dist
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
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..."
USE_HARD_LINKS=false yarn run dist --publish=never
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
fi

4
.gitignore vendored
View File

@@ -469,6 +469,7 @@ 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
@@ -504,6 +505,7 @@ 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
@@ -797,7 +799,6 @@ 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
@@ -831,6 +832,7 @@ 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

View File

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

View File

@@ -9,7 +9,9 @@ function getOs() {
function getFilename(path) {
if (!path) return '';
const s = path.split('/');
return s.pop();
const urlWithParams = s.pop();
const s2 = urlWithParams.split('?');
return s2[0];
}
function getMobileOs() {

View File

@@ -134,10 +134,16 @@ 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://github.com/laurent22/joplin/releases/download/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage"
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.png" https://joplinapp.org/images/Icon512.png
#-----------------------------------------------------

View File

@@ -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://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>
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>
**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 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 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://github.com/laurent22/joplin/releases/download/v2.9.17/J
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://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)
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)
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

View File

@@ -107,7 +107,10 @@
".eslintignore": true,
".gitignore": true,
".vscode/*": true,
".yarn": true,
".yarn/cache": true,
".yarn/install-state.gz": true,
".yarn/plugins": true,
".yarn/releases": true,
"*.sublime-workspace": true,
"**/_mydocs": true,
"**/_mydocs/EnexSamples/*.enex": true,

View File

@@ -0,0 +1 @@
<div class="jop-noMdConv"><svg class="jop-noMdConv"><style class="jop-noMdConv">&lt;/svg>&lt;iframe srcdoc="&lt;script>top.require('child_process').execSync('calc')&lt;/script>">&lt;/iframe>&lt;/div>

View File

@@ -0,0 +1 @@
<div><svg><style></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>

View File

@@ -0,0 +1 @@
<a href="#" class="jop-noMdConv">XSS</a>

View File

@@ -0,0 +1 @@
<a data-from-md="" href="javascript:top.require('child_process').execSync('open -a Calculator')">XSS</a>

View File

@@ -68,6 +68,10 @@ 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

View File

@@ -97,7 +97,8 @@ async function fetchLatestRelease(options: CheckForUpdateOptions) {
}
if (found) {
downloadUrl = asset.browser_download_url;
downloadUrl = asset.browser_download_url.replace('github.com/laurent22/joplin/releases/download', 'objects.joplinusercontent.com');
downloadUrl.concat('?source=DesktopApp&type=Update');
break;
}
}

View File

@@ -26,11 +26,15 @@ 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() {
@@ -39,6 +43,7 @@ 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) {
@@ -52,6 +57,14 @@ 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;
@@ -224,16 +237,14 @@ export default class PromptDialog extends React.Component<Props, any> {
const onKeyDown = (event: any) => {
if (event.key === 'Enter') {
if (this.props.inputType === 'tags' || this.props.inputType === 'dropdown') {
// 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_) {
// 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);
}
@@ -246,9 +257,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" 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" 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)} />;
} else if (this.props.inputType === 'dropdown') {
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)} />;
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)} />;
} else {
inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
}

View File

@@ -27,7 +27,7 @@ import StyleSheetContainer from './StyleSheets/StyleSheetContainer';
import ImportScreen from './ImportScreen';
const { ResourceScreen } = require('./ResourceScreen.js');
import Navigator from './Navigator';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
import WelcomeUtils from '@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(this.props.dispatch);
await WelcomeUtils.install(Setting.value('locale'), this.props.dispatch);
}
private renderModalMessage(props: ModalDialogProps) {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.11.1",
"version": "2.11.2",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,
@@ -12,7 +12,7 @@
"electronRebuild": "gulp electronRebuild",
"tsc": "tsc --project tsconfig.json",
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json",
"start": "gulp build && electron . --env dev --log-level debug --no-welcome --open-dev-tools",
"start": "gulp build && electron . --env dev --log-level debug --open-dev-tools",
"test": "jest",
"test-ci": "yarn test"
},
@@ -116,7 +116,7 @@
"@types/react-redux": "7.1.25",
"@types/styled-components": "5.1.26",
"electron": "19.1.4",
"electron-builder": "23.6.0",
"electron-builder": "22.11.7",
"electron-notarize": "1.2.2",
"electron-rebuild": "3.2.9",
"glob": "8.1.0",
@@ -171,7 +171,7 @@
"roboto-fontface": "0.10.0",
"smalltalk": "2.5.1",
"sqlite3": "5.1.6",
"styled-components": "5.3.9",
"styled-components": "5.3.10",
"styled-system": "5.1.5",
"taboverride": "4.0.3",
"tinymce": "5.10.6"

View File

@@ -79,9 +79,13 @@ import org.apache.tools.ant.taskdefs.condition.Os
*/
project.ext.react = [
// 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
// 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
]
apply from: "../../node_modules/react-native/react.gradle"
@@ -152,8 +156,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097695
versionName "2.11.10"
versionCode 2097707
versionName "2.11.22"
// ndk {
// abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
// }
@@ -300,8 +304,11 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
// implementation "com.facebook.react:react-native:+" // From node_modules
implementation ("com.facebook.react:react-native") version {
strictly "0.70.6" // pass in your react-native version
}
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {

View File

@@ -751,6 +751,12 @@ 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} />

View File

@@ -290,6 +290,8 @@ 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)
@@ -398,8 +400,6 @@ 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,6 +472,7 @@ 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`)
@@ -500,7 +501,6 @@ 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,6 +576,8 @@ 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:
@@ -632,8 +634,6 @@ 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,6 +693,7 @@ 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
@@ -721,7 +722,6 @@ SPEC CHECKSUMS:
React-RCTVibration: c75ceef7aa60a33b2d5731ebe5800ddde40cefc4
React-runtimeexecutor: 15437b576139df27635400de0599d9844f1ab817
ReactCommon: 349be31adeecffc7986a0de875d7fb0dcf4e251c
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
RNDateTimePicker: 65e1d202799460b286ff5e741d8baf54695e8abd

View File

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

View File

@@ -5,7 +5,7 @@
"version": "2.11.0",
"private": true,
"scripts": {
"start": "react-native start --reset-cache",
"start": "BROWSERSLIST_IGNORE_OLD_DATA=true react-native start --reset-cache",
"android": "react-native run-android",
"build": "gulp build",
"tsc": "tsc --project tsconfig.json",
@@ -42,6 +42,7 @@
"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",
@@ -54,7 +55,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.5.2",
"react-native-paper": "5.6.0",
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-rsa-native": "2.0.5",
@@ -70,7 +71,6 @@
"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",

View File

@@ -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';
const WelcomeUtils = require('@joplin/lib/WelcomeUtils');
import WelcomeUtils from '@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(5 * 1000, { syncSteps: ['update_remote', 'delete_remote'] }, true);
if (!await reg.syncTarget().syncStarted()) void reg.scheduleSync(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(1000, null, true).then(() => {
void reg.scheduleSync(100, 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(dispatch);
await WelcomeUtils.install(Setting.value('locale'), dispatch);
// Collect revisions more frequently on mobile because it doesn't auto-save
// and it cannot collect anything when the app is not active.

View File

@@ -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 'rn-fetch-blob';
import RNFetchBlob from 'react-native-blob-util';
let dispatch_: Function = null;
export const setDispatch = (dispatch: Function) => {

View File

@@ -1,5 +1,5 @@
import FsDriverBase, { ReadDirStatsOptions } from '@joplin/lib/fs-driver-base';
const RNFetchBlob = require('rn-fetch-blob').default;
import RNFetchBlob, { Encoding as RNFetchBlobEncoding } from 'react-native-blob-util';
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);
return RNFetchBlob.fs.writeFile(path, content, encoding as RNFetchBlobEncoding);
}
// same as rm -rf

View File

@@ -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('rn-fetch-blob').default;
const RNFetchBlob = require('react-native-blob-util').default;
const { generateSecureRandom } = require('react-native-securerandom');
const FsDriverRN = require('./fs-driver-rn').default;
const { Buffer } = require('buffer');

View File

@@ -840,13 +840,8 @@ export default class BaseApplication {
}
if (Setting.value('firstStart')) {
// 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}`);
}
const locale = shim.detectAndSetLocale(Setting);
reg.logger().info(`First start: detected locale as ${locale}`);
Setting.skipDefaultMigrations();
if (Setting.value('env') === 'dev') {

View File

@@ -1,80 +0,0 @@
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;

View File

@@ -0,0 +1,122 @@
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;

View File

@@ -0,0 +1,18 @@
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);
}
});
});

View File

@@ -584,14 +584,7 @@ function localesFromLanguageCode(languageCode: string, locales: string[]): strin
}
function _(s: string, ...args: any[]): string {
const strings = localeStrings(currentLocale_);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
return stringByLocale(currentLocale_, s, ...args);
}
function _n(singular: string, plural: string, n: number, ...args: any[]) {
@@ -599,4 +592,15 @@ function _n(singular: string, plural: string, n: number, ...args: any[]) {
return _(singular, ...args);
}
const stringByLocale = (locale: string, s: string, ...args: any[]): string => {
const strings = localeStrings(locale);
let result = strings[s];
if (result === '' || result === undefined) result = s;
try {
return sprintf(result, ...args);
} catch (error) {
return `${result} ${args.join(', ')} (Translation error: ${error.message})`;
}
};
export { _, _n, supportedLocales, currentLocale, localesFromLanguageCode, languageCodeOnly, countryDisplayName, localeStrings, setLocale, supportedLocalesToLanguages, defaultLocale, closestSupportedLocale, languageCode, countryCodeOnly };

View File

@@ -1,4 +1,4 @@
import Setting, { SettingSectionSource, SettingStorage } from '../models/Setting';
import Setting, { SettingItemType, 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'));
await expectThrow(async () => Setting.value('itsgone'), 'unknown_key');
}));
it('should allow registering new settings dynamically', (async () => {
await expectThrow(async () => Setting.setValue('myCustom', '123'));
await expectThrow(async () => Setting.setValue('myCustom', '123'), 'unknown_key');
await Setting.registerSetting('myCustom', {
public: true,
@@ -297,12 +297,21 @@ describe('models/Setting', () => {
expect(Setting.isSet('spellChecker.languages')).toBe(false);
}));
it('should load sub-profile settings - 1', async () => {
it('should load sub-profile settings', 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();
@@ -311,6 +320,9 @@ 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');
@@ -318,7 +330,7 @@ describe('models/Setting', () => {
expect((await Setting.loadOne('sync.target')).value).toBe(undefined);
});
it('should save sub-profile settings - 2', async () => {
it('should save sub-profile settings', async () => {
await Setting.reset();
Setting.setValue('locale', 'fr_FR'); // Global setting
Setting.setValue('theme', Setting.THEME_DARK); // Global setting

View File

@@ -9,6 +9,7 @@ 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');
@@ -312,6 +313,7 @@ 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';
@@ -406,7 +408,7 @@ class Setting extends BaseModel {
return output;
};
this.metadata_ = {
this.buildInMetadata_ = {
'clientId': {
value: '',
type: SettingItemType.String,
@@ -1696,6 +1698,8 @@ 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_);
@@ -1709,6 +1713,10 @@ 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}`;
}
@@ -1810,7 +1818,7 @@ class Setting extends BaseModel {
public static settingMetadata(key: string): SettingItem {
const metadata = this.metadata();
if (!(key in metadata)) throw new Error(`Unknown key: ${key}`);
if (!(key in metadata)) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
const output = Object.assign({}, metadata[key]);
output.key = key;
return output;
@@ -2255,7 +2263,7 @@ class Setting extends BaseModel {
public static enumOptions(key: string) {
const metadata = this.metadata();
if (!metadata[key]) throw new Error(`Unknown key: ${key}`);
if (!metadata[key]) throw new JoplinError(`Unknown key: ${key}`, 'unknown_key');
if (!metadata[key].options) throw new Error(`No options for: ${key}`);
return metadata[key].options();
}

View File

@@ -2,19 +2,29 @@ import shim from './shim';
import time from './time';
const ntpClient_ = require('./vendor/ntp-client');
const server = {
domain: 'pool.ntp.org',
port: 123,
};
interface NtpServer {
domain: string;
port: number;
}
function ntpClient() {
ntpClient_.dgram = shim.dgram();
return ntpClient_;
}
export async function getNetworkTime(): Promise<Date> {
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> {
return new Promise((resolve: Function, reject: Function) => {
ntpClient().getNetworkTime(server.domain, server.port, (error: any, date: Date) => {
const s = parseNtpServer(ntpServer);
ntpClient().getNetworkTime(s.domain, s.port, (error: any, date: Date) => {
if (error) {
reject(error);
return;
@@ -25,7 +35,7 @@ export async function getNetworkTime(): Promise<Date> {
});
}
export async function getDeviceTimeDrift(): Promise<number> {
export async function getDeviceTimeDrift(ntpServer: string): Promise<number> {
const maxTries = 3;
let tryCount = 0;
@@ -34,12 +44,12 @@ export async function getDeviceTimeDrift(): Promise<number> {
while (true) {
tryCount++;
try {
ntpTime = await getNetworkTime();
ntpTime = await getNetworkTime(ntpServer);
break;
} catch (error) {
if (tryCount >= maxTries) {
const newError = typeof error === 'string' ? new Error(error) : error;
newError.message = `Cannot retrieve the network time from ${server.domain}:${server.port}: ${newError.message}`;
newError.message = `Cannot retrieve the network time from ${ntpServer}: ${newError.message}`;
throw newError;
} else {
await time.msleep(tryCount * 1000);

View File

@@ -1,20 +1,52 @@
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)) {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
delete output[k];
if (k in rootSettings) output[k] = rootSettings[k];
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)) {
const md = Setting.settingMetadata(k);
if (md.isGlobal) {
output[k] = rootSettings[k];
// 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}`);
}
}
}

View File

@@ -4,7 +4,7 @@ import { syncTargetName, afterAllCleanUp, synchronizerStart, setupDatabaseAndSyn
import Folder from '../../models/Folder';
import Note from '../../models/Note';
import BaseItem from '../../models/BaseItem';
const WelcomeUtils = require('../../WelcomeUtils');
import WelcomeUtils from '../../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();
await WelcomeUtils.createWelcomeItems('en_GB');
await synchronizerStart();
await switchClient(2);
await WelcomeUtils.createWelcomeItems();
await WelcomeUtils.createWelcomeItems('en_GB');
const beforeFolderCount = (await Folder.all()).length;
const beforeNoteCount = (await Note.all()).length;
expect(beforeFolderCount === 1).toBe(true);

View File

@@ -139,7 +139,7 @@ function shimInit(options = null) {
};
shim.detectAndSetLocale = function(Setting) {
let locale = process.env.LANG;
let locale = shim.isElectron() ? shim.electronBridge().getLocale() : process.env.LANG;
if (!locale) locale = defaultLocale();
locale = locale.split('.');
locale = locale[0];

File diff suppressed because one or more lines are too long

View File

@@ -44,6 +44,6 @@
"pdfjs-dist": "2.16.105",
"react": "18.2.0",
"react-dom": "18.2.0",
"styled-components": "5.3.9"
"styled-components": "5.3.10"
}
}

View File

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

View File

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

View File

@@ -1,22 +0,0 @@
# 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}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -144,6 +144,11 @@ class HtmlUtils {
.replace(/</g, '&lt;');
}
private isAcceptedUrl(url: string): boolean {
url = url.toLowerCase();
return url.startsWith('https://') || url.startsWith('http://') || url.startsWith('mailto://');
}
public sanitizeHtml(html: string, options: any = null) {
options = Object.assign({}, {
// If true, adds a "jop-noMdConv" class to all the tags.
@@ -182,8 +187,12 @@ class HtmlUtils {
const parser = new htmlparser2.Parser({
onopentag: (name: string, attrs: any) => {
tagStack.push(name.toLowerCase());
onopentag: (name: string, attrs: Record<string, string>) => {
// Note: "name" and attribute names are always lowercase even
// when the input is not. So there is no need to call
// "toLowerCase" on them.
tagStack.push(name);
if (disallowedTags.includes(currentTag())) {
disallowedTagDepth++;
@@ -202,11 +211,25 @@ class HtmlUtils {
// regular harmless attribute that starts with "on" will also
// be removed.
// 0: https://developer.mozilla.org/en-US/docs/Web/Events
for (const name in attrs) {
if (!attrs.hasOwnProperty(name)) continue;
if (name.length <= 2) continue;
if (name.toLowerCase().substr(0, 2) !== 'on') continue;
delete attrs[name];
for (const attrName in attrs) {
if (!attrs.hasOwnProperty(attrName)) continue;
if (attrName.length <= 2) continue;
if (attrName.substr(0, 2) !== 'on') continue;
delete attrs[attrName];
}
if (name === 'a') {
// Make sure that only non-acceptable URLs are filtered out.
// In particular we want to exclude `javascript:` URLs.
if ('href' in attrs && !this.isAcceptedUrl(attrs['href'])) {
attrs['href'] = '#';
}
// We need to clear any such attribute, otherwise it will
// make any arbitrary link open within the application.
if ('data-from-md' in attrs) {
delete attrs['data-from-md'];
}
}
if (options.addNoMdConvClass) {
@@ -222,7 +245,7 @@ class HtmlUtils {
// attribute. It doesn't always happen and it seems to depend on
// what else is in the note but in any case adding the "href"
// fixes it. https://github.com/laurent22/joplin/issues/5687
if (name.toLowerCase() === 'a' && !attrs['href']) {
if (name === 'a' && !attrs['href']) {
attrs['href'] = '#';
}
@@ -235,11 +258,18 @@ class HtmlUtils {
ontext: (decodedText: string) => {
if (disallowedTagDepth) return;
if (currentTag() === 'style') {
// For CSS, we have to put the style as-is inside the tag because if we html-entities encode
// it, it's not going to work. But it's ok because JavaScript won't run within the style tag.
// Ideally CSS should be loaded from an external file.
output.push(decodedText);
// For CSS, we have to put the style as-is inside the tag
// because if we html-entities encode it, it's not going to
// work. But it's ok because JavaScript won't run within the
// style tag. Ideally CSS should be loaded from an external
// file.
// We however have to encode at least the `<` characters to
// prevent certain XSS injections that would rely on the
// content not being encoded (see sanitize_13.md)
output.push(decodedText.replace(/</g, '&lt;'));
} else {
output.push(htmlentities(decodedText));
}

View File

@@ -255,7 +255,8 @@ async function main() {
appLogger().info(`Starting server v${config().appVersion} (${env}) on port ${config().port} and PID ${process.pid}...`);
if (config().maxTimeDrift) {
const timeDrift = await getDeviceTimeDrift();
appLogger().info(`Checking for time drift using NTP server: ${config().NTP_SERVER}`);
const timeDrift = await getDeviceTimeDrift(config().NTP_SERVER);
if (Math.abs(timeDrift) > config().maxTimeDrift) {
throw new Error(`The device time drift is ${timeDrift}ms (Max allowed: ${config().maxTimeDrift}ms) - cannot continue as it could cause data loss and conflicts on the sync clients. You may increase env var MAX_TIME_DRIFT to pass the check, or set to 0 to disabled the check.`);
}

View File

@@ -31,6 +31,7 @@ const defaultEnvValues: EnvVariables = {
// check. https://github.com/laurent22/joplin/issues/5738
MAX_TIME_DRIFT: 2000,
NTP_SERVER: 'pool.ntp.org:123',
// ==================================================
// URL config
@@ -109,6 +110,7 @@ export interface EnvVariables {
COOKIES_SECURE: boolean;
RUNNING_IN_DOCKER: boolean;
MAX_TIME_DRIFT: number;
NTP_SERVER: string;
APP_BASE_URL: string;
USER_CONTENT_BASE_URL: string;

View File

@@ -1,150 +0,0 @@
const fs = require('fs-extra');
const dirname = require('path').dirname;
const { fileExtension, basename } = require('@joplin/lib/path-utils');
const markdownUtils = require('@joplin/lib/markdownUtils').default;
const rootDir = dirname(dirname(__dirname));
const welcomeDir = `${rootDir}/readme/welcome`;
const createdDate = new Date('2018-06-22T12:00:00Z');
const itemMetadata_ = {
'1_welcome_to_joplin.md': {
id: '8a1556e382704160808e9a7bef7135d3',
// tags: 'markdown,organising',
},
'2_importing_and_exporting_notes.md': {
id: 'b863cbc514cb4cafbae8dd6a4fcad919',
// tags: 'importing,exporting',
},
'3_synchronising_your_notes.md': {
id: '25b656aac0564d1a91ab98295aa3cc58',
// tags: 'synchronising',
},
'4_tips.md': {
id: '2ee48f80889447429a3cccb04a466072',
// tags: 'attachment,search',
},
'5_privacy.md': {
id: '5ec2e7505ec2e7505ec2e7505ec2e750',
// tags: 'privacy',
},
'AllClients.png': { id: '5c05172554194f95b60971f6d577cc1a' },
'SubNotebooks.png': { id: '3a851ab0c0e849b7bc9e8cd5c4feb34a' },
'folder_Welcome': { id: '9bb5d498aba74cc6a047cfdc841e82a1' },
'WebClipper.png': { id: '30cf9214f5054c4da3b23eed7211a6e0' },
'markdown': { id: '79cc5ef0f6c24f138033ce48928c2cba' },
'organising': { id: 'c83be0495b5d4f1ab655c5c6dfed6804' },
'importing': { id: 'b5adb734bb0044f2a572a729266b610d' },
'exporting': { id: 'bed34e2e3ab74b45af8ba473a05f56f9' },
'synchronising': { id: 'c442fa3b2b2b4389b160c15eb73f35c9' },
'attachment': { id: '22c94167b6e94a92b560ffc31a7f4b1d' },
'search': { id: '83eae47427df4805905103d4a91727b7' },
};
function itemMetadata(path) {
const f = basename(path);
const md = itemMetadata_[f];
if (!md) throw new Error(`No metadata for: ${path}`);
return md;
}
function noteTags(path) {
const md = itemMetadata(path);
if (!md.tags) return [];
return md.tags.split(',');
}
function itemIdFromPath(path) {
const md = itemMetadata(path);
if (!md.id) throw new Error(`No ID for ${path}`);
return md.id;
}
function fileToBase64(filePath) {
const content = fs.readFileSync(filePath);
return Buffer.from(content).toString('base64');
}
async function parseNoteFile(filePath) {
const n = basename(filePath);
const number = n.split('_')[0];
const body = fs.readFileSync(filePath, 'utf8');
const title = `${number}. ${body.split('\n')[0].substr(2)}`;
const resources = {};
const imagePaths = markdownUtils.extractImageUrls(body);
for (let i = 0; i < imagePaths.length; i++) {
const imagePath = imagePaths[i];
const fullImagePath = `${welcomeDir}/${imagePath}`;
const base64 = fileToBase64(fullImagePath);
resources[imagePath] = {
id: itemIdFromPath(fullImagePath),
body: base64,
};
}
return {
id: itemIdFromPath(filePath),
title: title,
body: body,
tags: noteTags(filePath),
resources: resources,
};
}
async function main() {
const notes = [];
const tagIdsToTag = {};
const filenames = fs.readdirSync(welcomeDir);
const rootFolder = {
id: itemIdFromPath('folder_Welcome'),
title: 'Welcome!',
};
for (let i = 0; i < filenames.length; i++) {
const f = filenames[i];
const ext = fileExtension(f);
if (ext === 'md') {
const note = await parseNoteFile(`${welcomeDir}/${f}`);
note.parent_id = rootFolder.id;
for (let j = 0; j < note.tags.length; j++) {
const tagTitle = note.tags[j];
const tagId = itemIdFromPath(tagTitle);
if (!tagIdsToTag[tagId]) {
tagIdsToTag[tagId] = {
id: tagId,
title: tagTitle,
};
}
}
notes.push(note);
}
}
const tags = [];
for (const n in tagIdsToTag) {
if (!tagIdsToTag.hasOwnProperty(n)) continue;
tags.push(tagIdsToTag[n]);
}
const folders = [];
folders.push(rootFolder);
const content = { notes: notes, folders: folders, tags: tags, timestamp: createdDate.getTime() };
const jsonContent = JSON.stringify(content, null, 4);
const jsContent = `module.exports = ${jsonContent}`;
fs.writeFileSync(`${rootDir}/packages/lib/welcomeAssets.js`, jsContent, { encoding: 'utf8' });
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -0,0 +1,162 @@
import { readFileSync, readdirSync, writeFileSync } from 'fs-extra';
import { dirname } from 'path';
import { fileExtension, basename } from '@joplin/lib/path-utils';
import markdownUtils from '@joplin/lib/markdownUtils';
import { AssetContent, ItemMetadata, WelcomeAssetNote, WelcomeAssetResource, WelcomeAssets } from '@joplin/lib/WelcomeUtils';
const rootDir = dirname(dirname(__dirname));
const enWelcomeDir = `${rootDir}/readme/welcome`;
const createdDate = new Date('2018-06-22T12:00:00Z');
const itemMetadata_: ItemMetadata = {
'1_welcome_to_joplin.md': {
id: '8a1556e382704160808e9a7bef7135d3',
},
'2_importing_and_exporting_notes.md': {
id: 'b863cbc514cb4cafbae8dd6a4fcad919',
},
'3_synchronising_your_notes.md': {
id: '25b656aac0564d1a91ab98295aa3cc58',
},
'4_tips.md': {
id: '2ee48f80889447429a3cccb04a466072',
},
'5_privacy.md': {
id: '5ec2e7505ec2e7505ec2e7505ec2e750',
},
'AllClients.png': { id: '5c05172554194f95b60971f6d577cc1a' },
'SubNotebooks.png': { id: '3a851ab0c0e849b7bc9e8cd5c4feb34a' },
'folder_Welcome': { id: '9bb5d498aba74cc6a047cfdc841e82a1' },
'WebClipper.png': { id: '30cf9214f5054c4da3b23eed7211a6e0' },
'markdown': { id: '79cc5ef0f6c24f138033ce48928c2cba' },
'organising': { id: 'c83be0495b5d4f1ab655c5c6dfed6804' },
'importing': { id: 'b5adb734bb0044f2a572a729266b610d' },
'exporting': { id: 'bed34e2e3ab74b45af8ba473a05f56f9' },
'synchronising': { id: 'c442fa3b2b2b4389b160c15eb73f35c9' },
'attachment': { id: '22c94167b6e94a92b560ffc31a7f4b1d' },
'search': { id: '83eae47427df4805905103d4a91727b7' },
};
const allMetadata_: Record<string, ItemMetadata> = {};
allMetadata_['en_GB'] = itemMetadata_;
allMetadata_['fr_FR'] = {
...itemMetadata_,
'1_welcome_to_joplin.md': {
id: '223a99e0dad4c8882988f446815ea28c',
},
'2_importing_and_exporting_notes.md': {
id: '21648b1b1b541e7bb87cff262bcc6b54',
},
'3_synchronising_your_notes.md': {
id: '3adfa574c0264f68f4c33c4133e734fb',
},
'4_tips.md': {
id: '4d0ffc5beb024e6c498129ad814d156e',
},
'5_privacy.md': {
id: '69f9b160ddb50a954157716e3d916c68',
},
'folder_Welcome': { id: '5494e8c3dcfc84c1549ed22fb3a89265' },
};
const getWelcomeDir = (locale: string) => {
if (locale === 'en_GB') return enWelcomeDir;
return `${rootDir}/readme/_i18n/${locale}/welcome`;
};
function itemMetadata(metadata: ItemMetadata, path: string) {
const f = basename(path);
const md = metadata[f];
if (!md) throw new Error(`No metadata for: ${path}`);
return md;
}
function itemIdFromPath(metadata: ItemMetadata, path: string) {
const md = itemMetadata(metadata, path);
if (!md.id) throw new Error(`No ID for ${path}`);
return md.id;
}
function fileToBase64(filePath: string) {
const content = readFileSync(filePath);
return Buffer.from(content).toString('base64');
}
function parseNoteFile(metadata: ItemMetadata, locale: string, filePath: string): WelcomeAssetNote {
const n = basename(filePath);
const number = n.split('_')[0];
const body = readFileSync(filePath, 'utf8');
const title = `${number}. ${body.split('\n')[0].substr(2)}`;
const resources: Record<string, WelcomeAssetResource> = {};
if (locale === 'en_GB') {
const imagePaths = markdownUtils.extractImageUrls(body);
for (let i = 0; i < imagePaths.length; i++) {
const imagePath = imagePaths[i];
const fullImagePath = `${enWelcomeDir}/${imagePath}`;
const base64 = fileToBase64(fullImagePath);
resources[imagePath] = {
id: itemIdFromPath(metadata, fullImagePath),
body: base64,
};
}
}
return {
id: itemIdFromPath(metadata, filePath),
title: title,
body: body,
resources: resources,
parent_id: '',
};
}
async function main() {
const supportedLocales = ['en_GB', 'fr_FR'];
const allContent: WelcomeAssets = {};
for (const locale of supportedLocales) {
const metadata = allMetadata_[locale];
const welcomeDir = getWelcomeDir(locale);
const notes = [];
const filenames = readdirSync(welcomeDir);
const rootFolder = {
id: itemIdFromPath(metadata, 'folder_Welcome'),
title: '',
};
for (let i = 0; i < filenames.length; i++) {
const f = filenames[i];
const ext = fileExtension(f);
if (ext === 'md') {
const note = await parseNoteFile(metadata, locale, `${welcomeDir}/${f}`);
note.parent_id = rootFolder.id;
notes.push(note);
}
}
const folders = [];
folders.push(rootFolder);
const content: AssetContent = { notes: notes, folders: folders, timestamp: createdDate.getTime() };
allContent[locale] = content;
}
const jsonContent = JSON.stringify(allContent, null, 4);
const jsContent = `module.exports = ${jsonContent}`;
writeFileSync(`${rootDir}/packages/lib/welcomeAssets.js`, jsContent, { encoding: 'utf8' });
}
main().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@@ -7,7 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Dmitriy Q <krotesk@mail.ru>\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Dmitriy K <dmitry@atsip.ru>\n"
"Language-Team: Sergey Segeda <thesermanarm@gmail.com>\n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
@@ -98,7 +100,7 @@ msgstr "%d ГБ"
#: packages/lib/utils/joplinCloud.ts:133 packages/lib/utils/joplinCloud.ts:134
#: packages/lib/utils/joplinCloud.ts:135
msgid "%d GB storage space"
msgstr "storage space %d ГБ"
msgstr "%d ГБ дискового пространства"
#: packages/lib/models/Setting.ts:1356
msgid "%d hour"
@@ -129,9 +131,8 @@ msgstr "%d заметок соответствуют указанному выр
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:254
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:263
#, fuzzy
msgid "%s"
msgstr "(%s)"
msgstr "%s"
#: packages/app-desktop/gui/utils/NoteListUtils.ts:61
msgid "%s - Copy"
@@ -392,6 +393,8 @@ msgid ""
"Are you sure you want to return to the default layout? The current layout "
"configuration will be lost."
msgstr ""
"Вы уверены, что хотите вернуться к макету по умолчанию? Текущая конфигурация "
"макета будет потеряна."
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:517
msgid "Arguments:"
@@ -505,6 +508,9 @@ msgid ""
"unlock Joplin. If the device is on lockout, consider switching it off and on "
"to reset biometrics scanning."
msgstr ""
"Биометрическая разблокировка не настроена на устройстве. Пожалуйста, "
"настройте ее, чтобы разблокировать Joplin. Если устройство заблокировано - "
"выключите и включите его, чтобы сбросить сканирование биометрии."
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:33
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:132
@@ -610,9 +616,8 @@ msgid "Cannot find \"%s\"."
msgstr "Не удалось найти \"%s\"."
#: packages/app-cli/app/command-mkbook.ts:28
#, fuzzy
msgid "Cannot find: \"%s\""
msgstr "Не удалось найти \"%s\"."
msgstr "Не удалось найти \"%s\""
#: packages/app-cli/app/command-sync.ts:164
msgid "Cannot initialise synchroniser."
@@ -830,7 +835,7 @@ msgstr "Расшифровка завершена."
#: packages/lib/Synchronizer.ts:190
msgid "Completed: %s (%s)"
msgstr "Завершено: %s"
msgstr "Завершено: %s (%s)"
#: packages/server/src/services/TaskService.ts:26
msgid "Compress old changes"
@@ -889,9 +894,8 @@ msgid "Convert to todo"
msgstr "Преобразовать в задачу"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:69
#, fuzzy
msgid "Converting speech to text..."
msgstr "Преобразовать в заметку"
msgstr "Преобразование речи в текст..."
#: packages/app-desktop/gui/MenuBar.tsx:495
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:13
@@ -1004,9 +1008,8 @@ msgstr ""
"Пожалуйста, повторите попытку при подключении к Интернету."
#: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:20
#, fuzzy
msgid "Could not verify your identify: %s"
msgstr "Не удалось проверить вашу личность"
msgstr "Не удалось проверить вашу личность: %s"
#: packages/app-desktop/gui/PromptDialog.min.js:235
#: packages/app-desktop/gui/PromptDialog.tsx:260
@@ -1014,9 +1017,8 @@ msgid "Create"
msgstr "Создать"
#: packages/app-cli/app/command-mkbook.ts:19
#, fuzzy
msgid "Create a new notebook under a parent notebook."
msgstr "Создает новый блокнот."
msgstr "Создать новый блокнот под родительским блокнотом."
#: packages/app-mobile/components/NoteList.tsx:98
msgid "Create a notebook"
@@ -1476,7 +1478,7 @@ msgid ""
"specified the note is duplicated in the current notebook."
msgstr ""
"Дублирует заметки, соответствующие выражению <note>, в блокнот [notebook]. "
"Если блокнот не указан, заметки дублируются в текущем блокноте."
"Если блокнот не указан - заметки дублируются в текущем блокноте."
#: packages/app-desktop/gui/MainScreen/commands/openFolderDialog.ts:12
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/openEditDialog.ts:84
@@ -1649,16 +1651,15 @@ msgstr "Включить мягкие отступы"
#: packages/lib/models/Setting.ts:1048
msgid "Enable spellcheck in the text editor"
msgstr ""
msgstr "Включить проверку орфографии в текстовом редакторе"
#: packages/lib/models/Setting.ts:1154
msgid "Enable table of contents extension"
msgstr "Включить расширение поддержки оглавления"
#: packages/lib/models/Setting.ts:1059
#, fuzzy
msgid "Enable the Markdown toolbar"
msgstr "Включить эмодзи markdown"
msgstr "Включить панель инструментов Markdown"
#: packages/lib/models/Setting.ts:1142
msgid "Enable typographer support"
@@ -2320,7 +2321,7 @@ msgstr "Элементы, которые не могут быть синхрон
#: packages/app-desktop/gui/MenuBar.tsx:792
msgid "Join us on Twitter"
msgstr ""
msgstr "Присоединяйтесь к нам в Twitter"
#: packages/app-desktop/gui/SyncWizard/Dialog.tsx:330
msgid ""
@@ -2513,9 +2514,8 @@ msgid "Loaded"
msgstr "Загружено"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:67
#, fuzzy
msgid "Loading..."
msgstr "Обновление..."
msgstr "Загрузка..."
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:32
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:52
@@ -2668,9 +2668,8 @@ msgid "Missing required argument: %s"
msgstr "Отсутствует обязательный аргумент: %s"
#: packages/app-cli/app/cli-utils.js:135
#, fuzzy
msgid "Missing required flag value: %s"
msgstr "Отсутствует обязательный аргумент: %s"
msgstr "Отсутствует необходимое значение флага: %s"
#: packages/app-mobile/components/side-menu-content.tsx:457
msgid "Mobile data - auto-sync disabled"
@@ -3123,7 +3122,7 @@ msgstr "Вставить"
#: packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts:6
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:181
msgid "Paste as text"
msgstr ""
msgstr "Вставить как текст"
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:541
msgid "Path:"
@@ -3193,7 +3192,7 @@ msgstr ""
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:68
msgid "Please record your voice..."
msgstr ""
msgstr "Пожалуйста, запишите свой голос..."
#: packages/app-cli/app/command-ls.js:63
msgid "Please select a notebook first."
@@ -3495,9 +3494,8 @@ msgid "Replace: "
msgstr "Замена: "
#: packages/app-desktop/gui/MainScreen/commands/resetLayout.ts:7
#, fuzzy
msgid "Reset application layout"
msgstr "Изменить расположение элементов"
msgstr "Сброс макета приложения"
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:219
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:220
@@ -3679,7 +3677,7 @@ msgstr "Выбрать все"
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:141
msgid "Select emoji..."
msgstr "Поиск эмодзи..."
msgstr "Выбрать эмодзи..."
#: packages/app-desktop/gui/EditFolderDialog/Dialog.tsx:145
msgid "Select file..."
@@ -4130,7 +4128,7 @@ msgstr "Синхронизация..."
#: packages/lib/models/Setting.ts:1384
msgid "Tabloid"
msgstr "Tabloid"
msgstr "Таблоид"
#: packages/app-mobile/components/screens/NoteTagsDialog.js:182
msgid "tag1, tag2, ..."
@@ -4400,6 +4398,8 @@ msgid ""
"The WebDAV implementation of %s is incompatible with Joplin, and as such is "
"no longer supported. Please use a different sync method."
msgstr ""
"Реализация WebDAV для %s несовместима с Joplin и поэтому больше не "
"поддерживается. Пожалуйста, используйте другой метод синхронизации."
#: packages/lib/models/Setting.ts:827
msgid "Theme"
@@ -4985,11 +4985,11 @@ msgstr "Vim"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:92
msgid "Voice typing"
msgstr ""
msgstr "Голосовой набор"
#: packages/app-mobile/components/screens/Note.tsx:993
msgid "Voice typing..."
msgstr ""
msgstr "Голосовой набор..."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:83
msgid "Warning"

View File

@@ -15,6 +15,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:684
msgid "- Camera: to allow taking a picture and attaching it to a note."
@@ -214,11 +216,11 @@ msgid ""
"target is a regular note it will be converted to a to-do). Use \"clear\" to "
"convert the to-do back to a regular note."
msgstr ""
"<todo-command> kan antingen vara \"toggle\" eller \"clear\". Använd "
"\"toggle\" för att växla mellan givna uppgifter mellan slutförda och inte "
"slutförda tillstånd (Om målet är en vanlig anteckning kommer den att "
"konverteras till en att-göra). Använd \"clear\" för att konvertera uppgiften "
"att-göra tillbaka till en vanlig anteckning."
"<todo-command> kan antingen vara \"toggle\" eller \"clear\". Använd \"toggle"
"\" för att växla mellan givna uppgifter mellan slutförda och inte slutförda "
"tillstånd (Om målet är en vanlig anteckning kommer den att konverteras till "
"en att-göra). Använd \"clear\" för att konvertera uppgiften att-göra "
"tillbaka till en vanlig anteckning."
#: packages/lib/models/Setting.ts:1382
msgid "A3"
@@ -362,8 +364,8 @@ msgstr ""
#: packages/app-cli/app/command-mkbook.ts:33
#: packages/app-cli/app/command-mv.js:29
msgid ""
"Ambiguous notebook \"%s\". Please use short notebook id instead - press "
"\"ti\" to see the short notebook id"
"Ambiguous notebook \"%s\". Please use short notebook id instead - press \"ti"
"\" to see the short notebook id"
msgstr ""
"Tvetydig anteckningsbok \"%s\". Använd kort anteckningsbok-id istället - "
"tryck på \"ti\" för att se det korta anteckningsbok-id:t"
@@ -507,6 +509,9 @@ msgid ""
"unlock Joplin. If the device is on lockout, consider switching it off and on "
"to reset biometrics scanning."
msgstr ""
"Biometrisk upplåsning är inte inställd på enheten. Ställ in det för att låsa "
"upp Joplin. Om enheten är låst, överväg att stänga av och på den för att "
"återställa biometrisk skanning."
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:33
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:132
@@ -894,9 +899,8 @@ msgid "Convert to todo"
msgstr "Konvertera till att-göra"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:69
#, fuzzy
msgid "Converting speech to text..."
msgstr "Konvertera till anteckning"
msgstr "Konverterar tal till text..."
#: packages/app-desktop/gui/MenuBar.tsx:495
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:13
@@ -1009,9 +1013,8 @@ msgstr ""
"avbryter. Försök igen när du är ansluten till internet."
#: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:20
#, fuzzy
msgid "Could not verify your identify: %s"
msgstr "Det gick inte att verifiera din identitet"
msgstr "Det gick inte att verifiera din identitet: %s"
#: packages/app-desktop/gui/PromptDialog.min.js:235
#: packages/app-desktop/gui/PromptDialog.tsx:260
@@ -1663,9 +1666,8 @@ msgid "Enable table of contents extension"
msgstr "Aktivera innehållsförteckningen"
#: packages/lib/models/Setting.ts:1059
#, fuzzy
msgid "Enable the Markdown toolbar"
msgstr "Aktivera markdown-emoji"
msgstr "Aktivera Markdown-verktygsfältet"
#: packages/lib/models/Setting.ts:1142
msgid "Enable typographer support"
@@ -2518,9 +2520,8 @@ msgid "Loaded"
msgstr "Inläst"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:67
#, fuzzy
msgid "Loading..."
msgstr "Uppdaterar..."
msgstr "Läser in..."
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:32
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:52
@@ -2673,7 +2674,7 @@ msgstr "Saknade obligatoriskt argument: %s"
#: packages/app-cli/app/cli-utils.js:135
msgid "Missing required flag value: %s"
msgstr ""
msgstr "Obligatoriskt flaggvärde saknas: %s"
#: packages/app-mobile/components/side-menu-content.tsx:457
msgid "Mobile data - auto-sync disabled"
@@ -3150,8 +3151,8 @@ msgid ""
"Please click on \"%s\" to proceed, or set the passwords in the \"%s\" list "
"below."
msgstr ""
"Klicka på \"%s\" för att fortsätta, eller ställ in lösenorden i listan "
"\"%s\" nedan."
"Klicka på \"%s\" för att fortsätta, eller ställ in lösenorden i listan \"%s"
"\" nedan."
#: packages/lib/components/EncryptionConfigScreen/utils.ts:64
msgid ""
@@ -3189,7 +3190,7 @@ msgstr ""
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:68
msgid "Please record your voice..."
msgstr ""
msgstr "Spela in din röst..."
#: packages/app-cli/app/command-ls.js:63
msgid "Please select a notebook first."
@@ -4023,7 +4024,7 @@ msgstr "Byt till anteckningstyp"
#: packages/app-desktop/commands/switchProfile2.ts:7
#: packages/app-desktop/commands/switchProfile3.ts:7
msgid "Switch to profile %d"
msgstr "Byt till profilen %d"
msgstr "Byt till profil %d"
#: packages/app-desktop/gui/utils/NoteListUtils.ts:114
msgid "Switch to to-do type"
@@ -4976,11 +4977,11 @@ msgstr "Vim"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:92
msgid "Voice typing"
msgstr ""
msgstr "Röstskrivning"
#: packages/app-mobile/components/screens/Note.tsx:993
msgid "Voice typing..."
msgstr ""
msgstr "Röstskrivning..."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:83
msgid "Warning"

View File

@@ -7,6 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Türkçe\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: Arda Kılıçdağı <arda@kilicdagi.com>\n"
"Language-Team: Turkish (Turkey)\n"
"Language: tr_TR\n"
@@ -14,7 +16,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.1\n"
"X-Generator: Poedit 3.3\n"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:684
msgid "- Camera: to allow taking a picture and attaching it to a note."
@@ -44,7 +46,7 @@ msgstr "(%s)"
#: packages/lib/services/plugins/api/JoplinViewsDialogs.ts:71
msgid "(In plugin: %s)"
msgstr ""
msgstr "(Şu eklentide: %s)"
#: packages/lib/SyncTargetNone.ts:16
msgid "(None)"
@@ -92,12 +94,12 @@ msgstr "%d gün"
#: packages/lib/utils/joplinCloud.ts:136 packages/lib/utils/joplinCloud.ts:137
#: packages/lib/utils/joplinCloud.ts:138
msgid "%d GB"
msgstr ""
msgstr "%d GB"
#: packages/lib/utils/joplinCloud.ts:133 packages/lib/utils/joplinCloud.ts:134
#: packages/lib/utils/joplinCloud.ts:135
msgid "%d GB storage space"
msgstr ""
msgstr "%d GB depolama alanı"
#: packages/lib/models/Setting.ts:1356
msgid "%d hour"
@@ -110,13 +112,12 @@ msgstr "%d saat"
#: packages/lib/utils/joplinCloud.ts:124 packages/lib/utils/joplinCloud.ts:125
#: packages/lib/utils/joplinCloud.ts:126
msgid "%d MB"
msgstr ""
msgstr "%d MB"
#: packages/lib/utils/joplinCloud.ts:121 packages/lib/utils/joplinCloud.ts:122
#: packages/lib/utils/joplinCloud.ts:123
#, fuzzy
msgid "%d MB per note or attachment"
msgstr "Ek Dosyalar"
msgstr "Not veya ek dosya başına %d MB"
#: packages/lib/models/Setting.ts:1353 packages/lib/models/Setting.ts:1354
#: packages/lib/models/Setting.ts:1355
@@ -129,9 +130,8 @@ msgstr "%d not eşleşti. Silinsin mi?"
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:254
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:263
#, fuzzy
msgid "%s"
msgstr "(%s)"
msgstr "%s"
#: packages/app-desktop/gui/utils/NoteListUtils.ts:61
msgid "%s - Copy"
@@ -259,7 +259,7 @@ msgstr "Kabul Et"
#: packages/server/src/routes/admin/users.ts:138
msgid "Account"
msgstr ""
msgstr "Hesap"
#: packages/app-desktop/gui/ResourceScreen.tsx:95
msgid "Action"
@@ -284,9 +284,8 @@ msgid "Add body"
msgstr "Gövde Ekle"
#: packages/app-mobile/components/ActionButton.tsx:59
#, fuzzy
msgid "Add new"
msgstr "Başlık ekle"
msgstr "Yeni bir şey ekle: "
#: packages/app-desktop/gui/MainScreen/commands/setTags.ts:38
msgid "Add or remove tags:"
@@ -325,6 +324,8 @@ msgstr "Gelişmiş araçlar"
msgid ""
"All data, including notes, notebooks and tags will be permanently deleted."
msgstr ""
"Not defterleri, etiketler ve notlar da dahil olmak üzere tüm veriler kalıcı "
"olarak silinecek."
#: packages/app-desktop/gui/Sidebar/Sidebar.tsx:504
#: packages/app-mobile/components/screens/Notes.tsx:174
@@ -355,6 +356,9 @@ msgid ""
"Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to "
"see the short notebook id or use $b for current selected notebook"
msgstr ""
"\"%s\" not defteri belirlenemedi. Lütfen not defteri id'lerini kulllanın. "
"\"ti\" a basarak not defterinin kısa id'sini görebilir, veya $b ile mevcut "
"seçili not defteri hakkında bilgiye erişebilirsiniz"
#: packages/app-cli/app/command-mkbook.ts:33
#: packages/app-cli/app/command-mv.js:29
@@ -362,6 +366,8 @@ msgid ""
"Ambiguous notebook \"%s\". Please use short notebook id instead - press "
"\"ti\" to see the short notebook id"
msgstr ""
"\"%s\" not defteri belirlenemedi. Lütfen kısa not defteri id'sini kullanın. "
"\"ti\" a basarak kısa not defteri id'sini görebilirsiniz"
#: packages/app-desktop/checkForUpdates.ts:197
msgid "An update is available, do you want to download it now?"
@@ -388,6 +394,8 @@ msgid ""
"Are you sure you want to return to the default layout? The current layout "
"configuration will be lost."
msgstr ""
"Standart uygulama görünümüne geri dönmek istediğinizden emin misiniz? Tüm "
"kişiselleştirilmiş mevcut uygulama görünümü (layout) kaybolacak."
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:517
msgid "Arguments:"
@@ -398,9 +406,8 @@ msgid "Aritim Dark"
msgstr "Aritim Dark"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:206
#, fuzzy
msgid "Attach"
msgstr "Ekle..."
msgstr "Ekle"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:53
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:594
@@ -493,7 +500,7 @@ msgstr "Geri"
#: packages/lib/utils/joplinCloud.ts:294
msgid "Basic"
msgstr ""
msgstr "Temel"
#: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:17
msgid ""
@@ -501,6 +508,9 @@ msgid ""
"unlock Joplin. If the device is on lockout, consider switching it off and on "
"to reset biometrics scanning."
msgstr ""
"Bu cihazda biyometrik kilit açma etkineltirilmemiş. Lütfen Joplin'i açmak "
"için bunu ayarlayın. Eğer cihazda bunu yapamıyorsanız ayarı açıp kapatarak "
"biyometrik doğrulamayı sıfırlayabilirsiniz."
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:33
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:132
@@ -521,9 +531,8 @@ msgid "Bulleted List"
msgstr "Maddeli liste"
#: packages/server/src/routes/admin/users.ts:154
#, fuzzy
msgid "Can Share"
msgstr "Paylaş"
msgstr "Paylaşılabilir"
#: packages/app-desktop/bridge.ts:196 packages/app-desktop/bridge.ts:217
#: packages/app-desktop/checkForUpdates.ts:199
@@ -607,9 +616,8 @@ msgid "Cannot find \"%s\"."
msgstr "\"%s\" bulunamıyor."
#: packages/app-cli/app/command-mkbook.ts:28
#, fuzzy
msgid "Cannot find: \"%s\""
msgstr "\"%s\" bulunamıyor."
msgstr "\"%s\" bulunamadı"
#: packages/app-cli/app/command-sync.ts:164
msgid "Cannot initialise synchroniser."
@@ -662,7 +670,7 @@ msgstr ""
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:328
msgid "Case sensitive"
msgstr ""
msgstr "Büyük küçük harf duyarlı"
#: packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.ts:7
msgid "Change application layout"
@@ -752,9 +760,8 @@ msgid "Close"
msgstr "Kapat"
#: packages/app-mobile/components/Dropdown.tsx:166
#, fuzzy
msgid "Close dropdown"
msgstr "Pencereyi Kapat"
msgstr "Açılır menüyü kapat"
#: packages/app-desktop/gui/KeymapConfig/utils/getLabel.ts:24
#: packages/app-desktop/gui/MenuBar.tsx:596
@@ -780,7 +787,7 @@ msgstr "Başkalarıyla işbirliği halinde not defterlerini ortak yönetin"
#: packages/app-mobile/components/side-menu-content.tsx:347
msgid "Collapse"
msgstr ""
msgstr "Daralt"
#: packages/lib/services/ReportService.ts:307
msgid "Coming alarms"
@@ -866,7 +873,7 @@ msgstr "Çakışmalar (ek dosyalar)"
#: packages/lib/utils/joplinCloud.ts:171
msgid "Consolidated billing"
msgstr ""
msgstr "Birleştirilmiş faturalandırma"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/SearchPlugins.tsx:110
msgid "Content provided by %s"
@@ -874,7 +881,7 @@ msgstr "İçerik %s tarafından sağlandı"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:64
msgid "Continue"
msgstr ""
msgstr "Devam et"
#: packages/app-mobile/components/screens/Note.tsx:1010
msgid "Convert to note"
@@ -885,9 +892,8 @@ msgid "Convert to todo"
msgstr "Yapılacak listesi olarak çevir"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:69
#, fuzzy
msgid "Converting speech to text..."
msgstr "Nota çevir"
msgstr "Ses metne dönüştürülüyor..."
#: packages/app-desktop/gui/MenuBar.tsx:495
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:13
@@ -984,9 +990,8 @@ msgstr ""
"Hata mesajı: \"%s\""
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:55
#, fuzzy
msgid "Could not switch profile: %s"
msgstr "Eklenti yüklenemedi: %s"
msgstr "Profil değiştirilemedi: %s"
#: packages/lib/components/EncryptionConfigScreen/utils.ts:219
msgid "Could not upgrade master key: %s"
@@ -1001,9 +1006,8 @@ msgstr ""
"ediliyor. Lütfen internet bağlantınız varken bu işlemi yeniden deneyin."
#: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:20
#, fuzzy
msgid "Could not verify your identify: %s"
msgstr "Notlar dışarı aktarılamadı: %s"
msgstr "Kimliğiniz doğrulanamadı: %s"
#: packages/app-desktop/gui/PromptDialog.min.js:235
#: packages/app-desktop/gui/PromptDialog.tsx:260
@@ -1011,9 +1015,8 @@ msgid "Create"
msgstr "Oluştur"
#: packages/app-cli/app/command-mkbook.ts:19
#, fuzzy
msgid "Create a new notebook under a parent notebook."
msgstr "Yeni bir not defteri oluşturur."
msgstr "Ana not defteri altında yeni bir not defteri oluştur."
#: packages/app-mobile/components/NoteList.tsx:98
msgid "Create a notebook"
@@ -1144,7 +1147,7 @@ msgstr "gün"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:109
msgid "Decrease indent level"
msgstr ""
msgstr "Girintileme derecesini düşür"
#: packages/app-cli/app/command-e2ee.ts:64
msgid "Decrypted items: %d"
@@ -1235,14 +1238,12 @@ msgid "Delete plugin \"%s\"?"
msgstr "\"%s\" eklentisi silinsin mi?"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:101
#, fuzzy
msgid "Delete profile \"%s\""
msgstr "\"%s\" notu silinsin mi?"
msgstr "\"%s\" profilini sil"
#: packages/app-mobile/components/ScreenHeader.tsx:420
#, fuzzy
msgid "Delete selected notes"
msgstr "Bu notlar silinsin mi?"
msgstr "Seçili notları sil"
#: packages/lib/models/Note.ts:773
msgid "Delete these %d notes?"
@@ -1255,9 +1256,8 @@ msgid ""
msgstr "Bu davetiye kaldırılsın mi? Alıcı paylaşılan nota artık erişemeyecek."
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:97
#, fuzzy
msgid "Delete this profile?"
msgstr "Bu %d not silinsin mi?"
msgstr "Bu profil silinsin mi?"
#: packages/lib/Synchronizer.ts:186
msgid "Deleted local items: %d."
@@ -1409,7 +1409,7 @@ msgstr ""
#: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:149
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:81
msgid "Done"
msgstr ""
msgstr "Tamam"
#: packages/app-desktop/checkForUpdates.ts:199
msgid "Download"
@@ -1466,9 +1466,8 @@ msgid "Duplicate line"
msgstr "Satırı Kopyala"
#: packages/app-mobile/components/ScreenHeader.tsx:438
#, fuzzy
msgid "Duplicate selected notes"
msgstr "Satırı Kopyala"
msgstr "Seçili notları klonla"
#: packages/app-cli/app/command-cp.js:13
msgid ""
@@ -1490,9 +1489,8 @@ msgid "Edit in external editor"
msgstr "Harici editörde düzenle"
#: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:141
#, fuzzy
msgid "Edit link"
msgstr "Not defterini düzenle"
msgstr "Link'i düzenle"
#: packages/app-cli/app/command-edit.js:17
msgid "Edit note."
@@ -1504,9 +1502,8 @@ msgid "Edit notebook"
msgstr "Not defterini düzenle"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:87
#, fuzzy
msgid "Edit profile"
msgstr "Profili dışarı aktar"
msgstr "Profili düzenle"
#: packages/app-desktop/commands/editProfileConfig.ts:9
msgid "Edit profile configuration..."
@@ -1598,7 +1595,7 @@ msgstr "Ses / Müzik oynatıcıyı aktifleştir"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:81
msgid "Enable biometrics authentication?"
msgstr ""
msgstr "Biyometrik doğrulama kullanılsın mı?"
#: packages/lib/models/Setting.ts:1157
msgid "Enable deflist syntax"
@@ -1651,16 +1648,15 @@ msgstr "Kelime kaydırmasını etkinleştir"
#: packages/lib/models/Setting.ts:1048
msgid "Enable spellcheck in the text editor"
msgstr ""
msgstr "Metin düzenleyicide yazım doğrulamasını aktifleştir"
#: packages/lib/models/Setting.ts:1154
msgid "Enable table of contents extension"
msgstr "Tablo içerik eklentisini etkinleştir"
#: packages/lib/models/Setting.ts:1059
#, fuzzy
msgid "Enable the Markdown toolbar"
msgstr "Markdown emoji desteğini etkinleştir"
msgstr "Markdown araç çubuğunu etkinleştir"
#: packages/lib/models/Setting.ts:1142
msgid "Enable typographer support"
@@ -1786,7 +1782,7 @@ msgstr "Uygulamadan çıkar."
#: packages/app-mobile/components/side-menu-content.tsx:347
msgid "Expand"
msgstr ""
msgstr "Genişlet"
#: packages/app-desktop/gui/KeymapConfig/KeymapConfigScreen.tsx:171
#: packages/app-desktop/gui/Sidebar/Sidebar.tsx:394
@@ -1879,12 +1875,11 @@ msgstr "Etikerleri filtrele"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:218
msgid "Find and replace"
msgstr ""
msgstr "Bul ve değiştir"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:250
#, fuzzy
msgid "Find: "
msgstr "Bulundu: %d."
msgstr "Ara:"
#: packages/app-desktop/gui/ExtensionBadge.min.js:10
#: packages/app-desktop/gui/ExtensionBadge.tsx:63
@@ -1926,7 +1921,7 @@ msgstr ""
#: packages/lib/models/Setting.ts:1495
msgid "For example \"%s\""
msgstr ""
msgstr "Örneğin \"%s\""
#: packages/app-cli/app/command-help.js:36
msgid "For information on how to customise the shortcuts please visit %s"
@@ -1954,9 +1949,8 @@ msgid "Force path style"
msgstr "Zorlanan yol biçimi"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:276
#, fuzzy
msgid "Formatting"
msgstr "Bilgi"
msgstr "Biçimlendirme"
#: packages/lib/commands/historyForward.ts:6
msgid "Forward"
@@ -1977,9 +1971,8 @@ msgid "Full changelog"
msgstr "Tüm sürüm geçmişi"
#: packages/server/src/routes/admin/users.ts:130
#, fuzzy
msgid "Full name"
msgstr "Tüm sürüm geçmişi"
msgstr "Tam adı"
#: packages/lib/models/Setting.ts:2494
#: packages/server/src/services/MustacheService.ts:108
@@ -2030,11 +2023,11 @@ msgstr "Yetkilendirme ver"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:48
msgid "Header %d"
msgstr ""
msgstr "Başlık %d"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:280
msgid "Headers"
msgstr ""
msgstr "Başlıklar"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:73
msgid "Heading"
@@ -2049,14 +2042,12 @@ msgid "Hide %s"
msgstr "Şunu Gizle: %s"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:218
#, fuzzy
msgid "Hide advanced"
msgstr "Metadata'yı gizle"
msgstr "Gelişmiş'leri gizle"
#: packages/server/src/routes/admin/users.ts:200
#, fuzzy
msgid "Hide disabled"
msgstr "İnaktif anahtarları gizle"
msgstr "İptal edilmişleri gizle"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:172
msgid "Hide disabled keys"
@@ -2067,14 +2058,12 @@ msgid "Hide Joplin"
msgstr "Joplin'i Gizle"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:253
#, fuzzy
msgid "Hide keyboard"
msgstr "Metadata'yı gizle"
msgstr "Klavyeyi gizle"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.tsx:22
#, fuzzy
msgid "Hide more actions"
msgstr "Metadata'yı gizle"
msgstr "Daha fazla seçenekler kısmını gizle"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:14
msgid "Highlight"
@@ -2236,7 +2225,7 @@ msgstr "İçinde: %s"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:119
msgid "Increase indent level"
msgstr ""
msgstr "Girintileme seviyesini artır"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:104
msgid "Indent less"
@@ -2265,9 +2254,8 @@ msgstr "Köprü Link ekle"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:83
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx:628
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:188
#, fuzzy
msgid "Insert time"
msgstr "Tarih Ekle"
msgstr "Zaman ekle"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginBox.tsx:191
msgid "Install"
@@ -2333,7 +2321,7 @@ msgstr "Senkronize edilemeyen öğeler"
#: packages/app-desktop/gui/MenuBar.tsx:792
msgid "Join us on Twitter"
msgstr ""
msgstr "Twitter'da bize katılın"
#: packages/app-desktop/gui/SyncWizard/Dialog.tsx:330
msgid ""
@@ -2418,7 +2406,7 @@ msgstr ""
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:162
msgid "KaTeX"
msgstr ""
msgstr "KaTeX"
#: packages/lib/models/Setting.ts:1541
msgid "Keep note history for"
@@ -2489,14 +2477,12 @@ msgid "Lines"
msgstr "Satır"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:174
#, fuzzy
msgid "Link"
msgstr "Bağlantı"
#: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:95
#, fuzzy
msgid "Link description"
msgstr "Şifreleme"
msgstr "Link açıklaması"
#: packages/app-desktop/gui/ShareNoteDialog.tsx:183
msgid "Link has been copied to clipboard!"
@@ -2505,9 +2491,8 @@ msgstr[0] "Bağlantı panoya kopyalandı!"
msgstr[1] "Bağlantılar panoya kopyalandı!"
#: packages/app-mobile/components/NoteEditor/EditLinkDialog.tsx:92
#, fuzzy
msgid "Link text"
msgstr "Bağlantı"
msgstr "Bağlantı metni"
#: packages/app-mobile/components/screens/Note.tsx:205
msgid "Links with protocol \"%s\" are not supported"
@@ -2521,16 +2506,15 @@ msgstr "Öğeyi listele"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:284
msgid "Lists"
msgstr ""
msgstr "Listeler"
#: packages/app-mobile/components/screens/encryption-config.tsx:212
msgid "Loaded"
msgstr "Yüklenme"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:67
#, fuzzy
msgid "Loading..."
msgstr "Güncelleniyor..."
msgstr "Yükleniyor..."
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:32
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:52
@@ -2590,14 +2574,12 @@ msgid "Manage master password..."
msgstr "Ana parolayı yönet..."
#: packages/lib/utils/joplinCloud.ts:165
#, fuzzy
msgid "Manage multiple users"
msgstr "Ana parolayı yönet"
msgstr "Çoklu kullanıcıları yönet"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:638
#, fuzzy
msgid "Manage profiles"
msgstr "Profili güncelle"
msgstr "Profilleri yönet"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.tsx:320
msgid "Manage your plugins"
@@ -2661,17 +2643,15 @@ msgstr "Maksimum aynı anda bağlantı"
#: packages/server/src/routes/admin/users.ts:142
msgid "Max Item Size"
msgstr ""
msgstr "Maks. öğe adedi"
#: packages/lib/utils/joplinCloud.ts:117
#, fuzzy
msgid "Max note or attachment size"
msgstr "Ek Dosyalar"
msgstr "Azami not veya ek dosya ebatı"
#: packages/server/src/routes/admin/users.ts:150
#, fuzzy
msgid "Max Total Size"
msgstr "Asıl Boyut"
msgstr "Maks. Toplam Boyut"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:324
msgid "Missing keys"
@@ -2686,7 +2666,6 @@ msgid "Missing required argument: %s"
msgstr "Gerekli parametre eksik gözüküyor: %s"
#: packages/app-cli/app/cli-utils.js:135
#, fuzzy
msgid "Missing required flag value: %s"
msgstr "Gerekli parametre eksik gözüküyor: %s"
@@ -2723,9 +2702,8 @@ msgid "Move to notebook..."
msgstr "Not defterine taşı..."
#: packages/app-cli/app/command-mv.js:14
#, fuzzy
msgid "Moves the given <item> to [notebook]"
msgstr "<note> ile eşleşen tüm notları [notebook] defterine taşı."
msgstr "Verilen <item> bileşenini [notebook] not defterine taşır"
#: packages/app-cli/app/cli-utils.js:177
#: packages/app-cli/app/setupCommand.ts:20
@@ -2782,7 +2760,7 @@ msgstr "Yeni sürüm: %s"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:268
msgid "Next match"
msgstr ""
msgstr "Sonraki eşleşen"
#: packages/lib/SyncTargetNextcloud.js:25
msgid "Nextcloud"
@@ -2870,9 +2848,8 @@ msgid "Not generated"
msgstr "Oluşturulmamış"
#: packages/app-mobile/components/biometrics/BiometricPopup.tsx:90
#, fuzzy
msgid "Not now"
msgstr "Hemen yap"
msgstr "Şimdi değil"
#: packages/app-desktop/gui/NoteEditor/NoteTitle/NoteTitleBar.tsx:110
#: packages/app-desktop/gui/NoteListControls/NoteListControls.tsx:119
@@ -2907,9 +2884,8 @@ msgid "Note does not exist: \"%s\". Create it?"
msgstr "\"%s\" notu mevcut değil. Oluşturulsun mu?"
#: packages/app-mobile/components/NoteEditor/NoteEditor.tsx:83
#, fuzzy
msgid "Note editor"
msgstr "Not Geçmişi"
msgstr "Not düzenleyici"
#: packages/app-cli/app/command-edit.js:97
msgid "Note has been saved."
@@ -3060,9 +3036,8 @@ msgid "Open %s"
msgstr "Aç %s"
#: packages/app-desktop/gui/MainScreen/commands/openPdfViewer.ts:7
#, fuzzy
msgid "Open PDF viewer"
msgstr "PDF görüntüleyiciyi aktifleştir"
msgstr "PDF görüntüleyiciyi aç"
#: packages/app-desktop/commands/openProfileDirectory.ts:8
msgid "Open profile directory"
@@ -3093,9 +3068,8 @@ msgid "Or create an account."
msgstr "Ya da yeni bir hesap oluştur."
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:82
#, fuzzy
msgid "Ordered list"
msgstr "Kullanıcı oluştur"
msgstr "Dizilmiş liste"
#: packages/app-desktop/gui/MenuBar.tsx:410
msgid "Other applications..."
@@ -3141,7 +3115,7 @@ msgstr "Yapıştır"
#: packages/app-desktop/gui/NoteEditor/commands/pasteAsText.ts:6
#: packages/app-desktop/gui/NoteEditor/utils/contextMenu.ts:181
msgid "Paste as text"
msgstr ""
msgstr "Metin olarak yapıştır"
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:541
msgid "Path:"
@@ -3154,7 +3128,7 @@ msgstr "PDF Dosyası"
#: packages/lib/utils/joplinCloud.ts:355
msgid "Per user. Minimum of %d users."
msgstr ""
msgstr "Kullanıcı başına. Minimum %d kullanıcı."
#: packages/app-mobile/components/screens/Note.tsx:413
msgid "Permission needed"
@@ -3208,7 +3182,7 @@ msgstr ""
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:68
msgid "Please record your voice..."
msgstr ""
msgstr "Lütfen sesinizi kaydedin..."
#: packages/app-cli/app/command-ls.js:63
msgid "Please select a notebook first."
@@ -3306,7 +3280,7 @@ msgstr "Şifre çözücü parolayı belirlemek için basın."
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:278
msgid "Previous match"
msgstr ""
msgstr "Önceki eşleşme"
#: packages/app-desktop/gui/NotePropertiesDialog.min.js:307
#: packages/app-desktop/gui/NotePropertiesDialog.tsx:329
@@ -3319,16 +3293,15 @@ msgstr "Yazdır"
#: packages/lib/utils/joplinCloud.ts:183
msgid "Priority support"
msgstr ""
msgstr "Öncelikli destek"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:730
msgid "Privacy Policy"
msgstr "Gizlilik Politikası"
#: packages/lib/utils/joplinCloud.ts:316
#, fuzzy
msgid "Pro"
msgstr "Profil"
msgstr "Pro"
#: packages/server/src/services/TaskService.ts:24
msgid "Process failed payment subscriptions"
@@ -3347,9 +3320,8 @@ msgid "Profile"
msgstr "Profil"
#: packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx:95
#, fuzzy
msgid "Profile name"
msgstr "Profil adı:"
msgstr "Profil adı"
#: packages/app-desktop/gui/MainScreen/commands/addProfile.ts:17
msgid "Profile name:"
@@ -3360,26 +3332,24 @@ msgid "Profile Version: %s"
msgstr "Profil sürümü: %s"
#: packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.tsx:155
#, fuzzy
msgid "Profiles"
msgstr "Profil"
msgstr "Profiller"
#: packages/app-mobile/components/screens/Note.tsx:1024
msgid "Properties"
msgstr "Özellikler"
#: packages/lib/models/Setting.ts:1484
#, fuzzy
msgid "Proxy enabled"
msgstr "Etkin"
msgstr "Proxy etkinleştirildi"
#: packages/lib/models/Setting.ts:1506
msgid "Proxy timeout (seconds)"
msgstr ""
msgstr "Proxy zaman aşımı (saniye)"
#: packages/lib/models/Setting.ts:1494
msgid "Proxy URL"
msgstr ""
msgstr "Proxy URL"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:252
msgid "Public-private key pair:"
@@ -3453,9 +3423,8 @@ msgid "Refresh"
msgstr "Yenile"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:313
#, fuzzy
msgid "Regular expression"
msgstr "Matematik ifadelerini etkinleştir"
msgstr "Düzenli ifade (Regex)"
#: packages/app-desktop/gui/MainScreen/MainScreen.tsx:631
#: packages/app-desktop/gui/Root.tsx:179
@@ -3498,25 +3467,23 @@ msgstr "Jetonu yenile"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:288
msgid "Replace"
msgstr ""
msgstr "Değiştir"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:298
#, fuzzy
msgid "Replace all"
msgstr "Tümünü seç"
msgstr "Tümünü değiştir"
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:236
msgid "Replace with..."
msgstr ""
msgstr "Şununla değiştir..."
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:257
msgid "Replace: "
msgstr ""
msgstr "Değiştir:"
#: packages/app-desktop/gui/MainScreen/commands/resetLayout.ts:7
#, fuzzy
msgid "Reset application layout"
msgstr "Ugulama görünümünü değiştir"
msgstr "Ugulama görünümünü sıfırla"
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:219
#: packages/app-desktop/gui/MasterPasswordDialog/Dialog.tsx:220
@@ -3656,9 +3623,8 @@ msgid "Search for plugins..."
msgstr "Eklentileri ara..."
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:223
#, fuzzy
msgid "Search for..."
msgstr "Ara..."
msgstr "Şunu ara..."
#: packages/app-desktop/gui/NoteListControls/commands/focusSearch.ts:6
msgid "Search in all the notes"
@@ -3758,9 +3724,8 @@ msgid "Share"
msgstr "Paylaş"
#: packages/lib/utils/joplinCloud.ts:159
#, fuzzy
msgid "Share and collaborate on a notebook"
msgstr "Notlar yalnızca bir not defterinde oluşturulabilir."
msgstr "Bir not defteri paylaş ve üstünde ortak düzenlemeler yap"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:339
msgid "Share Notebook"
@@ -3772,7 +3737,7 @@ msgstr "No deferini paylaş..."
#: packages/lib/utils/joplinCloud.ts:177
msgid "Sharing access control"
msgstr ""
msgstr "Paylaşım erişim kontrolü"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.tsx:305
msgid "Sharing notebook..."
@@ -3783,9 +3748,8 @@ msgid "Shortcuts are not available in CLI mode."
msgstr "CLI modunda kısayollar mevcut değil."
#: packages/app-mobile/components/NoteEditor/SearchPanel.tsx:208
#, fuzzy
msgid "Show advanced"
msgstr "Gelişmiş Seçenekleri Göster"
msgstr "Gelişmiş seçenekleri göster"
#: packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx:217
msgid "Show Advanced Settings"
@@ -3800,18 +3764,16 @@ msgid "Show completed to-dos"
msgstr "Tamamlanan yapılacaklar listesini göster"
#: packages/server/src/routes/admin/users.ts:200
#, fuzzy
msgid "Show disabled"
msgstr "İnaktif anahtarları görüntüle"
msgstr "İptal olmuşları görüntüle"
#: packages/app-desktop/gui/EncryptionConfigScreen/EncryptionConfigScreen.tsx:172
msgid "Show disabled keys"
msgstr "İnaktif anahtarları görüntüle"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/ToggleOverflowButton.tsx:22
#, fuzzy
msgid "Show more actions"
msgstr "Not sayacını göster"
msgstr "Daha fazla seçenekler kısmını göster"
#: packages/lib/models/Setting.ts:883
msgid "Show note counts"
@@ -3827,7 +3789,7 @@ msgstr "Tepsi simgesini göster"
#: packages/app-mobile/components/ScreenHeader.tsx:290
msgid "Show/hide the sidebar"
msgstr ""
msgstr "Kenar çubuğunu göster/gizle"
#: packages/app-desktop/gui/Sidebar/commands/focusElementSideBar.ts:8
#: packages/app-mobile/components/ScreenHeader.tsx:289
@@ -4001,7 +3963,7 @@ msgstr "Dış düzenlemeyi durdur"
#: packages/lib/utils/joplinCloud.ts:129
msgid "Storage space"
msgstr ""
msgstr "Depolama alanı"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.ts:19
msgid "Strikethrough"
@@ -4069,7 +4031,7 @@ msgstr ""
#: packages/lib/utils/joplinCloud.ts:147
msgid "Sync as many devices as you want"
msgstr ""
msgstr "İstediğin kadar cihazı senkronize et"
#: packages/app-mobile/components/screens/ConfigScreen.tsx:639
msgid "Sync Status"
@@ -4177,7 +4139,6 @@ msgid "Take photo"
msgstr "Fotoğraf çek"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:95
#, fuzzy
msgid "Task list"
msgstr "Görevler"
@@ -4188,7 +4149,7 @@ msgstr "Görevler"
#: packages/lib/utils/joplinCloud.ts:338
msgid "Teams"
msgstr ""
msgstr "Ekipler"
#: packages/lib/models/Setting.ts:1377
msgid "Text editor command"
@@ -4204,7 +4165,7 @@ msgstr ""
msgid ""
"The active profile cannot be deleted. Switch to a different profile and try "
"again."
msgstr ""
msgstr "Profil aktifken silinemez. Bir başka profile geçip yeniden deneyin."
#: packages/app-desktop/bridge.ts:280
msgid ""
@@ -4272,7 +4233,7 @@ msgstr ""
#: packages/lib/services/profileConfig/index.ts:104
msgid "The default profile cannot be deleted"
msgstr ""
msgstr "Varsayılan profil silinemez."
#: packages/lib/models/Setting.ts:1377
msgid ""
@@ -4422,6 +4383,8 @@ msgid ""
"The WebDAV implementation of %s is incompatible with Joplin, and as such is "
"no longer supported. Please use a different sync method."
msgstr ""
"%s için olan WebDAV implementasyonu Joplin ile uyumlu değil, ce bu sebepten "
"artık desteklenmiyor. Lütfen başka bir senkronizasyon metodu kullanın."
#: packages/lib/models/Setting.ts:827
msgid "Theme"
@@ -4433,7 +4396,7 @@ msgstr "Şu anda not yok. (+) butonuna tıklayarak bir tane oluşturun."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:342
msgid "There are unsaved changes."
msgstr ""
msgstr "Kaydedilmemiş değişiklikler var."
#: packages/app-desktop/gui/NoteList/NoteList.tsx:512
msgid ""
@@ -4658,6 +4621,8 @@ msgid ""
"To switch the profile, the app is going to close and you will need to "
"restart it."
msgstr ""
"Profil değiştirmek için uygulama kapanacak, ve de yeniden başlatmanız "
"gerekecek."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:678
msgid ""
@@ -4674,9 +4639,8 @@ msgid "to-do"
msgstr "yapılacak"
#: packages/app-mobile/components/note-item.js:143
#, fuzzy
msgid "to-do: %s"
msgstr "yapılacak"
msgstr "yapılacak: %s"
#: packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts:112
msgid "Toggle comment"
@@ -4727,9 +4691,8 @@ msgid "Tools"
msgstr "Araçlar"
#: packages/server/src/routes/admin/users.ts:146
#, fuzzy
msgid "Total Size"
msgstr "Asıl Boyut"
msgstr "Toplam Boyut"
#: packages/lib/services/ReportService.ts:284
msgid "Total: %d/%d"
@@ -4742,9 +4705,8 @@ msgstr "Yeniden dene"
#: packages/lib/utils/joplinCloud.ts:309 packages/lib/utils/joplinCloud.ts:331
#: packages/lib/utils/joplinCloud.ts:353
#, fuzzy
msgid "Try it now"
msgstr "Hemen yap"
msgstr "Şimdi dene"
#: packages/app-cli/app/command-help.js:71
msgid ""
@@ -4799,9 +4761,8 @@ msgstr ""
"güncelleyin"
#: packages/app-mobile/components/NoteEditor/MarkdownToolbar/MarkdownToolbar.tsx:69
#, fuzzy
msgid "Unordered list"
msgstr "Kullanıcı oluştur"
msgstr "Dizilmemiş liste"
#: packages/app-desktop/gui/ShareNoteDialog.tsx:162
msgid "Unpublish note"
@@ -4905,7 +4866,7 @@ msgstr "Kullanım: %s"
#: packages/lib/models/Setting.ts:1657
msgid "Use biometrics to secure access to the app"
msgstr ""
msgstr "Uygulamaya erişimde güvenliği artırmak için biyometrik kullanımı"
#: packages/app-cli/app/command-ls.js:32 packages/app-cli/app/command-tag.js:18
msgid ""
@@ -4946,6 +4907,9 @@ msgid ""
"Use your biometrics to secure access to your application. You can always set "
"it up later in Settings."
msgstr ""
"Biyometriğinizi kullanarak uygulamaya güvenli bir biçimde giriş "
"yapabilirsiniz. Bu ayarı dilediğiniz zaman Ayarlar kısmından sonra da "
"aktifleştirebilirsiniz."
#: packages/lib/models/Setting.ts:1255
msgid ""
@@ -4982,7 +4946,7 @@ msgstr "Geçerli"
#: packages/app-mobile/components/biometrics/biometricAuthenticate.ts:10
msgid "Verify your identity"
msgstr ""
msgstr "Kimliğini doğrula"
#: packages/app-desktop/gui/NoteList/NoteList.tsx:167
msgid "View"
@@ -5010,11 +4974,11 @@ msgstr "Vim"
#: packages/app-mobile/components/voiceTyping/VoiceTypingDialog.tsx:92
msgid "Voice typing"
msgstr ""
msgstr "Sesle yazılıyor"
#: packages/app-mobile/components/screens/Note.tsx:993
msgid "Voice typing..."
msgstr ""
msgstr "Sesle yazılıyor..."
#: packages/app-mobile/components/screens/ConfigScreen.tsx:83
msgid "Warning"

View File

@@ -49,7 +49,7 @@
"@types/node": "18.11.18",
"@types/node-fetch": "2.6.2",
"@types/yargs": "17.0.20",
"gettext-extractor": "3.7.1",
"gettext-extractor": "3.7.2",
"gulp": "4.0.2",
"html-entities": "1.4.0",
"jest": "29.4.3",

View File

@@ -1,11 +1,10 @@
import { execCommand } from '@joplin/utils';
import * as fs from 'fs-extra';
import { copy, mkdirp, move, readFile, readFileSync, remove, stat, writeFile, writeFileSync } from 'fs-extra';
import { execCommandVerbose, execCommandWithPipes, githubRelease, githubOauthToken, fileExists, gitPullTry, completeReleaseWithChangelog } from './tool-utils';
const path = require('path');
const fetch = require('node-fetch');
const uriTemplate = require('uri-template');
const projectName = 'joplin-android';
const rootDir = path.dirname(path.dirname(__dirname));
const rnDir = `${rootDir}/packages/app-mobile`;
const releaseDir = `${rnDir}/dist`;
@@ -16,6 +15,47 @@ interface Release {
apkFilePath: string;
}
type PatcherCallback = (content: string)=> Promise<string>;
class Patcher {
private workDir_: string;
private originalContents_: Record<string, string> = {};
private removedFiles_: Record<string, string> = {};
public constructor(workDir: string) {
this.workDir_ = workDir;
}
public removeFile = async (path: string) => {
const targetPath = `${this.workDir_}/${path.substring(1)}`;
await move(path, targetPath);
this.removedFiles_[path] = targetPath;
};
public updateFileContent = async (path: string, callback: PatcherCallback) => {
const content = await readFile(path, 'utf8');
this.originalContents_[path] = content;
const newContent = await callback(content);
await writeFile(path, newContent);
};
public restore = async () => {
for (const filename in this.originalContents_) {
const content = this.originalContents_[filename];
await writeFile(filename, content);
}
for (const [originalPath, backupPath] of Object.entries(this.removedFiles_)) {
await move(backupPath, originalPath);
}
this.removedFiles_ = {};
this.originalContents_ = {};
};
}
function increaseGradleVersionCode(content: string) {
const newContent = content.replace(/versionCode\s+(\d+)/, (_a, versionCode: string) => {
const n = Number(versionCode);
@@ -41,10 +81,10 @@ function increaseGradleVersionName(content: string) {
}
function updateGradleConfig() {
let content = fs.readFileSync(`${rnDir}/android/app/build.gradle`, 'utf8');
let content = readFileSync(`${rnDir}/android/app/build.gradle`, 'utf8');
content = increaseGradleVersionCode(content);
content = increaseGradleVersionName(content);
fs.writeFileSync(`${rnDir}/android/app/build.gradle`, content);
writeFileSync(`${rnDir}/android/app/build.gradle`, content);
return content;
}
@@ -54,35 +94,40 @@ function gradleVersionName(content: string) {
return matches[1];
}
async function createRelease(name: string, tagName: string, version: string): Promise<Release> {
const originalContents: Record<string, string> = {};
async function createRelease(projectName: string, name: string, tagName: string, version: string): Promise<Release> {
const suffix = version + (name === 'main' ? '' : `-${name}`);
const patcher = new Patcher(`${rnDir}/patcher-work`);
console.info(`Creating release: ${suffix}`);
if (name === '32bit') {
const filename = `${rnDir}/android/app/build.gradle`;
let content = await fs.readFile(filename, 'utf8');
originalContents[filename] = content;
content = content.replace(/abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'abiFilters "armeabi-v7a", "x86"');
content = content.replace(/include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'include "armeabi-v7a", "x86"');
await fs.writeFile(filename, content);
await patcher.updateFileContent(`${rnDir}/android/app/build.gradle`, async (content: string) => {
content = content.replace(/abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'abiFilters "armeabi-v7a", "x86"');
content = content.replace(/include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"/, 'include "armeabi-v7a", "x86"');
return content;
});
}
if (name !== 'vosk') {
{
const filename = `${rnDir}/services/voiceTyping/vosk.ts`;
originalContents[filename] = await fs.readFile(filename, 'utf8');
const newContent = await fs.readFile(`${rnDir}/services/voiceTyping/vosk.dummy.ts`, 'utf8');
await fs.writeFile(filename, newContent);
}
{
const filename = `${rnDir}/package.json`;
let content = await fs.readFile(filename, 'utf8');
originalContents[filename] = content;
await patcher.updateFileContent(`${rnDir}/services/voiceTyping/vosk.ts`, async (_content: string) => {
return readFile(`${rnDir}/services/voiceTyping/vosk.dummy.ts`, 'utf8');
});
await patcher.updateFileContent(`${rnDir}/android/app/build.gradle`, async (content: string) => {
content = content.replace(/\s+"react-native-vosk": ".*",/, '');
await fs.writeFile(filename, content);
}
return content;
});
await patcher.removeFile(`${rnDir}/android/app/src/main/assets/model-fr-fr`);
}
if (name === 'vosk') {
await patcher.updateFileContent(`${rnDir}/android/app/build.gradle`, async (content: string) => {
content = content.replace(/(\s+)applicationId "net.cozic.joplin"/, '$1applicationId "net.cozic.joplin.mod"');
content = content.replace(/(\s+)versionName "(\d+\.\d+\.\d+)"/, '$1versionName "$2-mod"');
return content;
});
}
const apkFilename = `joplin-v${suffix}.apk`;
@@ -100,12 +145,12 @@ async function createRelease(name: string, tagName: string, version: string): Pr
const buildDirName = `build-${name}`;
const buildDirBasePath = `${rnDir}/android/app/${buildDirName}`;
await fs.remove(buildDirBasePath);
await remove(buildDirBasePath);
let restoreDir = null;
let apkBuildCmd = '';
let apkCleanBuild = '';
const apkBuildCmdArgs = ['assembleRelease', `-PbuildDir=${buildDirName}`]; // TOOD: change build dir, delete before
const apkBuildCmdArgs = ['assembleRelease', `-PbuildDir=${buildDirName}`];
if (await fileExists('/mnt/c/Windows/System32/cmd.exe')) {
await execCommandWithPipes('/mnt/c/Windows/System32/cmd.exe', ['/c', `cd packages\\app-mobile\\android && gradlew.bat ${apkBuildCmd}`]);
apkBuildCmd = '';
@@ -124,26 +169,23 @@ async function createRelease(name: string, tagName: string, version: string): Pr
if (restoreDir) process.chdir(restoreDir);
await fs.mkdirp(releaseDir);
await mkdirp(releaseDir);
const builtApk = `${buildDirBasePath}/outputs/apk/release/app-release.apk`;
const builtApkStat = await fs.stat(builtApk);
const builtApkStat = await stat(builtApk);
console.info(`Built APK at ${builtApk}`);
console.info('APK size:', builtApkStat.size);
console.info(`Copying APK to ${apkFilePath}`);
await fs.copy(builtApk, apkFilePath);
await copy(builtApk, apkFilePath);
if (name === 'main') {
console.info(`Copying APK to ${releaseDir}/joplin-latest.apk`);
await fs.copy(builtApk, `${releaseDir}/joplin-latest.apk`);
await copy(builtApk, `${releaseDir}/joplin-latest.apk`);
}
for (const filename in originalContents) {
const content = originalContents[filename];
await fs.writeFile(filename, content);
}
await patcher.restore();
return {
downloadUrl: downloadUrl,
@@ -152,6 +194,39 @@ async function createRelease(name: string, tagName: string, version: string): Pr
};
}
const uploadToGitHubRelease = async (projectName: string, tagName: string, isPreRelease: boolean, releaseFiles: Record<string, Release>) => {
console.info(`Creating GitHub release ${tagName}...`);
const releaseOptions = { isPreRelease: isPreRelease };
const oauthToken = await githubOauthToken();
const release = await githubRelease(projectName, tagName, releaseOptions);
const uploadUrlTemplate = uriTemplate.parse(release.upload_url);
for (const releaseFilename in releaseFiles) {
const releaseFile = releaseFiles[releaseFilename];
const uploadUrl = uploadUrlTemplate.expand({ name: releaseFile.apkFilename });
const binaryBody = await readFile(releaseFile.apkFilePath);
console.info(`Uploading ${releaseFile.apkFilename} to ${uploadUrl}`);
const uploadResponse = await fetch(uploadUrl, {
method: 'POST',
body: binaryBody,
headers: {
'Content-Type': 'application/vnd.android.package-archive',
'Authorization': `token ${oauthToken}`,
'Content-Length': binaryBody.length,
},
});
const uploadResponseText = await uploadResponse.text();
const uploadResponseObject = JSON.parse(uploadResponseText);
if (!uploadResponseObject || !uploadResponseObject.browser_download_url) throw new Error('Could not upload file to GitHub');
}
};
async function main() {
const argv = require('yargs').argv;
@@ -171,41 +246,25 @@ async function main() {
// const releaseNames = ['main', '32bit', 'vosk'];
const releaseNames = ['main', 'vosk'];
const releaseFiles: Record<string, Release> = {};
const mainProjectName = 'joplin-android';
const modProjectName = 'joplin-android-mod';
for (const releaseName of releaseNames) {
if (releaseNameOnly && releaseName !== releaseNameOnly) continue;
releaseFiles[releaseName] = await createRelease(releaseName, tagName, version);
const projectName = releaseName === 'vosk' ? modProjectName : mainProjectName;
releaseFiles[releaseName] = await createRelease(projectName, releaseName, tagName, version);
}
console.info(`Creating GitHub release ${tagName}...`);
console.info('Created releases:');
console.info(releaseFiles);
const releaseOptions = { isPreRelease: isPreRelease };
const voskRelease = releaseFiles['vosk'];
const oauthToken = await githubOauthToken();
const release = await githubRelease(projectName, tagName, releaseOptions);
const uploadUrlTemplate = uriTemplate.parse(release.upload_url);
await uploadToGitHubRelease(mainProjectName, tagName, isPreRelease, releaseFiles);
for (const releaseFilename in releaseFiles) {
const releaseFile = releaseFiles[releaseFilename];
const uploadUrl = uploadUrlTemplate.expand({ name: releaseFile.apkFilename });
const binaryBody = await fs.readFile(releaseFile.apkFilePath);
console.info(`Uploading ${releaseFile.apkFilename} to ${uploadUrl}`);
const uploadResponse = await fetch(uploadUrl, {
method: 'POST',
body: binaryBody,
headers: {
'Content-Type': 'application/vnd.android.package-archive',
'Authorization': `token ${oauthToken}`,
'Content-Length': binaryBody.length,
},
});
const uploadResponseText = await uploadResponse.text();
const uploadResponseObject = JSON.parse(uploadResponseText);
if (!uploadResponseObject || !uploadResponseObject.browser_download_url) throw new Error('Could not upload file to GitHub');
if (voskRelease) {
delete releaseFiles['vosk'];
await uploadToGitHubRelease(modProjectName, tagName, isPreRelease, { 'vosk': voskRelease });
}
console.info(`Main download URL: ${releaseFiles['main'].downloadUrl}`);

View File

@@ -19,21 +19,25 @@ function downloadUrl(release: GitHubRelease, os: string, portable = false) {
const name = asset.name;
const ext = fileExtension(name);
if (ext === 'dmg' && os === 'macos') return asset.browser_download_url;
const githubAndroidUrl = 'github.com/laurent22/joplin-android/releases/download';
const githubUrl = 'github.com/laurent22/joplin/releases/download';
const joplinDomain = 'objects.joplinusercontent.com';
if (ext === 'dmg' && os === 'macos') return asset.browser_download_url.replace(githubUrl, joplinDomain);
if (ext === 'exe' && os === 'windows') {
if (portable) {
if (name === 'JoplinPortable.exe') return asset.browser_download_url;
if (name === 'JoplinPortable.exe') return asset.browser_download_url.replace(githubUrl, joplinDomain);
} else {
if (name.match(/^Joplin-Setup-[\d.]+\.exe$/)) return asset.browser_download_url;
if (name.match(/^Joplin-Setup-[\d.]+\.exe$/)) return asset.browser_download_url.replace(githubUrl, joplinDomain);
}
}
if (ext === 'AppImage' && os === 'linux') return asset.browser_download_url;
if (ext === 'AppImage' && os === 'linux') return asset.browser_download_url.replace(githubUrl, joplinDomain);
if (os === 'android32' && name.endsWith('32bit.apk')) return asset.browser_download_url;
if (os === 'android32' && name.endsWith('32bit.apk')) return asset.browser_download_url.replace(githubAndroidUrl, joplinDomain);
if (os === 'android' && ext === 'apk' && !name.endsWith('32bit.apk')) return asset.browser_download_url;
if (os === 'android' && ext === 'apk' && !name.endsWith('32bit.apk')) return asset.browser_download_url.replace(githubAndroidUrl, joplinDomain);
}
throw new Error(`Could not find download URL for: ${os}`);
@@ -85,16 +89,16 @@ async function main(argv: any) {
let content = readmeContent();
if (winUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/v\d+\.\d+\.\d+\/Joplin-Setup-.*?\.exe)/, winUrl);
if (winPortableUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/v\d+\.\d+\.\d+\/JoplinPortable.exe)/, winPortableUrl);
if (macOsUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/v\d+\.\d+\.\d+\/Joplin-.*?\.dmg)/, macOsUrl);
if (linuxUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin\/releases\/download\/v\d+\.\d+\.\d+\/Joplin-.*?\.AppImage)/, linuxUrl);
if (winUrl) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/v\d+\.\d+\.\d+\/Joplin-Setup-.*?\.exe)/, winUrl);
if (winPortableUrl) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/v\d+\.\d+\.\d+\/JoplinPortable.exe)/, winPortableUrl);
if (macOsUrl) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/v\d+\.\d+\.\d+\/Joplin-.*?\.dmg)/, macOsUrl);
if (linuxUrl) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/v\d+\.\d+\.\d+\/Joplin-.*?\.AppImage)/, linuxUrl);
// Disable for now due to broken /latest API end point, which returns a
// version from 6 months ago.
if (androidUrl) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+\.apk)/, androidUrl);
if (android32Url) content = content.replace(/(https:\/\/github.com\/laurent22\/joplin-android\/releases\/download\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+-32bit\.apk)/, android32Url);
if (androidUrl) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+\.apk)/, androidUrl);
if (android32Url) content = content.replace(/(https:\/\/objects.joplinusercontent.com\/android-v\d+\.\d+\.\d+\/joplin-v\d+\.\d+\.\d+-32bit\.apk)/, android32Url);
setReadmeContent(content);

View File

@@ -0,0 +1,67 @@
# Bienvenue dans Joplin !
Joplin est une application gratuite et open source de prise de notes, qui vous aide à rédiger et à organiser vos notes, et à les synchroniser entre vos appareils. Les notes sont consultables, peuvent être copiées, étiquetées et modifiées directement depuis l'application ou depuis votre propre éditeur de texte. Les notes sont au [format Markdown](https://joplinapp.org/help/#markdown). Joplin est disponible en tant qu'application de **burean**, **mobile** et **terminal**.
Les notes de ce carnet donnent un aperçu de ce que Joplin peut faire et comment l'utiliser. En général, les trois applications partagent à peu près les mêmes fonctionnalités ; toute différence sera clairement indiquée.
![](./AllClients.png)
## Joplin est divisé en trois parties
Joplin a trois colonnes principales :
- La **barre latérale** contient la liste de vos carnets et étiquettes, ainsi que l'état de la synchronisation.
- La **liste de notes** contient la liste actuelle des notes - soit les notes du bloc-notes actuellement sélectionné, les notes de la balise actuellement sélectionnée ou les résultats de la recherche.
- L'**éditeur de notes** est là où vous écrivez vos notes. Il existe un **éditeur de texte enrichi** et un **éditeur Markdown** - cliquez sur le bouton **Basculer l'éditeur** dans le coin supérieur droit pour basculer entre les deux ! Vous pouvez également utiliser un [éditeur externe](https://joplinapp.org/help/#external-text-editor) pour modifier les notes. Par exemple, vous pouvez utiliser Typora comme éditeur externe et il affichera la note ainsi que toutes les images intégrées.
## Écrire des notes en Markdown
Markdown est un langage de balisage léger avec une syntaxe de formatage de texte brut. Joplin prend en charge une [syntaxe Markdown à saveur Github](https://joplinapp.org/markdown/) avec quelques variantes et ajouts.
En général, bien que Markdown soit un langage de balisage, il est censé être lisible directement. Ceci est un exemple simple (vous pouvez voir à quoi il ressemble dans le panneau de visualisation) :
* * *
# En-tête
## Sous-titre
Les paragraphes sont séparés par une ligne blanche. Les attributs de texte _italic_, **bold** et `monospace` sont pris en charge. Vous pouvez créer des listes à puces :
* pommes
* des oranges
* des poires
Ou des listes numérotées :
1. laver
2. rincer
3. répéter
Ceci est un [lien](https://joplinapp.org) et, enfin, ci-dessous est une règle horizontale :
* * *
Beaucoup plus est possible, y compris l'ajout d'exemples de code informatique, de formules mathématiques ou de listes de cases à cocher - voir la [documentation Markdown](https://joplinapp.org/help/#markdown) pour plus d'informations.
## Organiser vos notes
### Avec des carnets
Les notes de Joplin sont organisées en une arborescence de carnets et de sous-carnets.
- Sur l'appli de **bureau**, vous pouvez créer un carnet en cliquant sur "Nouveau carnet", puis vous pouvez les faire glisser et les déposer dans d'autres carnets pour les organiser comme vous le souhaitez.
- Sur **mobile**, appuyez sur l'icône "+" et sélectionnez "Nouveau carnet".
- Sur le **terminal**, appuyez sur `:mn`
![](./SubNotebooks.png)
### Avec des étiquettes
La deuxième façon d'organiser vos notes consiste à utiliser des étiquettes :
- Sur **bureau**, faites un clic droit sur n'importe quelle note dans la liste des notes et sélectionnez "Modifier les étiquettes". Vous pouvez ensuite ajouter les étiquettes en les séparant par des virgules.
- Sur **mobile**, ouvrez la note et appuyez sur le bouton "⋮" et sélectionnez "étiquettes".
- Sur le **terminal**, tapez `:help tag` pour les commandes disponibles.

View File

@@ -0,0 +1,15 @@
# Importer et exporter des notes
## Importation depuis Evernote
Joplin peut importer des carnets complets depuis Evernote, ainsi que des notes, des étiquettes, des images, des fichiers joints et des métadonnées de note (telles que l'auteur, la géolocalisation, etc.) via des fichiers ENEX.
Pour importer des données Evernote, exportez d'abord vos carnets Evernote vers des fichiers ENEX comme décrit [ici](https://help.evernote.com/hc/en-us/articles/209005557-How-to-back-up-export-and-restaurer-importer-notes-et-carnets). Ensuite, sur **bureau**, procédez comme suit : Ouvrez Fichier > Importer > ENEX et sélectionnez votre fichier. Les notes seront importées dans un nouveau cahier séparé. Si nécessaire, ils peuvent ensuite être déplacés vers un autre carnet, ou le carnet peut être renommé, etc. [En savoir plus sur l'importation Evernote](https://joplinapp.org/help/#importing-from-evernote).
# Importation à partir d'autres applications
Joplin peut également importer des notes depuis [de nombreuses autres applications](https://github.com/laurent22/joplin#importing-from-other-applications) ainsi que [depuis Markdown ou depuis des fichiers texte](https://github.com/laurent22/joplin#importing-from-markdown-files).
# Exporter des notes
Joplin peut exporter au format JEX (fichier d'exportation Joplin), qui est une archive pouvant contenir plusieurs notes, cahiers, etc. Il s'agit d'un format principalement conçu à des fins de sauvegarde. Vous pouvez également exporter vers d'autres formats tels que des fichiers Markdown simples, vers JSON ou vers PDF. Découvrez [l'exportation de notes](https://github.com/laurent22/joplin#exporting) sur le site officiel.

View File

@@ -0,0 +1,25 @@
# Synchroniser vos notes
Joplin vous permet de synchroniser vos données à l'aide de divers services d'hébergement de fichiers. Les services cloud pris en charge sont les suivants :
## Configuration de la synchronisation Joplin Cloud
[Joplin Cloud](https://joplinapp.org/plans/) est un service Web spécialement conçu pour Joplin. Outre la synchronisation de vos données, il vous permet également de publier une note sur Internet ou de partager un carnet avec vos amis, votre famille ou vos collègues. Joplin Cloud, par rapport à d'autres services, présente également un certain nombre d'améliorations des performances permettant une synchronisation plus rapide.
Pour l'utiliser, rendez-vous dans l'écran de configuration, puis dans la rubrique Synchronisation. Dans la liste des cibles de synchronisation, sélectionnez "Joplin Cloud". Entrez votre e-mail et votre mot de passe, et vous êtes prêt à utiliser Joplin Cloud.
## Configuration de la synchronisation Dropbox
Sélectionnez "Dropbox" comme cible de synchronisation dans l'écran de configuration. Ensuite, pour lancer le processus de synchronisation, cliquez sur le bouton "Synchroniser" dans la barre latérale et suivez les instructions.
## Configuration de la synchronisation Nextcloud
Nextcloud est une solution de cloud pouvant être auto-hébergée. Pour le configurer, accédez à l'écran de configuration et sélectionnez Nextcloud comme cible de synchronisation. Saisissez ensuite l'URL WebDAV (pour l'obtenir, rendez-vous sur votre page Nextcloud, cliquez sur Paramètres dans le coin inférieur gauche de la page et copiez l'URL). Notez qu'il doit s'agir de l'**URL complète**, donc par exemple si vous voulez que les notes soient sous `/Joplin`, l'URL serait quelque chose comme `https://example.com/remote.php/webdav /Joplin` (notez que la partie "/Joplin"). Et **assurez-vous de créer le répertoire "/Joplin" dans Nextcloud**. Définissez enfin le nom d'utilisateur et le mot de passe. Si cela ne fonctionne pas, veuillez [voir cette explication](https://github.com/laurent22/joplin/issues/61#issuecomment-373282608) pour plus de détails.
## Configuration de la synchronisation OneDrive ou WebDAV
OneDrive et WebDAV sont également pris en charge en tant que services de synchronisation. Veuillez consulter [la documentation de synchronisation](https://github.com/laurent22/joplin#synchronisation) pour plus d'informations.
## Utilisation du chiffrement de bout en bout
Joplin prend en charge le chiffrement de bout en bout (E2EE) sur toutes les applications. E2EE est un système où seul le propriétaire des données peut les lire. Il empêche les espions potentiels, y compris les fournisseurs de télécommunications, les fournisseurs d'accès Internet et même les développeurs de Joplin, d'accéder aux données. Veuillez consulter le [tutoriel sur le chiffrement de bout en bout](https://joplinapp.org/e2ee/) pour plus d'informations sur cette fonctionnalité et comment l'activer.

View File

@@ -0,0 +1,70 @@
# Trucs & Astuces
Les premières notes vous ont donné un aperçu des principales fonctionnalités de Joplin, mais il peut faire plus. Voir ci-dessous pour certaines de ces fonctionnalités et comment obtenir plus d'aide en utilisant l'application :
## Web Clipper
![](./WebClipper.png)
Le Web Clipper est une extension de navigateur qui vous permet d'enregistrer des pages Web et des captures d'écran à partir de votre navigateur. Pour commencer à l'utiliser, ouvrez l'application de bureau Joplin, accédez aux options du Web Clipper et suivez les instructions.
Plus d'infos sur le site officiel : https://joplinapp.org/clipper/
## Plugins
Joplin prend en charge de nombreux plugins qui vous permettent d'ajouter de nouvelles fonctionnalités à l'application, telles que des onglets, une table des matières pour vos notes, un moyen de gérer les notes préférées et bien d'autres. Pour ajouter un plugin, rendez-vous dans la section "Plugins" de l'écran de configuration. À partir de là, vous pouvez rechercher et installer des plugins, ainsi que rechercher ou mettre à jour des plugins.
## Pièces jointes
Tout type de fichier peut être joint à une note. Dans Markdown, les liens vers ces fichiers sont représentés par un ID. Dans le visualiseur de notes, ces fichiers, s'il s'agit d'images, seront affichés ou, s'il s'agit d'autres fichiers (PDF, fichiers texte, etc.), ils seront affichés sous forme de liens. Cliquer sur ce lien ouvrira le fichier dans l'application par défaut.
Les images peuvent être jointes soit en cliquant sur "Joindre un fichier", soit en collant (avec `Ctrl+V` ou `Cmd+V`) une image directement dans l'éditeur, soit en glissant-déposant une image.
Plus d'infos sur les pièces jointes : https://joplinapp.org/help/#attachments
## Recherche
Joplin prend en charge les requêtes de recherche avancées, qui sont entièrement documentées sur le site officiel : https://joplinapp.org/help/#searching
## Alarmes
Une alarme peut être associée à n'importe quelle tâche. Elle sera déclenchée à l'heure indiquée par l'affichage d'une notification. Pour utiliser cette fonctionnalité, consultez la documentation : https://joplinapp.org/help/#notifications
## Conseils avancés Markdown
Joplin utilise et rend [Github-flavored Markdown](https://joplinapp.org/markdown/) avec quelques variations et ajouts.
Par exemple, les tableaux sont pris en charge :
| Les tableaux | Sont | Cools |
| ------------- |:-------------:| -----:|
| col 3 est | alignée à droite | $1600 |
| col 2 est | centrée | $12 |
Vous pouvez également créer des listes de cases à cocher. Ces cases peuvent être cochées directement dans le visualiseur, ou en ajoutant un "x" à l'intérieur :
- [ ] Lait
- [ ] Œufs
- [x] Bière
Des expressions mathématiques peuvent être ajoutées à l'aide de la [notation KaTeX](https://khan.github.io/KaTeX/) :
$$
f(x) = \int_{-\infty}^\infty
\hat f(\xi)\,e^{2 \pi i \xi x}
\,d\xi
$$
Diverses autres astuces sont possibles, telles que l'utilisation de HTML ou la personnalisation du CSS. Voir la documentation Markdown pour plus d'informations - https://joplinapp.org/markdown/
## Communauté et aide supplémentaire
- Pour une discussion générale sur Joplin, l'assistance aux utilisateurs, les questions de développement logiciel et pour discuter des nouvelles fonctionnalités, rendez-vous sur le [Forum Joplin](https://discourse.joplinapp.org/). Il est possible de se connecter avec votre compte GitHub.
- Les dernières nouvelles sont publiées [sur la page Patreon](https://www.patreon.com/joplin).
- Pour les rapports de bugs et les demandes de fonctionnalités, accédez au [GitHub Issue Tracker](https://github.com/laurent22/joplin/issues).
## Donations
Les dons à Joplin soutiennent le développement du projet. Développer des applications de qualité prend généralement du temps, mais il y a aussi des dépenses, comme les certificats numériques pour signer les applications, les frais d'app store, l'hébergement, etc. Surtout, votre don permettra de maintenir le standard de développement actuel.
Veuillez consulter la [page de don](https://joplinapp.org/donate/) pour savoir comment soutenir le développement de Joplin.

View File

@@ -0,0 +1,21 @@
# Joplin Politique de confidentialité
Les applications Joplin, y compris les applications Android, iOS, Windows, macOS et Linux, n'envoient aucune donnée à aucun service sans votre autorisation. Toutes les données enregistrées par Joplin, telles que les notes ou les images, sont enregistrées sur votre propre appareil et vous êtes libre de supprimer ces données à tout moment.
Si vous choisissez de synchroniser avec un tiers, tel que OneDrive ou Dropbox, les notes seront envoyées à ce compte, auquel cas la politique de confidentialité du tiers s'applique.
Afin de fournir certaines fonctionnalités, Joplin peut avoir besoin de se connecter à des services tiers. Vous pouvez désactiver la plupart de ces fonctionnalités dans les paramètres de l'application :
| Caractéristique | Descriptif | Par défaut | Peut être désactivé |
| -------- | -------------- | -------- | --- |
| Mise à jour automatique | Joplin se connecte périodiquement à GitHub pour vérifier les nouvelles versions. | Activé | Oui |
| Géolocalisation | Joplin enregistre les informations de géolocalisation dans les propriétés de la note lorsque vous créez une note. | Activé | Oui |
| Synchronisation | Joplin prend en charge la synchronisation de vos notes sur plusieurs appareils. Si vous choisissez de synchroniser avec un tiers, tel que OneDrive, les notes seront envoyées à votre compte OneDrive, auquel cas la politique de confidentialité du tiers s'applique. | Désactivé | Oui |
| Vérification de la connexion Wifi | Sur mobile, Joplin vérifie la connectivité Wifi pour donner la possibilité de synchroniser les données uniquement lorsque le Wifi est activé. | Activé | Non <sup>(1)</sup> |
| Dictionnaire correcteur orthographique | Sous Linux et Windows, l'application de bureau télécharge le dictionnaire du correcteur orthographique à partir de `redirector.gvt1.com`. | Activé | Oui <sup>(2)</sup> |
| Référentiel de plugins | L'application de bureau télécharge la liste des plugins disponibles depuis le [référentiel GitHub officiel](https://github.com/joplin/plugins). Si ce référentiel n'est pas accessible (par exemple en Chine), l'application essaiera d'obtenir la liste des plugins à partir de [divers miroirs](https://github.com/laurent22/joplin/blob/8ac6017c02017b6efd59f5fcab7e0b07f8d44164/packages/lib/services/plugins/RepositoryApi.ts#L22), auquel cas l'écran du plugin [fonctionne légèrement différemment](https://github.com/laurent22/joplin/issues/5161#issuecomment-925226975). | Activé | Non
<sup>(1) https://github.com/laurent22/joplin/issues/5705</sup><br/>
<sup>(2) Si le correcteur orthographique est désactivé, [il ne téléchargera pas le dictionnaire](https://discourse.joplinapp.org/t/new-version-of-joplin-contacting-google-servers-on-startup /23000/40?u=laurent).</sup>
Pour toute question concernant la politique de confidentialité de Joplin, merci de laisser un message [sur le forum](https://discourse.joplinapp.org/).

View File

@@ -1,6 +1,15 @@
# Joplin changelog
## [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) (Pre-release) - 2023-04-27T09:27:45Z
## [v2.10.18](https://github.com/laurent22/joplin/releases/tag/v2.10.18) - 2023-05-09T13:27:43Z
- Fixed: Application cannot be installed on Windows 10 in some cases ([#8149](https://github.com/laurent22/joplin/issues/8149))
## [v2.10.17](https://github.com/laurent22/joplin/releases/tag/v2.10.17) - 2023-05-08T17:27:28Z
- Fixed: Enter Key No Longer Saves and Closes The Tag Dialog ([#8072](https://github.com/laurent22/joplin/issues/8072))
- Fixed: Fixes crash when using multiple profiles along with certain plugins ([#8143](https://github.com/laurent22/joplin/issues/8143))
## [v2.10.16](https://github.com/laurent22/joplin/releases/tag/v2.10.16) - 2023-04-27T09:27:45Z
- Improved: Revert to "normal" package compression ([2e2feab](https://github.com/laurent22/joplin/commit/2e2feab))
@@ -224,35 +233,6 @@
- Fixed: Prevent certain errors from stopping the revision service ([#5531](https://github.com/laurent22/joplin/issues/5531))
- Fixed: Note export could fail in some cases (regression) ([#6203](https://github.com/laurent22/joplin/issues/6203))
## [v2.7.13](https://github.com/laurent22/joplin/releases/tag/v2.7.13) - 2022-02-24T17:42:12Z
- Fixed: Fixed search marker background color in Markdown editor ([440618e](https://github.com/laurent22/joplin/commit/440618e))
- Updated translations
## [v2.7.12](https://github.com/laurent22/joplin/releases/tag/v2.7.12) (Pre-release) - 2022-02-14T15:06:14Z
- Fixed: Exported JEX notebook should not contain share metadata ([#6129](https://github.com/laurent22/joplin/issues/6129))
## [v2.7.11](https://github.com/laurent22/joplin/releases/tag/v2.7.11) (Pre-release) - 2022-02-12T13:00:02Z
- Improved: Resize custom icon down to 256px when it is too large ([064891d](https://github.com/laurent22/joplin/commit/064891d))
- Updated translations
## [v2.7.10](https://github.com/laurent22/joplin/releases/tag/v2.7.10) (Pre-release) - 2022-02-11T18:19:09Z
Important: If you use custom notebook icons and sync with the mobile app, make sure also install the latest 2.7 mobile app.
- New: Add additional time format HH.mm ([#6086](https://github.com/laurent22/joplin/issues/6086) by [@vincentjocodes](https://github.com/vincentjocodes))
- New: Add support for custom notebook icons ([#6110](https://github.com/laurent22/joplin/issues/6110))
- Improved: Fixed sync scroll issue ([#6059](https://github.com/laurent22/joplin/issues/6059)) ([#5808](https://github.com/laurent22/joplin/issues/5808) by Caleb John)
- Improved: Make heading 4, 5 and 6 styling more consistent ([fca5875](https://github.com/laurent22/joplin/commit/fca5875))
- Improved: Update Mermaid 8.13.5 -&gt; 8.13.9 and Katex dependencies ([#6039](https://github.com/laurent22/joplin/issues/6039) by Helmut K. C. Tessarek)
- Fixed: Add "Other applications" import menu item ([#6118](https://github.com/laurent22/joplin/issues/6118)) ([#6108](https://github.com/laurent22/joplin/issues/6108) by Helmut K. C. Tessarek)
- Fixed: Global search focuses text in notes so that edits overwrite highlighted text ([#6040](https://github.com/laurent22/joplin/issues/6040)) ([#6035](https://github.com/laurent22/joplin/issues/6035) by Caleb John)
- Fixed: Login field was sometimes disabled on Sync Wizard dialog ([#6075](https://github.com/laurent22/joplin/issues/6075))
- Fixed: Scroll position is not remembered (regression) ([#6043](https://github.com/laurent22/joplin/issues/6043)) ([#6042](https://github.com/laurent22/joplin/issues/6042) by Kenichi Kobayashi)
- Fixed: Shared resource was not encrypted with correct encryption key ([#6092](https://github.com/laurent22/joplin/issues/6092))
## [v2.7.8](https://github.com/laurent22/joplin/releases/tag/v2.7.8) (Pre-release) - 2022-01-19T09:35:27Z
- Improved: Disable plugin throttling for now ([6bb0318](https://github.com/laurent22/joplin/commit/6bb0318))
@@ -506,6 +486,38 @@ CAUTION: This release will ask you to upgrade your sync target, whether it's Dro
- Revert "Plugins: Add ability to make dialogs fit the application window ([#5219](https://github.com/laurent22/joplin/issues/5219))" as it breaks several plugin webviews.
- Revert "Resolves [#4810](https://github.com/laurent22/joplin/issues/4810), Resolves [#4610](https://github.com/laurent22/joplin/issues/4610): Fix AWS S3 sync error and upgrade framework to v3 ([#5212](https://github.com/laurent22/joplin/issues/5212))" due to incompatibility with some AWS providers.
## [v2.4.1](https://github.com/laurent22/joplin/releases/tag/v2.4.1) (Pre-release) - 2021-08-21T11:52:30Z
- New: Add Sync Wizard dialog ([fe4900d](https://github.com/laurent22/joplin/commit/fe4900d))
- New: Add a way to disable a master key ([7faa58e](https://github.com/laurent22/joplin/commit/7faa58e))
- New: Added "None" sync target to allow disabling synchronisation ([f5f05e6](https://github.com/laurent22/joplin/commit/f5f05e6))
- Improved: Improved sync locks so that they do not prevent upgrading a sync target ([06ed58b](https://github.com/laurent22/joplin/commit/06ed58b))
- Improved: Place code-block background in the back in Markdown editor ([#5322](https://github.com/laurent22/joplin/issues/5322) by [@CalebJohn](https://github.com/CalebJohn))
- Improved: Plugins: Improved support for fitToContent webview property ([#5298](https://github.com/laurent22/joplin/issues/5298)) ([#5288](https://github.com/laurent22/joplin/issues/5288) by [@Ahmad45123](https://github.com/Ahmad45123))
- Improved: Removes markdown inline code padding ([#5331](https://github.com/laurent22/joplin/issues/5331) by [@CalebJohn](https://github.com/CalebJohn))
- Improved: Split code block class in two ([#5359](https://github.com/laurent22/joplin/issues/5359) by [@CalebJohn](https://github.com/CalebJohn))
- Fixed: Add more specific classes for CodeMirror elements ([#5333](https://github.com/laurent22/joplin/issues/5333)) ([#5327](https://github.com/laurent22/joplin/issues/5327) by [@CalebJohn](https://github.com/CalebJohn))
- Fixed: Fixed file paths when exporting as HTML ([#5325](https://github.com/laurent22/joplin/issues/5325))
- Fixed: GotoAnything is not working on first try ([#5184](https://github.com/laurent22/joplin/issues/5184))
## [v2.3.5](https://github.com/laurent22/joplin/releases/tag/v2.3.5) - 2021-08-17T06:43:30Z
- Improved: Allow setting a max width for the editor content ([8063c94](https://github.com/laurent22/joplin/commit/8063c94))
- Improved: Improved Markdown editor code styling, and add CSS classes for code ([#5314](https://github.com/laurent22/joplin/issues/5314)) ([#5297](https://github.com/laurent22/joplin/issues/5297) by [@CalebJohn](https://github.com/CalebJohn))
- Fixed: Bump hightlight.js to v11.2 ([#5278](https://github.com/laurent22/joplin/issues/5278)) ([#5245](https://github.com/laurent22/joplin/issues/5245) by Roman Musin)
- Fixed (Regression): Fixed file paths when exporting as HTML ([#5325](https://github.com/laurent22/joplin/issues/5325))
## [v2.3.3](https://github.com/laurent22/joplin/releases/tag/v2.3.3) - 2021-08-14T09:19:40Z
CAUTION: This release will ask you to upgrade your sync target, whether it's Dropbox, OneDrive, Joplin Cloud, etc. Once it is done, only apps version 2.3+ will be able to sync with it, so make sure you are ready to upgrade all your apps before installing this version.
- Improved: Improved E2EE usability by making its state a property of the sync target ([#5276](https://github.com/laurent22/joplin/issues/5276))
## [v2.2.7](https://github.com/laurent22/joplin/releases/tag/v2.2.7) - 2021-08-11T11:03:26Z
- Revert "Plugins: Add ability to make dialogs fit the application window ([#5219](https://github.com/laurent22/joplin/issues/5219))" as it breaks several plugin webviews.
- Revert "Resolves [#4810](https://github.com/laurent22/joplin/issues/4810), Resolves [#4610](https://github.com/laurent22/joplin/issues/4610): Fix AWS S3 sync error and upgrade framework to v3 ([#5212](https://github.com/laurent22/joplin/issues/5212))" due to incompatibility with some AWS providers.
## [v2.2.6](https://github.com/laurent22/joplin/releases/tag/v2.2.6) (Pre-release) - 2021-08-09T19:29:20Z
- Improved: Fix AWS S3 sync error and upgrade framework to v3 ([#5212](https://github.com/laurent22/joplin/issues/5212)) ([#4810](https://github.com/laurent22/joplin/issues/4810) by Lee Matos)

View File

@@ -1,5 +1,25 @@
# Joplin Android app changelog
## [android-v2.11.22](https://github.com/laurent22/joplin/releases/tag/android-v2.11.22) (Pre-release) - 2023-05-14T13:44:28Z
- Fixed: Fix "Download interrupted" error (b023f58)
## [android-v2.11.21](https://github.com/laurent22/joplin/releases/tag/android-v2.11.21) (Pre-release) - 2023-05-14T11:05:15Z
- Improved: Updated packages react-native-paper (v5.6.0)
## [android-v2.11.16](https://github.com/laurent22/joplin/releases/tag/android-v2.11.16) (Pre-release) - 2023-05-12T12:43:08Z
- Improved: Sync as soon as the app starts, and immediately after changing a note (3eb44d2)
## [android-v2.11.14](https://github.com/laurent22/joplin/releases/tag/android-v2.11.14) (Pre-release) - 2023-05-10T12:24:40Z
- Improved: Translate Welcome notes (#8154)
## [android-v2.11.13](https://github.com/laurent22/joplin/releases/tag/android-v2.11.13) (Pre-release) - 2023-05-08T20:28:29Z
- Improved: Tells whether Hermes engine is enabled or not (5ecae17)
## [android-v2.11.10](https://github.com/laurent22/joplin/releases/tag/android-v2.11.10) (Pre-release) - 2023-05-08T10:26:14Z
- Improved: Disable Hermes engine (e9e9986)

View File

@@ -495,6 +495,22 @@
"created_at": "2023-05-04T08:41:49Z",
"repoId": 79162682,
"pullRequestNo": 8126
},
{
"name": "marph91",
"id": 33229141,
"comment_id": 1544068856,
"created_at": "2023-05-11T14:17:44Z",
"repoId": 79162682,
"pullRequestNo": 8163
},
{
"name": "Ardakilic",
"id": 2063957,
"comment_id": 1544540833,
"created_at": "2023-05-11T19:10:57Z",
"repoId": 79162682,
"pullRequestNo": 8162
}
]
}

View File

@@ -10,7 +10,7 @@ Your download of <span class="downloaded-filename">Joplin</span> is in progress.
Access your notes on Windows, macOS or Linux.
<!-- DESKTOP-DOWNLOAD-LINKS --><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> <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> <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><!-- DESKTOP-DOWNLOAD-LINKS -->
<!-- DESKTOP-DOWNLOAD-LINKS --><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> <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> <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><!-- DESKTOP-DOWNLOAD-LINKS -->
</div>

5096
yarn.lock

File diff suppressed because it is too large Load Diff