You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-24 20:19:10 +02:00
Compare commits
55 Commits
webpack_el
...
android-v2
Author | SHA1 | Date | |
---|---|---|---|
|
9160f0e2a2 | ||
|
92272533e5 | ||
|
648e091523 | ||
|
b023f58497 | ||
|
c639791d4f | ||
|
6d52288e28 | ||
|
3e2f4b163b | ||
|
e0dbd198d8 | ||
|
a76c5c8746 | ||
|
20b43ce56e | ||
|
8215ce14c6 | ||
|
3fead0a8a7 | ||
|
c8bf3e8583 | ||
|
42dee6c275 | ||
|
6f3f866f78 | ||
|
adf2e7322d | ||
|
0df170926a | ||
|
20a26732a9 | ||
|
0da3e91a29 | ||
|
9ad56dc373 | ||
|
29dab26dce | ||
|
10e8fbcdab | ||
|
6c4f566765 | ||
|
78df302e86 | ||
|
f52dd4f098 | ||
|
dbab786c7b | ||
|
3eb44d27b2 | ||
|
52bea115ac | ||
|
19bdda25c6 | ||
|
b26bc9ed5f | ||
|
865cedc24f | ||
|
33f0811ad2 | ||
|
8cedf27fea | ||
|
052a829167 | ||
|
5371c97ccd | ||
|
c53b957293 | ||
|
e6f8dc96df | ||
|
21648b1b1b | ||
|
83db6f6596 | ||
|
3adfa574c0 | ||
|
4d0ffc5beb | ||
|
69f9b160dd | ||
|
c17b02cfb5 | ||
|
6dd57b63a6 | ||
|
248c8014c8 | ||
|
5fe2766a6b | ||
|
000e0ad517 | ||
|
c047375143 | ||
|
bd9e62cbd2 | ||
|
5ecae17538 | ||
|
35037e2dc9 | ||
|
059202be09 | ||
|
6672f63981 | ||
|
f390eca4de | ||
|
edc5e33559 |
@@ -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
|
||||
|
24
.github/scripts/run_ci.sh
vendored
24
.github/scripts/run_ci.sh
vendored
@@ -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
4
.gitignore
vendored
@@ -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
|
||||
|
@@ -15,7 +15,6 @@
|
||||
"@joplin/tools",
|
||||
"@joplin/react-native-saf-x",
|
||||
"@joplin/react-native-alarm-notification",
|
||||
"@joplin/react-native-vosk",
|
||||
"@joplin/utils"
|
||||
]
|
||||
}
|
||||
|
@@ -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() {
|
||||
|
@@ -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
|
||||
|
||||
#-----------------------------------------------------
|
||||
|
10
README.md
10
README.md
@@ -22,11 +22,11 @@ Three types of applications are available: for **desktop** (Windows, macOS and L
|
||||
|
||||
Operating System | Download
|
||||
---|---
|
||||
Windows (32 and 64-bit) | <a href='https://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
|
||||
|
@@ -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,
|
||||
|
1
packages/app-cli/tests/md_to_html/sanitize_13.html
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_13.html
Normal file
@@ -0,0 +1 @@
|
||||
<div class="jop-noMdConv"><svg class="jop-noMdConv"><style class="jop-noMdConv"></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>
|
1
packages/app-cli/tests/md_to_html/sanitize_13.md
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_13.md
Normal file
@@ -0,0 +1 @@
|
||||
<div><svg><style></svg><iframe srcdoc="<script>top.require('child_process').execSync('calc')</script>"></iframe></div>
|
1
packages/app-cli/tests/md_to_html/sanitize_14.html
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_14.html
Normal file
@@ -0,0 +1 @@
|
||||
<a href="#" class="jop-noMdConv">XSS</a>
|
1
packages/app-cli/tests/md_to_html/sanitize_14.md
Normal file
1
packages/app-cli/tests/md_to_html/sanitize_14.md
Normal file
@@ -0,0 +1 @@
|
||||
<a data-from-md="" href="javascript:top.require('child_process').execSync('open -a Calculator')">XSS</a>
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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)} />;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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"
|
||||
|
@@ -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}") {
|
||||
|
@@ -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} />
|
||||
|
@@ -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
|
||||
|
@@ -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 = [];
|
||||
|
@@ -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",
|
||||
|
@@ -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.
|
||||
|
@@ -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) => {
|
||||
|
@@ -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
|
||||
|
@@ -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');
|
||||
|
@@ -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') {
|
||||
|
@@ -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;
|
122
packages/lib/WelcomeUtils.ts
Normal file
122
packages/lib/WelcomeUtils.ts
Normal 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;
|
18
packages/lib/locale.test.ts
Normal file
18
packages/lib/locale.test.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
@@ -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 };
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
4
packages/react-native-vosk/.gitattributes
vendored
4
packages/react-native-vosk/.gitattributes
vendored
@@ -1,4 +0,0 @@
|
||||
*.pbxproj -text
|
||||
# specific for windows script files
|
||||
*.bat text eol=crlf
|
||||
*.a filter=lfs diff=lfs merge=lfs -text
|
@@ -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}}
|
72
packages/react-native-vosk/.gitignore
vendored
72
packages/react-native-vosk/.gitignore
vendored
@@ -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-*
|
@@ -1 +0,0 @@
|
||||
{}
|
@@ -1,3 +0,0 @@
|
||||
# Override Yarn command so we can automatically setup the repo on running `yarn`
|
||||
|
||||
yarn-path "scripts/bootstrap.js"
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
||||

