1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-02-04 07:53:44 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
Laurent Cozic
7db7dc4957 multiput 2021-06-18 16:47:57 +01:00
Laurent Cozic
1aa96af4db fix tests 2021-06-18 16:13:22 +01:00
Laurent Cozic
92dadd7509 tests 2021-06-18 15:39:24 +01:00
Laurent Cozic
000185bfb4 multiput 2021-06-18 15:19:51 +01:00
Laurent Cozic
e81427a1f2 Merge branch 'dev' into sync_batch_upload 2021-06-18 11:50:41 +01:00
Laurent Cozic
958e9163b6 All: Batch upload during initial sync 2021-06-17 12:45:34 +01:00
38 changed files with 293 additions and 519 deletions

View File

@@ -701,9 +701,6 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map
packages/app-mobile/components/screens/Note.d.ts
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Note.js.map

3
.gitignore vendored
View File

@@ -687,9 +687,6 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
packages/app-mobile/components/SelectDateTimeDialog.d.ts
packages/app-mobile/components/SelectDateTimeDialog.js
packages/app-mobile/components/SelectDateTimeDialog.js.map
packages/app-mobile/components/screens/ConfigScreen.d.ts
packages/app-mobile/components/screens/ConfigScreen.js
packages/app-mobile/components/screens/ConfigScreen.js.map
packages/app-mobile/components/screens/Note.d.ts
packages/app-mobile/components/screens/Note.js
packages/app-mobile/components/screens/Note.js.map

View File

