mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-30 10:36:35 +02:00
08af9de190
* Started revisions support * More rev changes * More rev changes * More revs changes * Fixed deletion algorithm * More tests and moved updated time to separate field * Display info when restoring note * Better handling of existing notes * wip * Further improvements and fixed tests * Better handling of changes created via sync * Enable chokidar again * Testing special case * Further improved logic to handle notes that existed before the revision service * Added tests * Better handling of encrypted revisions * Improved handling of deleted note revisions by moving logic to collectRevision * Improved handling of old notes by moving logic to collectRevision() * Handle case when deleting revisions while one is still encrypted * UI tweaks * Added revision service to mobile app * Fixed config screens on mobile and desktop * Enabled revisions on CLI app
383 lines
9.5 KiB
JavaScript
383 lines
9.5 KiB
JavaScript
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');
|
|
const Note = require('lib/models/Note');
|
|
const formatcoords = require('formatcoords');
|
|
const { bridge } = require('electron').remote.require('./bridge');
|
|
|
|
class NotePropertiesDialog extends React.Component {
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.okButton_click = this.okButton_click.bind(this);
|
|
this.cancelButton_click = this.cancelButton_click.bind(this);
|
|
this.onKeyDown = this.onKeyDown.bind(this);
|
|
this.revisionsLink_click = this.revisionsLink_click.bind(this);
|
|
this.okButton = React.createRef();
|
|
|
|
this.state = {
|
|
formNote: null,
|
|
editedKey: null,
|
|
editedValue: null,
|
|
};
|
|
|
|
this.keyToLabel_ = {
|
|
id: _('ID'),
|
|
user_created_time: _('Created'),
|
|
user_updated_time: _('Updated'),
|
|
location: _('Location'),
|
|
source_url: _('URL'),
|
|
revisionsLink: _('Note History'),
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.loadNote(this.props.noteId);
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
if (this.state.editedKey == null) {
|
|
this.okButton.current.focus();
|
|
}
|
|
}
|
|
|
|
async loadNote(noteId) {
|
|
if (!noteId) {
|
|
this.setState({ formNote: null });
|
|
} else {
|
|
const note = await Note.load(noteId);
|
|
const formNote = this.noteToFormNote(note);
|
|
this.setState({ formNote: formNote });
|
|
}
|
|
}
|
|
|
|
latLongFromLocation(location) {
|
|
const o = {};
|
|
const l = location.split(',');
|
|
if (l.length == 2) {
|
|
o.latitude = l[0].trim();
|
|
o.longitude = l[1].trim();
|
|
} else {
|
|
o.latitude = '';
|
|
o.longitude = '';
|
|
}
|
|
return o;
|
|
}
|
|
|
|
noteToFormNote(note) {
|
|
const formNote = {};
|
|
|
|
formNote.user_updated_time = time.formatMsToLocal(note.user_updated_time);
|
|
formNote.user_created_time = time.formatMsToLocal(note.user_created_time);
|
|
formNote.source_url = note.source_url;
|
|
|
|
formNote.location = '';
|
|
if (Number(note.latitude) || Number(note.longitude)) {
|
|
formNote.location = note.latitude + ', ' + note.longitude;
|
|
}
|
|
|
|
formNote.revisionsLink = note.id;
|
|
formNote.id = note.id;
|
|
|
|
return formNote;
|
|
}
|
|
|
|
formNoteToNote(formNote) {
|
|
const note = Object.assign({ id: formNote.id }, this.latLongFromLocation(formNote.location));
|
|
note.user_created_time = time.formatLocalToMs(formNote.user_created_time);
|
|
note.user_updated_time = time.formatLocalToMs(formNote.user_updated_time);
|
|
note.source_url = formNote.source_url;
|
|
|
|
return note;
|
|
}
|
|
|
|
styles(themeId) {
|
|
const styleKey = themeId;
|
|
if (styleKey === this.styleKey_) return this.styles_;
|
|
|
|
const theme = themeStyle(themeId);
|
|
|
|
this.styles_ = {};
|
|
this.styleKey_ = styleKey;
|
|
|
|
this.styles_.controlBox = {
|
|
marginBottom: '1em',
|
|
color: 'black', //This will apply for the calendar
|
|
};
|
|
|
|
this.styles_.button = {
|
|
minWidth: theme.buttonMinWidth,
|
|
minHeight: theme.buttonMinHeight,
|
|
marginLeft: 5,
|
|
color: theme.color,
|
|
backgroundColor: theme.backgroundColor,
|
|
border: '1px solid',
|
|
borderColor: theme.dividerColor,
|
|
};
|
|
|
|
this.styles_.editPropertyButton = {
|
|
color: theme.color,
|
|
textDecoration: 'none',
|
|
backgroundColor: theme.backgroundColor,
|
|
border: '1px solid',
|
|
borderColor: theme.dividerColor,
|
|
};
|
|
|
|
this.styles_.input = {
|
|
display:'inline-block',
|
|
color: theme.color,
|
|
backgroundColor: theme.backgroundColor,
|
|
border: '1px solid',
|
|
borderColor: theme.dividerColor,
|
|
};
|
|
|
|
return this.styles_;
|
|
}
|
|
|
|
async closeDialog(applyChanges) {
|
|
if (applyChanges) {
|
|
await this.saveProperty();
|
|
const note = this.formNoteToNote(this.state.formNote);
|
|
note.updated_time = Date.now();
|
|
await Note.save(note, { autoTimestamp: false });
|
|
} else {
|
|
await this.cancelProperty();
|
|
}
|
|
|
|
if (this.props.onClose) {
|
|
this.props.onClose();
|
|
}
|
|
}
|
|
|
|
okButton_click() {
|
|
this.closeDialog(true);
|
|
}
|
|
|
|
cancelButton_click() {
|
|
this.closeDialog(false);
|
|
}
|
|
|
|
revisionsLink_click() {
|
|
this.closeDialog(false);
|
|
if (this.props.onRevisionLinkClick) this.props.onRevisionLinkClick();
|
|
}
|
|
|
|
onKeyDown(event) {
|
|
if (event.keyCode === 13) {
|
|
this.closeDialog(true);
|
|
} else if (event.keyCode === 27) {
|
|
this.closeDialog(false);
|
|
}
|
|
}
|
|
|
|
editPropertyButtonClick(key, initialValue) {
|
|
this.setState({
|
|
editedKey: key,
|
|
editedValue: initialValue,
|
|
});
|
|
|
|
setTimeout(() => {
|
|
if (this.refs.editField.openCalendar) {
|
|
this.refs.editField.openCalendar();
|
|
} else {
|
|
this.refs.editField.focus();
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
async saveProperty() {
|
|
if (!this.state.editedKey) return;
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const newFormNote = Object.assign({}, this.state.formNote);
|
|
|
|
if (this.state.editedKey.indexOf('_time') >= 0) {
|
|
const dt = time.anythingToDateTime(this.state.editedValue, new Date());
|
|
newFormNote[this.state.editedKey] = time.formatMsToLocal(dt.getTime());
|
|
} else {
|
|
newFormNote[this.state.editedKey] = this.state.editedValue;
|
|
}
|
|
|
|
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() });
|
|
});
|
|
}
|
|
|
|
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>;
|
|
let controlComp = null;
|
|
let editComp = null;
|
|
let editCompHandler = null;
|
|
let editCompIcon = null;
|
|
|
|
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 })}}
|
|
/>
|
|
|
|
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}
|
|
/>
|
|
}
|
|
} else {
|
|
let displayedValue = value;
|
|
|
|
if (key === 'location') {
|
|
try {
|
|
const dms = formatcoords(value);
|
|
displayedValue = dms.format('DDMMss', { decimalPlaces: 0 });
|
|
} catch (error) {
|
|
displayedValue = '';
|
|
}
|
|
}
|
|
|
|
if (['source_url', 'location'].indexOf(key) >= 0) {
|
|
let url = '';
|
|
if (key === 'source_url') url = value;
|
|
if (key === 'location') {
|
|
const ll = this.latLongFromLocation(value);
|
|
url = Note.geoLocationUrlFromLatLong(ll.latitude, ll.longitude);
|
|
}
|
|
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>
|
|
} else {
|
|
controlComp = <div style={Object.assign({}, theme.textStyle, {display: 'inline-block'})}>{displayedValue}</div>
|
|
}
|
|
|
|
if (key !== 'id' && key !== 'revisionsLink') {
|
|
editCompHandler = () => {this.editPropertyButtonClick(key, value)};
|
|
editCompIcon = 'fa-edit';
|
|
}
|
|
}
|
|
|
|
if (editCompHandler) {
|
|
editComp = (
|
|
<a href="#" onClick={editCompHandler} style={styles.editPropertyButton}>
|
|
<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 }
|
|
</div>
|
|
);
|
|
}
|
|
|
|
formatLabel(key) {
|
|
if (this.keyToLabel_[key]) return this.keyToLabel_[key];
|
|
return key;
|
|
}
|
|
|
|
formatValue(key, note) {
|
|
if (key === 'location') {
|
|
if (!Number(note.latitude) && !Number(note.longitude)) return null;
|
|
const dms = formatcoords(Number(note.latitude), Number(note.longitude))
|
|
return dms.format('DDMMss', { decimalPlaces: 0 });
|
|
}
|
|
|
|
if (['user_updated_time', 'user_created_time'].indexOf(key) >= 0) {
|
|
return time.formatMsToLocal(note[key]);
|
|
}
|
|
|
|
return note[key];
|
|
}
|
|
|
|
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}
|
|
>
|
|
{_('Apply')}
|
|
</button>
|
|
);
|
|
buttonComps.push(<button key="cancel" style={styles.button} onClick={this.cancelButton_click}>{_('Cancel')}</button>);
|
|
|
|
const noteComps = [];
|
|
|
|
if (formNote) {
|
|
for (let key in formNote) {
|
|
if (!formNote.hasOwnProperty(key)) continue;
|
|
const comp = this.createNoteField(key, formNote[key]);
|
|
noteComps.push(comp);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div style={theme.dialogModalLayer}>
|
|
<div style={theme.dialogBox}>
|
|
<div style={theme.dialogTitle}>{_('Note properties')}</div>
|
|
<div>{noteComps}</div>
|
|
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
|
{buttonComps}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = NotePropertiesDialog;
|