|
||||
|
||||
Then put the model folder inside the assets folder created. In your file tree it should be located in android\app\src\main\assets. So, if you downloaded the french model named model-fr-fr, you should access the model by going to android\app\src\main\assets\model-fr-fr. In Android studio, your project structure should be like that:
|
||||

|
||||
|
||||
You can import as many models as you want.
|
||||
|
||||
### iOS
|
||||
In order to let the project work, you're going to need the iOS library. Mail contact@alphacephei.com to get the libraries. You're going to have a libvosk.xcframework file (or folder for not mac users), just copy it in the ios folder of the module (node_modules/react-native-vosk/ios/libvosk.xcframework). Then run in your root project:
|
||||
```sh
|
||||
npm run pods
|
||||
```
|
||||
|
||||
In XCode, right-click on your project folder, and click on "Add files to [your project name]".
|
||||
|
||||

|
||||
|
||||
Then navigate to your model folder. You can navigate to your Android assets folder as mentionned before, and chose your model here. It will avoid to have the model copied twice in your project. If you don't use the Android build, you can just put the model wherever you want, and select it.
|
||||
|
||||

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

|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import VoiceRecognition from 'react-native-voice-recognition';
|
||||
|
||||
// ...
|
||||
|
||||
const voiceRecognition = new VoiceRecognition();
|
||||
|
||||
voiceRecognition.loadModel('model-fr-fr').then(() => {
|
||||
// we can use promise...
|
||||
voiceRecognition
|
||||
.start()
|
||||
.then((res: any) => {
|
||||
console.log('Result is: ' + res);
|
||||
})
|
||||
|
||||
// ... or events
|
||||
const resultEvent = vosk.onResult((res) => {
|
||||
console.log('A onResult event has been caught: ' + res.data);
|
||||
});
|
||||
|
||||
// Don't forget to call resultEvent.remove(); when needed
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
Note that `start()` method will ask for audio record permission.
|
||||
|
||||
[Complete example...](https://github.com/riderodd/react-native-vosk/blob/main/example/src/App.tsx)
|
||||
|
||||
### Methods
|
||||
|
||||
| Method | Argument | Return | Description |
|
||||
|---|---|---|---|
|
||||
| `loadModel` | `path: string` | `Promise` | Loads the voice model used for recognition, it is required before using start method |
|
||||
| `start` | `grammar: string[]` or `none` | `Promise` | Starts the voice recognition and returns the recognized text as a promised string, you can recognize specific words using the `grammar` argument (ex: ["left", "right"]) according to kaldi's documentation |
|
||||
| `stop` | `none` | `none` | Stops the recognition |
|
||||
|
||||
### Events
|
||||
|
||||
| Method | Promise return | Description |
|
||||
|---|---|---|
|
||||
| `onResult` | The recognized word as a `string` | Triggers on voice recognition result |
|
||||
| `onFinalResult` | The recognized word as a `string` | Triggers if stopped using `stop()` method |
|
||||
| `onError` | The error that occured as a `string` or `exception` | Triggers if an error occured |
|
||||
| `onTimeout` | "timeout" `string` | Triggers on timeout |
|
||||
|
||||
#### Example
|
||||
|
||||
```js
|
||||
const resultEvent = voiceRecognition.onResult((res) => {
|
||||
console.log('A onResult event has been caught: ' + res.data);
|
||||
});
|
||||
|
||||
resultEvent.remove();
|
||||
```
|
||||
|
||||
Don't forget to remove the event listener once you don't need it anymore.
|
||||
|
||||
## Contributing
|
||||
|
||||
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
Vosk_kotlinVersion=1.7.0
|
||||
Vosk_minSdkVersion=21
|
||||
Vosk_targetSdkVersion=31
|
||||
Vosk_compileSdkVersion=31
|
||||
Vosk_ndkversion=21.4.7075529
|
@@ -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>
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -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 |
@@ -1,4 +0,0 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import "vosk_api.h"
|
@@ -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
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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 */;
|
||||
}
|
@@ -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
|
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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 */
|
@@ -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
|
95
packages/react-native-vosk/lib/commonjs/index.js
vendored
95
packages/react-native-vosk/lib/commonjs/index.js
vendored
@@ -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
|
88
packages/react-native-vosk/lib/module/index.js
vendored
88
packages/react-native-vosk/lib/module/index.js
vendored
@@ -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
|
@@ -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 {};
|
@@ -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"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
@@ -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
|
29
packages/react-native-vosk/scripts/bootstrap.js
vendored
29
packages/react-native-vosk/scripts/bootstrap.js
vendored
@@ -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;
|
@@ -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 = [];
|
||||
};
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"exclude": ["example"]
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
@@ -144,6 +144,11 @@ class HtmlUtils {
|
||||
.replace(/</g, '<');
|
||||
}
|
||||
|
||||
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, '<'));
|
||||
} else {
|
||||
output.push(htmlentities(decodedText));
|
||||
}
|
||||
|
@@ -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.`);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
});
|
162
packages/tools/build-welcome.ts
Normal file
162
packages/tools/build-welcome.ts
Normal 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);
|
||||
});
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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",
|
||||
|
@@ -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}`);
|
||||
|
@@ -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);
|
||||
|
||||
|
67
readme/_i18n/fr_FR/welcome/1_welcome_to_joplin.md
Normal file
67
readme/_i18n/fr_FR/welcome/1_welcome_to_joplin.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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`
|
||||
|
||||

|
||||
|
||||
### 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.
|
@@ -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.
|
25
readme/_i18n/fr_FR/welcome/3_synchronising_your_notes.md
Normal file
25
readme/_i18n/fr_FR/welcome/3_synchronising_your_notes.md
Normal 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.
|
70
readme/_i18n/fr_FR/welcome/4_tips.md
Normal file
70
readme/_i18n/fr_FR/welcome/4_tips.md
Normal 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
|
||||
|
||||

|
||||
|
||||
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.
|
21
readme/_i18n/fr_FR/welcome/5_privacy.md
Normal file
21
readme/_i18n/fr_FR/welcome/5_privacy.md
Normal 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/).
|
@@ -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 -> 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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
@@ -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>
|
||||
|
||||
|
Reference in New Issue
Block a user