1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-13 22:12:50 +02:00

Started applying config to Electron app

This commit is contained in:
Laurent Cozic
2019-07-29 14:13:23 +02:00
parent 4fe70fe8ee
commit 086f9e1123
32 changed files with 1560 additions and 1262 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,18 +1,14 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const Setting = require('lib/models/Setting.js'); const Setting = require('lib/models/Setting.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const pathUtils = require('lib/path-utils.js'); const pathUtils = require('lib/path-utils.js');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { commandArgumentsToString } = require('lib/string-utils');
const SyncTargetRegistry = require('lib/SyncTargetRegistry'); const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const shared = require('lib/components/shared/config-shared.js'); const shared = require('lib/components/shared/config-shared.js');
class ConfigScreenComponent extends React.Component { class ConfigScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -20,7 +16,7 @@ class ConfigScreenComponent extends React.Component {
this.checkSyncConfig_ = async () => { this.checkSyncConfig_ = async () => {
await shared.checkSyncConfig(this, this.state.settings); await shared.checkSyncConfig(this, this.state.settings);
} };
this.rowStyle_ = { this.rowStyle_ = {
marginBottom: 10, marginBottom: 10,
@@ -70,11 +66,7 @@ class ConfigScreenComponent extends React.Component {
sectionStyle.borderTopWidth = 0; sectionStyle.borderTopWidth = 0;
} }
const noteComp = section.name !== 'general' ? null : ( 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>;
<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') { if (section.name === 'sync') {
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']); const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
@@ -85,14 +77,18 @@ class ConfigScreenComponent extends React.Component {
const statusComp = !messages.length ? null : ( const statusComp = !messages.length ? null : (
<div style={statusStyle}> <div style={statusStyle}>
{messages[0]} {messages[0]}
{messages.length >= 1 ? (<p>{messages[1]}</p>) : null} {messages.length >= 1 ? <p>{messages[1]}</p> : null}
</div>); </div>
);
settingComps.push( settingComps.push(
<div key="check_sync_config_button" style={this.rowStyle_}> <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> <button disabled={this.state.checkSyncConfigResult === 'checking'} style={theme.buttonStyle} onClick={this.checkSyncConfig_}>
{ statusComp } {_('Check synchronisation configuration')}
</div>); </button>
{statusComp}
</div>
);
} }
} }
@@ -100,9 +96,7 @@ class ConfigScreenComponent extends React.Component {
<div key={key} style={sectionStyle}> <div key={key} style={sectionStyle}>
<h2 style={headerStyle}>{Setting.sectionNameToLabel(section.name)}</h2> <h2 style={headerStyle}>{Setting.sectionNameToLabel(section.name)}</h2>
{noteComp} {noteComp}
<div> <div>{settingComps}</div>
{settingComps}
</div>
</div> </div>
); );
} }
@@ -145,18 +139,14 @@ class ConfigScreenComponent extends React.Component {
const updateSettingValue = (key, value) => { const updateSettingValue = (key, value) => {
// console.info(key + ' = ' + value); // console.info(key + ' = ' + value);
return shared.updateSettingValue(this, key, value); return shared.updateSettingValue(this, key, value);
} };
// Component key needs to be key+value otherwise it doesn't update when the settings change. // Component key needs to be key+value otherwise it doesn't update when the settings change.
const md = Setting.settingMetadata(key); const md = Setting.settingMetadata(key);
const descriptionText = Setting.keyDescription(key, 'desktop'); const descriptionText = Setting.keyDescription(key, 'desktop');
const descriptionComp = descriptionText ? ( const descriptionComp = descriptionText ? <div style={descriptionStyle}>{descriptionText}</div> : null;
<div style={descriptionStyle}>
{descriptionText}
</div>
) : null;
if (md.isEnum) { if (md.isEnum) {
let items = []; let items = [];
@@ -164,31 +154,59 @@ class ConfigScreenComponent extends React.Component {
let array = this.keyValueToArray(settingOptions); let array = this.keyValueToArray(settingOptions);
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
const e = array[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 ( return (
<div key={key} style={rowStyle}> <div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div> <div style={labelStyle}>
<select value={value} style={controlStyle} onChange={(event) => { updateSettingValue(key, event.target.value) }}> <label>{md.label()}</label>
</div>
<select
value={value}
style={controlStyle}
onChange={event => {
updateSettingValue(key, event.target.value);
}}
>
{items} {items}
</select> </select>
{ descriptionComp } {descriptionComp}
</div> </div>
); );
} else if (md.type === Setting.TYPE_BOOL) { } else if (md.type === Setting.TYPE_BOOL) {
const onCheckboxClick = (event) => { const onCheckboxClick = event => {
updateSettingValue(key, !value) updateSettingValue(key, !value);
} };
// Hack: The {key+value.toString()} is needed as otherwise the checkbox doesn't update when the state changes. // 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. // There's probably a better way to do this but can't figure it out.
return ( return (
<div key={key+value.toString()} style={rowStyle}> <div key={key + value.toString()} style={rowStyle}>
<div style={controlStyle}> <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> <input
{ descriptionComp } 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>
</div> </div>
); );
@@ -196,7 +214,8 @@ class ConfigScreenComponent extends React.Component {
const inputStyle = Object.assign({}, controlStyle, { const inputStyle = Object.assign({}, controlStyle, {
width: '50%', width: '50%',
minWidth: '20em', minWidth: '20em',
border: '1px solid' }); border: '1px solid',
});
const inputType = md.secure === true ? 'password' : 'text'; const inputType = md.secure === true ? 'password' : 'text';
if (md.subType === 'file_path_and_args') { if (md.subType === 'file_path_and_args') {
@@ -206,7 +225,7 @@ class ConfigScreenComponent extends React.Component {
const path = pathUtils.extractExecutablePath(cmdString); const path = pathUtils.extractExecutablePath(cmdString);
const args = cmdString.substr(path.length + 1); const args = cmdString.substr(path.length + 1);
return [pathUtils.unquotePath(path), args]; return [pathUtils.unquotePath(path), args];
} };
const joinCmd = cmdArray => { const joinCmd = cmdArray => {
if (!cmdArray[0] && !cmdArray[1]) return ''; if (!cmdArray[0] && !cmdArray[1]) return '';
@@ -214,74 +233,101 @@ class ConfigScreenComponent extends React.Component {
if (!cmdString) cmdString = '""'; if (!cmdString) cmdString = '""';
if (cmdArray[1]) cmdString += ' ' + cmdArray[1]; if (cmdArray[1]) cmdString += ' ' + cmdArray[1];
return cmdString; return cmdString;
} };
const onPathChange = event => { const onPathChange = event => {
const cmd = splitCmd(this.state.settings[key]); const cmd = splitCmd(this.state.settings[key]);
cmd[0] = event.target.value; cmd[0] = event.target.value;
updateSettingValue(key, joinCmd(cmd)); updateSettingValue(key, joinCmd(cmd));
} };
const onArgsChange = event => { const onArgsChange = event => {
const cmd = splitCmd(this.state.settings[key]); const cmd = splitCmd(this.state.settings[key]);
cmd[1] = event.target.value; cmd[1] = event.target.value;
updateSettingValue(key, joinCmd(cmd)); updateSettingValue(key, joinCmd(cmd));
} };
const browseButtonClick = () => { const browseButtonClick = () => {
const paths = bridge().showOpenDialog(); const paths = bridge().showOpenDialog();
if (!paths || !paths.length) return; if (!paths || !paths.length) return;
const cmd = splitCmd(this.state.settings[key]); const cmd = splitCmd(this.state.settings[key]);
cmd[0] = paths[0] cmd[0] = paths[0];
updateSettingValue(key, joinCmd(cmd)); updateSettingValue(key, joinCmd(cmd));
} };
const cmd = splitCmd(this.state.settings[key]); const cmd = splitCmd(this.state.settings[key]);
return ( return (
<div key={key} style={rowStyle}> <div key={key} style={rowStyle}>
<div style={{display:'flex'}}> <div style={{ display: 'flex' }}>
<div style={{flex:0, whiteSpace: 'nowrap'}}> <div style={{ flex: 0, whiteSpace: 'nowrap' }}>
<div style={labelStyle}><label>{md.label()}</label></div> <div style={labelStyle}>
<label>{md.label()}</label>
</div> </div>
<div style={{flex:0}}> </div>
<div style={{ flex: 0 }}>
<div style={subLabel}>Path:</div> <div style={subLabel}>Path:</div>
<div style={subLabel}>Arguments:</div> <div style={subLabel}>Arguments:</div>
</div> </div>
<div style={{flex:1}}> <div style={{ flex: 1 }}>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: inputStyle.marginBottom}}> <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]} /> <input
<button onClick={browseButtonClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 5, minHeight: 20, height: 20 })}>{_('Browse...')}</button> 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> </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> </div>
<div style={{display:'flex'}}> <div style={{ display: 'flex' }}>
<div style={{flex:0, whiteSpace: 'nowrap'}}> <div style={{ flex: 0, whiteSpace: 'nowrap' }}>
<div style={invisibleLabel}><label>{md.label()}</label></div> <div style={invisibleLabel}>
<label>{md.label()}</label>
</div> </div>
<div style={{flex:1}}>
{ descriptionComp }
</div> </div>
<div style={{ flex: 1 }}>{descriptionComp}</div>
</div> </div>
</div> </div>
); );
} else { } else {
const onTextChange = (event) => { const onTextChange = event => {
updateSettingValue(key, event.target.value); updateSettingValue(key, event.target.value);
} };
return ( return (
<div key={key} style={rowStyle}> <div key={key} style={rowStyle}>
<div style={labelStyle}><label>{md.label()}</label></div> <div style={labelStyle}>
<input type={inputType} style={inputStyle} value={this.state.settings[key]} onChange={(event) => {onTextChange(event)}} /> <label>{md.label()}</label>
{ descriptionComp } </div>
<input
type={inputType}
style={inputStyle}
value={this.state.settings[key]}
onChange={event => {
onTextChange(event);
}}
/>
{descriptionComp}
</div> </div>
); );
} }
} else if (md.type === Setting.TYPE_INT) { } else if (md.type === Setting.TYPE_INT) {
const onNumChange = (event) => { const onNumChange = event => {
updateSettingValue(key, event.target.value); updateSettingValue(key, event.target.value);
}; };
@@ -290,9 +336,21 @@ class ConfigScreenComponent extends React.Component {
return ( return (
<div key={key} style={rowStyle}> <div key={key} style={rowStyle}>
<div style={labelStyle}><label>{label.join(' ')}</label></div> <div style={labelStyle}>
<input type="number" style={controlStyle} value={this.state.settings[key]} onChange={(event) => {onNumChange(event)}} min={md.minimum} max={md.maximum} step={md.step}/> <label>{label.join(' ')}</label>
{ descriptionComp } </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> </div>
); );
} else { } else {
@@ -318,13 +376,17 @@ class ConfigScreenComponent extends React.Component {
render() { render() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
const style = Object.assign({ const style = Object.assign(
backgroundColor: theme.backgroundColor {
}, this.props.style, { backgroundColor: theme.backgroundColor,
},
this.props.style,
{
overflow: 'hidden', overflow: 'hidden',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
}); }
);
let settings = this.state.settings; let settings = this.state.settings;
@@ -355,20 +417,41 @@ class ConfigScreenComponent extends React.Component {
return ( return (
<div style={style}> <div style={style}>
<div style={buttonBarStyle}> <div style={buttonBarStyle}>
<button onClick={() => {this.onCancelClick()}} style={buttonStyle}><i style={theme.buttonIconStyle} className={"fa fa-chevron-left"}></i>{_('Cancel')}</button> <button
<button disabled={!hasChanges} onClick={() => {this.onSaveClick()}} style={buttonStyleApprove}>{_('OK')}</button> onClick={() => {
<button disabled={!hasChanges} onClick={() => {this.onApplyClick()}} style={buttonStyleApprove}>{_('Apply')}</button> this.onCancelClick();
</div> }}
<div style={containerStyle}> style={buttonStyle}
{ settingComps } >
<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>
<div style={containerStyle}>{settingComps}</div>
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
settings: state.settings, settings: state.settings,

View File

@@ -1,23 +1,16 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js'); const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const Shared = require('lib/components/shared/dropbox-login-shared'); const Shared = require('lib/components/shared/dropbox-login-shared');
class DropboxLoginScreenComponent extends React.Component { class DropboxLoginScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
this.shared_ = new Shared( this.shared_ = new Shared(this, msg => bridge().showInfoMessageBox(msg), msg => bridge().showErrorMessageBox(msg));
this,
(msg) => bridge().showInfoMessageBox(msg),
(msg) => bridge().showErrorMessageBox(msg)
);
} }
componentWillMount() { componentWillMount() {
@@ -42,18 +35,23 @@ class DropboxLoginScreenComponent extends React.Component {
<div style={containerStyle}> <div style={containerStyle}>
<p style={theme.textStyle}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</p> <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> <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 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> <p>
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>{_('Submit')}</button> <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>
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
}; };

View File

@@ -1,7 +1,6 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const Setting = require('lib/models/Setting'); const Setting = require('lib/models/Setting');
const BaseItem = require('lib/models/BaseItem');
const EncryptionService = require('lib/services/EncryptionService'); const EncryptionService = require('lib/services/EncryptionService');
const { Header } = require('./Header.min.js'); const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
@@ -9,11 +8,9 @@ const { _ } = require('lib/locale.js');
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
const dialogs = require('./dialogs'); const dialogs = require('./dialogs');
const shared = require('lib/components/shared/encryption-config-shared.js'); const shared = require('lib/components/shared/encryption-config-shared.js');
const pathUtils = require('lib/path-utils.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
class EncryptionConfigScreenComponent extends React.Component { class EncryptionConfigScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
shared.constructor(this); shared.constructor(this);
@@ -55,15 +52,15 @@ class EncryptionConfigScreenComponent extends React.Component {
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
border: '1px solid', border: '1px solid',
borderColor: theme.dividerColor, borderColor: theme.dividerColor,
} };
const onSaveClick = () => { const onSaveClick = () => {
return shared.onSavePasswordClick(this, mk); return shared.onSavePasswordClick(this, mk);
} };
const onPasswordChange = (event) => { const onPasswordChange = event => {
return shared.onPasswordChange(this, mk, event.target.value); return shared.onPasswordChange(this, mk, event.target.value);
} };
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : ''; const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
const active = this.props.activeMasterKeyId === 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}>{mk.source_application}</td>
<td style={theme.textStyle}>{time.formatMsToLocal(mk.created_time)}</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}>{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> <td style={theme.textStyle}>{passwordOk}</td>
</tr> </tr>
); );
@@ -128,10 +130,19 @@ class EncryptionConfigScreenComponent extends React.Component {
} catch (error) { } catch (error) {
await dialogs.alert(error.message); await dialogs.alert(error.message);
} }
} };
const decryptedItemsInfo = <p style={theme.textStyle}>{shared.decryptedStatText(this)}</p>; 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; let masterKeySection = null;
@@ -164,7 +175,11 @@ class EncryptionConfigScreenComponent extends React.Component {
const rows = []; const rows = [];
for (let i = 0; i < nonExistingMasterKeyIds.length; i++) { for (let i = 0; i < nonExistingMasterKeyIds.length; i++) {
const id = nonExistingMasterKeyIds[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 = ( nonExistingMasterKeySection = (
@@ -176,7 +191,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<tr> <tr>
<th style={theme.textStyle}>{_('ID')}</th> <th style={theme.textStyle}>{_('ID')}</th>
</tr> </tr>
{ rows } {rows}
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -187,13 +202,25 @@ class EncryptionConfigScreenComponent extends React.Component {
<div> <div>
<Header style={headerStyle} /> <Header style={headerStyle} />
<div style={containerStyle}> <div style={containerStyle}>
{<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}> {
<div style={{ backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
<p style={theme.textStyle}> <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> <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> </p>
</div>} </div>
}
<h1 style={theme.h1Style}>{_('Status')}</h1> <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} {decryptedItemsInfo}
{toggleButton} {toggleButton}
{masterKeySection} {masterKeySection}
@@ -202,10 +229,9 @@ class EncryptionConfigScreenComponent extends React.Component {
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
masterKeys: state.masterKeys, masterKeys: state.masterKeys,

View File

@@ -1,12 +1,10 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
class HeaderComponent extends React.Component { class HeaderComponent extends React.Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@@ -18,13 +16,13 @@ class HeaderComponent extends React.Component {
this.searchOnQuery_ = null; this.searchOnQuery_ = null;
this.searchElement_ = null; this.searchElement_ = null;
const triggerOnQuery = (query) => { const triggerOnQuery = query => {
clearTimeout(this.scheduleSearchChangeEventIid_); clearTimeout(this.scheduleSearchChangeEventIid_);
if (this.searchOnQuery_) this.searchOnQuery_(query); if (this.searchOnQuery_) this.searchOnQuery_(query);
this.scheduleSearchChangeEventIid_ = null; this.scheduleSearchChangeEventIid_ = null;
} };
this.search_onChange = (event) => { this.search_onChange = event => {
this.setState({ searchQuery: event.target.value }); this.setState({ searchQuery: event.target.value });
if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_); if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_);
@@ -34,10 +32,10 @@ class HeaderComponent extends React.Component {
}, 500); }, 500);
}; };
this.search_onClear = (event) => { this.search_onClear = event => {
this.resetSearch(); this.resetSearch();
if (this.searchElement_) this.searchElement_.focus(); if (this.searchElement_) this.searchElement_.focus();
} };
this.search_onFocus = event => { this.search_onFocus = event => {
if (this.hideSearchUsageLinkIID_) { if (this.hideSearchUsageLinkIID_) {
@@ -46,7 +44,7 @@ class HeaderComponent extends React.Component {
} }
this.setState({ showSearchUsageLink: true }); this.setState({ showSearchUsageLink: true });
} };
this.search_onBlur = event => { this.search_onBlur = event => {
if (this.hideSearchUsageLinkIID_) return; if (this.hideSearchUsageLinkIID_) return;
@@ -54,22 +52,23 @@ class HeaderComponent extends React.Component {
this.hideSearchUsageLinkIID_ = setTimeout(() => { this.hideSearchUsageLinkIID_ = setTimeout(() => {
this.setState({ showSearchUsageLink: false }); this.setState({ showSearchUsageLink: false });
}, 5000); }, 5000);
} };
this.search_keyDown = event => { this.search_keyDown = event => {
if (event.keyCode === 27) { // ESCAPE if (event.keyCode === 27) {
// ESCAPE
this.resetSearch(); this.resetSearch();
} }
} };
this.resetSearch = () => { this.resetSearch = () => {
this.setState({ searchQuery: '' }); this.setState({ searchQuery: '' });
triggerOnQuery(''); triggerOnQuery('');
} };
this.searchUsageLink_click = event => { this.searchUsageLink_click = event => {
bridge().openExternal('https://joplinapp.org/#searching'); bridge().openExternal('https://joplinapp.org/#searching');
} };
} }
async componentWillReceiveProps(nextProps) { async componentWillReceiveProps(nextProps) {
@@ -79,7 +78,7 @@ class HeaderComponent extends React.Component {
} }
componentDidUpdate(prevProps) { 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(); this.resetSearch();
} }
} }
@@ -122,14 +121,14 @@ class HeaderComponent extends React.Component {
color: style.color, color: style.color,
}; };
if (options.title) iconStyle.marginRight = 5; if (options.title) iconStyle.marginRight = 5;
if("undefined" != typeof(options.iconRotation)) { if ('undefined' != typeof options.iconRotation) {
iconStyle.transition = "transform 0.15s ease-in-out"; iconStyle.transition = 'transform 0.15s ease-in-out';
iconStyle.transform = 'rotate(' + options.iconRotation + 'deg)'; 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']; let classes = ['button'];
if (!isEnabled) classes.push('disabled'); if (!isEnabled) classes.push('disabled');
@@ -139,16 +138,21 @@ class HeaderComponent extends React.Component {
const title = options.title ? options.title : ''; const title = options.title ? options.title : '';
return <a return (
<a
className={classes.join(' ')} className={classes.join(' ')}
style={finalStyle} style={finalStyle}
key={key} key={key}
href="#" href="#"
title={title} title={title}
onClick={() => { if (isEnabled) options.onClick() }} onClick={() => {
if (isEnabled) options.onClick();
}}
> >
{icon}<span className="title">{title}</span> {icon}
<span className="title">{title}</span>
</a> </a>
);
} }
makeSearch(key, style, options, state) { makeSearch(key, style, options, state) {
@@ -191,33 +195,24 @@ class HeaderComponent extends React.Component {
}; };
const iconName = state.searchQuery ? 'fa-times' : 'fa-search'; 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; if (options.onQuery) this.searchOnQuery_ = options.onQuery;
const usageLink = !this.state.showSearchUsageLink ? null : ( 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 ( return (
<div key={key} style={containerStyle}> <div key={key} style={containerStyle}>
<input <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} />
type="text" <a href="#" style={searchButton} onClick={this.search_onClear}>
style={inputStyle} {icon}
placeholder={options.title} </a>
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} {usageLink}
</div>); </div>
);
} }
render() { render() {
@@ -266,14 +261,13 @@ class HeaderComponent extends React.Component {
return ( return (
<div className="header" style={style}> <div className="header" style={style}>
{ items } {items}
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
windowCommand: state.windowCommand, windowCommand: state.windowCommand,

View File

@@ -1,12 +1,8 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge');
class HelpButtonComponent extends React.Component { class HelpButtonComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -19,16 +15,19 @@ class HelpButtonComponent extends React.Component {
render() { render() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
let style = Object.assign({}, this.props.style, {color: theme.color, textDecoration: 'none'}); let style = Object.assign({}, this.props.style, { color: theme.color, textDecoration: 'none' });
const helpIconStyle = {flex:0, width: 16, height: 16, marginLeft: 10}; const helpIconStyle = { flex: 0, width: 16, height: 16, marginLeft: 10 };
const extraProps = {}; const extraProps = {};
if (this.props.tip) extraProps['data-tip'] = this.props.tip; 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 { return {
theme: state.settings.theme, theme: state.settings.theme,
}; };

View File

@@ -1,9 +1,7 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
class IconButton extends React.Component { class IconButton extends React.Component {
render() { render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
@@ -11,9 +9,10 @@ class IconButton extends React.Component {
color: theme.color, color: theme.color,
fontSize: theme.fontSize * 1.4, 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({ const rootStyle = Object.assign(
{
display: 'flex', display: 'flex',
textDecoration: 'none', textDecoration: 'none',
padding: 10, padding: 10,
@@ -24,15 +23,23 @@ class IconButton extends React.Component {
justifyContent: 'center', justifyContent: 'center',
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
cursor: 'default', cursor: 'default',
}, style); },
style
);
return ( return (
<a href="#" style={rootStyle} className="icon-button" onClick={() => { if (this.props.onClick) this.props.onClick() }}> <a
{ icon } href="#"
style={rootStyle}
className="icon-button"
onClick={() => {
if (this.props.onClick) this.props.onClick();
}}
>
{icon}
</a> </a>
); );
} }
} }
module.exports = { IconButton }; module.exports = { IconButton };

View File

@@ -1,8 +1,6 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const Folder = require('lib/models/Folder.js'); const Folder = require('lib/models/Folder.js');
const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js'); const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
@@ -10,7 +8,6 @@ const { filename, basename } = require('lib/path-utils.js');
const { importEnex } = require('lib/import-enex'); const { importEnex } = require('lib/import-enex');
class ImportScreenComponent extends React.Component { class ImportScreenComponent extends React.Component {
componentWillMount() { componentWillMount() {
this.setState({ this.setState({
doImport: true, doImport: true,
@@ -21,11 +18,16 @@ class ImportScreenComponent extends React.Component {
componentWillReceiveProps(newProps) { componentWillReceiveProps(newProps) {
if (newProps.filePath) { if (newProps.filePath) {
this.setState({ this.setState(
{
doImport: true, doImport: true,
filePath: newProps.filePath, filePath: newProps.filePath,
messages: [], messages: [],
}, () => { this.doImport() }); },
() => {
this.doImport();
}
);
} }
} }
@@ -37,7 +39,6 @@ class ImportScreenComponent extends React.Component {
addMessage(key, text) { addMessage(key, text) {
const messages = this.state.messages.slice(); const messages = this.state.messages.slice();
let found = false;
messages.push({ key: key, text: text }); messages.push({ key: key, text: text });
@@ -60,15 +61,13 @@ class ImportScreenComponent extends React.Component {
async doImport() { async doImport() {
const filePath = this.props.filePath; const filePath = this.props.filePath;
const folderTitle = await Folder.findUniqueItemTitle(filename(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))); this.addMessage('start', _('New notebook "%s" will be created and file "%s" will be imported into it', folderTitle, basename(filePath)));
let lastProgress = ''; let lastProgress = '';
let progressCount = 0;
const options = { const options = {
onProgress: (progressState) => { onProgress: progressState => {
let line = []; let line = [];
line.push(_('Found: %d.', progressState.loaded)); line.push(_('Found: %d.', progressState.loaded));
line.push(_('Created: %d.', progressState.created)); line.push(_('Created: %d.', progressState.created));
@@ -79,12 +78,12 @@ class ImportScreenComponent extends React.Component {
lastProgress = line.join(' '); lastProgress = line.join(' ');
this.addMessage('progress', lastProgress); this.addMessage('progress', lastProgress);
}, },
onError: (error) => { onError: error => {
// Don't display the error directly because most of the time it doesn't matter // 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) // (eg. for weird broken HTML, but the note is still imported)
console.warn('When importing ENEX file', error); console.warn('When importing ENEX file', error);
}, },
} };
const folder = await Folder.save({ title: folderTitle }); const folder = await Folder.save({ title: folderTitle });
@@ -118,16 +117,13 @@ class ImportScreenComponent extends React.Component {
return ( return (
<div style={{}}> <div style={{}}>
<Header style={headerStyle} /> <Header style={headerStyle} />
<div style={messagesStyle}> <div style={messagesStyle}>{messageComps}</div>
{messageComps}
</div>
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
}; };

View File

@@ -1,7 +1,6 @@
const React = require('react'); const React = require('react');
class ItemList extends React.Component { class ItemList extends React.Component {
constructor() { constructor() {
super(); super();
@@ -52,7 +51,7 @@ class ItemList extends React.Component {
makeItemIndexVisible(itemIndex) { makeItemIndexVisible(itemIndex) {
const top = Math.min(this.props.items.length - 1, this.state.topItemIndex + 1); 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; 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'); if (!this.props.itemHeight) throw new Error('itemHeight is required');
const blankItem = function(key, height) { 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)]; let itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)];
@@ -98,7 +97,7 @@ class ItemList extends React.Component {
return ( return (
<div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}> <div ref={this.listRef} className={classes.join(' ')} style={style} onScroll={this.onScroll} onKeyDown={this.onKeyDown}>
{ itemComps } {itemComps}
</div> </div>
); );
} }

View File

@@ -20,7 +20,6 @@ const VerticalResizer = require('./VerticalResizer.min');
const PluginManager = require('lib/services/PluginManager'); const PluginManager = require('lib/services/PluginManager');
class MainScreenComponent extends React.Component { class MainScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -87,7 +86,7 @@ class MainScreenComponent extends React.Component {
type: 'NOTE_SET_NEW_ONE', type: 'NOTE_SET_NEW_ONE',
item: newNote, item: newNote,
}); });
} };
let commandProcessed = true; let commandProcessed = true;
@@ -107,7 +106,7 @@ class MainScreenComponent extends React.Component {
this.setState({ this.setState({
promptOptions: { promptOptions: {
label: _('Notebook title:'), label: _('Notebook title:'),
onClose: async (answer) => { onClose: async answer => {
if (answer) { if (answer) {
let folder = null; let folder = null;
try { try {
@@ -125,14 +124,22 @@ class MainScreenComponent extends React.Component {
} }
this.setState({ promptOptions: null }); this.setState({ promptOptions: null });
} },
}, },
}); });
} else if (command.name === 'setTags') { } else if (command.name === 'setTags') {
const tags = await Tag.tagsByNoteId(command.noteId); 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 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({ this.setState({
promptOptions: { promptOptions: {
@@ -140,13 +147,15 @@ class MainScreenComponent extends React.Component {
inputType: 'tags', inputType: 'tags',
value: noteTags, value: noteTags,
autocomplete: tagSuggestions, autocomplete: tagSuggestions,
onClose: async (answer) => { onClose: async answer => {
if (answer !== null) { 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); await Tag.setNoteTagsByTitles(command.noteId, tagTitles);
} }
this.setState({ promptOptions: null }); this.setState({ promptOptions: null });
} },
}, },
}); });
} else if (command.name === 'renameFolder') { } else if (command.name === 'renameFolder') {
@@ -157,7 +166,7 @@ class MainScreenComponent extends React.Component {
promptOptions: { promptOptions: {
label: _('Rename notebook:'), label: _('Rename notebook:'),
value: folder.title, value: folder.title,
onClose: async (answer) => { onClose: async answer => {
if (answer !== null) { if (answer !== null) {
try { try {
folder.title = answer; folder.title = answer;
@@ -167,7 +176,7 @@ class MainScreenComponent extends React.Component {
} }
} }
this.setState({ promptOptions: null }); this.setState({ promptOptions: null });
} },
}, },
}); });
} }
@@ -178,7 +187,7 @@ class MainScreenComponent extends React.Component {
promptOptions: { promptOptions: {
label: _('Rename tag:'), label: _('Rename tag:'),
value: tag.title, value: tag.title,
onClose: async (answer) => { onClose: async answer => {
if (answer !== null) { if (answer !== null) {
try { try {
tag.title = answer; tag.title = answer;
@@ -187,13 +196,12 @@ class MainScreenComponent extends React.Component {
bridge().showErrorMessageBox(error.message); bridge().showErrorMessageBox(error.message);
} }
} }
this.setState({promptOptions: null }); this.setState({ promptOptions: null });
} },
} },
}) });
} }
} else if (command.name === 'search') { } else if (command.name === 'search') {
if (!this.searchId_) this.searchId_ = uuid.create(); if (!this.searchId_) this.searchId_ = uuid.create();
this.props.dispatch({ this.props.dispatch({
@@ -222,7 +230,6 @@ class MainScreenComponent extends React.Component {
}); });
} }
} }
} else if (command.name === 'commandNoteProperties') { } else if (command.name === 'commandNoteProperties') {
this.setState({ this.setState({
notePropertiesDialogOptions: { notePropertiesDialogOptions: {
@@ -273,7 +280,7 @@ class MainScreenComponent extends React.Component {
} }
this.setState({ promptOptions: null }); this.setState({ promptOptions: null });
} },
}, },
}); });
} else if (command.name === 'selectTemplate') { } else if (command.name === 'selectTemplate') {
@@ -283,7 +290,7 @@ class MainScreenComponent extends React.Component {
inputType: 'dropdown', inputType: 'dropdown',
value: this.props.templates[0], // Need to start with some value value: this.props.templates[0], // Need to start with some value
autocomplete: this.props.templates, autocomplete: this.props.templates,
onClose: async (answer) => { onClose: async answer => {
if (answer) { if (answer) {
if (command.noteType === 'note' || command.noteType === 'todo') { if (command.noteType === 'note' || command.noteType === 'todo') {
createNewNote(answer.value, command.noteType === 'todo'); createNewNote(answer.value, command.noteType === 'todo');
@@ -297,7 +304,7 @@ class MainScreenComponent extends React.Component {
} }
this.setState({ promptOptions: null }); this.setState({ promptOptions: null });
} },
}, },
}); });
} else { } else {
@@ -313,7 +320,7 @@ class MainScreenComponent extends React.Component {
} }
styles(themeId, width, height, messageBoxVisible, isSidebarVisible, sidebarWidth, noteListWidth) { 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_; if (styleKey === this.styleKey_) return this.styles_;
const theme = themeStyle(themeId); const theme = themeStyle(themeId);
@@ -333,7 +340,7 @@ class MainScreenComponent extends React.Component {
alignItems: 'center', alignItems: 'center',
paddingLeft: 10, paddingLeft: 10,
backgroundColor: theme.warningBackgroundColor, backgroundColor: theme.warningBackgroundColor,
} };
this.styles_.verticalResizer = { this.styles_.verticalResizer = {
width: 5, width: 5,
@@ -390,10 +397,13 @@ class MainScreenComponent extends React.Component {
render() { render() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
const style = Object.assign({ const style = Object.assign(
{
color: theme.color, color: theme.color,
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
}, this.props.style); },
this.props.style
);
const promptOptions = this.state.promptOptions; const promptOptions = this.state.promptOptions;
const folders = this.props.folders; const folders = this.props.folders;
const notes = this.props.notes; const notes = this.props.notes;
@@ -408,47 +418,59 @@ class MainScreenComponent extends React.Component {
title: _('Toggle sidebar'), title: _('Toggle sidebar'),
iconName: 'fa-bars', iconName: 'fa-bars',
iconRotation: this.props.sidebarVisibility ? 0 : 90, iconRotation: this.props.sidebarVisibility ? 0 : 90,
onClick: () => { this.doCommand({ name: 'toggleSidebar'}) } onClick: () => {
this.doCommand({ name: 'toggleSidebar' });
},
}); });
headerItems.push({ headerItems.push({
title: _('New note'), title: _('New note'),
iconName: 'fa-file-o', iconName: 'fa-file-o',
enabled: !!folders.length && !onConflictFolder, enabled: !!folders.length && !onConflictFolder,
onClick: () => { this.doCommand({ name: 'newNote' }) }, onClick: () => {
this.doCommand({ name: 'newNote' });
},
}); });
headerItems.push({ headerItems.push({
title: _('New to-do'), title: _('New to-do'),
iconName: 'fa-check-square-o', iconName: 'fa-check-square-o',
enabled: !!folders.length && !onConflictFolder, enabled: !!folders.length && !onConflictFolder,
onClick: () => { this.doCommand({ name: 'newTodo' }) }, onClick: () => {
this.doCommand({ name: 'newTodo' });
},
}); });
headerItems.push({ headerItems.push({
title: _('New notebook'), title: _('New notebook'),
iconName: 'fa-book', iconName: 'fa-book',
onClick: () => { this.doCommand({ name: 'newNotebook' }) }, onClick: () => {
this.doCommand({ name: 'newNotebook' });
},
}); });
headerItems.push({ headerItems.push({
title: _('Layout'), title: _('Layout'),
iconName: 'fa-columns', iconName: 'fa-columns',
enabled: !!notes.length, enabled: !!notes.length,
onClick: () => { this.doCommand({ name: 'toggleVisiblePanes' }) }, onClick: () => {
this.doCommand({ name: 'toggleVisiblePanes' });
},
}); });
headerItems.push({ headerItems.push({
title: _('Search...'), title: _('Search...'),
iconName: 'fa-search', iconName: 'fa-search',
onQuery: (query) => { this.doCommand({ name: 'search', query: query }) }, onQuery: query => {
this.doCommand({ name: 'search', query: query });
},
type: 'search', type: 'search',
}); });
if (!this.promptOnClose_) { if (!this.promptOnClose_) {
this.promptOnClose_ = (answer, buttonType) => { this.promptOnClose_ = (answer, buttonType) => {
return this.state.promptOptions.onClose(answer, buttonType); return this.state.promptOptions.onClose(answer, buttonType);
} };
} }
const onViewDisabledItemsClick = () => { const onViewDisabledItemsClick = () => {
@@ -456,36 +478,58 @@ class MainScreenComponent extends React.Component {
type: 'NAV_GO', type: 'NAV_GO',
routeName: 'Status', routeName: 'Status',
}); });
} };
const onViewMasterKeysClick = () => { const onViewMasterKeysClick = () => {
this.props.dispatch({ this.props.dispatch({
type: 'NAV_GO', type: 'NAV_GO',
routeName: 'EncryptionConfig', routeName: 'EncryptionConfig',
}); });
} };
let messageComp = null; let messageComp = null;
if (messageBoxVisible) { if (messageBoxVisible) {
let msg = null; let msg = null;
if (this.props.hasDisabledSyncItems) { 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) { } 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 = ( messageComp = (
<div style={styles.messageBox}> <div style={styles.messageBox}>
<span style={theme.textStyle}> <span style={theme.textStyle}>{msg}</span>
{msg}
</span>
</div> </div>
); );
} }
const dialogInfo = PluginManager.instance().pluginDialogToShow(this.props.plugins); 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' }); 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={style}>
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div> <div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
{ notePropertiesDialogOptions.visible && <NotePropertiesDialog {notePropertiesDialogOptions.visible && <NotePropertiesDialog theme={this.props.theme} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
theme={this.props.theme}
noteId={notePropertiesDialogOptions.noteId}
onClose={this.notePropertiesDialog_close}
onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick}
/> }
<PromptDialog <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} />
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} /> <Header style={styles.header} showBackButton={false} items={headerItems} />
{messageComp} {messageComp}
<SideBar style={styles.sideBar} /> <SideBar style={styles.sideBar} />
<VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag}/> <VerticalResizer style={styles.verticalResizer} onDrag={this.sidebar_onDrag} />
<NoteList style={styles.noteList} /> <NoteList style={styles.noteList} />
<VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag}/> <VerticalResizer style={styles.verticalResizer} onDrag={this.noteList_onDrag} />
<NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} noteDevToolsVisible={this.props.noteDevToolsVisible}/> <NoteText style={styles.noteText} visiblePanes={this.props.noteVisiblePanes} noteDevToolsVisible={this.props.noteDevToolsVisible} />
{pluginDialog} {pluginDialog}
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
windowCommand: state.windowCommand, windowCommand: state.windowCommand,

View File

@@ -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 { connect } = require('react-redux');
const { app } = require('../app.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
class NavigatorComponent extends Component { class NavigatorComponent extends Component {
componentWillReceiveProps(newProps) { componentWillReceiveProps(newProps) {
if (newProps.route) { if (newProps.route) {
const screenInfo = this.props.screens[newProps.route.routeName]; const screenInfo = this.props.screens[newProps.route.routeName];
@@ -18,7 +17,10 @@ class NavigatorComponent extends Component {
updateWindowTitle(title) { updateWindowTitle(title) {
try { try {
if (bridge().window()) bridge().window().setTitle(title); if (bridge().window())
bridge()
.window()
.setTitle(title);
} catch (error) { } catch (error) {
console.warn('updateWindowTitle', error); console.warn('updateWindowTitle', error);
} }
@@ -39,19 +41,16 @@ class NavigatorComponent extends Component {
return ( return (
<div style={this.props.style}> <div style={this.props.style}>
<Screen style={screenStyle} {...screenProps}/> <Screen style={screenStyle} {...screenProps} />
</div> </div>
); );
} }
} }
const Navigator = connect( const Navigator = connect(state => {
(state) => {
return { return {
route: state.route, route: state.route,
}; };
} })(NavigatorComponent);
)(NavigatorComponent)
module.exports = { Navigator }; module.exports = { Navigator };

View File

@@ -7,20 +7,14 @@ const BaseModel = require('lib/BaseModel');
const markJsUtils = require('lib/markJsUtils'); const markJsUtils = require('lib/markJsUtils');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const eventManager = require('../eventManager'); 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 Mark = require('mark.js/dist/mark.min.js');
const SearchEngine = require('lib/services/SearchEngine'); const SearchEngine = require('lib/services/SearchEngine');
const Note = require('lib/models/Note');
const NoteListUtils = require('./utils/NoteListUtils'); const NoteListUtils = require('./utils/NoteListUtils');
const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils'); const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils');
class NoteListComponent extends React.Component { class NoteListComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -116,9 +110,9 @@ class NoteListComponent extends React.Component {
id: item.id, id: item.id,
}); });
} }
} };
const onDragStart = (event) => { const onDragStart = event => {
let noteIds = []; let noteIds = [];
// Here there is two cases: // Here there is two cases:
@@ -136,17 +130,17 @@ class NoteListComponent extends React.Component {
event.dataTransfer.setDragImage(new Image(), 1, 1); event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData(); event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds)); 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 checked = event.target.checked;
const newNote = { const newNote = {
id: item.id, id: item.id,
todo_completed: checked ? time.unixMs() : 0, todo_completed: checked ? time.unixMs() : 0,
} };
await Note.save(newNote, { userSideValidation: true }); await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id }); eventManager.emit('todoToggle', { noteId: item.id });
} };
const hPadding = 10; 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 // 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. // but don't know how it will look in other OSes.
const checkbox = item.is_todo ? const checkbox = item.is_todo ? (
<div style={{display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding}}> <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) }}/> <input
style={{ margin: 0, marginBottom: 1 }}
type="checkbox"
defaultChecked={!!item.todo_completed}
onClick={event => {
onCheckboxClick(event, item);
}}
/>
</div> </div>
: null; ) : null;
let listItemTitleStyle = Object.assign({}, this.style().listItemTitle); let listItemTitleStyle = Object.assign({}, this.style().listItemTitle);
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4; 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 // with `textContent` so it cannot contain any XSS attacks. We use this feature because
// mark.js can only deal with DOM elements. // mark.js can only deal with DOM elements.
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml // https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span> titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>;
} else { } else {
titleComp = <span>{displayTitle}</span> titleComp = <span>{displayTitle}</span>;
} }
const watchedIconStyle = { const watchedIconStyle = {
paddingRight: 4, paddingRight: 4,
color: theme.color, color: theme.color,
}; };
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : ( const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : <i style={watchedIconStyle} className={'fa fa-external-link'}></i>;
<i style={watchedIconStyle} className={"fa fa-external-link"}></i>
);
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef(); if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
const ref = this.itemAnchorRefs_[item.id]; const ref = this.itemAnchorRefs_[item.id];
// Need to include "todo_completed" in key so that checkbox is updated when // Need to include "todo_completed" in key so that checkbox is updated when
// item is changed via sync. // item is changed via sync.
return <div key={item.id + '_' + item.todo_completed} style={style}> return (
<div key={item.id + '_' + item.todo_completed} style={style}>
{checkbox} {checkbox}
<a <a
ref={ref} ref={ref}
className="list-item" className="list-item"
onContextMenu={(event) => this.itemContextMenu(event)} onContextMenu={event => this.itemContextMenu(event)}
href="#" href="#"
draggable={true} draggable={true}
style={listItemTitleStyle} style={listItemTitleStyle}
onClick={(event) => { onTitleClick(event, item) }} onClick={event => {
onDragStart={(event) => onDragStart(event) } onTitleClick(event, item);
}}
onDragStart={event => onDragStart(event)}
data-id={item.id} data-id={item.id}
> >
{watchedIcon} {watchedIcon}
{titleComp} {titleComp}
</a> </a>
</div> </div>
);
} }
itemAnchorRef(itemId) { itemAnchorRef(itemId) {
@@ -288,7 +291,8 @@ class NoteListComponent extends React.Component {
const keyCode = event.keyCode; const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds; 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]; const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId); let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
const inc = keyCode === 38 ? -1 : +1; const inc = keyCode === 38 ? -1 : +1;
@@ -312,12 +316,14 @@ class NoteListComponent extends React.Component {
event.preventDefault(); 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(); event.preventDefault();
await NoteListUtils.confirmDeleteNotes(noteIds); await NoteListUtils.confirmDeleteNotes(noteIds);
} }
if (noteIds.length && keyCode === 32) { // SPACE if (noteIds.length && keyCode === 32) {
// SPACE
event.preventDefault(); event.preventDefault();
const notes = BaseModel.modelsByIds(this.props.notes, noteIds); const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
@@ -332,7 +338,8 @@ class NoteListComponent extends React.Component {
this.focusNoteId_(todos[0].id); this.focusNoteId_(todos[0].id);
} }
if (keyCode === 9) { // TAB if (keyCode === 9) {
// TAB
event.preventDefault(); event.preventDefault();
if (event.shiftKey) { if (event.shiftKey) {
@@ -361,7 +368,7 @@ class NoteListComponent extends React.Component {
this.focusItemIID_ = setInterval(() => { this.focusItemIID_ = setInterval(() => {
if (this.itemAnchorRef(noteId)) { if (this.itemAnchorRef(noteId)) {
this.itemAnchorRef(noteId).focus(); this.itemAnchorRef(noteId).focus();
clearInterval(this.focusItemIID_) clearInterval(this.focusItemIID_);
this.focusItemIID_ = null; this.focusItemIID_ = null;
} }
}, 10); }, 10);
@@ -384,34 +391,26 @@ class NoteListComponent extends React.Component {
if (!notes.length) { if (!notes.length) {
const padding = 10; const padding = 10;
const emptyDivStyle = Object.assign({ const emptyDivStyle = Object.assign(
{
padding: padding + 'px', padding: padding + 'px',
fontSize: theme.fontSize, fontSize: theme.fontSize,
color: theme.color, color: theme.color,
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
}, style); },
style
);
emptyDivStyle.width = emptyDivStyle.width - padding * 2; emptyDivStyle.width = emptyDivStyle.width - padding * 2;
emptyDivStyle.height = emptyDivStyle.height - 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 ( return <ItemList ref={this.itemListRef} itemHeight={this.style().listItem.height} className={'note-list'} items={notes} style={style} itemRenderer={this.itemRenderer} onKeyDown={this.onKeyDown} />;
<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 { return {
notes: state.notes, notes: state.notes,
folders: state.folders, folders: state.folders,

View File

@@ -1,7 +1,5 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const moment = require('moment');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
const Datetime = require('react-datetime'); const Datetime = require('react-datetime');
@@ -10,7 +8,6 @@ const formatcoords = require('formatcoords');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
class NotePropertiesDialog extends React.Component { class NotePropertiesDialog extends React.Component {
constructor() { constructor() {
super(); super();
@@ -131,7 +128,7 @@ class NotePropertiesDialog extends React.Component {
}; };
this.styles_.input = { this.styles_.input = {
display:'inline-block', display: 'inline-block',
color: theme.color, color: theme.color,
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
border: '1px solid', border: '1px solid',
@@ -205,68 +202,86 @@ class NotePropertiesDialog extends React.Component {
newFormNote[this.state.editedKey] = this.state.editedValue; newFormNote[this.state.editedKey] = this.state.editedValue;
} }
this.setState({ this.setState(
{
formNote: newFormNote, formNote: newFormNote,
editedKey: null, editedKey: null,
editedValue: null editedValue: null,
}, () => { resolve() }); },
() => {
resolve();
}
);
}); });
} }
async cancelProperty() { async cancelProperty() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.okButton.current.focus(); this.okButton.current.focus();
this.setState({ this.setState(
{
editedKey: null, editedKey: null,
editedValue: null editedValue: null,
}, () => { resolve() }); },
() => {
resolve();
}
);
}); });
} }
createNoteField(key, value) { createNoteField(key, value) {
const styles = this.styles(this.props.theme); const styles = this.styles(this.props.theme);
const theme = themeStyle(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 controlComp = null;
let editComp = null; let editComp = null;
let editCompHandler = null; let editCompHandler = null;
let editCompIcon = null; let editCompIcon = null;
const onKeyDown = (event) => { const onKeyDown = event => {
if (event.keyCode === 13) { if (event.keyCode === 13) {
this.saveProperty(); this.saveProperty();
} else if (event.keyCode === 27) { } else if (event.keyCode === 27) {
this.cancelProperty(); this.cancelProperty();
} }
} };
if (this.state.editedKey === key) { if (this.state.editedKey === key) {
if (key.indexOf('_time') >= 0) { if (key.indexOf('_time') >= 0) {
controlComp = (
controlComp = <Datetime <Datetime
ref="editField" ref="editField"
defaultValue={value} defaultValue={value}
dateFormat={time.dateFormat()} dateFormat={time.dateFormat()}
timeFormat={time.timeFormat()} timeFormat={time.timeFormat()}
inputProps={{ inputProps={{
onKeyDown: (event) => onKeyDown(event, key), onKeyDown: event => onKeyDown(event, key),
style: styles.input style: styles.input,
}}
onChange={momentObject => {
this.setState({ editedValue: momentObject });
}} }}
onChange={(momentObject) => {this.setState({ editedValue: momentObject })}}
/> />
);
editCompHandler = () => {this.saveProperty()}; editCompHandler = () => {
this.saveProperty();
};
editCompIcon = 'fa-save'; editCompIcon = 'fa-save';
} else { } else {
controlComp = (
controlComp = <input <input
defaultValue={value} defaultValue={value}
type="text" type="text"
ref="editField" ref="editField"
onChange={(event) => {this.setState({ editedValue: event.target.value })}} onChange={event => {
onKeyDown={(event) => onKeyDown(event)} this.setState({ editedValue: event.target.value });
}}
onKeyDown={event => onKeyDown(event)}
style={styles.input} style={styles.input}
/> />
);
} }
} else { } else {
let displayedValue = value; let displayedValue = value;
@@ -287,15 +302,25 @@ class NotePropertiesDialog extends React.Component {
const ll = this.latLongFromLocation(value); const ll = this.latLongFromLocation(value);
url = Note.geoLocationUrlFromLatLong(ll.latitude, ll.longitude); 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') { } 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 { } 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) { if (['id', 'revisionsLink', 'markup_language'].indexOf(key) < 0) {
editCompHandler = () => {this.editPropertyButtonClick(key, value)}; editCompHandler = () => {
this.editPropertyButtonClick(key, value);
};
editCompIcon = 'fa-edit'; editCompIcon = 'fa-edit';
} }
} }
@@ -303,16 +328,16 @@ class NotePropertiesDialog extends React.Component {
if (editCompHandler) { if (editCompHandler) {
editComp = ( editComp = (
<a href="#" onClick={editCompHandler} style={styles.editPropertyButton}> <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> </a>
); );
} }
return ( return (
<div key={key} style={this.styles_.controlBox} className="note-property-box"> <div key={key} style={this.styles_.controlBox} className="note-property-box">
{ labelComp } {labelComp}
{ controlComp } {controlComp}
{ editComp } {editComp}
</div> </div>
); );
} }
@@ -325,7 +350,7 @@ class NotePropertiesDialog extends React.Component {
formatValue(key, note) { formatValue(key, note) {
if (key === 'location') { if (key === 'location') {
if (!Number(note.latitude) && !Number(note.longitude)) return null; 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 }); return dms.format('DDMMss', { decimalPlaces: 0 });
} }
@@ -337,24 +362,21 @@ class NotePropertiesDialog extends React.Component {
} }
render() { render() {
const style = this.props.style;
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
const styles = this.styles(this.props.theme); const styles = this.styles(this.props.theme);
const formNote = this.state.formNote; const formNote = this.state.formNote;
const buttonComps = []; const buttonComps = [];
buttonComps.push( buttonComps.push(
<button <button key="ok" style={styles.button} onClick={this.okButton_click} ref={this.okButton} onKeyDown={this.onKeyDown}>
key="ok"
style={styles.button}
onClick={this.okButton_click}
ref={this.okButton}
onKeyDown={this.onKeyDown}
>
{_('Apply')} {_('Apply')}
</button> </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 = []; const noteComps = [];
@@ -371,14 +393,11 @@ class NotePropertiesDialog extends React.Component {
<div style={theme.dialogBox}> <div style={theme.dialogBox}>
<div style={theme.dialogTitle}>{_('Note properties')}</div> <div style={theme.dialogTitle}>{_('Note properties')}</div>
<div>{noteComps}</div> <div>{noteComps}</div>
<div style={{ textAlign: 'right', marginTop: 10 }}> <div style={{ textAlign: 'right', marginTop: 10 }}>{buttonComps}</div>
{buttonComps}
</div>
</div> </div>
</div> </div>
); );
} }
} }
module.exports = NotePropertiesDialog; module.exports = NotePropertiesDialog;

View File

@@ -6,6 +6,7 @@ const NoteTextViewer = require('./NoteTextViewer.min');
const HelpButton = require('./HelpButton.min'); const HelpButton = require('./HelpButton.min');
const BaseModel = require('lib/BaseModel'); const BaseModel = require('lib/BaseModel');
const Revision = require('lib/models/Revision'); const Revision = require('lib/models/Revision');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting'); const Setting = require('lib/models/Setting');
const RevisionService = require('lib/services/RevisionService'); const RevisionService = require('lib/services/RevisionService');
const shared = require('lib/components/shared/note-screen-shared.js'); 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'); const { substrWithEllipsis } = require('lib/string-utils');
class NoteRevisionViewerComponent extends React.PureComponent { class NoteRevisionViewerComponent extends React.PureComponent {
constructor() { constructor() {
super(); super();
@@ -56,12 +56,15 @@ class NoteRevisionViewerComponent extends React.PureComponent {
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId); const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
this.setState({ this.setState(
{
revisions: revisions, revisions: revisions,
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '', currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
}, () => { },
() => {
this.reloadNote(); this.reloadNote();
}); }
);
} }
async importButton_onClick() { async importButton_onClick() {
@@ -82,11 +85,14 @@ class NoteRevisionViewerComponent extends React.PureComponent {
if (!value) { if (!value) {
if (this.props.onBack) this.props.onBack(); if (this.props.onBack) this.props.onBack();
} else { } else {
this.setState({ this.setState(
{
currentRevId: value, currentRevId: value,
}, () => { },
() => {
this.reloadNote(); this.reloadNote();
}); }
);
} }
} }
@@ -130,45 +136,45 @@ class NoteRevisionViewerComponent extends React.PureComponent {
const rev = revs[i]; const rev = revs[i];
const stats = Revision.revisionPatchStatsText(rev); const stats = Revision.revisionPatchStatsText(rev);
revisionListItems.push(<option revisionListItems.push(
key={rev.id} <option key={rev.id} value={rev.id}>
value={rev.id} {time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}
>{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}</option>); </option>
);
} }
const restoreButtonTitle = _('Restore'); 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 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 = ( const titleInput = (
<div style={{display:'flex', flexDirection: 'row', alignItems:'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom:10}}> <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> <button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''}/> {'⬅ ' + _('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}> <select disabled={!this.state.revisions.length} value={this.state.currentRevId} style={style.revisionList} onChange={this.revisionList_onChange}>
{revisionListItems} {revisionListItems}
</select> </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> <button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick}/> {restoreButtonTitle}
</button>
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick} />
</div> </div>
); );
const viewer = <NoteTextViewer const viewer = <NoteTextViewer viewerStyle={{ display: 'flex', flex: 1 }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} />;
viewerStyle={{display:'flex', flex:1}}
ref={this.viewerRef_}
onDomReady={this.viewer_domReady}
/>
return ( return (
<div style={style.root}> <div style={style.root}>
{titleInput} {titleInput}
{viewer} {viewer}
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip"/> <ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
}; };

View File

@@ -4,7 +4,6 @@ const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
class NoteSearchBarComponent extends React.Component { class NoteSearchBarComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -54,14 +53,12 @@ class NoteSearchBarComponent extends React.Component {
color: theme.color, color: theme.color,
}; };
const icon = <i style={iconStyle} className={"fa " + iconName}></i> const icon = <i style={iconStyle} className={'fa ' + iconName}></i>;
return ( return (
<a <a href="#" style={searchButton} onClick={clickHandler}>
href="#" {icon}
style={searchButton} </a>
onClick={clickHandler}
>{icon}</a>
); );
} }
@@ -72,7 +69,8 @@ class NoteSearchBarComponent extends React.Component {
} }
searchInput_keyDown(event) { searchInput_keyDown(event) {
if (event.keyCode === 13) { // ENTER if (event.keyCode === 13) {
// ENTER
event.preventDefault(); event.preventDefault();
if (!event.shiftKey) { if (!event.shiftKey) {
@@ -82,7 +80,8 @@ class NoteSearchBarComponent extends React.Component {
} }
} }
if (event.keyCode === 27) { // ESCAPE if (event.keyCode === 27) {
// ESCAPE
event.preventDefault(); event.preventDefault();
if (this.props.onClose) this.props.onClose(); if (this.props.onClose) this.props.onClose();
@@ -110,32 +109,34 @@ class NoteSearchBarComponent extends React.Component {
} }
render() { render() {
const theme = themeStyle(this.props.theme);
const closeButton = this.buttonIconComponent('fa-times', this.closeButton_click); const closeButton = this.buttonIconComponent('fa-times', this.closeButton_click);
const previousButton = this.buttonIconComponent('fa-chevron-up', this.previousButton_click); const previousButton = this.buttonIconComponent('fa-chevron-up', this.previousButton_click);
const nextButton = this.buttonIconComponent('fa-chevron-down', this.nextButton_click); const nextButton = this.buttonIconComponent('fa-chevron-down', this.nextButton_click);
return ( return (
<div style={this.props.style}> <div style={this.props.style}>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center' }}> <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{ closeButton } {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> <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 } {nextButton}
{ previousButton } {previousButton}
</div> </div>
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, 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;

View File

@@ -2,15 +2,11 @@ const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class NoteStatusBarComponent extends React.Component { class NoteStatusBarComponent extends React.Component {
style() { style() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
const itemHeight = 34;
let style = { let style = {
root: Object.assign({}, theme.textStyle, { root: Object.assign({}, theme.textStyle, {
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
@@ -22,18 +18,12 @@ class NoteStatusBarComponent extends React.Component {
} }
render() { render() {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
const note = this.props.note; 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 { return {
// notes: state.notes, // notes: state.notes,
// folders: state.folders, // folders: state.folders,

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,7 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class NoteTextViewerComponent extends React.Component { class NoteTextViewerComponent extends React.Component {
constructor() { constructor() {
super(); super();
@@ -49,7 +46,7 @@ class NoteTextViewerComponent extends React.Component {
let isAlreadyReady = false; let isAlreadyReady = false;
try { try {
isAlreadyReady = !this.webviewRef_.current.isLoading() isAlreadyReady = !this.webviewRef_.current.isLoading();
} catch (error) { } catch (error) {
// Ignore - it means the view has not started loading, and the DOM ready event has not been emitted yet // 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." // 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); return this.webviewRef_.current.getWebContents().printToPDF(options, callback);
} }
print(options = {}) { print() {
// In Electron 4x, print is broken so need to use this hack: // In Electron 4x, print is broken so need to use this hack:
// https://github.com/electron/electron/issues/16219#issuecomment-451454948 // https://github.com/electron/electron/issues/16219#issuecomment-451454948
// Note that this is not a perfect workaround since it means the options are ignored // 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) // 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().print({});
return this.webviewRef_.current.getWebContents().executeJavaScript("window.print()") return this.webviewRef_.current.getWebContents().executeJavaScript('window.print()');
} }
openDevTools() { openDevTools() {
@@ -138,23 +135,21 @@ class NoteTextViewerComponent extends React.Component {
// ---------------------------------------------------------------- // ----------------------------------------------------------------
render() { render() {
return <webview return <webview ref={this.webviewRef_} style={this.props.viewerStyle} preload="gui/note-viewer/preload.js" src="gui/note-viewer/index.html" webpreferences="contextIsolation" />;
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 { return {
theme: state.settings.theme, 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;

View File

@@ -7,7 +7,6 @@ const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
class OneDriveLoginScreenComponent extends React.Component { class OneDriveLoginScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
this.webview_ = null; this.webview_ = null;
@@ -37,7 +36,7 @@ class OneDriveLoginScreenComponent extends React.Component {
webview_domReady() { webview_domReady() {
this.setState({ webviewReady: true }); this.setState({ webviewReady: true });
this.webview_.addEventListener('did-navigate', async (event) => { this.webview_.addEventListener('did-navigate', async event => {
const url = event.url; const url = event.url;
if (this.authCode_) return; if (this.authCode_) return;
@@ -50,11 +49,14 @@ class OneDriveLoginScreenComponent extends React.Component {
this.authCode_ = parsedUrl.query.code; this.authCode_ = parsedUrl.query.code;
try { 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' }); this.props.dispatch({ type: 'NAV_BACK' });
reg.scheduleSync(0); reg.scheduleSync(0);
} catch (error) { } 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; this.authCode_ = null;
@@ -62,11 +64,17 @@ class OneDriveLoginScreenComponent extends React.Component {
} }
startUrl() { startUrl() {
return reg.syncTarget().api().authCodeUrl(this.redirectUrl()); return reg
.syncTarget()
.api()
.authCodeUrl(this.redirectUrl());
} }
redirectUrl() { redirectUrl() {
return reg.syncTarget().api().nativeClientRedirectUrl(); return reg
.syncTarget()
.api()
.nativeClientRedirectUrl();
} }
render() { render() {
@@ -94,14 +102,13 @@ class OneDriveLoginScreenComponent extends React.Component {
return ( return (
<div> <div>
<Header style={headerStyle} buttons={headerButtons} /> <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> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
}; };

View File

@@ -1,7 +1,5 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const moment = require('moment');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { time } = require('lib/time-utils.js'); const { time } = require('lib/time-utils.js');
const Datetime = require('react-datetime'); const Datetime = require('react-datetime');
@@ -10,7 +8,6 @@ const Select = require('react-select').default;
const makeAnimated = require('react-select/lib/animated').default; const makeAnimated = require('react-select/lib/animated').default;
class PromptDialog extends React.Component { class PromptDialog extends React.Component {
constructor() { constructor() {
super(); super();
@@ -103,32 +100,39 @@ class PromptDialog extends React.Component {
}; };
this.styles_.select = { this.styles_.select = {
control: (provided) => (Object.assign(provided, { control: provided =>
Object.assign(provided, {
minWidth: width * 0.2, minWidth: width * 0.2,
maxWidth: width * 0.5, maxWidth: width * 0.5,
})), }),
input: (provided) => (Object.assign(provided, { input: provided =>
Object.assign(provided, {
minWidth: '20px', minWidth: '20px',
color: theme.color, color: theme.color,
})), }),
menu: (provided) => (Object.assign(provided, { menu: provided =>
Object.assign(provided, {
color: theme.color, color: theme.color,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
backgroundColor: theme.backgroundColor, backgroundColor: theme.backgroundColor,
})), }),
option: (provided) => (Object.assign(provided, { option: provided =>
Object.assign(provided, {
color: theme.color, color: theme.color,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
})), }),
multiValueLabel: (provided) => (Object.assign(provided, { multiValueLabel: provided =>
Object.assign(provided, {
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
})), }),
multiValueRemove: (provided) => (Object.assign(provided, { multiValueRemove: provided =>
Object.assign(provided, {
color: theme.color, color: theme.color,
})), }),
}; };
this.styles_.selectTheme = (tagTheme) => (Object.assign(tagTheme, { this.styles_.selectTheme = tagTheme =>
Object.assign(tagTheme, {
borderRadius: 2, borderRadius: 2,
colors: Object.assign(tagTheme.colors, { colors: Object.assign(tagTheme.colors, {
primary: theme.raisedBackgroundColor, primary: theme.raisedBackgroundColor,
@@ -147,7 +151,7 @@ class PromptDialog extends React.Component {
danger: theme.backgroundColor, danger: theme.backgroundColor,
dangerLight: theme.colorError2, dangerLight: theme.colorError2,
}), }),
})); });
this.styles_.desc = Object.assign({}, theme.textStyle, { this.styles_.desc = Object.assign({}, theme.textStyle, {
marginTop: 10, marginTop: 10,
@@ -173,11 +177,11 @@ class PromptDialog extends React.Component {
this.props.onClose(accept ? outputAnswer : null, buttonType); this.props.onClose(accept ? outputAnswer : null, buttonType);
} }
this.setState({ visible: false, answer: '' }); this.setState({ visible: false, answer: '' });
} };
const onChange = (event) => { const onChange = event => {
this.setState({ answer: event.target.value }); this.setState({ answer: event.target.value });
} };
// const anythingToDate = (o) => { // const anythingToDate = (o) => {
// if (o && o.toDate) return o.toDate(); // if (o && o.toDate) return o.toDate();
@@ -188,16 +192,16 @@ class PromptDialog extends React.Component {
// return m.isValid() ? m.toDate() : null; // return m.isValid() ? m.toDate() : null;
// } // }
const onDateTimeChange = (momentObject) => { const onDateTimeChange = momentObject => {
this.setState({ answer: momentObject }); this.setState({ answer: momentObject });
} };
const onSelectChange = (newValue) => { const onSelectChange = newValue => {
this.setState({ answer: newValue }); this.setState({ answer: newValue });
this.focusInput_ = true; this.focusInput_ = true;
} };
const onKeyDown = (event) => { const onKeyDown = event => {
if (event.key === 'Enter') { if (event.key === 'Enter') {
if (this.props.inputType !== 'tags' && this.props.inputType !== 'dropdown') { if (this.props.inputType !== 'tags' && this.props.inputType !== 'dropdown') {
onClose(true); onClose(true);
@@ -208,80 +212,55 @@ class PromptDialog extends React.Component {
} else if (event.key === 'Escape') { } else if (event.key === 'Escape') {
onClose(false); onClose(false);
} }
} };
const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null; const descComp = this.props.description ? <div style={styles.desc}>{this.props.description}</div> : null;
let inputComp = null; let inputComp = null;
if (this.props.inputType === 'datetime') { if (this.props.inputType === 'datetime') {
inputComp = <Datetime inputComp = <Datetime value={this.state.answer} inputProps={{ style: styles.input }} dateFormat={time.dateFormat()} timeFormat={time.timeFormat()} onChange={momentObject => onDateTimeChange(momentObject)} />;
value={this.state.answer}
inputProps={{style: styles.input}}
dateFormat={time.dateFormat()}
timeFormat={time.timeFormat()}
onChange={(momentObject) => onDateTimeChange(momentObject)}
/>
} else if (this.props.inputType === 'tags') { } else if (this.props.inputType === 'tags') {
inputComp = <CreatableSelect 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)} />;
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') { } else if (this.props.inputType === 'dropdown') {
inputComp = <Select 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)} />;
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 { } else {
inputComp = <input inputComp = <input style={styles.input} ref={this.answerInput_} value={this.state.answer} type="text" onChange={event => onChange(event)} onKeyDown={event => onKeyDown(event)} />;
style={styles.input}
ref={this.answerInput_}
value={this.state.answer}
type="text"
onChange={(event) => onChange(event)}
onKeyDown={(event) => onKeyDown(event)}
/>
} }
const buttonComps = []; const buttonComps = [];
if (buttonTypes.indexOf('ok') >= 0) buttonComps.push(<button key="ok" style={styles.button} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>); if (buttonTypes.indexOf('ok') >= 0)
if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={styles.button} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>); buttonComps.push(
if (buttonTypes.indexOf('clear') >= 0) buttonComps.push(<button key="clear" style={styles.button} onClick={() => onClose(false, 'clear')}>{_('Clear')}</button>); <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 ( return (
<div style={styles.modalLayer}> <div style={styles.modalLayer}>
<div style={styles.promptDialog}> <div style={styles.promptDialog}>
<label style={styles.label}>{this.props.label ? this.props.label : ''}</label> <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} {inputComp}
{descComp} {descComp}
</div> </div>
<div style={{ textAlign: 'right', marginTop: 10 }}> <div style={{ textAlign: 'right', marginTop: 10 }}>{buttonComps}</div>
{buttonComps}
</div>
</div> </div>
</div> </div>
); );
} }
} }
module.exports = { PromptDialog }; module.exports = { PromptDialog };

View File

@@ -1,6 +1,5 @@
const React = require('react'); const React = require('react');
const { render } = require('react-dom'); const { render } = require('react-dom');
const { createStore } = require('redux');
const { connect, Provider } = require('react-redux'); const { connect, Provider } = require('react-redux');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
@@ -21,10 +20,12 @@ const { app } = require('../app');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
async function initialize(dispatch) { async function initialize() {
this.wcsTimeoutId_ = null; this.wcsTimeoutId_ = null;
bridge().window().on('resize', function() { bridge()
.window()
.on('resize', function() {
if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_); if (this.wcsTimeoutId_) clearTimeout(this.wcsTimeoutId_);
this.wcsTimeoutId_ = setTimeout(() => { this.wcsTimeoutId_ = setTimeout(() => {
@@ -52,12 +53,11 @@ async function initialize(dispatch) {
store.dispatch({ store.dispatch({
type: 'SIDEBAR_VISIBILITY_SET', type: 'SIDEBAR_VISIBILITY_SET',
visibility: Setting.value('sidebarVisibility') visibility: Setting.value('sidebarVisibility'),
}); });
} }
class RootComponent extends React.Component { class RootComponent extends React.Component {
async componentDidMount() { async componentDidMount() {
if (this.props.appState == 'starting') { if (this.props.appState == 'starting') {
this.props.dispatch({ this.props.dispatch({
@@ -93,14 +93,11 @@ class RootComponent extends React.Component {
ClipperConfig: { screen: ClipperConfigScreen, title: () => _('Clipper Options') }, ClipperConfig: { screen: ClipperConfigScreen, title: () => _('Clipper Options') },
}; };
return ( return <Navigator style={navigatorStyle} screens={screens} />;
<Navigator style={navigatorStyle} screens={screens} />
);
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
size: state.windowContentSize, size: state.windowContentSize,
appState: state.appState, appState: state.appState,
@@ -116,4 +113,4 @@ render(
<Root /> <Root />
</Provider>, </Provider>,
document.getElementById('react-root') document.getElementById('react-root')
) );

View File

@@ -1,28 +1,25 @@
const React = require("react"); const React = require('react');
const { connect } = require("react-redux"); const { connect } = require('react-redux');
const shared = require("lib/components/shared/side-menu-shared.js"); const shared = require('lib/components/shared/side-menu-shared.js');
const { Synchronizer } = require("lib/synchronizer.js"); const { Synchronizer } = require('lib/synchronizer.js');
const BaseModel = require("lib/BaseModel.js"); const BaseModel = require('lib/BaseModel.js');
const Setting = require('lib/models/Setting.js'); const Setting = require('lib/models/Setting.js');
const Folder = require("lib/models/Folder.js"); const Folder = require('lib/models/Folder.js');
const Note = require("lib/models/Note.js"); const Note = require('lib/models/Note.js');
const Tag = require("lib/models/Tag.js"); const Tag = require('lib/models/Tag.js');
const { _ } = require("lib/locale.js"); const { _ } = require('lib/locale.js');
const { themeStyle } = require("../theme.js"); const { themeStyle } = require('../theme.js');
const { bridge } = require("electron").remote.require("./bridge"); const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu; const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem; const MenuItem = bridge().MenuItem;
const InteropServiceHelper = require("../InteropServiceHelper.js"); const InteropServiceHelper = require('../InteropServiceHelper.js');
const { substrWithEllipsis } = require('lib/string-utils'); const { substrWithEllipsis } = require('lib/string-utils');
const { shim } = require('lib/shim');
class SideBarComponent extends React.Component { class SideBarComponent extends React.Component {
constructor() { constructor() {
super(); super();
this.onFolderDragStart_ = (event) => { this.onFolderDragStart_ = event => {
const folderId = event.currentTarget.getAttribute('folderid'); const folderId = event.currentTarget.getAttribute('folderid');
if (!folderId) return; if (!folderId) return;
@@ -31,12 +28,12 @@ class SideBarComponent extends React.Component {
event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId])); event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId]));
}; };
this.onFolderDragOver_ = (event) => { this.onFolderDragOver_ = event => {
if (event.dataTransfer.types.indexOf("text/x-jop-note-ids") >= 0) event.preventDefault(); 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(); 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 folderId = event.currentTarget.getAttribute('folderid');
const dt = event.dataTransfer; const dt = event.dataTransfer;
if (!dt) return; 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 // 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. // 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(); event.preventDefault();
if (!folderId) return; 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++) { for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], folderId); 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(); 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++) { for (let i = 0; i < folderIds.length; i++) {
await Folder.moveToFolder(folderIds[i], folderId); await Folder.moveToFolder(folderIds[i], folderId);
} }
} }
}; };
this.onTagDrop_ = async (event) => { this.onTagDrop_ = async event => {
const tagId = event.currentTarget.getAttribute('tagid'); const tagId = event.currentTarget.getAttribute('tagid');
const dt = event.dataTransfer; const dt = event.dataTransfer;
if (!dt) return; 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(); 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++) { for (let i = 0; i < noteIds.length; i++) {
await Tag.addNote(tagId, noteIds[i]); await Tag.addNote(tagId, noteIds[i]);
} }
} }
} };
this.onFolderToggleClick_ = async (event) => { this.onFolderToggleClick_ = async event => {
const folderId = event.currentTarget.getAttribute('folderid'); const folderId = event.currentTarget.getAttribute('folderid');
this.props.dispatch({ this.props.dispatch({
@@ -99,7 +96,7 @@ class SideBarComponent extends React.Component {
this.state = { this.state = {
tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'), tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'),
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded') folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'),
}; };
} }
@@ -113,23 +110,23 @@ class SideBarComponent extends React.Component {
backgroundColor: theme.backgroundColor2, backgroundColor: theme.backgroundColor2,
}, },
listItemContainer: { listItemContainer: {
boxSizing: "border-box", boxSizing: 'border-box',
height: itemHeight, height: itemHeight,
// paddingLeft: 14, // paddingLeft: 14,
display: "flex", display: 'flex',
alignItems: "stretch", alignItems: 'stretch',
// Allow 3 levels of color depth // Allow 3 levels of color depth
backgroundColor: theme.depthColor.replace('OPACITY', Math.min(depth * 0.1, 0.3)), backgroundColor: theme.depthColor.replace('OPACITY', Math.min(depth * 0.1, 0.3)),
}, },
listItem: { listItem: {
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
fontSize: theme.fontSize, fontSize: theme.fontSize,
textDecoration: "none", textDecoration: 'none',
color: theme.color2, color: theme.color2,
cursor: "default", cursor: 'default',
opacity: 0.8, opacity: 0.8,
whiteSpace: "nowrap", whiteSpace: 'nowrap',
display: "flex", display: 'flex',
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
}, },
@@ -138,60 +135,60 @@ class SideBarComponent extends React.Component {
}, },
listItemExpandIcon: { listItemExpandIcon: {
color: theme.color2, color: theme.color2,
cursor: "default", cursor: 'default',
opacity: 0.8, opacity: 0.8,
// fontFamily: theme.fontFamily, // fontFamily: theme.fontFamily,
fontSize: theme.fontSize, fontSize: theme.fontSize,
textDecoration: "none", textDecoration: 'none',
paddingRight: 5, paddingRight: 5,
display: "flex", display: 'flex',
alignItems: 'center', alignItems: 'center',
}, },
conflictFolder: { conflictFolder: {
color: theme.colorError2, color: theme.colorError2,
fontWeight: "bold", fontWeight: 'bold',
}, },
header: { header: {
height: itemHeight * 1.8, height: itemHeight * 1.8,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
fontSize: theme.fontSize * 1.16, fontSize: theme.fontSize * 1.16,
textDecoration: "none", textDecoration: 'none',
boxSizing: "border-box", boxSizing: 'border-box',
color: theme.color2, color: theme.color2,
paddingLeft: 8, paddingLeft: 8,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
}, },
button: { button: {
padding: 6, padding: 6,
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
fontSize: theme.fontSize, fontSize: theme.fontSize,
textDecoration: "none", textDecoration: 'none',
boxSizing: "border-box", boxSizing: 'border-box',
color: theme.color2, color: theme.color2,
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
border: "1px solid rgba(255,255,255,0.2)", border: '1px solid rgba(255,255,255,0.2)',
marginTop: 10, marginTop: 10,
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
cursor: "default", cursor: 'default',
}, },
syncReport: { syncReport: {
fontFamily: theme.fontFamily, fontFamily: theme.fontFamily,
fontSize: Math.round(theme.fontSize * 0.9), fontSize: Math.round(theme.fontSize * 0.9),
color: theme.color2, color: theme.color2,
opacity: 0.5, opacity: 0.5,
display: "flex", display: 'flex',
alignItems: "left", alignItems: 'left',
justifyContent: "top", justifyContent: 'top',
flexDirection: "column", flexDirection: 'column',
marginTop: 10, marginTop: 10,
marginLeft: 5, marginLeft: 5,
marginRight: 5, marginRight: 5,
marginBottom: 10, 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() { componentWillUnmount() {
this.clearForceUpdateDuringSync(); this.clearForceUpdateDuringSync();
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps) {
if (prevProps.windowCommand !== this.props.windowCommand) { if (prevProps.windowCommand !== this.props.windowCommand) {
this.doCommand(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) { async itemContextMenu(event) {
const itemId = event.target.getAttribute("data-id"); const itemId = event.target.getAttribute('data-id');
if (itemId === Folder.conflictFolderId()) return; if (itemId === Folder.conflictFolderId()) return;
const itemType = Number(event.target.getAttribute("data-type")); const itemType = Number(event.target.getAttribute('data-type'));
if (!itemId || !itemType) throw new Error("No data on element"); if (!itemId || !itemType) throw new Error('No data on element');
let deleteMessage = ""; let deleteMessage = '';
if (itemType === BaseModel.TYPE_FOLDER) { if (itemType === BaseModel.TYPE_FOLDER) {
const folder = await Folder.load(itemId); 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)); 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); const tag = await Tag.load(itemId);
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32)); deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
} else if (itemType === BaseModel.TYPE_SEARCH) { } else if (itemType === BaseModel.TYPE_SEARCH) {
deleteMessage = _("Remove this search from the sidebar?"); deleteMessage = _('Remove this search from the sidebar?');
} }
const menu = new Menu(); const menu = new Menu();
@@ -291,7 +286,7 @@ class SideBarComponent extends React.Component {
menu.append( menu.append(
new MenuItem({ new MenuItem({
label: _("Delete"), label: _('Delete'),
click: async () => { click: async () => {
const ok = bridge().showConfirmMessageBox(deleteMessage); const ok = bridge().showConfirmMessageBox(deleteMessage);
if (!ok) return; if (!ok) return;
@@ -302,7 +297,7 @@ class SideBarComponent extends React.Component {
await Tag.untagAll(itemId); await Tag.untagAll(itemId);
} else if (itemType === BaseModel.TYPE_SEARCH) { } else if (itemType === BaseModel.TYPE_SEARCH) {
this.props.dispatch({ this.props.dispatch({
type: "SEARCH_DELETE", type: 'SEARCH_DELETE',
id: itemId, id: itemId,
}); });
} }
@@ -313,11 +308,11 @@ class SideBarComponent extends React.Component {
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) { if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
menu.append( menu.append(
new MenuItem({ new MenuItem({
label: _("Rename"), label: _('Rename'),
click: async () => { click: async () => {
this.props.dispatch({ this.props.dispatch({
type: "WINDOW_COMMAND", type: 'WINDOW_COMMAND',
name: "renameFolder", name: 'renameFolder',
id: itemId, 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 exportMenu = new Menu();
const ioService = new InteropService(); const ioService = new InteropService();
@@ -348,14 +343,19 @@ class SideBarComponent extends React.Component {
const module = ioModules[i]; const module = ioModules[i];
if (module.type !== 'exporter') continue; if (module.type !== 'exporter') continue;
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => { exportMenu.append(
new MenuItem({
label: module.fullLabel(),
click: async () => {
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] }); await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
}})); },
})
);
} }
menu.append( menu.append(
new MenuItem({ new MenuItem({
label: _("Export"), label: _('Export'),
submenu: exportMenu, submenu: exportMenu,
}) })
); );
@@ -367,9 +367,9 @@ class SideBarComponent extends React.Component {
label: _('Rename'), label: _('Rename'),
click: async () => { click: async () => {
this.props.dispatch({ this.props.dispatch({
type: "WINDOW_COMMAND", type: 'WINDOW_COMMAND',
name: "renameTag", name: 'renameTag',
id: itemId id: itemId,
}); });
}, },
}) })
@@ -381,14 +381,14 @@ class SideBarComponent extends React.Component {
folderItem_click(folder) { folderItem_click(folder) {
this.props.dispatch({ this.props.dispatch({
type: "FOLDER_SELECT", type: 'FOLDER_SELECT',
id: folder ? folder.id : null, id: folder ? folder.id : null,
}); });
} }
tagItem_click(tag) { tagItem_click(tag) {
this.props.dispatch({ this.props.dispatch({
type: "TAG_SELECT", type: 'TAG_SELECT',
id: tag ? tag.id : null, id: tag ? tag.id : null,
}); });
} }
@@ -405,8 +405,6 @@ class SideBarComponent extends React.Component {
} }
anchorItemRef(type, id) { anchorItemRef(type, id) {
let refs = null;
if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {}; if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {};
if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id]; if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id];
this.anchorItemRefs[type][id] = React.createRef(); this.anchorItemRefs[type][id] = React.createRef();
@@ -426,17 +424,23 @@ class SideBarComponent extends React.Component {
let expandIconStyle = { let expandIconStyle = {
visibility: hasChildren ? 'visible' : 'hidden', visibility: hasChildren ? 'visible' : 'hidden',
paddingLeft: 8 + depth * 10, paddingLeft: 8 + depth * 10,
} };
const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'fa-plus-square' : 'fa-minus-square'; 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 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 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); const anchorRef = this.anchorItemRef('folder', folder.id);
return ( 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}> <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 <a
ref={anchorRef} ref={anchorRef}
className="list-item" className="list-item"
@@ -506,15 +510,15 @@ class SideBarComponent extends React.Component {
// } // }
makeDivider(key) { 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 = {}) { makeHeader(key, label, iconName, extraProps = {}) {
const style = this.style().header; 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) { if (extraProps.toggleblock || extraProps.onClick) {
style.cursor = "pointer"; style.cursor = 'pointer';
} }
let headerClick = extraProps.onClick || null; let headerClick = extraProps.onClick || null;
@@ -525,22 +529,27 @@ class SideBarComponent extends React.Component {
const toggleKey = `${key}IsExpanded`; const toggleKey = `${key}IsExpanded`;
if (extraProps.toggleblock) { if (extraProps.toggleblock) {
let isExpanded = this.state[toggleKey]; let isExpanded = this.state[toggleKey];
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75, 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>;
marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125}}></i>;
} }
const ref = this.anchorItemRef('headers', key); const ref = this.anchorItemRef('headers', key);
return ( return (
<div ref={ref} style={style} key={key} {...extraProps} onClick={(event) => { <div
ref={ref}
style={style}
key={key}
{...extraProps}
onClick={event => {
// if a custom click event is attached, trigger that. // if a custom click event is attached, trigger that.
if (headerClick) { if (headerClick) {
headerClick(key, event); headerClick(key, event);
} }
this.onHeaderClick_(key, event); this.onHeaderClick_(key, event);
}}> }}
>
{icon} {icon}
<span style={{flex: 1 }}>{label}</span> <span style={{ flex: 1 }}>{label}</span>
{toggleIcon} {toggleIcon}
</div> </div>
); );
@@ -562,7 +571,8 @@ class SideBarComponent extends React.Component {
const keyCode = event.keyCode; const keyCode = event.keyCode;
const selectedItem = this.selectedItem(); const selectedItem = this.selectedItem();
if (keyCode === 40 || keyCode === 38) { // DOWN / UP if (keyCode === 40 || keyCode === 38) {
// DOWN / UP
event.preventDefault(); event.preventDefault();
const focusItems = []; const focusItems = [];
@@ -603,7 +613,8 @@ class SideBarComponent extends React.Component {
focusItem.ref.current.focus(); focusItem.ref.current.focus();
} }
if (keyCode === 9) { // TAB if (keyCode === 9) {
// TAB
event.preventDefault(); event.preventDefault();
if (event.shiftKey) { 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(); event.preventDefault();
this.props.dispatch({ this.props.dispatch({
@@ -644,15 +656,15 @@ class SideBarComponent extends React.Component {
synchronizeButton(type) { synchronizeButton(type) {
const style = Object.assign({}, this.style().button, { marginBottom: 5 }); const style = Object.assign({}, this.style().button, { marginBottom: 5 });
const iconName = "fa-refresh"; const iconName = 'fa-refresh';
const label = type === "sync" ? _("Synchronise") : _("Cancel"); const label = type === 'sync' ? _('Synchronise') : _('Cancel');
let iconStyle = { fontSize: style.fontSize, marginRight: 5 }; let iconStyle = { fontSize: style.fontSize, marginRight: 5 };
if(type !== 'sync'){ if (type !== 'sync') {
iconStyle.animation = 'icon-infinite-rotation 1s linear infinite'; 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 ( return (
<a <a
className="synchronize-button" className="synchronize-button"
@@ -670,32 +682,38 @@ class SideBarComponent extends React.Component {
} }
render() { render() {
const theme = themeStyle(this.props.theme);
const style = Object.assign({}, this.style().root, this.props.style, { const style = Object.assign({}, this.style().root, this.props.style, {
overflowX: "hidden", overflowX: 'hidden',
overflowY: "hidden", overflowY: 'hidden',
display: 'inline-flex', display: 'inline-flex',
flexDirection: 'column', flexDirection: 'column',
}); });
let items = []; let items = [];
items.push(this.makeHeader("folderHeader", _("Notebooks"), "fa-book", { items.push(
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
onDrop: this.onFolderDrop_, onDrop: this.onFolderDrop_,
folderid: '', folderid: '',
toggleblock: 1 toggleblock: 1,
})); })
);
if (this.props.folders.length) { if (this.props.folders.length) {
const result = shared.renderFolders(this.props, this.folderItem.bind(this)); const result = shared.renderFolders(this.props, this.folderItem.bind(this));
const folderItems = result.items; const folderItems = result.items;
this.folderItemsOrder_ = result.order; this.folderItemsOrder_ = result.order;
items.push(<div className="folders" key="folder_items" style={{display: this.state.folderHeaderIsExpanded ? 'block': 'none'}}> items.push(
{folderItems}</div>); <div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none' }}>
{folderItems}
</div>
);
} }
items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags", { items.push(
toggleblock: 1 this.makeHeader('tagHeader', _('Tags'), 'fa-tags', {
})); toggleblock: 1,
})
);
if (this.props.tags.length) { if (this.props.tags.length) {
const result = shared.renderTags(this.props, this.tagItem.bind(this)); const result = shared.renderTags(this.props, this.tagItem.bind(this));
@@ -703,7 +721,7 @@ class SideBarComponent extends React.Component {
this.tagItemsOrder_ = result.order; this.tagItemsOrder_ = result.order;
items.push( 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} {tagItems}
</div> </div>
); );
@@ -725,13 +743,13 @@ class SideBarComponent extends React.Component {
const syncReportText = []; const syncReportText = [];
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
syncReportText.push( syncReportText.push(
<div key={i} style={{ wordWrap: "break-word", width: "100%" }}> <div key={i} style={{ wordWrap: 'break-word', width: '100%' }}>
{lines[i]} {lines[i]}
</div> </div>
); );
} }
const syncButton = this.synchronizeButton(this.props.syncStarted ? "cancel" : "sync"); const syncButton = this.synchronizeButton(this.props.syncStarted ? 'cancel' : 'sync');
const syncReportComp = !syncReportText.length ? null : ( const syncReportComp = !syncReportText.length ? null : (
<div style={this.style().syncReport} key="sync_report"> <div style={this.style().syncReport} key="sync_report">
@@ -741,11 +759,8 @@ class SideBarComponent extends React.Component {
return ( return (
<div ref={this.rootRef} onKeyDown={this.onKeyDown} className="side-bar" style={style}> <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:1, overflowX: 'hidden', overflowY: 'auto'}}> <div style={{ flex: 0 }}>
{items}
</div>
<div style={{flex:0}}>
{syncReportComp} {syncReportComp}
{syncButton} {syncButton}
</div> </div>

View File

@@ -1,6 +1,5 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const Setting = require('lib/models/Setting.js'); const Setting = require('lib/models/Setting.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js'); const { Header } = require('./Header.min.js');
@@ -10,7 +9,6 @@ const { ReportService } = require('lib/services/report.js');
const fs = require('fs-extra'); const fs = require('fs-extra');
class StatusScreenComponent extends React.Component { class StatusScreenComponent extends React.Component {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@@ -29,7 +27,7 @@ class StatusScreenComponent extends React.Component {
} }
async exportDebugReportClick() { async exportDebugReportClick() {
const filename = 'syncReport-' + (new Date()).getTime() + '.csv'; const filename = 'syncReport-' + new Date().getTime() + '.csv';
const filePath = bridge().showSaveDialog({ const filePath = bridge().showSaveDialog({
title: _('Please select where the sync status should be exported to'), 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 style = this.props.style;
const headerStyle = Object.assign({}, theme.headerStyle, { width: style.width }); 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; const containerPadding = 10;
@@ -58,7 +56,11 @@ class StatusScreenComponent extends React.Component {
}); });
function renderSectionTitleHtml(key, title) { 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) => { const renderSectionHtml = (key, section) => {
@@ -77,9 +79,13 @@ class StatusScreenComponent extends React.Component {
const onClick = async () => { const onClick = async () => {
await item.retryHandler(); await item.retryHandler();
this.resfreshScreen(); this.resfreshScreen();
} };
retryLink = <a href="#" onClick={onClick} style={retryStyle}>{_('Retry')}</a>; retryLink = (
<a href="#" onClick={onClick} style={retryStyle}>
{_('Retry')}
</a>
);
} }
text = item.text; text = item.text;
} else { } else {
@@ -88,28 +94,18 @@ class StatusScreenComponent extends React.Component {
if (!text) text = '\xa0'; 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>
return ( {retryLink}
<div key={key}>
{itemsHtml}
</div> </div>
); );
} }
function renderBodyHtml(report) { return <div key={key}>{itemsHtml}</div>;
let output = [];
let baseStyle = {
paddingLeft: 6,
paddingRight: 6,
paddingTop: 2,
paddingBottom: 2,
flex: 0,
color: theme.color,
fontSize: theme.fontSize,
}; };
function renderBodyHtml(report) {
let sectionsHtml = []; let sectionsHtml = [];
for (let i = 0; i < report.length; i++) { for (let i = 0; i < report.length; i++) {
@@ -118,11 +114,7 @@ class StatusScreenComponent extends React.Component {
sectionsHtml.push(renderSectionHtml(i, section)); sectionsHtml.push(renderSectionHtml(i, section));
} }
return ( return <div>{sectionsHtml}</div>;
<div>
{sectionsHtml}
</div>
);
} }
let body = renderBodyHtml(this.state.report); let body = renderBodyHtml(this.state.report);
@@ -131,16 +123,17 @@ class StatusScreenComponent extends React.Component {
<div style={style}> <div style={style}>
<Header style={headerStyle} /> <Header style={headerStyle} />
<div style={containerStyle}> <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} {body}
</div> </div>
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { return {
theme: state.settings.theme, theme: state.settings.theme,
settings: state.settings, settings: state.settings,

View File

@@ -12,7 +12,7 @@ class TagItemComponent extends React.Component {
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { theme: state.settings.theme }; return { theme: state.settings.theme };
}; };

View File

@@ -18,12 +18,14 @@ class TagListComponent extends React.Component {
const tagItems = []; const tagItems = [];
if (tags || tags.length > 0) { if (tags || tags.length > 0) {
// Sort by id for now, but probably needs to be changed in the future. // 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++) { for (let i = 0; i < tags.length; i++) {
const props = { const props = {
title: tags[i].title, title: tags[i].title,
key: tags[i].id key: tags[i].id,
}; };
tagItems.push(<TagItem {...props} />); tagItems.push(<TagItem {...props} />);
} }
@@ -35,13 +37,13 @@ class TagListComponent extends React.Component {
return ( return (
<div className="tag-list" style={style}> <div className="tag-list" style={style}>
{ tagItems } {tagItems}
</div> </div>
) );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { theme: state.settings.theme }; return { theme: state.settings.theme };
}; };

View File

@@ -1,13 +1,10 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux'); const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const ToolbarButton = require('./ToolbarButton.min.js'); const ToolbarButton = require('./ToolbarButton.min.js');
const ToolbarSpace = require('./ToolbarSpace.min.js'); const ToolbarSpace = require('./ToolbarSpace.min.js');
class ToolbarComponent extends React.Component { class ToolbarComponent extends React.Component {
render() { render() {
const style = this.props.style; const style = this.props.style;
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
@@ -28,10 +25,13 @@ class ToolbarComponent extends React.Component {
if (!key) key = o.type + '_' + i; if (!key) key = o.type + '_' + i;
const props = Object.assign({ const props = Object.assign(
{
key: key, key: key,
theme: this.props.theme, theme: this.props.theme,
}, o); },
o
);
if (itemType === 'button') { if (itemType === 'button') {
itemComps.push(<ToolbarButton {...props} />); itemComps.push(<ToolbarButton {...props} />);
@@ -43,14 +43,13 @@ class ToolbarComponent extends React.Component {
return ( return (
<div className="editor-toolbar" style={style}> <div className="editor-toolbar" style={style}>
{ itemComps } {itemComps}
</div> </div>
); );
} }
} }
const mapStateToProps = (state) => { const mapStateToProps = state => {
return { theme: state.settings.theme }; return { theme: state.settings.theme };
}; };

View File

@@ -1,9 +1,7 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
class ToolbarButton extends React.Component { class ToolbarButton extends React.Component {
render() { render() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
@@ -16,13 +14,13 @@ class ToolbarButton extends React.Component {
if (this.props.iconName) { if (this.props.iconName) {
const iconStyle = { const iconStyle = {
fontSize: Math.round(theme.fontSize * 1.5), fontSize: Math.round(theme.fontSize * 1.5),
color: theme.color color: theme.color,
}; };
if (title) iconStyle.marginRight = 5; 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']; let classes = ['button'];
if (!isEnabled) classes.push('disabled'); if (!isEnabled) classes.push('disabled');
@@ -36,13 +34,15 @@ class ToolbarButton extends React.Component {
style={finalStyle} style={finalStyle}
title={tooltip} title={tooltip}
href="#" href="#"
onClick={() => { if (isEnabled && this.props.onClick) this.props.onClick() }} onClick={() => {
if (isEnabled && this.props.onClick) this.props.onClick();
}}
> >
{icon}{title} {icon}
{title}
</a> </a>
); );
} }
} }
module.exports = ToolbarButton; module.exports = ToolbarButton;

View File

@@ -1,22 +1,14 @@
const React = require('react'); const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js'); const { themeStyle } = require('../theme.js');
class ToolbarSpace extends React.Component { class ToolbarSpace extends React.Component {
render() { render() {
const theme = themeStyle(this.props.theme); const theme = themeStyle(this.props.theme);
const style = Object.assign({}, theme.toolbarStyle); const style = Object.assign({}, theme.toolbarStyle);
style.minWidth = style.height / 2; style.minWidth = style.height / 2;
return ( return <span style={style}></span>;
<span
style={style}
>
</span>
);
} }
} }
module.exports = ToolbarSpace; module.exports = ToolbarSpace;

View File

@@ -1,12 +1,11 @@
const React = require("react"); const React = require('react');
const electron = require('electron'); const electron = require('electron');
class VerticalResizer extends React.PureComponent { class VerticalResizer extends React.PureComponent {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
parentRight: 0, parentRight: 0,
parentHeight: 0, parentHeight: 0,
parentWidth: 0, parentWidth: 0,
@@ -29,9 +28,9 @@ class VerticalResizer extends React.PureComponent {
} }
onDragStart(event) { 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();
@@ -39,7 +38,7 @@ class VerticalResizer extends React.PureComponent {
drag: { drag: {
startX: cursor.x, startX: cursor.x,
lastX: cursor.x, lastX: cursor.x,
} },
}); });
if (this.props.onDragStart) this.props.onDragStart({}); if (this.props.onDragStart) this.props.onDragStart({});
@@ -58,11 +57,14 @@ class VerticalResizer extends React.PureComponent {
const delta = newX - this.state.drag.lastX; const delta = newX - this.state.drag.lastX;
if (!delta) return; if (!delta) return;
this.setState({ this.setState(
{
drag: Object.assign({}, this.state.drag, { lastX: newX }), drag: Object.assign({}, this.state.drag, { lastX: newX }),
}, () => { },
() => {
this.props.onDrag({ deltaX: delta }); this.props.onDrag({ deltaX: delta });
}); }
);
} }
onDragEnd(event) { onDragEnd(event) {
@@ -76,26 +78,22 @@ class VerticalResizer extends React.PureComponent {
render() { render() {
const debug = false; const debug = false;
const rootStyle = Object.assign({}, { const rootStyle = Object.assign(
{},
{
height: '100%', height: '100%',
width:5, width: 5,
borderColor:'red', borderColor: 'red',
borderWidth: debug ? 1 : 0, borderWidth: debug ? 1 : 0,
borderStyle:'solid', borderStyle: 'solid',
cursor: 'col-resize', cursor: 'col-resize',
boxSizing: 'border-box', boxSizing: 'border-box',
opacity: 0, opacity: 0,
}, this.props.style); },
this.props.style
return (
<div
style={rootStyle}
draggable={true}
onDragStart={this.onDragStart}
onDrag={this.onDrag}
onDragEnd={this.onDragEnd}
/>
); );
return <div style={rootStyle} draggable={true} onDragStart={this.onDragStart} onDrag={this.onDrag} onDragEnd={this.onDragEnd} />;
} }
} }

View File

@@ -1,7 +1,6 @@
const smalltalk = require('smalltalk'); const smalltalk = require('smalltalk');
class Dialogs { class Dialogs {
async alert(message, title = '') { async alert(message, title = '') {
await smalltalk.alert(title, message); await smalltalk.alert(title, message);
} }
@@ -25,7 +24,6 @@ class Dialogs {
return null; return null;
} }
} }
} }
const dialogs = new Dialogs(); const dialogs = new Dialogs();

View File

@@ -17,7 +17,7 @@ ipcRenderer.on('setMarkers', (event, keywords, options) => {
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords, options: 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 // 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; if (!event.data || event.data.target !== 'main') return;

View File

@@ -1,6 +1,4 @@
const { time } = require('lib/time-utils.js');
const BaseModel = require('lib/BaseModel'); const BaseModel = require('lib/BaseModel');
const markJsUtils = require('lib/markJsUtils');
const { _ } = require('lib/locale.js'); const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge'); const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu; const Menu = bridge().Menu;
@@ -8,49 +6,62 @@ const MenuItem = bridge().MenuItem;
const eventManager = require('../../eventManager'); const eventManager = require('../../eventManager');
const InteropService = require('lib/services/InteropService'); const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('../../InteropServiceHelper.js'); const InteropServiceHelper = require('../../InteropServiceHelper.js');
const Search = require('lib/models/Search');
const Note = require('lib/models/Note'); const Note = require('lib/models/Note');
const SearchEngine = require('lib/services/SearchEngine'); const { substrWithEllipsis } = require('lib/string-utils');
const { replaceRegexDiacritics, pregQuote, substrWithEllipsis } = require('lib/string-utils');
class NoteListUtils { class NoteListUtils {
static makeContextMenu(noteIds, props) { 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; let hasEncrypted = false;
for (let i = 0; i < notes.length; i++) { 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) { if (!hasEncrypted) {
menu.append(new MenuItem({label: _('Add or remove tags'), enabled: noteIds.length === 1, click: async () => { menu.append(
new MenuItem({
label: _('Add or remove tags'),
enabled: noteIds.length === 1,
click: async () => {
props.dispatch({ props.dispatch({
type: 'WINDOW_COMMAND', type: 'WINDOW_COMMAND',
name: 'setTags', name: 'setTags',
noteId: noteIds[0], noteId: noteIds[0],
}); });
}})); },
})
);
menu.append(new MenuItem({label: _('Duplicate'), click: async () => { menu.append(
new MenuItem({
label: _('Duplicate'),
click: async () => {
for (let i = 0; i < noteIds.length; i++) { for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]); const note = await Note.load(noteIds[i]);
await Note.duplicate(noteIds[i], { await Note.duplicate(noteIds[i], {
uniqueTitle: _('%s - Copy', note.title), uniqueTitle: _('%s - Copy', note.title),
}); });
} }
}})); },
})
);
if (noteIds.length <= 1) { if (noteIds.length <= 1) {
menu.append(new MenuItem({label: _('Switch between note and to-do type'), click: async () => { menu.append(
new MenuItem({
label: _('Switch between note and to-do type'),
click: async () => {
for (let i = 0; i < noteIds.length; i++) { for (let i = 0; i < noteIds.length; i++) {
const note = await Note.load(noteIds[i]); const note = await Note.load(noteIds[i]);
await Note.save(Note.toggleIsTodo(note), { userSideValidation: true }); await Note.save(Note.toggleIsTodo(note), { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id }); eventManager.emit('noteTypeToggle', { noteId: note.id });
} }
}})); },
})
);
} else { } else {
const switchNoteType = async (noteIds, type) => { const switchNoteType = async (noteIds, type) => {
for (let i = 0; i < noteIds.length; i++) { for (let i = 0; i < noteIds.length; i++) {
@@ -60,18 +71,31 @@ class NoteListUtils {
await Note.save(newNote, { userSideValidation: true }); await Note.save(newNote, { userSideValidation: true });
eventManager.emit('noteTypeToggle', { noteId: note.id }); eventManager.emit('noteTypeToggle', { noteId: note.id });
} }
} };
menu.append(new MenuItem({label: _('Switch to note type'), click: async () => { menu.append(
new MenuItem({
label: _('Switch to note type'),
click: async () => {
await switchNoteType(noteIds, 'note'); await switchNoteType(noteIds, 'note');
}})); },
})
);
menu.append(new MenuItem({label: _('Switch to to-do type'), click: async () => { menu.append(
new MenuItem({
label: _('Switch to to-do type'),
click: async () => {
await switchNoteType(noteIds, 'todo'); await switchNoteType(noteIds, 'todo');
}})); },
})
);
} }
menu.append(new MenuItem({label: _('Copy Markdown link'), click: async () => { menu.append(
new MenuItem({
label: _('Copy Markdown link'),
click: async () => {
const { clipboard } = require('electron'); const { clipboard } = require('electron');
const links = []; const links = [];
for (let i = 0; i < noteIds.length; i++) { for (let i = 0; i < noteIds.length; i++) {
@@ -79,7 +103,9 @@ class NoteListUtils {
links.push(Note.markdownTag(note)); links.push(Note.markdownTag(note));
} }
clipboard.writeText(links.join(' ')); clipboard.writeText(links.join(' '));
}})); },
})
);
const exportMenu = new Menu(); const exportMenu = new Menu();
@@ -89,28 +115,43 @@ class NoteListUtils {
const module = ioModules[i]; const module = ioModules[i];
if (module.type !== 'exporter') continue; if (module.type !== 'exporter') continue;
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => { exportMenu.append(
new MenuItem({
label: module.fullLabel(),
click: async () => {
await InteropServiceHelper.export(props.dispatch.bind(this), module, { sourceNoteIds: noteIds }); await InteropServiceHelper.export(props.dispatch.bind(this), module, { sourceNoteIds: noteIds });
}})); },
})
);
} }
if (noteIds.length === 1) { if (noteIds.length === 1) {
exportMenu.append(new MenuItem({ label: 'PDF - ' + _('PDF File') , click: () => { exportMenu.append(
new MenuItem({
label: 'PDF - ' + _('PDF File'),
click: () => {
props.dispatch({ props.dispatch({
type: 'WINDOW_COMMAND', type: 'WINDOW_COMMAND',
name: 'exportPdf', name: 'exportPdf',
}); });
}})); },
})
);
} }
const exportMenuItem = new MenuItem({label: _('Export'), submenu: exportMenu}); const exportMenuItem = new MenuItem({ label: _('Export'), submenu: exportMenu });
menu.append(exportMenuItem); menu.append(exportMenuItem);
} }
menu.append(new MenuItem({label: _('Delete'), click: async () => { menu.append(
new MenuItem({
label: _('Delete'),
click: async () => {
await this.confirmDeleteNotes(noteIds); await this.confirmDeleteNotes(noteIds);
}})); },
})
);
return menu; return menu;
} }
@@ -135,7 +176,6 @@ class NoteListUtils {
if (!ok) return; if (!ok) return;
await Note.batchDelete(noteIds); await Note.batchDelete(noteIds);
} }
} }
module.exports = NoteListUtils; module.exports = NoteListUtils;