You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-29 23:48:19 +02:00
Compare commits
14 Commits
v2.8.2
...
multi_prof
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6cd988939 | ||
|
|
a99eec7cdf | ||
|
|
7ef09dfa77 | ||
|
|
b11573a2a7 | ||
|
|
f4034b1ff0 | ||
|
|
dfadacd7f4 | ||
|
|
ecc7b17708 | ||
|
|
ee6ab55649 | ||
|
|
b0d64e2f51 | ||
|
|
f322d40910 | ||
|
|
557cb9a6c3 | ||
|
|
d5a55c7908 | ||
|
|
7308bbd3ca | ||
|
|
c1e8f9befd |
@@ -232,9 +232,6 @@ packages/app-desktop/gui/Dialog.js.map
|
||||
packages/app-desktop/gui/DialogButtonRow.d.ts
|
||||
packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow.js.map
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.d.ts
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js.map
|
||||
packages/app-desktop/gui/DialogTitle.d.ts
|
||||
packages/app-desktop/gui/DialogTitle.js
|
||||
packages/app-desktop/gui/DialogTitle.js.map
|
||||
@@ -1063,9 +1060,6 @@ packages/lib/database.js.map
|
||||
packages/lib/debug/DebugService.d.ts
|
||||
packages/lib/debug/DebugService.js
|
||||
packages/lib/debug/DebugService.js.map
|
||||
packages/lib/dom.d.ts
|
||||
packages/lib/dom.js
|
||||
packages/lib/dom.js.map
|
||||
packages/lib/dummy.test.d.ts
|
||||
packages/lib/dummy.test.js
|
||||
packages/lib/dummy.test.js.map
|
||||
|
||||
2
.github/workflows/github-actions-main.yml
vendored
2
.github/workflows/github-actions-main.yml
vendored
@@ -6,7 +6,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
# Removed windows-2016 for now - discontinued by GitHub
|
||||
os: [macos-latest, ubuntu-latest, windows-2019]
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
steps:
|
||||
|
||||
# Silence apt-get update errors (for example when a module doesn't
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -222,9 +222,6 @@ packages/app-desktop/gui/Dialog.js.map
|
||||
packages/app-desktop/gui/DialogButtonRow.d.ts
|
||||
packages/app-desktop/gui/DialogButtonRow.js
|
||||
packages/app-desktop/gui/DialogButtonRow.js.map
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.d.ts
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js
|
||||
packages/app-desktop/gui/DialogButtonRow/useKeyboardHandler.js.map
|
||||
packages/app-desktop/gui/DialogTitle.d.ts
|
||||
packages/app-desktop/gui/DialogTitle.js
|
||||
packages/app-desktop/gui/DialogTitle.js.map
|
||||
@@ -1053,9 +1050,6 @@ packages/lib/database.js.map
|
||||
packages/lib/debug/DebugService.d.ts
|
||||
packages/lib/debug/DebugService.js
|
||||
packages/lib/debug/DebugService.js.map
|
||||
packages/lib/dom.d.ts
|
||||
packages/lib/dom.js
|
||||
packages/lib/dom.js.map
|
||||
packages/lib/dummy.test.d.ts
|
||||
packages/lib/dummy.test.js
|
||||
packages/lib/dummy.test.js.map
|
||||
|
||||
@@ -45,7 +45,7 @@ while [ "$NUM" -lt 400 ]; do
|
||||
echo "config keychain.supported 0" >> "$CMD_FILE"
|
||||
echo "config sync.target 10" >> "$CMD_FILE"
|
||||
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
|
||||
echo "config sync.10.password 111111" >> "$CMD_FILE"
|
||||
echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
|
||||
echo "sync" >> "$CMD_FILE"
|
||||
|
||||
yarn start --profile "$PROFILE_DIR" batch "$CMD_FILE"
|
||||
|
||||
@@ -33,14 +33,14 @@
|
||||
],
|
||||
"owner": "Laurent Cozic"
|
||||
},
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"bin": "./main.js",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.8",
|
||||
"@joplin/renderer": "~2.8",
|
||||
"@joplin/lib": "~2.7",
|
||||
"@joplin/renderer": "~2.7",
|
||||
"aws-sdk": "^2.588.0",
|
||||
"chalk": "^4.1.0",
|
||||
"compare-version": "^0.1.2",
|
||||
@@ -67,7 +67,7 @@
|
||||
"yargs-parser": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.8",
|
||||
"@joplin/tools": "~2.7",
|
||||
"@types/fs-extra": "^9.0.6",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
|
||||
@@ -44,7 +44,7 @@ const processUser = async (userNum: number) => {
|
||||
|
||||
try {
|
||||
const userEmail = `user${userNum}@example.com`;
|
||||
const userPassword = '111111';
|
||||
const userPassword = 'hunter1hunter2hunter3';
|
||||
const commandFile = `${tempDir}/populateDatabase-${userNum}.txt`;
|
||||
const profileDir = `${homedir()}/.config/joplindev-populate/joplindev-testing-${userNum}`;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Joplin Web Clipper [DEV]",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "Capture and save web pages and screenshots from your browser to Joplin.",
|
||||
"homepage_url": "https://joplinapp.org",
|
||||
"content_security_policy": "script-src 'self'; object-src 'self'",
|
||||
|
||||
@@ -554,17 +554,11 @@ class Application extends BaseApplication {
|
||||
// setTimeout(() => {
|
||||
// this.dispatch({
|
||||
// type: 'DIALOG_OPEN',
|
||||
// name: 'syncWizard',
|
||||
// name: 'editFolder',
|
||||
// props: { folderId: '3d90f7da26b947dc9c8c6c65e86cd231' },
|
||||
// });
|
||||
// }, 2000);
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.dispatch({
|
||||
// type: 'DIALOG_OPEN',
|
||||
// name: 'editFolder',
|
||||
// });
|
||||
// }, 3000);
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.dispatch({
|
||||
// type: 'NAV_GO',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
import { openFileWithExternalEditor } from '@joplin/lib/services/ExternalEditWatcher/utils';
|
||||
import Setting from '../../lib/models/Setting';
|
||||
import { openFileWithExternalEditor } from '../../lib/services/ExternalEditWatcher/utils';
|
||||
import bridge from '../services/bridge';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import CommandService, { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
|
||||
import { AppState } from '../app.reducer';
|
||||
import bridge from '../services/bridge';
|
||||
import { isInsideContainer } from '@joplin/lib/dom';
|
||||
|
||||
export const declaration: CommandDeclaration = {
|
||||
name: 'replaceMisspelling',
|
||||
};
|
||||
|
||||
function isInsideContainer(node: any, className: string): boolean {
|
||||
while (node) {
|
||||
if (node.classList && node.classList.contains(className)) return true;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const runtime = (): CommandRuntime => {
|
||||
return {
|
||||
execute: async (context: CommandContext, suggestion: string) => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import bridge from '../../services/bridge';
|
||||
import Setting, { AppType, SyncStartupOperation } from '@joplin/lib/models/Setting';
|
||||
import control_PluginsStates from './controls/plugins/PluginsStates';
|
||||
import EncryptionConfigScreen from '../EncryptionConfigScreen/EncryptionConfigScreen';
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
const pathUtils = require('@joplin/lib/path-utils');
|
||||
@@ -27,7 +26,7 @@ class ConfigScreenComponent extends React.Component<any, any> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
shared.init(this, reg);
|
||||
shared.init(this);
|
||||
|
||||
this.state = {
|
||||
selectedSectionName: 'general',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const DialogModalLayer = styled.div`
|
||||
@@ -32,6 +33,20 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function Dialog(props: Props) {
|
||||
const onWindowKeydown = useCallback((event: any) => {
|
||||
if (event.key === 'Escape') {
|
||||
if (props.onClose) props.onClose();
|
||||
}
|
||||
}, [props.onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onWindowKeydown);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onWindowKeydown);
|
||||
};
|
||||
}, [onWindowKeydown]);
|
||||
|
||||
return (
|
||||
<DialogModalLayer className={props.className}>
|
||||
<DialogRoot>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import useKeyboardHandler from './DialogButtonRow/useKeyboardHandler';
|
||||
const React = require('react');
|
||||
import { useMemo } from 'react';
|
||||
const { _ } = require('@joplin/lib/locale');
|
||||
const { themeStyle } = require('@joplin/lib/theme');
|
||||
|
||||
export interface ButtonSpec {
|
||||
name: string;
|
||||
@@ -38,26 +37,32 @@ export default function DialogButtonRow(props: Props) {
|
||||
};
|
||||
}, [theme.buttonStyle]);
|
||||
|
||||
const onOkButtonClick = useCallback(() => {
|
||||
if (props.onClick && !props.okButtonDisabled) props.onClick({ buttonName: 'ok' });
|
||||
}, [props.onClick, props.okButtonDisabled]);
|
||||
const okButton_click = () => {
|
||||
if (props.onClick) props.onClick({ buttonName: 'ok' });
|
||||
};
|
||||
|
||||
const onCancelButtonClick = useCallback(() => {
|
||||
if (props.onClick && !props.cancelButtonDisabled) props.onClick({ buttonName: 'cancel' });
|
||||
}, [props.onClick, props.cancelButtonDisabled]);
|
||||
const cancelButton_click = () => {
|
||||
if (props.onClick) props.onClick({ buttonName: 'cancel' });
|
||||
};
|
||||
|
||||
const onCustomButtonClick = useCallback((event: ClickEvent) => {
|
||||
const customButton_click = (event: ClickEvent) => {
|
||||
if (props.onClick) props.onClick(event);
|
||||
}, [props.onClick]);
|
||||
};
|
||||
|
||||
const onKeyDown = useKeyboardHandler({ onOkButtonClick, onCancelButtonClick });
|
||||
const onKeyDown = (event: any) => {
|
||||
if (event.keyCode === 13) {
|
||||
okButton_click();
|
||||
} else if (event.keyCode === 27) {
|
||||
cancelButton_click();
|
||||
}
|
||||
};
|
||||
|
||||
const buttonComps = [];
|
||||
|
||||
if (props.customButtons) {
|
||||
for (const b of props.customButtons) {
|
||||
buttonComps.push(
|
||||
<button key={b.name} style={buttonStyle} onClick={() => onCustomButtonClick({ buttonName: b.name })} onKeyDown={onKeyDown}>
|
||||
<button key={b.name} style={buttonStyle} onClick={() => customButton_click({ buttonName: b.name })} onKeyDown={onKeyDown}>
|
||||
{b.label}
|
||||
</button>
|
||||
);
|
||||
@@ -66,7 +71,7 @@ export default function DialogButtonRow(props: Props) {
|
||||
|
||||
if (props.okButtonShow !== false) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={onOkButtonClick} ref={props.okButtonRef} onKeyDown={onKeyDown}>
|
||||
<button disabled={props.okButtonDisabled} key="ok" style={buttonStyle} onClick={okButton_click} ref={props.okButtonRef} onKeyDown={onKeyDown}>
|
||||
{props.okButtonLabel ? props.okButtonLabel : _('OK')}
|
||||
</button>
|
||||
);
|
||||
@@ -74,7 +79,7 @@ export default function DialogButtonRow(props: Props) {
|
||||
|
||||
if (props.cancelButtonShow !== false) {
|
||||
buttonComps.push(
|
||||
<button disabled={props.cancelButtonDisabled} key="cancel" style={Object.assign({}, buttonStyle)} onClick={onCancelButtonClick}>
|
||||
<button disabled={props.cancelButtonDisabled} key="cancel" style={Object.assign({}, buttonStyle)} onClick={cancelButton_click}>
|
||||
{props.cancelButtonLabel ? props.cancelButtonLabel : _('Cancel')}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { isInsideContainer } from '@joplin/lib/dom';
|
||||
|
||||
interface Props {
|
||||
onOkButtonClick: Function;
|
||||
onCancelButtonClick: Function;
|
||||
}
|
||||
|
||||
const globalKeydownHandlers: string[] = [];
|
||||
|
||||
export default (props: Props) => {
|
||||
const [elementId] = useState(`${Math.round(Math.random() * 10000000)}`);
|
||||
const globalKeydownHandlersRef = useRef(globalKeydownHandlers);
|
||||
|
||||
useEffect(() => {
|
||||
globalKeydownHandlersRef.current.push(elementId);
|
||||
return () => {
|
||||
const idx = globalKeydownHandlersRef.current.findIndex(e => e === elementId);
|
||||
globalKeydownHandlersRef.current.splice(idx, 1);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isTopDialog = () => {
|
||||
const ln = globalKeydownHandlersRef.current.length;
|
||||
return ln && globalKeydownHandlersRef.current[ln - 1] === elementId;
|
||||
};
|
||||
|
||||
const isInSubModal = (targetElement: any) => {
|
||||
// If we are inside a sub-modal within the dialog, we shouldn't handle
|
||||
// global key events. It can be for example the emoji picker. In general
|
||||
// it's difficult to know whether an element is a modal or not, so we'll
|
||||
// have to add special cases here. Normally there shouldn't be many of
|
||||
// these.
|
||||
if (isInsideContainer(targetElement, 'emoji-picker')) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const onKeyDown = useCallback((event: any) => {
|
||||
// Early exit if it's neither ENTER nor ESCAPE, because isInSubModal
|
||||
// function can be costly.
|
||||
if (event.keyCode !== 13 && event.keyCode !== 27) return;
|
||||
|
||||
if (!isTopDialog() || isInSubModal(event.target)) return;
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
if (event.target.nodeName !== 'TEXTAREA') {
|
||||
props.onOkButtonClick();
|
||||
}
|
||||
} else if (event.keyCode === 27) {
|
||||
props.onCancelButtonClick();
|
||||
}
|
||||
}, [props.onOkButtonClick, props.onCancelButtonClick]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [onKeyDown]);
|
||||
|
||||
return onKeyDown;
|
||||
};
|
||||
@@ -137,7 +137,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
return (
|
||||
<td style={theme.textStyle}>
|
||||
<input type="password" style={passwordStyle} value={password} onChange={event => onInputPasswordChange(mk, event.target.value)} />{' '}
|
||||
<button style={theme.buttonStyle} onClick={() => onSavePasswordClick(mk, { ...props.passwords, ...inputPasswords })}>
|
||||
<button style={theme.buttonStyle} onClick={() => onSavePasswordClick(mk, props.passwords)}>
|
||||
{_('Save')}
|
||||
</button>
|
||||
</td>
|
||||
@@ -268,7 +268,7 @@ const EncryptionConfigScreen = (props: Props) => {
|
||||
const buttonTitle = CommandService.instance().label('openMasterPasswordDialog');
|
||||
|
||||
const needPasswordMessage = !needMasterPassword ? null : (
|
||||
<p className="needpassword">{_('Your password is needed to decrypt some of your data.')}<br/>{_('Please click on "%s" to proceed, or set the passwords in the "%s" list below.', buttonTitle, _('Encryption keys'))}</p>
|
||||
<p className="needpassword">{_('Your master password is needed to decrypt some of your data.')}<br/>{_('Please click on "%s" to proceed', buttonTitle)}</p>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
.manage-password-section > .status {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.manage-password-section > .needpassword {
|
||||
|
||||
@@ -37,7 +37,7 @@ import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncIn
|
||||
import { parseCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
||||
import ElectronAppWrapper from '../../ElectronAppWrapper';
|
||||
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
|
||||
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
|
||||
import { MasterKeyEntity } from '../../../lib/services/e2ee/types';
|
||||
import commands from './commands/index';
|
||||
import invitationRespond from '../../services/share/invitationRespond';
|
||||
const { connect } = require('react-redux');
|
||||
|
||||
@@ -34,12 +34,6 @@ export default function(props: Props) {
|
||||
const [updatingPassword, setUpdatingPassword] = useState(false);
|
||||
const [mode, setMode] = useState<Mode>(Mode.Set);
|
||||
|
||||
const showCurrentPassword = useMemo(() => {
|
||||
if ([MasterPasswordStatus.NotSet, MasterPasswordStatus.Invalid].includes(status)) return false;
|
||||
if (mode === Mode.Reset) return false;
|
||||
return true;
|
||||
}, [status]);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
props.dispatch({
|
||||
type: 'DIALOG_CLOSE',
|
||||
@@ -69,7 +63,7 @@ export default function(props: Props) {
|
||||
setUpdatingPassword(true);
|
||||
try {
|
||||
if (mode === Mode.Set) {
|
||||
await updateMasterPassword(showCurrentPassword ? currentPassword : null, password1);
|
||||
await updateMasterPassword(currentPassword, password1);
|
||||
} else if (mode === Mode.Reset) {
|
||||
await resetMasterPassword(EncryptionService.instance(), KvStore.instance(), ShareService.instance(), password1);
|
||||
} else {
|
||||
@@ -121,7 +115,7 @@ export default function(props: Props) {
|
||||
}, [password1, password2, updatingPassword, needToRepeatPassword]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowPasswordForm([MasterPasswordStatus.NotSet, MasterPasswordStatus.Invalid].includes(status));
|
||||
setShowPasswordForm(status === MasterPasswordStatus.NotSet);
|
||||
}, [status]);
|
||||
|
||||
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||
@@ -137,7 +131,8 @@ export default function(props: Props) {
|
||||
|
||||
function renderPasswordForm() {
|
||||
const renderCurrentPassword = () => {
|
||||
if (!showCurrentPassword) return null;
|
||||
if (status === MasterPasswordStatus.NotSet) return null;
|
||||
if (mode === Mode.Reset) return null;
|
||||
|
||||
// If the master password is in the keychain we preload it into the
|
||||
// field and allow displaying it. That way if the user has forgotten
|
||||
|
||||
@@ -20,7 +20,7 @@ import bridge from '../services/bridge';
|
||||
import checkForUpdates from '../checkForUpdates';
|
||||
const { connect } = require('react-redux');
|
||||
import { reg } from '@joplin/lib/registry';
|
||||
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||
import { ProfileConfig } from '../../lib/services/profileConfig/types';
|
||||
const packageInfo = require('../packageInfo.js');
|
||||
const { clipboard } = require('electron');
|
||||
const Menu = bridge().Menu;
|
||||
|
||||
@@ -110,7 +110,7 @@ function NoteListItem(props: NoteListItemProps, ref: any) {
|
||||
|
||||
let listItemTitleStyle = Object.assign({}, props.style.listItemTitle);
|
||||
listItemTitleStyle.paddingLeft = !item.is_todo ? hPadding : 4;
|
||||
if (item.is_shared) listItemTitleStyle.color = theme.colorWarn3;
|
||||
if (item.is_shared) listItemTitleStyle.color = theme.colorWarn2;
|
||||
if (item.is_todo && !!item.todo_completed) listItemTitleStyle = Object.assign(listItemTitleStyle, props.style.listItemTitleCompleted);
|
||||
|
||||
const displayTitle = Note.displayTitle(item);
|
||||
|
||||
@@ -15,7 +15,7 @@ import Button from './Button/Button';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from '../app.reducer';
|
||||
import { getEncryptionEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
|
||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||
import SyncTargetRegistry from '../../lib/SyncTargetRegistry';
|
||||
const { clipboard } = require('electron');
|
||||
|
||||
interface Props {
|
||||
|
||||
@@ -275,5 +275,4 @@ Component-specific classes
|
||||
|
||||
.master-password-dialog .fa-times {
|
||||
color: var(--joplin-color-error);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.8.2",
|
||||
"version": "2.7.14",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
@@ -105,7 +105,7 @@
|
||||
},
|
||||
"homepage": "https://github.com/laurent22/joplin#readme",
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.8",
|
||||
"@joplin/tools": "~2.7",
|
||||
"@testing-library/react-hooks": "^3.4.2",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.6",
|
||||
@@ -138,8 +138,8 @@
|
||||
"@electron/remote": "^2.0.1",
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"@joeattardi/emoji-button": "^4.6.0",
|
||||
"@joplin/lib": "~2.8",
|
||||
"@joplin/renderer": "~2.8",
|
||||
"@joplin/lib": "~2.7",
|
||||
"@joplin/renderer": "~2.7",
|
||||
"async-mutex": "^0.1.3",
|
||||
"codemirror": "^5.56.0",
|
||||
"color": "^3.1.2",
|
||||
|
||||
@@ -92,7 +92,7 @@ do
|
||||
echo "config sync.target 10" >> "$CMD_FILE"
|
||||
# echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
|
||||
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
|
||||
echo "config sync.10.password 111111" >> "$CMD_FILE"
|
||||
echo "config sync.10.password hunter1hunter2hunter3" >> "$CMD_FILE"
|
||||
|
||||
elif [[ $CMD == "e2ee" ]]; then
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 2097667
|
||||
versionName "2.8.0"
|
||||
versionName "2.7.2"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
||||
|
||||
this.scrollViewRef_ = React.createRef();
|
||||
|
||||
shared.init(this, reg);
|
||||
shared.init(this);
|
||||
|
||||
this.checkSyncConfig_ = async () => {
|
||||
// to ignore TLS erros we need to chage the global state of the app, if the check fails we need to restore the original state
|
||||
|
||||
@@ -498,7 +498,7 @@
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.8.0;
|
||||
MARKETING_VERSION = 12.7.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -526,7 +526,7 @@
|
||||
INFOPLIST_FILE = Joplin/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
MARKETING_VERSION = 12.8.0;
|
||||
MARKETING_VERSION = 12.7.1;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -674,7 +674,7 @@
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.8.0;
|
||||
MARKETING_VERSION = 12.7.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
@@ -705,7 +705,7 @@
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
|
||||
MARKETING_VERSION = 12.8.0;
|
||||
MARKETING_VERSION = 12.7.1;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.cozic.joplin.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -507,7 +507,7 @@ async function initialize(dispatch: Function) {
|
||||
|
||||
// Setting.setValue('sync.target', 10);
|
||||
// Setting.setValue('sync.10.username', 'user1@example.com');
|
||||
// Setting.setValue('sync.10.password', '111111');
|
||||
// Setting.setValue('sync.10.password', 'hunter1hunter2hunter3');
|
||||
}
|
||||
|
||||
if (Setting.value('db.ftsEnabled') === -1) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 1,
|
||||
"id": "<%= pluginId %>",
|
||||
"app_min_version": "2.8",
|
||||
"app_min_version": "2.7",
|
||||
"version": "1.0.0",
|
||||
"name": "<%= pluginName %>",
|
||||
"description": "<%= pluginDescription %>",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "generator-joplin",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.3",
|
||||
"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.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "Pack an HTML file and all its linked resources into a single HTML file",
|
||||
"main": "dist/index.js",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -22,7 +22,7 @@ import TaskQueue from './TaskQueue';
|
||||
import ItemUploader from './services/synchronizer/ItemUploader';
|
||||
import { FileApi, RemoteItem } from './file-api';
|
||||
import JoplinDatabase from './JoplinDatabase';
|
||||
import { fetchSyncInfo, getActiveMasterKey, localSyncInfo, mergeSyncInfos, saveLocalSyncInfo, setMasterKeyHasBeenUsed, SyncInfo, syncInfoEquals, uploadSyncInfo } from './services/synchronizer/syncInfoUtils';
|
||||
import { fetchSyncInfo, getActiveMasterKey, localSyncInfo, mergeSyncInfos, saveLocalSyncInfo, SyncInfo, syncInfoEquals, uploadSyncInfo } from './services/synchronizer/syncInfoUtils';
|
||||
import { getMasterPassword, setupAndDisableEncryption, setupAndEnableEncryption } from './services/e2ee/utils';
|
||||
import { generateKeyPair } from './services/e2ee/ppk';
|
||||
import syncDebugLog from './services/synchronizer/syncDebugLog';
|
||||
@@ -439,13 +439,10 @@ export default class Synchronizer {
|
||||
let remoteInfo = await fetchSyncInfo(this.api());
|
||||
logger.info('Sync target remote info:', remoteInfo);
|
||||
|
||||
let syncTargetIsNew = false;
|
||||
|
||||
if (!remoteInfo.version) {
|
||||
logger.info('Sync target is new - setting it up...');
|
||||
await this.migrationHandler().upgrade(Setting.value('syncVersion'));
|
||||
remoteInfo = await fetchSyncInfo(this.api());
|
||||
syncTargetIsNew = true;
|
||||
}
|
||||
|
||||
logger.info('Sync target is already setup - checking it...');
|
||||
@@ -458,16 +455,11 @@ export default class Synchronizer {
|
||||
|
||||
localInfo = await this.setPpkIfNotExist(localInfo, remoteInfo);
|
||||
|
||||
if (syncTargetIsNew && localInfo.activeMasterKeyId) {
|
||||
localInfo = setMasterKeyHasBeenUsed(localInfo, localInfo.activeMasterKeyId);
|
||||
}
|
||||
|
||||
// console.info('LOCAL', localInfo);
|
||||
// console.info('REMOTE', remoteInfo);
|
||||
|
||||
if (!syncInfoEquals(localInfo, remoteInfo)) {
|
||||
let newInfo = mergeSyncInfos(localInfo, remoteInfo);
|
||||
if (newInfo.activeMasterKeyId) newInfo = setMasterKeyHasBeenUsed(newInfo, newInfo.activeMasterKeyId);
|
||||
const newInfo = mergeSyncInfos(localInfo, remoteInfo);
|
||||
const previousE2EE = localInfo.e2ee;
|
||||
logger.info('Sync target info differs between local and remote - merging infos: ', newInfo.toObject());
|
||||
|
||||
|
||||
@@ -3,35 +3,15 @@ const SyncTargetRegistry = require('../../SyncTargetRegistry').default;
|
||||
const ObjectUtils = require('../../ObjectUtils');
|
||||
const { _ } = require('../../locale');
|
||||
const { createSelector } = require('reselect');
|
||||
const Logger = require('@joplin/lib/Logger').default;
|
||||
|
||||
const logger = Logger.create('config/lib');
|
||||
|
||||
const shared = {};
|
||||
|
||||
shared.onSettingsSaved = () => {};
|
||||
|
||||
shared.init = function(comp, reg) {
|
||||
shared.init = function(comp) {
|
||||
if (!comp.state) comp.state = {};
|
||||
comp.state.checkSyncConfigResult = null;
|
||||
comp.state.settings = {};
|
||||
comp.state.changedSettingKeys = [];
|
||||
comp.state.showAdvancedSettings = false;
|
||||
|
||||
shared.onSettingsSaved = (event) => {
|
||||
const savedSettingKeys = event.savedSettingKeys;
|
||||
|
||||
// After changing the sync settings we immediately trigger a sync
|
||||
// operation. This will ensure that the client gets the sync info as
|
||||
// early as possible, in particular the encryption state (encryption
|
||||
// keys, whether it's enabled, etc.). This should prevent situations
|
||||
// where the user tried to setup E2EE on the client even though it's
|
||||
// already been done on another client.
|
||||
if (savedSettingKeys.find(s => s.startsWith('sync.'))) {
|
||||
logger.info('Sync settings have been changed - scheduling a sync');
|
||||
void reg.scheduleSync();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
shared.advancedSettingsButton_click = (comp) => {
|
||||
@@ -99,8 +79,6 @@ shared.scheduleSaveSettings = function(comp) {
|
||||
};
|
||||
|
||||
shared.saveSettings = function(comp) {
|
||||
const savedSettingKeys = comp.state.changedSettingKeys.slice();
|
||||
|
||||
for (const key in comp.state.settings) {
|
||||
if (!comp.state.settings.hasOwnProperty(key)) continue;
|
||||
if (comp.state.changedSettingKeys.indexOf(key) < 0) continue;
|
||||
@@ -108,8 +86,6 @@ shared.saveSettings = function(comp) {
|
||||
}
|
||||
|
||||
comp.setState({ changedSettingKeys: [] });
|
||||
|
||||
shared.onSettingsSaved({ savedSettingKeys });
|
||||
};
|
||||
|
||||
shared.settingsToComponents = function(comp, device, settings) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
|
||||
export const isInsideContainer = (node: any, className: string): boolean => {
|
||||
while (node) {
|
||||
if (node.classList && node.classList.contains(className)) return true;
|
||||
node = node.parentNode;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -1516,7 +1516,7 @@ class Setting extends BaseModel {
|
||||
|
||||
this.metadata_ = Object.assign(this.metadata_, this.customMetadata_);
|
||||
|
||||
if (this.constants_.env === Env.Dev) this.validateMetadata(this.metadata_);
|
||||
if (this.value('env') === Env.Dev) this.validateMetadata(this.metadata_);
|
||||
|
||||
return this.metadata_;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/lib",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "Joplin Core library",
|
||||
"author": "Laurent Cozic",
|
||||
"homepage": "",
|
||||
@@ -33,8 +33,8 @@
|
||||
"@joplin/fork-htmlparser2": "^4.1.39",
|
||||
"@joplin/fork-sax": "^1.2.43",
|
||||
"@joplin/fork-uslug": "^1.0.4",
|
||||
"@joplin/htmlpack": "~2.8",
|
||||
"@joplin/renderer": "~2.8",
|
||||
"@joplin/htmlpack": "~2.7",
|
||||
"@joplin/renderer": "~2.7",
|
||||
"@joplin/turndown": "^4.0.61",
|
||||
"@joplin/turndown-plugin-gfm": "^1.0.43",
|
||||
"@types/nanoid": "^3.0.0",
|
||||
|
||||
@@ -254,7 +254,6 @@ export default class EncryptionService {
|
||||
model.created_time = now;
|
||||
model.updated_time = now;
|
||||
model.source_application = Setting.value('appId');
|
||||
model.hasBeenUsed = false;
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ export interface MasterKeyEntity {
|
||||
content?: string;
|
||||
type_?: number;
|
||||
enabled?: number;
|
||||
hasBeenUsed?: boolean;
|
||||
}
|
||||
|
||||
export type RSAKeyPair = any; // Depends on implementation
|
||||
|
||||
@@ -46,6 +46,7 @@ export default class InteropService_Importer_Md extends InteropService_Importer_
|
||||
}
|
||||
|
||||
async importDirectory(dirPath: string, parentFolderId: string) {
|
||||
console.info(`Import: ${dirPath}`);
|
||||
const supportedFileExtension = this.metadata().fileExtensions;
|
||||
const stats = await shim.fsDriver().readDirStats(dirPath);
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
@@ -53,6 +54,7 @@ export default class InteropService_Importer_Md extends InteropService_Importer_
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
if (await this.isDirectoryEmpty(`${dirPath}/${stat.path}`)) {
|
||||
console.info(`Ignoring empty directory: ${stat.path}`);
|
||||
continue;
|
||||
}
|
||||
const folderTitle = await Folder.findUniqueItemTitle(basename(stat.path));
|
||||
|
||||
@@ -379,28 +379,6 @@ describe('services_rest_Api', function() {
|
||||
expect(resourceV2.size).toBe((await shim.fsDriver().stat(Resource.fullPath(resourceV2))).size);
|
||||
}));
|
||||
|
||||
it('should allow updating a resource file only', (async () => {
|
||||
await api.route(RequestMethod.POST, 'resources', null, JSON.stringify({
|
||||
title: 'resource',
|
||||
}), [{ path: `${supportDir}/photo.jpg` }]);
|
||||
|
||||
const resourceV1: ResourceEntity = (await Resource.all())[0];
|
||||
|
||||
await msleep(1);
|
||||
|
||||
await api.route(RequestMethod.PUT, `resources/${resourceV1.id}`, null, null, [
|
||||
{
|
||||
path: `${supportDir}/photo-large.png`,
|
||||
},
|
||||
]);
|
||||
|
||||
const resourceV2: ResourceEntity = (await Resource.all())[0];
|
||||
|
||||
// It should have updated the file content, but not the metadata
|
||||
expect(resourceV2.title).toBe(resourceV1.title);
|
||||
expect(resourceV2.size).toBeGreaterThan(resourceV1.size);
|
||||
}));
|
||||
|
||||
it('should update resource properties', (async () => {
|
||||
await api.route(RequestMethod.POST, 'resources', null, JSON.stringify({
|
||||
title: 'resource',
|
||||
|
||||
@@ -26,7 +26,6 @@ const input: Theme = {
|
||||
selectedColor2: '#131313',
|
||||
colorError2: '#ff6c6c',
|
||||
colorWarn2: '#ffcb81',
|
||||
colorWarn3: '#ff7626',
|
||||
|
||||
// Color scheme "3" is used for the config screens for example/
|
||||
// It's dark text over gray background.
|
||||
@@ -77,7 +76,6 @@ const expected = `
|
||||
--joplin-selected-color2: #131313;
|
||||
--joplin-color-error2: #ff6c6c;
|
||||
--joplin-color-warn2: #ffcb81;
|
||||
--joplin-color-warn3: #ff7626;
|
||||
--joplin-background-color3: #F4F5F6;
|
||||
--joplin-background-color-hover3: #CBDAF1;
|
||||
--joplin-color3: #738598;
|
||||
|
||||
@@ -9,7 +9,7 @@ import ResourceFetcher from '../../services/ResourceFetcher';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import BaseItem from '../../models/BaseItem';
|
||||
import Synchronizer from '../../Synchronizer';
|
||||
import { fetchSyncInfo, getEncryptionEnabled, localSyncInfo, setEncryptionEnabled } from '../synchronizer/syncInfoUtils';
|
||||
import { getEncryptionEnabled, setEncryptionEnabled } from '../synchronizer/syncInfoUtils';
|
||||
import { loadMasterKeysFromSettings, setupAndDisableEncryption, setupAndEnableEncryption } from '../e2ee/utils';
|
||||
|
||||
let insideBeforeEach = false;
|
||||
@@ -73,32 +73,6 @@ describe('Synchronizer.e2ee', function() {
|
||||
expect(!folder1_2.encryption_cipher_text).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark the key has having been used when synchronising the first time', (async () => {
|
||||
setEncryptionEnabled(true);
|
||||
await loadEncryptionMasterKey();
|
||||
await Folder.save({ title: 'folder1' });
|
||||
await synchronizerStart();
|
||||
|
||||
const localInfo = localSyncInfo();
|
||||
const remoteInfo = await fetchSyncInfo(fileApi());
|
||||
expect(localInfo.masterKeys[0].hasBeenUsed).toBe(true);
|
||||
expect(remoteInfo.masterKeys[0].hasBeenUsed).toBe(true);
|
||||
}));
|
||||
|
||||
it('should mark the key has having been used when synchronising after enabling encryption', (async () => {
|
||||
await Folder.save({ title: 'folder1' });
|
||||
await synchronizerStart();
|
||||
|
||||
setEncryptionEnabled(true);
|
||||
await loadEncryptionMasterKey();
|
||||
await synchronizerStart();
|
||||
|
||||
const localInfo = localSyncInfo();
|
||||
const remoteInfo = await fetchSyncInfo(fileApi());
|
||||
expect(localInfo.masterKeys[0].hasBeenUsed).toBe(true);
|
||||
expect(remoteInfo.masterKeys[0].hasBeenUsed).toBe(true);
|
||||
}));
|
||||
|
||||
it('should enable encryption automatically when downloading new master key (and none was previously available)',(async () => {
|
||||
// Enable encryption on client 1 and sync an item
|
||||
setEncryptionEnabled(true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, msleep } from '../../testing/test-utils';
|
||||
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService } from '../../testing/test-utils';
|
||||
import MasterKey from '../../models/MasterKey';
|
||||
import { masterKeyEnabled, mergeSyncInfos, setMasterKeyEnabled, SyncInfo, syncInfoEquals } from './syncInfoUtils';
|
||||
import { masterKeyEnabled, setMasterKeyEnabled, SyncInfo, syncInfoEquals } from './syncInfoUtils';
|
||||
|
||||
describe('syncInfoUtils', function() {
|
||||
|
||||
@@ -92,33 +92,4 @@ describe('syncInfoUtils', function() {
|
||||
}
|
||||
});
|
||||
|
||||
it('should merge sync target info and takes into account usage of master key - 1', async () => {
|
||||
const syncInfo1 = new SyncInfo();
|
||||
syncInfo1.masterKeys = [{
|
||||
id: '1',
|
||||
content: 'content1',
|
||||
hasBeenUsed: true,
|
||||
}];
|
||||
syncInfo1.activeMasterKeyId = '1';
|
||||
|
||||
await msleep(1);
|
||||
|
||||
const syncInfo2 = new SyncInfo();
|
||||
syncInfo2.masterKeys = [{
|
||||
id: '2',
|
||||
content: 'content2',
|
||||
hasBeenUsed: false,
|
||||
}];
|
||||
syncInfo2.activeMasterKeyId = '2';
|
||||
|
||||
// If one master key has been used and the other not, it should select
|
||||
// the one that's been used regardless of timestamps.
|
||||
expect(mergeSyncInfos(syncInfo1, syncInfo2).activeMasterKeyId).toBe('1');
|
||||
|
||||
// If both master keys have been used it should rely on timestamp
|
||||
// (latest modified is picked).
|
||||
syncInfo2.masterKeys[0].hasBeenUsed = true;
|
||||
expect(mergeSyncInfos(syncInfo1, syncInfo2).activeMasterKeyId).toBe('2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -90,62 +90,14 @@ export function localSyncInfoFromState(state: State): SyncInfo {
|
||||
return new SyncInfo(state.settings['syncInfoCache']);
|
||||
}
|
||||
|
||||
// When deciding which master key should be active we should take into account
|
||||
// whether it's been used or not. If it's been used before it should most likely
|
||||
// remain the active one, regardless of timestamps. This is because the extra
|
||||
// key was most likely created by mistake by the user, in particular in this
|
||||
// kind of scenario:
|
||||
//
|
||||
// - Client 1 setup sync with sync target
|
||||
// - Client 1 enable encryption
|
||||
// - Client 1 sync
|
||||
//
|
||||
// Then user 2 does the same:
|
||||
//
|
||||
// - Client 2 setup sync with sync target
|
||||
// - Client 2 enable encryption
|
||||
// - Client 2 sync
|
||||
//
|
||||
// The problem is that enabling encryption was not needed since it was already
|
||||
// done (and recorded in info.json) on the sync target. As a result an extra key
|
||||
// has been created and it has been set as the active one, but we shouldn't use
|
||||
// it. Instead the key created by client 1 should be used and made active again.
|
||||
//
|
||||
// And we can do this using the "hasBeenUsed" field which tells us which keys
|
||||
// has already been used to encrypt data. In this case, at the moment we compare
|
||||
// local and remote sync info (before synchronising the data), key1.hasBeenUsed
|
||||
// is true, but key2.hasBeenUsed is false.
|
||||
const mergeActiveMasterKeys = (s1: SyncInfo, s2: SyncInfo, output: SyncInfo) => {
|
||||
const activeMasterKey1 = getActiveMasterKey(s1);
|
||||
const activeMasterKey2 = getActiveMasterKey(s2);
|
||||
let doDefaultAction = false;
|
||||
|
||||
if (activeMasterKey1 && activeMasterKey2) {
|
||||
if (activeMasterKey1.hasBeenUsed && !activeMasterKey2.hasBeenUsed) {
|
||||
output.setWithTimestamp(s1, 'activeMasterKeyId');
|
||||
} else if (!activeMasterKey1.hasBeenUsed && activeMasterKey2.hasBeenUsed) {
|
||||
output.setWithTimestamp(s2, 'activeMasterKeyId');
|
||||
} else {
|
||||
doDefaultAction = true;
|
||||
}
|
||||
} else {
|
||||
doDefaultAction = true;
|
||||
}
|
||||
|
||||
if (doDefaultAction) {
|
||||
output.setWithTimestamp(s1.keyTimestamp('activeMasterKeyId') > s2.keyTimestamp('activeMasterKeyId') ? s1 : s2, 'activeMasterKeyId');
|
||||
}
|
||||
};
|
||||
|
||||
export function mergeSyncInfos(s1: SyncInfo, s2: SyncInfo): SyncInfo {
|
||||
const output: SyncInfo = new SyncInfo();
|
||||
|
||||
output.setWithTimestamp(s1.keyTimestamp('e2ee') > s2.keyTimestamp('e2ee') ? s1 : s2, 'e2ee');
|
||||
output.setWithTimestamp(s1.keyTimestamp('activeMasterKeyId') > s2.keyTimestamp('activeMasterKeyId') ? s1 : s2, 'activeMasterKeyId');
|
||||
output.setWithTimestamp(s1.keyTimestamp('ppk') > s2.keyTimestamp('ppk') ? s1 : s2, 'ppk');
|
||||
output.version = s1.version > s2.version ? s1.version : s2.version;
|
||||
|
||||
mergeActiveMasterKeys(s1, s2, output);
|
||||
|
||||
output.masterKeys = s1.masterKeys.slice();
|
||||
|
||||
for (const mk of s2.masterKeys) {
|
||||
@@ -202,14 +154,6 @@ export class SyncInfo {
|
||||
this.activeMasterKeyId_ = 'activeMasterKeyId' in s ? s.activeMasterKeyId : { value: '', updatedTime: 0 };
|
||||
this.masterKeys_ = 'masterKeys' in s ? s.masterKeys : [];
|
||||
this.ppk_ = 'ppk' in s ? s.ppk : { value: null, updatedTime: 0 };
|
||||
|
||||
// Migration for master keys that didn't have "hasBeenUsed" property -
|
||||
// in that case we assume they've been used at least once.
|
||||
for (const mk of this.masterKeys_) {
|
||||
if (!('hasBeenUsed' in mk) || mk.hasBeenUsed === undefined) {
|
||||
mk.hasBeenUsed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setWithTimestamp(fromSyncInfo: SyncInfo, propName: string) {
|
||||
@@ -331,21 +275,6 @@ export function setMasterKeyEnabled(mkId: string, enabled: boolean = true) {
|
||||
saveLocalSyncInfo(s);
|
||||
}
|
||||
|
||||
export const setMasterKeyHasBeenUsed = (s: SyncInfo, mkId: string) => {
|
||||
const idx = s.masterKeys.findIndex(mk => mk.id === mkId);
|
||||
if (idx < 0) throw new Error(`No such master key: ${mkId}`);
|
||||
|
||||
s.masterKeys[idx] = {
|
||||
...s.masterKeys[idx],
|
||||
hasBeenUsed: true,
|
||||
updated_time: Date.now(),
|
||||
};
|
||||
|
||||
saveLocalSyncInfo(s);
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
export function masterKeyEnabled(mk: MasterKeyEntity): boolean {
|
||||
if ('enabled' in mk) return !!mk.enabled;
|
||||
return true;
|
||||
|
||||
@@ -235,8 +235,6 @@ function shimInit(options = null) {
|
||||
const readChunk = require('read-chunk');
|
||||
const imageType = require('image-type');
|
||||
|
||||
const isUpdate = !!options.destinationResourceId;
|
||||
|
||||
const uuid = require('./uuid').default;
|
||||
|
||||
if (!(await fs.pathExists(filePath))) throw new Error(_('Cannot access %s', filePath));
|
||||
@@ -244,16 +242,12 @@ function shimInit(options = null) {
|
||||
defaultProps = defaultProps ? defaultProps : {};
|
||||
|
||||
let resourceId = defaultProps.id ? defaultProps.id : uuid.create();
|
||||
if (isUpdate) resourceId = options.destinationResourceId;
|
||||
if (options.destinationResourceId) resourceId = options.destinationResourceId;
|
||||
|
||||
let resource = isUpdate ? {} : Resource.new();
|
||||
let resource = options.destinationResourceId ? {} : Resource.new();
|
||||
resource.id = resourceId;
|
||||
|
||||
// When this is an update we auto-update the mime type, in case the
|
||||
// content type has changed, but we keep the title. It is still possible
|
||||
// to modify the title on update using defaultProps.
|
||||
resource.mime = mimeUtils.fromFilename(filePath);
|
||||
if (!isUpdate) resource.title = basename(filePath);
|
||||
resource.title = basename(filePath);
|
||||
|
||||
let fileExt = safeFileExtension(fileExtension(filePath));
|
||||
|
||||
@@ -294,7 +288,7 @@ function shimInit(options = null) {
|
||||
const saveOptions = { isNew: true };
|
||||
if (options.userSideValidation) saveOptions.userSideValidation = true;
|
||||
|
||||
if (isUpdate) {
|
||||
if (options.destinationResourceId) {
|
||||
saveOptions.isNew = false;
|
||||
const tempPath = `${targetPath}.tmp`;
|
||||
await shim.fsDriver().move(targetPath, tempPath);
|
||||
|
||||
@@ -29,7 +29,6 @@ const theme: Theme = {
|
||||
selectedColor2: '#013F74',
|
||||
colorError2: '#ff6c6c',
|
||||
colorWarn2: '#ffcb81',
|
||||
colorWarn3: '#ffcb81',
|
||||
|
||||
// Color scheme "3" is used for the config screens for example/
|
||||
// It's dark text over gray background.
|
||||
|
||||
@@ -26,7 +26,6 @@ const theme: Theme = {
|
||||
selectedColor2: '#131313',
|
||||
colorError2: '#ff6c6c',
|
||||
colorWarn2: '#ffcb81',
|
||||
colorWarn3: '#ff7626',
|
||||
|
||||
// Color scheme "3" is used for the config screens for example/
|
||||
// It's dark text over gray background.
|
||||
|
||||
@@ -27,8 +27,7 @@ export interface Theme {
|
||||
color2: string;
|
||||
selectedColor2: string;
|
||||
colorError2: string;
|
||||
colorWarn2: string; // On a darker background (eg. sidebar)
|
||||
colorWarn3: string; // On a lighter background (eg. note list)
|
||||
colorWarn2: string;
|
||||
|
||||
// Color scheme "3" is used for the config screens for example/
|
||||
// It's dark text over gray background.
|
||||
|
||||
@@ -129,11 +129,11 @@ const features: Record<FeatureId, PlanFeature> = {
|
||||
pro: true,
|
||||
teams: true,
|
||||
basicInfo: '1 GB storage space',
|
||||
proInfo: '10 GB storage space',
|
||||
teamsInfo: '10 GB storage space',
|
||||
proInfo: '200 GB storage space',
|
||||
teamsInfo: '200 GB storage space',
|
||||
basicInfoShort: '1 GB',
|
||||
proInfoShort: '10 GB',
|
||||
teamsInfoShort: '10 GB',
|
||||
proInfoShort: '200 GB',
|
||||
teamsInfoShort: '200 GB',
|
||||
},
|
||||
publishNote: {
|
||||
title: 'Publish notes to the internet',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/plugin-repo-cli",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"bin": "./dist/index.js",
|
||||
@@ -18,8 +18,8 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.8",
|
||||
"@joplin/tools": "~2.8",
|
||||
"@joplin/lib": "~2.7",
|
||||
"@joplin/tools": "~2.7",
|
||||
"fs-extra": "^9.0.1",
|
||||
"gh-release-assets": "^2.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/renderer",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "The Joplin note renderer, used the mobile and desktop application",
|
||||
"repository": "https://github.com/laurent22/joplin/tree/dev/packages/renderer",
|
||||
"main": "index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start-dev": "yarn run build && JOPLIN_IS_TESTING=1 nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
|
||||
@@ -23,8 +23,8 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.40.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@joplin/lib": "~2.8",
|
||||
"@joplin/renderer": "~2.8",
|
||||
"@joplin/lib": "~2.7",
|
||||
"@joplin/renderer": "~2.7",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
@@ -58,7 +58,7 @@
|
||||
"zxcvbn": "^4.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@joplin/tools": "~2.8",
|
||||
"@joplin/tools": "~2.7",
|
||||
"@rmp135/sql-ts": "^1.12.1",
|
||||
"@types/fs-extra": "^8.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
|
||||
@@ -5,7 +5,7 @@ import uuidgen from '../utils/uuidgen';
|
||||
import { ErrorUnprocessableEntity, ErrorBadRequest } from '../utils/errors';
|
||||
import { Models, NewModelFactoryHandler } from './factory';
|
||||
import * as EventEmitter from 'events';
|
||||
import { Config, Env } from '../utils/types';
|
||||
import { Config } from '../utils/types';
|
||||
import personalizedUserContentBaseUrl from '@joplin/lib/services/joplinServer/personalizedUserContentBaseUrl';
|
||||
import Logger from '@joplin/lib/Logger';
|
||||
import dbuuid from '../utils/dbuuid';
|
||||
@@ -85,10 +85,6 @@ export default abstract class BaseModel<T> {
|
||||
return this.config_.userContentBaseUrl;
|
||||
}
|
||||
|
||||
protected get env(): Env {
|
||||
return this.config_.env;
|
||||
}
|
||||
|
||||
protected personalizedUserContentBaseUrl(userId: Uuid): string {
|
||||
return personalizedUserContentBaseUrl(userId, this.baseUrl, this.userContentBaseUrl);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ import changeEmailConfirmationTemplate from '../views/emails/changeEmailConfirma
|
||||
import changeEmailNotificationTemplate from '../views/emails/changeEmailNotificationTemplate';
|
||||
import { NotificationKey } from './NotificationModel';
|
||||
import prettyBytes = require('pretty-bytes');
|
||||
import { Env } from '../utils/types';
|
||||
|
||||
const logger = Logger.create('UserModel');
|
||||
|
||||
@@ -238,8 +237,6 @@ export default class UserModel extends BaseModel<User> {
|
||||
}
|
||||
|
||||
private validatePassword(password: string) {
|
||||
if (this.env === Env.Dev) return;
|
||||
|
||||
const result = zxcvbn(password);
|
||||
if (result.score < 3) {
|
||||
let msg: string[] = [result.feedback.warning];
|
||||
|
||||
@@ -161,8 +161,8 @@ describe('admin/users', function() {
|
||||
await patchUser(session.id, {
|
||||
id: user.id,
|
||||
email: 'changed@example.com',
|
||||
password: '111111',
|
||||
password2: '111111',
|
||||
password: 'hunter11hunter22hunter33',
|
||||
password2: 'hunter11hunter22hunter33',
|
||||
}, '/admin/users/me');
|
||||
|
||||
const sessions = await models().session().all();
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function createTestUsers(db: DbConnection, config: Config, options:
|
||||
...options,
|
||||
};
|
||||
|
||||
const password = '111111';
|
||||
const password = 'hunter1hunter2hunter3';
|
||||
|
||||
const models = newModelFactory(db, config);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/tools",
|
||||
"version": "2.8.0",
|
||||
"version": "2.7.0",
|
||||
"description": "Various tools for Joplin",
|
||||
"main": "index.js",
|
||||
"author": "Laurent Cozic",
|
||||
@@ -20,8 +20,8 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@joplin/lib": "~2.8",
|
||||
"@joplin/renderer": "~2.8",
|
||||
"@joplin/lib": "~2.7",
|
||||
"@joplin/renderer": "~2.7",
|
||||
"@types/node-fetch": "1.6.9",
|
||||
"dayjs": "^1.10.7",
|
||||
"execa": "^4.1.0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { execCommand2, gitCurrentBranch, githubRelease, gitPullTry, rootDir } from './tool-utils';
|
||||
import { execCommand2, githubRelease, gitPullTry, rootDir } from './tool-utils';
|
||||
|
||||
const appDir = `${rootDir}/packages/app-desktop`;
|
||||
|
||||
@@ -27,12 +27,10 @@ async function main() {
|
||||
console.info('Release options: ', releaseOptions);
|
||||
|
||||
const release = await githubRelease('joplin', tagName, releaseOptions);
|
||||
const currentBranch = await gitCurrentBranch();
|
||||
|
||||
console.info(`Created GitHub release: ${release.html_url}`);
|
||||
console.info('GitHub release page: https://github.com/laurent22/joplin/releases');
|
||||
console.info(`To create changelog: node packages/tools/git-changelog.js ${version}`);
|
||||
console.info(`To merge the version update: git checkout dev && git mergeff ${currentBranch} && git push && git checkout ${currentBranch}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@@ -335,11 +335,6 @@ export async function gitPullTry(ignoreIfNotBranch = true) {
|
||||
}
|
||||
}
|
||||
|
||||
export const gitCurrentBranch = async (): Promise<string> => {
|
||||
const output = await execCommand2('git rev-parse --abbrev-ref HEAD', { quiet: true });
|
||||
return output.trim();
|
||||
};
|
||||
|
||||
export async function githubUsername(email: string, name: string) {
|
||||
const cache = await loadGitHubUsernameCache();
|
||||
const cacheKey = `${email}:${name}`;
|
||||
|
||||
38
yarn.lock
38
yarn.lock
@@ -2896,9 +2896,9 @@ __metadata:
|
||||
"@electron/remote": ^2.0.1
|
||||
"@fortawesome/fontawesome-free": ^5.13.0
|
||||
"@joeattardi/emoji-button": ^4.6.0
|
||||
"@joplin/lib": ~2.8
|
||||
"@joplin/renderer": ~2.8
|
||||
"@joplin/tools": ~2.8
|
||||
"@joplin/lib": ~2.7
|
||||
"@joplin/renderer": ~2.7
|
||||
"@joplin/tools": ~2.7
|
||||
"@testing-library/react-hooks": ^3.4.2
|
||||
"@types/jest": ^26.0.15
|
||||
"@types/node": ^14.14.6
|
||||
@@ -3089,7 +3089,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/htmlpack@^2.6.1, @joplin/htmlpack@workspace:packages/htmlpack, @joplin/htmlpack@~2.8":
|
||||
"@joplin/htmlpack@^2.6.1, @joplin/htmlpack@workspace:packages/htmlpack, @joplin/htmlpack@~2.7":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/htmlpack@workspace:packages/htmlpack"
|
||||
dependencies:
|
||||
@@ -3102,7 +3102,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/lib@^2.6.3, @joplin/lib@workspace:packages/lib, @joplin/lib@~2.8":
|
||||
"@joplin/lib@^2.6.3, @joplin/lib@workspace:packages/lib, @joplin/lib@~2.7":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/lib@workspace:packages/lib"
|
||||
dependencies:
|
||||
@@ -3111,8 +3111,8 @@ __metadata:
|
||||
"@joplin/fork-htmlparser2": ^4.1.39
|
||||
"@joplin/fork-sax": ^1.2.43
|
||||
"@joplin/fork-uslug": ^1.0.4
|
||||
"@joplin/htmlpack": ~2.8
|
||||
"@joplin/renderer": ~2.8
|
||||
"@joplin/htmlpack": ~2.7
|
||||
"@joplin/renderer": ~2.7
|
||||
"@joplin/turndown": ^4.0.61
|
||||
"@joplin/turndown-plugin-gfm": ^1.0.43
|
||||
"@types/fs-extra": ^9.0.6
|
||||
@@ -3257,8 +3257,8 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/plugin-repo-cli@workspace:packages/plugin-repo-cli"
|
||||
dependencies:
|
||||
"@joplin/lib": ~2.8
|
||||
"@joplin/tools": ~2.8
|
||||
"@joplin/lib": ~2.7
|
||||
"@joplin/tools": ~2.7
|
||||
"@types/fs-extra": ^9.0.6
|
||||
"@types/jest": ^26.0.15
|
||||
"@types/node": ^14.14.6
|
||||
@@ -3283,7 +3283,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/renderer@^2.6.3, @joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.8":
|
||||
"@joplin/renderer@^2.6.3, @joplin/renderer@workspace:packages/renderer, @joplin/renderer@~2.7":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/renderer@workspace:packages/renderer"
|
||||
dependencies:
|
||||
@@ -3354,9 +3354,9 @@ __metadata:
|
||||
dependencies:
|
||||
"@aws-sdk/client-s3": ^3.40.0
|
||||
"@fortawesome/fontawesome-free": ^5.15.1
|
||||
"@joplin/lib": ~2.8
|
||||
"@joplin/renderer": ~2.8
|
||||
"@joplin/tools": ~2.8
|
||||
"@joplin/lib": ~2.7
|
||||
"@joplin/renderer": ~2.7
|
||||
"@joplin/tools": ~2.7
|
||||
"@koa/cors": ^3.1.0
|
||||
"@rmp135/sql-ts": ^1.12.1
|
||||
"@types/fs-extra": ^8.0.0
|
||||
@@ -3434,12 +3434,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@joplin/tools@workspace:packages/tools, @joplin/tools@~2.8":
|
||||
"@joplin/tools@workspace:packages/tools, @joplin/tools@~2.7":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/tools@workspace:packages/tools"
|
||||
dependencies:
|
||||
"@joplin/lib": ~2.8
|
||||
"@joplin/renderer": ~2.8
|
||||
"@joplin/lib": ~2.7
|
||||
"@joplin/renderer": ~2.7
|
||||
"@rmp135/sql-ts": ^1.6.0
|
||||
"@types/fs-extra": ^9.0.6
|
||||
"@types/jest": ^26.0.15
|
||||
@@ -18644,9 +18644,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "joplin@workspace:packages/app-cli"
|
||||
dependencies:
|
||||
"@joplin/lib": ~2.8
|
||||
"@joplin/renderer": ~2.8
|
||||
"@joplin/tools": ~2.8
|
||||
"@joplin/lib": ~2.7
|
||||
"@joplin/renderer": ~2.7
|
||||
"@joplin/tools": ~2.7
|
||||
"@types/fs-extra": ^9.0.6
|
||||
"@types/jest": ^26.0.15
|
||||
"@types/node": ^14.14.6
|
||||
|
||||
Reference in New Issue
Block a user