mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Started applying config to Electron app
This commit is contained in:
parent
4fe70fe8ee
commit
086f9e1123
File diff suppressed because one or more lines are too long
@ -1,18 +1,14 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { commandArgumentsToString } = require('lib/string-utils');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const shared = require('lib/components/shared/config-shared.js');
|
||||
|
||||
class ConfigScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -20,7 +16,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
this.checkSyncConfig_ = async () => {
|
||||
await shared.checkSyncConfig(this, this.state.settings);
|
||||
}
|
||||
};
|
||||
|
||||
this.rowStyle_ = {
|
||||
marginBottom: 10,
|
||||
@ -70,11 +66,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
sectionStyle.borderTopWidth = 0;
|
||||
}
|
||||
|
||||
const noteComp = section.name !== 'general' ? null : (
|
||||
<div style={Object.assign({}, theme.textStyle, {marginBottom: 10})}>
|
||||
{_('Notes and settings are stored in: %s', pathUtils.toSystemSlashes(Setting.value('profileDir'), process.platform))}
|
||||
</div>
|
||||
);
|
||||
const noteComp = section.name !== 'general' ? null : <div style={Object.assign({}, theme.textStyle, { marginBottom: 10 })}>{_('Notes and settings are stored in: %s', pathUtils.toSystemSlashes(Setting.value('profileDir'), process.platform))}</div>;
|
||||
|
||||
if (section.name === 'sync') {
|
||||
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
|
||||
@ -85,14 +77,18 @@ class ConfigScreenComponent extends React.Component {
|
||||
const statusComp = !messages.length ? null : (
|
||||
<div style={statusStyle}>
|
||||
{messages[0]}
|
||||
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null}
|
||||
</div>);
|
||||
{messages.length >= 1 ? <p>{messages[1]}</p> : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
settingComps.push(
|
||||
<div key="check_sync_config_button" style={this.rowStyle_}>
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} style={theme.buttonStyle} onClick={this.checkSyncConfig_}>{_('Check synchronisation configuration')}</button>
|
||||
{ statusComp }
|
||||
</div>);
|
||||
<button disabled={this.state.checkSyncConfigResult === 'checking'} style={theme.buttonStyle} onClick={this.checkSyncConfig_}>
|
||||
{_('Check synchronisation configuration')}
|
||||
</button>
|
||||
{statusComp}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,9 +96,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
<div key={key} style={sectionStyle}>
|
||||
<h2 style={headerStyle}>{Setting.sectionNameToLabel(section.name)}</h2>
|
||||
{noteComp}
|
||||
<div>
|
||||
{settingComps}
|
||||
</div>
|
||||
<div>{settingComps}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -145,18 +139,14 @@ class ConfigScreenComponent extends React.Component {
|
||||
const updateSettingValue = (key, value) => {
|
||||
// console.info(key + ' = ' + value);
|
||||
return shared.updateSettingValue(this, key, value);
|
||||
}
|
||||
};
|
||||
|
||||
// Component key needs to be key+value otherwise it doesn't update when the settings change.
|
||||
|
||||
const md = Setting.settingMetadata(key);
|
||||
|
||||
const descriptionText = Setting.keyDescription(key, 'desktop');
|
||||
const descriptionComp = descriptionText ? (
|
||||
<div style={descriptionStyle}>
|
||||
{descriptionText}
|
||||
</div>
|
||||
) : null;
|
||||
const descriptionComp = descriptionText ? <div style={descriptionStyle}>{descriptionText}</div> : null;
|
||||
|
||||
if (md.isEnum) {
|
||||
let items = [];
|
||||
@ -164,31 +154,59 @@ class ConfigScreenComponent extends React.Component {
|
||||
let array = this.keyValueToArray(settingOptions);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const e = array[i];
|
||||
items.push(<option value={e.key.toString()} key={e.key}>{settingOptions[e.key]}</option>);
|
||||
items.push(
|
||||
<option value={e.key.toString()} key={e.key}>
|
||||
{settingOptions[e.key]}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<select value={value} style={controlStyle} onChange={(event) => { updateSettingValue(key, event.target.value) }}>
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
<select
|
||||
value={value}
|
||||
style={controlStyle}
|
||||
onChange={event => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
}}
|
||||
>
|
||||
{items}
|
||||
</select>
|
||||
{ descriptionComp }
|
||||
{descriptionComp}
|
||||
</div>
|
||||
);
|
||||
} else if (md.type === Setting.TYPE_BOOL) {
|
||||
const onCheckboxClick = (event) => {
|
||||
updateSettingValue(key, !value)
|
||||
}
|
||||
const onCheckboxClick = event => {
|
||||
updateSettingValue(key, !value);
|
||||
};
|
||||
|
||||
// Hack: The {key+value.toString()} is needed as otherwise the checkbox doesn't update when the state changes.
|
||||
// There's probably a better way to do this but can't figure it out.
|
||||
|
||||
return (
|
||||
<div key={key+value.toString()} style={rowStyle}>
|
||||
<div key={key + value.toString()} style={rowStyle}>
|
||||
<div style={controlStyle}>
|
||||
<input id={'setting_checkbox_' + key} type="checkbox" checked={!!value} onChange={(event) => { onCheckboxClick(event) }}/><label onClick={(event) => { onCheckboxClick(event) }} style={labelStyle} htmlFor={'setting_checkbox_' + key}>{md.label()}</label>
|
||||
{ descriptionComp }
|
||||
<input
|
||||
id={'setting_checkbox_' + key}
|
||||
type="checkbox"
|
||||
checked={!!value}
|
||||
onChange={event => {
|
||||
onCheckboxClick(event);
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
onClick={event => {
|
||||
onCheckboxClick(event);
|
||||
}}
|
||||
style={labelStyle}
|
||||
htmlFor={'setting_checkbox_' + key}
|
||||
>
|
||||
{md.label()}
|
||||
</label>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -196,7 +214,8 @@ class ConfigScreenComponent extends React.Component {
|
||||
const inputStyle = Object.assign({}, controlStyle, {
|
||||
width: '50%',
|
||||
minWidth: '20em',
|
||||
border: '1px solid' });
|
||||
border: '1px solid',
|
||||
});
|
||||
const inputType = md.secure === true ? 'password' : 'text';
|
||||
|
||||
if (md.subType === 'file_path_and_args') {
|
||||
@ -206,7 +225,7 @@ class ConfigScreenComponent extends React.Component {
|
||||
const path = pathUtils.extractExecutablePath(cmdString);
|
||||
const args = cmdString.substr(path.length + 1);
|
||||
return [pathUtils.unquotePath(path), args];
|
||||
}
|
||||
};
|
||||
|
||||
const joinCmd = cmdArray => {
|
||||
if (!cmdArray[0] && !cmdArray[1]) return '';
|
||||
@ -214,74 +233,101 @@ class ConfigScreenComponent extends React.Component {
|
||||
if (!cmdString) cmdString = '""';
|
||||
if (cmdArray[1]) cmdString += ' ' + cmdArray[1];
|
||||
return cmdString;
|
||||
}
|
||||
};
|
||||
|
||||
const onPathChange = event => {
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
cmd[0] = event.target.value;
|
||||
updateSettingValue(key, joinCmd(cmd));
|
||||
}
|
||||
};
|
||||
|
||||
const onArgsChange = event => {
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
cmd[1] = event.target.value;
|
||||
updateSettingValue(key, joinCmd(cmd));
|
||||
}
|
||||
};
|
||||
|
||||
const browseButtonClick = () => {
|
||||
const paths = bridge().showOpenDialog();
|
||||
if (!paths || !paths.length) return;
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
cmd[0] = paths[0]
|
||||
cmd[0] = paths[0];
|
||||
updateSettingValue(key, joinCmd(cmd));
|
||||
}
|
||||
};
|
||||
|
||||
const cmd = splitCmd(this.state.settings[key]);
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={{display:'flex'}}>
|
||||
<div style={{flex:0, whiteSpace: 'nowrap'}}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ flex: 0, whiteSpace: 'nowrap' }}>
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex:0}}>
|
||||
<div style={{ flex: 0 }}>
|
||||
<div style={subLabel}>Path:</div>
|
||||
<div style={subLabel}>Arguments:</div>
|
||||
</div>
|
||||
<div style={{flex:1}}>
|
||||
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom}}>
|
||||
<input type={inputType} style={Object.assign({}, inputStyle, {marginBottom:0})} onChange={(event) => {onPathChange(event)}} value={cmd[0]} />
|
||||
<button onClick={browseButtonClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 5, minHeight: 20, height: 20 })}>{_('Browse...')}</button>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom }}>
|
||||
<input
|
||||
type={inputType}
|
||||
style={Object.assign({}, inputStyle, { marginBottom: 0 })}
|
||||
onChange={event => {
|
||||
onPathChange(event);
|
||||
}}
|
||||
value={cmd[0]}
|
||||
/>
|
||||
<button onClick={browseButtonClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 5, minHeight: 20, height: 20 })}>
|
||||
{_('Browse...')}
|
||||
</button>
|
||||
</div>
|
||||
<input type={inputType} style={inputStyle} onChange={(event) => {onArgsChange(event)}} value={cmd[1]}/>
|
||||
<input
|
||||
type={inputType}
|
||||
style={inputStyle}
|
||||
onChange={event => {
|
||||
onArgsChange(event);
|
||||
}}
|
||||
value={cmd[1]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{display:'flex'}}>
|
||||
<div style={{flex:0, whiteSpace: 'nowrap'}}>
|
||||
<div style={invisibleLabel}><label>{md.label()}</label></div>
|
||||
</div>
|
||||
<div style={{flex:1}}>
|
||||
{ descriptionComp }
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ flex: 0, whiteSpace: 'nowrap' }}>
|
||||
<div style={invisibleLabel}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>{descriptionComp}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const onTextChange = (event) => {
|
||||
const onTextChange = event => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{md.label()}</label></div>
|
||||
<input type={inputType} style={inputStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} />
|
||||
{ descriptionComp }
|
||||
<div style={labelStyle}>
|
||||
<label>{md.label()}</label>
|
||||
</div>
|
||||
<input
|
||||
type={inputType}
|
||||
style={inputStyle}
|
||||
value={this.state.settings[key]}
|
||||
onChange={event => {
|
||||
onTextChange(event);
|
||||
}}
|
||||
/>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else if (md.type === Setting.TYPE_INT) {
|
||||
const onNumChange = (event) => {
|
||||
const onNumChange = event => {
|
||||
updateSettingValue(key, event.target.value);
|
||||
};
|
||||
|
||||
@ -290,9 +336,21 @@ class ConfigScreenComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div key={key} style={rowStyle}>
|
||||
<div style={labelStyle}><label>{label.join(' ')}</label></div>
|
||||
<input type="number" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onNumChange(event)}} min={md.minimum} max={md.maximum} step={md.step}/>
|
||||
{ descriptionComp }
|
||||
<div style={labelStyle}>
|
||||
<label>{label.join(' ')}</label>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
style={controlStyle}
|
||||
value={this.state.settings[key]}
|
||||
onChange={event => {
|
||||
onNumChange(event);
|
||||
}}
|
||||
min={md.minimum}
|
||||
max={md.maximum}
|
||||
step={md.step}
|
||||
/>
|
||||
{descriptionComp}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
@ -318,13 +376,17 @@ class ConfigScreenComponent extends React.Component {
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const style = Object.assign({
|
||||
backgroundColor: theme.backgroundColor
|
||||
}, this.props.style, {
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
const style = Object.assign(
|
||||
{
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
this.props.style,
|
||||
{
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}
|
||||
);
|
||||
|
||||
let settings = this.state.settings;
|
||||
|
||||
@ -355,20 +417,41 @@ class ConfigScreenComponent extends React.Component {
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={buttonBarStyle}>
|
||||
<button onClick={() => {this.onCancelClick()}} style={buttonStyle}><i style={theme.buttonIconStyle} className={"fa fa-chevron-left"}></i>{_('Cancel')}</button>
|
||||
<button disabled={!hasChanges} onClick={() => {this.onSaveClick()}} style={buttonStyleApprove}>{_('OK')}</button>
|
||||
<button disabled={!hasChanges} onClick={() => {this.onApplyClick()}} style={buttonStyleApprove}>{_('Apply')}</button>
|
||||
</div>
|
||||
<div style={containerStyle}>
|
||||
{ settingComps }
|
||||
<button
|
||||
onClick={() => {
|
||||
this.onCancelClick();
|
||||
}}
|
||||
style={buttonStyle}
|
||||
>
|
||||
<i style={theme.buttonIconStyle} className={'fa fa-chevron-left'}></i>
|
||||
{_('Cancel')}
|
||||
</button>
|
||||
<button
|
||||
disabled={!hasChanges}
|
||||
onClick={() => {
|
||||
this.onSaveClick();
|
||||
}}
|
||||
style={buttonStyleApprove}
|
||||
>
|
||||
{_('OK')}
|
||||
</button>
|
||||
<button
|
||||
disabled={!hasChanges}
|
||||
onClick={() => {
|
||||
this.onApplyClick();
|
||||
}}
|
||||
style={buttonStyleApprove}
|
||||
>
|
||||
{_('Apply')}
|
||||
</button>
|
||||
</div>
|
||||
<div style={containerStyle}>{settingComps}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
settings: state.settings,
|
||||
|
@ -1,23 +1,16 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const Shared = require('lib/components/shared/dropbox-login-shared');
|
||||
|
||||
class DropboxLoginScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.shared_ = new Shared(
|
||||
this,
|
||||
(msg) => bridge().showInfoMessageBox(msg),
|
||||
(msg) => bridge().showErrorMessageBox(msg)
|
||||
);
|
||||
this.shared_ = new Shared(this, msg => bridge().showInfoMessageBox(msg), msg => bridge().showErrorMessageBox(msg));
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
@ -42,18 +35,23 @@ class DropboxLoginScreenComponent extends React.Component {
|
||||
<div style={containerStyle}>
|
||||
<p style={theme.textStyle}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</p>
|
||||
<p style={theme.textStyle}>{_('Step 1: Open this URL in your browser to authorise the application:')}</p>
|
||||
<a style={theme.textStyle} href="#" onClick={this.shared_.loginUrl_click}>{this.state.loginUrl}</a>
|
||||
<a style={theme.textStyle} href="#" onClick={this.shared_.loginUrl_click}>
|
||||
{this.state.loginUrl}
|
||||
</a>
|
||||
<p style={theme.textStyle}>{_('Step 2: Enter the code provided by Dropbox:')}</p>
|
||||
<p><input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle}/></p>
|
||||
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>{_('Submit')}</button>
|
||||
<p>
|
||||
<input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle} />
|
||||
</p>
|
||||
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>
|
||||
{_('Submit')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const BaseItem = require('lib/models/BaseItem');
|
||||
const EncryptionService = require('lib/services/EncryptionService');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
@ -9,11 +8,9 @@ const { _ } = require('lib/locale.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const dialogs = require('./dialogs');
|
||||
const shared = require('lib/components/shared/encryption-config-shared.js');
|
||||
const pathUtils = require('lib/path-utils.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class EncryptionConfigScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
shared.constructor(this);
|
||||
@ -55,15 +52,15 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
border: '1px solid',
|
||||
borderColor: theme.dividerColor,
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveClick = () => {
|
||||
return shared.onSavePasswordClick(this, mk);
|
||||
}
|
||||
};
|
||||
|
||||
const onPasswordChange = (event) => {
|
||||
const onPasswordChange = event => {
|
||||
return shared.onPasswordChange(this, mk, event.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
|
||||
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
|
||||
@ -76,7 +73,12 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
<td style={theme.textStyle}>{mk.source_application}</td>
|
||||
<td style={theme.textStyle}>{time.formatMsToLocal(mk.created_time)}</td>
|
||||
<td style={theme.textStyle}>{time.formatMsToLocal(mk.updated_time)}</td>
|
||||
<td style={theme.textStyle}><input type="password" style={passwordStyle} value={password} onChange={(event) => onPasswordChange(event)}/> <button style={theme.buttonStyle} onClick={() => onSaveClick()}>{_('Save')}</button></td>
|
||||
<td style={theme.textStyle}>
|
||||
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
|
||||
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
|
||||
{_('Save')}
|
||||
</button>
|
||||
</td>
|
||||
<td style={theme.textStyle}>{passwordOk}</td>
|
||||
</tr>
|
||||
);
|
||||
@ -128,10 +130,19 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
} catch (error) {
|
||||
await dialogs.alert(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const decryptedItemsInfo = <p style={theme.textStyle}>{shared.decryptedStatText(this)}</p>;
|
||||
const toggleButton = <button style={theme.buttonStyle} onClick={() => { onToggleButtonClick() }}>{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}</button>
|
||||
const toggleButton = (
|
||||
<button
|
||||
style={theme.buttonStyle}
|
||||
onClick={() => {
|
||||
onToggleButtonClick();
|
||||
}}
|
||||
>
|
||||
{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}
|
||||
</button>
|
||||
);
|
||||
|
||||
let masterKeySection = null;
|
||||
|
||||
@ -164,7 +175,11 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
const rows = [];
|
||||
for (let i = 0; i < nonExistingMasterKeyIds.length; i++) {
|
||||
const id = nonExistingMasterKeyIds[i];
|
||||
rows.push(<tr key={id}><td style={theme.textStyle}>{id}</td></tr>);
|
||||
rows.push(
|
||||
<tr key={id}>
|
||||
<td style={theme.textStyle}>{id}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
nonExistingMasterKeySection = (
|
||||
@ -176,7 +191,7 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
<tr>
|
||||
<th style={theme.textStyle}>{_('ID')}</th>
|
||||
</tr>
|
||||
{ rows }
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -187,13 +202,25 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
<div>
|
||||
<Header style={headerStyle} />
|
||||
<div style={containerStyle}>
|
||||
{<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
<p style={theme.textStyle}>
|
||||
<span>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</span> <a onClick={() => {bridge().openExternal('https://joplinapp.org/e2ee/')}} href="#">https://joplinapp.org/e2ee/</a>
|
||||
</p>
|
||||
</div>}
|
||||
{
|
||||
<div style={{ backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
|
||||
<p style={theme.textStyle}>
|
||||
<span>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</span>{' '}
|
||||
<a
|
||||
onClick={() => {
|
||||
bridge().openExternal('https://joplinapp.org/e2ee/');
|
||||
}}
|
||||
href="#"
|
||||
>
|
||||
https://joplinapp.org/e2ee/
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<h1 style={theme.h1Style}>{_('Status')}</h1>
|
||||
<p style={theme.textStyle}>{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong></p>
|
||||
<p style={theme.textStyle}>
|
||||
{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong>
|
||||
</p>
|
||||
{decryptedItemsInfo}
|
||||
{toggleButton}
|
||||
{masterKeySection}
|
||||
@ -202,10 +229,9 @@ class EncryptionConfigScreenComponent extends React.Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
masterKeys: state.masterKeys,
|
||||
|
@ -1,12 +1,10 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class HeaderComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@ -18,13 +16,13 @@ class HeaderComponent extends React.Component {
|
||||
this.searchOnQuery_ = null;
|
||||
this.searchElement_ = null;
|
||||
|
||||
const triggerOnQuery = (query) => {
|
||||
const triggerOnQuery = query => {
|
||||
clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
if (this.searchOnQuery_) this.searchOnQuery_(query);
|
||||
this.scheduleSearchChangeEventIid_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
this.search_onChange = (event) => {
|
||||
this.search_onChange = event => {
|
||||
this.setState({ searchQuery: event.target.value });
|
||||
|
||||
if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_);
|
||||
@ -34,10 +32,10 @@ class HeaderComponent extends React.Component {
|
||||
}, 500);
|
||||
};
|
||||
|
||||
this.search_onClear = (event) => {
|
||||
this.search_onClear = event => {
|
||||
this.resetSearch();
|
||||
if (this.searchElement_) this.searchElement_.focus();
|
||||
}
|
||||
};
|
||||
|
||||
this.search_onFocus = event => {
|
||||
if (this.hideSearchUsageLinkIID_) {
|
||||
@ -46,7 +44,7 @@ class HeaderComponent extends React.Component {
|
||||
}
|
||||
|
||||
this.setState({ showSearchUsageLink: true });
|
||||
}
|
||||
};
|
||||
|
||||
this.search_onBlur = event => {
|
||||
if (this.hideSearchUsageLinkIID_) return;
|
||||
@ -54,22 +52,23 @@ class HeaderComponent extends React.Component {
|
||||
this.hideSearchUsageLinkIID_ = setTimeout(() => {
|
||||
this.setState({ showSearchUsageLink: false });
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.search_keyDown = event => {
|
||||
if (event.keyCode === 27) { // ESCAPE
|
||||
if (event.keyCode === 27) {
|
||||
// ESCAPE
|
||||
this.resetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
this.resetSearch = () => {
|
||||
this.setState({ searchQuery: '' });
|
||||
triggerOnQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
this.searchUsageLink_click = event => {
|
||||
bridge().openExternal('https://joplinapp.org/#searching');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
@ -79,11 +78,11 @@ class HeaderComponent extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
|
||||
if (prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
|
||||
this.resetSearch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.hideSearchUsageLinkIID_) {
|
||||
clearTimeout(this.hideSearchUsageLinkIID_);
|
||||
@ -122,14 +121,14 @@ class HeaderComponent extends React.Component {
|
||||
color: style.color,
|
||||
};
|
||||
if (options.title) iconStyle.marginRight = 5;
|
||||
if("undefined" != typeof(options.iconRotation)) {
|
||||
iconStyle.transition = "transform 0.15s ease-in-out";
|
||||
if ('undefined' != typeof options.iconRotation) {
|
||||
iconStyle.transition = 'transform 0.15s ease-in-out';
|
||||
iconStyle.transform = 'rotate(' + options.iconRotation + 'deg)';
|
||||
}
|
||||
icon = <i style={iconStyle} className={"fa " + options.iconName}></i>
|
||||
icon = <i style={iconStyle} className={'fa ' + options.iconName}></i>;
|
||||
}
|
||||
|
||||
const isEnabled = (!('enabled' in options) || options.enabled);
|
||||
const isEnabled = !('enabled' in options) || options.enabled;
|
||||
let classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
@ -139,16 +138,21 @@ class HeaderComponent extends React.Component {
|
||||
|
||||
const title = options.title ? options.title : '';
|
||||
|
||||
return <a
|
||||
className={classes.join(' ')}
|
||||
style={finalStyle}
|
||||
key={key}
|
||||
href="#"
|
||||
title={title}
|
||||
onClick={() => { if (isEnabled) options.onClick() }}
|
||||
>
|
||||
{icon}<span className="title">{title}</span>
|
||||
</a>
|
||||
return (
|
||||
<a
|
||||
className={classes.join(' ')}
|
||||
style={finalStyle}
|
||||
key={key}
|
||||
href="#"
|
||||
title={title}
|
||||
onClick={() => {
|
||||
if (isEnabled) options.onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<span className="title">{title}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
makeSearch(key, style, options, state) {
|
||||
@ -159,8 +163,8 @@ class HeaderComponent extends React.Component {
|
||||
flex: 1,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 1, // vertical alignment with buttons
|
||||
paddingBottom: 0, // vertical alignment with buttons
|
||||
paddingTop: 1, // vertical alignment with buttons
|
||||
paddingBottom: 0, // vertical alignment with buttons
|
||||
height: style.fontSize * 2,
|
||||
color: style.color,
|
||||
fontSize: style.fontSize,
|
||||
@ -191,33 +195,24 @@ class HeaderComponent extends React.Component {
|
||||
};
|
||||
|
||||
const iconName = state.searchQuery ? 'fa-times' : 'fa-search';
|
||||
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
|
||||
const icon = <i style={iconStyle} className={'fa ' + iconName}></i>;
|
||||
if (options.onQuery) this.searchOnQuery_ = options.onQuery;
|
||||
|
||||
const usageLink = !this.state.showSearchUsageLink ? null : (
|
||||
<a onClick={this.searchUsageLink_click} style={theme.urlStyle} href="#">{_('Usage')}</a>
|
||||
<a onClick={this.searchUsageLink_click} style={theme.urlStyle} href="#">
|
||||
{_('Usage')}
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={key} style={containerStyle}>
|
||||
<input
|
||||
type="text"
|
||||
style={inputStyle}
|
||||
placeholder={options.title}
|
||||
value={state.searchQuery}
|
||||
onChange={this.search_onChange}
|
||||
ref={elem => this.searchElement_ = elem}
|
||||
onFocus={this.search_onFocus}
|
||||
onBlur={this.search_onBlur}
|
||||
onKeyDown={this.search_keyDown}
|
||||
/>
|
||||
<a
|
||||
href="#"
|
||||
style={searchButton}
|
||||
onClick={this.search_onClear}
|
||||
>{icon}</a>
|
||||
<input type="text" style={inputStyle} placeholder={options.title} value={state.searchQuery} onChange={this.search_onChange} ref={elem => (this.searchElement_ = elem)} onFocus={this.search_onFocus} onBlur={this.search_onBlur} onKeyDown={this.search_keyDown} />
|
||||
<a href="#" style={searchButton} onClick={this.search_onClear}>
|
||||
{icon}
|
||||
</a>
|
||||
{usageLink}
|
||||
</div>);
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -226,7 +221,7 @@ class HeaderComponent extends React.Component {
|
||||
const showBackButton = this.props.showBackButton === undefined || this.props.showBackButton === true;
|
||||
style.height = theme.headerHeight;
|
||||
style.display = 'flex';
|
||||
style.flexDirection = 'row';
|
||||
style.flexDirection = 'row';
|
||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
@ -266,14 +261,13 @@ class HeaderComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="header" style={style}>
|
||||
{ items }
|
||||
{items}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
windowCommand: state.windowCommand,
|
||||
|
@ -1,12 +1,8 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class HelpButtonComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -19,16 +15,19 @@ class HelpButtonComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
let style = Object.assign({}, this.props.style, {color: theme.color, textDecoration: 'none'});
|
||||
const helpIconStyle = {flex:0, width: 16, height: 16, marginLeft: 10};
|
||||
let style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
|
||||
const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
|
||||
const extraProps = {};
|
||||
if (this.props.tip) extraProps['data-tip'] = this.props.tip;
|
||||
return <a href="#" style={style} onClick={this.onClick} {...extraProps}><i style={helpIconStyle} className={"fa fa-question-circle"}></i></a>
|
||||
return (
|
||||
<a href="#" style={style} onClick={this.onClick} {...extraProps}>
|
||||
<i style={helpIconStyle} className={'fa fa-question-circle'}></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
|
@ -1,9 +1,7 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
|
||||
class IconButton extends React.Component {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
@ -11,28 +9,37 @@ class IconButton extends React.Component {
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize * 1.4,
|
||||
};
|
||||
const icon = <i style={iconStyle} className={"fa " + this.props.iconName}></i>
|
||||
const icon = <i style={iconStyle} className={'fa ' + this.props.iconName}></i>;
|
||||
|
||||
const rootStyle = Object.assign({
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
padding: 10,
|
||||
width: theme.buttonMinHeight,
|
||||
height: theme.buttonMinHeight,
|
||||
boxSizing: 'border-box',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.backgroundColor,
|
||||
cursor: 'default',
|
||||
}, style);
|
||||
const rootStyle = Object.assign(
|
||||
{
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
padding: 10,
|
||||
width: theme.buttonMinHeight,
|
||||
height: theme.buttonMinHeight,
|
||||
boxSizing: 'border-box',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.backgroundColor,
|
||||
cursor: 'default',
|
||||
},
|
||||
style
|
||||
);
|
||||
|
||||
return (
|
||||
<a href="#" style={rootStyle} className="icon-button" onClick={() => { if (this.props.onClick) this.props.onClick() }}>
|
||||
{ icon }
|
||||
<a
|
||||
href="#"
|
||||
style={rootStyle}
|
||||
className="icon-button"
|
||||
onClick={() => {
|
||||
if (this.props.onClick) this.props.onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { IconButton };
|
||||
|
@ -1,8 +1,6 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
@ -10,7 +8,6 @@ const { filename, basename } = require('lib/path-utils.js');
|
||||
const { importEnex } = require('lib/import-enex');
|
||||
|
||||
class ImportScreenComponent extends React.Component {
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
@ -21,11 +18,16 @@ class ImportScreenComponent extends React.Component {
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.filePath) {
|
||||
this.setState({
|
||||
doImport: true,
|
||||
filePath: newProps.filePath,
|
||||
messages: [],
|
||||
}, () => { this.doImport() });
|
||||
this.setState(
|
||||
{
|
||||
doImport: true,
|
||||
filePath: newProps.filePath,
|
||||
messages: [],
|
||||
},
|
||||
() => {
|
||||
this.doImport();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +39,6 @@ class ImportScreenComponent extends React.Component {
|
||||
|
||||
addMessage(key, text) {
|
||||
const messages = this.state.messages.slice();
|
||||
let found = false;
|
||||
|
||||
messages.push({ key: key, text: text });
|
||||
|
||||
@ -60,15 +61,13 @@ class ImportScreenComponent extends React.Component {
|
||||
async doImport() {
|
||||
const filePath = this.props.filePath;
|
||||
const folderTitle = await Folder.findUniqueItemTitle(filename(filePath));
|
||||
const messages = this.state.messages.slice();
|
||||
|
||||
this.addMessage('start', _('New notebook "%s" will be created and file "%s" will be imported into it', folderTitle, basename(filePath)));
|
||||
|
||||
let lastProgress = '';
|
||||
let progressCount = 0;
|
||||
|
||||
const options = {
|
||||
onProgress: (progressState) => {
|
||||
onProgress: progressState => {
|
||||
let line = [];
|
||||
line.push(_('Found: %d.', progressState.loaded));
|
||||
line.push(_('Created: %d.', progressState.created));
|
||||
@ -79,15 +78,15 @@ class ImportScreenComponent extends React.Component {
|
||||
lastProgress = line.join(' ');
|
||||
this.addMessage('progress', lastProgress);
|
||||
},
|
||||
onError: (error) => {
|
||||
onError: error => {
|
||||
// Don't display the error directly because most of the time it doesn't matter
|
||||
// (eg. for weird broken HTML, but the note is still imported)
|
||||
console.warn('When importing ENEX file', error);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const folder = await Folder.save({ title: folderTitle });
|
||||
|
||||
|
||||
await importEnex(folder.id, filePath, options);
|
||||
|
||||
this.addMessage('done', _('The notes have been imported: %s', lastProgress));
|
||||
@ -118,16 +117,13 @@ class ImportScreenComponent extends React.Component {
|
||||
return (
|
||||
<div style={{}}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={messagesStyle}>
|
||||
{messageComps}
|
||||
</div>
|
||||
<div style={messagesStyle}>{messageComps}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
@ -135,4 +131,4 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const ImportScreen = connect(mapStateToProps)(ImportScreenComponent);
|
||||
|
||||
module.exports = { ImportScreen };
|
||||
module.exports = { ImportScreen };
|
||||
|
@ -1,7 +1,6 @@
|
||||
const React = require('react');
|
||||
|
||||
class ItemList extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -52,7 +51,7 @@ class ItemList extends React.Component {
|
||||
|
||||
makeItemIndexVisible(itemIndex) {
|
||||
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex + 1);
|
||||
const bottom = Math.max(0, this.state.bottomItemIndex)
|
||||
const bottom = Math.max(0, this.state.bottomItemIndex);
|
||||
|
||||
if (itemIndex >= top && itemIndex <= bottom) return;
|
||||
|
||||
@ -81,8 +80,8 @@ class ItemList extends React.Component {
|
||||
if (!this.props.itemHeight) throw new Error('itemHeight is required');
|
||||
|
||||
const blankItem = function(key, height) {
|
||||
return <div key={key} style={{height:height}}></div>
|
||||
}
|
||||
return <div key={key} style={{ height: height }}></div>;
|
||||
};
|
||||
|
||||
let itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)];
|
||||
|
||||
@ -98,10 +97,10 @@ class ItemList extends React.Component {
|
||||
|
||||
return (
|
||||
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}>
|
||||
{ itemComps }
|
||||
{itemComps}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ItemList };
|
||||
module.exports = { ItemList };
|
||||
|
@ -20,7 +20,6 @@ const VerticalResizer = require('./VerticalResizer.min');
|
||||
const PluginManager = require('lib/services/PluginManager');
|
||||
|
||||
class MainScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -87,7 +86,7 @@ class MainScreenComponent extends React.Component {
|
||||
type: 'NOTE_SET_NEW_ONE',
|
||||
item: newNote,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let commandProcessed = true;
|
||||
|
||||
@ -107,7 +106,7 @@ class MainScreenComponent extends React.Component {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
label: _('Notebook title:'),
|
||||
onClose: async (answer) => {
|
||||
onClose: async answer => {
|
||||
if (answer) {
|
||||
let folder = null;
|
||||
try {
|
||||
@ -125,14 +124,22 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'setTags') {
|
||||
const tags = await Tag.tagsByNoteId(command.noteId);
|
||||
const noteTags = tags.map((a) => { return {value: a.id, label: a.title } }).sort((a, b) => { return a.label.localeCompare(b.label); });
|
||||
const noteTags = tags
|
||||
.map(a => {
|
||||
return { value: a.id, label: a.title };
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.label.localeCompare(b.label);
|
||||
});
|
||||
const allTags = await Tag.allWithNotes();
|
||||
const tagSuggestions = allTags.map((a) => { return {value: a.id, label: a.title } });
|
||||
const tagSuggestions = allTags.map(a => {
|
||||
return { value: a.id, label: a.title };
|
||||
});
|
||||
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
@ -140,24 +147,26 @@ class MainScreenComponent extends React.Component {
|
||||
inputType: 'tags',
|
||||
value: noteTags,
|
||||
autocomplete: tagSuggestions,
|
||||
onClose: async (answer) => {
|
||||
onClose: async answer => {
|
||||
if (answer !== null) {
|
||||
const tagTitles = answer.map((a) => { return a.label.trim() });
|
||||
const tagTitles = answer.map(a => {
|
||||
return a.label.trim();
|
||||
});
|
||||
await Tag.setNoteTagsByTitles(command.noteId, tagTitles);
|
||||
}
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'renameFolder') {
|
||||
const folder = await Folder.load(command.id);
|
||||
|
||||
|
||||
if (folder) {
|
||||
this.setState({
|
||||
promptOptions: {
|
||||
label: _('Rename notebook:'),
|
||||
value: folder.title,
|
||||
onClose: async (answer) => {
|
||||
onClose: async answer => {
|
||||
if (answer !== null) {
|
||||
try {
|
||||
folder.title = answer;
|
||||
@ -167,7 +176,7 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -178,7 +187,7 @@ class MainScreenComponent extends React.Component {
|
||||
promptOptions: {
|
||||
label: _('Rename tag:'),
|
||||
value: tag.title,
|
||||
onClose: async (answer) => {
|
||||
onClose: async answer => {
|
||||
if (answer !== null) {
|
||||
try {
|
||||
tag.title = answer;
|
||||
@ -187,13 +196,12 @@ class MainScreenComponent extends React.Component {
|
||||
bridge().showErrorMessageBox(error.message);
|
||||
}
|
||||
}
|
||||
this.setState({promptOptions: null });
|
||||
}
|
||||
}
|
||||
})
|
||||
this.setState({ promptOptions: null });
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (command.name === 'search') {
|
||||
|
||||
if (!this.searchId_) this.searchId_ = uuid.create();
|
||||
|
||||
this.props.dispatch({
|
||||
@ -222,7 +230,6 @@ class MainScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else if (command.name === 'commandNoteProperties') {
|
||||
this.setState({
|
||||
notePropertiesDialogOptions: {
|
||||
@ -273,7 +280,7 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'selectTemplate') {
|
||||
@ -283,7 +290,7 @@ class MainScreenComponent extends React.Component {
|
||||
inputType: 'dropdown',
|
||||
value: this.props.templates[0], // Need to start with some value
|
||||
autocomplete: this.props.templates,
|
||||
onClose: async (answer) => {
|
||||
onClose: async answer => {
|
||||
if (answer) {
|
||||
if (command.noteType === 'note' || command.noteType === 'todo') {
|
||||
createNewNote(answer.value, command.noteType === 'todo');
|
||||
@ -297,7 +304,7 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
this.setState({ promptOptions: null });
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@ -313,7 +320,7 @@ class MainScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
styles(themeId, width, height, messageBoxVisible, isSidebarVisible, sidebarWidth, noteListWidth) {
|
||||
const styleKey = [themeId, width, height, messageBoxVisible, (+isSidebarVisible), sidebarWidth, noteListWidth].join('_');
|
||||
const styleKey = [themeId, width, height, messageBoxVisible, +isSidebarVisible, sidebarWidth, noteListWidth].join('_');
|
||||
if (styleKey === this.styleKey_) return this.styles_;
|
||||
|
||||
const theme = themeStyle(themeId);
|
||||
@ -333,7 +340,7 @@ class MainScreenComponent extends React.Component {
|
||||
alignItems: 'center',
|
||||
paddingLeft: 10,
|
||||
backgroundColor: theme.warningBackgroundColor,
|
||||
}
|
||||
};
|
||||
|
||||
this.styles_.verticalResizer = {
|
||||
width: 5,
|
||||
@ -390,10 +397,13 @@ class MainScreenComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = Object.assign({
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
}, this.props.style);
|
||||
const style = Object.assign(
|
||||
{
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
},
|
||||
this.props.style
|
||||
);
|
||||
const promptOptions = this.state.promptOptions;
|
||||
const folders = this.props.folders;
|
||||
const notes = this.props.notes;
|
||||
@ -408,47 +418,59 @@ class MainScreenComponent extends React.Component {
|
||||
title: _('Toggle sidebar'),
|
||||
iconName: 'fa-bars',
|
||||
iconRotation: this.props.sidebarVisibility ? 0 : 90,
|
||||
onClick: () => { this.doCommand({ name: 'toggleSidebar'}) }
|
||||
onClick: () => {
|
||||
this.doCommand({ name: 'toggleSidebar' });
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('New note'),
|
||||
iconName: 'fa-file-o',
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newNote' }) },
|
||||
onClick: () => {
|
||||
this.doCommand({ name: 'newNote' });
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('New to-do'),
|
||||
iconName: 'fa-check-square-o',
|
||||
enabled: !!folders.length && !onConflictFolder,
|
||||
onClick: () => { this.doCommand({ name: 'newTodo' }) },
|
||||
onClick: () => {
|
||||
this.doCommand({ name: 'newTodo' });
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('New notebook'),
|
||||
iconName: 'fa-book',
|
||||
onClick: () => { this.doCommand({ name: 'newNotebook' }) },
|
||||
onClick: () => {
|
||||
this.doCommand({ name: 'newNotebook' });
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('Layout'),
|
||||
iconName: 'fa-columns',
|
||||
enabled: !!notes.length,
|
||||
onClick: () => { this.doCommand({ name: 'toggleVisiblePanes' }) },
|
||||
onClick: () => {
|
||||
this.doCommand({ name: 'toggleVisiblePanes' });
|
||||
},
|
||||
});
|
||||
|
||||
headerItems.push({
|
||||
title: _('Search...'),
|
||||
iconName: 'fa-search',
|
||||
onQuery: (query) => { this.doCommand({ name: 'search', query: query }) },
|
||||
onQuery: query => {
|
||||
this.doCommand({ name: 'search', query: query });
|
||||
},
|
||||
type: 'search',
|
||||
});
|
||||
|
||||
if (!this.promptOnClose_) {
|
||||
this.promptOnClose_ = (answer, buttonType) => {
|
||||
return this.state.promptOptions.onClose(answer, buttonType);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onViewDisabledItemsClick = () => {
|
||||
@ -456,36 +478,58 @@ class MainScreenComponent extends React.Component {
|
||||
type: 'NAV_GO',
|
||||
routeName: 'Status',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onViewMasterKeysClick = () => {
|
||||
this.props.dispatch({
|
||||
type: 'NAV_GO',
|
||||
routeName: 'EncryptionConfig',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let messageComp = null;
|
||||
|
||||
if (messageBoxVisible) {
|
||||
let msg = null;
|
||||
if (this.props.hasDisabledSyncItems) {
|
||||
msg = <span>{_('Some items cannot be synchronised.')} <a href="#" onClick={() => { onViewDisabledItemsClick() }}>{_('View them now')}</a></span>
|
||||
msg = (
|
||||
<span>
|
||||
{_('Some items cannot be synchronised.')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => {
|
||||
onViewDisabledItemsClick();
|
||||
}}
|
||||
>
|
||||
{_('View them now')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
} else if (this.props.showMissingMasterKeyMessage) {
|
||||
msg = <span>{_('One or more master keys need a password.')} <a href="#" onClick={() => { onViewMasterKeysClick() }}>{_('Set the password')}</a></span>
|
||||
msg = (
|
||||
<span>
|
||||
{_('One or more master keys need a password.')}{' '}
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => {
|
||||
onViewMasterKeysClick();
|
||||
}}
|
||||
>
|
||||
{_('Set the password')}
|
||||
</a>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
messageComp = (
|
||||
<div style={styles.messageBox}>
|
||||
<span style={theme.textStyle}>
|
||||
{msg}
|
||||
</span>
|
||||
<span style={theme.textStyle}>{msg}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const dialogInfo = PluginManager.instance().pluginDialogToShow(this.props.plugins);
|
||||
const pluginDialog = !dialogInfo ? null : <dialogInfo.Dialog {...dialogInfo.props}/>;
|
||||
const pluginDialog = !dialogInfo ? null : <dialogInfo.Dialog {...dialogInfo.props} />;
|
||||
|
||||
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
|
||||
|
||||
@ -495,41 +539,25 @@ class MainScreenComponent extends React.Component {
|
||||
<div style={style}>
|
||||
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
|
||||
|
||||
{ notePropertiesDialogOptions.visible && <NotePropertiesDialog
|
||||
theme={this.props.theme}
|
||||
noteId={notePropertiesDialogOptions.noteId}
|
||||
onClose={this.notePropertiesDialog_close}
|
||||
onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick}
|
||||
/> }
|
||||
{notePropertiesDialogOptions.visible && <NotePropertiesDialog theme={this.props.theme} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
|
||||
|
||||
<PromptDialog
|
||||
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
|
||||
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}
|
||||
theme={this.props.theme}
|
||||
style={styles.prompt}
|
||||
onClose={this.promptOnClose_}
|
||||
label={promptOptions ? promptOptions.label : ''}
|
||||
description={promptOptions ? promptOptions.description : null}
|
||||
visible={!!this.state.promptOptions}
|
||||
buttons={promptOptions && ('buttons' in promptOptions) ? promptOptions.buttons : null}
|
||||
inputType={promptOptions && ('inputType' in promptOptions) ? promptOptions.inputType : null} />
|
||||
<PromptDialog autocomplete={promptOptions && 'autocomplete' in promptOptions ? promptOptions.autocomplete : null} defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''} theme={this.props.theme} style={styles.prompt} onClose={this.promptOnClose_} label={promptOptions ? promptOptions.label : ''} description={promptOptions ? promptOptions.description : null} visible={!!this.state.promptOptions} buttons={promptOptions && 'buttons' in promptOptions ? promptOptions.buttons : null} inputType={promptOptions && 'inputType' in promptOptions ? promptOptions.inputType : null} />
|
||||
|
||||
<Header style={styles.header} showBackButton={false} items={headerItems} />
|
||||
{messageComp}
|
||||
<SideBar style={styles.sideBar} />
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag}/>
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag} />
|
||||
<NoteList style={styles.noteList} />
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag}/>
|
||||
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} noteDevToolsVisible={this.props.noteDevToolsVisible}/>
|
||||
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag} />
|
||||
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} noteDevToolsVisible={this.props.noteDevToolsVisible} />
|
||||
|
||||
{pluginDialog}
|
||||
{pluginDialog}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
windowCommand: state.windowCommand,
|
||||
|
@ -1,10 +1,9 @@
|
||||
const React = require('react'); const Component = React.Component;
|
||||
const React = require('react');
|
||||
const Component = React.Component;
|
||||
const { connect } = require('react-redux');
|
||||
const { app } = require('../app.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class NavigatorComponent extends Component {
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.route) {
|
||||
const screenInfo = this.props.screens[newProps.route.routeName];
|
||||
@ -18,7 +17,10 @@ class NavigatorComponent extends Component {
|
||||
|
||||
updateWindowTitle(title) {
|
||||
try {
|
||||
if (bridge().window()) bridge().window().setTitle(title);
|
||||
if (bridge().window())
|
||||
bridge()
|
||||
.window()
|
||||
.setTitle(title);
|
||||
} catch (error) {
|
||||
console.warn('updateWindowTitle', error);
|
||||
}
|
||||
@ -39,19 +41,16 @@ class NavigatorComponent extends Component {
|
||||
|
||||
return (
|
||||
<div style={this.props.style}>
|
||||
<Screen style={screenStyle} {...screenProps}/>
|
||||
<Screen style={screenStyle} {...screenProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Navigator = connect(
|
||||
(state) => {
|
||||
return {
|
||||
route: state.route,
|
||||
};
|
||||
}
|
||||
)(NavigatorComponent)
|
||||
const Navigator = connect(state => {
|
||||
return {
|
||||
route: state.route,
|
||||
};
|
||||
})(NavigatorComponent);
|
||||
|
||||
module.exports = { Navigator };
|
||||
module.exports = { Navigator };
|
||||
|
@ -7,20 +7,14 @@ const BaseModel = require('lib/BaseModel');
|
||||
const markJsUtils = require('lib/markJsUtils');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const eventManager = require('../eventManager');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
||||
const Search = require('lib/models/Search');
|
||||
const { stateUtils } = require('lib/reducer');
|
||||
const Mark = require('mark.js/dist/mark.min.js');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
const Note = require('lib/models/Note');
|
||||
const NoteListUtils = require('./utils/NoteListUtils');
|
||||
const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils');
|
||||
|
||||
class NoteListComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -116,9 +110,9 @@ class NoteListComponent extends React.Component {
|
||||
id: item.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onDragStart = (event) => {
|
||||
const onDragStart = event => {
|
||||
let noteIds = [];
|
||||
|
||||
// Here there is two cases:
|
||||
@ -132,21 +126,21 @@ class NoteListComponent extends React.Component {
|
||||
}
|
||||
|
||||
if (!noteIds.length) return;
|
||||
|
||||
|
||||
event.dataTransfer.setDragImage(new Image(), 1, 1);
|
||||
event.dataTransfer.clearData();
|
||||
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
|
||||
}
|
||||
};
|
||||
|
||||
const onCheckboxClick = async (event) => {
|
||||
const onCheckboxClick = async event => {
|
||||
const checked = event.target.checked;
|
||||
const newNote = {
|
||||
id: item.id,
|
||||
todo_completed: checked ? time.unixMs() : 0,
|
||||
}
|
||||
};
|
||||
await Note.save(newNote, { userSideValidation: true });
|
||||
eventManager.emit('todoToggle', { noteId: item.id });
|
||||
}
|
||||
};
|
||||
|
||||
const hPadding = 10;
|
||||
|
||||
@ -167,11 +161,18 @@ class NoteListComponent extends React.Component {
|
||||
|
||||
// Setting marginBottom = 1 because it makes the checkbox looks more centered, at least on Windows
|
||||
// but don't know how it will look in other OSes.
|
||||
const checkbox = item.is_todo ?
|
||||
<div style={{display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding}}>
|
||||
<input style={{margin:0, marginBottom:1}} type="checkbox" defaultChecked={!!item.todo_completed} onClick={(event) => { onCheckboxClick(event, item) }}/>
|
||||
const checkbox = item.is_todo ? (
|
||||
<div style={{ display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding }}>
|
||||
<input
|
||||
style={{ margin: 0, marginBottom: 1 }}
|
||||
type="checkbox"
|
||||
defaultChecked={!!item.todo_completed}
|
||||
onClick={event => {
|
||||
onCheckboxClick(event, item);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
: null;
|
||||
) : null;
|
||||
|
||||
let listItemTitleStyle = Object.assign({}, this.style().listItemTitle);
|
||||
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
|
||||
@ -204,41 +205,43 @@ class NoteListComponent extends React.Component {
|
||||
// with `textContent` so it cannot contain any XSS attacks. We use this feature because
|
||||
// mark.js can only deal with DOM elements.
|
||||
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
|
||||
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>
|
||||
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>;
|
||||
} else {
|
||||
titleComp = <span>{displayTitle}</span>
|
||||
titleComp = <span>{displayTitle}</span>;
|
||||
}
|
||||
|
||||
const watchedIconStyle = {
|
||||
paddingRight: 4,
|
||||
color: theme.color,
|
||||
};
|
||||
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : (
|
||||
<i style={watchedIconStyle} className={"fa fa-external-link"}></i>
|
||||
);
|
||||
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : <i style={watchedIconStyle} className={'fa fa-external-link'}></i>;
|
||||
|
||||
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
|
||||
const ref = this.itemAnchorRefs_[item.id];
|
||||
|
||||
// Need to include "todo_completed" in key so that checkbox is updated when
|
||||
// item is changed via sync.
|
||||
return <div key={item.id + '_' + item.todo_completed} style={style}>
|
||||
{checkbox}
|
||||
<a
|
||||
ref={ref}
|
||||
className="list-item"
|
||||
onContextMenu={(event) => this.itemContextMenu(event)}
|
||||
href="#"
|
||||
draggable={true}
|
||||
style={listItemTitleStyle}
|
||||
onClick={(event) => { onTitleClick(event, item) }}
|
||||
onDragStart={(event) => onDragStart(event) }
|
||||
data-id={item.id}
|
||||
>
|
||||
{watchedIcon}
|
||||
{titleComp}
|
||||
</a>
|
||||
</div>
|
||||
// item is changed via sync.
|
||||
return (
|
||||
<div key={item.id + '_' + item.todo_completed} style={style}>
|
||||
{checkbox}
|
||||
<a
|
||||
ref={ref}
|
||||
className="list-item"
|
||||
onContextMenu={event => this.itemContextMenu(event)}
|
||||
href="#"
|
||||
draggable={true}
|
||||
style={listItemTitleStyle}
|
||||
onClick={event => {
|
||||
onTitleClick(event, item);
|
||||
}}
|
||||
onDragStart={event => onDragStart(event)}
|
||||
data-id={item.id}
|
||||
>
|
||||
{watchedIcon}
|
||||
{titleComp}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
itemAnchorRef(itemId) {
|
||||
@ -279,7 +282,7 @@ class NoteListComponent extends React.Component {
|
||||
if (this.props.notes[i].id === id) {
|
||||
this.itemListRef.current.makeItemIndexVisible(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -288,7 +291,8 @@ class NoteListComponent extends React.Component {
|
||||
const keyCode = event.keyCode;
|
||||
const noteIds = this.props.selectedNoteIds;
|
||||
|
||||
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
|
||||
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) {
|
||||
// DOWN / UP
|
||||
const noteId = noteIds[0];
|
||||
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
|
||||
const inc = keyCode === 38 ? -1 : +1;
|
||||
@ -312,12 +316,14 @@ class NoteListComponent extends React.Component {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (noteIds.length && (keyCode === 46 || (keyCode === 8 && event.metaKey))) { // DELETE / CMD+Backspace
|
||||
if (noteIds.length && (keyCode === 46 || (keyCode === 8 && event.metaKey))) {
|
||||
// DELETE / CMD+Backspace
|
||||
event.preventDefault();
|
||||
await NoteListUtils.confirmDeleteNotes(noteIds);
|
||||
}
|
||||
|
||||
if (noteIds.length && keyCode === 32) { // SPACE
|
||||
if (noteIds.length && keyCode === 32) {
|
||||
// SPACE
|
||||
event.preventDefault();
|
||||
|
||||
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
|
||||
@ -332,7 +338,8 @@ class NoteListComponent extends React.Component {
|
||||
this.focusNoteId_(todos[0].id);
|
||||
}
|
||||
|
||||
if (keyCode === 9) { // TAB
|
||||
if (keyCode === 9) {
|
||||
// TAB
|
||||
event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
@ -361,7 +368,7 @@ class NoteListComponent extends React.Component {
|
||||
this.focusItemIID_ = setInterval(() => {
|
||||
if (this.itemAnchorRef(noteId)) {
|
||||
this.itemAnchorRef(noteId).focus();
|
||||
clearInterval(this.focusItemIID_)
|
||||
clearInterval(this.focusItemIID_);
|
||||
this.focusItemIID_ = null;
|
||||
}
|
||||
}, 10);
|
||||
@ -381,37 +388,29 @@ class NoteListComponent extends React.Component {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
let notes = this.props.notes.slice();
|
||||
|
||||
|
||||
if (!notes.length) {
|
||||
const padding = 10;
|
||||
const emptyDivStyle = Object.assign({
|
||||
padding: padding + 'px',
|
||||
fontSize: theme.fontSize,
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
fontFamily: theme.fontFamily,
|
||||
}, style);
|
||||
const emptyDivStyle = Object.assign(
|
||||
{
|
||||
padding: padding + 'px',
|
||||
fontSize: theme.fontSize,
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
fontFamily: theme.fontFamily,
|
||||
},
|
||||
style
|
||||
);
|
||||
emptyDivStyle.width = emptyDivStyle.width - padding * 2;
|
||||
emptyDivStyle.height = emptyDivStyle.height - padding * 2;
|
||||
return <div style={emptyDivStyle}>{ this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>
|
||||
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemList
|
||||
ref={this.itemListRef}
|
||||
itemHeight={this.style().listItem.height}
|
||||
className={"note-list"}
|
||||
items={notes}
|
||||
style={style}
|
||||
itemRenderer={this.itemRenderer}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
);
|
||||
return <ItemList ref={this.itemListRef} itemHeight={this.style().listItem.height} className={'note-list'} items={notes} style={style} itemRenderer={this.itemRenderer} onKeyDown={this.onKeyDown} />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
notes: state.notes,
|
||||
folders: state.folders,
|
||||
@ -427,4 +426,4 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const NoteList = connect(mapStateToProps)(NoteListComponent);
|
||||
|
||||
module.exports = { NoteList };
|
||||
module.exports = { NoteList };
|
||||
|
@ -1,7 +1,5 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const moment = require('moment');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const Datetime = require('react-datetime');
|
||||
@ -10,7 +8,6 @@ const formatcoords = require('formatcoords');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
class NotePropertiesDialog extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -129,9 +126,9 @@ class NotePropertiesDialog extends React.Component {
|
||||
border: '1px solid',
|
||||
borderColor: theme.dividerColor,
|
||||
};
|
||||
|
||||
|
||||
this.styles_.input = {
|
||||
display:'inline-block',
|
||||
display: 'inline-block',
|
||||
color: theme.color,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
border: '1px solid',
|
||||
@ -205,68 +202,86 @@ class NotePropertiesDialog extends React.Component {
|
||||
newFormNote[this.state.editedKey] = this.state.editedValue;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
formNote: newFormNote,
|
||||
editedKey: null,
|
||||
editedValue: null
|
||||
}, () => { resolve() });
|
||||
this.setState(
|
||||
{
|
||||
formNote: newFormNote,
|
||||
editedKey: null,
|
||||
editedValue: null,
|
||||
},
|
||||
() => {
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async cancelProperty() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.okButton.current.focus();
|
||||
this.setState({
|
||||
editedKey: null,
|
||||
editedValue: null
|
||||
}, () => { resolve() });
|
||||
this.setState(
|
||||
{
|
||||
editedKey: null,
|
||||
editedValue: null,
|
||||
},
|
||||
() => {
|
||||
resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
createNoteField(key, value) {
|
||||
const styles = this.styles(this.props.theme);
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const labelComp = <label style={Object.assign({}, theme.textStyle, {marginRight: '1em', width: '6em', display:'inline-block', fontWeight: 'bold'})}>{this.formatLabel(key)}</label>;
|
||||
const labelComp = <label style={Object.assign({}, theme.textStyle, { marginRight: '1em', width: '6em', display: 'inline-block', fontWeight: 'bold' })}>{this.formatLabel(key)}</label>;
|
||||
let controlComp = null;
|
||||
let editComp = null;
|
||||
let editCompHandler = null;
|
||||
let editCompIcon = null;
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
const onKeyDown = event => {
|
||||
if (event.keyCode === 13) {
|
||||
this.saveProperty();
|
||||
} else if (event.keyCode === 27) {
|
||||
this.cancelProperty();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (this.state.editedKey === key) {
|
||||
if (key.indexOf('_time') >= 0) {
|
||||
controlComp = (
|
||||
<Datetime
|
||||
ref="editField"
|
||||
defaultValue={value}
|
||||
dateFormat={time.dateFormat()}
|
||||
timeFormat={time.timeFormat()}
|
||||
inputProps={{
|
||||
onKeyDown: event => onKeyDown(event, key),
|
||||
style: styles.input,
|
||||
}}
|
||||
onChange={momentObject => {
|
||||
this.setState({ editedValue: momentObject });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
controlComp = <Datetime
|
||||
ref="editField"
|
||||
defaultValue={value}
|
||||
dateFormat={time.dateFormat()}
|
||||
timeFormat={time.timeFormat()}
|
||||
inputProps={{
|
||||
onKeyDown: (event) => onKeyDown(event, key),
|
||||
style: styles.input
|
||||
}}
|
||||
onChange={(momentObject) => {this.setState({ editedValue: momentObject })}}
|
||||
/>
|
||||
|
||||
editCompHandler = () => {this.saveProperty()};
|
||||
editCompHandler = () => {
|
||||
this.saveProperty();
|
||||
};
|
||||
editCompIcon = 'fa-save';
|
||||
} else {
|
||||
|
||||
controlComp = <input
|
||||
defaultValue={value}
|
||||
type="text"
|
||||
ref="editField"
|
||||
onChange={(event) => {this.setState({ editedValue: event.target.value })}}
|
||||
onKeyDown={(event) => onKeyDown(event)}
|
||||
style={styles.input}
|
||||
/>
|
||||
controlComp = (
|
||||
<input
|
||||
defaultValue={value}
|
||||
type="text"
|
||||
ref="editField"
|
||||
onChange={event => {
|
||||
this.setState({ editedValue: event.target.value });
|
||||
}}
|
||||
onKeyDown={event => onKeyDown(event)}
|
||||
style={styles.input}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let displayedValue = value;
|
||||
@ -287,15 +302,25 @@ class NotePropertiesDialog extends React.Component {
|
||||
const ll = this.latLongFromLocation(value);
|
||||
url = Note.geoLocationUrlFromLatLong(ll.latitude, ll.longitude);
|
||||
}
|
||||
controlComp = <a href="#" onClick={() => bridge().openExternal(url)} style={theme.urlStyle}>{displayedValue}</a>
|
||||
controlComp = (
|
||||
<a href="#" onClick={() => bridge().openExternal(url)} style={theme.urlStyle}>
|
||||
{displayedValue}
|
||||
</a>
|
||||
);
|
||||
} else if (key === 'revisionsLink') {
|
||||
controlComp = <a href="#" onClick={this.revisionsLink_click} style={theme.urlStyle}>{_('Previous versions of this note')}</a>
|
||||
controlComp = (
|
||||
<a href="#" onClick={this.revisionsLink_click} style={theme.urlStyle}>
|
||||
{_('Previous versions of this note')}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
controlComp = <div style={Object.assign({}, theme.textStyle, {display: 'inline-block'})}>{displayedValue}</div>
|
||||
controlComp = <div style={Object.assign({}, theme.textStyle, { display: 'inline-block' })}>{displayedValue}</div>;
|
||||
}
|
||||
|
||||
if (['id', 'revisionsLink', 'markup_language'].indexOf(key) < 0) {
|
||||
editCompHandler = () => {this.editPropertyButtonClick(key, value)};
|
||||
editCompHandler = () => {
|
||||
this.editPropertyButtonClick(key, value);
|
||||
};
|
||||
editCompIcon = 'fa-edit';
|
||||
}
|
||||
}
|
||||
@ -303,16 +328,16 @@ class NotePropertiesDialog extends React.Component {
|
||||
if (editCompHandler) {
|
||||
editComp = (
|
||||
<a href="#" onClick={editCompHandler} style={styles.editPropertyButton}>
|
||||
<i className={'fa ' + editCompIcon} aria-hidden="true" style={{ marginLeft: '.5em'}}></i>
|
||||
<i className={'fa ' + editCompIcon} aria-hidden="true" style={{ marginLeft: '.5em' }}></i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} style={this.styles_.controlBox} className="note-property-box">
|
||||
{ labelComp }
|
||||
{ controlComp }
|
||||
{ editComp }
|
||||
{labelComp}
|
||||
{controlComp}
|
||||
{editComp}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -325,7 +350,7 @@ class NotePropertiesDialog extends React.Component {
|
||||
formatValue(key, note) {
|
||||
if (key === 'location') {
|
||||
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
||||
const dms = formatcoords(Number(note.latitude), Number(note.longitude))
|
||||
const dms = formatcoords(Number(note.latitude), Number(note.longitude));
|
||||
return dms.format('DDMMss', { decimalPlaces: 0 });
|
||||
}
|
||||
|
||||
@ -337,24 +362,21 @@ class NotePropertiesDialog extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const styles = this.styles(this.props.theme);
|
||||
const formNote = this.state.formNote;
|
||||
|
||||
const buttonComps = [];
|
||||
buttonComps.push(
|
||||
<button
|
||||
key="ok"
|
||||
style={styles.button}
|
||||
onClick={this.okButton_click}
|
||||
ref={this.okButton}
|
||||
onKeyDown={this.onKeyDown}
|
||||
>
|
||||
<button key="ok" style={styles.button} onClick={this.okButton_click} ref={this.okButton} onKeyDown={this.onKeyDown}>
|
||||
{_('Apply')}
|
||||
</button>
|
||||
);
|
||||
buttonComps.push(<button key="cancel" style={styles.button} onClick={this.cancelButton_click}>{_('Cancel')}</button>);
|
||||
buttonComps.push(
|
||||
<button key="cancel" style={styles.button} onClick={this.cancelButton_click}>
|
||||
{_('Cancel')}
|
||||
</button>
|
||||
);
|
||||
|
||||
const noteComps = [];
|
||||
|
||||
@ -371,14 +393,11 @@ class NotePropertiesDialog extends React.Component {
|
||||
<div style={theme.dialogBox}>
|
||||
<div style={theme.dialogTitle}>{_('Note properties')}</div>
|
||||
<div>{noteComps}</div>
|
||||
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
||||
{buttonComps}
|
||||
</div>
|
||||
<div style={{ textAlign: 'right', marginTop: 10 }}>{buttonComps}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = NotePropertiesDialog;
|
||||
|
@ -6,6 +6,7 @@ const NoteTextViewer = require('./NoteTextViewer.min');
|
||||
const HelpButton = require('./HelpButton.min');
|
||||
const BaseModel = require('lib/BaseModel');
|
||||
const Revision = require('lib/models/Revision');
|
||||
const Note = require('lib/models/Note');
|
||||
const Setting = require('lib/models/Setting');
|
||||
const RevisionService = require('lib/services/RevisionService');
|
||||
const shared = require('lib/components/shared/note-screen-shared.js');
|
||||
@ -15,7 +16,6 @@ const ReactTooltip = require('react-tooltip');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
|
||||
class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -55,13 +55,16 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
// this.viewerRef_.current.wrappedInstance.openDevTools();
|
||||
|
||||
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
|
||||
|
||||
this.setState({
|
||||
revisions: revisions,
|
||||
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
|
||||
}, () => {
|
||||
this.reloadNote();
|
||||
});
|
||||
|
||||
this.setState(
|
||||
{
|
||||
revisions: revisions,
|
||||
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
|
||||
},
|
||||
() => {
|
||||
this.reloadNote();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async importButton_onClick() {
|
||||
@ -82,11 +85,14 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
if (!value) {
|
||||
if (this.props.onBack) this.props.onBack();
|
||||
} else {
|
||||
this.setState({
|
||||
currentRevId: value,
|
||||
}, () => {
|
||||
this.reloadNote();
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
currentRevId: value,
|
||||
},
|
||||
() => {
|
||||
this.reloadNote();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +123,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
resources: await shared.attachedResources(noteBody),
|
||||
});
|
||||
|
||||
this.viewerRef_.current.wrappedInstance.send('setHtml', result.html, { cssFiles: result.cssFiles });
|
||||
this.viewerRef_.current.wrappedInstance.send('setHtml', result.html, { cssFiles: result.cssFiles });
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -130,45 +136,45 @@ class NoteRevisionViewerComponent extends React.PureComponent {
|
||||
const rev = revs[i];
|
||||
const stats = Revision.revisionPatchStatsText(rev);
|
||||
|
||||
revisionListItems.push(<option
|
||||
key={rev.id}
|
||||
value={rev.id}
|
||||
>{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}</option>);
|
||||
revisionListItems.push(
|
||||
<option key={rev.id} value={rev.id}>
|
||||
{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
const restoreButtonTitle = _('Restore');
|
||||
const helpMessage = _('Click "%s" to restore the note. It will be copied in the notebook named "%s". The current version of the note will not be replaced or modified.', restoreButtonTitle, RevisionService.instance().restoreFolderTitle());
|
||||
|
||||
const titleInput = (
|
||||
<div style={{display:'flex', flexDirection: 'row', alignItems:'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom:10}}>
|
||||
<button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>{'⬅ ' + _('Back')}</button>
|
||||
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''}/>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom: 10 }}>
|
||||
<button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>
|
||||
{'⬅ ' + _('Back')}
|
||||
</button>
|
||||
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''} />
|
||||
<select disabled={!this.state.revisions.length} value={this.state.currentRevId} style={style.revisionList} onChange={this.revisionList_onChange}>
|
||||
{revisionListItems}
|
||||
</select>
|
||||
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>{restoreButtonTitle}</button>
|
||||
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick}/>
|
||||
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>
|
||||
{restoreButtonTitle}
|
||||
</button>
|
||||
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const viewer = <NoteTextViewer
|
||||
viewerStyle={{display:'flex', flex:1}}
|
||||
ref={this.viewerRef_}
|
||||
onDomReady={this.viewer_domReady}
|
||||
/>
|
||||
const viewer = <NoteTextViewer viewerStyle={{ display: 'flex', flex: 1 }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} />;
|
||||
|
||||
return (
|
||||
<div style={style.root}>
|
||||
{titleInput}
|
||||
{viewer}
|
||||
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip"/>
|
||||
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
@ -176,4 +182,4 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const NoteRevisionViewer = connect(mapStateToProps)(NoteRevisionViewerComponent);
|
||||
|
||||
module.exports = NoteRevisionViewer;
|
||||
module.exports = NoteRevisionViewer;
|
||||
|
@ -4,7 +4,6 @@ const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class NoteSearchBarComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -54,14 +53,12 @@ class NoteSearchBarComponent extends React.Component {
|
||||
color: theme.color,
|
||||
};
|
||||
|
||||
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
|
||||
const icon = <i style={iconStyle} className={'fa ' + iconName}></i>;
|
||||
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
style={searchButton}
|
||||
onClick={clickHandler}
|
||||
>{icon}</a>
|
||||
<a href="#" style={searchButton} onClick={clickHandler}>
|
||||
{icon}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -72,7 +69,8 @@ class NoteSearchBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
searchInput_keyDown(event) {
|
||||
if (event.keyCode === 13) { // ENTER
|
||||
if (event.keyCode === 13) {
|
||||
// ENTER
|
||||
event.preventDefault();
|
||||
|
||||
if (!event.shiftKey) {
|
||||
@ -82,7 +80,8 @@ class NoteSearchBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (event.keyCode === 27) { // ESCAPE
|
||||
if (event.keyCode === 27) {
|
||||
// ESCAPE
|
||||
event.preventDefault();
|
||||
|
||||
if (this.props.onClose) this.props.onClose();
|
||||
@ -110,32 +109,34 @@ class NoteSearchBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const closeButton = this.buttonIconComponent('fa-times', this.closeButton_click);
|
||||
const previousButton = this.buttonIconComponent('fa-chevron-up', this.previousButton_click);
|
||||
const nextButton = this.buttonIconComponent('fa-chevron-down', this.nextButton_click);
|
||||
|
||||
return (
|
||||
<div style={this.props.style}>
|
||||
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{ closeButton }
|
||||
<input placeholder={_('Search...')} value={this.state.query} onChange={this.searchInput_change} onKeyDown={this.searchInput_keyDown} ref="searchInput" type="text" style={{width: 200, marginRight: 5}}></input>
|
||||
{ nextButton }
|
||||
{ previousButton }
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
{closeButton}
|
||||
<input placeholder={_('Search...')} value={this.state.query} onChange={this.searchInput_change} onKeyDown={this.searchInput_keyDown} ref="searchInput" type="text" style={{ width: 200, marginRight: 5 }}></input>
|
||||
{nextButton}
|
||||
{previousButton}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const NoteSearchBar = connect(mapStateToProps, null, null, { withRef: true })(NoteSearchBarComponent);
|
||||
const NoteSearchBar = connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{ withRef: true }
|
||||
)(NoteSearchBarComponent);
|
||||
|
||||
module.exports = NoteSearchBar;
|
||||
module.exports = NoteSearchBar;
|
||||
|
@ -2,15 +2,11 @@ const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class NoteStatusBarComponent extends React.Component {
|
||||
|
||||
style() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
const itemHeight = 34;
|
||||
|
||||
let style = {
|
||||
root: Object.assign({}, theme.textStyle, {
|
||||
backgroundColor: theme.backgroundColor,
|
||||
@ -22,18 +18,12 @@ class NoteStatusBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = this.props.style;
|
||||
const note = this.props.note;
|
||||
|
||||
return (
|
||||
<div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>
|
||||
);
|
||||
return <div style={this.style().root}>{time.formatMsToLocal(note.user_updated_time)}</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
// notes: state.notes,
|
||||
// folders: state.folders,
|
||||
@ -44,4 +34,4 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const NoteStatusBar = connect(mapStateToProps)(NoteStatusBarComponent);
|
||||
|
||||
module.exports = { NoteStatusBar };
|
||||
module.exports = { NoteStatusBar };
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,7 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class NoteTextViewerComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -49,7 +46,7 @@ class NoteTextViewerComponent extends React.Component {
|
||||
|
||||
let isAlreadyReady = false;
|
||||
try {
|
||||
isAlreadyReady = !this.webviewRef_.current.isLoading()
|
||||
isAlreadyReady = !this.webviewRef_.current.isLoading();
|
||||
} catch (error) {
|
||||
// Ignore - it means the view has not started loading, and the DOM ready event has not been emitted yet
|
||||
// Error is "The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called."
|
||||
@ -111,14 +108,14 @@ class NoteTextViewerComponent extends React.Component {
|
||||
return this.webviewRef_.current.getWebContents().printToPDF(options, callback);
|
||||
}
|
||||
|
||||
print(options = {}) {
|
||||
print() {
|
||||
// In Electron 4x, print is broken so need to use this hack:
|
||||
// https://github.com/electron/electron/issues/16219#issuecomment-451454948
|
||||
// Note that this is not a perfect workaround since it means the options are ignored
|
||||
// In particular it means that background images and colours won't be printed (printBackground property will be ignored)
|
||||
|
||||
// return this.webviewRef_.current.getWebContents().print({});
|
||||
return this.webviewRef_.current.getWebContents().executeJavaScript("window.print()")
|
||||
return this.webviewRef_.current.getWebContents().executeJavaScript('window.print()');
|
||||
}
|
||||
|
||||
openDevTools() {
|
||||
@ -138,23 +135,21 @@ class NoteTextViewerComponent extends React.Component {
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
render() {
|
||||
return <webview
|
||||
ref={this.webviewRef_}
|
||||
style={this.props.viewerStyle}
|
||||
preload="gui/note-viewer/preload.js"
|
||||
src="gui/note-viewer/index.html"
|
||||
webpreferences="contextIsolation"
|
||||
/>
|
||||
return <webview ref={this.webviewRef_} style={this.props.viewerStyle} preload="gui/note-viewer/preload.js" src="gui/note-viewer/index.html" webpreferences="contextIsolation" />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
};
|
||||
|
||||
const NoteTextViewer = connect(mapStateToProps, null, null, { withRef: true })(NoteTextViewerComponent);
|
||||
const NoteTextViewer = connect(
|
||||
mapStateToProps,
|
||||
null,
|
||||
null,
|
||||
{ withRef: true }
|
||||
)(NoteTextViewerComponent);
|
||||
|
||||
module.exports = NoteTextViewer;
|
||||
module.exports = NoteTextViewer;
|
||||
|
@ -7,7 +7,6 @@ const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
|
||||
class OneDriveLoginScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.webview_ = null;
|
||||
@ -36,8 +35,8 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
|
||||
webview_domReady() {
|
||||
this.setState({ webviewReady: true });
|
||||
|
||||
this.webview_.addEventListener('did-navigate', async (event) => {
|
||||
|
||||
this.webview_.addEventListener('did-navigate', async event => {
|
||||
const url = event.url;
|
||||
|
||||
if (this.authCode_) return;
|
||||
@ -50,11 +49,14 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
this.authCode_ = parsedUrl.query.code;
|
||||
|
||||
try {
|
||||
await reg.syncTarget().api().execTokenRequest(this.authCode_, this.redirectUrl(), true);
|
||||
await reg
|
||||
.syncTarget()
|
||||
.api()
|
||||
.execTokenRequest(this.authCode_, this.redirectUrl(), true);
|
||||
this.props.dispatch({ type: 'NAV_BACK' });
|
||||
reg.scheduleSync(0);
|
||||
} catch (error) {
|
||||
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'));
|
||||
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + '\n\n' + url.match(/.{1,64}/g).join('\n'));
|
||||
}
|
||||
|
||||
this.authCode_ = null;
|
||||
@ -62,11 +64,17 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
startUrl() {
|
||||
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
|
||||
return reg
|
||||
.syncTarget()
|
||||
.api()
|
||||
.authCodeUrl(this.redirectUrl());
|
||||
}
|
||||
|
||||
redirectUrl() {
|
||||
return reg.syncTarget().api().nativeClientRedirectUrl();
|
||||
return reg
|
||||
.syncTarget()
|
||||
.api()
|
||||
.nativeClientRedirectUrl();
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -94,14 +102,13 @@ class OneDriveLoginScreenComponent extends React.Component {
|
||||
return (
|
||||
<div>
|
||||
<Header style={headerStyle} buttons={headerButtons} />
|
||||
<webview src={this.startUrl()} style={webviewStyle} nodeintegration="1" ref={elem => this.webview_ = elem} />
|
||||
<webview src={this.startUrl()} style={webviewStyle} nodeintegration="1" ref={elem => (this.webview_ = elem)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
};
|
||||
|
@ -1,7 +1,5 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const moment = require('moment');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const Datetime = require('react-datetime');
|
||||
@ -10,7 +8,6 @@ const Select = require('react-select').default;
|
||||
const makeAnimated = require('react-select/lib/animated').default;
|
||||
|
||||
class PromptDialog extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@ -103,51 +100,58 @@ class PromptDialog extends React.Component {
|
||||
};
|
||||
|
||||
this.styles_.select = {
|
||||
control: (provided) => (Object.assign(provided, {
|
||||
minWidth: width * 0.2,
|
||||
maxWidth: width * 0.5,
|
||||
})),
|
||||
input: (provided) => (Object.assign(provided, {
|
||||
minWidth: '20px',
|
||||
color: theme.color,
|
||||
})),
|
||||
menu: (provided) => (Object.assign(provided, {
|
||||
color: theme.color,
|
||||
fontFamily: theme.fontFamily,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
})),
|
||||
option: (provided) => (Object.assign(provided, {
|
||||
color: theme.color,
|
||||
fontFamily: theme.fontFamily,
|
||||
})),
|
||||
multiValueLabel: (provided) => (Object.assign(provided, {
|
||||
fontFamily: theme.fontFamily,
|
||||
})),
|
||||
multiValueRemove: (provided) => (Object.assign(provided, {
|
||||
color: theme.color,
|
||||
})),
|
||||
control: provided =>
|
||||
Object.assign(provided, {
|
||||
minWidth: width * 0.2,
|
||||
maxWidth: width * 0.5,
|
||||
}),
|
||||
input: provided =>
|
||||
Object.assign(provided, {
|
||||
minWidth: '20px',
|
||||
color: theme.color,
|
||||
}),
|
||||
menu: provided =>
|
||||
Object.assign(provided, {
|
||||
color: theme.color,
|
||||
fontFamily: theme.fontFamily,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
}),
|
||||
option: provided =>
|
||||
Object.assign(provided, {
|
||||
color: theme.color,
|
||||
fontFamily: theme.fontFamily,
|
||||
}),
|
||||
multiValueLabel: provided =>
|
||||
Object.assign(provided, {
|
||||
fontFamily: theme.fontFamily,
|
||||
}),
|
||||
multiValueRemove: provided =>
|
||||
Object.assign(provided, {
|
||||
color: theme.color,
|
||||
}),
|
||||
};
|
||||
|
||||
this.styles_.selectTheme = (tagTheme) => (Object.assign(tagTheme, {
|
||||
borderRadius: 2,
|
||||
colors: Object.assign(tagTheme.colors, {
|
||||
primary: theme.raisedBackgroundColor,
|
||||
primary25: theme.raisedBackgroundColor,
|
||||
neutral0: theme.backgroundColor,
|
||||
neutral5: theme.backgroundColor,
|
||||
neutral10: theme.raisedBackgroundColor,
|
||||
neutral20: theme.raisedBackgroundColor,
|
||||
neutral30: theme.raisedBackgroundColor,
|
||||
neutral40: theme.color,
|
||||
neutral50: theme.color,
|
||||
neutral60: theme.color,
|
||||
neutral70: theme.color,
|
||||
neutral80: theme.color,
|
||||
neutral90: theme.color,
|
||||
danger: theme.backgroundColor,
|
||||
dangerLight: theme.colorError2,
|
||||
}),
|
||||
}));
|
||||
this.styles_.selectTheme = tagTheme =>
|
||||
Object.assign(tagTheme, {
|
||||
borderRadius: 2,
|
||||
colors: Object.assign(tagTheme.colors, {
|
||||
primary: theme.raisedBackgroundColor,
|
||||
primary25: theme.raisedBackgroundColor,
|
||||
neutral0: theme.backgroundColor,
|
||||
neutral5: theme.backgroundColor,
|
||||
neutral10: theme.raisedBackgroundColor,
|
||||
neutral20: theme.raisedBackgroundColor,
|
||||
neutral30: theme.raisedBackgroundColor,
|
||||
neutral40: theme.color,
|
||||
neutral50: theme.color,
|
||||
neutral60: theme.color,
|
||||
neutral70: theme.color,
|
||||
neutral80: theme.color,
|
||||
neutral90: theme.color,
|
||||
danger: theme.backgroundColor,
|
||||
dangerLight: theme.colorError2,
|
||||
}),
|
||||
});
|
||||
|
||||
this.styles_.desc = Object.assign({}, theme.textStyle, {
|
||||
marginTop: 10,
|
||||
@ -173,11 +177,11 @@ class PromptDialog extends React.Component {
|
||||
this.props.onClose(accept ? outputAnswer : null, buttonType);
|
||||
}
|
||||
this.setState({ visible: false, answer: '' });
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (event) => {
|
||||
const onChange = event => {
|
||||
this.setState({ answer: event.target.value });
|
||||
}
|
||||
};
|
||||
|
||||
// const anythingToDate = (o) => {
|
||||
// if (o && o.toDate) return o.toDate();
|
||||
@ -188,16 +192,16 @@ class PromptDialog extends React.Component {
|
||||
// return m.isValid() ? m.toDate() : null;
|
||||
// }
|
||||
|
||||
const onDateTimeChange = (momentObject) => {
|
||||
const onDateTimeChange = momentObject => {
|
||||
this.setState({ answer: momentObject });
|
||||
}
|
||||
};
|
||||
|
||||
const onSelectChange = (newValue) => {
|
||||
const onSelectChange = newValue => {
|
||||
this.setState({ answer: newValue });
|
||||
this.focusInput_ = true;
|
||||
}
|
||||
};
|
||||
|
||||
const onKeyDown = (event) => {
|
||||
const onKeyDown = event => {
|
||||
if (event.key === 'Enter') {
|
||||
if (this.props.inputType !== 'tags' && this.props.inputType !== 'dropdown') {
|
||||
onClose(true);
|
||||
@ -208,80 +212,55 @@ class PromptDialog extends React.Component {
|
||||
} else if (event.key === 'Escape') {
|
||||
onClose(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null;
|
||||
|
||||
let inputComp = null;
|
||||
|
||||
if (this.props.inputType === 'datetime') {
|
||||
inputComp = <Datetime
|
||||
value={this.state.answer}
|
||||
inputProps={{style: styles.input}}
|
||||
dateFormat={time.dateFormat()}
|
||||
timeFormat={time.timeFormat()}
|
||||
onChange={(momentObject) => onDateTimeChange(momentObject)}
|
||||
/>
|
||||
inputComp = <Datetime value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={momentObject => onDateTimeChange(momentObject)} />;
|
||||
} else if (this.props.inputType === 'tags') {
|
||||
inputComp = <CreatableSelect
|
||||
styles={styles.select}
|
||||
theme={styles.selectTheme}
|
||||
ref={this.answerInput_}
|
||||
value={this.state.answer}
|
||||
placeholder=""
|
||||
components={makeAnimated()}
|
||||
isMulti={true}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={true}
|
||||
options={this.props.autocomplete}
|
||||
onChange={onSelectChange}
|
||||
onKeyDown={(event) => onKeyDown(event)}
|
||||
/>
|
||||
inputComp = <CreatableSelect styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} value={this.state.answer} placeholder="" components={makeAnimated()} isMulti={true} isClearable={false} backspaceRemovesValue={true} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
} else if (this.props.inputType === 'dropdown') {
|
||||
inputComp = <Select
|
||||
styles={styles.select}
|
||||
theme={styles.selectTheme}
|
||||
ref={this.answerInput_}
|
||||
components={makeAnimated()}
|
||||
value={this.props.answer}
|
||||
defaultValue={this.props.defaultValue}
|
||||
isClearable={false}
|
||||
options={this.props.autocomplete}
|
||||
onChange={onSelectChange}
|
||||
onKeyDown={(event) => onKeyDown(event)}
|
||||
/>
|
||||
inputComp = <Select styles={styles.select} theme={styles.selectTheme} ref={this.answerInput_} components={makeAnimated()} value={this.props.answer} defaultValue={this.props.defaultValue} isClearable={false} options={this.props.autocomplete} onChange={onSelectChange} onKeyDown={event => onKeyDown(event)} />;
|
||||
} else {
|
||||
inputComp = <input
|
||||
style={styles.input}
|
||||
ref={this.answerInput_}
|
||||
value={this.state.answer}
|
||||
type="text"
|
||||
onChange={(event) => onChange(event)}
|
||||
onKeyDown={(event) => onKeyDown(event)}
|
||||
/>
|
||||
inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
|
||||
}
|
||||
|
||||
const buttonComps = [];
|
||||
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>);
|
||||
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>);
|
||||
if (buttonTypes.indexOf('clear') >= 0) buttonComps.push(<button key="clear" style={styles.button} onClick={() => onClose(false, 'clear')}>{_('Clear')}</button>);
|
||||
if (buttonTypes.indexOf('ok') >= 0)
|
||||
buttonComps.push(
|
||||
<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>
|
||||
{_('OK')}
|
||||
</button>
|
||||
);
|
||||
if (buttonTypes.indexOf('cancel') >= 0)
|
||||
buttonComps.push(
|
||||
<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>
|
||||
{_('Cancel')}
|
||||
</button>
|
||||
);
|
||||
if (buttonTypes.indexOf('clear') >= 0)
|
||||
buttonComps.push(
|
||||
<button key="clear" style={styles.button} onClick={() => onClose(false, 'clear')}>
|
||||
{_('Clear')}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div style={styles.modalLayer}>
|
||||
<div style={styles.promptDialog}>
|
||||
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label>
|
||||
<div style={{display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor}}>
|
||||
<div style={{ display: 'inline-block', color: 'black', backgroundColor: theme.backgroundColor }}>
|
||||
{inputComp}
|
||||
{descComp}
|
||||
</div>
|
||||
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
||||
{buttonComps}
|
||||
</div>
|
||||
<div style={{ textAlign: 'right', marginTop: 10 }}>{buttonComps}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { PromptDialog };
|
||||
|
@ -1,6 +1,5 @@
|
||||
const React = require('react');
|
||||
const { render } = require('react-dom');
|
||||
const { createStore } = require('redux');
|
||||
const { connect, Provider } = require('react-redux');
|
||||
|
||||
const { _ } = require('lib/locale.js');
|
||||
@ -21,20 +20,22 @@ const { app } = require('../app');
|
||||
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
|
||||
async function initialize(dispatch) {
|
||||
async function initialize() {
|
||||
this.wcsTimeoutId_ = null;
|
||||
|
||||
bridge().window().on('resize', function() {
|
||||
if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_);
|
||||
bridge()
|
||||
.window()
|
||||
.on('resize', function() {
|
||||
if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_);
|
||||
|
||||
this.wcsTimeoutId_ = setTimeout(() => {
|
||||
store.dispatch({
|
||||
type: 'WINDOW_CONTENT_SIZE_SET',
|
||||
size: bridge().windowContentSize(),
|
||||
});
|
||||
this.wcsTimeoutId_ = null;
|
||||
}, 10);
|
||||
});
|
||||
this.wcsTimeoutId_ = setTimeout(() => {
|
||||
store.dispatch({
|
||||
type: 'WINDOW_CONTENT_SIZE_SET',
|
||||
size: bridge().windowContentSize(),
|
||||
});
|
||||
this.wcsTimeoutId_ = null;
|
||||
}, 10);
|
||||
});
|
||||
|
||||
// Need to dispatch this to make sure the components are
|
||||
// displayed at the right size. The windowContentSize is
|
||||
@ -52,12 +53,11 @@ async function initialize(dispatch) {
|
||||
|
||||
store.dispatch({
|
||||
type: 'SIDEBAR_VISIBILITY_SET',
|
||||
visibility: Setting.value('sidebarVisibility')
|
||||
visibility: Setting.value('sidebarVisibility'),
|
||||
});
|
||||
}
|
||||
|
||||
class RootComponent extends React.Component {
|
||||
|
||||
async componentDidMount() {
|
||||
if (this.props.appState == 'starting') {
|
||||
this.props.dispatch({
|
||||
@ -93,14 +93,11 @@ class RootComponent extends React.Component {
|
||||
ClipperConfig: { screen: ClipperConfigScreen, title: () => _('Clipper Options') },
|
||||
};
|
||||
|
||||
return (
|
||||
<Navigator style={navigatorStyle} screens={screens} />
|
||||
);
|
||||
return <Navigator style={navigatorStyle} screens={screens} />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
size: state.windowContentSize,
|
||||
appState: state.appState,
|
||||
@ -116,4 +113,4 @@ render(
|
||||
<Root />
|
||||
</Provider>,
|
||||
document.getElementById('react-root')
|
||||
)
|
||||
);
|
||||
|
@ -1,28 +1,25 @@
|
||||
const React = require("react");
|
||||
const { connect } = require("react-redux");
|
||||
const shared = require("lib/components/shared/side-menu-shared.js");
|
||||
const { Synchronizer } = require("lib/synchronizer.js");
|
||||
const BaseModel = require("lib/BaseModel.js");
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const shared = require('lib/components/shared/side-menu-shared.js');
|
||||
const { Synchronizer } = require('lib/synchronizer.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const Folder = require("lib/models/Folder.js");
|
||||
const Note = require("lib/models/Note.js");
|
||||
const Tag = require("lib/models/Tag.js");
|
||||
const { _ } = require("lib/locale.js");
|
||||
const { themeStyle } = require("../theme.js");
|
||||
const { bridge } = require("electron").remote.require("./bridge");
|
||||
const Folder = require('lib/models/Folder.js');
|
||||
const Note = require('lib/models/Note.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const InteropServiceHelper = require("../InteropServiceHelper.js");
|
||||
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
const { shim } = require('lib/shim');
|
||||
|
||||
class SideBarComponent extends React.Component {
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.onFolderDragStart_ = (event) => {
|
||||
this.onFolderDragStart_ = event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
if (!folderId) return;
|
||||
|
||||
@ -31,12 +28,12 @@ class SideBarComponent extends React.Component {
|
||||
event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId]));
|
||||
};
|
||||
|
||||
this.onFolderDragOver_ = (event) => {
|
||||
if (event.dataTransfer.types.indexOf("text/x-jop-note-ids") >= 0) event.preventDefault();
|
||||
if (event.dataTransfer.types.indexOf("text/x-jop-folder-ids") >= 0) event.preventDefault();
|
||||
this.onFolderDragOver_ = event => {
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault();
|
||||
if (event.dataTransfer.types.indexOf('text/x-jop-folder-ids') >= 0) event.preventDefault();
|
||||
};
|
||||
|
||||
this.onFolderDrop_ = async (event) => {
|
||||
this.onFolderDrop_ = async event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
@ -45,41 +42,41 @@ class SideBarComponent extends React.Component {
|
||||
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
|
||||
// since there's no such thing as a root note.
|
||||
|
||||
if (dt.types.indexOf("text/x-jop-note-ids") >= 0) {
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!folderId) return;
|
||||
|
||||
const noteIds = JSON.parse(dt.getData("text/x-jop-note-ids"));
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Note.moveToFolder(noteIds[i], folderId);
|
||||
}
|
||||
} else if (dt.types.indexOf("text/x-jop-folder-ids") >= 0) {
|
||||
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const folderIds = JSON.parse(dt.getData("text/x-jop-folder-ids"));
|
||||
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
|
||||
for (let i = 0; i < folderIds.length; i++) {
|
||||
await Folder.moveToFolder(folderIds[i], folderId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.onTagDrop_ = async (event) => {
|
||||
this.onTagDrop_ = async event => {
|
||||
const tagId = event.currentTarget.getAttribute('tagid');
|
||||
const dt = event.dataTransfer;
|
||||
if (!dt) return;
|
||||
|
||||
if (dt.types.indexOf("text/x-jop-note-ids") >= 0) {
|
||||
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
|
||||
event.preventDefault();
|
||||
|
||||
const noteIds = JSON.parse(dt.getData("text/x-jop-note-ids"));
|
||||
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
await Tag.addNote(tagId, noteIds[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.onFolderToggleClick_ = async (event) => {
|
||||
this.onFolderToggleClick_ = async event => {
|
||||
const folderId = event.currentTarget.getAttribute('folderid');
|
||||
|
||||
this.props.dispatch({
|
||||
@ -99,7 +96,7 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
this.state = {
|
||||
tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'),
|
||||
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded')
|
||||
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'),
|
||||
};
|
||||
}
|
||||
|
||||
@ -113,23 +110,23 @@ class SideBarComponent extends React.Component {
|
||||
backgroundColor: theme.backgroundColor2,
|
||||
},
|
||||
listItemContainer: {
|
||||
boxSizing: "border-box",
|
||||
boxSizing: 'border-box',
|
||||
height: itemHeight,
|
||||
// paddingLeft: 14,
|
||||
display: "flex",
|
||||
alignItems: "stretch",
|
||||
display: 'flex',
|
||||
alignItems: 'stretch',
|
||||
// Allow 3 levels of color depth
|
||||
backgroundColor: theme.depthColor.replace('OPACITY', Math.min(depth * 0.1, 0.3)),
|
||||
},
|
||||
listItem: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: "none",
|
||||
textDecoration: 'none',
|
||||
color: theme.color2,
|
||||
cursor: "default",
|
||||
cursor: 'default',
|
||||
opacity: 0.8,
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
@ -138,60 +135,60 @@ class SideBarComponent extends React.Component {
|
||||
},
|
||||
listItemExpandIcon: {
|
||||
color: theme.color2,
|
||||
cursor: "default",
|
||||
cursor: 'default',
|
||||
opacity: 0.8,
|
||||
// fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: "none",
|
||||
textDecoration: 'none',
|
||||
paddingRight: 5,
|
||||
display: "flex",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
conflictFolder: {
|
||||
color: theme.colorError2,
|
||||
fontWeight: "bold",
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
header: {
|
||||
height: itemHeight * 1.8,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize * 1.16,
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.color2,
|
||||
paddingLeft: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
button: {
|
||||
padding: 6,
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: theme.fontSize,
|
||||
textDecoration: "none",
|
||||
boxSizing: "border-box",
|
||||
textDecoration: 'none',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.color2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
cursor: "default",
|
||||
cursor: 'default',
|
||||
},
|
||||
syncReport: {
|
||||
fontFamily: theme.fontFamily,
|
||||
fontSize: Math.round(theme.fontSize * 0.9),
|
||||
color: theme.color2,
|
||||
opacity: 0.5,
|
||||
display: "flex",
|
||||
alignItems: "left",
|
||||
justifyContent: "top",
|
||||
flexDirection: "column",
|
||||
display: 'flex',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'top',
|
||||
flexDirection: 'column',
|
||||
marginTop: 10,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
marginBottom: 10,
|
||||
wordWrap: "break-word",
|
||||
wordWrap: 'break-word',
|
||||
},
|
||||
};
|
||||
|
||||
@ -236,42 +233,40 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (shim.isLinux()) {
|
||||
// For some reason, the UI seems to sleep in some Linux distro during
|
||||
// sync. Cannot find the reason for it and cannot replicate, so here
|
||||
// as a test force the update at regular intervals.
|
||||
// https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
|
||||
if (!prevProps.syncStarted && this.props.syncStarted) {
|
||||
this.clearForceUpdateDuringSync();
|
||||
|
||||
this.forceUpdateDuringSyncIID_ = setInterval(() => {
|
||||
this.forceUpdate();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.clearForceUpdateDuringSync();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.windowCommand !== this.props.windowCommand) {
|
||||
this.doCommand(this.props.windowCommand);
|
||||
}
|
||||
|
||||
// if (shim.isLinux()) {
|
||||
// // For some reason, the UI seems to sleep in some Linux distro during
|
||||
// // sync. Cannot find the reason for it and cannot replicate, so here
|
||||
// // as a test force the update at regular intervals.
|
||||
// // https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
|
||||
// if (!prevProps.syncStarted && this.props.syncStarted) {
|
||||
// this.clearForceUpdateDuringSync();
|
||||
|
||||
// this.forceUpdateDuringSyncIID_ = setInterval(() => {
|
||||
// this.forceUpdate();
|
||||
// }, 2000);
|
||||
// }
|
||||
|
||||
// if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync();
|
||||
// }
|
||||
}
|
||||
|
||||
async itemContextMenu(event) {
|
||||
const itemId = event.target.getAttribute("data-id");
|
||||
const itemId = event.target.getAttribute('data-id');
|
||||
if (itemId === Folder.conflictFolderId()) return;
|
||||
|
||||
const itemType = Number(event.target.getAttribute("data-type"));
|
||||
if (!itemId || !itemType) throw new Error("No data on element");
|
||||
const itemType = Number(event.target.getAttribute('data-type'));
|
||||
if (!itemId || !itemType) throw new Error('No data on element');
|
||||
|
||||
let deleteMessage = "";
|
||||
let deleteMessage = '';
|
||||
if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
const folder = await Folder.load(itemId);
|
||||
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
|
||||
@ -279,7 +274,7 @@ class SideBarComponent extends React.Component {
|
||||
const tag = await Tag.load(itemId);
|
||||
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
deleteMessage = _("Remove this search from the sidebar?");
|
||||
deleteMessage = _('Remove this search from the sidebar?');
|
||||
}
|
||||
|
||||
const menu = new Menu();
|
||||
@ -291,7 +286,7 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Delete"),
|
||||
label: _('Delete'),
|
||||
click: async () => {
|
||||
const ok = bridge().showConfirmMessageBox(deleteMessage);
|
||||
if (!ok) return;
|
||||
@ -302,7 +297,7 @@ class SideBarComponent extends React.Component {
|
||||
await Tag.untagAll(itemId);
|
||||
} else if (itemType === BaseModel.TYPE_SEARCH) {
|
||||
this.props.dispatch({
|
||||
type: "SEARCH_DELETE",
|
||||
type: 'SEARCH_DELETE',
|
||||
id: itemId,
|
||||
});
|
||||
}
|
||||
@ -313,11 +308,11 @@ class SideBarComponent extends React.Component {
|
||||
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Rename"),
|
||||
label: _('Rename'),
|
||||
click: async () => {
|
||||
this.props.dispatch({
|
||||
type: "WINDOW_COMMAND",
|
||||
name: "renameFolder",
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'renameFolder',
|
||||
id: itemId,
|
||||
});
|
||||
},
|
||||
@ -337,9 +332,9 @@ class SideBarComponent extends React.Component {
|
||||
// })
|
||||
// );
|
||||
|
||||
menu.append(new MenuItem({ type: "separator" }));
|
||||
menu.append(new MenuItem({ type: 'separator' }));
|
||||
|
||||
const InteropService = require("lib/services/InteropService.js");
|
||||
const InteropService = require('lib/services/InteropService.js');
|
||||
|
||||
const exportMenu = new Menu();
|
||||
const ioService = new InteropService();
|
||||
@ -348,14 +343,19 @@ class SideBarComponent extends React.Component {
|
||||
const module = ioModules[i];
|
||||
if (module.type !== 'exporter') continue;
|
||||
|
||||
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => {
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
|
||||
}}));
|
||||
exportMenu.append(
|
||||
new MenuItem({
|
||||
label: module.fullLabel(),
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _("Export"),
|
||||
label: _('Export'),
|
||||
submenu: exportMenu,
|
||||
})
|
||||
);
|
||||
@ -367,9 +367,9 @@ class SideBarComponent extends React.Component {
|
||||
label: _('Rename'),
|
||||
click: async () => {
|
||||
this.props.dispatch({
|
||||
type: "WINDOW_COMMAND",
|
||||
name: "renameTag",
|
||||
id: itemId
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'renameTag',
|
||||
id: itemId,
|
||||
});
|
||||
},
|
||||
})
|
||||
@ -381,14 +381,14 @@ class SideBarComponent extends React.Component {
|
||||
|
||||
folderItem_click(folder) {
|
||||
this.props.dispatch({
|
||||
type: "FOLDER_SELECT",
|
||||
type: 'FOLDER_SELECT',
|
||||
id: folder ? folder.id : null,
|
||||
});
|
||||
}
|
||||
|
||||
tagItem_click(tag) {
|
||||
this.props.dispatch({
|
||||
type: "TAG_SELECT",
|
||||
type: 'TAG_SELECT',
|
||||
id: tag ? tag.id : null,
|
||||
});
|
||||
}
|
||||
@ -405,8 +405,6 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
anchorItemRef(type, id) {
|
||||
let refs = null;
|
||||
|
||||
if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {};
|
||||
if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id];
|
||||
this.anchorItemRefs[type][id] = React.createRef();
|
||||
@ -426,17 +424,23 @@ class SideBarComponent extends React.Component {
|
||||
let expandIconStyle = {
|
||||
visibility: hasChildren ? 'visible' : 'hidden',
|
||||
paddingLeft: 8 + depth * 10,
|
||||
}
|
||||
};
|
||||
|
||||
const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'fa-plus-square' : 'fa-minus-square';
|
||||
const expandIcon = <i style={expandIconStyle} className={"fa " + iconName}></i>
|
||||
const expandLink = hasChildren ? <a style={expandLinkStyle} href="#" folderid={folder.id} onClick={this.onFolderToggleClick_}>{expandIcon}</a> : <span style={expandLinkStyle}>{expandIcon}</span>
|
||||
const expandIcon = <i style={expandIconStyle} className={'fa ' + iconName}></i>;
|
||||
const expandLink = hasChildren ? (
|
||||
<a style={expandLinkStyle} href="#" folderid={folder.id} onClick={this.onFolderToggleClick_}>
|
||||
{expandIcon}
|
||||
</a>
|
||||
) : (
|
||||
<span style={expandLinkStyle}>{expandIcon}</span>
|
||||
);
|
||||
|
||||
const anchorRef = this.anchorItemRef('folder', folder.id);
|
||||
|
||||
return (
|
||||
<div className="list-item-container" style={containerStyle} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} folderid={folder.id}>
|
||||
{ expandLink }
|
||||
{expandLink}
|
||||
<a
|
||||
ref={anchorRef}
|
||||
className="list-item"
|
||||
@ -506,15 +510,15 @@ class SideBarComponent extends React.Component {
|
||||
// }
|
||||
|
||||
makeDivider(key) {
|
||||
return <div style={{ height: 2, backgroundColor: "blue" }} key={key} />;
|
||||
return <div style={{ height: 2, backgroundColor: 'blue' }} key={key} />;
|
||||
}
|
||||
|
||||
makeHeader(key, label, iconName, extraProps = {}) {
|
||||
const style = this.style().header;
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={"fa " + iconName} />;
|
||||
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={'fa ' + iconName} />;
|
||||
|
||||
if (extraProps.toggleblock || extraProps.onClick) {
|
||||
style.cursor = "pointer";
|
||||
style.cursor = 'pointer';
|
||||
}
|
||||
|
||||
let headerClick = extraProps.onClick || null;
|
||||
@ -525,22 +529,27 @@ class SideBarComponent extends React.Component {
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
if (extraProps.toggleblock) {
|
||||
let isExpanded = this.state[toggleKey];
|
||||
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75,
|
||||
marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125}}></i>;
|
||||
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75, marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125 }}></i>;
|
||||
}
|
||||
|
||||
const ref = this.anchorItemRef('headers', key);
|
||||
|
||||
return (
|
||||
<div ref={ref} style={style} key={key} {...extraProps} onClick={(event) => {
|
||||
// if a custom click event is attached, trigger that.
|
||||
<div
|
||||
ref={ref}
|
||||
style={style}
|
||||
key={key}
|
||||
{...extraProps}
|
||||
onClick={event => {
|
||||
// if a custom click event is attached, trigger that.
|
||||
if (headerClick) {
|
||||
headerClick(key, event);
|
||||
headerClick(key, event);
|
||||
}
|
||||
this.onHeaderClick_(key, event);
|
||||
}}>
|
||||
this.onHeaderClick_(key, event);
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<span style={{flex: 1 }}>{label}</span>
|
||||
<span style={{ flex: 1 }}>{label}</span>
|
||||
{toggleIcon}
|
||||
</div>
|
||||
);
|
||||
@ -562,7 +571,8 @@ class SideBarComponent extends React.Component {
|
||||
const keyCode = event.keyCode;
|
||||
const selectedItem = this.selectedItem();
|
||||
|
||||
if (keyCode === 40 || keyCode === 38) { // DOWN / UP
|
||||
if (keyCode === 40 || keyCode === 38) {
|
||||
// DOWN / UP
|
||||
event.preventDefault();
|
||||
|
||||
const focusItems = [];
|
||||
@ -603,7 +613,8 @@ class SideBarComponent extends React.Component {
|
||||
focusItem.ref.current.focus();
|
||||
}
|
||||
|
||||
if (keyCode === 9) { // TAB
|
||||
if (keyCode === 9) {
|
||||
// TAB
|
||||
event.preventDefault();
|
||||
|
||||
if (event.shiftKey) {
|
||||
@ -621,7 +632,8 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) { // SPACE
|
||||
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) {
|
||||
// SPACE
|
||||
event.preventDefault();
|
||||
|
||||
this.props.dispatch({
|
||||
@ -631,28 +643,28 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onHeaderClick_(key, event) {
|
||||
onHeaderClick_(key, event) {
|
||||
const currentHeader = event.currentTarget;
|
||||
const toggleBlock = +currentHeader.getAttribute('toggleblock');
|
||||
if (toggleBlock) {
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
const isExpanded = this.state[toggleKey];
|
||||
this.setState({ [toggleKey]: !isExpanded });
|
||||
Setting.setValue(toggleKey, !isExpanded);
|
||||
const toggleKey = `${key}IsExpanded`;
|
||||
const isExpanded = this.state[toggleKey];
|
||||
this.setState({ [toggleKey]: !isExpanded });
|
||||
Setting.setValue(toggleKey, !isExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
synchronizeButton(type) {
|
||||
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
|
||||
const iconName = "fa-refresh";
|
||||
const label = type === "sync" ? _("Synchronise") : _("Cancel");
|
||||
const iconName = 'fa-refresh';
|
||||
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
|
||||
let iconStyle = { fontSize: style.fontSize, marginRight: 5 };
|
||||
|
||||
if(type !== 'sync'){
|
||||
if (type !== 'sync') {
|
||||
iconStyle.animation = 'icon-infinite-rotation 1s linear infinite';
|
||||
}
|
||||
|
||||
const icon = <i style={iconStyle} className={"fa " + iconName} />;
|
||||
const icon = <i style={iconStyle} className={'fa ' + iconName} />;
|
||||
return (
|
||||
<a
|
||||
className="synchronize-button"
|
||||
@ -670,32 +682,38 @@ class SideBarComponent extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = Object.assign({}, this.style().root, this.props.style, {
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden",
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
let items = [];
|
||||
items.push(this.makeHeader("folderHeader", _("Notebooks"), "fa-book", {
|
||||
onDrop: this.onFolderDrop_,
|
||||
folderid: '',
|
||||
toggleblock: 1
|
||||
}));
|
||||
items.push(
|
||||
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
|
||||
onDrop: this.onFolderDrop_,
|
||||
folderid: '',
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.folders.length) {
|
||||
const result = shared.renderFolders(this.props, this.folderItem.bind(this));
|
||||
const folderItems = result.items;
|
||||
this.folderItemsOrder_ = result.order;
|
||||
items.push(<div className="folders" key="folder_items" style={{display: this.state.folderHeaderIsExpanded ? 'block': 'none'}}>
|
||||
{folderItems}</div>);
|
||||
items.push(
|
||||
<div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none' }}>
|
||||
{folderItems}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags", {
|
||||
toggleblock: 1
|
||||
}));
|
||||
items.push(
|
||||
this.makeHeader('tagHeader', _('Tags'), 'fa-tags', {
|
||||
toggleblock: 1,
|
||||
})
|
||||
);
|
||||
|
||||
if (this.props.tags.length) {
|
||||
const result = shared.renderTags(this.props, this.tagItem.bind(this));
|
||||
@ -703,7 +721,7 @@ class SideBarComponent extends React.Component {
|
||||
this.tagItemsOrder_ = result.order;
|
||||
|
||||
items.push(
|
||||
<div className="tags" key="tag_items" style={{display: this.state.tagHeaderIsExpanded ? 'block': 'none'}}>
|
||||
<div className="tags" key="tag_items" style={{ display: this.state.tagHeaderIsExpanded ? 'block' : 'none' }}>
|
||||
{tagItems}
|
||||
</div>
|
||||
);
|
||||
@ -725,27 +743,24 @@ class SideBarComponent extends React.Component {
|
||||
const syncReportText = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
syncReportText.push(
|
||||
<div key={i} style={{ wordWrap: "break-word", width: "100%" }}>
|
||||
<div key={i} style={{ wordWrap: 'break-word', width: '100%' }}>
|
||||
{lines[i]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const syncButton = this.synchronizeButton(this.props.syncStarted ? "cancel" : "sync");
|
||||
const syncButton = this.synchronizeButton(this.props.syncStarted ? 'cancel' : 'sync');
|
||||
|
||||
const syncReportComp = !syncReportText.length ? null : (
|
||||
<div style={this.style().syncReport} key="sync_report">
|
||||
{syncReportText}
|
||||
</div>
|
||||
);
|
||||
{syncReportText}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={this.rootRef} onKeyDown={this.onKeyDown} className="side-bar" style={style}>
|
||||
|
||||
<div style={{flex:1, overflowX: 'hidden', overflowY: 'auto'}}>
|
||||
{items}
|
||||
</div>
|
||||
<div style={{flex:0}}>
|
||||
<div style={{ flex: 1, overflowX: 'hidden', overflowY: 'auto' }}>{items}</div>
|
||||
<div style={{ flex: 0 }}>
|
||||
{syncReportComp}
|
||||
{syncButton}
|
||||
</div>
|
||||
|
@ -1,6 +1,5 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const { Header } = require('./Header.min.js');
|
||||
@ -10,7 +9,6 @@ const { ReportService } = require('lib/services/report.js');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
class StatusScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
@ -29,7 +27,7 @@ class StatusScreenComponent extends React.Component {
|
||||
}
|
||||
|
||||
async exportDebugReportClick() {
|
||||
const filename = 'syncReport-' + (new Date()).getTime() + '.csv';
|
||||
const filename = 'syncReport-' + new Date().getTime() + '.csv';
|
||||
|
||||
const filePath = bridge().showSaveDialog({
|
||||
title: _('Please select where the sync status should be exported to'),
|
||||
@ -48,7 +46,7 @@ class StatusScreenComponent extends React.Component {
|
||||
const style = this.props.style;
|
||||
|
||||
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width });
|
||||
const retryStyle = Object.assign({}, theme.urlStyle, {marginLeft: 5});
|
||||
const retryStyle = Object.assign({}, theme.urlStyle, { marginLeft: 5 });
|
||||
|
||||
const containerPadding = 10;
|
||||
|
||||
@ -58,7 +56,11 @@ class StatusScreenComponent extends React.Component {
|
||||
});
|
||||
|
||||
function renderSectionTitleHtml(key, title) {
|
||||
return <h2 key={'section_' + key} style={theme.h2Style}>{title}</h2>
|
||||
return (
|
||||
<h2 key={'section_' + key} style={theme.h2Style}>
|
||||
{title}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
|
||||
const renderSectionHtml = (key, section) => {
|
||||
@ -77,9 +79,13 @@ class StatusScreenComponent extends React.Component {
|
||||
const onClick = async () => {
|
||||
await item.retryHandler();
|
||||
this.resfreshScreen();
|
||||
}
|
||||
};
|
||||
|
||||
retryLink = <a href="#" onClick={onClick} style={retryStyle}>{_('Retry')}</a>;
|
||||
retryLink = (
|
||||
<a href="#" onClick={onClick} style={retryStyle}>
|
||||
{_('Retry')}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
text = item.text;
|
||||
} else {
|
||||
@ -88,28 +94,18 @@ class StatusScreenComponent extends React.Component {
|
||||
|
||||
if (!text) text = '\xa0';
|
||||
|
||||
itemsHtml.push(<div style={theme.textStyle} key={'item_' + n}><span>{text}</span>{retryLink}</div>);
|
||||
itemsHtml.push(
|
||||
<div style={theme.textStyle} key={'item_' + n}>
|
||||
<span>{text}</span>
|
||||
{retryLink}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key}>
|
||||
{itemsHtml}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <div key={key}>{itemsHtml}</div>;
|
||||
};
|
||||
|
||||
function renderBodyHtml(report) {
|
||||
let output = [];
|
||||
let baseStyle = {
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
flex: 0,
|
||||
color: theme.color,
|
||||
fontSize: theme.fontSize,
|
||||
};
|
||||
|
||||
let sectionsHtml = [];
|
||||
|
||||
for (let i = 0; i < report.length; i++) {
|
||||
@ -118,29 +114,26 @@ class StatusScreenComponent extends React.Component {
|
||||
sectionsHtml.push(renderSectionHtml(i, section));
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sectionsHtml}
|
||||
</div>
|
||||
);
|
||||
return <div>{sectionsHtml}</div>;
|
||||
}
|
||||
|
||||
let body = renderBodyHtml(this.state.report);
|
||||
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<Header style={headerStyle} />
|
||||
<div style={containerStyle}>
|
||||
<a style={theme.textStyle} onClick={() => this.exportDebugReportClick()}href="#">Export debug report</a>
|
||||
<a style={theme.textStyle} onClick={() => this.exportDebugReportClick()} href="#">
|
||||
Export debug report
|
||||
</a>
|
||||
{body}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
theme: state.settings.theme,
|
||||
settings: state.settings,
|
||||
|
@ -12,7 +12,7 @@ class TagItemComponent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,7 @@ class TagListComponent extends React.Component {
|
||||
const tags = this.props.items;
|
||||
|
||||
style.display = 'flex';
|
||||
style.flexDirection = 'row';
|
||||
style.flexDirection = 'row';
|
||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||
style.boxSizing = 'border-box';
|
||||
style.fontSize = theme.fontSize;
|
||||
@ -18,12 +18,14 @@ class TagListComponent extends React.Component {
|
||||
const tagItems = [];
|
||||
if (tags || tags.length > 0) {
|
||||
// Sort by id for now, but probably needs to be changed in the future.
|
||||
tags.sort((a, b) => { return a.title < b.title ? -1 : +1; });
|
||||
tags.sort((a, b) => {
|
||||
return a.title < b.title ? -1 : +1;
|
||||
});
|
||||
|
||||
for (let i = 0; i < tags.length; i++) {
|
||||
const props = {
|
||||
title: tags[i].title,
|
||||
key: tags[i].id
|
||||
key: tags[i].id,
|
||||
};
|
||||
tagItems.push(<TagItem {...props} />);
|
||||
}
|
||||
@ -35,13 +37,13 @@ class TagListComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="tag-list" style={style}>
|
||||
{ tagItems }
|
||||
{tagItems}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
};
|
||||
|
||||
|
@ -1,19 +1,16 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { reg } = require('lib/registry.js');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const ToolbarButton = require('./ToolbarButton.min.js');
|
||||
const ToolbarSpace = require('./ToolbarSpace.min.js');
|
||||
|
||||
class ToolbarComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const style = this.props.style;
|
||||
const theme = themeStyle(this.props.theme);
|
||||
style.height = theme.toolbarHeight;
|
||||
style.display = 'flex';
|
||||
style.flexDirection = 'row';
|
||||
style.flexDirection = 'row';
|
||||
style.borderBottom = '1px solid ' + theme.dividerColor;
|
||||
style.boxSizing = 'border-box';
|
||||
|
||||
@ -28,10 +25,13 @@ class ToolbarComponent extends React.Component {
|
||||
|
||||
if (!key) key = o.type + '_' + i;
|
||||
|
||||
const props = Object.assign({
|
||||
key: key,
|
||||
theme: this.props.theme,
|
||||
}, o);
|
||||
const props = Object.assign(
|
||||
{
|
||||
key: key,
|
||||
theme: this.props.theme,
|
||||
},
|
||||
o
|
||||
);
|
||||
|
||||
if (itemType === 'button') {
|
||||
itemComps.push(<ToolbarButton {...props} />);
|
||||
@ -43,17 +43,16 @@ class ToolbarComponent extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="editor-toolbar" style={style}>
|
||||
{ itemComps }
|
||||
{itemComps}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return { theme: state.settings.theme };
|
||||
};
|
||||
|
||||
const Toolbar = connect(mapStateToProps)(ToolbarComponent);
|
||||
|
||||
module.exports = Toolbar;
|
||||
module.exports = Toolbar;
|
||||
|
@ -1,9 +1,7 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
|
||||
class ToolbarButton extends React.Component {
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
|
||||
@ -16,13 +14,13 @@ class ToolbarButton extends React.Component {
|
||||
if (this.props.iconName) {
|
||||
const iconStyle = {
|
||||
fontSize: Math.round(theme.fontSize * 1.5),
|
||||
color: theme.color
|
||||
color: theme.color,
|
||||
};
|
||||
if (title) iconStyle.marginRight = 5;
|
||||
icon = <i style={iconStyle} className={"fa " + this.props.iconName}></i>
|
||||
icon = <i style={iconStyle} className={'fa ' + this.props.iconName}></i>;
|
||||
}
|
||||
|
||||
const isEnabled = (!('enabled' in this.props) || this.props.enabled === true);
|
||||
const isEnabled = !('enabled' in this.props) || this.props.enabled === true;
|
||||
let classes = ['button'];
|
||||
if (!isEnabled) classes.push('disabled');
|
||||
|
||||
@ -36,13 +34,15 @@ class ToolbarButton extends React.Component {
|
||||
style={finalStyle}
|
||||
title={tooltip}
|
||||
href="#"
|
||||
onClick={() => { if (isEnabled && this.props.onClick) this.props.onClick() }}
|
||||
>
|
||||
{icon}{title}
|
||||
onClick={() => {
|
||||
if (isEnabled && this.props.onClick) this.props.onClick();
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ToolbarButton;
|
||||
module.exports = ToolbarButton;
|
||||
|
@ -1,22 +1,14 @@
|
||||
const React = require('react');
|
||||
const { connect } = require('react-redux');
|
||||
const { themeStyle } = require('../theme.js');
|
||||
|
||||
class ToolbarSpace extends React.Component {
|
||||
|
||||
render() {
|
||||
const theme = themeStyle(this.props.theme);
|
||||
const style = Object.assign({}, theme.toolbarStyle);
|
||||
style.minWidth = style.height / 2;
|
||||
|
||||
return (
|
||||
<span
|
||||
style={style}
|
||||
>
|
||||
</span>
|
||||
);
|
||||
return <span style={style}></span>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ToolbarSpace;
|
||||
module.exports = ToolbarSpace;
|
||||
|
@ -1,12 +1,11 @@
|
||||
const React = require("react");
|
||||
const React = require('react');
|
||||
const electron = require('electron');
|
||||
|
||||
class VerticalResizer extends React.PureComponent {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
this.state = {
|
||||
parentRight: 0,
|
||||
parentHeight: 0,
|
||||
parentWidth: 0,
|
||||
@ -29,17 +28,17 @@ class VerticalResizer extends React.PureComponent {
|
||||
}
|
||||
|
||||
onDragStart(event) {
|
||||
document.addEventListener('dragover', this.document_onDragOver)
|
||||
document.addEventListener('dragover', this.document_onDragOver);
|
||||
|
||||
event.dataTransfer.dropEffect= 'none';
|
||||
event.dataTransfer.dropEffect = 'none';
|
||||
|
||||
const cursor = electron.screen.getCursorScreenPoint();
|
||||
|
||||
const cursor = electron.screen.getCursorScreenPoint();
|
||||
|
||||
this.setState({
|
||||
drag: {
|
||||
startX: cursor.x,
|
||||
lastX: cursor.x,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (this.props.onDragStart) this.props.onDragStart({});
|
||||
@ -58,11 +57,14 @@ class VerticalResizer extends React.PureComponent {
|
||||
const delta = newX - this.state.drag.lastX;
|
||||
if (!delta) return;
|
||||
|
||||
this.setState({
|
||||
drag: Object.assign({}, this.state.drag, { lastX: newX }),
|
||||
}, () => {
|
||||
this.props.onDrag({ deltaX: delta });
|
||||
});
|
||||
this.setState(
|
||||
{
|
||||
drag: Object.assign({}, this.state.drag, { lastX: newX }),
|
||||
},
|
||||
() => {
|
||||
this.props.onDrag({ deltaX: delta });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onDragEnd(event) {
|
||||
@ -76,26 +78,22 @@ class VerticalResizer extends React.PureComponent {
|
||||
render() {
|
||||
const debug = false;
|
||||
|
||||
const rootStyle = Object.assign({}, {
|
||||
height: '100%',
|
||||
width:5,
|
||||
borderColor:'red',
|
||||
borderWidth: debug ? 1 : 0,
|
||||
borderStyle:'solid',
|
||||
cursor: 'col-resize',
|
||||
boxSizing: 'border-box',
|
||||
opacity: 0,
|
||||
}, this.props.style);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={rootStyle}
|
||||
draggable={true}
|
||||
onDragStart={this.onDragStart}
|
||||
onDrag={this.onDrag}
|
||||
onDragEnd={this.onDragEnd}
|
||||
/>
|
||||
const rootStyle = Object.assign(
|
||||
{},
|
||||
{
|
||||
height: '100%',
|
||||
width: 5,
|
||||
borderColor: 'red',
|
||||
borderWidth: debug ? 1 : 0,
|
||||
borderStyle: 'solid',
|
||||
cursor: 'col-resize',
|
||||
boxSizing: 'border-box',
|
||||
opacity: 0,
|
||||
},
|
||||
this.props.style
|
||||
);
|
||||
|
||||
return <div style={rootStyle} draggable={true} onDragStart={this.onDragStart} onDrag={this.onDrag} onDragEnd={this.onDragEnd} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
const smalltalk = require('smalltalk');
|
||||
|
||||
class Dialogs {
|
||||
|
||||
async alert(message, title = '') {
|
||||
await smalltalk.alert(title, message);
|
||||
}
|
||||
@ -25,7 +24,6 @@ class Dialogs {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dialogs = new Dialogs();
|
||||
|
@ -17,7 +17,7 @@ ipcRenderer.on('setMarkers', (event, keywords, options) => {
|
||||
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords, options: options } }, '*');
|
||||
});
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
window.addEventListener('message', event => {
|
||||
// Here we only deal with messages that are sent from the webview to the main Electron process
|
||||
if (!event.data || event.data.target !== 'main') return;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
const { time } = require('lib/time-utils.js');
|
||||
const BaseModel = require('lib/BaseModel');
|
||||
const markJsUtils = require('lib/markJsUtils');
|
||||
const { _ } = require('lib/locale.js');
|
||||
const { bridge } = require('electron').remote.require('./bridge');
|
||||
const Menu = bridge().Menu;
|
||||
@ -8,49 +6,62 @@ const MenuItem = bridge().MenuItem;
|
||||
const eventManager = require('../../eventManager');
|
||||
const InteropService = require('lib/services/InteropService');
|
||||
const InteropServiceHelper = require('../../InteropServiceHelper.js');
|
||||
const Search = require('lib/models/Search');
|
||||
const Note = require('lib/models/Note');
|
||||
const SearchEngine = require('lib/services/SearchEngine');
|
||||
const { replaceRegexDiacritics, pregQuote, substrWithEllipsis } = require('lib/string-utils');
|
||||
const { substrWithEllipsis } = require('lib/string-utils');
|
||||
|
||||
class NoteListUtils {
|
||||
|
||||
static makeContextMenu(noteIds, props) {
|
||||
const notes = noteIds.map((id) => BaseModel.byId(props.notes, id));
|
||||
const notes = noteIds.map(id => BaseModel.byId(props.notes, id));
|
||||
|
||||
let hasEncrypted = false;
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
if (!!notes[i].encryption_applied) hasEncrypted = true;
|
||||
if (notes[i].encryption_applied) hasEncrypted = true;
|
||||
}
|
||||
|
||||
const menu = new Menu()
|
||||
const menu = new Menu();
|
||||
|
||||
if (!hasEncrypted) {
|
||||
menu.append(new MenuItem({label: _('Add or remove tags'), enabled: noteIds.length === 1, click: async () => {
|
||||
props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'setTags',
|
||||
noteId: noteIds[0],
|
||||
});
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Add or remove tags'),
|
||||
enabled: noteIds.length === 1,
|
||||
click: async () => {
|
||||
props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'setTags',
|
||||
noteId: noteIds[0],
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(new MenuItem({label: _('Duplicate'), click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.duplicate(noteIds[i], {
|
||||
uniqueTitle: _('%s - Copy', note.title),
|
||||
});
|
||||
}
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Duplicate'),
|
||||
click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.duplicate(noteIds[i], {
|
||||
uniqueTitle: _('%s - Copy', note.title),
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
if (noteIds.length <= 1) {
|
||||
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id });
|
||||
}
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Switch between note and to-do type'),
|
||||
click: async () => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id });
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const switchNoteType = async (noteIds, type) => {
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
@ -60,26 +71,41 @@ class NoteListUtils {
|
||||
await Note.save(newNote, { userSideValidation: true });
|
||||
eventManager.emit('noteTypeToggle', { noteId: note.id });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
menu.append(new MenuItem({label: _('Switch to note type'), click: async () => {
|
||||
await switchNoteType(noteIds, 'note');
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Switch to note type'),
|
||||
click: async () => {
|
||||
await switchNoteType(noteIds, 'note');
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(new MenuItem({label: _('Switch to to-do type'), click: async () => {
|
||||
await switchNoteType(noteIds, 'todo');
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Switch to to-do type'),
|
||||
click: async () => {
|
||||
await switchNoteType(noteIds, 'todo');
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({label: _('Copy Markdown link'), click: async () => {
|
||||
const { clipboard } = require('electron');
|
||||
const links = [];
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
links.push(Note.markdownTag(note));
|
||||
}
|
||||
clipboard.writeText(links.join(' '));
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Copy Markdown link'),
|
||||
click: async () => {
|
||||
const { clipboard } = require('electron');
|
||||
const links = [];
|
||||
for (let i = 0; i < noteIds.length; i++) {
|
||||
const note = await Note.load(noteIds[i]);
|
||||
links.push(Note.markdownTag(note));
|
||||
}
|
||||
clipboard.writeText(links.join(' '));
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const exportMenu = new Menu();
|
||||
|
||||
@ -89,28 +115,43 @@ class NoteListUtils {
|
||||
const module = ioModules[i];
|
||||
if (module.type !== 'exporter') continue;
|
||||
|
||||
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => {
|
||||
await InteropServiceHelper.export(props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
|
||||
}}));
|
||||
exportMenu.append(
|
||||
new MenuItem({
|
||||
label: module.fullLabel(),
|
||||
click: async () => {
|
||||
await InteropServiceHelper.export(props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (noteIds.length === 1) {
|
||||
exportMenu.append(new MenuItem({ label: 'PDF - ' + _('PDF File') , click: () => {
|
||||
props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'exportPdf',
|
||||
});
|
||||
}}));
|
||||
exportMenu.append(
|
||||
new MenuItem({
|
||||
label: 'PDF - ' + _('PDF File'),
|
||||
click: () => {
|
||||
props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'exportPdf',
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const exportMenuItem = new MenuItem({label: _('Export'), submenu: exportMenu});
|
||||
const exportMenuItem = new MenuItem({ label: _('Export'), submenu: exportMenu });
|
||||
|
||||
menu.append(exportMenuItem);
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({label: _('Delete'), click: async () => {
|
||||
await this.confirmDeleteNotes(noteIds);
|
||||
}}));
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Delete'),
|
||||
click: async () => {
|
||||
await this.confirmDeleteNotes(noteIds);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return menu;
|
||||
}
|
||||
@ -135,7 +176,6 @@ class NoteListUtils {
|
||||
if (!ok) return;
|
||||
await Note.batchDelete(noteIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = NoteListUtils;
|
||||
module.exports = NoteListUtils;
|
||||
|
Loading…
Reference in New Issue
Block a user