1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Started applying config to Electron app

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

View File

@ -1,23 +1,16 @@
const React = require('react');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { bridge } = require('electron').remote.require('./bridge');
const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js');
const SyncTargetRegistry = require('lib/SyncTargetRegistry');
const { _ } = require('lib/locale.js');
const Shared = require('lib/components/shared/dropbox-login-shared');
class DropboxLoginScreenComponent extends React.Component {
constructor() {
super();
this.shared_ = new Shared(
this,
(msg) => bridge().showInfoMessageBox(msg),
(msg) => bridge().showErrorMessageBox(msg)
);
this.shared_ = new Shared(this, msg => bridge().showInfoMessageBox(msg), msg => bridge().showErrorMessageBox(msg));
}
componentWillMount() {
@ -42,18 +35,23 @@ class DropboxLoginScreenComponent extends React.Component {
<div style={containerStyle}>
<p style={theme.textStyle}>{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}</p>
<p style={theme.textStyle}>{_('Step 1: Open this URL in your browser to authorise the application:')}</p>
<a style={theme.textStyle} href="#" onClick={this.shared_.loginUrl_click}>{this.state.loginUrl}</a>
<a style={theme.textStyle} href="#" onClick={this.shared_.loginUrl_click}>
{this.state.loginUrl}
</a>
<p style={theme.textStyle}>{_('Step 2: Enter the code provided by Dropbox:')}</p>
<p><input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle}/></p>
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>{_('Submit')}</button>
<p>
<input type="text" value={this.state.authCode} onChange={this.shared_.authCodeInput_change} style={inputStyle} />
</p>
<button disabled={this.state.checkingAuthToken} onClick={this.shared_.submit_click}>
{_('Submit')}
</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
};

View File

@ -1,7 +1,6 @@
const React = require('react');
const { connect } = require('react-redux');
const Setting = require('lib/models/Setting');
const BaseItem = require('lib/models/BaseItem');
const EncryptionService = require('lib/services/EncryptionService');
const { Header } = require('./Header.min.js');
const { themeStyle } = require('../theme.js');
@ -9,11 +8,9 @@ const { _ } = require('lib/locale.js');
const { time } = require('lib/time-utils.js');
const dialogs = require('./dialogs');
const shared = require('lib/components/shared/encryption-config-shared.js');
const pathUtils = require('lib/path-utils.js');
const { bridge } = require('electron').remote.require('./bridge');
class EncryptionConfigScreenComponent extends React.Component {
constructor() {
super();
shared.constructor(this);
@ -55,15 +52,15 @@ class EncryptionConfigScreenComponent extends React.Component {
backgroundColor: theme.backgroundColor,
border: '1px solid',
borderColor: theme.dividerColor,
}
};
const onSaveClick = () => {
return shared.onSavePasswordClick(this, mk);
}
};
const onPasswordChange = (event) => {
const onPasswordChange = event => {
return shared.onPasswordChange(this, mk, event.target.value);
}
};
const password = this.state.passwords[mk.id] ? this.state.passwords[mk.id] : '';
const active = this.props.activeMasterKeyId === mk.id ? '✔' : '';
@ -76,7 +73,12 @@ class EncryptionConfigScreenComponent extends React.Component {
<td style={theme.textStyle}>{mk.source_application}</td>
<td style={theme.textStyle}>{time.formatMsToLocal(mk.created_time)}</td>
<td style={theme.textStyle}>{time.formatMsToLocal(mk.updated_time)}</td>
<td style={theme.textStyle}><input type="password" style={passwordStyle} value={password} onChange={(event) => onPasswordChange(event)}/> <button style={theme.buttonStyle} onClick={() => onSaveClick()}>{_('Save')}</button></td>
<td style={theme.textStyle}>
<input type="password" style={passwordStyle} value={password} onChange={event => onPasswordChange(event)} />{' '}
<button style={theme.buttonStyle} onClick={() => onSaveClick()}>
{_('Save')}
</button>
</td>
<td style={theme.textStyle}>{passwordOk}</td>
</tr>
);
@ -128,10 +130,19 @@ class EncryptionConfigScreenComponent extends React.Component {
} catch (error) {
await dialogs.alert(error.message);
}
}
};
const decryptedItemsInfo = <p style={theme.textStyle}>{shared.decryptedStatText(this)}</p>;
const toggleButton = <button style={theme.buttonStyle} onClick={() => { onToggleButtonClick() }}>{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}</button>
const toggleButton = (
<button
style={theme.buttonStyle}
onClick={() => {
onToggleButtonClick();
}}
>
{this.props.encryptionEnabled ? _('Disable encryption') : _('Enable encryption')}
</button>
);
let masterKeySection = null;
@ -164,7 +175,11 @@ class EncryptionConfigScreenComponent extends React.Component {
const rows = [];
for (let i = 0; i < nonExistingMasterKeyIds.length; i++) {
const id = nonExistingMasterKeyIds[i];
rows.push(<tr key={id}><td style={theme.textStyle}>{id}</td></tr>);
rows.push(
<tr key={id}>
<td style={theme.textStyle}>{id}</td>
</tr>
);
}
nonExistingMasterKeySection = (
@ -176,7 +191,7 @@ class EncryptionConfigScreenComponent extends React.Component {
<tr>
<th style={theme.textStyle}>{_('ID')}</th>
</tr>
{ rows }
{rows}
</tbody>
</table>
</div>
@ -187,13 +202,25 @@ class EncryptionConfigScreenComponent extends React.Component {
<div>
<Header style={headerStyle} />
<div style={containerStyle}>
{<div style={{backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
<p style={theme.textStyle}>
<span>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</span> <a onClick={() => {bridge().openExternal('https://joplinapp.org/e2ee/')}} href="#">https://joplinapp.org/e2ee/</a>
</p>
</div>}
{
<div style={{ backgroundColor: theme.warningBackgroundColor, paddingLeft: 10, paddingRight: 10, paddingTop: 2, paddingBottom: 2 }}>
<p style={theme.textStyle}>
<span>{_('For more information about End-To-End Encryption (E2EE) and advices on how to enable it please check the documentation:')}</span>{' '}
<a
onClick={() => {
bridge().openExternal('https://joplinapp.org/e2ee/');
}}
href="#"
>
https://joplinapp.org/e2ee/
</a>
</p>
</div>
}
<h1 style={theme.h1Style}>{_('Status')}</h1>
<p style={theme.textStyle}>{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong></p>
<p style={theme.textStyle}>
{_('Encryption is:')} <strong>{this.props.encryptionEnabled ? _('Enabled') : _('Disabled')}</strong>
</p>
{decryptedItemsInfo}
{toggleButton}
{masterKeySection}
@ -202,10 +229,9 @@ class EncryptionConfigScreenComponent extends React.Component {
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
masterKeys: state.masterKeys,

View File

@ -1,12 +1,10 @@
const React = require('react');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge');
class HeaderComponent extends React.Component {
constructor() {
super();
this.state = {
@ -18,13 +16,13 @@ class HeaderComponent extends React.Component {
this.searchOnQuery_ = null;
this.searchElement_ = null;
const triggerOnQuery = (query) => {
const triggerOnQuery = query => {
clearTimeout(this.scheduleSearchChangeEventIid_);
if (this.searchOnQuery_) this.searchOnQuery_(query);
this.scheduleSearchChangeEventIid_ = null;
}
};
this.search_onChange = (event) => {
this.search_onChange = event => {
this.setState({ searchQuery: event.target.value });
if (this.scheduleSearchChangeEventIid_) clearTimeout(this.scheduleSearchChangeEventIid_);
@ -34,10 +32,10 @@ class HeaderComponent extends React.Component {
}, 500);
};
this.search_onClear = (event) => {
this.search_onClear = event => {
this.resetSearch();
if (this.searchElement_) this.searchElement_.focus();
}
};
this.search_onFocus = event => {
if (this.hideSearchUsageLinkIID_) {
@ -46,7 +44,7 @@ class HeaderComponent extends React.Component {
}
this.setState({ showSearchUsageLink: true });
}
};
this.search_onBlur = event => {
if (this.hideSearchUsageLinkIID_) return;
@ -54,22 +52,23 @@ class HeaderComponent extends React.Component {
this.hideSearchUsageLinkIID_ = setTimeout(() => {
this.setState({ showSearchUsageLink: false });
}, 5000);
}
};
this.search_keyDown = event => {
if (event.keyCode === 27) { // ESCAPE
if (event.keyCode === 27) {
// ESCAPE
this.resetSearch();
}
}
};
this.resetSearch = () => {
this.setState({ searchQuery: '' });
triggerOnQuery('');
}
};
this.searchUsageLink_click = event => {
bridge().openExternal('https://joplinapp.org/#searching');
}
};
}
async componentWillReceiveProps(nextProps) {
@ -79,11 +78,11 @@ class HeaderComponent extends React.Component {
}
componentDidUpdate(prevProps) {
if(prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
if (prevProps.notesParentType !== this.props.notesParentType && this.props.notesParentType !== 'Search' && this.state.searchQuery) {
this.resetSearch();
}
}
componentWillUnmount() {
if (this.hideSearchUsageLinkIID_) {
clearTimeout(this.hideSearchUsageLinkIID_);
@ -122,14 +121,14 @@ class HeaderComponent extends React.Component {
color: style.color,
};
if (options.title) iconStyle.marginRight = 5;
if("undefined" != typeof(options.iconRotation)) {
iconStyle.transition = "transform 0.15s ease-in-out";
if ('undefined' != typeof options.iconRotation) {
iconStyle.transition = 'transform 0.15s ease-in-out';
iconStyle.transform = 'rotate(' + options.iconRotation + 'deg)';
}
icon = <i style={iconStyle} className={"fa " + options.iconName}></i>
icon = <i style={iconStyle} className={'fa ' + options.iconName}></i>;
}
const isEnabled = (!('enabled' in options) || options.enabled);
const isEnabled = !('enabled' in options) || options.enabled;
let classes = ['button'];
if (!isEnabled) classes.push('disabled');
@ -139,16 +138,21 @@ class HeaderComponent extends React.Component {
const title = options.title ? options.title : '';
return <a
className={classes.join(' ')}
style={finalStyle}
key={key}
href="#"
title={title}
onClick={() => { if (isEnabled) options.onClick() }}
>
{icon}<span className="title">{title}</span>
</a>
return (
<a
className={classes.join(' ')}
style={finalStyle}
key={key}
href="#"
title={title}
onClick={() => {
if (isEnabled) options.onClick();
}}
>
{icon}
<span className="title">{title}</span>
</a>
);
}
makeSearch(key, style, options, state) {
@ -159,8 +163,8 @@ class HeaderComponent extends React.Component {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
paddingTop: 1, // vertical alignment with buttons
paddingBottom: 0, // vertical alignment with buttons
paddingTop: 1, // vertical alignment with buttons
paddingBottom: 0, // vertical alignment with buttons
height: style.fontSize * 2,
color: style.color,
fontSize: style.fontSize,
@ -191,33 +195,24 @@ class HeaderComponent extends React.Component {
};
const iconName = state.searchQuery ? 'fa-times' : 'fa-search';
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
const icon = <i style={iconStyle} className={'fa ' + iconName}></i>;
if (options.onQuery) this.searchOnQuery_ = options.onQuery;
const usageLink = !this.state.showSearchUsageLink ? null : (
<a onClick={this.searchUsageLink_click} style={theme.urlStyle} href="#">{_('Usage')}</a>
<a onClick={this.searchUsageLink_click} style={theme.urlStyle} href="#">
{_('Usage')}
</a>
);
return (
<div key={key} style={containerStyle}>
<input
type="text"
style={inputStyle}
placeholder={options.title}
value={state.searchQuery}
onChange={this.search_onChange}
ref={elem => this.searchElement_ = elem}
onFocus={this.search_onFocus}
onBlur={this.search_onBlur}
onKeyDown={this.search_keyDown}
/>
<a
href="#"
style={searchButton}
onClick={this.search_onClear}
>{icon}</a>
<input type="text" style={inputStyle} placeholder={options.title} value={state.searchQuery} onChange={this.search_onChange} ref={elem => (this.searchElement_ = elem)} onFocus={this.search_onFocus} onBlur={this.search_onBlur} onKeyDown={this.search_keyDown} />
<a href="#" style={searchButton} onClick={this.search_onClear}>
{icon}
</a>
{usageLink}
</div>);
</div>
);
}
render() {
@ -226,7 +221,7 @@ class HeaderComponent extends React.Component {
const showBackButton = this.props.showBackButton === undefined || this.props.showBackButton === true;
style.height = theme.headerHeight;
style.display = 'flex';
style.flexDirection = 'row';
style.flexDirection = 'row';
style.borderBottom = '1px solid ' + theme.dividerColor;
style.boxSizing = 'border-box';
@ -266,14 +261,13 @@ class HeaderComponent extends React.Component {
return (
<div className="header" style={style}>
{ items }
{items}
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
windowCommand: state.windowCommand,

View File

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

View File

@ -1,9 +1,7 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js');
class IconButton extends React.Component {
render() {
const style = this.props.style;
const theme = themeStyle(this.props.theme);
@ -11,28 +9,37 @@ class IconButton extends React.Component {
color: theme.color,
fontSize: theme.fontSize * 1.4,
};
const icon = <i style={iconStyle} className={"fa " + this.props.iconName}></i>
const icon = <i style={iconStyle} className={'fa ' + this.props.iconName}></i>;
const rootStyle = Object.assign({
display: 'flex',
textDecoration: 'none',
padding: 10,
width: theme.buttonMinHeight,
height: theme.buttonMinHeight,
boxSizing: 'border-box',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.backgroundColor,
cursor: 'default',
}, style);
const rootStyle = Object.assign(
{
display: 'flex',
textDecoration: 'none',
padding: 10,
width: theme.buttonMinHeight,
height: theme.buttonMinHeight,
boxSizing: 'border-box',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.backgroundColor,
cursor: 'default',
},
style
);
return (
<a href="#" style={rootStyle} className="icon-button" onClick={() => { if (this.props.onClick) this.props.onClick() }}>
{ icon }
<a
href="#"
style={rootStyle}
className="icon-button"
onClick={() => {
if (this.props.onClick) this.props.onClick();
}}
>
{icon}
</a>
);
}
}
module.exports = { IconButton };

View File

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

View File

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

View File

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

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

View File

@ -7,20 +7,14 @@ const BaseModel = require('lib/BaseModel');
const markJsUtils = require('lib/markJsUtils');
const { _ } = require('lib/locale.js');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const eventManager = require('../eventManager');
const InteropService = require('lib/services/InteropService');
const InteropServiceHelper = require('../InteropServiceHelper.js');
const Search = require('lib/models/Search');
const { stateUtils } = require('lib/reducer');
const Mark = require('mark.js/dist/mark.min.js');
const SearchEngine = require('lib/services/SearchEngine');
const Note = require('lib/models/Note');
const NoteListUtils = require('./utils/NoteListUtils');
const { replaceRegexDiacritics, pregQuote } = require('lib/string-utils');
class NoteListComponent extends React.Component {
constructor() {
super();
@ -116,9 +110,9 @@ class NoteListComponent extends React.Component {
id: item.id,
});
}
}
};
const onDragStart = (event) => {
const onDragStart = event => {
let noteIds = [];
// Here there is two cases:
@ -132,21 +126,21 @@ class NoteListComponent extends React.Component {
}
if (!noteIds.length) return;
event.dataTransfer.setDragImage(new Image(), 1, 1);
event.dataTransfer.clearData();
event.dataTransfer.setData('text/x-jop-note-ids', JSON.stringify(noteIds));
}
};
const onCheckboxClick = async (event) => {
const onCheckboxClick = async event => {
const checked = event.target.checked;
const newNote = {
id: item.id,
todo_completed: checked ? time.unixMs() : 0,
}
};
await Note.save(newNote, { userSideValidation: true });
eventManager.emit('todoToggle', { noteId: item.id });
}
};
const hPadding = 10;
@ -167,11 +161,18 @@ class NoteListComponent extends React.Component {
// Setting marginBottom = 1 because it makes the checkbox looks more centered, at least on Windows
// but don't know how it will look in other OSes.
const checkbox = item.is_todo ?
<div style={{display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding}}>
<input style={{margin:0, marginBottom:1}} type="checkbox" defaultChecked={!!item.todo_completed} onClick={(event) => { onCheckboxClick(event, item) }}/>
const checkbox = item.is_todo ? (
<div style={{ display: 'flex', height: style.height, alignItems: 'center', paddingLeft: hPadding }}>
<input
style={{ margin: 0, marginBottom: 1 }}
type="checkbox"
defaultChecked={!!item.todo_completed}
onClick={event => {
onCheckboxClick(event, item);
}}
/>
</div>
: null;
) : null;
let listItemTitleStyle = Object.assign({}, this.style().listItemTitle);
listItemTitleStyle.paddingLeft = !checkbox ? hPadding : 4;
@ -204,41 +205,43 @@ class NoteListComponent extends React.Component {
// with `textContent` so it cannot contain any XSS attacks. We use this feature because
// mark.js can only deal with DOM elements.
// https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>
titleComp = <span dangerouslySetInnerHTML={{ __html: titleElement.outerHTML }}></span>;
} else {
titleComp = <span>{displayTitle}</span>
titleComp = <span>{displayTitle}</span>;
}
const watchedIconStyle = {
paddingRight: 4,
color: theme.color,
};
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : (
<i style={watchedIconStyle} className={"fa fa-external-link"}></i>
);
const watchedIcon = this.props.watchedNoteFiles.indexOf(item.id) < 0 ? null : <i style={watchedIconStyle} className={'fa fa-external-link'}></i>;
if (!this.itemAnchorRefs_[item.id]) this.itemAnchorRefs_[item.id] = React.createRef();
const ref = this.itemAnchorRefs_[item.id];
// Need to include "todo_completed" in key so that checkbox is updated when
// item is changed via sync.
return <div key={item.id + '_' + item.todo_completed} style={style}>
{checkbox}
<a
ref={ref}
className="list-item"
onContextMenu={(event) => this.itemContextMenu(event)}
href="#"
draggable={true}
style={listItemTitleStyle}
onClick={(event) => { onTitleClick(event, item) }}
onDragStart={(event) => onDragStart(event) }
data-id={item.id}
>
{watchedIcon}
{titleComp}
</a>
</div>
// item is changed via sync.
return (
<div key={item.id + '_' + item.todo_completed} style={style}>
{checkbox}
<a
ref={ref}
className="list-item"
onContextMenu={event => this.itemContextMenu(event)}
href="#"
draggable={true}
style={listItemTitleStyle}
onClick={event => {
onTitleClick(event, item);
}}
onDragStart={event => onDragStart(event)}
data-id={item.id}
>
{watchedIcon}
{titleComp}
</a>
</div>
);
}
itemAnchorRef(itemId) {
@ -279,7 +282,7 @@ class NoteListComponent extends React.Component {
if (this.props.notes[i].id === id) {
this.itemListRef.current.makeItemIndexVisible(i);
break;
}
}
}
}
}
@ -288,7 +291,8 @@ class NoteListComponent extends React.Component {
const keyCode = event.keyCode;
const noteIds = this.props.selectedNoteIds;
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) { // DOWN / UP
if (noteIds.length === 1 && (keyCode === 40 || keyCode === 38)) {
// DOWN / UP
const noteId = noteIds[0];
let noteIndex = BaseModel.modelIndexById(this.props.notes, noteId);
const inc = keyCode === 38 ? -1 : +1;
@ -312,12 +316,14 @@ class NoteListComponent extends React.Component {
event.preventDefault();
}
if (noteIds.length && (keyCode === 46 || (keyCode === 8 && event.metaKey))) { // DELETE / CMD+Backspace
if (noteIds.length && (keyCode === 46 || (keyCode === 8 && event.metaKey))) {
// DELETE / CMD+Backspace
event.preventDefault();
await NoteListUtils.confirmDeleteNotes(noteIds);
}
if (noteIds.length && keyCode === 32) { // SPACE
if (noteIds.length && keyCode === 32) {
// SPACE
event.preventDefault();
const notes = BaseModel.modelsByIds(this.props.notes, noteIds);
@ -332,7 +338,8 @@ class NoteListComponent extends React.Component {
this.focusNoteId_(todos[0].id);
}
if (keyCode === 9) { // TAB
if (keyCode === 9) {
// TAB
event.preventDefault();
if (event.shiftKey) {
@ -361,7 +368,7 @@ class NoteListComponent extends React.Component {
this.focusItemIID_ = setInterval(() => {
if (this.itemAnchorRef(noteId)) {
this.itemAnchorRef(noteId).focus();
clearInterval(this.focusItemIID_)
clearInterval(this.focusItemIID_);
this.focusItemIID_ = null;
}
}, 10);
@ -381,37 +388,29 @@ class NoteListComponent extends React.Component {
const theme = themeStyle(this.props.theme);
const style = this.props.style;
let notes = this.props.notes.slice();
if (!notes.length) {
const padding = 10;
const emptyDivStyle = Object.assign({
padding: padding + 'px',
fontSize: theme.fontSize,
color: theme.color,
backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily,
}, style);
const emptyDivStyle = Object.assign(
{
padding: padding + 'px',
fontSize: theme.fontSize,
color: theme.color,
backgroundColor: theme.backgroundColor,
fontFamily: theme.fontFamily,
},
style
);
emptyDivStyle.width = emptyDivStyle.width - padding * 2;
emptyDivStyle.height = emptyDivStyle.height - padding * 2;
return <div style={emptyDivStyle}>{ this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>
return <div style={emptyDivStyle}>{this.props.folders.length ? _('No notes in here. Create one by clicking on "New note".') : _('There is currently no notebook. Create one by clicking on "New notebook".')}</div>;
}
return (
<ItemList
ref={this.itemListRef}
itemHeight={this.style().listItem.height}
className={"note-list"}
items={notes}
style={style}
itemRenderer={this.itemRenderer}
onKeyDown={this.onKeyDown}
/>
);
return <ItemList ref={this.itemListRef} itemHeight={this.style().listItem.height} className={'note-list'} items={notes} style={style} itemRenderer={this.itemRenderer} onKeyDown={this.onKeyDown} />;
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
notes: state.notes,
folders: state.folders,
@ -427,4 +426,4 @@ const mapStateToProps = (state) => {
const NoteList = connect(mapStateToProps)(NoteListComponent);
module.exports = { NoteList };
module.exports = { NoteList };

View File

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

View File

@ -6,6 +6,7 @@ const NoteTextViewer = require('./NoteTextViewer.min');
const HelpButton = require('./HelpButton.min');
const BaseModel = require('lib/BaseModel');
const Revision = require('lib/models/Revision');
const Note = require('lib/models/Note');
const Setting = require('lib/models/Setting');
const RevisionService = require('lib/services/RevisionService');
const shared = require('lib/components/shared/note-screen-shared.js');
@ -15,7 +16,6 @@ const ReactTooltip = require('react-tooltip');
const { substrWithEllipsis } = require('lib/string-utils');
class NoteRevisionViewerComponent extends React.PureComponent {
constructor() {
super();
@ -55,13 +55,16 @@ class NoteRevisionViewerComponent extends React.PureComponent {
// this.viewerRef_.current.wrappedInstance.openDevTools();
const revisions = await Revision.allByType(BaseModel.TYPE_NOTE, this.props.noteId);
this.setState({
revisions: revisions,
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
}, () => {
this.reloadNote();
});
this.setState(
{
revisions: revisions,
currentRevId: revisions.length ? revisions[revisions.length - 1].id : '',
},
() => {
this.reloadNote();
}
);
}
async importButton_onClick() {
@ -82,11 +85,14 @@ class NoteRevisionViewerComponent extends React.PureComponent {
if (!value) {
if (this.props.onBack) this.props.onBack();
} else {
this.setState({
currentRevId: value,
}, () => {
this.reloadNote();
});
this.setState(
{
currentRevId: value,
},
() => {
this.reloadNote();
}
);
}
}
@ -117,7 +123,7 @@ class NoteRevisionViewerComponent extends React.PureComponent {
resources: await shared.attachedResources(noteBody),
});
this.viewerRef_.current.wrappedInstance.send('setHtml', result.html, { cssFiles: result.cssFiles });
this.viewerRef_.current.wrappedInstance.send('setHtml', result.html, { cssFiles: result.cssFiles });
}
render() {
@ -130,45 +136,45 @@ class NoteRevisionViewerComponent extends React.PureComponent {
const rev = revs[i];
const stats = Revision.revisionPatchStatsText(rev);
revisionListItems.push(<option
key={rev.id}
value={rev.id}
>{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}</option>);
revisionListItems.push(
<option key={rev.id} value={rev.id}>
{time.formatMsToLocal(rev.item_updated_time) + ' (' + stats + ')'}
</option>
);
}
const restoreButtonTitle = _('Restore');
const helpMessage = _('Click "%s" to restore the note. It will be copied in the notebook named "%s". The current version of the note will not be replaced or modified.', restoreButtonTitle, RevisionService.instance().restoreFolderTitle());
const titleInput = (
<div style={{display:'flex', flexDirection: 'row', alignItems:'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom:10}}>
<button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>{'⬅ ' + _('Back')}</button>
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''}/>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', marginBottom: 10, borderWidth: 1, borderBottomStyle: 'solid', borderColor: theme.dividerColor, paddingBottom: 10 }}>
<button onClick={this.backButton_click} style={Object.assign({}, theme.buttonStyle, { marginRight: 10, height: theme.inputStyle.height })}>
{'⬅ ' + _('Back')}
</button>
<input readOnly type="text" style={style.titleInput} value={this.state.note ? this.state.note.title : ''} />
<select disabled={!this.state.revisions.length} value={this.state.currentRevId} style={style.revisionList} onChange={this.revisionList_onChange}>
{revisionListItems}
</select>
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>{restoreButtonTitle}</button>
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick}/>
<button disabled={!this.state.revisions.length || this.state.restoring} onClick={this.importButton_onClick} style={Object.assign({}, theme.buttonStyle, { marginLeft: 10, height: theme.inputStyle.height })}>
{restoreButtonTitle}
</button>
<HelpButton tip={helpMessage} id="noteRevisionHelpButton" onClick={this.helpButton_onClick} />
</div>
);
const viewer = <NoteTextViewer
viewerStyle={{display:'flex', flex:1}}
ref={this.viewerRef_}
onDomReady={this.viewer_domReady}
/>
const viewer = <NoteTextViewer viewerStyle={{ display: 'flex', flex: 1 }} ref={this.viewerRef_} onDomReady={this.viewer_domReady} />;
return (
<div style={style.root}>
{titleInput}
{viewer}
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip"/>
<ReactTooltip place="bottom" delayShow={300} className="help-tooltip" />
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
};
@ -176,4 +182,4 @@ const mapStateToProps = (state) => {
const NoteRevisionViewer = connect(mapStateToProps)(NoteRevisionViewerComponent);
module.exports = NoteRevisionViewer;
module.exports = NoteRevisionViewer;

View File

@ -4,7 +4,6 @@ const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class NoteSearchBarComponent extends React.Component {
constructor() {
super();
@ -54,14 +53,12 @@ class NoteSearchBarComponent extends React.Component {
color: theme.color,
};
const icon = <i style={iconStyle} className={"fa " + iconName}></i>
const icon = <i style={iconStyle} className={'fa ' + iconName}></i>;
return (
<a
href="#"
style={searchButton}
onClick={clickHandler}
>{icon}</a>
<a href="#" style={searchButton} onClick={clickHandler}>
{icon}
</a>
);
}
@ -72,7 +69,8 @@ class NoteSearchBarComponent extends React.Component {
}
searchInput_keyDown(event) {
if (event.keyCode === 13) { // ENTER
if (event.keyCode === 13) {
// ENTER
event.preventDefault();
if (!event.shiftKey) {
@ -82,7 +80,8 @@ class NoteSearchBarComponent extends React.Component {
}
}
if (event.keyCode === 27) { // ESCAPE
if (event.keyCode === 27) {
// ESCAPE
event.preventDefault();
if (this.props.onClose) this.props.onClose();
@ -110,32 +109,34 @@ class NoteSearchBarComponent extends React.Component {
}
render() {
const theme = themeStyle(this.props.theme);
const closeButton = this.buttonIconComponent('fa-times', this.closeButton_click);
const previousButton = this.buttonIconComponent('fa-chevron-up', this.previousButton_click);
const nextButton = this.buttonIconComponent('fa-chevron-down', this.nextButton_click);
return (
<div style={this.props.style}>
<div style={{display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{ closeButton }
<input placeholder={_('Search...')} value={this.state.query} onChange={this.searchInput_change} onKeyDown={this.searchInput_keyDown} ref="searchInput" type="text" style={{width: 200, marginRight: 5}}></input>
{ nextButton }
{ previousButton }
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
{closeButton}
<input placeholder={_('Search...')} value={this.state.query} onChange={this.searchInput_change} onKeyDown={this.searchInput_keyDown} ref="searchInput" type="text" style={{ width: 200, marginRight: 5 }}></input>
{nextButton}
{previousButton}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
};
};
const NoteSearchBar = connect(mapStateToProps, null, null, { withRef: true })(NoteSearchBarComponent);
const NoteSearchBar = connect(
mapStateToProps,
null,
null,
{ withRef: true }
)(NoteSearchBarComponent);
module.exports = NoteSearchBar;
module.exports = NoteSearchBar;

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,7 @@
const React = require('react');
const { connect } = require('react-redux');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class NoteTextViewerComponent extends React.Component {
constructor() {
super();
@ -49,7 +46,7 @@ class NoteTextViewerComponent extends React.Component {
let isAlreadyReady = false;
try {
isAlreadyReady = !this.webviewRef_.current.isLoading()
isAlreadyReady = !this.webviewRef_.current.isLoading();
} catch (error) {
// Ignore - it means the view has not started loading, and the DOM ready event has not been emitted yet
// Error is "The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called."
@ -111,14 +108,14 @@ class NoteTextViewerComponent extends React.Component {
return this.webviewRef_.current.getWebContents().printToPDF(options, callback);
}
print(options = {}) {
print() {
// In Electron 4x, print is broken so need to use this hack:
// https://github.com/electron/electron/issues/16219#issuecomment-451454948
// Note that this is not a perfect workaround since it means the options are ignored
// In particular it means that background images and colours won't be printed (printBackground property will be ignored)
// return this.webviewRef_.current.getWebContents().print({});
return this.webviewRef_.current.getWebContents().executeJavaScript("window.print()")
return this.webviewRef_.current.getWebContents().executeJavaScript('window.print()');
}
openDevTools() {
@ -138,23 +135,21 @@ class NoteTextViewerComponent extends React.Component {
// ----------------------------------------------------------------
render() {
return <webview
ref={this.webviewRef_}
style={this.props.viewerStyle}
preload="gui/note-viewer/preload.js"
src="gui/note-viewer/index.html"
webpreferences="contextIsolation"
/>
return <webview ref={this.webviewRef_} style={this.props.viewerStyle} preload="gui/note-viewer/preload.js" src="gui/note-viewer/index.html" webpreferences="contextIsolation" />;
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
};
};
const NoteTextViewer = connect(mapStateToProps, null, null, { withRef: true })(NoteTextViewerComponent);
const NoteTextViewer = connect(
mapStateToProps,
null,
null,
{ withRef: true }
)(NoteTextViewerComponent);
module.exports = NoteTextViewer;
module.exports = NoteTextViewer;

View File

@ -7,7 +7,6 @@ const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
class OneDriveLoginScreenComponent extends React.Component {
constructor() {
super();
this.webview_ = null;
@ -36,8 +35,8 @@ class OneDriveLoginScreenComponent extends React.Component {
webview_domReady() {
this.setState({ webviewReady: true });
this.webview_.addEventListener('did-navigate', async (event) => {
this.webview_.addEventListener('did-navigate', async event => {
const url = event.url;
if (this.authCode_) return;
@ -50,11 +49,14 @@ class OneDriveLoginScreenComponent extends React.Component {
this.authCode_ = parsedUrl.query.code;
try {
await reg.syncTarget().api().execTokenRequest(this.authCode_, this.redirectUrl(), true);
await reg
.syncTarget()
.api()
.execTokenRequest(this.authCode_, this.redirectUrl(), true);
this.props.dispatch({ type: 'NAV_BACK' });
reg.scheduleSync(0);
} catch (error) {
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + "\n\n" + url.match(/.{1,64}/g).join('\n'));
bridge().showErrorMessageBox('Could not login to OneDrive. Please try again.\n\n' + error.message + '\n\n' + url.match(/.{1,64}/g).join('\n'));
}
this.authCode_ = null;
@ -62,11 +64,17 @@ class OneDriveLoginScreenComponent extends React.Component {
}
startUrl() {
return reg.syncTarget().api().authCodeUrl(this.redirectUrl());
return reg
.syncTarget()
.api()
.authCodeUrl(this.redirectUrl());
}
redirectUrl() {
return reg.syncTarget().api().nativeClientRedirectUrl();
return reg
.syncTarget()
.api()
.nativeClientRedirectUrl();
}
render() {
@ -94,14 +102,13 @@ class OneDriveLoginScreenComponent extends React.Component {
return (
<div>
<Header style={headerStyle} buttons={headerButtons} />
<webview src={this.startUrl()} style={webviewStyle} nodeintegration="1" ref={elem => this.webview_ = elem} />
<webview src={this.startUrl()} style={webviewStyle} nodeintegration="1" ref={elem => (this.webview_ = elem)} />
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return {
theme: state.settings.theme,
};

View File

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

View File

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

View File

@ -1,28 +1,25 @@
const React = require("react");
const { connect } = require("react-redux");
const shared = require("lib/components/shared/side-menu-shared.js");
const { Synchronizer } = require("lib/synchronizer.js");
const BaseModel = require("lib/BaseModel.js");
const React = require('react');
const { connect } = require('react-redux');
const shared = require('lib/components/shared/side-menu-shared.js');
const { Synchronizer } = require('lib/synchronizer.js');
const BaseModel = require('lib/BaseModel.js');
const Setting = require('lib/models/Setting.js');
const Folder = require("lib/models/Folder.js");
const Note = require("lib/models/Note.js");
const Tag = require("lib/models/Tag.js");
const { _ } = require("lib/locale.js");
const { themeStyle } = require("../theme.js");
const { bridge } = require("electron").remote.require("./bridge");
const Folder = require('lib/models/Folder.js');
const Note = require('lib/models/Note.js');
const Tag = require('lib/models/Tag.js');
const { _ } = require('lib/locale.js');
const { themeStyle } = require('../theme.js');
const { bridge } = require('electron').remote.require('./bridge');
const Menu = bridge().Menu;
const MenuItem = bridge().MenuItem;
const InteropServiceHelper = require("../InteropServiceHelper.js");
const InteropServiceHelper = require('../InteropServiceHelper.js');
const { substrWithEllipsis } = require('lib/string-utils');
const { shim } = require('lib/shim');
class SideBarComponent extends React.Component {
constructor() {
super();
this.onFolderDragStart_ = (event) => {
this.onFolderDragStart_ = event => {
const folderId = event.currentTarget.getAttribute('folderid');
if (!folderId) return;
@ -31,12 +28,12 @@ class SideBarComponent extends React.Component {
event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId]));
};
this.onFolderDragOver_ = (event) => {
if (event.dataTransfer.types.indexOf("text/x-jop-note-ids") >= 0) event.preventDefault();
if (event.dataTransfer.types.indexOf("text/x-jop-folder-ids") >= 0) event.preventDefault();
this.onFolderDragOver_ = event => {
if (event.dataTransfer.types.indexOf('text/x-jop-note-ids') >= 0) event.preventDefault();
if (event.dataTransfer.types.indexOf('text/x-jop-folder-ids') >= 0) event.preventDefault();
};
this.onFolderDrop_ = async (event) => {
this.onFolderDrop_ = async event => {
const folderId = event.currentTarget.getAttribute('folderid');
const dt = event.dataTransfer;
if (!dt) return;
@ -45,41 +42,41 @@ class SideBarComponent extends React.Component {
// to put the dropped folder at the root. But for notes, folderId needs to always be defined
// since there's no such thing as a root note.
if (dt.types.indexOf("text/x-jop-note-ids") >= 0) {
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
if (!folderId) return;
const noteIds = JSON.parse(dt.getData("text/x-jop-note-ids"));
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
for (let i = 0; i < noteIds.length; i++) {
await Note.moveToFolder(noteIds[i], folderId);
}
} else if (dt.types.indexOf("text/x-jop-folder-ids") >= 0) {
} else if (dt.types.indexOf('text/x-jop-folder-ids') >= 0) {
event.preventDefault();
const folderIds = JSON.parse(dt.getData("text/x-jop-folder-ids"));
const folderIds = JSON.parse(dt.getData('text/x-jop-folder-ids'));
for (let i = 0; i < folderIds.length; i++) {
await Folder.moveToFolder(folderIds[i], folderId);
}
}
};
this.onTagDrop_ = async (event) => {
this.onTagDrop_ = async event => {
const tagId = event.currentTarget.getAttribute('tagid');
const dt = event.dataTransfer;
if (!dt) return;
if (dt.types.indexOf("text/x-jop-note-ids") >= 0) {
if (dt.types.indexOf('text/x-jop-note-ids') >= 0) {
event.preventDefault();
const noteIds = JSON.parse(dt.getData("text/x-jop-note-ids"));
const noteIds = JSON.parse(dt.getData('text/x-jop-note-ids'));
for (let i = 0; i < noteIds.length; i++) {
await Tag.addNote(tagId, noteIds[i]);
}
}
}
};
this.onFolderToggleClick_ = async (event) => {
this.onFolderToggleClick_ = async event => {
const folderId = event.currentTarget.getAttribute('folderid');
this.props.dispatch({
@ -99,7 +96,7 @@ class SideBarComponent extends React.Component {
this.state = {
tagHeaderIsExpanded: Setting.value('tagHeaderIsExpanded'),
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded')
folderHeaderIsExpanded: Setting.value('folderHeaderIsExpanded'),
};
}
@ -113,23 +110,23 @@ class SideBarComponent extends React.Component {
backgroundColor: theme.backgroundColor2,
},
listItemContainer: {
boxSizing: "border-box",
boxSizing: 'border-box',
height: itemHeight,
// paddingLeft: 14,
display: "flex",
alignItems: "stretch",
display: 'flex',
alignItems: 'stretch',
// Allow 3 levels of color depth
backgroundColor: theme.depthColor.replace('OPACITY', Math.min(depth * 0.1, 0.3)),
},
listItem: {
fontFamily: theme.fontFamily,
fontSize: theme.fontSize,
textDecoration: "none",
textDecoration: 'none',
color: theme.color2,
cursor: "default",
cursor: 'default',
opacity: 0.8,
whiteSpace: "nowrap",
display: "flex",
whiteSpace: 'nowrap',
display: 'flex',
flex: 1,
alignItems: 'center',
},
@ -138,60 +135,60 @@ class SideBarComponent extends React.Component {
},
listItemExpandIcon: {
color: theme.color2,
cursor: "default",
cursor: 'default',
opacity: 0.8,
// fontFamily: theme.fontFamily,
fontSize: theme.fontSize,
textDecoration: "none",
textDecoration: 'none',
paddingRight: 5,
display: "flex",
display: 'flex',
alignItems: 'center',
},
conflictFolder: {
color: theme.colorError2,
fontWeight: "bold",
fontWeight: 'bold',
},
header: {
height: itemHeight * 1.8,
fontFamily: theme.fontFamily,
fontSize: theme.fontSize * 1.16,
textDecoration: "none",
boxSizing: "border-box",
textDecoration: 'none',
boxSizing: 'border-box',
color: theme.color2,
paddingLeft: 8,
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
},
button: {
padding: 6,
fontFamily: theme.fontFamily,
fontSize: theme.fontSize,
textDecoration: "none",
boxSizing: "border-box",
textDecoration: 'none',
boxSizing: 'border-box',
color: theme.color2,
display: "flex",
alignItems: "center",
justifyContent: "center",
border: "1px solid rgba(255,255,255,0.2)",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid rgba(255,255,255,0.2)',
marginTop: 10,
marginLeft: 5,
marginRight: 5,
cursor: "default",
cursor: 'default',
},
syncReport: {
fontFamily: theme.fontFamily,
fontSize: Math.round(theme.fontSize * 0.9),
color: theme.color2,
opacity: 0.5,
display: "flex",
alignItems: "left",
justifyContent: "top",
flexDirection: "column",
display: 'flex',
alignItems: 'left',
justifyContent: 'top',
flexDirection: 'column',
marginTop: 10,
marginLeft: 5,
marginRight: 5,
marginBottom: 10,
wordWrap: "break-word",
wordWrap: 'break-word',
},
};
@ -236,42 +233,40 @@ class SideBarComponent extends React.Component {
}
}
componentDidUpdate(prevProps) {
if (shim.isLinux()) {
// For some reason, the UI seems to sleep in some Linux distro during
// sync. Cannot find the reason for it and cannot replicate, so here
// as a test force the update at regular intervals.
// https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
if (!prevProps.syncStarted && this.props.syncStarted) {
this.clearForceUpdateDuringSync();
this.forceUpdateDuringSyncIID_ = setInterval(() => {
this.forceUpdate();
}, 2000);
}
if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync();
}
}
componentWillUnmount() {
this.clearForceUpdateDuringSync();
}
componentDidUpdate(prevProps, prevState, snapshot) {
componentDidUpdate(prevProps) {
if (prevProps.windowCommand !== this.props.windowCommand) {
this.doCommand(this.props.windowCommand);
}
// if (shim.isLinux()) {
// // For some reason, the UI seems to sleep in some Linux distro during
// // sync. Cannot find the reason for it and cannot replicate, so here
// // as a test force the update at regular intervals.
// // https://github.com/laurent22/joplin/issues/312#issuecomment-429472193
// if (!prevProps.syncStarted && this.props.syncStarted) {
// this.clearForceUpdateDuringSync();
// this.forceUpdateDuringSyncIID_ = setInterval(() => {
// this.forceUpdate();
// }, 2000);
// }
// if (prevProps.syncStarted && !this.props.syncStarted) this.clearForceUpdateDuringSync();
// }
}
async itemContextMenu(event) {
const itemId = event.target.getAttribute("data-id");
const itemId = event.target.getAttribute('data-id');
if (itemId === Folder.conflictFolderId()) return;
const itemType = Number(event.target.getAttribute("data-type"));
if (!itemId || !itemType) throw new Error("No data on element");
const itemType = Number(event.target.getAttribute('data-type'));
if (!itemId || !itemType) throw new Error('No data on element');
let deleteMessage = "";
let deleteMessage = '';
if (itemType === BaseModel.TYPE_FOLDER) {
const folder = await Folder.load(itemId);
deleteMessage = _('Delete notebook "%s"?\n\nAll notes and sub-notebooks within this notebook will also be deleted.', substrWithEllipsis(folder.title, 0, 32));
@ -279,7 +274,7 @@ class SideBarComponent extends React.Component {
const tag = await Tag.load(itemId);
deleteMessage = _('Remove tag "%s" from all notes?', substrWithEllipsis(tag.title, 0, 32));
} else if (itemType === BaseModel.TYPE_SEARCH) {
deleteMessage = _("Remove this search from the sidebar?");
deleteMessage = _('Remove this search from the sidebar?');
}
const menu = new Menu();
@ -291,7 +286,7 @@ class SideBarComponent extends React.Component {
menu.append(
new MenuItem({
label: _("Delete"),
label: _('Delete'),
click: async () => {
const ok = bridge().showConfirmMessageBox(deleteMessage);
if (!ok) return;
@ -302,7 +297,7 @@ class SideBarComponent extends React.Component {
await Tag.untagAll(itemId);
} else if (itemType === BaseModel.TYPE_SEARCH) {
this.props.dispatch({
type: "SEARCH_DELETE",
type: 'SEARCH_DELETE',
id: itemId,
});
}
@ -313,11 +308,11 @@ class SideBarComponent extends React.Component {
if (itemType === BaseModel.TYPE_FOLDER && !item.encryption_applied) {
menu.append(
new MenuItem({
label: _("Rename"),
label: _('Rename'),
click: async () => {
this.props.dispatch({
type: "WINDOW_COMMAND",
name: "renameFolder",
type: 'WINDOW_COMMAND',
name: 'renameFolder',
id: itemId,
});
},
@ -337,9 +332,9 @@ class SideBarComponent extends React.Component {
// })
// );
menu.append(new MenuItem({ type: "separator" }));
menu.append(new MenuItem({ type: 'separator' }));
const InteropService = require("lib/services/InteropService.js");
const InteropService = require('lib/services/InteropService.js');
const exportMenu = new Menu();
const ioService = new InteropService();
@ -348,14 +343,19 @@ class SideBarComponent extends React.Component {
const module = ioModules[i];
if (module.type !== 'exporter') continue;
exportMenu.append(new MenuItem({ label: module.fullLabel() , click: async () => {
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
}}));
exportMenu.append(
new MenuItem({
label: module.fullLabel(),
click: async () => {
await InteropServiceHelper.export(this.props.dispatch.bind(this), module, { sourceFolderIds: [itemId] });
},
})
);
}
menu.append(
new MenuItem({
label: _("Export"),
label: _('Export'),
submenu: exportMenu,
})
);
@ -367,9 +367,9 @@ class SideBarComponent extends React.Component {
label: _('Rename'),
click: async () => {
this.props.dispatch({
type: "WINDOW_COMMAND",
name: "renameTag",
id: itemId
type: 'WINDOW_COMMAND',
name: 'renameTag',
id: itemId,
});
},
})
@ -381,14 +381,14 @@ class SideBarComponent extends React.Component {
folderItem_click(folder) {
this.props.dispatch({
type: "FOLDER_SELECT",
type: 'FOLDER_SELECT',
id: folder ? folder.id : null,
});
}
tagItem_click(tag) {
this.props.dispatch({
type: "TAG_SELECT",
type: 'TAG_SELECT',
id: tag ? tag.id : null,
});
}
@ -405,8 +405,6 @@ class SideBarComponent extends React.Component {
}
anchorItemRef(type, id) {
let refs = null;
if (!this.anchorItemRefs[type]) this.anchorItemRefs[type] = {};
if (this.anchorItemRefs[type][id]) return this.anchorItemRefs[type][id];
this.anchorItemRefs[type][id] = React.createRef();
@ -426,17 +424,23 @@ class SideBarComponent extends React.Component {
let expandIconStyle = {
visibility: hasChildren ? 'visible' : 'hidden',
paddingLeft: 8 + depth * 10,
}
};
const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'fa-plus-square' : 'fa-minus-square';
const expandIcon = <i style={expandIconStyle} className={"fa " + iconName}></i>
const expandLink = hasChildren ? <a style={expandLinkStyle} href="#" folderid={folder.id} onClick={this.onFolderToggleClick_}>{expandIcon}</a> : <span style={expandLinkStyle}>{expandIcon}</span>
const expandIcon = <i style={expandIconStyle} className={'fa ' + iconName}></i>;
const expandLink = hasChildren ? (
<a style={expandLinkStyle} href="#" folderid={folder.id} onClick={this.onFolderToggleClick_}>
{expandIcon}
</a>
) : (
<span style={expandLinkStyle}>{expandIcon}</span>
);
const anchorRef = this.anchorItemRef('folder', folder.id);
return (
<div className="list-item-container" style={containerStyle} key={folder.id} onDragStart={this.onFolderDragStart_} onDragOver={this.onFolderDragOver_} onDrop={this.onFolderDrop_} draggable={true} folderid={folder.id}>
{ expandLink }
{expandLink}
<a
ref={anchorRef}
className="list-item"
@ -506,15 +510,15 @@ class SideBarComponent extends React.Component {
// }
makeDivider(key) {
return <div style={{ height: 2, backgroundColor: "blue" }} key={key} />;
return <div style={{ height: 2, backgroundColor: 'blue' }} key={key} />;
}
makeHeader(key, label, iconName, extraProps = {}) {
const style = this.style().header;
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={"fa " + iconName} />;
const icon = <i style={{ fontSize: style.fontSize, marginRight: 5 }} className={'fa ' + iconName} />;
if (extraProps.toggleblock || extraProps.onClick) {
style.cursor = "pointer";
style.cursor = 'pointer';
}
let headerClick = extraProps.onClick || null;
@ -525,22 +529,27 @@ class SideBarComponent extends React.Component {
const toggleKey = `${key}IsExpanded`;
if (extraProps.toggleblock) {
let isExpanded = this.state[toggleKey];
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75,
marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125}}></i>;
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75, marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125 }}></i>;
}
const ref = this.anchorItemRef('headers', key);
return (
<div ref={ref} style={style} key={key} {...extraProps} onClick={(event) => {
// if a custom click event is attached, trigger that.
<div
ref={ref}
style={style}
key={key}
{...extraProps}
onClick={event => {
// if a custom click event is attached, trigger that.
if (headerClick) {
headerClick(key, event);
headerClick(key, event);
}
this.onHeaderClick_(key, event);
}}>
this.onHeaderClick_(key, event);
}}
>
{icon}
<span style={{flex: 1 }}>{label}</span>
<span style={{ flex: 1 }}>{label}</span>
{toggleIcon}
</div>
);
@ -562,7 +571,8 @@ class SideBarComponent extends React.Component {
const keyCode = event.keyCode;
const selectedItem = this.selectedItem();
if (keyCode === 40 || keyCode === 38) { // DOWN / UP
if (keyCode === 40 || keyCode === 38) {
// DOWN / UP
event.preventDefault();
const focusItems = [];
@ -603,7 +613,8 @@ class SideBarComponent extends React.Component {
focusItem.ref.current.focus();
}
if (keyCode === 9) { // TAB
if (keyCode === 9) {
// TAB
event.preventDefault();
if (event.shiftKey) {
@ -621,7 +632,8 @@ class SideBarComponent extends React.Component {
}
}
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) { // SPACE
if (selectedItem && selectedItem.type === 'folder' && keyCode === 32) {
// SPACE
event.preventDefault();
this.props.dispatch({
@ -631,28 +643,28 @@ class SideBarComponent extends React.Component {
}
}
onHeaderClick_(key, event) {
onHeaderClick_(key, event) {
const currentHeader = event.currentTarget;
const toggleBlock = +currentHeader.getAttribute('toggleblock');
if (toggleBlock) {
const toggleKey = `${key}IsExpanded`;
const isExpanded = this.state[toggleKey];
this.setState({ [toggleKey]: !isExpanded });
Setting.setValue(toggleKey, !isExpanded);
const toggleKey = `${key}IsExpanded`;
const isExpanded = this.state[toggleKey];
this.setState({ [toggleKey]: !isExpanded });
Setting.setValue(toggleKey, !isExpanded);
}
}
synchronizeButton(type) {
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
const iconName = "fa-refresh";
const label = type === "sync" ? _("Synchronise") : _("Cancel");
const iconName = 'fa-refresh';
const label = type === 'sync' ? _('Synchronise') : _('Cancel');
let iconStyle = { fontSize: style.fontSize, marginRight: 5 };
if(type !== 'sync'){
if (type !== 'sync') {
iconStyle.animation = 'icon-infinite-rotation 1s linear infinite';
}
const icon = <i style={iconStyle} className={"fa " + iconName} />;
const icon = <i style={iconStyle} className={'fa ' + iconName} />;
return (
<a
className="synchronize-button"
@ -670,32 +682,38 @@ class SideBarComponent extends React.Component {
}
render() {
const theme = themeStyle(this.props.theme);
const style = Object.assign({}, this.style().root, this.props.style, {
overflowX: "hidden",
overflowY: "hidden",
overflowX: 'hidden',
overflowY: 'hidden',
display: 'inline-flex',
flexDirection: 'column',
});
let items = [];
items.push(this.makeHeader("folderHeader", _("Notebooks"), "fa-book", {
onDrop: this.onFolderDrop_,
folderid: '',
toggleblock: 1
}));
items.push(
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
onDrop: this.onFolderDrop_,
folderid: '',
toggleblock: 1,
})
);
if (this.props.folders.length) {
const result = shared.renderFolders(this.props, this.folderItem.bind(this));
const folderItems = result.items;
this.folderItemsOrder_ = result.order;
items.push(<div className="folders" key="folder_items" style={{display: this.state.folderHeaderIsExpanded ? 'block': 'none'}}>
{folderItems}</div>);
items.push(
<div className="folders" key="folder_items" style={{ display: this.state.folderHeaderIsExpanded ? 'block' : 'none' }}>
{folderItems}
</div>
);
}
items.push(this.makeHeader("tagHeader", _("Tags"), "fa-tags", {
toggleblock: 1
}));
items.push(
this.makeHeader('tagHeader', _('Tags'), 'fa-tags', {
toggleblock: 1,
})
);
if (this.props.tags.length) {
const result = shared.renderTags(this.props, this.tagItem.bind(this));
@ -703,7 +721,7 @@ class SideBarComponent extends React.Component {
this.tagItemsOrder_ = result.order;
items.push(
<div className="tags" key="tag_items" style={{display: this.state.tagHeaderIsExpanded ? 'block': 'none'}}>
<div className="tags" key="tag_items" style={{ display: this.state.tagHeaderIsExpanded ? 'block' : 'none' }}>
{tagItems}
</div>
);
@ -725,27 +743,24 @@ class SideBarComponent extends React.Component {
const syncReportText = [];
for (let i = 0; i < lines.length; i++) {
syncReportText.push(
<div key={i} style={{ wordWrap: "break-word", width: "100%" }}>
<div key={i} style={{ wordWrap: 'break-word', width: '100%' }}>
{lines[i]}
</div>
);
}
const syncButton = this.synchronizeButton(this.props.syncStarted ? "cancel" : "sync");
const syncButton = this.synchronizeButton(this.props.syncStarted ? 'cancel' : 'sync');
const syncReportComp = !syncReportText.length ? null : (
<div style={this.style().syncReport} key="sync_report">
{syncReportText}
</div>
);
{syncReportText}
</div>
);
return (
<div ref={this.rootRef} onKeyDown={this.onKeyDown} className="side-bar" style={style}>
<div style={{flex:1, overflowX: 'hidden', overflowY: 'auto'}}>
{items}
</div>
<div style={{flex:0}}>
<div style={{ flex: 1, overflowX: 'hidden', overflowY: 'auto' }}>{items}</div>
<div style={{ flex: 0 }}>
{syncReportComp}
{syncButton}
</div>

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class TagListComponent extends React.Component {
const tags = this.props.items;
style.display = 'flex';
style.flexDirection = 'row';
style.flexDirection = 'row';
style.borderBottom = '1px solid ' + theme.dividerColor;
style.boxSizing = 'border-box';
style.fontSize = theme.fontSize;
@ -18,12 +18,14 @@ class TagListComponent extends React.Component {
const tagItems = [];
if (tags || tags.length > 0) {
// Sort by id for now, but probably needs to be changed in the future.
tags.sort((a, b) => { return a.title < b.title ? -1 : +1; });
tags.sort((a, b) => {
return a.title < b.title ? -1 : +1;
});
for (let i = 0; i < tags.length; i++) {
const props = {
title: tags[i].title,
key: tags[i].id
key: tags[i].id,
};
tagItems.push(<TagItem {...props} />);
}
@ -35,13 +37,13 @@ class TagListComponent extends React.Component {
return (
<div className="tag-list" style={style}>
{ tagItems }
{tagItems}
</div>
)
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return { theme: state.settings.theme };
};

View File

@ -1,19 +1,16 @@
const React = require('react');
const { connect } = require('react-redux');
const { reg } = require('lib/registry.js');
const { themeStyle } = require('../theme.js');
const { _ } = require('lib/locale.js');
const ToolbarButton = require('./ToolbarButton.min.js');
const ToolbarSpace = require('./ToolbarSpace.min.js');
class ToolbarComponent extends React.Component {
render() {
const style = this.props.style;
const theme = themeStyle(this.props.theme);
style.height = theme.toolbarHeight;
style.display = 'flex';
style.flexDirection = 'row';
style.flexDirection = 'row';
style.borderBottom = '1px solid ' + theme.dividerColor;
style.boxSizing = 'border-box';
@ -28,10 +25,13 @@ class ToolbarComponent extends React.Component {
if (!key) key = o.type + '_' + i;
const props = Object.assign({
key: key,
theme: this.props.theme,
}, o);
const props = Object.assign(
{
key: key,
theme: this.props.theme,
},
o
);
if (itemType === 'button') {
itemComps.push(<ToolbarButton {...props} />);
@ -43,17 +43,16 @@ class ToolbarComponent extends React.Component {
return (
<div className="editor-toolbar" style={style}>
{ itemComps }
{itemComps}
</div>
);
}
}
const mapStateToProps = (state) => {
const mapStateToProps = state => {
return { theme: state.settings.theme };
};
const Toolbar = connect(mapStateToProps)(ToolbarComponent);
module.exports = Toolbar;
module.exports = Toolbar;

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ ipcRenderer.on('setMarkers', (event, keywords, options) => {
window.postMessage({ target: 'webview', name: 'setMarkers', data: { keywords: keywords, options: options } }, '*');
});
window.addEventListener('message', (event) => {
window.addEventListener('message', event => {
// Here we only deal with messages that are sent from the webview to the main Electron process
if (!event.data || event.data.target !== 'main') return;

View File

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