You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-30 20:39:46 +02:00
Compare commits
94 Commits
cli-v2.13.
...
cli-v2.13.
Author | SHA1 | Date | |
---|---|---|---|
|
9bad668cc5 | ||
|
c18c31ab7f | ||
|
7c24a2f4be | ||
|
56438ea644 | ||
|
7f9bc1e15c | ||
|
b1c8cb5632 | ||
|
f0a1b41794 | ||
|
02982464a6 | ||
|
62e317db05 | ||
|
e0795748a9 | ||
|
67070ed3d5 | ||
|
fec8c6131c | ||
|
24ed5bda63 | ||
|
dbb354ad10 | ||
|
92dccbe98d | ||
|
9b775d77f6 | ||
|
4fd6937d05 | ||
|
7230f0e698 | ||
|
ada82538ee | ||
|
e7dd981db6 | ||
|
767bf9f002 | ||
|
f698068587 | ||
|
d0955b4ca2 | ||
|
18e86a7ba3 | ||
|
f9a1ab4d40 | ||
|
062d0898a0 | ||
|
3b51b4fd72 | ||
|
1a78ff4398 | ||
|
544af8d118 | ||
|
2616c377a9 | ||
|
4a63331306 | ||
|
48621443ec | ||
|
79fd66b94c | ||
|
6a6c8c1d83 | ||
|
cf19dacbaf | ||
|
50925abc40 | ||
|
c80cbaa32f | ||
|
f7cb1aef4b | ||
|
96d5d1dfab | ||
|
98d608fec5 | ||
|
1af46b0246 | ||
|
1e530b74d4 | ||
|
e61c4acce5 | ||
|
184499711d | ||
|
2c0181d097 | ||
|
06ea12adb3 | ||
|
9923e5c821 | ||
|
9a06e59cfe | ||
|
80a2cd91f4 | ||
|
df9ed3e487 | ||
|
368d0130f6 | ||
|
824e1b44dd | ||
|
ccf1c8ee31 | ||
|
d5f6d83f6d | ||
|
5d422f85c8 | ||
|
78aeb46d56 | ||
|
091bf45149 | ||
|
8d9d24740b | ||
|
21e5f88cb2 | ||
|
5d4259d064 | ||
|
9ac03ec33a | ||
|
e760276341 | ||
|
206f35ffe5 | ||
|
ddf716479d | ||
|
ec7f94df25 | ||
|
bcbba0973f | ||
|
bd1ddb8522 | ||
|
fb47398554 | ||
|
10356f4009 | ||
|
ba83fca47a | ||
|
b01295f0fd | ||
|
b928e614cc | ||
|
973b9c354c | ||
|
c12444d6e8 | ||
|
1401d28f82 | ||
|
335269f92d | ||
|
6211606a22 | ||
|
5f7d438ac1 | ||
|
ee2df96cfb | ||
|
692e925997 | ||
|
39803f53a0 | ||
|
b3591808b7 | ||
|
2427677fd5 | ||
|
9a051effcd | ||
|
e6e9f92e01 | ||
|
ca6762c891 | ||
|
76d07beb27 | ||
|
f3daa7f0e4 | ||
|
041ad22443 | ||
|
6cd0938ee4 | ||
|
c3dc30ee5d | ||
|
37c925dcf2 | ||
|
05bd51f85c | ||
|
cfbc37df8d |
@@ -383,6 +383,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
|
||||
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
|
||||
packages/app-desktop/integration-tests/models/SettingsScreen.js
|
||||
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
|
||||
packages/app-desktop/integration-tests/util/createStartupArgs.js
|
||||
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
|
||||
packages/app-desktop/integration-tests/util/test.js
|
||||
packages/app-desktop/playwright.config.js
|
||||
packages/app-desktop/plugins/GotoAnything.js
|
||||
@@ -508,7 +510,10 @@ packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.ios.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
packages/app-mobile/tools/buildInjectedJs.js
|
||||
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
|
||||
packages/app-mobile/tools/buildInjectedJs/constants.js
|
||||
packages/app-mobile/tools/buildInjectedJs/copyJs.js
|
||||
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
|
||||
packages/app-mobile/utils/ShareExtension.js
|
||||
packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
|
@@ -157,6 +157,8 @@ module.exports = {
|
||||
// In user-facing text, it should be "notebook".
|
||||
'id-denylist': ['error', 'err', 'notebook', 'notebooks'],
|
||||
'prefer-arrow-callback': ['error'],
|
||||
|
||||
'no-constant-binary-expression': ['error'],
|
||||
},
|
||||
'plugins': [
|
||||
'react',
|
||||
|
62
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
62
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
name: Bug Report
|
||||
description: Report a reproducible bug or regression in Joplin.
|
||||
labels: ['bug']
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: "Operating system"
|
||||
multiple: false
|
||||
options:
|
||||
- "Windows"
|
||||
- "macOS"
|
||||
- "Linux"
|
||||
- "Android"
|
||||
- "iOS"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: "Joplin version"
|
||||
placeholder: "For example 2.3.6"
|
||||
description:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: desktop-about-content
|
||||
attributes:
|
||||
label: "Desktop version info"
|
||||
description: "If this issue is about the **desktop app**, please open the \"About\" dialog under the \"Help\" or \"Joplin\" menu and copy its content here."
|
||||
placeholder: "Joplin 2.13.5 (dev, darwin)\n\nClient ID: ..."
|
||||
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current behaviour
|
||||
description: What did Joplin do? Include screenshots and video recordings for UI problems if needed. If you are reporting a clipper bug, please include an example URL that shows the issue.
|
||||
placeholder: |
|
||||
1. This
|
||||
2. Then that
|
||||
3. Then this
|
||||
4. Etc.
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: What did you expect Joplin to do?
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: "If relevant, please provide a log file as described here: https://joplinapp.org/help/apps/debugging"
|
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
52
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,52 +0,0 @@
|
||||
---
|
||||
name: "\U0001F41B Bug Report"
|
||||
about: Report a reproducible bug or regression in Joplin.
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Please provide a clear and concise description of what the bug is. (In the section Steps To Reproduce.)
|
||||
Include screenshots for UI problems if needed.
|
||||
DO NOT create screenshots of text !!! Copy and paste the text into a code block.
|
||||
Please test using the latest Joplin release to make sure your issue has not already been fixed.
|
||||
-->
|
||||
|
||||
<!--
|
||||
IMPORTANT: If you are reporting a clipper bug, please include an example URL that shows the issue.
|
||||
Without the URL the issue is likely to be closed.
|
||||
-->
|
||||
|
||||
## Environment
|
||||
|
||||
Joplin version:
|
||||
Platform:
|
||||
OS specifics:
|
||||
|
||||
<!--
|
||||
Platform can be one of: macOS, Linux, Windows, Android, iOS, terminal (or a combination)
|
||||
OS specifics: e.g. OS version, Linux distribution, Android/iOS version...
|
||||
-->
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
<!--
|
||||
Issues without reproduction steps are likely to stall.
|
||||
-->
|
||||
|
||||
## Describe what you expected to happen
|
||||
|
||||
|
||||
|
||||
## Logfile
|
||||
|
||||
<!--
|
||||
Please attach a debug log. Issues without a debug log are likely to stall.
|
||||
For information on how to collect a log file: https://joplinapp.org/help/apps/debugging/
|
||||
-->
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: "\U0001F914 Feature requests and support"
|
||||
url: https://discourse.joplinapp.org/
|
||||
about: I have a question or feature request …
|
||||
- name: Feature Requests
|
||||
url: https://discourse.joplinapp.org/c/features/
|
||||
about: Discuss ideas for new features or changes
|
||||
- name: Support
|
||||
url: https://discourse.joplinapp.org/c/support/
|
||||
about: Please ask for help here
|
4
.github/scripts/run_ci.sh
vendored
4
.github/scripts/run_ci.sh
vendored
@@ -75,6 +75,10 @@ if [ "$IS_PULL_REQUEST" == "1" ] || [ "$IS_DEV_BRANCH" = "1" ]; then
|
||||
if [ "$IS_LINUX" == "1" ]; then
|
||||
echo "Running Joplin Server tests using PostgreSQL..."
|
||||
sudo docker-compose --file docker-compose.db-dev.yml up -d
|
||||
cmdResult=$?
|
||||
if [ $cmdResult -ne 0 ]; then
|
||||
exit $cmdResult
|
||||
fi
|
||||
export JOPLIN_TESTS_SERVER_DB=pg
|
||||
else
|
||||
echo "Running Joplin Server tests using SQLite..."
|
||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -365,6 +365,8 @@ packages/app-desktop/integration-tests/models/MainScreen.js
|
||||
packages/app-desktop/integration-tests/models/NoteEditorScreen.js
|
||||
packages/app-desktop/integration-tests/models/SettingsScreen.js
|
||||
packages/app-desktop/integration-tests/util/activateMainMenuItem.js
|
||||
packages/app-desktop/integration-tests/util/createStartupArgs.js
|
||||
packages/app-desktop/integration-tests/util/firstNonDevToolsWindow.js
|
||||
packages/app-desktop/integration-tests/util/test.js
|
||||
packages/app-desktop/playwright.config.js
|
||||
packages/app-desktop/plugins/GotoAnything.js
|
||||
@@ -490,7 +492,10 @@ packages/app-mobile/services/profiles/index.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.android.js
|
||||
packages/app-mobile/services/voiceTyping/vosk.ios.js
|
||||
packages/app-mobile/setupQuickActions.js
|
||||
packages/app-mobile/tools/buildInjectedJs.js
|
||||
packages/app-mobile/tools/buildInjectedJs/BundledFile.js
|
||||
packages/app-mobile/tools/buildInjectedJs/constants.js
|
||||
packages/app-mobile/tools/buildInjectedJs/copyJs.js
|
||||
packages/app-mobile/tools/buildInjectedJs/gulpTasks.js
|
||||
packages/app-mobile/utils/ShareExtension.js
|
||||
packages/app-mobile/utils/ShareUtils.test.js
|
||||
packages/app-mobile/utils/ShareUtils.js
|
||||
|
@@ -205,9 +205,16 @@ if command -v lsb_release &> /dev/null; then
|
||||
# Check for "The SUID sandbox helper binary was found, but is not configured correctly" problem.
|
||||
# It is present in Debian 1X. A (temporary) patch will be applied at .desktop file
|
||||
# Linux Mint 4 Debbie is based on Debian 10 and requires the same param handling.
|
||||
if [[ $DISTVER =~ Debian1. ]] || [ "$DISTVER" = "Linuxmint4" ] && [ "$DISTCODENAME" = "debbie" ] || [ "$DISTVER" = "CentOS" ] && [[ "$DISTMAJOR" =~ 6|7 ]]
|
||||
#
|
||||
# This also works around Ubuntu 23.10+'s restrictions on unprivileged user namespaces. Electron
|
||||
# uses these to sandbox processes. Unfortunately, it doesn't look like we can get around this
|
||||
# without writing the AppImage to a non-user-writable location (without invalidating other security
|
||||
# controls). See https://discourse.joplinapp.org/t/possible-future-requirement-for-no-sandbox-flag-for-ubuntu-23-10/.
|
||||
if [[ $DISTVER = "Ubuntu23.10" || $DISTVER =~ Debian1. || ( "$DISTVER" = "Linuxmint4" && "$DISTCODENAME" = "debbie" ) || ( "$DISTVER" = "CentOS" && "$DISTMAJOR" =~ 6|7 ) ]]
|
||||
then
|
||||
SANDBOXPARAM="--no-sandbox"
|
||||
print "${COLOR_YELLOW}WARNING${COLOR_RESET} Electron sandboxing disabled."
|
||||
print " See https://discourse.joplinapp.org/t/32160/5 for details."
|
||||
fi
|
||||
fi
|
||||
|
||||
|
@@ -42,8 +42,8 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/215668?s=96&v=4"/></br>[avanderberg](https://github.com/avanderberg) | <img width="50" src="https://avatars2.githubusercontent.com/u/67130?s=96&v=4"/></br>[chr15m](https://github.com/chr15m) | <img width="50" src="https://avatars2.githubusercontent.com/u/2793530?s=96&v=4"/></br>[CyberXZT](https://github.com/CyberXZT) | <img width="50" src="https://avatars2.githubusercontent.com/u/1307332?s=96&v=4"/></br>[dbrandonjohnson](https://github.com/dbrandonjohnson) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/14873877?s=96&v=4"/></br>[dchecks](https://github.com/dchecks) | <img width="50" src="https://avatars2.githubusercontent.com/u/56287?s=96&v=4"/></br>[fats](https://github.com/fats) | <img width="50" src="https://avatars2.githubusercontent.com/u/8030470?s=96&v=4"/></br>[Galliver7](https://github.com/Galliver7) | <img width="50" src="https://avatars2.githubusercontent.com/u/64712218?s=96&v=4"/></br>[Hegghammer](https://github.com/Hegghammer) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/1310474?s=96&v=4"/></br>[jknowles](https://github.com/jknowles) | <img width="50" src="https://avatars2.githubusercontent.com/u/11947658?s=96&v=4"/></br>[KentBrockman](https://github.com/KentBrockman) | <img width="50" src="https://avatars2.githubusercontent.com/u/24908652?s=96&v=4"/></br>[konishi-t](https://github.com/konishi-t) | <img width="50" src="https://avatars2.githubusercontent.com/u/42319182?s=96&v=4"/></br>[marcdw1289](https://github.com/marcdw1289) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) | <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | | |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/126279083?s=96&v=4"/></br>[matmoly](https://github.com/matmoly) | <img width="50" src="https://avatars2.githubusercontent.com/u/1788010?s=96&v=4"/></br>[maxtruxa](https://github.com/maxtruxa) | <img width="50" src="https://avatars2.githubusercontent.com/u/4560672?s=96&v=4"/></br>[mu88](https://github.com/mu88) | <img width="50" src="https://avatars2.githubusercontent.com/u/31054972?s=96&v=4"/></br>[saarantras](https://github.com/saarantras) |
|
||||
| <img width="50" src="https://avatars2.githubusercontent.com/u/327998?s=96&v=4"/></br>[sif](https://github.com/sif) | <img width="50" src="https://avatars2.githubusercontent.com/u/765564?s=96&v=4"/></br>[taskcruncher](https://github.com/taskcruncher) | <img width="50" src="https://avatars2.githubusercontent.com/u/333944?s=96&v=4"/></br>[tateisu](https://github.com/tateisu) | |
|
||||
<!-- SPONSORS-GITHUB -->
|
||||
|
||||
# Community
|
||||
|
@@ -6,7 +6,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
image: postgres:16
|
||||
command: postgres -c work_mem=100000
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
@@ -18,7 +18,7 @@ services:
|
||||
- POSTGRES_PORT=5432
|
||||
- POSTGRES_HOST=localhost
|
||||
db:
|
||||
image: postgres:15
|
||||
image: postgres:16
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
|
@@ -19,7 +19,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
image: postgres:16
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
|
@@ -367,6 +367,12 @@
|
||||
"type": "shell",
|
||||
"command": "cd ${workspaceFolder}/packages/server && yarn tsc",
|
||||
"group": "build",
|
||||
},
|
||||
{
|
||||
"label": "transpile-lib",
|
||||
"type": "shell",
|
||||
"command": "cd ${workspaceFolder}/packages/lib && yarn tsc",
|
||||
"group": "build",
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -395,6 +401,19 @@
|
||||
"APP_BASE_URL": "http://joplincloud.local:22300",
|
||||
"API_BASE_URL": "http://api.joplincloud.local:22300",
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "lib: debug test file",
|
||||
"preLaunchTask": "transpile-lib",
|
||||
"program": "${workspaceFolder}/packages/lib/node_modules/.bin/jest",
|
||||
"args": [
|
||||
"${fileBasenameNoExtension}",
|
||||
"--config",
|
||||
"packages/lib/jest.config.js",
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.13.1",
|
||||
"version": "2.13.2",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -73,7 +73,7 @@
|
||||
"@joplin/tools": "~2.13",
|
||||
"@types/fs-extra": "11.0.3",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/node": "18.18.6",
|
||||
"@types/node": "18.18.7",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
"gulp": "4.0.2",
|
||||
"jest": "29.7.0",
|
||||
|
@@ -36,6 +36,10 @@ describe('HtmlToMd', () => {
|
||||
htmlToMdOptions.preserveImageTagsWithSize = true;
|
||||
}
|
||||
|
||||
if (htmlFilename.indexOf('preserve_nested_tables') === 0) {
|
||||
htmlToMdOptions.preserveNestedTables = true;
|
||||
}
|
||||
|
||||
const html = await readFile(htmlPath, 'utf8');
|
||||
let expectedMd = await readFile(mdPath, 'utf8');
|
||||
|
||||
|
@@ -0,0 +1,19 @@
|
||||
<body>
|
||||
<table border="5px" bordercolor="#8707B0">
|
||||
<tr>
|
||||
<td>Left side of the main table</td>
|
||||
<td>
|
||||
<table border="5px" bordercolor="#F35557">
|
||||
<h4 align="center">Nested Table</h4>
|
||||
<tr>
|
||||
<td>nested table C1</td>
|
||||
<td>nested table C2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>nested table</td>
|
||||
<td>nested table</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
@@ -0,0 +1 @@
|
||||
<table border="5px" bordercolor="#8707B0"><tbody><tr><td>Left side of the main table</td><td><h4 align="center">Nested Table</h4><table border="5px" bordercolor="#F35557"><tbody><tr><td>nested table C1</td><td>nested table C2</td></tr><tr><td>nested table</td><td>nested table</td></tr></tbody></table></td></tr></tbody></table>
|
@@ -26,7 +26,7 @@ const { FoldersScreenUtils } = require('@joplin/lib/folders-screen-utils.js');
|
||||
import Folder from '@joplin/lib/models/Folder';
|
||||
import Tag from '@joplin/lib/models/Tag';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
const packageInfo: PackageInfo = require('./packageInfo.js');
|
||||
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker';
|
||||
import ClipperServer from '@joplin/lib/ClipperServer';
|
||||
const { webFrame } = require('electron');
|
||||
@@ -68,6 +68,7 @@ import path = require('path');
|
||||
import { checkPreInstalledDefaultPlugins, installDefaultPlugins, setSettingsForDefaultPlugins } from '@joplin/lib/services/plugins/defaultPlugins/defaultPluginsUtils';
|
||||
import userFetcher, { initializeUserFetcher } from '@joplin/lib/utils/userFetcher';
|
||||
import { parseNotesParent } from '@joplin/lib/reducer';
|
||||
import { PackageInfo } from '@joplin/lib/versionInfo';
|
||||
|
||||
const pluginClasses = [
|
||||
require('./plugins/GotoAnything').default,
|
||||
|
@@ -5,8 +5,9 @@ import bridge from './services/bridge';
|
||||
import KvStore from '@joplin/lib/services/KvStore';
|
||||
import * as ArrayUtils from '@joplin/lib/ArrayUtils';
|
||||
import { CheckForUpdateOptions, extractVersionInfo, GitHubRelease } from './utils/checkForUpdatesUtils';
|
||||
const packageInfo = require('./packageInfo.js');
|
||||
import { PackageInfo } from '@joplin/lib/versionInfo';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
const packageInfo: PackageInfo = require('./packageInfo.js');
|
||||
|
||||
const logger = Logger.create('checkForUpdates');
|
||||
|
||||
|
@@ -92,7 +92,15 @@ export default function Sidebar(props: Props) {
|
||||
function renderButton(section: any) {
|
||||
const selected = props.selection === section.name;
|
||||
return (
|
||||
<StyledListItem key={section.name} isSubSection={Setting.isSubSection(section.name)} selected={selected} onClick={() => { props.onSelectionChange({ section: section }); }}>
|
||||
<StyledListItem
|
||||
key={section.name}
|
||||
href='#'
|
||||
role='tab'
|
||||
aria-selected={selected}
|
||||
isSubSection={Setting.isSubSection(section.name)}
|
||||
selected={selected}
|
||||
onClick={() => { props.onSelectionChange({ section: section }); }}
|
||||
>
|
||||
<StyledListItemIcon
|
||||
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
|
||||
/>
|
||||
@@ -123,7 +131,7 @@ export default function Sidebar(props: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<StyledRoot role='tablist'>
|
||||
{buttons}
|
||||
</StyledRoot>
|
||||
);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import * as React from 'react';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
|
||||
import PluginService, { Plugins } from '@joplin/lib/services/plugins/PluginService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import restart from '../services/restart';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const packageInfo: PackageInfo = require('../packageInfo.js');
|
||||
const ipcRenderer = require('electron').ipcRenderer;
|
||||
|
||||
interface ErrorInfo {
|
||||
|
@@ -8,7 +8,7 @@ import KeymapService from '@joplin/lib/services/KeymapService';
|
||||
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||
import shim from '@joplin/lib/shim';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import versionInfo from '@joplin/lib/versionInfo';
|
||||
import versionInfo, { PackageInfo } from '@joplin/lib/versionInfo';
|
||||
import makeDiscourseDebugUrl from '@joplin/lib/makeDiscourseDebugUrl';
|
||||
import { ImportModule } from '@joplin/lib/services/interop/Module';
|
||||
import InteropServiceHelper from '../InteropServiceHelper';
|
||||
@@ -25,7 +25,7 @@ import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||
import PluginService, { PluginSettings } from '@joplin/lib/services/plugins/PluginService';
|
||||
import { getListRendererById, getListRendererIds } from '@joplin/lib/services/noteList/renderers';
|
||||
import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const packageInfo: PackageInfo = require('../packageInfo.js');
|
||||
const { clipboard } = require('electron');
|
||||
const Menu = bridge().Menu;
|
||||
|
||||
|
@@ -559,11 +559,21 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
|
||||
const toolbarPluginButtons = pluginCommandNames.length ? ` | ${pluginCommandNames.join(' ')}` : '';
|
||||
|
||||
// The toolbar is going to wrap based on groups of buttons
|
||||
// (delimited by |). It means that if we leave large groups of
|
||||
// buttons towards the end of the toolbar it's going to needlessly
|
||||
// hide many buttons even when there is space. So this is why below,
|
||||
// we create small groups of just one button towards the end.
|
||||
|
||||
const toolbar = [
|
||||
'bold', 'italic', 'joplinHighlight', 'joplinStrikethrough', 'formattingExtras', '|',
|
||||
'link', 'joplinInlineCode', 'joplinCodeBlock', 'joplinAttach', '|',
|
||||
'bullist', 'numlist', 'joplinChecklist', '|',
|
||||
'h1', 'h2', 'h3', 'hr', 'blockquote', 'table', `joplinInsertDateTime${toolbarPluginButtons}`,
|
||||
'h1', 'h2', 'h3', '|',
|
||||
'hr', '|',
|
||||
'blockquote', '|',
|
||||
'table', '|',
|
||||
`joplinInsertDateTime${toolbarPluginButtons}`,
|
||||
];
|
||||
|
||||
const editors = await (window as any).tinymce.init({
|
||||
|
@@ -9,7 +9,10 @@ export async function htmlToMarkdown(markupLanguage: number, html: string, origi
|
||||
|
||||
if (markupLanguage === MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN) {
|
||||
const htmlToMd = new HtmlToMd();
|
||||
newBody = htmlToMd.parse(html, { preserveImageTagsWithSize: true });
|
||||
newBody = htmlToMd.parse(html, {
|
||||
preserveImageTagsWithSize: true,
|
||||
preserveNestedTables: true,
|
||||
});
|
||||
newBody = await Note.replaceResourceExternalToInternalLinks(newBody, { useAbsolutePaths: true });
|
||||
} else {
|
||||
newBody = await Note.replaceResourceExternalToInternalLinks(html, { useAbsolutePaths: true });
|
||||
|
@@ -5,6 +5,8 @@ import SettingsScreen from './models/SettingsScreen';
|
||||
import { _electron as electron } from '@playwright/test';
|
||||
import { writeFile } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import createStartupArgs from './util/createStartupArgs';
|
||||
import firstNonDevToolsWindow from './util/firstNonDevToolsWindow';
|
||||
|
||||
|
||||
test.describe('main', () => {
|
||||
@@ -130,11 +132,9 @@ test.describe('main', () => {
|
||||
|
||||
// We need to write to the force-safe-mode file before opening the Electron app.
|
||||
// Open the app ourselves:
|
||||
const startupArgs = [
|
||||
'main.js', '--env', 'dev', '--profile', profileDirectory,
|
||||
];
|
||||
const startupArgs = createStartupArgs(profileDirectory);
|
||||
const electronApp = await electron.launch({ args: startupArgs });
|
||||
const mainWindow = await electronApp.firstWindow();
|
||||
const mainWindow = await firstNonDevToolsWindow(electronApp);
|
||||
|
||||
const safeModeDisableLink = mainWindow.getByText('Disable safe mode and restart');
|
||||
await safeModeDisableLink.waitFor();
|
||||
|
@@ -0,0 +1,9 @@
|
||||
|
||||
const createStartupArgs = (profileDirectory: string) => {
|
||||
// We need to run with --env dev to disable the single instance check.
|
||||
return [
|
||||
'main.js', '--env', 'dev', '--profile', profileDirectory,
|
||||
];
|
||||
};
|
||||
|
||||
export default createStartupArgs;
|
@@ -0,0 +1,43 @@
|
||||
import { ElectronApplication, Page } from '@playwright/test';
|
||||
|
||||
const isDevTools = async (page: Page) => {
|
||||
// It seems that the developer tools window can have titles in different
|
||||
// formats (e.g. DevTools, Developer Tools).
|
||||
return (await page.title()).match(/Dev(eloper)?\s*Tools/i);
|
||||
};
|
||||
|
||||
const firstNonDevToolsWindow = async (electronApp: ElectronApplication) => {
|
||||
// Wait for the window event as soon as possible -- it's possible that
|
||||
// the window we want will be shown while doing other async checks.
|
||||
const nextNonDevToolsPage = electronApp.waitForEvent('window', {
|
||||
predicate: async page => {
|
||||
return !(await isDevTools(page));
|
||||
},
|
||||
});
|
||||
|
||||
// First use firstWindow -- it's possible that the first window
|
||||
// has already been shown.
|
||||
let mainWindow = await electronApp.firstWindow();
|
||||
|
||||
if (await isDevTools(mainWindow)) {
|
||||
for (const window of electronApp.windows()) {
|
||||
if (!(await isDevTools(window))) {
|
||||
mainWindow = window;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (await isDevTools(mainWindow)) {
|
||||
mainWindow = await nextNonDevToolsPage;
|
||||
}
|
||||
}
|
||||
|
||||
// waitForEvent will throw if no additional windows are created.
|
||||
// Ignore.
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
nextNonDevToolsPage.catch(_error => {});
|
||||
|
||||
return mainWindow;
|
||||
};
|
||||
|
||||
export default firstNonDevToolsWindow;
|
@@ -2,6 +2,8 @@ import { resolve, join, dirname } from 'path';
|
||||
import { remove, mkdirp } from 'fs-extra';
|
||||
import { _electron as electron, Page, ElectronApplication, test as base } from '@playwright/test';
|
||||
import uuid from '@joplin/lib/uuid';
|
||||
import createStartupArgs from './createStartupArgs';
|
||||
import firstNonDevToolsWindow from './firstNonDevToolsWindow';
|
||||
|
||||
|
||||
|
||||
@@ -32,9 +34,7 @@ export const test = base.extend<JoplinFixtures>({
|
||||
},
|
||||
|
||||
electronApp: async ({ profileDirectory }, use) => {
|
||||
const startupArgs = [
|
||||
'main.js', '--env', 'dev', '--profile', profileDirectory,
|
||||
];
|
||||
const startupArgs = createStartupArgs(profileDirectory);
|
||||
const electronApp = await electron.launch({ args: startupArgs });
|
||||
|
||||
await use(electronApp);
|
||||
@@ -44,8 +44,8 @@ export const test = base.extend<JoplinFixtures>({
|
||||
},
|
||||
|
||||
mainWindow: async ({ electronApp }, use) => {
|
||||
const window = await electronApp.firstWindow();
|
||||
await use(window);
|
||||
const mainWindow = await firstNonDevToolsWindow(electronApp);
|
||||
await use(mainWindow);
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.13.5",
|
||||
"version": "2.13.7",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -120,8 +120,8 @@
|
||||
"@playwright/test": "1.38.1",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/node": "18.18.6",
|
||||
"@types/react": "18.2.31",
|
||||
"@types/node": "18.18.7",
|
||||
"@types/react": "18.2.33",
|
||||
"@types/react-redux": "7.1.28",
|
||||
"@types/styled-components": "5.1.29",
|
||||
"electron": "26.5.0",
|
||||
|
@@ -110,8 +110,8 @@ android {
|
||||
applicationId "net.cozic.joplin"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097726
|
||||
versionName "2.13.6"
|
||||
versionCode 2097729
|
||||
versionName "2.13.9"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
@@ -1,10 +1,12 @@
|
||||
const React = require('react');
|
||||
import { useState, useCallback, useMemo } from 'react';
|
||||
|
||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
import { FAB, Portal } from 'react-native-paper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { Dispatch } from 'redux';
|
||||
const Icon = require('react-native-vector-icons/Ionicons').default;
|
||||
|
||||
// eslint-disable-next-line no-undef -- Don't know why it says React is undefined when it's defined above
|
||||
type FABGroupProps = React.ComponentProps<typeof FAB.Group>;
|
||||
|
||||
type OnButtonPress = ()=> void;
|
||||
interface ButtonSpec {
|
||||
@@ -19,6 +21,7 @@ interface ActionButtonProps {
|
||||
|
||||
// If not given, an "add" button will be used.
|
||||
mainButton?: ButtonSpec;
|
||||
dispatch: Dispatch;
|
||||
}
|
||||
|
||||
const defaultOnPress = () => {};
|
||||
@@ -36,10 +39,12 @@ const useIcon = (iconName: string) => {
|
||||
|
||||
const ActionButton = (props: ActionButtonProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const onMenuToggled = useCallback(
|
||||
(state: { open: boolean }) => setOpen(state.open)
|
||||
, [setOpen]);
|
||||
|
||||
const onMenuToggled: FABGroupProps['onStateChange'] = useCallback(state => {
|
||||
props.dispatch({
|
||||
type: 'SIDE_MENU_CLOSE',
|
||||
});
|
||||
setOpen(state.open);
|
||||
}, [setOpen, props.dispatch]);
|
||||
|
||||
const actions = useMemo(() => (props.buttons ?? []).map(button => {
|
||||
return {
|
||||
|
@@ -178,6 +178,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
||||
onRequestClose={() => {
|
||||
closeList();
|
||||
}}
|
||||
supportedOrientations={['landscape', 'portrait']}
|
||||
>
|
||||
<TouchableWithoutFeedback
|
||||
accessibilityElementsHidden={true}
|
||||
|
@@ -48,6 +48,8 @@ interface Props {
|
||||
// See react-native-webview's prop with the same name.
|
||||
mixedContentMode?: 'never' | 'always';
|
||||
|
||||
allowFileAccessFromJs?: boolean;
|
||||
|
||||
// Initial javascript. Must evaluate to true.
|
||||
injectedJavaScript: string;
|
||||
|
||||
@@ -143,6 +145,7 @@ const ExtendedWebView = (props: Props, ref: Ref<WebViewControl>) => {
|
||||
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
|
||||
mixedContentMode={props.mixedContentMode}
|
||||
allowFileAccess={true}
|
||||
allowFileAccessFromFileURLs={props.allowFileAccessFromJs}
|
||||
injectedJavaScript={props.injectedJavaScript}
|
||||
onMessage={props.onMessage}
|
||||
onError={props.onError}
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { useRef, useCallback } from 'react';
|
||||
|
||||
import useSource from './hooks/useSource';
|
||||
import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
|
||||
import useOnMessage, { HandleMessageCallback, HandleScrollCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
|
||||
import useOnResourceLongPress from './hooks/useOnResourceLongPress';
|
||||
|
||||
const React = require('react');
|
||||
import { View } from 'react-native';
|
||||
import BackButtonDialogBox from '../BackButtonDialogBox';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import ExtendedWebView from '../ExtendedWebView';
|
||||
import ExtendedWebView, { WebViewControl } from '../ExtendedWebView';
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
@@ -18,11 +18,13 @@ interface Props {
|
||||
highlightedKeywords: string[];
|
||||
noteResources: any;
|
||||
paddingBottom: number;
|
||||
initialScroll: number|null;
|
||||
noteHash: string;
|
||||
onJoplinLinkClick: HandleMessageCallback;
|
||||
onCheckboxChange?: HandleMessageCallback;
|
||||
onRequestEditResource?: HandleMessageCallback;
|
||||
onMarkForDownload?: OnMarkForDownloadCallback;
|
||||
onScroll: HandleScrollCallback;
|
||||
onLoadEnd?: ()=> void;
|
||||
}
|
||||
|
||||
@@ -32,6 +34,7 @@ const webViewStyle = {
|
||||
|
||||
export default function NoteBodyViewer(props: Props) {
|
||||
const dialogBoxRef = useRef(null);
|
||||
const webviewRef = useRef<WebViewControl>(null);
|
||||
|
||||
const { html, injectedJs } = useSource(
|
||||
props.noteBody,
|
||||
@@ -41,6 +44,7 @@ export default function NoteBodyViewer(props: Props) {
|
||||
props.noteResources,
|
||||
props.paddingBottom,
|
||||
props.noteHash,
|
||||
props.initialScroll,
|
||||
);
|
||||
|
||||
const onResourceLongPress = useOnResourceLongPress(
|
||||
@@ -59,6 +63,7 @@ export default function NoteBodyViewer(props: Props) {
|
||||
onJoplinLinkClick: props.onJoplinLinkClick,
|
||||
onRequestEditResource: props.onRequestEditResource,
|
||||
onResourceLongPress,
|
||||
onMainContainerScroll: props.onScroll,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -96,6 +101,7 @@ export default function NoteBodyViewer(props: Props) {
|
||||
return (
|
||||
<View style={props.style}>
|
||||
<ExtendedWebView
|
||||
ref={webviewRef}
|
||||
webviewInstanceId='NoteBodyViewer'
|
||||
themeId={props.themeId}
|
||||
style={webViewStyle}
|
||||
|
@@ -3,6 +3,7 @@ import shared from '@joplin/lib/components/shared/note-screen-shared';
|
||||
|
||||
export type HandleMessageCallback = (message: string)=> void;
|
||||
export type OnMarkForDownloadCallback = (resource: { resourceId: string })=> void;
|
||||
export type HandleScrollCallback = (scrollTop: number)=> void;
|
||||
|
||||
interface MessageCallbacks {
|
||||
onMarkForDownload?: OnMarkForDownloadCallback;
|
||||
@@ -10,6 +11,7 @@ interface MessageCallbacks {
|
||||
onResourceLongPress: HandleMessageCallback;
|
||||
onRequestEditResource?: HandleMessageCallback;
|
||||
onCheckboxChange: HandleMessageCallback;
|
||||
onMainContainerScroll: HandleScrollCallback;
|
||||
}
|
||||
|
||||
export default function useOnMessage(
|
||||
@@ -24,6 +26,7 @@ export default function useOnMessage(
|
||||
// Thus, useCallback should depend on each callback individually.
|
||||
const {
|
||||
onMarkForDownload, onResourceLongPress, onCheckboxChange, onRequestEditResource, onJoplinLinkClick,
|
||||
onMainContainerScroll,
|
||||
} = callbacks;
|
||||
|
||||
return useCallback((event: any) => {
|
||||
@@ -35,10 +38,23 @@ export default function useOnMessage(
|
||||
// https://github.com/laurent22/joplin/issues/4494
|
||||
const msg = event.nativeEvent.data;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Got IPC message: ', msg);
|
||||
const isScrollMessage = msg.startsWith('onscroll:');
|
||||
|
||||
if (msg.indexOf('checkboxclick:') === 0) {
|
||||
// Scroll messages are very frequent so we avoid logging them.
|
||||
if (!isScrollMessage) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Got IPC message: ', msg);
|
||||
}
|
||||
|
||||
if (isScrollMessage) {
|
||||
const eventData = JSON.parse(msg.substring(msg.indexOf(':') + 1));
|
||||
|
||||
if (typeof eventData.scrollTop !== 'number') {
|
||||
throw new Error(`Invalid scroll message, ${msg}`);
|
||||
}
|
||||
|
||||
onMainContainerScroll?.(eventData.scrollTop);
|
||||
} else if (msg.indexOf('checkboxclick:') === 0) {
|
||||
const newBody = shared.toggleCheckbox(msg, noteBody);
|
||||
onCheckboxChange?.(newBody);
|
||||
} else if (msg.indexOf('markForDownload:') === 0) {
|
||||
@@ -63,5 +79,6 @@ export default function useOnMessage(
|
||||
onJoplinLinkClick,
|
||||
onResourceLongPress,
|
||||
onRequestEditResource,
|
||||
onMainContainerScroll,
|
||||
]);
|
||||
}
|
||||
|
@@ -40,7 +40,16 @@ const onlyCheckboxHasChangedHack = (previousBody: string, newBody: string) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
export default function useSource(noteBody: string, noteMarkupLanguage: number, themeId: number, highlightedKeywords: string[], noteResources: any, paddingBottom: number, noteHash: string): UseSourceResult {
|
||||
export default function useSource(
|
||||
noteBody: string,
|
||||
noteMarkupLanguage: number,
|
||||
themeId: number,
|
||||
highlightedKeywords: string[],
|
||||
noteResources: any,
|
||||
paddingBottom: number,
|
||||
noteHash: string,
|
||||
initialScroll: number|null,
|
||||
): UseSourceResult {
|
||||
const [html, setHtml] = useState<string>('');
|
||||
const [injectedJs, setInjectedJs] = useState<string[]>([]);
|
||||
const [resourceLoadedTime, setResourceLoadedTime] = useState(0);
|
||||
@@ -142,6 +151,12 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
|
||||
const resourceDownloadMode = Setting.value('sync.resourceDownloadMode');
|
||||
|
||||
// On iOS, the root container has slow inertial scroll, which feels very different from
|
||||
// the native scroll in other apps. This is not the case, however, when a child (e.g. a div)
|
||||
// scrolls the content instead.
|
||||
// Use a div to scroll on iOS instead of the main container:
|
||||
const scrollRenderedMdContainer = shim.mobilePlatform() === 'ios';
|
||||
|
||||
const js = [];
|
||||
js.push('try {');
|
||||
js.push(shim.injectedJs('webviewLib'));
|
||||
@@ -149,15 +164,46 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
// the ReactNativeWebView actually supports only one, so the second arg is ignored (and currently not needed for the mobile app).
|
||||
js.push('window.joplinPostMessage_ = (msg, args) => { return window.ReactNativeWebView.postMessage(msg); };');
|
||||
js.push('webviewLib.initialize({ postMessage: msg => { return window.ReactNativeWebView.postMessage(msg); } });');
|
||||
js.push(`
|
||||
const scrollingElement =
|
||||
${scrollRenderedMdContainer ? 'document.querySelector("#rendered-md")' : 'document.scrollingElement'};
|
||||
let lastScrollTop;
|
||||
const onMainContentScroll = () => {
|
||||
const newScrollTop = scrollingElement.scrollTop;
|
||||
if (lastScrollTop !== newScrollTop) {
|
||||
const eventData = { scrollTop: newScrollTop };
|
||||
window.ReactNativeWebView.postMessage('onscroll:' + JSON.stringify(eventData));
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for events on both scrollingElement and window
|
||||
// - On Android, scrollingElement.addEventListener('scroll', callback) doesn't call callback on
|
||||
// scroll. However, window.addEventListener('scroll', callback) does.
|
||||
// - iOS needs a listener to be added to scrollingElement -- events aren't received when
|
||||
// the listener is added to window with window.addEventListener('scroll', ...).
|
||||
scrollingElement.addEventListener('scroll', onMainContentScroll);
|
||||
window.addEventListener('scroll', onMainContentScroll);
|
||||
|
||||
const scrollContentToPosition = (position) => {
|
||||
scrollingElement.scrollTop = position;
|
||||
};
|
||||
`);
|
||||
js.push(`
|
||||
const readyStateCheckInterval = setInterval(function() {
|
||||
if (document.readyState === "complete") {
|
||||
clearInterval(readyStateCheckInterval);
|
||||
if ("${resourceDownloadMode}" === "manual") webviewLib.setupResourceManualDownload();
|
||||
|
||||
const hash = "${noteHash}";
|
||||
// Gives it a bit of time before scrolling to the anchor
|
||||
// so that images are loaded.
|
||||
if (hash) {
|
||||
const initialScroll = ${JSON.stringify(initialScroll)};
|
||||
|
||||
// Don't scroll to a hash if we're given initial scroll (initial scroll
|
||||
// overrides scrolling to a hash).
|
||||
if ((initialScroll ?? null) !== null) {
|
||||
scrollContentToPosition(initialScroll);
|
||||
} else if (hash) {
|
||||
// Gives it a bit of time before scrolling to the anchor
|
||||
// so that images are loaded.
|
||||
setTimeout(() => {
|
||||
const e = document.getElementById(hash);
|
||||
if (!e) {
|
||||
@@ -171,6 +217,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
}, 10);
|
||||
`);
|
||||
js.push('} catch (e) {');
|
||||
js.push(' console.error(e);');
|
||||
js.push(' window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e))');
|
||||
js.push(' true;');
|
||||
js.push('}');
|
||||
@@ -186,10 +233,11 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
iOS seems to increase inertial scrolling friction when the WebView body/root elements
|
||||
scroll. Scroll the main container instead.
|
||||
*/
|
||||
:root > body {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
const scrollRenderedMdContainerCss = `
|
||||
body > #rendered-md {
|
||||
width: 100vw;
|
||||
overflow: auto;
|
||||
@@ -197,10 +245,6 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
padding-bottom: ${paddingBottom}px;
|
||||
padding-top: ${paddingTop};
|
||||
}
|
||||
|
||||
:root > body {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
const defaultCss = `
|
||||
code {
|
||||
@@ -219,6 +263,7 @@ export default function useSource(noteBody: string, noteMarkupLanguage: number,
|
||||
<style>
|
||||
${defaultCss}
|
||||
${shim.mobilePlatform() === 'ios' ? iOSSpecificCss : ''}
|
||||
${scrollRenderedMdContainer ? scrollRenderedMdContainerCss : ''}
|
||||
${editPopupCss}
|
||||
</style>
|
||||
${assetsToHeaders(result.pluginAssets, { asHtml: true })}
|
||||
|
@@ -11,18 +11,17 @@ import { WebViewMessageEvent } from 'react-native-webview';
|
||||
import ExtendedWebView, { WebViewControl } from '../../ExtendedWebView';
|
||||
import { clearAutosave, writeAutosave } from './autosave';
|
||||
import { LocalizedStrings } from './js-draw/types';
|
||||
import VersionInfo from 'react-native-version-info';
|
||||
|
||||
|
||||
const logger = Logger.create('ImageEditor');
|
||||
|
||||
type OnSaveCallback = (svgData: string)=> void;
|
||||
type OnCancelCallback = ()=> void;
|
||||
|
||||
// Returns the empty string to load from a template.
|
||||
type LoadInitialSVGCallback = ()=> Promise<string>;
|
||||
|
||||
interface Props {
|
||||
themeId: number;
|
||||
loadInitialSVGData: LoadInitialSVGCallback;
|
||||
resourceFilename: string|null;
|
||||
onSave: OnSaveCallback;
|
||||
onExit: OnCancelCallback;
|
||||
}
|
||||
@@ -166,10 +165,23 @@ const ImageEditor = (props: Props) => {
|
||||
redo: _('Redo'),
|
||||
}), []);
|
||||
|
||||
const appInfo = useMemo(() => {
|
||||
return {
|
||||
name: 'Joplin',
|
||||
description: `v${VersionInfo.appVersion}`,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const injectedJavaScript = useMemo(() => `
|
||||
window.onerror = (message, source, lineno) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
"error: " + message + " in file://" + source + ", line " + lineno
|
||||
"error: " + message + " in file://" + source + ", line " + lineno,
|
||||
);
|
||||
};
|
||||
|
||||
window.onunhandledrejection = (error) => {
|
||||
window.ReactNativeWebView.postMessage(
|
||||
"error: " + error.reason,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -229,6 +241,7 @@ const ImageEditor = (props: Props) => {
|
||||
${JSON.stringify(Setting.value('imageeditor.jsdrawToolbar'))},
|
||||
${JSON.stringify(Setting.value('locale'))},
|
||||
${JSON.stringify(localizedStrings)},
|
||||
${JSON.stringify({ appInfo })},
|
||||
);
|
||||
|
||||
// Start loading the SVG file (if present) after loading the editor.
|
||||
@@ -242,7 +255,7 @@ const ImageEditor = (props: Props) => {
|
||||
);
|
||||
}
|
||||
true;
|
||||
`, [localizedStrings]);
|
||||
`, [localizedStrings, appInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
webviewRef.current?.injectJS(`
|
||||
@@ -255,19 +268,17 @@ const ImageEditor = (props: Props) => {
|
||||
}, [css]);
|
||||
|
||||
const onReadyToLoadData = useCallback(async () => {
|
||||
const initialSVGData = await props.loadInitialSVGData?.() ?? '';
|
||||
|
||||
// It can take some time for initialSVGData to be transferred to the WebView.
|
||||
// Thus, do so after the main content has been loaded.
|
||||
webviewRef.current.injectJS(`(async () => {
|
||||
if (window.editorControl) {
|
||||
const initialSVGData = ${JSON.stringify(initialSVGData)};
|
||||
const initialSVGPath = ${JSON.stringify(props.resourceFilename)};
|
||||
const initialTemplateData = ${JSON.stringify(Setting.value('imageeditor.imageTemplate'))};
|
||||
|
||||
editorControl.loadImageOrTemplate(initialSVGData, initialTemplateData);
|
||||
editorControl.loadImageOrTemplate(initialSVGPath, initialTemplateData);
|
||||
}
|
||||
})();`);
|
||||
}, [webviewRef, props.loadInitialSVGData]);
|
||||
}, [webviewRef, props.resourceFilename]);
|
||||
|
||||
const onMessage = useCallback(async (event: WebViewMessageEvent) => {
|
||||
const data = event.nativeEvent.data;
|
||||
@@ -306,6 +317,7 @@ const ImageEditor = (props: Props) => {
|
||||
themeId={props.themeId}
|
||||
html={html}
|
||||
injectedJavaScript={injectedJavaScript}
|
||||
allowFileAccessFromJs={true}
|
||||
onMessage={onMessage}
|
||||
onError={onError}
|
||||
ref={webviewRef}
|
||||
|
@@ -57,7 +57,7 @@ describe('createJsDrawEditor', () => {
|
||||
});
|
||||
|
||||
// Load no image and an empty template so that autosave can start
|
||||
await editorControl.loadImageOrTemplate(undefined, '{}');
|
||||
await editorControl.loadImageOrTemplate('', '{}');
|
||||
|
||||
expect(calledAutosaveCount).toBe(0);
|
||||
|
||||
|
@@ -120,20 +120,44 @@ export const createJsDrawEditor = (
|
||||
editor.showLoadingWarning(0);
|
||||
editor.setReadOnly(true);
|
||||
|
||||
const fetchInitialSvgData = (resourceUrl: string) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (!resourceUrl) {
|
||||
resolve('');
|
||||
}
|
||||
|
||||
// fetch seems to be unable to request file:// URLs.
|
||||
// https://github.com/react-native-webview/react-native-webview/issues/1560#issuecomment-1783611805
|
||||
const request = new XMLHttpRequest();
|
||||
|
||||
const onError = () => {
|
||||
reject(`Failed to load initial SVG data: ${request.status}, ${request.statusText}, ${request.responseText}`);
|
||||
};
|
||||
|
||||
request.addEventListener('load', _ => {
|
||||
resolve(request.responseText);
|
||||
});
|
||||
request.addEventListener('error', onError);
|
||||
request.addEventListener('abort', onError);
|
||||
|
||||
request.open('GET', resourceUrl);
|
||||
request.send();
|
||||
});
|
||||
};
|
||||
|
||||
const editorControl = {
|
||||
editor,
|
||||
loadImageOrTemplate: async (svgData: string|undefined, templateData: string) => {
|
||||
loadImageOrTemplate: async (resourceUrl: string, templateData: string) => {
|
||||
// loadFromSVG shows its own loading message. Hide the original.
|
||||
editor.hideLoadingWarning();
|
||||
|
||||
if (svgData && svgData.length > 0) {
|
||||
await editor.loadFromSVG(svgData);
|
||||
} else {
|
||||
await applyTemplateToEditor(editor, templateData);
|
||||
const svgData = await fetchInitialSvgData(resourceUrl);
|
||||
|
||||
// The editor expects to be saved initially (without
|
||||
// unsaved changes). Save now.
|
||||
saveNow();
|
||||
// Load from a template if no initial data
|
||||
if (svgData === '') {
|
||||
await applyTemplateToEditor(editor, templateData);
|
||||
} else {
|
||||
await editor.loadFromSVG(svgData);
|
||||
}
|
||||
|
||||
// We can now edit and save safely (without data loss).
|
||||
|
@@ -40,9 +40,7 @@ interface ActionButtonProps {
|
||||
onPress: Callback;
|
||||
}
|
||||
|
||||
const ActionButton = (
|
||||
props: ActionButtonProps,
|
||||
) => {
|
||||
const ActionButton = (props: ActionButtonProps) => {
|
||||
return (
|
||||
<CustomButton
|
||||
themeId={props.themeId}
|
||||
|
@@ -70,6 +70,7 @@ interface ScreenHeaderProps {
|
||||
onRedoButtonPress: OnPressCallback;
|
||||
onSaveButtonPress: OnPressCallback;
|
||||
sortButton_press?: OnPressCallback;
|
||||
onSearchButtonPress?: OnPressCallback;
|
||||
|
||||
showSideMenuButton?: boolean;
|
||||
showSearchButton?: boolean;
|
||||
@@ -242,7 +243,11 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
||||
}
|
||||
|
||||
private searchButton_press() {
|
||||
void NavService.go('Search');
|
||||
if (this.props.onSearchButtonPress) {
|
||||
this.props.onSearchButtonPress();
|
||||
} else {
|
||||
void NavService.go('Search');
|
||||
}
|
||||
}
|
||||
|
||||
private async duplicateButton_press() {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions, AccessibilityInfo } from 'react-native';
|
||||
import Setting, { AppType } from '@joplin/lib/models/Setting';
|
||||
import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting';
|
||||
import NavService from '@joplin/lib/services/NavService';
|
||||
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
||||
import checkPermissions from '../../../utils/checkPermissions';
|
||||
@@ -18,21 +18,25 @@ import * as shared from '@joplin/lib/components/shared/config/config-shared';
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import biometricAuthenticate from '../../biometrics/biometricAuthenticate';
|
||||
import configScreenStyles, { ConfigScreenStyles } from './configScreenStyles';
|
||||
import NoteExportButton from './NoteExportSection/NoteExportButton';
|
||||
import NoteExportButton, { exportButtonDescription, exportButtonTitle } from './NoteExportSection/NoteExportButton';
|
||||
import SettingsButton from './SettingsButton';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactElement, ReactNode } from 'react';
|
||||
import { Dispatch } from 'redux';
|
||||
import SectionHeader from './SectionHeader';
|
||||
import ExportProfileButton from './NoteExportSection/ExportProfileButton';
|
||||
import ExportProfileButton, { exportProfileButtonTitle } from './NoteExportSection/ExportProfileButton';
|
||||
import SettingComponent from './SettingComponent';
|
||||
import ExportDebugReportButton from './NoteExportSection/ExportDebugReportButton';
|
||||
import ExportDebugReportButton, { exportDebugReportTitle } from './NoteExportSection/ExportDebugReportButton';
|
||||
import SectionSelector from './SectionSelector';
|
||||
import { TextInput } from 'react-native-paper';
|
||||
|
||||
interface ConfigScreenState {
|
||||
settings: any;
|
||||
changedSettingKeys: string[];
|
||||
|
||||
searchQuery: string;
|
||||
searching: boolean;
|
||||
|
||||
fixingSearchIndex: boolean;
|
||||
checkSyncConfigResult: { ok: boolean; errorMessage: string }|'checking'|null;
|
||||
showAdvancedSettings: boolean;
|
||||
@@ -66,6 +70,8 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
selectedSectionName: null,
|
||||
fixingSearchIndex: false,
|
||||
sidebarWidth: 100,
|
||||
searchQuery: '',
|
||||
searching: false,
|
||||
};
|
||||
|
||||
this.scrollViewRef_ = React.createRef<ScrollView>();
|
||||
@@ -115,10 +121,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
};
|
||||
|
||||
private manageProfilesButtonPress_ = () => {
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'ProfileSwitcher',
|
||||
});
|
||||
void NavService.go('ProfileSwitcher');
|
||||
};
|
||||
|
||||
private fixSearchEngineIndexButtonPress_ = async () => {
|
||||
@@ -131,6 +134,21 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
void NavService.go('Log');
|
||||
};
|
||||
|
||||
private setShowSearch_(searching: boolean) {
|
||||
if (searching !== this.state.searching) {
|
||||
this.setState({ searching });
|
||||
AccessibilityInfo.announceForAccessibility(searching ? _('Search shown') : _('Search hidden'));
|
||||
}
|
||||
}
|
||||
|
||||
private onSearchButtonPress_ = () => {
|
||||
this.setShowSearch_(!this.state.searching);
|
||||
};
|
||||
|
||||
private onSearchUpdate_ = (newQuery: string) => {
|
||||
this.setState({ searchQuery: newQuery });
|
||||
};
|
||||
|
||||
private updateSidebarWidth = () => {
|
||||
const windowWidth = Dimensions.get('window').width;
|
||||
|
||||
@@ -153,10 +171,13 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
return this.state.sidebarWidth > windowWidth / 2;
|
||||
}
|
||||
|
||||
private switchSectionPress_ = (section: string) => {
|
||||
private onJumpToSection_ = (section: string) => {
|
||||
const label = Setting.sectionNameToLabel(section);
|
||||
AccessibilityInfo.announceForAccessibility(_('Opening section %s', label));
|
||||
this.setState({ selectedSectionName: section });
|
||||
this.setState({
|
||||
selectedSectionName: section,
|
||||
searching: false,
|
||||
});
|
||||
};
|
||||
|
||||
private showSectionNavigation_ = () => {
|
||||
@@ -206,36 +227,65 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
return 0;
|
||||
}
|
||||
|
||||
private hasUnsavedChanges() {
|
||||
return this.state.changedSettingKeys.length > 0;
|
||||
}
|
||||
|
||||
private promptSaveChanges(): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
if (this.hasUnsavedChanges()) {
|
||||
const dialogTitle: string|null = null;
|
||||
Alert.alert(
|
||||
dialogTitle,
|
||||
_('There are unsaved changes.'),
|
||||
[{
|
||||
text: _('Save changes'),
|
||||
onPress: async () => {
|
||||
await this.saveButton_press();
|
||||
resolve();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _('Discard changes'),
|
||||
onPress: () => resolve(),
|
||||
}],
|
||||
);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleNavigateToNewScren = async (): Promise<boolean> => {
|
||||
await this.promptSaveChanges();
|
||||
|
||||
// Continue navigation
|
||||
return false;
|
||||
};
|
||||
|
||||
private handleBackButtonPress = (): boolean => {
|
||||
const goBack = async () => {
|
||||
BackButtonService.removeHandler(this.handleBackButtonPress);
|
||||
await BackButtonService.back();
|
||||
};
|
||||
|
||||
// Cancel search on back
|
||||
if (this.state.searching) {
|
||||
this.setShowSearch_(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Show navigation when pressing "back" (unless always visible).
|
||||
if (this.state.selectedSectionName && this.navigationFillsScreen()) {
|
||||
this.showSectionNavigation_();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.state.changedSettingKeys.length > 0) {
|
||||
const dialogTitle: string|null = null;
|
||||
Alert.alert(
|
||||
dialogTitle,
|
||||
_('There are unsaved changes.'),
|
||||
[{
|
||||
text: _('Save changes'),
|
||||
onPress: async () => {
|
||||
await this.saveButton_press();
|
||||
await goBack();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _('Discard changes'),
|
||||
onPress: goBack,
|
||||
}],
|
||||
);
|
||||
|
||||
if (this.hasUnsavedChanges()) {
|
||||
void (async () => {
|
||||
await this.promptSaveChanges();
|
||||
await goBack();
|
||||
})();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,12 +305,14 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
}
|
||||
|
||||
BackButtonService.addHandler(this.handleBackButtonPress);
|
||||
NavService.addHandler(this.handleNavigateToNewScren);
|
||||
Dimensions.addEventListener('change', this.updateSidebarWidth);
|
||||
this.updateSidebarWidth();
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
BackButtonService.removeHandler(this.handleBackButtonPress);
|
||||
NavService.removeHandler(this.handleNavigateToNewScren);
|
||||
}
|
||||
|
||||
private renderButton(key: string, title: string, clickHandler: ()=> void, options: any = null) {
|
||||
@@ -276,10 +328,73 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
);
|
||||
}
|
||||
|
||||
public sectionToComponent(key: string, section: any, settings: any, isSelected: boolean) {
|
||||
const settingComps = [];
|
||||
public sectionToComponent(key: string, section: SettingMetadataSection, settings: any, isSelected: boolean) {
|
||||
const settingComps: ReactElement[] = [];
|
||||
|
||||
const headerTitle = Setting.sectionNameToLabel(section.name);
|
||||
|
||||
const matchesSearchQuery = (relatedText: string|string[]) => {
|
||||
let searchThrough;
|
||||
if (Array.isArray(relatedText)) {
|
||||
searchThrough = relatedText.join('\n');
|
||||
} else {
|
||||
searchThrough = relatedText;
|
||||
}
|
||||
searchThrough = searchThrough.toLocaleLowerCase();
|
||||
|
||||
const searchQuery = this.state.searchQuery.toLocaleLowerCase().trim();
|
||||
|
||||
const hasSearchMatches =
|
||||
headerTitle.toLocaleLowerCase() === searchQuery
|
||||
|| searchThrough.includes(searchQuery);
|
||||
|
||||
// Don't show results when the search input is empty
|
||||
return this.state.searchQuery.length > 0 && hasSearchMatches;
|
||||
};
|
||||
|
||||
const addSettingComponent = (component: ReactElement, relatedText: string|string[]) => {
|
||||
const hiddenBySearch = this.state.searching && !matchesSearchQuery(relatedText);
|
||||
if (component && !hiddenBySearch) {
|
||||
settingComps.push(component);
|
||||
}
|
||||
};
|
||||
|
||||
const addSettingButton = (key: string, title: string, clickHandler: ()=> void, options: any = null) => {
|
||||
const relatedText = [title];
|
||||
if (typeof options === 'object' && options?.description) {
|
||||
relatedText.push(options.description);
|
||||
}
|
||||
addSettingComponent(this.renderButton(key, title, clickHandler, options), relatedText);
|
||||
};
|
||||
|
||||
const styleSheet = this.styles().styleSheet;
|
||||
const addSettingLink = (key: string, title: string, target: string) => {
|
||||
const component = (
|
||||
<View key={key} style={styleSheet.settingContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
void Linking.openURL(target);
|
||||
}}
|
||||
accessibilityRole='link'
|
||||
>
|
||||
<Text key="label" style={styleSheet.linkText}>
|
||||
{title}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
addSettingComponent(component, title);
|
||||
};
|
||||
|
||||
const addSettingText = (key: string, text: string) => {
|
||||
addSettingComponent(
|
||||
<View key={key} style={styleSheet.settingContainer}>
|
||||
<Text style={styleSheet.settingText}>{text}</Text>
|
||||
</View>,
|
||||
text,
|
||||
);
|
||||
};
|
||||
|
||||
for (let i = 0; i < section.metadatas.length; i++) {
|
||||
const md = section.metadatas[i];
|
||||
@@ -300,24 +415,29 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
</View>
|
||||
);
|
||||
|
||||
settingComps.push(this.renderButton('check_sync_config_button', _('Check synchronisation configuration'), this.checkSyncConfig_, { statusComp: statusComp }));
|
||||
addSettingButton('check_sync_config_button', _('Check synchronisation configuration'), this.checkSyncConfig_, { statusComp: statusComp });
|
||||
}
|
||||
}
|
||||
|
||||
const settingComp = this.settingToComponent(md.key, settings[md.key]);
|
||||
settingComps.push(settingComp);
|
||||
const relatedText = [md.label?.() ?? '', md.description?.() ?? ''];
|
||||
addSettingComponent(
|
||||
settingComp,
|
||||
relatedText,
|
||||
);
|
||||
}
|
||||
|
||||
if (section.name === 'sync') {
|
||||
settingComps.push(this.renderButton('e2ee_config_button', _('Encryption Config'), this.e2eeConfig_));
|
||||
addSettingButton('e2ee_config_button', _('Encryption Config'), this.e2eeConfig_);
|
||||
}
|
||||
|
||||
if (section.name === 'joplinCloud') {
|
||||
const label = _('Email to note');
|
||||
const description = _('Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook');
|
||||
settingComps.push(
|
||||
addSettingComponent(
|
||||
<View key="joplinCloud">
|
||||
<View style={this.styles().styleSheet.settingContainerNoBottomBorder}>
|
||||
<Text style={this.styles().styleSheet.settingText}>{_('Email to note')}</Text>
|
||||
<Text style={this.styles().styleSheet.settingText}>{label}</Text>
|
||||
<Text style={{ fontWeight: 'bold' }}>{this.props.settings['sync.10.inboxEmail']}</Text>
|
||||
</View>
|
||||
{
|
||||
@@ -329,20 +449,30 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
)
|
||||
}
|
||||
</View>,
|
||||
[label, description],
|
||||
);
|
||||
}
|
||||
|
||||
if (section.name === 'tools') {
|
||||
settingComps.push(this.renderButton('profiles_buttons', _('Manage profiles'), this.manageProfilesButtonPress_));
|
||||
settingComps.push(this.renderButton('status_button', _('Sync Status'), this.syncStatusButtonPress_));
|
||||
settingComps.push(this.renderButton('log_button', _('Log'), this.logButtonPress_));
|
||||
settingComps.push(this.renderButton('fix_search_engine_index', this.state.fixingSearchIndex ? _('Fixing search index...') : _('Fix search index'), this.fixSearchEngineIndexButtonPress_, { disabled: this.state.fixingSearchIndex, description: _('Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.') }));
|
||||
addSettingButton('profiles_buttons', _('Manage profiles'), this.manageProfilesButtonPress_);
|
||||
addSettingButton('status_button', _('Sync Status'), this.syncStatusButtonPress_);
|
||||
addSettingButton('log_button', _('Log'), this.logButtonPress_);
|
||||
addSettingButton('fix_search_engine_index', this.state.fixingSearchIndex ? _('Fixing search index...') : _('Fix search index'), this.fixSearchEngineIndexButtonPress_, { disabled: this.state.fixingSearchIndex, description: _('Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.') });
|
||||
}
|
||||
|
||||
if (section.name === 'export') {
|
||||
settingComps.push(<NoteExportButton key='export_as_jex_button' styles={this.styles()} />);
|
||||
settingComps.push(<ExportDebugReportButton key='export_report_button' styles={this.styles()}/>);
|
||||
settingComps.push(<ExportProfileButton key='export_data' styles={this.styles()}/>);
|
||||
addSettingComponent(
|
||||
<NoteExportButton key='export_as_jex_button' styles={this.styles()} />,
|
||||
[exportButtonTitle(), exportButtonDescription()],
|
||||
);
|
||||
addSettingComponent(
|
||||
<ExportDebugReportButton key='export_report_button' styles={this.styles()}/>,
|
||||
exportDebugReportTitle(),
|
||||
);
|
||||
addSettingComponent(
|
||||
<ExportProfileButton key='export_data' styles={this.styles()}/>,
|
||||
exportProfileButtonTitle(),
|
||||
);
|
||||
}
|
||||
|
||||
if (section.name === 'moreInfo') {
|
||||
@@ -350,7 +480,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
// Note: `PermissionsAndroid` doesn't work so we have to ask the user to manually
|
||||
// set these permissions. https://stackoverflow.com/questions/49771084/permission-always-returns-never-ask-again
|
||||
|
||||
settingComps.push(
|
||||
addSettingComponent(
|
||||
<View key="permission_info" style={styleSheet.settingContainer}>
|
||||
<View key="permission_info_wrapper">
|
||||
<Text key="perm1a" style={styleSheet.settingText}>
|
||||
@@ -367,95 +497,60 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
</Text>
|
||||
</View>
|
||||
</View>,
|
||||
'',
|
||||
);
|
||||
}
|
||||
|
||||
settingComps.push(
|
||||
<View key="donate_link" style={styleSheet.settingContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
void Linking.openURL('https://joplinapp.org/donate/');
|
||||
}}
|
||||
>
|
||||
<Text key="label" style={styleSheet.linkText}>
|
||||
{_('Make a donation')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>,
|
||||
);
|
||||
addSettingLink('donate_link', _('Make a donation'), 'https://joplinapp.org/donate/');
|
||||
addSettingLink('website_link', _('Joplin website'), 'https://joplinapp.org/');
|
||||
addSettingLink('privacy_link', _('Privacy Policy'), 'https://joplinapp.org/privacy/');
|
||||
|
||||
settingComps.push(
|
||||
<View key="website_link" style={styleSheet.settingContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
void Linking.openURL('https://joplinapp.org/');
|
||||
}}
|
||||
>
|
||||
<Text key="label" style={styleSheet.linkText}>
|
||||
{_('Joplin website')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>,
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="privacy_link" style={styleSheet.settingContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
void Linking.openURL('https://joplinapp.org/privacy/');
|
||||
}}
|
||||
>
|
||||
<Text key="label" style={styleSheet.linkText}>
|
||||
{_('Privacy Policy')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>,
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="version_info_app" style={styleSheet.settingContainer}>
|
||||
<Text style={styleSheet.settingText}>{`Joplin ${VersionInfo.appVersion}`}</Text>
|
||||
</View>,
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="version_info_db" style={styleSheet.settingContainer}>
|
||||
<Text style={styleSheet.settingText}>{_('Database v%s', reg.db().version())}</Text>
|
||||
</View>,
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="version_info_fts" style={styleSheet.settingContainer}>
|
||||
<Text style={styleSheet.settingText}>{_('FTS enabled: %d', this.props.settings['db.ftsEnabled'])}</Text>
|
||||
</View>,
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<View key="version_info_hermes" style={styleSheet.settingContainer}>
|
||||
<Text style={styleSheet.settingText}>{_('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0)}</Text>
|
||||
</View>,
|
||||
);
|
||||
addSettingText('version_info_app', `Joplin ${VersionInfo.appVersion}`);
|
||||
addSettingText('version_info_db', _('Database v%s', reg.db().version()));
|
||||
addSettingText('version_info_fts', _('FTS enabled: %d', this.props.settings['db.ftsEnabled']));
|
||||
addSettingText('version_info_hermes', _('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0));
|
||||
|
||||
const featureFlagKeys = Setting.featureFlagKeys(AppType.Mobile);
|
||||
if (featureFlagKeys.length) {
|
||||
const headerKey = 'featureFlags';
|
||||
settingComps.push(<SectionHeader
|
||||
key={headerKey}
|
||||
styles={this.styles().styleSheet}
|
||||
title={_('Feature flags')}
|
||||
onLayout={event => this.onHeaderLayout(headerKey, event)}
|
||||
/>);
|
||||
const featureFlagsTitle = _('Feature flags');
|
||||
addSettingComponent(
|
||||
<SectionHeader
|
||||
key={headerKey}
|
||||
styles={this.styles().styleSheet}
|
||||
title={featureFlagsTitle}
|
||||
onLayout={event => this.onHeaderLayout(headerKey, event)}
|
||||
/>,
|
||||
_('Feature flags'),
|
||||
);
|
||||
|
||||
settingComps.push(<View key="featureFlagsContainer">{this.renderFeatureFlags(settings, featureFlagKeys)}</View>);
|
||||
addSettingComponent(
|
||||
<View key="featureFlagsContainer">{this.renderFeatureFlags(settings, featureFlagKeys)}</View>,
|
||||
featureFlagsTitle,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!settingComps.length) return null;
|
||||
if (!isSelected) return null;
|
||||
if (!isSelected && !this.state.searching) return null;
|
||||
|
||||
const headerComponent = (
|
||||
<TouchableOpacity onPress={() => {
|
||||
this.onJumpToSection_(section.name);
|
||||
}}>
|
||||
<SectionHeader
|
||||
styles={styleSheet}
|
||||
title={headerTitle}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
return (
|
||||
<View key={key} onLayout={(event: any) => this.onSectionLayout(key, event)}>
|
||||
<View>{settingComps}</View>
|
||||
<View>
|
||||
{this.state.searching ? headerComponent : null}
|
||||
{settingComps}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -541,18 +636,22 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
currentSectionName = 'general';
|
||||
}
|
||||
|
||||
if (this.state.searching) {
|
||||
currentSectionName = null;
|
||||
}
|
||||
|
||||
const sectionSelector = (
|
||||
<SectionSelector
|
||||
selectedSectionName={currentSectionName}
|
||||
styles={this.styles()}
|
||||
settings={settings}
|
||||
openSection={this.switchSectionPress_}
|
||||
openSection={this.onJumpToSection_}
|
||||
width={this.state.sidebarWidth}
|
||||
/>
|
||||
);
|
||||
|
||||
let currentSection: ReactNode;
|
||||
if (currentSectionName) {
|
||||
if (currentSectionName || this.state.searching) {
|
||||
const settingComps = shared.settingsToComponents2(
|
||||
this, AppType.Mobile, settings, currentSectionName,
|
||||
|
||||
@@ -560,11 +659,20 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
// of React in lib/ and app-mobile/
|
||||
) as ReactNode[];
|
||||
|
||||
const searchInput = <TextInput
|
||||
value={this.state.searchQuery}
|
||||
label={_('Search')}
|
||||
placeholder={_('Search...')}
|
||||
onChangeText={this.onSearchUpdate_}
|
||||
autoFocus={true}
|
||||
/>;
|
||||
|
||||
currentSection = (
|
||||
<ScrollView
|
||||
ref={this.scrollViewRef_}
|
||||
style={{ flexGrow: 1 }}
|
||||
>
|
||||
{this.state.searching ? searchInput : null}
|
||||
{settingComps}
|
||||
</ScrollView>
|
||||
);
|
||||
@@ -598,10 +706,11 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
||||
<ScreenHeader
|
||||
title={screenHeadingText}
|
||||
showSaveButton={true}
|
||||
showSearchButton={false}
|
||||
showSearchButton={true}
|
||||
showSideMenuButton={false}
|
||||
saveButtonDisabled={!this.state.changedSettingKeys.length}
|
||||
saveButtonDisabled={!this.hasUnsavedChanges()}
|
||||
onSaveButtonPress={this.saveButton_press}
|
||||
onSearchButtonPress={this.onSearchButtonPress_}
|
||||
/>
|
||||
{mainComponent}
|
||||
</View>
|
||||
|
@@ -11,6 +11,8 @@ interface Props {
|
||||
styles: ConfigScreenStyles;
|
||||
}
|
||||
|
||||
export const exportDebugReportTitle = () => _('Export Debug Report');
|
||||
|
||||
const ExportDebugReportButton = (props: Props) => {
|
||||
const [creatingReport, setCreatingReport] = useState(false);
|
||||
|
||||
@@ -24,7 +26,7 @@ const ExportDebugReportButton = (props: Props) => {
|
||||
|
||||
const exportDebugReportButton = (
|
||||
<SettingsButton
|
||||
title={creatingReport ? _('Creating report...') : _('Export Debug Report')}
|
||||
title={creatingReport ? _('Creating report...') : exportDebugReportTitle()}
|
||||
clickHandler={exportDebugButtonPress}
|
||||
styles={props.styles}
|
||||
disabled={creatingReport}
|
||||
|
@@ -13,6 +13,8 @@ interface Props {
|
||||
styles: ConfigScreenStyles;
|
||||
}
|
||||
|
||||
export const exportProfileButtonTitle = () => _('Export profile');
|
||||
|
||||
const ExportProfileButton = (props: Props) => {
|
||||
const [profileExportStatus, setProfileExportStatus] = useState<'idle'|'prompt'|'exporting'>('idle');
|
||||
const [profileExportPath, setProfileExportPath] = useState<string>('');
|
||||
@@ -31,7 +33,7 @@ const ExportProfileButton = (props: Props) => {
|
||||
const exportProfileButton = (
|
||||
<SettingsButton
|
||||
styles={props.styles}
|
||||
title={profileExportStatus === 'exporting' ? _('Exporting profile...') : _('Export profile')}
|
||||
title={profileExportStatus === 'exporting' ? _('Exporting profile...') : exportProfileButtonTitle()}
|
||||
clickHandler={exportProfileButtonPress}
|
||||
description={_('For debugging purpose only: export your profile to an external SD card.')}
|
||||
disabled={profileExportStatus === 'exporting'}
|
||||
|
@@ -24,6 +24,9 @@ enum ExportStatus {
|
||||
Exported,
|
||||
}
|
||||
|
||||
export const exportButtonTitle = () => _('Export all notes as JEX');
|
||||
export const exportButtonDescription = () => _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
|
||||
|
||||
const NoteExportButton: FunctionComponent<Props> = props => {
|
||||
const [exportStatus, setExportStatus] = useState<ExportStatus>(ExportStatus.NotStarted);
|
||||
const [exportProgress, setExportProgress] = useState<number|undefined>(0);
|
||||
@@ -80,13 +83,12 @@ const NoteExportButton: FunctionComponent<Props> = props => {
|
||||
indeterminate={exportProgress === undefined}
|
||||
progress={exportProgress}/>
|
||||
);
|
||||
const descriptionText = _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
|
||||
|
||||
const startOrCancelExportButton = (
|
||||
<SettingsButton
|
||||
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : _('Export all notes as JEX')}
|
||||
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : exportButtonTitle()}
|
||||
disabled={exportStatus === ExportStatus.Exporting}
|
||||
description={exportStatus === ExportStatus.NotStarted ? descriptionText : null}
|
||||
description={exportStatus === ExportStatus.NotStarted ? exportButtonDescription() : null}
|
||||
statusComponent={progressComponent}
|
||||
clickHandler={startExport}
|
||||
styles={props.styles}
|
||||
|
@@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting';
|
||||
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
|
||||
import { ConfigScreenStyles } from './configScreenStyles';
|
||||
import { FlatList, Text, Pressable, View } from 'react-native';
|
||||
import { FlatList, Text, Pressable, View, ViewStyle } from 'react-native';
|
||||
import { settingsSections } from '@joplin/lib/components/shared/config/config-shared';
|
||||
import Icon from '../../Icon';
|
||||
|
||||
@@ -53,7 +53,7 @@ const SectionSelector: FunctionComponent<Props> = props => {
|
||||
</Text>
|
||||
<Text
|
||||
style={styles.sidebarButtonDescriptionText}
|
||||
numberOfLines={2}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode='tail'
|
||||
>
|
||||
{shortDescription ?? ''}
|
||||
@@ -82,8 +82,15 @@ const SectionSelector: FunctionComponent<Props> = props => {
|
||||
}
|
||||
}, [props.selectedSectionName, flatListRef, sections]);
|
||||
|
||||
const containerStyle: ViewStyle = useMemo(() => ({
|
||||
width: props.width,
|
||||
maxWidth: props.width,
|
||||
minWidth: props.width,
|
||||
flex: 1,
|
||||
}), [props.width]);
|
||||
|
||||
return (
|
||||
<View style={{ width: props.width, flexDirection: 'column' }}>
|
||||
<View style={containerStyle}>
|
||||
<FlatList
|
||||
role='tablist'
|
||||
ref={setFlatListRef}
|
||||
|
@@ -86,11 +86,13 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
|
||||
fontSize: theme.fontSize,
|
||||
};
|
||||
|
||||
const fadedOpacity = 0.75;
|
||||
const sidebarButtonDescriptionText: TextStyle = {
|
||||
...sidebarButtonMainText,
|
||||
fontSize: theme.fontSizeSmaller,
|
||||
color: theme.color,
|
||||
opacity: 0.75,
|
||||
opacity: fadedOpacity,
|
||||
paddingTop: 3,
|
||||
};
|
||||
|
||||
|
||||
@@ -185,6 +187,7 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => {
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
width: sidebarButtonHeight * 0.8,
|
||||
opacity: fadedOpacity,
|
||||
},
|
||||
sidebarSelectedButtonText: {
|
||||
...sidebarButtonMainText,
|
||||
|
@@ -61,6 +61,9 @@ const emptyArray: any[] = [];
|
||||
const logger = Logger.create('screens/Note');
|
||||
|
||||
class NoteScreenComponent extends BaseScreenComponent {
|
||||
// This isn't in this.state because we don't want changing scroll to trigger
|
||||
// a re-render.
|
||||
private lastBodyScroll: number|undefined = undefined;
|
||||
|
||||
public static navigationOptions(): any {
|
||||
return { header: null };
|
||||
@@ -81,7 +84,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
fromShare: false,
|
||||
showCamera: false,
|
||||
showImageEditor: false,
|
||||
loadImageEditorData: null,
|
||||
imageEditorResource: null,
|
||||
noteResources: {},
|
||||
|
||||
@@ -747,7 +749,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
if (this.useEditorBeta()) {
|
||||
// The beta editor needs to be explicitly informed of changes
|
||||
// to the note's body
|
||||
this.editorRef.current.insertText(newText);
|
||||
if (this.editorRef.current) {
|
||||
this.editorRef.current.insertText(newText);
|
||||
} else {
|
||||
logger.error(`Tried to attach resource ${resource.id} to the note when the editor is not visible!`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newNote.body += `\n${resourceTag}`;
|
||||
@@ -812,31 +818,34 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}, 'image');
|
||||
}
|
||||
|
||||
private drawPicture_onPress = async () => {
|
||||
// Create a new empty drawing and attach it now.
|
||||
const resource = await this.attachNewDrawing('');
|
||||
await this.editDrawing(resource);
|
||||
};
|
||||
|
||||
private async updateDrawing(svgData: string) {
|
||||
let resource: ResourceEntity|null = this.state.imageEditorResource;
|
||||
|
||||
if (!resource) {
|
||||
throw new Error('No resource is loaded in the editor');
|
||||
resource = await this.attachNewDrawing(svgData);
|
||||
|
||||
// Set resouce and file path to allow
|
||||
// 1. subsequent saves to update the resource
|
||||
// 2. the editor to load from the resource's filepath (can happen
|
||||
// if the webview is reloaded).
|
||||
this.setState({
|
||||
imageEditorResourceFilepath: Resource.fullPath(resource),
|
||||
imageEditorResource: resource,
|
||||
});
|
||||
} else {
|
||||
logger.info('Saving drawing to resource', resource.id);
|
||||
|
||||
const tempFilePath = join(Setting.value('tempDir'), uuid.createNano());
|
||||
await shim.fsDriver().writeFile(tempFilePath, svgData, 'utf8');
|
||||
|
||||
resource = await Resource.updateResourceBlobContent(
|
||||
resource.id,
|
||||
tempFilePath,
|
||||
);
|
||||
await shim.fsDriver().remove(tempFilePath);
|
||||
|
||||
await this.refreshResource(resource);
|
||||
}
|
||||
|
||||
logger.info('Saving drawing to resource', resource.id);
|
||||
|
||||
const tempFilePath = join(Setting.value('tempDir'), uuid.createNano());
|
||||
await shim.fsDriver().writeFile(tempFilePath, svgData, 'utf8');
|
||||
|
||||
resource = await Resource.updateResourceBlobContent(
|
||||
resource.id,
|
||||
tempFilePath,
|
||||
);
|
||||
await shim.fsDriver().remove(tempFilePath);
|
||||
|
||||
await this.refreshResource(resource);
|
||||
}
|
||||
|
||||
private onSaveDrawing = async (svgData: string) => {
|
||||
@@ -847,13 +856,28 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
this.setState({ showImageEditor: false });
|
||||
};
|
||||
|
||||
private drawPicture_onPress = async () => {
|
||||
if (this.state.mode === 'edit') {
|
||||
// Create a new empty drawing and attach it now, before the image editor is opened.
|
||||
// With the present structure of Note.tsx, the we can't use this.editorRef while
|
||||
// the image editor is open, and thus can't attach drawings at the cursor locaiton.
|
||||
const resource = await this.attachNewDrawing('');
|
||||
await this.editDrawing(resource);
|
||||
} else {
|
||||
logger.info('Showing image editor...');
|
||||
this.setState({
|
||||
showImageEditor: true,
|
||||
imageEditorResourceFilepath: null,
|
||||
imageEditorResource: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private async editDrawing(item: BaseItem) {
|
||||
const filePath = Resource.fullPath(item);
|
||||
this.setState({
|
||||
showImageEditor: true,
|
||||
loadImageEditorData: async () => {
|
||||
return await shim.fsDriver().readFile(filePath);
|
||||
},
|
||||
imageEditorResourceFilepath: filePath,
|
||||
imageEditorResource: item,
|
||||
});
|
||||
}
|
||||
@@ -1260,6 +1284,10 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}, 5);
|
||||
}
|
||||
|
||||
private onBodyViewerScroll = (scrollTop: number) => {
|
||||
this.lastBodyScroll = scrollTop;
|
||||
};
|
||||
|
||||
public onBodyViewerCheckboxChange(newBody: string) {
|
||||
void this.saveOneProperty('body', newBody);
|
||||
}
|
||||
@@ -1302,7 +1330,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return <CameraView themeId={this.props.themeId} style={{ flex: 1 }} onPhoto={this.cameraView_onPhoto} onCancel={this.cameraView_onCancel} />;
|
||||
} else if (this.state.showImageEditor) {
|
||||
return <ImageEditor
|
||||
loadInitialSVGData={this.state.loadImageEditorData}
|
||||
resourceFilename={this.state.imageEditorResourceFilepath}
|
||||
themeId={this.props.themeId}
|
||||
onSave={this.onSaveDrawing}
|
||||
onExit={this.onCloseDrawing}
|
||||
@@ -1334,6 +1362,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
onMarkForDownload={this.onMarkForDownload}
|
||||
onRequestEditResource={this.onEditResource}
|
||||
onLoadEnd={this.onBodyViewerLoadEnd}
|
||||
onScroll={this.onBodyViewerScroll}
|
||||
initialScroll={this.lastBodyScroll}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
@@ -1416,7 +1446,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
|
||||
if (this.state.mode === 'edit') return null;
|
||||
|
||||
return <ActionButton mainButton={editButton} />;
|
||||
return <ActionButton mainButton={editButton} dispatch={this.props.dispatch} />;
|
||||
};
|
||||
|
||||
// Save button is not really needed anymore with the improved save logic
|
||||
|
@@ -16,6 +16,7 @@ const DialogBox = require('react-native-dialogbox').default;
|
||||
const { BaseScreenComponent } = require('../base-screen');
|
||||
const { BackButtonService } = require('../../services/back-button.js');
|
||||
import { AppState } from '../../utils/types';
|
||||
const { ALL_NOTES_FILTER_ID } = require('@joplin/lib/reserved-ids.js');
|
||||
|
||||
class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
|
||||
@@ -108,7 +109,7 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
}
|
||||
|
||||
public async componentDidUpdate(prevProps: any) {
|
||||
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType) {
|
||||
if (prevProps.notesOrder !== this.props.notesOrder || prevProps.selectedFolderId !== this.props.selectedFolderId || prevProps.selectedTagId !== this.props.selectedTagId || prevProps.selectedSmartFilterId !== this.props.selectedSmartFilterId || prevProps.notesParentType !== this.props.notesParentType || prevProps.uncompletedTodosOnTop !== this.props.uncompletedTodosOnTop || prevProps.showCompletedTodos !== this.props.showCompletedTodos) {
|
||||
await this.refreshNotes(this.props);
|
||||
}
|
||||
}
|
||||
@@ -223,17 +224,32 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
let buttonFolderId = this.props.selectedFolderId !== Folder.conflictFolderId() ? this.props.selectedFolderId : null;
|
||||
if (!buttonFolderId) buttonFolderId = this.props.activeFolderId;
|
||||
|
||||
const addFolderNoteButtons = !!buttonFolderId;
|
||||
const isAllNotes =
|
||||
this.props.notesParentType === 'SmartFilter'
|
||||
&& this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID;
|
||||
|
||||
// Usually, when showing all notes, activeFolderId/selectedFolderId is set to the last
|
||||
// active folder.
|
||||
// If the app starts showing all notes, activeFolderId/selectedFolderId are
|
||||
// empty or null. As such, we need a special case to show the buttons:
|
||||
const addFolderNoteButtons = !!buttonFolderId || isAllNotes;
|
||||
const thisComp = this;
|
||||
|
||||
const makeActionButtonComp = () => {
|
||||
const getTargetFolderId = async () => {
|
||||
if (!buttonFolderId && isAllNotes) {
|
||||
return (await Folder.defaultFolder()).id;
|
||||
}
|
||||
return buttonFolderId;
|
||||
};
|
||||
if (addFolderNoteButtons && this.props.folders.length > 0) {
|
||||
const buttons = [];
|
||||
buttons.push({
|
||||
label: _('New to-do'),
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const folderId = await getTargetFolderId();
|
||||
const isTodo = true;
|
||||
void this.newNoteNavigate(buttonFolderId, isTodo);
|
||||
void this.newNoteNavigate(folderId, isTodo);
|
||||
},
|
||||
color: '#9b59b6',
|
||||
icon: 'checkbox-outline',
|
||||
@@ -241,14 +257,15 @@ class NotesScreenComponent extends BaseScreenComponent<any> {
|
||||
|
||||
buttons.push({
|
||||
label: _('New note'),
|
||||
onPress: () => {
|
||||
onPress: async () => {
|
||||
const folderId = await getTargetFolderId();
|
||||
const isTodo = false;
|
||||
void this.newNoteNavigate(buttonFolderId, isTodo);
|
||||
void this.newNoteNavigate(folderId, isTodo);
|
||||
},
|
||||
color: '#9b59b6',
|
||||
icon: 'document',
|
||||
});
|
||||
return <ActionButton buttons={buttons}/>;
|
||||
return <ActionButton buttons={buttons} dispatch={this.props.dispatch}/>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
@@ -1,17 +1,13 @@
|
||||
const gulp = require('gulp');
|
||||
const utils = require('@joplin/tools/gulp/utils');
|
||||
import { buildInjectedJS, watchInjectedJS } from './tools/buildInjectedJs';
|
||||
|
||||
import gulpTasks from './tools/buildInjectedJs/gulpTasks';
|
||||
|
||||
const tasks = {
|
||||
encodeAssets: {
|
||||
fn: require('./tools/encodeAssets'),
|
||||
},
|
||||
buildInjectedJs: {
|
||||
fn: buildInjectedJS,
|
||||
},
|
||||
watchInjectedJs: {
|
||||
fn: watchInjectedJS,
|
||||
},
|
||||
...gulpTasks,
|
||||
podInstall: {
|
||||
fn: require('./tools/podInstall'),
|
||||
},
|
||||
@@ -19,6 +15,22 @@ const tasks = {
|
||||
|
||||
utils.registerGulpTasks(gulp, tasks);
|
||||
|
||||
gulp.task('buildInjectedJs', gulp.series(
|
||||
'beforeBundle',
|
||||
'buildCodeMirrorEditor',
|
||||
'buildJsDrawEditor',
|
||||
'copyWebviewLib',
|
||||
));
|
||||
|
||||
gulp.task('watchInjectedJs', gulp.series(
|
||||
'beforeBundle',
|
||||
'copyWebviewLib',
|
||||
gulp.parallel(
|
||||
'watchCodeMirrorEditor',
|
||||
'watchJsDrawEditor',
|
||||
),
|
||||
));
|
||||
|
||||
gulp.task('build', gulp.series(
|
||||
'buildInjectedJs',
|
||||
'encodeAssets',
|
||||
|
@@ -523,13 +523,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.13.4;
|
||||
MARKETING_VERSION = 12.13.9;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -552,12 +552,12 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Joplin/Joplin.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.13.4;
|
||||
MARKETING_VERSION = 12.13.9;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -704,14 +704,14 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.13.4;
|
||||
MARKETING_VERSION = 12.13.9;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
@@ -735,14 +735,14 @@
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 101;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = A9BXAFS6CT;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.13.4;
|
||||
MARKETING_VERSION = 12.13.9;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
@@ -359,9 +359,9 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-rsa-native (2.0.5):
|
||||
- React
|
||||
- react-native-saf-x (2.13.0):
|
||||
- react-native-saf-x (2.13.3):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.7.2):
|
||||
- react-native-safe-area-context (4.7.4):
|
||||
- React-Core
|
||||
- react-native-slider (4.4.3):
|
||||
- React-Core
|
||||
@@ -479,7 +479,8 @@ PODS:
|
||||
- React
|
||||
- RNShare (9.4.1):
|
||||
- React-Core
|
||||
- RNVectorIcons (10.0.0):
|
||||
- RNVectorIcons (10.0.1):
|
||||
- RCT-Folly (= 2021.07.22.00)
|
||||
- React-Core
|
||||
- RNZipArchive (6.1.0):
|
||||
- React-Core
|
||||
@@ -786,8 +787,8 @@ SPEC CHECKSUMS:
|
||||
react-native-image-resizer: 681f7607418b97c084ba2d0999b153b103040d8a
|
||||
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
|
||||
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
|
||||
react-native-saf-x: b758d1b38a96a96b8179a16e96ed966a507ca5c2
|
||||
react-native-safe-area-context: 7aa8e6d9d0f3100a820efb1a98af68aa747f9284
|
||||
react-native-saf-x: 0f7531c9f8bdbb62bbd55ceb7433de7bb756cd73
|
||||
react-native-safe-area-context: 2cd91d532de12acdb0a9cbc8d43ac72a8e4c897c
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
||||
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
|
||||
@@ -817,7 +818,7 @@ SPEC CHECKSUMS:
|
||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNShare: 32e97adc8d8c97d4a26bcdd3c45516882184f8b6
|
||||
RNVectorIcons: 8b5bb0fa61d54cd2020af4f24a51841ce365c7e9
|
||||
RNVectorIcons: ace237de89f1574ef3c963ae9d5da3bd6fbeb02a
|
||||
RNZipArchive: ef9451b849c45a29509bf44e65b788829ab07801
|
||||
SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
@@ -826,4 +827,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 3b2cace838120977b5b54871752c9dddf5a11cea
|
||||
|
||||
COCOAPODS: 1.14.2
|
||||
COCOAPODS: 1.12.1
|
||||
|
@@ -62,13 +62,13 @@
|
||||
"react-native-popup-menu": "0.16.1",
|
||||
"react-native-quick-actions": "0.3.13",
|
||||
"react-native-rsa-native": "2.0.5",
|
||||
"react-native-safe-area-context": "4.7.2",
|
||||
"react-native-safe-area-context": "4.7.4",
|
||||
"react-native-securerandom": "1.0.1",
|
||||
"react-native-share": "9.4.1",
|
||||
"react-native-side-menu-updated": "1.3.2",
|
||||
"react-native-sqlite-storage": "6.0.1",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-vector-icons": "10.0.0",
|
||||
"react-native-vector-icons": "10.0.1",
|
||||
"react-native-version-info": "1.1.1",
|
||||
"react-native-vosk": "0.1.12",
|
||||
"react-native-webview": "13.6.2",
|
||||
@@ -88,14 +88,14 @@
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/runtime": "7.20.0",
|
||||
"@joplin/tools": "~2.13",
|
||||
"@js-draw/material-icons": "1.5.0",
|
||||
"@js-draw/material-icons": "1.13.2",
|
||||
"@lezer/highlight": "1.1.4",
|
||||
"@testing-library/jest-native": "5.4.3",
|
||||
"@testing-library/react-native": "12.3.0",
|
||||
"@testing-library/react-native": "12.3.1",
|
||||
"@tsconfig/react-native": "2.0.2",
|
||||
"@types/fs-extra": "11.0.3",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/react": "18.2.31",
|
||||
"@types/react": "18.2.33",
|
||||
"@types/react-native": "0.70.6",
|
||||
"@types/react-redux": "7.1.28",
|
||||
"@types/tar-stream": "2.2.3",
|
||||
@@ -106,7 +106,7 @@
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jetifier": "2.0.0",
|
||||
"js-draw": "1.5.0",
|
||||
"js-draw": "1.13.2",
|
||||
"jsdom": "22.1.0",
|
||||
"metro-react-native-babel-preset": "0.73.9",
|
||||
"nodemon": "3.0.1",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
hash:"7d3976ee03fc0f6880dd54c78d1a325b", files: {
|
||||
hash:"0f14c5392fd01275e9aec0841c384482", files: {
|
||||
'highlight.js/atom-one-dark-reasonable.css': { data: require('./highlight.js/atom-one-dark-reasonable.css.base64.js'), mime: 'text/css', encoding: 'base64' },
|
||||
'highlight.js/atom-one-light.css': { data: require('./highlight.js/atom-one-light.css.base64.js'), mime: 'text/css', encoding: 'base64' },
|
||||
'katex/fonts/KaTeX_AMS-Regular.woff2': { data: require('./katex/fonts/KaTeX_AMS-Regular.woff2.base64.js'), mime: 'application/octet-stream', encoding: 'base64' },
|
||||
|
File diff suppressed because one or more lines are too long
@@ -317,6 +317,7 @@ const appReducer = (state = appDefaultState, action: any) => {
|
||||
|
||||
if ('smartFilterId' in action) {
|
||||
newState.smartFilterId = action.smartFilterId;
|
||||
newState.selectedSmartFilterId = action.smartFilterId;
|
||||
newState.notesParentType = 'SmartFilter';
|
||||
}
|
||||
|
||||
@@ -1032,10 +1033,13 @@ class AppComponent extends React.Component {
|
||||
|
||||
let sideMenuContent: ReactNode = null;
|
||||
let menuPosition: SideMenuPosition = 'left';
|
||||
let disableSideMenuGestures = this.props.disableSideMenuGestures;
|
||||
|
||||
if (this.props.routeName === 'Note') {
|
||||
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContentNote options={this.props.noteSideMenuOptions}/></SafeAreaView>;
|
||||
menuPosition = 'right';
|
||||
} else if (this.props.routeName === 'Config') {
|
||||
disableSideMenuGestures = true;
|
||||
} else {
|
||||
sideMenuContent = <SafeAreaView style={{ flex: 1, backgroundColor: theme.backgroundColor }}><SideMenuContent/></SafeAreaView>;
|
||||
}
|
||||
@@ -1076,7 +1080,7 @@ class AppComponent extends React.Component {
|
||||
openMenuOffset={this.state.sideMenuWidth}
|
||||
menuPosition={menuPosition}
|
||||
onChange={(isOpen: boolean) => this.sideMenu_change(isOpen)}
|
||||
disableGestures={this.props.disableSideMenuGestures}
|
||||
disableGestures={disableSideMenuGestures}
|
||||
onSliding={(percent: number) => {
|
||||
this.props.dispatch({
|
||||
type: 'SIDE_MENU_OPEN_PERCENT',
|
||||
|
@@ -3,30 +3,14 @@
|
||||
// files: First here we convert the JS file to a plain string, and that string
|
||||
// is then loaded by eg. the Mermaid plugin, and finally injected in the WebView.
|
||||
|
||||
import { mkdirp, readFile, writeFile } from 'fs-extra';
|
||||
import { dirname, extname, basename } from 'path';
|
||||
|
||||
// We need this to be transpiled to `const webpack = require('webpack')`.
|
||||
// As such, do a namespace import. See https://www.typescriptlang.org/tsconfig#esModuleInterop
|
||||
import * as webpack from 'webpack';
|
||||
import copyJs from './copyJs';
|
||||
|
||||
const rootDir = dirname(dirname(dirname(__dirname)));
|
||||
const mobileDir = `${rootDir}/packages/app-mobile`;
|
||||
const outputDir = `${mobileDir}/lib/rnInjectedJs`;
|
||||
|
||||
// Stores the contents of the file at [filePath] as an importable string.
|
||||
// [name] should be the name (excluding the .js extension) of the output file that will contain
|
||||
// the JSON-ified file content.
|
||||
async function copyJs(name: string, filePath: string) {
|
||||
const outputPath = `${outputDir}/${name}.js`;
|
||||
console.info(`Creating: ${outputPath}`);
|
||||
const js = await readFile(filePath, 'utf-8');
|
||||
const json = `module.exports = ${JSON.stringify(js)};`;
|
||||
await writeFile(outputPath, json);
|
||||
}
|
||||
|
||||
|
||||
class BundledFile {
|
||||
export default class BundledFile {
|
||||
private readonly bundleOutputPath: string;
|
||||
private readonly bundleBaseName: string;
|
||||
private readonly rootFileDirectory: string;
|
||||
@@ -82,17 +66,23 @@ class BundledFile {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Creates a file that can be imported by React native. This file contains the
|
||||
// bundled JS as a string.
|
||||
private async copyToImportableFile() {
|
||||
await copyJs(`${this.bundleBaseName}.bundle`, this.bundleOutputPath);
|
||||
}
|
||||
|
||||
private handleErrors(error: Error | undefined | null, stats: webpack.Stats | undefined): boolean {
|
||||
let failed = false;
|
||||
|
||||
if (error) {
|
||||
console.error(`Error: ${error.name}`, error.message, error.stack);
|
||||
console.error(`Error (${this.bundleName}): ${error.name}`, error.message, error.stack);
|
||||
failed = true;
|
||||
} else if (stats?.hasErrors() || stats?.hasWarnings()) {
|
||||
const data = stats.toJson();
|
||||
|
||||
if (data.warnings && data.warningsCount) {
|
||||
console.warn('Warnings: ', data.warningsCount);
|
||||
console.warn(`Warnings (${this.bundleName}): `, data.warningsCount);
|
||||
for (const warning of data.warnings) {
|
||||
// Stack contains the message
|
||||
if (warning.stack) {
|
||||
@@ -103,7 +93,7 @@ class BundledFile {
|
||||
}
|
||||
}
|
||||
if (data.errors && data.errorsCount) {
|
||||
console.error('Errors: ', data.errorsCount);
|
||||
console.error(`Errors (${this.bundleName}): `, data.errorsCount);
|
||||
for (const error of data.errors) {
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
@@ -127,19 +117,34 @@ class BundledFile {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
console.info(`Building bundle: ${this.bundleName}...`);
|
||||
|
||||
compiler.run((error, stats) => {
|
||||
let failed = this.handleErrors(error, stats);
|
||||
compiler.run((buildError, stats) => {
|
||||
// Always output stats, even on success
|
||||
console.log(`Bundle ${this.bundleName} built: `, stats?.toString());
|
||||
|
||||
let failed = this.handleErrors(buildError, stats);
|
||||
|
||||
// Clean up.
|
||||
compiler.close(async (error) => {
|
||||
if (error) {
|
||||
console.error('Error cleaning up:', error);
|
||||
compiler.close(async (closeError) => {
|
||||
if (closeError) {
|
||||
console.error('Error cleaning up:', closeError);
|
||||
failed = true;
|
||||
}
|
||||
|
||||
let copyError;
|
||||
if (!failed) {
|
||||
try {
|
||||
await this.copyToImportableFile();
|
||||
} catch (error) {
|
||||
console.error('Error copying', error);
|
||||
failed = true;
|
||||
copyError = error;
|
||||
}
|
||||
}
|
||||
|
||||
if (!failed) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
reject(closeError ?? buildError ?? copyError);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -160,44 +165,4 @@ class BundledFile {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Creates a file that can be imported by React native. This file contains the
|
||||
// bundled JS as a string.
|
||||
public async copyToImportableFile() {
|
||||
await copyJs(`${this.bundleBaseName}.bundle`, this.bundleOutputPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const bundledFiles: BundledFile[] = [
|
||||
new BundledFile(
|
||||
'codeMirrorBundle',
|
||||
`${mobileDir}/components/NoteEditor/CodeMirror/CodeMirror.ts`,
|
||||
),
|
||||
new BundledFile(
|
||||
'svgEditorBundle',
|
||||
`${mobileDir}/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts`,
|
||||
),
|
||||
];
|
||||
|
||||
export async function buildInjectedJS() {
|
||||
await mkdirp(outputDir);
|
||||
|
||||
|
||||
// Build all in parallel
|
||||
await Promise.all(bundledFiles.map(async file => {
|
||||
await file.build();
|
||||
await file.copyToImportableFile();
|
||||
}));
|
||||
|
||||
await copyJs('webviewLib', `${mobileDir}/../lib/renderers/webviewLib.js`);
|
||||
}
|
||||
|
||||
export async function watchInjectedJS() {
|
||||
// Watch for changes
|
||||
for (const file of bundledFiles) {
|
||||
file.startWatching();
|
||||
}
|
||||
}
|
||||
|
||||
|
6
packages/app-mobile/tools/buildInjectedJs/constants.ts
Normal file
6
packages/app-mobile/tools/buildInjectedJs/constants.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
import { dirname } from 'path';
|
||||
|
||||
export const mobileDir = dirname(dirname(__dirname));
|
||||
export const outputDir = `${mobileDir}/lib/rnInjectedJs`;
|
||||
export const rootDir = dirname(dirname(mobileDir));
|
16
packages/app-mobile/tools/buildInjectedJs/copyJs.ts
Normal file
16
packages/app-mobile/tools/buildInjectedJs/copyJs.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
|
||||
import { readFile, writeFile } from 'fs-extra';
|
||||
import { outputDir } from './constants';
|
||||
|
||||
// Stores the contents of the file at [filePath] as an importable string.
|
||||
// [name] should be the name (excluding the .js extension) of the output file that will contain
|
||||
// the JSON-ified file content.
|
||||
const copyJs = async (name: string, filePath: string) => {
|
||||
const outputPath = `${outputDir}/${name}.js`;
|
||||
console.info(`Creating: ${outputPath}`);
|
||||
const js = await readFile(filePath, 'utf-8');
|
||||
const json = `module.exports = ${JSON.stringify(js)};`;
|
||||
await writeFile(outputPath, json);
|
||||
};
|
||||
export default copyJs;
|
38
packages/app-mobile/tools/buildInjectedJs/gulpTasks.ts
Normal file
38
packages/app-mobile/tools/buildInjectedJs/gulpTasks.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import BundledFile from './BundledFile';
|
||||
import { mkdirp } from 'fs-extra';
|
||||
import { mobileDir, outputDir } from './constants';
|
||||
import copyJs from './copyJs';
|
||||
|
||||
|
||||
const codeMirrorBundle = new BundledFile(
|
||||
'codeMirrorBundle',
|
||||
`${mobileDir}/components/NoteEditor/CodeMirror/CodeMirror.ts`,
|
||||
);
|
||||
|
||||
const jsDrawBundle = new BundledFile(
|
||||
'svgEditorBundle',
|
||||
`${mobileDir}/components/NoteEditor/ImageEditor/js-draw/createJsDrawEditor.ts`,
|
||||
);
|
||||
|
||||
const gulpTasks = {
|
||||
beforeBundle: {
|
||||
fn: () => mkdirp(outputDir),
|
||||
},
|
||||
buildCodeMirrorEditor: {
|
||||
fn: () => codeMirrorBundle.build(),
|
||||
},
|
||||
buildJsDrawEditor: {
|
||||
fn: () => jsDrawBundle.build(),
|
||||
},
|
||||
watchCodeMirrorEditor: {
|
||||
fn: () => codeMirrorBundle.startWatching(),
|
||||
},
|
||||
watchJsDrawEditor: {
|
||||
fn: () => jsDrawBundle.startWatching(),
|
||||
},
|
||||
copyWebviewLib: {
|
||||
fn: () => copyJs('webviewLib', `${mobileDir}/../lib/renderers/webviewLib.js`),
|
||||
},
|
||||
};
|
||||
|
||||
export default gulpTasks;
|
@@ -9,6 +9,7 @@ import * as tar from 'tar-stream';
|
||||
import { resolve } from 'path';
|
||||
import { Buffer } from 'buffer';
|
||||
import Logger from '@joplin/utils/Logger';
|
||||
import JoplinError from '@joplin/lib/JoplinError';
|
||||
|
||||
const logger = Logger.create('fs-driver-rn');
|
||||
|
||||
@@ -285,6 +286,10 @@ export default class FsDriverRN extends FsDriverBase {
|
||||
}
|
||||
|
||||
public async readFileChunk(handle: any, length: number, rawEncoding = 'base64') {
|
||||
if (!handle?.stat) {
|
||||
throw new JoplinError('File does not exist (reading file chunk).', 'ENOENT');
|
||||
}
|
||||
|
||||
const encoding = normalizeEncoding(rawEncoding);
|
||||
|
||||
if (handle.offset + length > handle.stat.size) {
|
||||
|
@@ -167,6 +167,18 @@ const testReadFileChunkUtf8 = async (tempDir: string) => {
|
||||
|
||||
await fsDriver.close(filePath);
|
||||
}
|
||||
|
||||
// Should throw when the file doesn't exist
|
||||
let readData = undefined;
|
||||
try {
|
||||
const handle = await fsDriver.open(`${filePath}.noexist`, 'r');
|
||||
readData = await fsDriver.readFileChunk(handle, 1, 'utf8');
|
||||
} catch (error) {
|
||||
await expectToBe(error.code, 'ENOENT');
|
||||
}
|
||||
|
||||
// Should not have read any data
|
||||
await expectToBe(readData, undefined);
|
||||
};
|
||||
|
||||
const testTarCreate = async (tempDir: string) => {
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"@joplin/lib": "~2.13",
|
||||
"@testing-library/react-hooks": "8.0.1",
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/react": "18.2.31",
|
||||
"@types/react": "18.2.33",
|
||||
"@types/react-redux": "7.1.28",
|
||||
"@types/styled-components": "5.1.29",
|
||||
"jest": "29.7.0",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@joplin/fork-htmlparser2",
|
||||
"description": "Fast & forgiving HTML/XML/RSS parser",
|
||||
"version": "4.1.48",
|
||||
"version": "4.1.50",
|
||||
"author": "Felix Boehm <me@feedic.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "29.5.5",
|
||||
"@types/node": "18.18.6",
|
||||
"@types/node": "18.18.7",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.2",
|
||||
"@typescript-eslint/parser": "6.7.2",
|
||||
"coveralls": "3.1.1",
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "@joplin/fork-sax",
|
||||
"description": "An evented streaming XML parser in JavaScript",
|
||||
"author": "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)",
|
||||
"version": "1.2.52",
|
||||
"version": "1.2.54",
|
||||
"main": "lib/sax.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/fork-uslug",
|
||||
"version": "1.0.13",
|
||||
"version": "1.0.15",
|
||||
"description": "A permissive slug generator that works with unicode.",
|
||||
"author": "Jeremy Selier <jerem.selier@gmail.com>",
|
||||
"publishConfig": {
|
||||
|
@@ -12,17 +12,6 @@ module.exports = class extends Generator {
|
||||
|
||||
this.option('silent');
|
||||
this.option('update');
|
||||
|
||||
// This appears to be deprecated and undocumented
|
||||
// Maybe need this instead?
|
||||
// https://github.com/yeoman/generator/issues/1294#issuecomment-841668595
|
||||
|
||||
// this.option('nodePackageManager', 'npm');
|
||||
|
||||
if (this.options.update) {
|
||||
// When updating, overwrite files without prompting
|
||||
this.conflicter.force = true;
|
||||
}
|
||||
}
|
||||
|
||||
async prompting() {
|
||||
@@ -165,6 +154,15 @@ module.exports = class extends Generator {
|
||||
},
|
||||
},
|
||||
);
|
||||
} else if (this.options.update) {
|
||||
this.fs.copy(
|
||||
this.templatePath(file),
|
||||
destFilePath, {
|
||||
process: (sourceBuffer) => {
|
||||
return sourceBuffer.toString();
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.fs.copyTpl(
|
||||
this.templatePath(file),
|
||||
|
@@ -7,7 +7,7 @@ This documentation describes how to create a plugin, and how to work with the pl
|
||||
First, install [Yeoman](http://yeoman.io) and generator-joplin using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)).
|
||||
|
||||
```bash
|
||||
npm install -g yo
|
||||
npm install -g yo@4.3.1
|
||||
npm install -g generator-joplin
|
||||
```
|
||||
|
||||
|
@@ -2,7 +2,7 @@ import { ModelType } from '../../../BaseModel';
|
||||
import Plugin from '../Plugin';
|
||||
import { Path } from './types';
|
||||
/**
|
||||
* This module provides access to the Joplin data API: https://joplinapp.org/api/references/rest_api/
|
||||
* This module provides access to the Joplin data API: https://joplinapp.org/help/api/references/rest_api
|
||||
* This is the main way to retrieve data, such as notes, notebooks, tags, etc.
|
||||
* or to update them or delete them.
|
||||
*
|
||||
@@ -18,7 +18,7 @@ import { Path } from './types';
|
||||
* * `data`: (Optional) Applies to PUT and POST calls only. The request body contains the data you want to create or modify, for example the content of a note or folder.
|
||||
* * `files`: (Optional) Used to create new resources and associate them with files.
|
||||
*
|
||||
* Please refer to the [Joplin API documentation](https://joplinapp.org/api/references/rest_api/) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
|
||||
* Please refer to the [Joplin API documentation](https://joplinapp.org/help/api/references/rest_api) for complete details about each call. As the plugin runs within the Joplin application **you do not need an authorisation token** to use this API.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
|
@@ -9,7 +9,7 @@ import { ExportModule, ImportModule } from './types';
|
||||
*
|
||||
* See the documentation of the [[ExportModule]] and [[ImportModule]] for more information.
|
||||
*
|
||||
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
|
||||
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/help/api/references/rest_api
|
||||
*/
|
||||
export default class JoplinInterop {
|
||||
registerExportModule(module: ExportModule): Promise<void>;
|
||||
|
@@ -524,13 +524,13 @@ export interface ContentScriptModuleLoadedEvent {
|
||||
}
|
||||
|
||||
export interface ContentScriptModule {
|
||||
onLoaded?: (event:ContentScriptModuleLoadedEvent) => void;
|
||||
plugin: () => any;
|
||||
assets?: () => void;
|
||||
onLoaded?: (event: ContentScriptModuleLoadedEvent)=> void;
|
||||
plugin: ()=> any;
|
||||
assets?: ()=> void;
|
||||
}
|
||||
|
||||
export interface MarkdownItContentScriptModule extends Omit<ContentScriptModule, 'plugin'> {
|
||||
plugin: (markdownIt:any, options:any) => any;
|
||||
plugin: (markdownIt: any, options: any)=> any;
|
||||
}
|
||||
|
||||
export enum ContentScriptType {
|
||||
@@ -552,7 +552,7 @@ export enum ContentScriptType {
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* See [the
|
||||
* demo](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/content_script)
|
||||
* for a simple Markdown-it plugin example.
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
|
||||
"prepare": "npm run dist",
|
||||
"updateVersion": "webpack --env joplin-plugin-config=updateVersion",
|
||||
"update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update"
|
||||
"update": "npm install -g generator-joplin && yo joplin --node-package-manager npm --update --force"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
|
@@ -93,19 +93,24 @@ function validateCategories(categories) {
|
||||
|
||||
function validateScreenshots(screenshots) {
|
||||
if (!screenshots) return null;
|
||||
// eslint-disable-next-line github/array-foreach -- Old code before rule was applied
|
||||
screenshots.forEach(screenshot => {
|
||||
for (const screenshot of screenshots) {
|
||||
if (!screenshot.src) throw new Error('You must specify a src for each screenshot');
|
||||
|
||||
// Avoid attempting to download and verify URL screenshots.
|
||||
if (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const screenshotType = screenshot.src.split('.').pop();
|
||||
if (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \n${allPossibleScreenshotsType}\n`);
|
||||
|
||||
const screenshotPath = path.resolve(srcDir, screenshot.src);
|
||||
const screenshotPath = path.resolve(rootDir, screenshot.src);
|
||||
|
||||
// Max file size is 1MB
|
||||
const fileMaxSize = 1024;
|
||||
const fileSize = fs.statSync(screenshotPath).size / 1024;
|
||||
if (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function readManifest(manifestPath) {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generator-joplin",
|
||||
"version": "2.13.1",
|
||||
"version": "2.13.2",
|
||||
"description": "Scaffolds out a new Joplin plugin",
|
||||
"homepage": "https://github.com/laurent22/joplin/tree/dev/packages/generator-joplin",
|
||||
"author": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/htmlpack",
|
||||
"version": "2.13.2",
|
||||
"version": "2.13.4",
|
||||
"description": "Pack an HTML file and all its linked resources into a single HTML file",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
@@ -14,7 +14,7 @@
|
||||
"author": "Laurent Cozic",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.48",
|
||||
"@joplin/fork-htmlparser2": "^4.1.50",
|
||||
"css": "3.0.0",
|
||||
"datauri": "4.1.0",
|
||||
"fs-extra": "11.1.1",
|
||||
|
@@ -26,6 +26,7 @@ import time from './time';
|
||||
import BaseSyncTarget from './BaseSyncTarget';
|
||||
const reduxSharedMiddleware = require('./components/shared/reduxSharedMiddleware');
|
||||
const os = require('os');
|
||||
import dns = require('dns');
|
||||
import fs = require('fs-extra');
|
||||
const EventEmitter = require('events');
|
||||
const syswidecas = require('./vendor/syswide-cas');
|
||||
@@ -171,6 +172,13 @@ export default class BaseApplication {
|
||||
this.showStackTraces_ = true;
|
||||
}
|
||||
|
||||
// Work around issues with ipv6 resolution -- default to ipv4first.
|
||||
// (possibly incorrect URL serialization see https://github.com/mswjs/msw/issues/1388#issuecomment-1241180921).
|
||||
// See also https://github.com/node-fetch/node-fetch/issues/1624#issuecomment-1407717012
|
||||
if (flags.matched.allowOverridingDnsResultOrder) {
|
||||
dns.setDefaultResultOrder('ipv4first');
|
||||
}
|
||||
|
||||
return {
|
||||
matched: flags.matched,
|
||||
argv: flags.argv,
|
||||
|
@@ -7,6 +7,7 @@ const pdfUrlRegex = /[\s\S]*?\.pdf$/i;
|
||||
export interface ParseOptions {
|
||||
anchorNames?: string[];
|
||||
preserveImageTagsWithSize?: boolean;
|
||||
preserveNestedTables?: boolean;
|
||||
baseUrl?: string;
|
||||
disableEscapeContent?: boolean;
|
||||
convertEmbeddedPdfsToLinks?: boolean;
|
||||
@@ -20,6 +21,7 @@ export default class HtmlToMd {
|
||||
anchorNames: options.anchorNames ? options.anchorNames.map(n => n.trim().toLowerCase()) : [],
|
||||
codeBlockStyle: 'fenced',
|
||||
preserveImageTagsWithSize: !!options.preserveImageTagsWithSize,
|
||||
preserveNestedTables: !!options.preserveNestedTables,
|
||||
bulletListMarker: '-',
|
||||
emDelimiter: '*',
|
||||
strongDelimiter: '**',
|
||||
|
@@ -224,6 +224,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"يتوفر تحديث ، هل تريد تنزيله الآن؟"
|
||||
],
|
||||
@@ -1254,6 +1257,9 @@
|
||||
"جارٍ إنشاء الروابط...",
|
||||
"جارٍ إنشاء الروابط..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"احصل عليها الآن:"
|
||||
],
|
||||
@@ -1589,7 +1595,7 @@
|
||||
"Logout": [
|
||||
"تسجيل الخروج"
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1643,6 +1649,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing Master Keys": [
|
||||
"المفاتيح الرئيسة مفقودة"
|
||||
],
|
||||
@@ -1895,6 +1904,9 @@
|
||||
"Open...": [
|
||||
"فتح..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"ألغيت العملية"
|
||||
],
|
||||
@@ -2972,6 +2984,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"عند إنشاء قائمة جديدة للمهام:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"كلمات"
|
||||
],
|
||||
|
@@ -188,6 +188,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Има обновление, искате ли да го свалите сега?"
|
||||
],
|
||||
@@ -929,6 +932,9 @@
|
||||
"General": [
|
||||
"Общи"
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
""
|
||||
],
|
||||
@@ -1196,7 +1202,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1232,6 +1238,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing Master Keys": [
|
||||
"Липсващи главни ключове"
|
||||
],
|
||||
@@ -1430,6 +1439,9 @@
|
||||
"Open...": [
|
||||
"Отвори..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Действието бе отменено"
|
||||
],
|
||||
@@ -2393,6 +2405,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Когато се създава нова задача:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -191,6 +191,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Nova je verzija dostupna. Želite li je preuzeti?"
|
||||
],
|
||||
@@ -938,6 +941,9 @@
|
||||
"General": [
|
||||
"Opće"
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Preuzmite sada:"
|
||||
],
|
||||
@@ -1202,7 +1208,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1238,6 +1244,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing Master Keys": [
|
||||
"Nedostaju glavni ključevi"
|
||||
],
|
||||
@@ -1424,6 +1433,9 @@
|
||||
"Open...": [
|
||||
"Otvori..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Radnja je prekinuta"
|
||||
],
|
||||
@@ -2387,6 +2399,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Prilikom kreiranja novog zadatka:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Riječi"
|
||||
],
|
||||
|
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Hi ha disponible una actualització. Voleu descarregar-la ara?"
|
||||
],
|
||||
@@ -1267,6 +1270,9 @@
|
||||
"Generant enllaç...",
|
||||
"Generant enllaços..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Obteniu-lo ara:"
|
||||
],
|
||||
@@ -1622,8 +1628,8 @@
|
||||
"Logout": [
|
||||
"Desconnecta"
|
||||
],
|
||||
"Logs": [
|
||||
"Registres"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Donatius"
|
||||
@@ -1685,6 +1691,9 @@
|
||||
"Max Total Size": [
|
||||
"Mida màxima total"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Claus que falten"
|
||||
],
|
||||
@@ -1949,6 +1958,9 @@
|
||||
"Open...": [
|
||||
"Obre..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"L'operació s'ha cancel·lat"
|
||||
],
|
||||
@@ -3167,6 +3179,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"En crear un llistat de tasques pendents:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Paraules"
|
||||
],
|
||||
|
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Je k dispozici aktualizace, chcete ji stáhnout?"
|
||||
],
|
||||
@@ -1236,6 +1239,9 @@
|
||||
"Vytváření odkazů...",
|
||||
"Vytváření odkazů..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Získat hned:"
|
||||
],
|
||||
@@ -1586,8 +1592,8 @@
|
||||
"Logout": [
|
||||
"Odhlásit se"
|
||||
],
|
||||
"Logs": [
|
||||
"Logy"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Přispět"
|
||||
@@ -1649,6 +1655,9 @@
|
||||
"Max Total Size": [
|
||||
"Maximální celková velikost"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Chybějící klíče"
|
||||
],
|
||||
@@ -1913,6 +1922,9 @@
|
||||
"Open...": [
|
||||
"Otevřít..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operace zrušena"
|
||||
],
|
||||
@@ -3110,6 +3122,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Při vytváření nového úkolu:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Slova"
|
||||
],
|
||||
|
@@ -74,6 +74,9 @@
|
||||
"%s: %s": [
|
||||
"%s: %s"
|
||||
],
|
||||
"%s: Missing password.": [
|
||||
"%s: Manglende adgangskode."
|
||||
],
|
||||
"&Edit": [
|
||||
"&Redigér"
|
||||
],
|
||||
@@ -108,7 +111,7 @@
|
||||
"(wysiwyg: %s)"
|
||||
],
|
||||
"(You may disable this prompt in the options)": [
|
||||
""
|
||||
"(Du kan deaktivere denne prompt i indstillingerne)"
|
||||
],
|
||||
"- Camera: to allow taking a picture and attaching it to a note.": [
|
||||
"- Kamera: Tilladelse til fotografering og vedhæftning af et billede til en note."
|
||||
@@ -218,6 +221,12 @@
|
||||
"Always": [
|
||||
"Altid"
|
||||
],
|
||||
"Always ask": [
|
||||
"Spørg altid"
|
||||
],
|
||||
"Always resize": [
|
||||
"Ændr altid størrelsen"
|
||||
],
|
||||
"Ambiguous notebook \"%s\". Please use notebook id instead - press \"ti\" to see the short notebook id or use $b for current selected notebook": [
|
||||
"Tvetydig notesbog \"%s\". Brug venligst notesbog-id i stedet - tryk på \"ti\" for at se det korte notebogs-id eller brug $b for den aktuelle valgte notesbog"
|
||||
],
|
||||
@@ -225,13 +234,16 @@
|
||||
"Tvetydig notesbog \"%s\". Brug venligst kort notesbog-id i stedet - tryk på \"ti\" for at se det korte notebogs-id"
|
||||
],
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
"Der blev fundet en automatisk gemt tegning. Vedhæfter en kopi af den til noten?"
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Opdatering er tilgængelig, vil du hente den nu?"
|
||||
],
|
||||
"Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook": [
|
||||
"Enhver e-mail, der sendes til denne adresse, bliver konverteret til en note og føjet til din samling. Noten gemmes i indbakkens notesbog."
|
||||
"Enhver e-mail, der sendes til denne adresse, bliver konverteret til en note og føjet til din samling. Noten gemmes i indbakkens notesbog"
|
||||
],
|
||||
"Appearance": [
|
||||
"Udseende"
|
||||
@@ -464,6 +476,9 @@
|
||||
"Code View": [
|
||||
"Kodevisning"
|
||||
],
|
||||
"Collaborate on a notebook with others": [
|
||||
"Samarbejd med andre om en notesbog"
|
||||
],
|
||||
"Collaborate on notebooks with others": [
|
||||
"Samarbejd med andre om notesbøger"
|
||||
],
|
||||
@@ -488,6 +503,9 @@
|
||||
"Command palette...": [
|
||||
"Kommando-udvalg..."
|
||||
],
|
||||
"Compact": [
|
||||
"Kompakt"
|
||||
],
|
||||
"Completed": [
|
||||
"Fuldført"
|
||||
],
|
||||
@@ -651,6 +669,12 @@
|
||||
"Creates a new to-do.": [
|
||||
"Opretter en ny opgave."
|
||||
],
|
||||
"Creating new note...": [
|
||||
"Opretter ny note..."
|
||||
],
|
||||
"Creating new to-do...": [
|
||||
"Opretter nye gøremål..."
|
||||
],
|
||||
"Creating report...": [
|
||||
"Opretter rapport..."
|
||||
],
|
||||
@@ -673,7 +697,7 @@
|
||||
"Brugerdefinerede TLS certifikater"
|
||||
],
|
||||
"Customise the note publishing banner": [
|
||||
""
|
||||
"Tilpas banneret til udgivelse af noter"
|
||||
],
|
||||
"Cut": [
|
||||
"Klip"
|
||||
@@ -793,7 +817,7 @@
|
||||
"Destinationsformat: %s"
|
||||
],
|
||||
"Detailed": [
|
||||
""
|
||||
"Detailed"
|
||||
],
|
||||
"Directory": [
|
||||
"Indeks"
|
||||
@@ -819,6 +843,9 @@
|
||||
"Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?": [
|
||||
"Slås kryptering fra vil *alle* dine noter og vedhæftninger blive re-synkroniseret og sendt ukrypteret til synk-modtageren. Vil du fortsætte?"
|
||||
],
|
||||
"Discard": [
|
||||
"Kassér"
|
||||
],
|
||||
"Discard changes": [
|
||||
"Kassér ændringer"
|
||||
],
|
||||
@@ -892,10 +919,10 @@
|
||||
"Dracula"
|
||||
],
|
||||
"Draw picture": [
|
||||
""
|
||||
"Tegn billede"
|
||||
],
|
||||
"Drawing": [
|
||||
""
|
||||
"Tegner"
|
||||
],
|
||||
"Drop notes or files here": [
|
||||
"Slip noter eller filer her"
|
||||
@@ -1098,6 +1125,9 @@
|
||||
"Enter notebook title": [
|
||||
"Indtast notesbogstitel"
|
||||
],
|
||||
"Enter password": [
|
||||
"Indtast adgangskode"
|
||||
],
|
||||
"Enum": [
|
||||
"Enum"
|
||||
],
|
||||
@@ -1267,6 +1297,9 @@
|
||||
"Opretter link...",
|
||||
"Opretter links..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Få den nu:"
|
||||
],
|
||||
@@ -1334,7 +1367,7 @@
|
||||
"HTML Indeks"
|
||||
],
|
||||
"HTML document": [
|
||||
""
|
||||
"HTML-dokument"
|
||||
],
|
||||
"HTML File": [
|
||||
"HTML fil"
|
||||
@@ -1553,7 +1586,7 @@
|
||||
"Indstil knap-rækkefølge"
|
||||
],
|
||||
"Leave it blank to download the language files from the default website": [
|
||||
"Lad den være tom for at downloade sprogfilerne fra standardwebstedet."
|
||||
"Lad den være tom for at downloade sprogfilerne fra standardwebstedet"
|
||||
],
|
||||
"Leave notebook...": [
|
||||
"Forlad notesbog..."
|
||||
@@ -1622,8 +1655,8 @@
|
||||
"Logout": [
|
||||
"Log ud"
|
||||
],
|
||||
"Logs": [
|
||||
"Logfiler"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Giv en donation"
|
||||
@@ -1685,6 +1718,9 @@
|
||||
"Max Total Size": [
|
||||
"Maks samlet størrelse"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Manglende nøgler"
|
||||
],
|
||||
@@ -1731,7 +1767,7 @@
|
||||
"N"
|
||||
],
|
||||
"Never resize": [
|
||||
""
|
||||
"Ændr aldrig størrelse"
|
||||
],
|
||||
"New note": [
|
||||
"Ny note"
|
||||
@@ -1862,6 +1898,9 @@
|
||||
"Note list growth factor": [
|
||||
"Noteliste-vækstfaktor"
|
||||
],
|
||||
"Note list style": [
|
||||
"Notatliste-stil"
|
||||
],
|
||||
"Note properties": [
|
||||
"Noteegenskaber"
|
||||
],
|
||||
@@ -1949,6 +1988,9 @@
|
||||
"Open...": [
|
||||
"Åbn..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Udførelse annulleret"
|
||||
],
|
||||
@@ -2238,7 +2280,7 @@
|
||||
"Nulstil hovedadgangskode"
|
||||
],
|
||||
"Resize large images:": [
|
||||
""
|
||||
"Ændr størrelsen på store billeder:"
|
||||
],
|
||||
"Resources: %d.": [
|
||||
"Ressourcer: %d."
|
||||
@@ -2312,6 +2354,9 @@
|
||||
"Save changes": [
|
||||
"Gem ændringer"
|
||||
],
|
||||
"Save changes?": [
|
||||
"Gem ændringer?"
|
||||
],
|
||||
"Save geo-location with notes": [
|
||||
"Gem geo-lokation i noter"
|
||||
],
|
||||
@@ -2387,6 +2432,9 @@
|
||||
"Share a copy of all notes in a file format that can be imported by Joplin on a computer.": [
|
||||
"Del en kopi af alle noter i et filformat, der kan importeres af Joplin på en computer."
|
||||
],
|
||||
"Share a notebook with others": [
|
||||
"Del en notesbog med andre"
|
||||
],
|
||||
"Share Notebook": [
|
||||
"Del notesbog"
|
||||
],
|
||||
@@ -2394,7 +2442,7 @@
|
||||
"Del notesbog..."
|
||||
],
|
||||
"Share permissions": [
|
||||
""
|
||||
"Deletilladelser"
|
||||
],
|
||||
"Sharing notebook...": [
|
||||
"Deler notesbog..."
|
||||
@@ -2436,10 +2484,13 @@
|
||||
"Vis/skjul sidebjælken"
|
||||
],
|
||||
"Shrink large images before adding them to notes.": [
|
||||
""
|
||||
"Formindsk store billeder, før du tilføjer dem til noter."
|
||||
],
|
||||
"Side menu closed": [
|
||||
"Sidemenu lukket"
|
||||
],
|
||||
"Side menu opened": [
|
||||
""
|
||||
"Sidemenu åbnet"
|
||||
],
|
||||
"Sidebar": [
|
||||
"Sidebjælke"
|
||||
@@ -2672,6 +2723,9 @@
|
||||
"Teams": [
|
||||
"Teams"
|
||||
],
|
||||
"Text document": [
|
||||
"Tekstdokument"
|
||||
],
|
||||
"Text editor command": [
|
||||
"Tekstredigeringskomando"
|
||||
],
|
||||
@@ -2765,6 +2819,9 @@
|
||||
"The sync target needs to be upgraded. Press this banner to proceed.": [
|
||||
"Synk-mål skal opgraderes! Tryk på denne banner for at fortsætte."
|
||||
],
|
||||
"The synchronisation password is missing.": [
|
||||
"Adgangskoden til synkronisering mangler."
|
||||
],
|
||||
"The tag \"%s\" already exists. Please choose a different name.": [
|
||||
"Etiketten \"%s\" eksisterer allerede. Vælg venligst et andet navn."
|
||||
],
|
||||
@@ -2814,7 +2871,7 @@
|
||||
"Disse udvidelser forbedrer Markdown-fortolkeren med yderligere funktioner. Vær opmærksom på, at selvom disse funktioner kan være nyttige, er de ikke standard Markdown og derfor vil de fleste kun virke i Joplin. Derudover er nogle af dem ikke kompatible med WYSIWYG editoren. Hvis du åbner en note, som bruger en af disse udvidelser i den editor, vil du miste udvidelsesformatteringen. Det angives nedenfor, hvilke udvidelser der er kompatible eller ikke med WYSIWYG editoren."
|
||||
],
|
||||
"This allows another user to share a notebook with you, and you can then both collaborate on it. It does not however allow you to share a notebook with someone else, unless you have the feature \"%s\".": [
|
||||
""
|
||||
"Dette giver en anden bruger mulighed for at dele en notesbog med dig, og I kan så begge samarbejde om den. Det giver dig dog ikke mulighed for at dele en notesbog med en anden, medmindre du har funktionen \"%s\"."
|
||||
],
|
||||
"This attachment is not downloaded or not decrypted yet": [
|
||||
"Denne vedhæftning er ikke downloadet eller ikke dekrypteret endnu"
|
||||
@@ -2825,6 +2882,9 @@
|
||||
"This authorisation token is only needed to allow third-party applications to access Joplin.": [
|
||||
"Denne autoriseringstoken er kun nødvendig for at tillade tredjepartsprogrammer at tilgå Joplin."
|
||||
],
|
||||
"This drawing may have unsaved changes.": [
|
||||
"Denne tegning kan have ændringer, der ikke er gemt."
|
||||
],
|
||||
"This is an advanced tool to show the attachments that are linked to your notes. Please be careful when deleting one of them as they cannot be restored afterwards.": [
|
||||
"Dette er et avanceret værktøj til at vise de vedhæftninger, som er knyttet til dine noter. Vær forsigtig med at slette dem, da de ikke kan gendannes bagefter."
|
||||
],
|
||||
@@ -2981,9 +3041,15 @@
|
||||
"Type: %s.": [
|
||||
"Tast: %s."
|
||||
],
|
||||
"Unable to edit resource of type %s": [
|
||||
"Kan ikke redigere ressource af typen %s"
|
||||
],
|
||||
"Unable to export or share data. Reason: %s": [
|
||||
"Kan ikke eksportere eller dele data. Årsag: %s"
|
||||
],
|
||||
"Unable to share log data. Reason: %s": [
|
||||
"Kan ikke dele logdata. Årsag: %s"
|
||||
],
|
||||
"Uncompleted to-dos on top": [
|
||||
"Ufærdige opgaver øverst"
|
||||
],
|
||||
@@ -3161,6 +3227,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Ved oprettelse af ny opgave:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Ord"
|
||||
],
|
||||
|
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Es ist eine Aktualisierung verfügbar. Soll sie jetzt heruntergeladen werden?"
|
||||
],
|
||||
@@ -1267,6 +1270,9 @@
|
||||
"Link wird erzeugt ...",
|
||||
"Links werden erzeugt ..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Hole es jetzt:"
|
||||
],
|
||||
@@ -1622,8 +1628,8 @@
|
||||
"Logout": [
|
||||
"Abmelden"
|
||||
],
|
||||
"Logs": [
|
||||
"Protokolle"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Spenden"
|
||||
@@ -1685,6 +1691,9 @@
|
||||
"Max Total Size": [
|
||||
"Maximale Gesamtgröße"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Fehlende Schlüssel"
|
||||
],
|
||||
@@ -1949,6 +1958,9 @@
|
||||
"Open...": [
|
||||
"Öffnen ..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Vorgang abgebrochen"
|
||||
],
|
||||
@@ -3158,6 +3170,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Wenn eine neue Aufgabe erstellt wird:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Wörter"
|
||||
],
|
||||
|
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Υπάρχει διαθέσιμη μια ενημέρωση, θέλετε να την κατεβάσετε τώρα;"
|
||||
],
|
||||
@@ -1243,6 +1246,9 @@
|
||||
"Δημιουργία συνδέσμου...",
|
||||
"Δημιουργία νεου %s..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Λήψη τώρα:"
|
||||
],
|
||||
@@ -1598,8 +1604,8 @@
|
||||
"Logout": [
|
||||
"Αποσύνδεση"
|
||||
],
|
||||
"Logs": [
|
||||
"Αρχεία Καταγραφής"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Κάντε μια δωρεά"
|
||||
@@ -1661,6 +1667,9 @@
|
||||
"Max Total Size": [
|
||||
"Μέγιστο συνολικό μέγεθος"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Απουσία κλειδιών"
|
||||
],
|
||||
@@ -1925,6 +1934,9 @@
|
||||
"Open...": [
|
||||
"Άνοιγμα..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Η λειτουργία ακυρώθηκε"
|
||||
],
|
||||
@@ -3128,6 +3140,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Κατά τη δημιουργία ενός νέου to-do:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Λέξεις"
|
||||
],
|
||||
|
@@ -236,6 +236,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
""
|
||||
],
|
||||
@@ -885,6 +888,9 @@
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": [
|
||||
""
|
||||
],
|
||||
"Donate, website": [
|
||||
""
|
||||
],
|
||||
"Done": [
|
||||
""
|
||||
],
|
||||
@@ -999,6 +1005,9 @@
|
||||
"Email to Note": [
|
||||
""
|
||||
],
|
||||
"Email To Note, login information": [
|
||||
""
|
||||
],
|
||||
"Emails": [
|
||||
""
|
||||
],
|
||||
@@ -1173,6 +1182,9 @@
|
||||
"Export profile": [
|
||||
""
|
||||
],
|
||||
"Export your data": [
|
||||
""
|
||||
],
|
||||
"Exported successfully!": [
|
||||
""
|
||||
],
|
||||
@@ -1294,6 +1306,9 @@
|
||||
"",
|
||||
""
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
""
|
||||
],
|
||||
@@ -1567,6 +1582,9 @@
|
||||
"Language": [
|
||||
""
|
||||
],
|
||||
"Language, date format": [
|
||||
""
|
||||
],
|
||||
"Last error: %s": [
|
||||
""
|
||||
],
|
||||
@@ -1649,7 +1667,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1712,6 +1730,9 @@
|
||||
"Max Total Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
""
|
||||
],
|
||||
@@ -1979,6 +2000,9 @@
|
||||
"Open...": [
|
||||
""
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
""
|
||||
],
|
||||
@@ -2276,6 +2300,9 @@
|
||||
"Restart and upgrade": [
|
||||
""
|
||||
],
|
||||
"Restart in safe mode": [
|
||||
""
|
||||
],
|
||||
"Restart now": [
|
||||
""
|
||||
],
|
||||
@@ -2390,6 +2417,9 @@
|
||||
"Select parent notebook": [
|
||||
""
|
||||
],
|
||||
"Send bug report": [
|
||||
""
|
||||
],
|
||||
"Server is already running on port %d": [
|
||||
""
|
||||
],
|
||||
@@ -2654,6 +2684,9 @@
|
||||
"Sync your notes": [
|
||||
""
|
||||
],
|
||||
"Sync, encryption, proxy": [
|
||||
""
|
||||
],
|
||||
"Synchronisation": [
|
||||
""
|
||||
],
|
||||
@@ -2831,6 +2864,9 @@
|
||||
"Theme": [
|
||||
""
|
||||
],
|
||||
"Themes, editor font": [
|
||||
""
|
||||
],
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": [
|
||||
""
|
||||
],
|
||||
@@ -2981,6 +3017,9 @@
|
||||
"Toggle external editing": [
|
||||
""
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
""
|
||||
],
|
||||
"Toggle note list": [
|
||||
""
|
||||
],
|
||||
@@ -3215,6 +3254,9 @@
|
||||
"When creating a new to-do:": [
|
||||
""
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -236,6 +236,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
""
|
||||
],
|
||||
@@ -885,6 +888,9 @@
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": [
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below."
|
||||
],
|
||||
"Donate, website": [
|
||||
""
|
||||
],
|
||||
"Done": [
|
||||
""
|
||||
],
|
||||
@@ -999,6 +1005,9 @@
|
||||
"Email to Note": [
|
||||
""
|
||||
],
|
||||
"Email To Note, login information": [
|
||||
""
|
||||
],
|
||||
"Emails": [
|
||||
""
|
||||
],
|
||||
@@ -1173,6 +1182,9 @@
|
||||
"Export profile": [
|
||||
""
|
||||
],
|
||||
"Export your data": [
|
||||
""
|
||||
],
|
||||
"Exported successfully!": [
|
||||
""
|
||||
],
|
||||
@@ -1294,6 +1306,9 @@
|
||||
"",
|
||||
""
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
""
|
||||
],
|
||||
@@ -1567,6 +1582,9 @@
|
||||
"Language": [
|
||||
""
|
||||
],
|
||||
"Language, date format": [
|
||||
""
|
||||
],
|
||||
"Last error: %s": [
|
||||
""
|
||||
],
|
||||
@@ -1649,7 +1667,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1712,6 +1730,9 @@
|
||||
"Max Total Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
""
|
||||
],
|
||||
@@ -1979,6 +2000,9 @@
|
||||
"Open...": [
|
||||
""
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
""
|
||||
],
|
||||
@@ -2276,6 +2300,9 @@
|
||||
"Restart and upgrade": [
|
||||
""
|
||||
],
|
||||
"Restart in safe mode": [
|
||||
""
|
||||
],
|
||||
"Restart now": [
|
||||
""
|
||||
],
|
||||
@@ -2390,6 +2417,9 @@
|
||||
"Select parent notebook": [
|
||||
""
|
||||
],
|
||||
"Send bug report": [
|
||||
""
|
||||
],
|
||||
"Server is already running on port %d": [
|
||||
""
|
||||
],
|
||||
@@ -2654,6 +2684,9 @@
|
||||
"Sync your notes": [
|
||||
""
|
||||
],
|
||||
"Sync, encryption, proxy": [
|
||||
""
|
||||
],
|
||||
"Synchronisation": [
|
||||
"Synchronization"
|
||||
],
|
||||
@@ -2828,6 +2861,9 @@
|
||||
"Theme": [
|
||||
""
|
||||
],
|
||||
"Themes, editor font": [
|
||||
""
|
||||
],
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": [
|
||||
""
|
||||
],
|
||||
@@ -2978,6 +3014,9 @@
|
||||
"Toggle external editing": [
|
||||
""
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
""
|
||||
],
|
||||
"Toggle note list": [
|
||||
""
|
||||
],
|
||||
@@ -3212,6 +3251,9 @@
|
||||
"When creating a new to-do:": [
|
||||
""
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -194,6 +194,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
""
|
||||
],
|
||||
@@ -770,6 +773,9 @@
|
||||
"Email to Note": [
|
||||
""
|
||||
],
|
||||
"Email To Note, login information": [
|
||||
""
|
||||
],
|
||||
"Emails": [
|
||||
""
|
||||
],
|
||||
@@ -1010,6 +1016,9 @@
|
||||
"General": [
|
||||
"Ĝenerala"
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Akiri ĝin nun:"
|
||||
],
|
||||
@@ -1296,7 +1305,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1335,6 +1344,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
""
|
||||
],
|
||||
@@ -1539,6 +1551,9 @@
|
||||
"Open...": [
|
||||
"Malfermi..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operacio nuligita"
|
||||
],
|
||||
@@ -1857,6 +1872,9 @@
|
||||
"Select all": [
|
||||
"Elekti ĉiujn"
|
||||
],
|
||||
"Send bug report": [
|
||||
""
|
||||
],
|
||||
"Server is already running on port %d": [
|
||||
""
|
||||
],
|
||||
@@ -2073,6 +2091,9 @@
|
||||
"Sync to provided target (defaults to sync.target config value)": [
|
||||
""
|
||||
],
|
||||
"Sync, encryption, proxy": [
|
||||
""
|
||||
],
|
||||
"Synchronisation interval": [
|
||||
""
|
||||
],
|
||||
@@ -2357,6 +2378,9 @@
|
||||
"to-do": [
|
||||
"tasko"
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
""
|
||||
],
|
||||
"Toggle sidebar": [
|
||||
""
|
||||
],
|
||||
@@ -2537,6 +2561,9 @@
|
||||
"When creating a new to-do:": [
|
||||
""
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -224,6 +224,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Está disponible una actualización. ¿Quiere descargarla ahora?"
|
||||
],
|
||||
@@ -1225,6 +1228,9 @@
|
||||
"Creando enlace...",
|
||||
"Creando enlaces..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Obtenla ahora en:"
|
||||
],
|
||||
@@ -1574,8 +1580,8 @@
|
||||
"Logout": [
|
||||
"Cerrar sesión"
|
||||
],
|
||||
"Logs": [
|
||||
"Registros"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Hacer una donación"
|
||||
@@ -1637,6 +1643,9 @@
|
||||
"Max Total Size": [
|
||||
"Tamaño original"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Claves faltantes"
|
||||
],
|
||||
@@ -1898,6 +1907,9 @@
|
||||
"Open...": [
|
||||
"Abrir..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operación cancelada"
|
||||
],
|
||||
@@ -3092,6 +3104,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Al crear una tarea nueva:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Palabras"
|
||||
],
|
||||
|
@@ -191,6 +191,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Saadaval on värskendus, kas soovite selle kohe alla laadida?"
|
||||
],
|
||||
@@ -868,6 +871,9 @@
|
||||
"Lingi loomine...",
|
||||
"Linkide loomine..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Võta nüüd:"
|
||||
],
|
||||
@@ -1124,7 +1130,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1154,6 +1160,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing Master Keys": [
|
||||
"Puuduvad juhtklahvid"
|
||||
],
|
||||
@@ -1337,6 +1346,9 @@
|
||||
"Open...": [
|
||||
"Avatud..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Toiming tühistati"
|
||||
],
|
||||
@@ -2219,6 +2231,9 @@
|
||||
"When creating a new note:": [
|
||||
"Uue märkme loomisel:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -176,6 +176,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
""
|
||||
],
|
||||
@@ -627,6 +630,9 @@
|
||||
"Do not ask for confirmation.": [
|
||||
"Ez galdetu berresteko."
|
||||
],
|
||||
"Donate, website": [
|
||||
""
|
||||
],
|
||||
"Done": [
|
||||
""
|
||||
],
|
||||
@@ -909,6 +915,9 @@
|
||||
"Full name": [
|
||||
""
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
""
|
||||
],
|
||||
@@ -1174,7 +1183,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Manage your plugins": [
|
||||
@@ -1207,6 +1216,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing required argument: %s": [
|
||||
"Beharrezko argumentua faltan: %s"
|
||||
],
|
||||
@@ -1366,6 +1378,9 @@
|
||||
"Open...": [
|
||||
""
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
" Eragiketa utzita"
|
||||
],
|
||||
@@ -2092,6 +2107,9 @@
|
||||
"To work correctly, the app needs the following permissions. Please enable them in your phone settings, in Apps > Joplin > Permissions": [
|
||||
""
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
""
|
||||
],
|
||||
"Toggle sidebar": [
|
||||
""
|
||||
],
|
||||
@@ -2254,6 +2272,9 @@
|
||||
"Welcome to Joplin!\n\nType `:help shortcuts` for the list of keyboard shortcuts, or just `:help` for usage information.\n\nFor example, to create a notebook press `mb`; to create a note press `mn`.": [
|
||||
"Ongi etorri Joplin-era!\n\nIdatz `:help shortcuts` lasterbideak ikusteko, edo soilik `:help`erabilerako informaziorako.\n\nEsate baterako, koadernoa sortzeko sakatu `mb`: oharra sortzeko sakatu `mn`"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Päivitys on saatavilla. Haluatko ladata sen nyt?"
|
||||
],
|
||||
@@ -1234,6 +1237,9 @@
|
||||
"Luodaan linkki...",
|
||||
"Luodaan linkkejä..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Hae se nyt:"
|
||||
],
|
||||
@@ -1583,8 +1589,8 @@
|
||||
"Logout": [
|
||||
"Uloskirjaus"
|
||||
],
|
||||
"Logs": [
|
||||
"Lokit"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Tee lahjoitus"
|
||||
@@ -1646,6 +1652,9 @@
|
||||
"Max Total Size": [
|
||||
"Suurin kokonaiskoko"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Puuttuvat avaimet"
|
||||
],
|
||||
@@ -1910,6 +1919,9 @@
|
||||
"Open...": [
|
||||
"Avaa..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Toiminto peruutettu"
|
||||
],
|
||||
@@ -3107,6 +3119,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Kun luot uuden tehtävän:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Sanat"
|
||||
],
|
||||
|
@@ -236,6 +236,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
"Un dessin automatiquement sauvegardé a été trouvé. Le joindre à la note ?"
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
"Erreur : %s"
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Une mise à jour est disponible, souhaitez vous la télécharger maintenant ?"
|
||||
],
|
||||
@@ -885,6 +888,9 @@
|
||||
"Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.": [
|
||||
"Ne perdez pas le mot de passe car, par sécurité, ce sera la seule façon de déchiffrer les données. Pour activer le chiffrement, veuillez entrer votre mot de passe ci-dessous."
|
||||
],
|
||||
"Donate, website": [
|
||||
"Donations, site web"
|
||||
],
|
||||
"Done": [
|
||||
"Terminé"
|
||||
],
|
||||
@@ -999,6 +1005,9 @@
|
||||
"Email to Note": [
|
||||
"Conversion email en note"
|
||||
],
|
||||
"Email To Note, login information": [
|
||||
"\"Email to note\", info de connexion"
|
||||
],
|
||||
"Emails": [
|
||||
"Emails"
|
||||
],
|
||||
@@ -1173,6 +1182,9 @@
|
||||
"Export profile": [
|
||||
"Exporter le profil"
|
||||
],
|
||||
"Export your data": [
|
||||
"Exporter vos données"
|
||||
],
|
||||
"Exported successfully!": [
|
||||
"Exporté avec succès !"
|
||||
],
|
||||
@@ -1294,6 +1306,9 @@
|
||||
"Génération du lien…",
|
||||
"Génération des liens…"
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
"Géolocalisation, vérification orthographique, barre d'outils, taille des images"
|
||||
],
|
||||
"Get it now:": [
|
||||
"L'obtenir maintenant :"
|
||||
],
|
||||
@@ -1567,6 +1582,9 @@
|
||||
"Language": [
|
||||
"Langue"
|
||||
],
|
||||
"Language, date format": [
|
||||
"Langue, format de la date"
|
||||
],
|
||||
"Last error: %s": [
|
||||
"Dernière erreur : %s"
|
||||
],
|
||||
@@ -1649,8 +1667,8 @@
|
||||
"Logout": [
|
||||
"Se déconnecter"
|
||||
],
|
||||
"Logs": [
|
||||
"Journal"
|
||||
"Logs, profiles, sync status": [
|
||||
"Logs, profils, info synchronisation"
|
||||
],
|
||||
"Make a donation": [
|
||||
"Faire un don"
|
||||
@@ -1712,6 +1730,9 @@
|
||||
"Max Total Size": [
|
||||
"Taille max totale"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
"Lecteur média, math, diagrammes, sommaire"
|
||||
],
|
||||
"Missing keys": [
|
||||
"Clefs manquantes"
|
||||
],
|
||||
@@ -1979,6 +2000,9 @@
|
||||
"Open...": [
|
||||
"Ouvrir…"
|
||||
],
|
||||
"Opening section %s": [
|
||||
"Ouverture section %s"
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Opération annulée"
|
||||
],
|
||||
@@ -2276,6 +2300,9 @@
|
||||
"Restart and upgrade": [
|
||||
"Redémarrer et mettre à jour"
|
||||
],
|
||||
"Restart in safe mode": [
|
||||
"Redémarrer en mode sans échec"
|
||||
],
|
||||
"Restart now": [
|
||||
"Redémarrer maintenant"
|
||||
],
|
||||
@@ -2390,6 +2417,9 @@
|
||||
"Select parent notebook": [
|
||||
"Sélectionner le carnet parent"
|
||||
],
|
||||
"Send bug report": [
|
||||
"Envoyer rapport de débogage"
|
||||
],
|
||||
"Server is already running on port %d": [
|
||||
"Le serveur tourne déjà sur le port %d"
|
||||
],
|
||||
@@ -2654,6 +2684,9 @@
|
||||
"Sync your notes": [
|
||||
"Synchroniser vos notes"
|
||||
],
|
||||
"Sync, encryption, proxy": [
|
||||
"Synchronisation, chiffrement, proxy"
|
||||
],
|
||||
"Synchronisation": [
|
||||
"Synchronisation"
|
||||
],
|
||||
@@ -2831,6 +2864,9 @@
|
||||
"Theme": [
|
||||
"Apparence"
|
||||
],
|
||||
"Themes, editor font": [
|
||||
"Thèmes, police de l'éditeur"
|
||||
],
|
||||
"There are currently no notes. Create one by clicking on the (+) button.": [
|
||||
"Ce carnet ne contient aucune note. Créez‑en une en appuyant sur le bouton (+)."
|
||||
],
|
||||
@@ -2981,6 +3017,9 @@
|
||||
"Toggle external editing": [
|
||||
"Basculer l'édition externe"
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
"Activation historique des notes"
|
||||
],
|
||||
"Toggle note list": [
|
||||
"Basculer liste de notes"
|
||||
],
|
||||
@@ -3215,6 +3254,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Lors de la création d'une tâche :"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
"La fenêtre ne répond pas."
|
||||
],
|
||||
"Words": [
|
||||
"Mots"
|
||||
],
|
||||
|
@@ -173,6 +173,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Hai unha actualización dispoñíbel, desexa descargala agora?"
|
||||
],
|
||||
@@ -900,6 +903,9 @@
|
||||
"Full name": [
|
||||
""
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
""
|
||||
],
|
||||
@@ -1168,7 +1174,7 @@
|
||||
"Logout": [
|
||||
""
|
||||
],
|
||||
"Logs": [
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
@@ -1204,6 +1210,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing Master Keys": [
|
||||
"Faltan as chaves mestras"
|
||||
],
|
||||
@@ -1381,6 +1390,9 @@
|
||||
"Open...": [
|
||||
"Abrir…"
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operación cancelada"
|
||||
],
|
||||
@@ -2137,6 +2149,9 @@
|
||||
"Toggle editor layout": [
|
||||
"Cambiar a disposición do editor"
|
||||
],
|
||||
"Toggle note history, keep notes for": [
|
||||
""
|
||||
],
|
||||
"Toggle sidebar": [
|
||||
""
|
||||
],
|
||||
@@ -2317,6 +2332,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Cando se crea unha nova tarefa:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
""
|
||||
],
|
||||
|
@@ -227,6 +227,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Dostupna je nova verzija. Želiš li je sada preuzeti?"
|
||||
],
|
||||
@@ -1269,6 +1272,9 @@
|
||||
"Generiranje poveznica …",
|
||||
"Generiranje poveznica …"
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Nabavi sada:"
|
||||
],
|
||||
@@ -1625,8 +1631,8 @@
|
||||
"Logout": [
|
||||
"Odjava"
|
||||
],
|
||||
"Logs": [
|
||||
"Dnevnici"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Doniraj"
|
||||
@@ -1688,6 +1694,9 @@
|
||||
"Max Total Size": [
|
||||
"Maksimalna ukupna veličina"
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Nedostajući ključevi"
|
||||
],
|
||||
@@ -1952,6 +1961,9 @@
|
||||
"Open...": [
|
||||
"Otvori …"
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operacija je prekinuta"
|
||||
],
|
||||
@@ -3167,6 +3179,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Prilikom stvaranja novog zadatka:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Broj riječi"
|
||||
],
|
||||
|
@@ -209,6 +209,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Egy frissítés elérhető, le szeretné tölteni most?"
|
||||
],
|
||||
@@ -1159,6 +1162,9 @@
|
||||
"Link létrehozása...",
|
||||
"Linkek létrehozása..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Szerezze be most:"
|
||||
],
|
||||
@@ -1478,8 +1484,8 @@
|
||||
"Logout": [
|
||||
"Kilépés"
|
||||
],
|
||||
"Logs": [
|
||||
"Log-ok"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Adakozzon"
|
||||
@@ -1526,6 +1532,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Hiányzó kulcsok"
|
||||
],
|
||||
@@ -1775,6 +1784,9 @@
|
||||
"Open...": [
|
||||
"Megnyitás..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Művelet megszakítva"
|
||||
],
|
||||
@@ -2897,6 +2909,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Amikor egy új to-do-t hoz létre:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Szavak"
|
||||
],
|
||||
|
@@ -218,6 +218,9 @@
|
||||
"An autosaved drawing was found. Attach a copy of it to the note?": [
|
||||
""
|
||||
],
|
||||
"An error occurred: %s": [
|
||||
""
|
||||
],
|
||||
"An update is available, do you want to download it now?": [
|
||||
"Pembaruan tersedia, apakah Anda ingin mengunduhnya sekarang?"
|
||||
],
|
||||
@@ -1178,6 +1181,9 @@
|
||||
"Generating link...": [
|
||||
"Menghasilkan tautan..."
|
||||
],
|
||||
"Geolocation, spellcheck, editor toolbar, image resize": [
|
||||
""
|
||||
],
|
||||
"Get it now:": [
|
||||
"Dapatkan sekarang:"
|
||||
],
|
||||
@@ -1511,8 +1517,8 @@
|
||||
"Logout": [
|
||||
"Keluar"
|
||||
],
|
||||
"Logs": [
|
||||
"Log"
|
||||
"Logs, profiles, sync status": [
|
||||
""
|
||||
],
|
||||
"Make a donation": [
|
||||
"Beri donasi"
|
||||
@@ -1562,6 +1568,9 @@
|
||||
"Max Item Size": [
|
||||
""
|
||||
],
|
||||
"Media player, math, diagrams, table of contents": [
|
||||
""
|
||||
],
|
||||
"Missing keys": [
|
||||
"Kunci yang Hilang"
|
||||
],
|
||||
@@ -1811,6 +1820,9 @@
|
||||
"Open...": [
|
||||
"Buka..."
|
||||
],
|
||||
"Opening section %s": [
|
||||
""
|
||||
],
|
||||
"Operation cancelled": [
|
||||
"Operasi dibatalkan"
|
||||
],
|
||||
@@ -2966,6 +2978,9 @@
|
||||
"When creating a new to-do:": [
|
||||
"Ketika membuat tugas baru:"
|
||||
],
|
||||
"Window unresponsive.": [
|
||||
""
|
||||
],
|
||||
"Words": [
|
||||
"Kata"
|
||||
],
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user