@@ -33,7 +33,6 @@ class Command extends BaseCommand {
return [
['--target <target>', _('Sync to provided target (defaults to sync.target config value)')],
['--upgrade', _('Upgrade the sync target to the latest version.')],
['--use-lock <value>', 'Disable local locks that prevent multiple clients from synchronizing at the same time (Default = 1)'],
];
}
@@ -125,21 +124,17 @@ class Command extends BaseCommand {
const lockFilePath = `${require('os').tmpdir()}/synclock_${md5(escape(Setting.value('profileDir')))}`; // https://github.com/pvorb/node-md5/issues/41
if (!(await fs.pathExists(lockFilePath))) await fs.writeFile(lockFilePath, 'synclock');
const useLock = args.options.useLock !== 0;
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
if (useLock) {
try {
if (await Command.isLocked(lockFilePath)) throw new Error(_('Synchronisation is already in progress.'));
this.releaseLockFn_ = await Command.lockFile(lockFilePath);
} catch (error) {
if (error.code == 'ELOCKED') {
const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file);
this.stdout(msg);
return;
}
throw error;
this.releaseLockFn_ = await Command.lockFile(lockFilePath);
} catch (error) {
if (error.code == 'ELOCKED') {
const msg = _('Lock file is already being hold. If you know that no synchronisation is taking place, you may delete the lock file at "%s" and resume the operation.', error.file);
this.stdout(msg);
return;
}
throw error;
}
const cleanUp = () => {

View File

@@ -8,46 +8,25 @@ joplin.plugins.register({
iconName: 'fas fa-music',
});
await joplin.settings.registerSettings({
'myCustomSetting': {
value: 123,
type: SettingItemType.Int,
section: 'myCustomSection',
public: true,
label: 'My Custom Setting',
},
await joplin.settings.registerSetting('myCustomSetting', {
value: 123,
type: SettingItemType.Int,
section: 'myCustomSection',
public: true,
label: 'My Custom Setting',
});
'multiOptionTest': {
value: 'en',
type: SettingItemType.String,
section: 'myCustomSection',
isEnum: true,
public: true,
label: 'Multi-options test',
options: {
'en': 'English',
'fr': 'French',
'es': 'Spanish',
},
},
'mySecureSetting': {
value: 'hunter2',
type: SettingItemType.String,
section: 'myCustomSection',
public: true,
secure: true,
label: 'My Secure Setting',
},
'myFileSetting': {
value: 'abcd',
type: SettingItemType.String,
section: 'myCustomSection',
public: true,
label: 'My file setting',
description: 'This setting will be saved to settings.json',
['storage' as any]: 2, // Should be `storage: SettingStorage.File`
await joplin.settings.registerSetting('multiOptionTest', {
value: 'en',
type: SettingItemType.String,
section: 'myCustomSection',
isEnum: true,
public: true,
label: 'Multi-options test',
options: {
'en': 'English',
'fr': 'French',
'es': 'Spanish',
},
});
@@ -68,11 +47,7 @@ joplin.plugins.register({
iconName: 'fas fa-drum',
execute: async () => {
const value = await joplin.settings.value('myCustomSetting');
console.info('Current value is: ' + value);
const secureValue = await joplin.settings.value('mySecureSetting');
console.info('Secure value is: ' + secureValue);
const fileValue = await joplin.settings.value('myFileSetting');
console.info('Setting in file is: ' + fileValue);
alert('Current value is: ' + value);
},
});

View File

@@ -5,9 +5,6 @@ set -e
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../../../../.."
API_BASE_URL="https://test.joplincloud.com"
# API_BASE_URL="http://api.joplincloud.local:22300"
COMMANDS=($(echo $1 | tr "," "\n"))
PROFILE_DIR=~/.config/joplindev-testperf
@@ -19,7 +16,7 @@ for CMD in "${COMMANDS[@]}"
do
if [[ $CMD == "createUsers" ]]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' $API_BASE_URL/api/debug
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
# elif [[ $CMD == "createData" ]]; then
@@ -35,7 +32,7 @@ do
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 9" >> "$CMD_FILE"
echo "config sync.9.path $API_BASE_URL" >> "$CMD_FILE"
echo "config sync.9.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.9.password 123456" >> "$CMD_FILE"
@@ -55,4 +52,5 @@ cd "$ROOT_DIR/packages/app-cli"
npm start -- --profile "$PROFILE_DIR" batch "$CMD_FILE"
npm start -- --profile "$PROFILE_DIR" import ~/Desktop/Joplin_17_06_2021.jex
# npm start -- --profile "$PROFILE_DIR" import ~/Desktop/Tout_18_06_2021.jex
npm start -- --profile "$PROFILE_DIR" sync --use-lock 1
npm start -- --profile "$PROFILE_DIR" sync

View File

@@ -4,7 +4,7 @@ import ButtonBar from './ButtonBar';
import Button, { ButtonLevel } from '../Button/Button';
import { _ } from '@joplin/lib/locale';
import bridge from '../../services/bridge';
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
import Setting, { SyncStartupOperation } from '@joplin/lib/models/Setting';
import control_PluginsStates from './controls/plugins/PluginsStates';
const { connect } = require('react-redux');
@@ -360,7 +360,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, AppType.Desktop);
const descriptionText = Setting.keyDescription(key, 'desktop');
const descriptionComp = this.renderDescription(this.props.themeId, descriptionText);
if (settingKeyToControl[key]) {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.3",
"version": "2.0.11",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.1.3",
"version": "2.1.0",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -4,5 +4,5 @@
# It could be used to develop plugins too.
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/settings"
PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/external_assets"
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"

View File

@@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097636
versionName "2.1.1"
versionCode 2097635
versionName "2.1.0"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View File

@@ -1,4 +1,5 @@
const React = require('react');
const { StyleSheet } = require('react-native');
const { themeStyle } = require('./global-style.js');

View File

@@ -1,29 +1,28 @@
import Slider from '@react-native-community/slider';
const React = require('react');
const { Platform, Linking, View, Switch, StyleSheet, ScrollView, Text, Button, TouchableOpacity, TextInput, Alert, PermissionsAndroid } = require('react-native');
import Setting, { AppType } from '@joplin/lib/models/Setting';
import NavService from '@joplin/lib/services/NavService';
import ReportService from '@joplin/lib/services/ReportService';
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
import checkPermissions from '../../utils/checkPermissions';
import time from '@joplin/lib/time';
import shim from '@joplin/lib/shim';
import setIgnoreTlsErrors from '../../utils/TlsUtils';
import { reg } from '@joplin/lib/registry';
import { State } from '@joplin/lib/reducer';
const VersionInfo = require('react-native-version-info');
const { Platform, TouchableOpacity, Linking, View, Switch, StyleSheet, Text, Button, ScrollView, TextInput, Alert, PermissionsAndroid } = require('react-native');
const { connect } = require('react-redux');
const { ScreenHeader } = require('../screen-header.js');
const { _ } = require('@joplin/lib/locale');
const { BaseScreenComponent } = require('../base-screen.js');
const { Dropdown } = require('../Dropdown.js');
const { themeStyle } = require('../global-style.js');
const Setting = require('@joplin/lib/models/Setting').default;
const shared = require('@joplin/lib/components/shared/config-shared.js');
const SyncTargetRegistry = require('@joplin/lib/SyncTargetRegistry');
const { reg } = require('@joplin/lib/registry.js');
const NavService = require('@joplin/lib/services/NavService').default;
const VersionInfo = require('react-native-version-info').default;
const ReportService = require('@joplin/lib/services/ReportService').default;
const time = require('@joplin/lib/time').default;
const shim = require('@joplin/lib/shim').default;
const SearchEngine = require('@joplin/lib/services/searchengine/SearchEngine').default;
const RNFS = require('react-native-fs');
const checkPermissions = require('../../utils/checkPermissions.js').default;
import setIgnoreTlsErrors from '../../utils/TlsUtils';
class ConfigScreenComponent extends BaseScreenComponent {
static navigationOptions(): any {
static navigationOptions() {
return { header: null };
}
@@ -50,7 +49,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.e2eeConfig_ = () => {
void NavService.go('EncryptionConfig');
NavService.go('EncryptionConfig');
};
this.saveButton_press = async () => {
@@ -70,7 +69,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.syncStatusButtonPress_ = () => {
void NavService.go('Status');
NavService.go('Status');
};
this.exportDebugButtonPress_ = async () => {
@@ -121,7 +120,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
throw new Error('Permission denied');
}
const copyFiles = async (source: string, dest: string) => {
const copyFiles = async (source, dest) => {
await shim.fsDriver().mkdir(dest);
const files = await shim.fsDriver().readDirStats(source);
@@ -149,7 +148,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
};
this.logButtonPress_ = () => {
void NavService.go('Log');
NavService.go('Log');
};
}
@@ -176,7 +175,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
if (this.styles_[themeId]) return this.styles_[themeId];
this.styles_ = {};
const styles: any = {
const styles = {
body: {
flex: 1,
justifyContent: 'flex-start',
@@ -264,7 +263,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
return this.styles_[themeId];
}
renderHeader(key: string, title: string) {
renderHeader(key, title) {
const theme = themeStyle(this.props.themeId);
return (
<View key={key} style={this.styles().headerWrapperStyle}>
@@ -273,7 +272,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
renderButton(key: string, title: string, clickHandler: Function, options: any = null) {
renderButton(key, title, clickHandler, options = null) {
if (!options) options = {};
let descriptionComp = null;
@@ -298,7 +297,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
sectionToComponent(key: string, section: any, settings: any) {
sectionToComponent(key, section, settings) {
const settingComps = [];
for (let i = 0; i < section.metadatas.length; i++) {
@@ -342,32 +341,12 @@ class ConfigScreenComponent extends BaseScreenComponent {
);
}
private renderToggle(key: string, label: string, value: any, updateSettingValue: Function, descriptionComp: any = null) {
const theme = themeStyle(this.props.themeId);
return (
<View key={key}>
<View style={this.containerStyle(false)}>
<Text key="label" style={this.styles().switchSettingText}>
{label}
</Text>
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value: any) => updateSettingValue(key, value)} />
</View>
{descriptionComp}
</View>
);
}
private containerStyle(hasDescription: boolean): any {
return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
}
settingToComponent(key: string, value: any) {
settingToComponent(key, value) {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
const output: any = null;
const output = null;
const updateSettingValue = (key: string, value: any) => {
const updateSettingValue = (key, value) => {
return shared.updateSettingValue(this, key, value);
};
@@ -375,7 +354,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const settingDescription = md.description ? md.description() : '';
const descriptionComp = !settingDescription ? null : <Text style={this.styles().settingDescriptionText}>{settingDescription}</Text>;
const containerStyle = this.containerStyle(!!settingDescription);
const containerStyle = !settingDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder;
if (md.isEnum) {
value = value.toString();
@@ -409,7 +388,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
color: theme.color,
fontSize: theme.fontSize,
}}
onValueChange={(itemValue: any) => {
onValueChange={(itemValue) => {
updateSettingValue(key, itemValue);
}}
/>
@@ -418,18 +397,17 @@ class ConfigScreenComponent extends BaseScreenComponent {
</View>
);
} else if (md.type == Setting.TYPE_BOOL) {
return this.renderToggle(key, md.label(), value, updateSettingValue, descriptionComp);
// return (
// <View key={key}>
// <View style={containerStyle}>
// <Text key="label" style={this.styles().switchSettingText}>
// {md.label()}
// </Text>
// <Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={(value:any) => updateSettingValue(key, value)} />
// </View>
// {descriptionComp}
// </View>
// );
return (
<View key={key}>
<View style={containerStyle}>
<Text key="label" style={this.styles().switchSettingText}>
{md.label()}
</Text>
<Switch key="control" style={this.styles().switchSettingControl} trackColor={{ false: theme.dividerColor }} value={value} onValueChange={value => updateSettingValue(key, value)} />
</View>
{descriptionComp}
</View>
);
} else if (md.type == Setting.TYPE_INT) {
const unitLabel = md.unitLabel ? md.unitLabel(value) : value;
// Note: Do NOT add the minimumTrackTintColor and maximumTrackTintColor props
@@ -453,7 +431,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
<Text key="label" style={this.styles().settingText}>
{md.label()}
</Text>
<TextInput autoCorrect={false} autoCompleteType="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={(value: any) => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
<TextInput autoCorrect={false} autoCompleteType="off" selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} autoCapitalize="none" key="control" style={this.styles().settingControl} value={value} onChangeText={value => updateSettingValue(key, value)} secureTextEntry={!!md.secure} />
</View>
);
} else {
@@ -463,19 +441,6 @@ class ConfigScreenComponent extends BaseScreenComponent {
return output;
}
private renderFeatureFlags(settings: any): any[] {
const updateSettingValue = (key: string, value: any) => {
console.info('UPDATE', key, value);
return shared.updateSettingValue(this, key, value);
};
const output: any[] = [];
for (const key of Setting.featureFlagKeys(AppType.Mobile)) {
output.push(this.renderToggle(key, key, settings[key], updateSettingValue));
}
return output;
}
render() {
const settings = this.state.settings;
@@ -499,7 +464,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
const profileExportPrompt = (
<View style={this.styles().settingContainer} key="profileExport">
<Text style={this.styles().settingText}>Path:</Text>
<TextInput style={{ ...this.styles().textInput, paddingRight: 20 }} onChange={(event: any) => this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard" keyboardAppearance={theme.keyboardAppearance}></TextInput>
<TextInput style={{ ...this.styles().textInput, paddingRight: 20 }} onChange={(event) => this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard" keyboardAppearance={theme.keyboardAppearance}></TextInput>
<Button title="OK" onPress={this.exportProfileButtonPress2_}></Button>
</View>
);
@@ -508,9 +473,6 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
}
settingComps.push(this.renderHeader('featureFlags', _('Feature flags')));
settingComps.push(<View key="featureFlagsContainer">{this.renderFeatureFlags(settings)}</View>);
settingComps.push(this.renderHeader('moreInfo', _('More information')));
if (Platform.OS === 'android' && Platform.Version >= 23) {
@@ -606,11 +568,11 @@ class ConfigScreenComponent extends BaseScreenComponent {
}
}
const ConfigScreen = connect((state: State) => {
const ConfigScreen = connect(state => {
return {
settings: state.settings,
themeId: state.settings.theme,
};
})(ConfigScreenComponent);
export default ConfigScreen;
module.exports = { ConfigScreen };

View File

@@ -1,5 +1,5 @@
module.exports = {
hash:"6608023b8053b48e0eec248644475e33", files: {
hash:"45572502e8b0a8e9b85192de7291684c", 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

View File

@@ -57,7 +57,7 @@ import JoplinDatabase from '@joplin/lib/JoplinDatabase';
import Database from '@joplin/lib/database';
const { NotesScreen } = require('./components/screens/notes.js');
const { TagsScreen } = require('./components/screens/tags.js');
import ConfigScreen from './components/screens/ConfigScreen';
const { ConfigScreen } = require('./components/screens/config.js');
const { FolderScreen } = require('./components/screens/folder.js');
const { LogScreen } = require('./components/screens/log.js');
const { StatusScreen } = require('./components/screens/status.js');

View File

@@ -20,7 +20,6 @@ import JoplinError from './JoplinError';
import ShareService from './services/share/ShareService';
import TaskQueue from './TaskQueue';
import ItemUploader from './services/synchronizer/ItemUploader';
import { FileApi } from './file-api';
const { sprintf } = require('sprintf-js');
const { Dirnames } = require('./services/synchronizer/utils/types');
@@ -28,18 +27,6 @@ interface RemoteItem {
id: string;
path?: string;
type_?: number;
isDeleted?: boolean;
// This the time when the file was created on the server. It is used for
// example for the locking mechanim or any file that's not an actual Joplin
// item.
updated_time?: number;
// This is the time that corresponds to the actual Joplin item updated_time
// value. A note is always uploaded with a delay so the server updated_time
// value will always be ahead. However for synchronising we need to know the
// exact Joplin item updated_time value.
jop_updated_time?: number;
}
function isCannotSyncError(error: any): boolean {
@@ -63,7 +50,7 @@ export default class Synchronizer {
public static verboseMode: boolean = true;
private db_: any;
private api_: FileApi;
private api_: any;
private appType_: string;
private logger_: Logger = new Logger();
private state_: string = 'idle';
@@ -87,7 +74,7 @@ export default class Synchronizer {
public dispatch: Function;
public constructor(db: any, api: FileApi, appType: string) {
public constructor(db: any, api: any, appType: string) {
this.db_ = db;
this.api_ = api;
this.appType_ = appType;
@@ -320,7 +307,7 @@ export default class Synchronizer {
if (this.syncTargetIsLocked_) throw new JoplinError('Sync target is locked - aborting API call', 'lockError');
try {
const output = await (this.api() as any)[fnName](...args);
const output = await this.api()[fnName](...args);
return output;
} catch (error) {
const lockStatus = await this.lockErrorStatus_();
@@ -378,7 +365,7 @@ export default class Synchronizer {
this.dispatch({ type: 'SYNC_STARTED' });
eventManager.emit('syncStart');
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... supportsAccurateTimestamp = ${this.api().supportsAccurateTimestamp}; supportsMultiPut = ${this.api().supportsMultiPut} [${synchronizationId}]`);
this.logSyncOperation('starting', null, null, `Starting synchronisation to target ${syncTargetId}... [${synchronizationId}]`);
const handleCannotSyncItem = async (ItemClass: any, syncTargetId: any, item: any, cannotSyncReason: string, itemLocation: any = null) => {
await ItemClass.saveSyncDisabled(syncTargetId, item, cannotSyncReason, itemLocation);
@@ -782,27 +769,16 @@ export default class Synchronizer {
logger: this.logger(),
});
const remotes: RemoteItem[] = listResult.items;
const remotes = listResult.items;
this.logSyncOperation('fetchingTotal', null, null, 'Fetching delta items from sync target', remotes.length);
const remoteIds = remotes.map(r => BaseItem.pathToId(r.path));
const locals = await BaseItem.loadItemsByIds(remoteIds);
for (const remote of remotes) {
if (this.cancelling()) break;
let needsToDownload = true;
if (this.api().supportsAccurateTimestamp) {
const local = locals.find(l => l.id === BaseItem.pathToId(remote.path));
if (local && local.updated_time === remote.jop_updated_time) needsToDownload = false;
}
if (needsToDownload) {
this.downloadQueue_.push(remote.path, async () => {
return this.apiCall('get', remote.path);
});
}
this.downloadQueue_.push(remote.path, async () => {
return this.apiCall('get', remote.path);
});
}
for (let i = 0; i < remotes.length; i++) {
@@ -824,10 +800,9 @@ export default class Synchronizer {
};
const path = remote.path;
const remoteId = BaseItem.pathToId(path);
let action = null;
let reason = '';
let local = locals.find(l => l.id === remoteId);
let local = await BaseItem.loadItemByPath(path);
let ItemClass = null;
let content = null;
@@ -846,14 +821,10 @@ export default class Synchronizer {
action = 'deleteLocal';
reason = 'remote has been deleted';
} else {
if (this.api().supportsAccurateTimestamp && remote.jop_updated_time === local.updated_time) {
// Nothing to do, and no need to fetch the content
} else {
content = await loadContent();
if (content && content.updated_time > local.updated_time) {
action = 'updateLocal';
reason = 'remote is more recent than local';
}
content = await loadContent();
if (content && content.updated_time > local.updated_time) {
action = 'updateLocal';
reason = 'remote is more recent than local';
}
}
}

View File

@@ -1,7 +1,6 @@
import { MultiPutItem } from './file-api';
import JoplinError from './JoplinError';
import JoplinServerApi from './JoplinServerApi';
import Setting from './models/Setting';
import { trimSlashes } from './path-utils';
// All input paths should be in the format: "path/to/file". This is converted to
@@ -34,11 +33,7 @@ export default class FileApiDriverJoplinServer {
}
public get supportsMultiPut() {
return Setting.value('featureFlag.syncMultiPut');
}
public get supportsAccurateTimestamp() {
return Setting.value('featureFlag.syncAccurateTimestamps');
return true;
}
public requestRepeatCount() {
@@ -49,8 +44,7 @@ export default class FileApiDriverJoplinServer {
const output = {
path: rootPath ? path.substr(rootPath.length + 1) : path,
updated_time: md.updated_time,
jop_updated_time: md.jop_updated_time,
isDir: false,
isDir: false, // !!md.is_directory,
isDeleted: isDeleted,
};

View File

@@ -24,10 +24,6 @@ export default class FileApiDriverMemory {
return true;
}
public get supportsAccurateTimestamp() {
return true;
}
decodeContent_(content: any) {
return Buffer.from(content, 'base64').toString('utf-8');
}

View File

@@ -86,26 +86,10 @@ class FileApi {
if (this.driver_.initialize) return this.driver_.initialize(this.fullPath(''));
}
// This can be true if the driver implements uploading items in batch. Will
// probably only be supported by Joplin Server.
public get supportsMultiPut(): boolean {
return !!this.driver().supportsMultiPut;
}
// This can be true when the sync target timestamps (updated_time) provided
// in the delta call are guaranteed to be accurate. That requires
// explicitely setting the timestamp, which is not done anymore on any sync
// target as it wasn't accurate (for example, the file system can't be
// relied on, and even OneDrive for some reason doesn't guarantee that the
// timestamp you set is what you get back).
//
// The only reliable one at the moment is Joplin Server since it reads the
// updated_time property directly from the item (it unserializes it
// server-side).
public get supportsAccurateTimestamp(): boolean {
return !!this.driver().supportsAccurateTimestamp;
}
async fetchRemoteDateOffset_() {
const tempFile = `${this.tempDirName()}/timeCheck${Math.round(Math.random() * 1000000)}.txt`;
const startTime = Date.now();

View File

@@ -42,7 +42,7 @@ export interface SettingItem {
label?(): string;
description?: Function;
options?(): any;
appTypes?: AppType[];
appTypes?: string[];
show?(settings: any): boolean;
filter?(value: any): any;
secure?: boolean;
@@ -93,18 +93,12 @@ export enum Env {
Prod = 'prod',
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
Cli = 'cli',
}
export interface Constants {
env: Env;
isDemo: boolean;
appName: string;
appId: string;
appType: AppType;
appType: string;
resourceDirName: string;
resourceDir: string;
profileDir: string;
@@ -186,7 +180,7 @@ class Setting extends BaseModel {
isDemo: false,
appName: 'joplin',
appId: 'SET_ME', // Each app should set this identifier
appType: 'SET_ME' as any, // 'cli' or 'mobile'
appType: 'SET_ME', // 'cli' or 'mobile'
resourceDirName: '',
resourceDir: '',
profileDir: '',
@@ -296,7 +290,7 @@ class Setting extends BaseModel {
value: true,
type: SettingItemType.Bool,
public: false,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
storage: SettingStorage.File,
},
'sync.target': {
@@ -306,7 +300,7 @@ class Setting extends BaseModel {
public: true,
section: 'sync',
label: () => _('Synchronisation target'),
description: (appType: AppType) => {
description: (appType: string) => {
return appType !== 'cli' ? null : _('The target to synchronise to. Each sync target may have additional parameters which are named as `sync.NUM.NAME` (all documented below).');
},
options: () => {
@@ -559,7 +553,7 @@ class Setting extends BaseModel {
public: true,
advanced: true,
isEnum: true,
appTypes: [AppType.Mobile, AppType.Desktop],
appTypes: ['mobile', 'desktop'],
label: () => _('Attachment download behaviour'),
description: () => _('In "Manual" mode, attachments are downloaded only when you click on them. In "Auto", they are downloaded when you open the note. In "Always", all the attachments are downloaded whether you open the note or not.'),
options: () => {
@@ -651,7 +645,7 @@ class Setting extends BaseModel {
value: Setting.THEME_LIGHT,
type: SettingItemType.Int,
public: true,
appTypes: [AppType.Mobile, AppType.Desktop],
appTypes: ['mobile', 'desktop'],
show: (settings) => {
return !settings['themeAutoDetect'];
},
@@ -666,7 +660,7 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
section: 'appearance',
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
public: true,
label: () => _('Automatically switch theme to match system theme'),
storage: SettingStorage.File,
@@ -679,7 +673,7 @@ class Setting extends BaseModel {
show: (settings) => {
return settings['themeAutoDetect'];
},
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
isEnum: true,
label: () => _('Preferred light theme'),
section: 'appearance',
@@ -694,7 +688,7 @@ class Setting extends BaseModel {
show: (settings) => {
return settings['themeAutoDetect'];
},
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
isEnum: true,
label: () => _('Preferred dark theme'),
section: 'appearance',
@@ -708,13 +702,13 @@ class Setting extends BaseModel {
public: false,
},
showNoteCounts: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false, advanced: true, appTypes: [AppType.Desktop], label: () => _('Show note counts') },
showNoteCounts: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false, advanced: true, appTypes: ['desktop'], label: () => _('Show note counts') },
layoutButtonSequence: {
value: Setting.LAYOUT_ALL,
type: SettingItemType.Int,
public: false,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
isEnum: true,
options: () => ({
[Setting.LAYOUT_ALL]: _('%s / %s / %s', _('Editor'), _('Viewer'), _('Split View')),
@@ -724,15 +718,15 @@ class Setting extends BaseModel {
}),
storage: SettingStorage.File,
},
uncompletedTodosOnTop: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: [AppType.Cli], label: () => _('Uncompleted to-dos on top') },
showCompletedTodos: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: [AppType.Cli], label: () => _('Show completed to-dos') },
uncompletedTodosOnTop: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: ['cli'], label: () => _('Uncompleted to-dos on top') },
showCompletedTodos: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, appTypes: ['cli'], label: () => _('Show completed to-dos') },
'notes.sortOrder.field': {
value: 'user_updated_time',
type: SettingItemType.String,
section: 'note',
isEnum: true,
public: true,
appTypes: [AppType.Cli],
appTypes: ['cli'],
label: () => _('Sort notes by'),
options: () => {
const Note = require('./Note').default;
@@ -750,17 +744,17 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
public: true,
section: 'note',
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Auto-pair braces, parenthesis, quotations, etc.'),
storage: SettingStorage.File,
},
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
'notes.sortOrder.reverse': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'note', public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
'folders.sortOrder.field': {
value: 'title',
type: SettingItemType.String,
isEnum: true,
public: true,
appTypes: [AppType.Cli],
appTypes: ['cli'],
label: () => _('Sort notebooks by'),
options: () => {
const Folder = require('./Folder').default;
@@ -773,7 +767,7 @@ class Setting extends BaseModel {
},
storage: SettingStorage.File,
},
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: true, label: () => _('Reverse sort order'), appTypes: [AppType.Cli] },
'folders.sortOrder.reverse': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: true, label: () => _('Reverse sort order'), appTypes: ['cli'] },
trackLocation: { value: true, type: SettingItemType.Bool, section: 'note', storage: SettingStorage.File, public: true, label: () => _('Save geo-location with notes') },
// 2020-10-29: For now disable the beta editor due to
@@ -786,7 +780,7 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
section: 'note',
public: false, // mobilePlatform === 'ios',
appTypes: [AppType.Mobile],
appTypes: ['mobile'],
label: () => 'Opt-in to the editor beta',
description: () => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.',
},
@@ -797,7 +791,7 @@ class Setting extends BaseModel {
section: 'note',
isEnum: true,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('When creating a new to-do:'),
options: () => {
return {
@@ -813,7 +807,7 @@ class Setting extends BaseModel {
section: 'note',
isEnum: true,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('When creating a new note:'),
options: () => {
return {
@@ -829,7 +823,7 @@ class Setting extends BaseModel {
type: SettingItemType.Object,
section: 'plugins',
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
needRestart: true,
autoSave: true,
},
@@ -840,38 +834,38 @@ class Setting extends BaseModel {
section: 'plugins',
public: true,
advanced: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => 'Development plugins',
description: () => 'You may add multiple plugin paths, each separated by a comma. You will need to restart the application for the changes to take effect.',
storage: SettingStorage.File,
},
// Deprecated - use markdown.plugin.*
'markdown.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: [AppType.Mobile, AppType.Desktop] },
'markdown.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: [AppType.Mobile, AppType.Desktop] },
'markdown.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
'markdown.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, public: false, appTypes: ['mobile', 'desktop'] },
// Deprecated
'markdown.plugin.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.linkify': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Linkify')}${wysiwygYes}` },
'markdown.plugin.softbreaks': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable soft breaks')}${wysiwygYes}` },
'markdown.plugin.typographer': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable typographer support')}${wysiwygYes}` },
'markdown.plugin.linkify': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Linkify')}${wysiwygYes}` },
'markdown.plugin.katex': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },
'markdown.plugin.katex': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable math expressions')}${wysiwygYes}` },
'markdown.plugin.fountain': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Fountain syntax support')}${wysiwygYes}` },
'markdown.plugin.mermaid': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable Mermaid diagrams support')}${wysiwygYes}` },
'markdown.plugin.audioPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable audio player')}${wysiwygNo}` },
'markdown.plugin.videoPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable video player')}${wysiwygNo}` },
'markdown.plugin.pdfViewer': { storage: SettingStorage.File, value: !mobilePlatform, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Desktop], label: () => `${_('Enable PDF viewer')}${wysiwygNo}` },
'markdown.plugin.mark': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ==mark== syntax')}${wysiwygYes}` },
'markdown.plugin.footnote': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable footnotes')}${wysiwygNo}` },
'markdown.plugin.toc': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable table of contents extension')}${wysiwygNo}` },
'markdown.plugin.sub': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ~sub~ syntax')}${wysiwygYes}` },
'markdown.plugin.sup': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ^sup^ syntax')}${wysiwygYes}` },
'markdown.plugin.deflist': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable deflist syntax')}${wysiwygNo}` },
'markdown.plugin.abbr': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable abbreviation syntax')}${wysiwygNo}` },
'markdown.plugin.emoji': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable markdown emoji')}${wysiwygNo}` },
'markdown.plugin.insert': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable ++insert++ syntax')}${wysiwygYes}` },
'markdown.plugin.multitable': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: [AppType.Mobile, AppType.Desktop], label: () => `${_('Enable multimarkdown table extension')}${wysiwygNo}` },
'markdown.plugin.audioPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable audio player')}${wysiwygNo}` },
'markdown.plugin.videoPlayer': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable video player')}${wysiwygNo}` },
'markdown.plugin.pdfViewer': { storage: SettingStorage.File, value: !mobilePlatform, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['desktop'], label: () => `${_('Enable PDF viewer')}${wysiwygNo}` },
'markdown.plugin.mark': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ==mark== syntax')}${wysiwygYes}` },
'markdown.plugin.footnote': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable footnotes')}${wysiwygNo}` },
'markdown.plugin.toc': { storage: SettingStorage.File, value: true, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable table of contents extension')}${wysiwygNo}` },
'markdown.plugin.sub': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ~sub~ syntax')}${wysiwygYes}` },
'markdown.plugin.sup': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ^sup^ syntax')}${wysiwygYes}` },
'markdown.plugin.deflist': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable deflist syntax')}${wysiwygNo}` },
'markdown.plugin.abbr': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable abbreviation syntax')}${wysiwygNo}` },
'markdown.plugin.emoji': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable markdown emoji')}${wysiwygNo}` },
'markdown.plugin.insert': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable ++insert++ syntax')}${wysiwygYes}` },
'markdown.plugin.multitable': { storage: SettingStorage.File, value: false, type: SettingItemType.Bool, section: 'markdownPlugins', public: true, appTypes: ['mobile', 'desktop'], label: () => `${_('Enable multimarkdown table extension')}${wysiwygNo}` },
// Tray icon (called AppIndicator) doesn't work in Ubuntu
// http://www.webupd8.org/2017/04/fix-appindicator-not-working-for.html
@@ -882,7 +876,7 @@ class Setting extends BaseModel {
type: SettingItemType.Bool,
section: 'application',
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Show tray icon'),
description: () => {
return platform === 'linux' ? _('Note: Does not work in all desktop environments.') : _('This will allow Joplin to run in the background. It is recommended to enable this setting so that your notes are constantly being synchronised, thus reducing the number of conflicts.');
@@ -890,7 +884,7 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
},
startMinimized: { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: true, appTypes: [AppType.Desktop], label: () => _('Start application minimised in the tray icon') },
startMinimized: { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Start application minimised in the tray icon') },
collapsedFolderIds: { value: [], type: SettingItemType.Array, public: false },
@@ -913,9 +907,9 @@ class Setting extends BaseModel {
},
// Deprecated in favour of windowContentZoomFactor
'style.zoom': { value: 100, type: SettingItemType.Int, public: false, storage: SettingStorage.File, appTypes: [AppType.Desktop], section: 'appearance', label: () => '', minimum: 50, maximum: 500, step: 10 },
'style.zoom': { value: 100, type: SettingItemType.Int, public: false, storage: SettingStorage.File, appTypes: ['desktop'], section: 'appearance', label: () => '', minimum: 50, maximum: 500, step: 10 },
'style.editor.fontSize': { value: 13, type: SettingItemType.Int, public: true, storage: SettingStorage.File, appTypes: [AppType.Desktop], section: 'appearance', label: () => _('Editor font size'), minimum: 4, maximum: 50, step: 1 },
'style.editor.fontSize': { value: 13, type: SettingItemType.Int, public: true, storage: SettingStorage.File, appTypes: ['desktop'], section: 'appearance', label: () => _('Editor font size'), minimum: 4, maximum: 50, step: 1 },
'style.editor.fontFamily':
(mobilePlatform) ?
({
@@ -924,7 +918,7 @@ class Setting extends BaseModel {
isEnum: true,
public: true,
label: () => _('Editor font'),
appTypes: [AppType.Mobile],
appTypes: ['mobile'],
section: 'appearance',
options: () => {
// IMPORTANT: The font mapping must match the one in global-styles.js::editorFont()
@@ -946,7 +940,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
section: 'appearance',
label: () => _('Editor font family'),
description: () =>
@@ -957,7 +951,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
section: 'appearance',
label: () => _('Editor monospace font family'),
description: () =>
@@ -965,7 +959,7 @@ class Setting extends BaseModel {
storage: SettingStorage.File,
},
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
'ui.layout': { value: {}, type: SettingItemType.Object, storage: SettingStorage.File, public: false, appTypes: ['desktop'] },
// TODO: Is there a better way to do this? The goal here is to simply have
// a way to display a link to the customizable stylesheets, not for it to
@@ -984,7 +978,7 @@ class Setting extends BaseModel {
},
type: SettingItemType.Button,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Custom stylesheet for rendered Markdown'),
section: 'appearance',
advanced: true,
@@ -1001,7 +995,7 @@ class Setting extends BaseModel {
},
type: SettingItemType.Button,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Custom stylesheet for Joplin-wide app styles'),
section: 'appearance',
advanced: true,
@@ -1012,7 +1006,7 @@ class Setting extends BaseModel {
value: null,
type: SettingItemType.Button,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Re-upload local data to sync target'),
section: 'sync',
advanced: true,
@@ -1023,7 +1017,7 @@ class Setting extends BaseModel {
value: null,
type: SettingItemType.Button,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => _('Delete local data and re-download from sync target'),
section: 'sync',
advanced: true,
@@ -1031,8 +1025,8 @@ class Setting extends BaseModel {
},
autoUpdateEnabled: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: platform !== 'linux', appTypes: [AppType.Desktop], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', storage: SettingStorage.File, public: true, appTypes: [AppType.Desktop], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
autoUpdateEnabled: { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, section: 'application', public: platform !== 'linux', appTypes: ['desktop'], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', storage: SettingStorage.File, public: true, appTypes: ['desktop'], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
'clipperServer.autoStart': { value: false, type: SettingItemType.Bool, storage: SettingStorage.File, public: false },
'sync.interval': {
value: 300,
@@ -1061,13 +1055,13 @@ class Setting extends BaseModel {
public: true,
label: () => _('Synchronise only over WiFi connection'),
storage: SettingStorage.File,
appTypes: [AppType.Mobile],
appTypes: ['mobile'],
},
noteVisiblePanes: { value: ['editor', 'viewer'], type: SettingItemType.Array, storage: SettingStorage.File, public: false, appTypes: [AppType.Desktop] },
tagHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: [AppType.Desktop] },
folderHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: [AppType.Desktop] },
editor: { value: '', type: SettingItemType.String, subType: 'file_path_and_args', storage: SettingStorage.File, public: true, appTypes: [AppType.Cli, AppType.Desktop], label: () => _('Text editor command'), description: () => _('The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.') },
'export.pdfPageSize': { value: 'A4', type: SettingItemType.String, advanced: true, storage: SettingStorage.File, isEnum: true, public: true, appTypes: [AppType.Desktop], label: () => _('Page size for PDF export'), options: () => {
noteVisiblePanes: { value: ['editor', 'viewer'], type: SettingItemType.Array, storage: SettingStorage.File, public: false, appTypes: ['desktop'] },
tagHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: ['desktop'] },
folderHeaderIsExpanded: { value: true, type: SettingItemType.Bool, public: false, appTypes: ['desktop'] },
editor: { value: '', type: SettingItemType.String, subType: 'file_path_and_args', storage: SettingStorage.File, public: true, appTypes: ['cli', 'desktop'], label: () => _('Text editor command'), description: () => _('The editor command (may include arguments) that will be used to open a note. If none is provided it will try to auto-detect the default editor.') },
'export.pdfPageSize': { value: 'A4', type: SettingItemType.String, advanced: true, storage: SettingStorage.File, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('Page size for PDF export'), options: () => {
return {
'A4': _('A4'),
'Letter': _('Letter'),
@@ -1077,7 +1071,7 @@ class Setting extends BaseModel {
'Legal': _('Legal'),
};
} },
'export.pdfPageOrientation': { value: 'portrait', type: SettingItemType.String, storage: SettingStorage.File, advanced: true, isEnum: true, public: true, appTypes: [AppType.Desktop], label: () => _('Page orientation for PDF export'), options: () => {
'export.pdfPageOrientation': { value: 'portrait', type: SettingItemType.String, storage: SettingStorage.File, advanced: true, isEnum: true, public: true, appTypes: ['desktop'], label: () => _('Page orientation for PDF export'), options: () => {
return {
'portrait': _('Portrait'),
'landscape': _('Landscape'),
@@ -1088,7 +1082,7 @@ class Setting extends BaseModel {
value: '',
type: SettingItemType.String,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
isEnum: true,
advanced: true,
label: () => _('Keyboard Mode'),
@@ -1106,7 +1100,7 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
public: true,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
label: () => 'Enable spell checking in Markdown editor? (WARNING BETA feature)',
description: () => 'Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)',
},
@@ -1120,7 +1114,7 @@ class Setting extends BaseModel {
return [SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav'), SyncTargetRegistry.nameToId('joplinServer')].indexOf(settings['sync.target']) >= 0;
},
public: true,
appTypes: [AppType.Desktop, AppType.Cli],
appTypes: ['desktop', 'cli'],
label: () => _('Custom TLS certificates'),
description: () => _('Comma-separated list of paths to directories to load the certificates from, or path to individual cert files. For example: /my/cert_dir, /other/custom.pem. Note that if you make changes to the TLS settings, you must save your changes before clicking on "Check synchronisation configuration".'),
storage: SettingStorage.File,
@@ -1135,6 +1129,7 @@ class Setting extends BaseModel {
[SyncTargetRegistry.nameToId('nextcloud'), SyncTargetRegistry.nameToId('webdav'), SyncTargetRegistry.nameToId('joplinServer')].indexOf(settings['sync.target']) >= 0;
},
public: true,
appTypes: ['desktop', 'cli', 'mobile'],
label: () => _('Ignore TLS certificate errors'),
storage: SettingStorage.File,
},
@@ -1151,7 +1146,7 @@ class Setting extends BaseModel {
},
'api.token': { value: null, type: SettingItemType.String, public: false, storage: SettingStorage.File },
'api.port': { value: null, type: SettingItemType.Int, storage: SettingStorage.File, public: true, appTypes: [AppType.Cli], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
'api.port': { value: null, type: SettingItemType.Int, storage: SettingStorage.File, public: true, appTypes: ['cli'], description: () => _('Specify the port that should be used by the API server. If not set, a default will be used.') },
'resourceService.lastProcessedChangeId': { value: 0, type: SettingItemType.Int, public: false },
'searchEngine.lastProcessedChangeId': { value: 0, type: SettingItemType.Int, public: false },
@@ -1180,8 +1175,8 @@ class Setting extends BaseModel {
'welcome.wasBuilt': { value: false, type: SettingItemType.Bool, public: false },
'welcome.enabled': { value: true, type: SettingItemType.Bool, public: false },
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: [AppType.Mobile] },
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: [AppType.Mobile] },
'camera.type': { value: 0, type: SettingItemType.Int, public: false, appTypes: ['mobile'] },
'camera.ratio': { value: '4:3', type: SettingItemType.String, public: false, appTypes: ['mobile'] },
'spellChecker.enabled': { value: true, type: SettingItemType.Bool, storage: SettingStorage.File, public: false },
'spellChecker.language': { value: '', type: SettingItemType.String, storage: SettingStorage.File, public: false },
@@ -1190,7 +1185,7 @@ class Setting extends BaseModel {
value: 100,
type: SettingItemType.Int,
public: false,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
minimum: 30,
maximum: 300,
step: 10,
@@ -1202,7 +1197,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: [AppType.Cli],
appTypes: ['cli'],
label: () => _('Notebook list growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@@ -1216,7 +1211,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: [AppType.Cli],
appTypes: ['cli'],
label: () => _('Note list growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@@ -1230,7 +1225,7 @@ class Setting extends BaseModel {
type: SettingItemType.Int,
section: 'appearance',
public: true,
appTypes: [AppType.Cli],
appTypes: ['cli'],
label: () => _('Note area growth factor'),
description: () =>
_('The factor property sets how the item will grow or shrink ' +
@@ -1244,24 +1239,9 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
public: false,
appTypes: [AppType.Desktop],
appTypes: ['desktop'],
storage: SettingStorage.Database,
},
'featureFlag.syncAccurateTimestamps': {
value: false,
type: SettingItemType.Bool,
public: false,
storage: SettingStorage.File,
},
'featureFlag.syncMultiPut': {
value: false,
type: SettingItemType.Bool,
public: false,
storage: SettingStorage.File,
},
};
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
@@ -1269,11 +1249,6 @@ class Setting extends BaseModel {
return this.metadata_;
}
public static featureFlagKeys(appType: AppType): string[] {
const keys = this.keys(false, appType);
return keys.filter(k => k.indexOf('featureFlag.') === 0);
}
private static validateKey(key: string) {
if (!key) throw new Error('Cannot register empty key');
if (key.length > 128) throw new Error(`Key length cannot be longer than 128 characters: ${key}`);
@@ -1329,7 +1304,7 @@ class Setting extends BaseModel {
return key in this.metadata();
}
static keyDescription(key: string, appType: AppType = null) {
static keyDescription(key: string, appType: string = null) {
const md = this.settingMetadata(key);
if (!md.description) return null;
return md.description(appType);
@@ -1339,7 +1314,7 @@ class Setting extends BaseModel {
return this.metadata()[key] && this.metadata()[key].secure === true;
}
static keys(publicOnly: boolean = false, appType: AppType = null, options: KeysOptions = null) {
static keys(publicOnly: boolean = false, appType: string = null, options: KeysOptions = null) {
options = Object.assign({}, {
secureOnly: false,
}, options);
@@ -1373,18 +1348,10 @@ class Setting extends BaseModel {
}
// Low-level method to load a setting directly from the database. Should not be used in most cases.
public static async loadOne(key: string): Promise<CacheItem> {
public static async loadOne(key: string) {
if (this.keyStorage(key) === SettingStorage.File) {
const fromFile = await this.fileHandler.load();
return {
key,
value: fromFile[key],
};
} else if (this.settingMetadata(key).secure) {
return {
key,
value: await this.keychainService().password(`setting.${key}`),
};
return fromFile[key];
} else {
return this.modelSelectOne('SELECT * FROM settings WHERE key = ?', [key]);
}
@@ -1837,7 +1804,7 @@ class Setting extends BaseModel {
this.saveTimeoutId_ = null;
}
static publicSettings(appType: AppType) {
static publicSettings(appType: string) {
if (!appType) throw new Error('appType is required');
const metadata = this.metadata();

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/lib",
"version": "2.1.0",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -3,7 +3,7 @@ export default class NavService {
public static dispatch: Function = () => {};
private static handlers_: Function[] = [];
public static async go(routeName: string) {
static async go(routeName: string) {
if (this.handlers_.length) {
const r = await this.handlers_[this.handlers_.length - 1]();
if (r) return r;
@@ -15,7 +15,7 @@ export default class NavService {
});
}
public static addHandler(handler: Function) {
static addHandler(handler: Function) {
for (let i = this.handlers_.length - 1; i >= 0; i--) {
const h = this.handlers_[i];
if (h === handler) return;
@@ -24,7 +24,7 @@ export default class NavService {
this.handlers_.push(handler);
}
public static removeHandler(hanlder: Function) {
static removeHandler(hanlder: Function) {
for (let i = this.handlers_.length - 1; i >= 0; i--) {
const h = this.handlers_[i];
if (h === hanlder) this.handlers_.splice(i, 1);

View File

@@ -65,7 +65,6 @@ export default class JoplinSettings {
if ('minimum' in setting) internalSettingItem.minimum = setting.minimum;
if ('maximum' in setting) internalSettingItem.maximum = setting.maximum;
if ('step' in setting) internalSettingItem.step = setting.step;
if ('storage' in setting) internalSettingItem.storage = setting.storage;
await Setting.registerSetting(this.namespacedKey(key), internalSettingItem);
}

View File

@@ -334,17 +334,6 @@ export enum SettingItemType {
Button = 6,
}
export enum AppType {
Desktop = 'desktop',
Mobile = 'mobile',
Cli = 'cli',
}
export enum SettingStorage {
Database = 1,
File = 2,
}
// Redefine a simplified interface to mask internal details
// and to remove function calls as they would have to be async.
export interface SettingItem {
@@ -383,7 +372,7 @@ export interface SettingItem {
/**
* Reserved property. Not used at the moment.
*/
appTypes?: AppType[];
appTypes?: string[];
/**
* Set this to `true` to store secure data, such as passwords. Any such
@@ -404,11 +393,6 @@ export interface SettingItem {
minimum?: number;
maximum?: number;
step?: number;
/**
* Either store the setting in the database or in settings.json. Defaults to database.
*/
storage?: SettingStorage;
}
export interface SettingSection {

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/renderer",
"version": "2.1.0",
"version": "2.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4757,9 +4757,9 @@
}
},
"khroma": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/khroma/-/khroma-1.4.1.tgz",
"integrity": "sha512-+GmxKvmiRuCcUYDgR7g5Ngo0JEDeOsGdNONdU2zsiBQaK4z19Y2NvXqfEDE0ZiIrg45GTZyAnPLVsLZZACYm3Q=="
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/khroma/-/khroma-1.1.0.tgz",
"integrity": "sha512-aTO+YX22tYOLEQJYFiatAj1lc5QZ+H5sHWFRBWNCiKwc5NWNUJZyeSeiHEPeURJ2a1GEVYcmyMUwGjjLe5ec5A=="
},
"kind-of": {
"version": "6.0.3",
@@ -4998,9 +4998,9 @@
"dev": true
},
"mermaid": {
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.10.2.tgz",
"integrity": "sha512-Za5MrbAOMbEsyY4ONgGjfYz06sbwF1iNGRzp1sQqpOtvXxjxGu/J1jRJ8QyE9kD/D9zj1/KlRrYegWEvA7eZ5Q==",
"version": "8.8.4",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.8.4.tgz",
"integrity": "sha512-YPn35uEAIrOcsDPjCiKNXXBdO1Aoazsv2zTZjG4+oXa7+tTVUb5sI81NqaTYa47RnoH9Vl4waLlEEJfB8KM9VA==",
"requires": {
"@braintree/sanitize-url": "^3.1.0",
"d3": "^5.7.0",
@@ -6539,9 +6539,9 @@
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
},
"uglify-js": {
"version": "3.13.9",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz",
"integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g=="
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz",
"integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ=="
},
"union-value": {
"version": "1.0.1",

View File

@@ -45,7 +45,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-toc-done-right": "^4.1.0",
"md5": "^2.2.1",
"mermaid": "^8.10.2",
"mermaid": "^8.8.4",
"uslug": "git+https://github.com/laurent22/uslug.git#emoji-support"
},
"gitHead": "80c0089d2c52aff608b2bea74389de5a7f12f2e2"

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.1.3",
"version": "2.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.1.3",
"version": "2.1.1",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",

Binary file not shown.

View File

@@ -356,7 +356,6 @@ export interface Item extends WithDates, WithUuid {
jop_share_id?: Uuid;
jop_type?: number;
jop_encryption_applied?: number;
jop_updated_time?: number;
}
export interface UserItem extends WithDates {
@@ -504,7 +503,6 @@ export const databaseSchema: DatabaseTables = {
jop_share_id: { type: 'string' },
jop_type: { type: 'number' },
jop_encryption_applied: { type: 'number' },
jop_updated_time: { type: 'number' },
},
user_items: {
id: { type: 'number' },

View File

@@ -1,29 +0,0 @@
import { Knex } from 'knex';
import { DbConnection } from '../db';
export async function up(db: DbConnection): Promise<any> {
await db.schema.alterTable('items', function(table: Knex.CreateTableBuilder) {
table.bigInteger('jop_updated_time').defaultTo(0).notNullable();
});
while (true) {
const items = await db('items')
.select('id', 'content')
.where('jop_type', '>', 0)
.andWhere('jop_updated_time', '=', 0)
.limit(1000);
if (!items.length) break;
await db.transaction(async trx => {
for (const item of items) {
const unserialized = JSON.parse(item.content);
await trx('items').update({ jop_updated_time: unserialized.updated_time }).where('id', '=', item.id);
}
});
}
}
export async function down(_db: DbConnection): Promise<any> {
}

View File

@@ -5,12 +5,14 @@ import { ErrorResyncRequired } from '../utils/errors';
import BaseModel, { SaveOptions } from './BaseModel';
import { PaginatedResults, Pagination, PaginationOrderDir } from './utils/pagination';
export interface DeltaChange extends Change {
jop_updated_time?: number;
export interface ChangeWithItem {
item: Item;
updated_time: number;
type: ChangeType;
}
export interface PaginatedChanges extends PaginatedResults {
items: DeltaChange[];
items: Change[];
}
export interface ChangePagination {
@@ -156,20 +158,9 @@ export default class ChangeModel extends BaseModel<Change> {
.orderBy('counter', 'asc')
.limit(pagination.limit) as any[];
const changes: Change[] = await query;
const changes = await query;
const items: Item[] = await this.db('items').select('id', 'jop_updated_time').whereIn('items.id', changes.map(c => c.item_id));
let finalChanges: DeltaChange[] = this.compressChanges(changes);
finalChanges = await this.removeDeletedItems(finalChanges, items);
finalChanges = finalChanges.map(c => {
const item = items.find(item => item.id === c.item_id);
if (!item) return c;
return {
...c,
jop_updated_time: item.jop_updated_time,
};
});
const finalChanges = await this.removeDeletedItems(this.compressChanges(changes));
return {
items: finalChanges,
@@ -180,14 +171,14 @@ export default class ChangeModel extends BaseModel<Change> {
};
}
private async removeDeletedItems(changes: Change[], items: Item[] = null): Promise<Change[]> {
private async removeDeletedItems(changes: Change[]): Promise<Change[]> {
const itemIds = changes.map(c => c.item_id);
// We skip permission check here because, when an item is shared, we need
// to fetch files that don't belong to the current user. This check
// would not be needed anyway because the change items are generated in
// a context where permissions have already been checked.
items = items === null ? await this.db('items').select('id').whereIn('items.id', itemIds) : items;
const items: Item[] = await this.db('items').select('id').whereIn('items.id', itemIds);
const output: Change[] = [];

View File

@@ -285,7 +285,6 @@ export default class ItemModel extends BaseModel<Item> {
item.share_id = itemRow.jop_share_id;
item.type_ = itemRow.jop_type;
item.encryption_applied = itemRow.jop_encryption_applied;
item.updated_time = itemRow.jop_updated_time;
return item;
}
@@ -337,7 +336,6 @@ export default class ItemModel extends BaseModel<Item> {
item.jop_type = joplinItem.type_;
item.jop_encryption_applied = joplinItem.encryption_applied || 0;
item.jop_share_id = joplinItem.share_id || '';
item.jop_updated_time = joplinItem.updated_time;
const joplinItemToSave = { ...joplinItem };
@@ -346,7 +344,6 @@ export default class ItemModel extends BaseModel<Item> {
delete joplinItemToSave.share_id;
delete joplinItemToSave.type_;
delete joplinItemToSave.encryption_applied;
delete joplinItemToSave.updated_time;
item.content = Buffer.from(JSON.stringify(joplinItemToSave));
} else {

View File

@@ -1,10 +1,9 @@
import { execCommand2, rootDir } from './tool-utils';
function getVersionFromTag(tagName: string, isPreRelease: boolean): string {
function getVersionFromTag(tagName: string): string {
if (tagName.indexOf('server-') !== 0) throw new Error(`Invalid tag: ${tagName}`);
const s = tagName.split('-');
const suffix = isPreRelease ? '-beta' : '';
return s[1].substr(1) + suffix;
return s[1].substr(1);
}
function getIsPreRelease(tagName: string): boolean {
@@ -16,8 +15,8 @@ async function main() {
if (!argv.tagName) throw new Error('--tag-name not provided');
const tagName = argv.tagName;
const imageVersion = getVersionFromTag(tagName);
const isPreRelease = getIsPreRelease(tagName);
const imageVersion = getVersionFromTag(tagName, isPreRelease);
process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);
@@ -27,12 +26,10 @@ async function main() {
console.info('isPreRelease:', isPreRelease);
await execCommand2(`docker build -t "joplin/server:${imageVersion}" -f Dockerfile.server .`);
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2(`docker push joplin/server:${imageVersion}`);
if (!isPreRelease) {
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2('docker push joplin/server:latest');
}
if (!isPreRelease) await execCommand2('docker push joplin/server:latest');
}
main().catch((error) => {

View File

@@ -14,17 +14,15 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Francisco Mora <francisco.m.collao@gmail.com>\n"
"Last-Translator: Mario Campo <mario.campo@gmail.com>\n"
"Language-Team: Spanish <lucas.vieites@gmail.com>\n"
"Language: es_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0\n"
"X-Generator: Poedit 2.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-SourceCharset: UTF-8\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-desktop/bridge.js:106 packages/app-desktop/bridge.js:110
#: packages/app-desktop/bridge.js:126 packages/app-desktop/bridge.js:134
@@ -133,11 +131,11 @@ msgstr "Descargar"
#: packages/app-desktop/checkForUpdates.js:189
msgid "Skip this version"
msgstr "Omitir esta versión"
msgstr ""
#: packages/app-desktop/checkForUpdates.js:189
msgid "Full changelog"
msgstr "Registro de cambios completo"
msgstr ""
#: packages/app-desktop/gui/NoteRevisionViewer.min.js:75
#: packages/lib/services/RevisionService.js:242
@@ -282,12 +280,14 @@ msgid "Retry"
msgstr "Reintentar"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:137
#, fuzzy
msgid "Advanced tools"
msgstr "Herramientas avanzadas"
msgstr "Opciones avanzadas"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:139
#, fuzzy
msgid "Export debug report"
msgstr "Exportar Informe de depuración"
msgstr "Exportar Informe de Depuración"
#: packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/CodeMirror.js:183
msgid "strong text"
@@ -345,23 +345,23 @@ msgstr "Casillas"
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:17
msgid "Highlight"
msgstr "Destacar"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:22
msgid "Strikethrough"
msgstr "Tachado"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:27
msgid "Insert"
msgstr "Insertar"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:33
msgid "Superscript"
msgstr "Superindice"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/utils/setupToolbarButtons.js:39
msgid "Subscript"
msgstr "Subíndice"
msgstr ""
#: packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.js:564
#: packages/app-mobile/components/screens/Note.js:1016
@@ -527,8 +527,9 @@ msgid "Delete line"
msgstr "Borrar línea"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:92
#, fuzzy
msgid "Duplicate line"
msgstr "Duplicar linea"
msgstr "Duplicar"
#: packages/app-desktop/gui/NoteEditor/commands/editorCommandDeclarations.js:96
msgid "Undo"
@@ -721,12 +722,10 @@ msgid ""
"Safe mode is currently active. Note rendering and all plugins are "
"temporarily disabled."
msgstr ""
"El modo seguro está activo actualmente. La representación de notas y todos "
"los complementos están temporalmente deshabilitados."
#: packages/app-desktop/gui/MainScreen/MainScreen.js:447
msgid "Disable safe mode and restart"
msgstr "Desactivar el modo seguro y reiniciar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:451
msgid ""
@@ -772,15 +771,15 @@ msgstr "Más información"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:477
#, javascript-format
msgid "%s (%s) would like to share a notebook with you."
msgstr "%s (%s) le gustaría compartir una libreta contigo."
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:479
msgid "Accept"
msgstr "Aceptar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:481
msgid "Reject"
msgstr "Rechazar"
msgstr ""
#: packages/app-desktop/gui/MainScreen/MainScreen.js:485
msgid "Some items cannot be synchronised."
@@ -866,8 +865,9 @@ msgid "Toggle editors"
msgstr "Alternar editores"
#: packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js:16
#, fuzzy
msgid "Share notebook..."
msgstr "Compartir libreta..."
msgstr "Compartir nota..."
#: packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js:16
msgid "Change application layout"
@@ -947,7 +947,7 @@ msgstr "¡El token ha sido copiado al portapapeles!"
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:44
msgid "Are you sure you want to renew the authorisation token?"
msgstr "¿Está seguro de que desea renovar el token de autorización?"
msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:84
msgid "The web clipper service is enabled and set to auto-start."
@@ -1031,7 +1031,7 @@ msgstr ""
#: packages/app-desktop/gui/ClipperConfigScreen.min.js:222
msgid "Renew token"
msgstr "Renovar token"
msgstr ""
#: packages/app-desktop/gui/MenuBar.js:167
#, javascript-format
@@ -1124,8 +1124,9 @@ msgid "&Go"
msgstr "&Ir"
#: packages/app-desktop/gui/MenuBar.js:631
#, fuzzy
msgid "Note&book"
msgstr "Libreta"
msgstr "Libretas"
#: packages/app-desktop/gui/MenuBar.js:637
msgid "&Note"
@@ -1523,12 +1524,13 @@ msgid "You do not have any installed plugin."
msgstr "No tiene ningún plugin instalado."
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:232
#, fuzzy
msgid "Could not connect to plugin repository"
msgstr "No se pudo conectar con el repositorio del plugin: %s"
msgstr "No se pudo instalar el plugin: %s"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:234
msgid "Try again"
msgstr "Intenta nuevamente"
msgstr ""
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:242
msgid "Plugin tools"
@@ -1716,8 +1718,9 @@ msgstr ""
"(límite: %s)."
#: packages/app-desktop/gui/ShareNoteDialog.js:141
#, fuzzy
msgid "Unshare note"
msgstr "Dejar de compartir nota"
msgstr "Compartir"
#: packages/app-desktop/gui/ShareNoteDialog.js:168
msgid "Synchronising..."
@@ -1752,20 +1755,19 @@ msgstr[0] "Copiar Enlace Compartible"
msgstr[1] "Copiar Enlaces Compartible"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:138
#, fuzzy
msgid "Unshare"
msgstr "Dejar de compartir"
msgstr "Compartir"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:180
msgid ""
"Delete this invitation? The recipient will no longer have access to this "
"shared notebook."
msgstr ""
"¿Eliminar esta invitación? El destinatario ya no tendrá acceso a esta "
"libreta compartida."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:194
msgid "Add recipient:"
msgstr "Agregar destinatario:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:197
#: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
@@ -1775,43 +1777,45 @@ msgstr "Compartir"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:206
msgid "Recipient has not yet accepted the invitation"
msgstr "El destinatario aún no ha aceptado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:207
msgid "Recipient has rejected the invitation"
msgstr "El destinatario ha rechazado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:208
msgid "Recipient has accepted the invitation"
msgstr "El destinatario ha aceptado la invitación"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:218
msgid "Recipients:"
msgstr "Destinatarios:"
msgstr ""
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:230
#, fuzzy
msgid "Synchronizing..."
msgstr "Sincronizando..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:231
#, fuzzy
msgid "Sharing notebook..."
msgstr "Compartir libreta..."
msgstr "Compartir nota..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:241
msgid ""
"Unshare this notebook? The recipients will no longer have access to its "
"content."
msgstr ""
"¿Dejar de compartir esta libreta? Los destinatarios ya no tendrán acceso a "
"su contenido."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:251
#, fuzzy
msgid "Share Notebook"
msgstr "Compartir Libreta"
msgstr "Compartir Notas"
#: packages/app-desktop/commands/toggleSafeMode.js:18
#, fuzzy
msgid "Toggle safe mode"
msgstr "Alternar modo seguro"
msgstr "Alternar la barra lateral"
#: packages/app-desktop/commands/toggleExternalEditing.js:18
msgid "Toggle external editing"
@@ -1903,7 +1907,7 @@ msgstr "Configuración"
#: packages/app-mobile/components/side-menu-content.js:351
msgid "Mobile data - auto-sync disabled"
msgstr "Datos móviles: sincronización automática deshabilitada"
msgstr ""
#: packages/app-mobile/components/note-list.js:97
msgid "You currently have no notebooks."
@@ -2253,15 +2257,15 @@ msgstr ""
"cambiada! [Cambiarla ahora](%s)"
#: packages/server/dist/models/UserModel.js:134
#, fuzzy
msgid "attachment"
msgstr "adjunto"
msgstr "Adjuntos"
#: packages/server/dist/models/UserModel.js:134
#, javascript-format
msgid ""
"Cannot save %s \"%s\" because it is larger than than the allowed limit (%s)"
msgstr ""
"No se puede guardar %s \"%s\" porque es mayor que el límite permitido (%s)"
#: packages/lib/onedrive-api-node-utils.js:46
#, javascript-format
@@ -2452,20 +2456,23 @@ msgid "Joplin Server URL"
msgstr "URL del Servidor de Joplin"
#: packages/lib/models/Setting.js:327
#, fuzzy
msgid "Joplin Server email"
msgstr "Email del servidor Joplin"
msgstr "Servidor de Joplin"
#: packages/lib/models/Setting.js:338
msgid "Joplin Server password"
msgstr "Contraseña del Servidor de Joplin"
#: packages/lib/models/Setting.js:365
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Email del servidor Joplin"
msgstr "Servidor de Joplin"
#: packages/lib/models/Setting.js:376
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Contraseña del servidor Joplin"
msgstr "Contraseña del Servidor de Joplin"
#: packages/lib/models/Setting.js:388
msgid "Attachment download behaviour"
@@ -2707,12 +2714,11 @@ msgid ""
"Used for most text in the markdown editor. If not found, a generic "
"proportional (variable width) font is used."
msgstr ""
"Se utiliza para la mayor parte del texto en el editor markdown. Si no se "
"encuentra, se utiliza una fuente genérica proporcional (ancho variable)."
#: packages/lib/models/Setting.js:758
#, fuzzy
msgid "Editor monospace font family"
msgstr "Familia de fuente monoespaciada del editor"
msgstr "Familia de fuente del editor"
#: packages/lib/models/Setting.js:759
msgid ""
@@ -2720,10 +2726,6 @@ msgid ""
"tables, checkboxes, code). If not found, a generic monospace (fixed width) "
"font is used."
msgstr ""
"Se utiliza cuando se necesita una fuente de ancho fijo para presentar el "
"texto de manera legible (por ejemplo, tablas, casillas de verificación, "
"código). Si no se encuentra, se utiliza una fuente genérica monoespaciada "
"(ancho fijo)."
#: packages/lib/models/Setting.js:780
msgid "Custom stylesheet for rendered Markdown"
@@ -2735,13 +2737,11 @@ msgstr "Hoja de estilos para personalizar todo Joplin"
#: packages/lib/models/Setting.js:806
msgid "Re-upload local data to sync target"
msgstr "Vuelva a cargar datos locales para sincronizar el destino"
msgstr ""
#: packages/lib/models/Setting.js:816
msgid "Delete local data and re-download from sync target"
msgstr ""
"Elimine los datos locales y vuelva a descargarlos desde el destino de "
"sincronización"
#: packages/lib/models/Setting.js:821
msgid "Automatically update the application"
@@ -2778,7 +2778,7 @@ msgstr "%d horas"
#: packages/lib/models/Setting.js:849
msgid "Synchronise only over WiFi connection"
msgstr "Sincronizar solo a través de una conexión WiFi"
msgstr ""
#: packages/lib/models/Setting.js:856
msgid "Text editor command"
@@ -3064,8 +3064,9 @@ msgid "Encrypted items cannot be modified"
msgstr "Los elementos cifrados no pueden ser modificados"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Nube de Joplin"
msgstr "Foro de Joplin"
#: packages/lib/BaseApplication.js:152 packages/lib/BaseApplication.js:164
#: packages/lib/BaseApplication.js:196
@@ -3223,15 +3224,16 @@ msgstr ""
"Joplin a la última versión"
#: packages/lib/JoplinServerApi.js:80
#, javascript-format
#, fuzzy, javascript-format
msgid ""
"Could not connect to Joplin Server. Please check the Synchronisation options "
"in the config screen. Full error was:\n"
"\n"
"%s"
msgstr ""
"No se pudo conectar al servidor Joplin. Compruebe las opciones de "
"sincronización en la pantalla de configuración. El error completo fue:\n"
"No se pudo conectar con la aplicación de Joplin de Nextcloud. Verifique la "
"configuración en la pantalla de configuración de Sincronización. El error "
"completo fue:\n"
"\n"
"%s"
@@ -3906,8 +3908,6 @@ msgid ""
"Runs the commands contained in the text file. There should be one command "
"per line."
msgstr ""
"Ejecuta los comandos contenidos en el archivo de texto. Debe haber un "
"comando por línea."
#: packages/app-cli/app/command-version.js:11
msgid "Displays version information"

View File

@@ -6,15 +6,13 @@ msgid ""
msgstr ""
"Project-Id-Version: Joplin-CLI 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: 南宫小骏 <jackytsu@vip.qq.com>\n"
"Language-Team: zh_CN <jackytsu.vip.qq.com>\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.0\n"
"X-Generator: Poedit 2.4.3\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: packages/app-desktop/bridge.js:106 packages/app-desktop/bridge.js:110
@@ -1089,8 +1087,9 @@ msgid "&Go"
msgstr "跳转 (&G)"
#: packages/app-desktop/gui/MenuBar.js:631
#, fuzzy
msgid "Note&book"
msgstr "笔记本 (&B)"
msgstr "笔记本"
#: packages/app-desktop/gui/MenuBar.js:637
msgid "&Note"
@@ -1659,8 +1658,9 @@ msgid "Warning: not all resources shown for performance reasons (limit: %s)."
msgstr "警告: 由于性能原因无法显示所有资源 (最多:%s)。"
#: packages/app-desktop/gui/ShareNoteDialog.js:141
#, fuzzy
msgid "Unshare note"
msgstr "取消分享笔记"
msgstr "取消分享"
#: packages/app-desktop/gui/ShareNoteDialog.js:168
msgid "Synchronising..."
@@ -2172,6 +2172,7 @@ msgid ""
msgstr "默认管理员密码不安全且尚未更改![现在更改](%s)"
#: packages/server/dist/models/UserModel.js:134
#, fuzzy
msgid "attachment"
msgstr "附件"
@@ -2179,7 +2180,7 @@ msgstr "附件"
#, javascript-format
msgid ""
"Cannot save %s \"%s\" because it is larger than than the allowed limit (%s)"
msgstr "无法保存 %s “%s”,因为超过了允许的限制大小(%s)。"
msgstr ""
#: packages/lib/onedrive-api-node-utils.js:46
#, javascript-format
@@ -2369,12 +2370,14 @@ msgid "Joplin Server password"
msgstr "Joplin 服务器密码"
#: packages/lib/models/Setting.js:365
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Joplin 论坛邮箱"
msgstr "Joplin Server 邮箱"
#: packages/lib/models/Setting.js:376
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Joplin 论坛密码"
msgstr "Joplin 服务器密码"
#: packages/lib/models/Setting.js:388
msgid "Attachment download behaviour"
@@ -2953,6 +2956,7 @@ msgid "Encrypted items cannot be modified"
msgstr "无法修改已加密的条目"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Joplin 论坛"

View File

@@ -1,9 +1,5 @@
# Joplin Server Changelog
## [server-v2.1.3-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.1.3-beta) (Pre-release) - 2021-06-19T14:15:06Z
- New: Add support for uploading multiple items in one request (3b9c02e)
## [server-v2.1.1](https://github.com/laurent22/joplin/releases/tag/server-v2.1.1) - 2021-06-17T17:27:29Z
- New: Added account info to dashboard and title to pages (7f0b3fd)