mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Electron: Resolves #755: Added note properties dialog box to view and edit created time, updated time, source URL and geolocation
This commit is contained in:
parent
1b784fe3b0
commit
4e8372174b
@ -5,6 +5,7 @@ const { SideBar } = require('./SideBar.min.js');
|
||||
const { NoteList } = require('./NoteList.min.js');
|
||||
const { NoteText } = require('./NoteText.min.js');
|
||||
const { PromptDialog } = require('./PromptDialog.min.js');
|
||||
const NotePropertiesDialog = require('./NotePropertiesDialog.min.js');
|
||||
const Setting = require('lib/models/Setting.js');
|
||||
const BaseModel = require('lib/BaseModel.js');
|
||||
const Tag = require('lib/models/Tag.js');
|
||||
@ -19,13 +20,24 @@ const eventManager = require('../eventManager');
|
||||
|
||||
class MainScreenComponent extends React.Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.notePropertiesDialog_close = this.notePropertiesDialog_close.bind(this);
|
||||
}
|
||||
|
||||
notePropertiesDialog_close() {
|
||||
this.setState({ notePropertiesDialogOptions: {} });
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.setState({
|
||||
promptOptions: null,
|
||||
modalLayer: {
|
||||
visible: false,
|
||||
message: '',
|
||||
}
|
||||
},
|
||||
notePropertiesDialogOptions: {},
|
||||
});
|
||||
}
|
||||
|
||||
@ -189,6 +201,13 @@ class MainScreenComponent extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
} else if (command.name === 'commandNoteProperties') {
|
||||
this.setState({
|
||||
notePropertiesDialogOptions: {
|
||||
noteId: command.noteId,
|
||||
visible: true,
|
||||
},
|
||||
});
|
||||
} else if (command.name === 'toggleVisiblePanes') {
|
||||
this.toggleVisiblePanes();
|
||||
} else if (command.name === 'toggleSidebar') {
|
||||
@ -412,10 +431,19 @@ class MainScreenComponent extends React.Component {
|
||||
|
||||
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
|
||||
|
||||
const notePropertiesDialogOptions = this.state.notePropertiesDialogOptions;
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
|
||||
|
||||
<NotePropertiesDialog
|
||||
theme={this.props.theme}
|
||||
noteId={notePropertiesDialogOptions.noteId}
|
||||
visible={!!notePropertiesDialogOptions.visible}
|
||||
onClose={this.notePropertiesDialog_close}
|
||||
/>
|
||||
|
||||
<PromptDialog
|
||||
autocomplete={promptOptions && ('autocomplete' in promptOptions) ? promptOptions.autocomplete : null}
|
||||
defaultValue={promptOptions && promptOptions.value ? promptOptions.value : ''}
|
||||
@ -427,6 +455,7 @@ class MainScreenComponent extends React.Component {
|
||||
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} />
|
||||
|
364
ElectronClient/app/gui/NotePropertiesDialog.jsx
Normal file
364
ElectronClient/app/gui/NotePropertiesDialog.jsx
Normal file
@ -0,0 +1,364 @@
|
||||
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.state = {
|
||||
formNote: null,
|
||||
editedKey: null,
|
||||
editedValue: null,
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.keyToLabel_ = {
|
||||
id: _('ID'),
|
||||
user_created_time: _('Created'),
|
||||
user_updated_time: _('Updated'),
|
||||
location: _('Location'),
|
||||
source_url: _('URL'),
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if ('visible' in newProps && newProps.visible !== this.state.visible) {
|
||||
this.setState({ visible: newProps.visible });
|
||||
}
|
||||
|
||||
if ('noteId' in newProps) {
|
||||
this.loadNote(newProps.noteId);
|
||||
}
|
||||
}
|
||||
|
||||
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.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_.modalLayer = {
|
||||
zIndex: 9999,
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(0,0,0,0.6)',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
this.styles_.dialogBox = {
|
||||
backgroundColor: 'white',
|
||||
padding: 16,
|
||||
boxShadow: '6px 6px 20px rgba(0,0,0,0.5)',
|
||||
marginTop: 20,
|
||||
}
|
||||
|
||||
this.styles_.controlBox = {
|
||||
marginBottom: '1em',
|
||||
};
|
||||
|
||||
this.styles_.button = {
|
||||
minWidth: theme.buttonMinWidth,
|
||||
minHeight: theme.buttonMinHeight,
|
||||
marginLeft: 5,
|
||||
};
|
||||
|
||||
this.styles_.editPropertyButton = {
|
||||
color: theme.color,
|
||||
textDecoration: 'none',
|
||||
};
|
||||
|
||||
this.styles_.dialogTitle = Object.assign({}, theme.h1Style, { marginBottom: '1.2em' });
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
visible: false,
|
||||
});
|
||||
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
okButton_click() {
|
||||
this.closeDialog(true);
|
||||
}
|
||||
|
||||
cancelButton_click() {
|
||||
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.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)
|
||||
}}
|
||||
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={{display:'inline-block'}}
|
||||
/>
|
||||
}
|
||||
} 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 {
|
||||
controlComp = <div style={Object.assign({}, theme.textStyle, {display: 'inline-block'})}>{displayedValue}</div>
|
||||
}
|
||||
|
||||
if (key !== 'id') {
|
||||
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}>{_('Apply')}</button>);
|
||||
buttonComps.push(<button key="cancel" style={styles.button} onClick={this.cancelButton_click}>{_('Cancel')}</button>);
|
||||
|
||||
const noteComps = [];
|
||||
|
||||
const modalLayerStyle = Object.assign({}, styles.modalLayer);
|
||||
if (!this.state.visible) modalLayerStyle.display = 'none';
|
||||
|
||||
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={modalLayerStyle}>
|
||||
<div style={styles.dialogBox}>
|
||||
<div style={styles.dialogTitle}>Note properties</div>
|
||||
<div>{noteComps}</div>
|
||||
<div style={{ textAlign: 'right', marginTop: 10 }}>
|
||||
{buttonComps}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = NotePropertiesDialog;
|
@ -1145,6 +1145,25 @@ class NoteTextComponent extends React.Component {
|
||||
type: 'separator',
|
||||
});
|
||||
|
||||
toolbarItems.push({
|
||||
tooltip: _('Note properties'),
|
||||
iconName: 'fa-info-circle',
|
||||
onClick: () => {
|
||||
const n = this.state.note;
|
||||
if (!n || !n.id) return;
|
||||
|
||||
this.props.dispatch({
|
||||
type: 'WINDOW_COMMAND',
|
||||
name: 'commandNoteProperties',
|
||||
noteId: n.id,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
toolbarItems.push({
|
||||
type: 'separator',
|
||||
});
|
||||
|
||||
toolbarItems.push({
|
||||
tooltip: _('Hyperlink'),
|
||||
iconName: 'fa-link',
|
||||
|
@ -102,7 +102,8 @@ class PromptDialog extends React.Component {
|
||||
if (this.props.onClose) {
|
||||
let outputAnswer = this.state.answer;
|
||||
if (this.props.inputType === 'datetime') {
|
||||
outputAnswer = anythingToDate(outputAnswer);
|
||||
// outputAnswer = anythingToDate(outputAnswer);
|
||||
outputAnswer = time.anythingToDateTime(outputAnswer);
|
||||
}
|
||||
this.props.onClose(accept ? outputAnswer : null, buttonType);
|
||||
}
|
||||
@ -113,14 +114,14 @@ class PromptDialog extends React.Component {
|
||||
this.setState({ answer: event.target.value });
|
||||
}
|
||||
|
||||
const anythingToDate = (o) => {
|
||||
if (o && o.toDate) return o.toDate();
|
||||
if (!o) return null;
|
||||
let m = moment(o, time.dateTimeFormat());
|
||||
if (m.isValid()) return m.toDate();
|
||||
m = moment(o, time.dateFormat());
|
||||
return m.isValid() ? m.toDate() : null;
|
||||
}
|
||||
// const anythingToDate = (o) => {
|
||||
// if (o && o.toDate) return o.toDate();
|
||||
// if (!o) return null;
|
||||
// let m = moment(o, time.dateTimeFormat());
|
||||
// if (m.isValid()) return m.toDate();
|
||||
// m = moment(o, time.dateFormat());
|
||||
// return m.isValid() ? m.toDate() : null;
|
||||
// }
|
||||
|
||||
const onDateTimeChange = (momentObject) => {
|
||||
this.setState({ answer: momentObject });
|
||||
|
5
ElectronClient/app/package-lock.json
generated
5
ElectronClient/app/package-lock.json
generated
@ -2693,6 +2693,11 @@
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"formatcoords": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/formatcoords/-/formatcoords-1.1.3.tgz",
|
||||
"integrity": "sha1-dS8FarL+NMHUrooZBIzgw44W7QM="
|
||||
},
|
||||
"fragment-cache": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
|
||||
|
@ -87,6 +87,7 @@
|
||||
"es6-promise-pool": "^2.5.0",
|
||||
"follow-redirects": "^1.5.0",
|
||||
"form-data": "^2.3.2",
|
||||
"formatcoords": "^1.1.3",
|
||||
"fs-extra": "^5.0.0",
|
||||
"highlight.js": "^9.12.0",
|
||||
"html-entities": "^1.2.1",
|
||||
|
@ -78,3 +78,7 @@ table td, table th {
|
||||
.smalltalk {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.note-property-box .rdt {
|
||||
display: inline-block;
|
||||
}
|
@ -76,11 +76,15 @@ globalStyle.textStyle2 = Object.assign({}, globalStyle.textStyle, {
|
||||
color: globalStyle.color2,
|
||||
});
|
||||
|
||||
globalStyle.urlStyle = Object.assign({}, globalStyle.textStyle, { color: "#155BDA", textDecoration: 'underline' });
|
||||
|
||||
globalStyle.h1Style = Object.assign({}, globalStyle.textStyle);
|
||||
globalStyle.h1Style.fontSize *= 1.5;
|
||||
globalStyle.h1Style.fontWeight = 'bold';
|
||||
|
||||
globalStyle.h2Style = Object.assign({}, globalStyle.textStyle);
|
||||
globalStyle.h2Style.fontSize *= 1.3;
|
||||
globalStyle.h2Style.fontWeight = 'bold';
|
||||
|
||||
globalStyle.toolbarStyle = {
|
||||
height: globalStyle.toolbarHeight,
|
||||
|
@ -287,7 +287,7 @@ class BaseModel {
|
||||
}
|
||||
|
||||
// Remove fields that are not in the `fields` list, if provided.
|
||||
// Note that things like update_time, user_update_time will still
|
||||
// Note that things like update_time, user_updated_time will still
|
||||
// be part of the final list of fields if autoTimestamp is on.
|
||||
// id also will stay.
|
||||
if (!options.isNew && options.fields) {
|
||||
@ -314,6 +314,10 @@ class BaseModel {
|
||||
// The purpose of user_updated_time is to allow the user to manually set the time of a note (in which case
|
||||
// options.autoTimestamp will be `false`). However note that if the item is later changed, this timestamp
|
||||
// will be set again to the current time.
|
||||
//
|
||||
// The technique to modify user_updated_time while keeping updated_time current (so that sync can happen) is to
|
||||
// manually set updated_time when saving and to set autoTimestamp to false, for example:
|
||||
// Note.save({ id: "...", updated_time: Date.now(), user_updated_time: 1436342618000 }, { autoTimestamp: false })
|
||||
if (options.autoTimestamp && this.hasField('user_updated_time')) {
|
||||
o.user_updated_time = timeNow;
|
||||
}
|
||||
|
@ -100,7 +100,12 @@ class Note extends BaseItem {
|
||||
static geolocationUrl(note) {
|
||||
if (!('latitude' in note) || !('longitude' in note)) throw new Error('Latitude or longitude is missing');
|
||||
if (!Number(note.latitude) && !Number(note.longitude)) throw new Error(_('This note does not have geolocation information.'));
|
||||
return sprintf('https://www.openstreetmap.org/?lat=%s&lon=%s&zoom=20', note.latitude, note.longitude)
|
||||
return this.geoLocationUrlFromLatLong(note.latitude, note.longitude);
|
||||
//return sprintf('https://www.openstreetmap.org/?lat=%s&lon=%s&zoom=20', note.latitude, note.longitude);
|
||||
}
|
||||
|
||||
static geoLocationUrlFromLatLong(lat, long) {
|
||||
return sprintf('https://www.openstreetmap.org/?lat=%s&lon=%s&zoom=20', lat, long)
|
||||
}
|
||||
|
||||
static modelType() {
|
||||
@ -465,17 +470,6 @@ class Note extends BaseItem {
|
||||
return note;
|
||||
}
|
||||
|
||||
// Not used?
|
||||
|
||||
// static async delete(id, options = null) {
|
||||
// let r = await super.delete(id, options);
|
||||
|
||||
// this.dispatch({
|
||||
// type: 'NOTE_DELETE',
|
||||
// id: id,
|
||||
// });
|
||||
// }
|
||||
|
||||
static async batchDelete(ids, options = null) {
|
||||
const result = await super.batchDelete(ids, options);
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
|
@ -60,6 +60,23 @@ class Time {
|
||||
return moment(ms).format(format);
|
||||
}
|
||||
|
||||
formatLocalToMs(localDateTime, format = null) {
|
||||
if (format === null) format = this.dateTimeFormat();
|
||||
const m = moment(localDateTime, format);
|
||||
if (m.isValid()) return m.toDate().getTime();
|
||||
throw new Error('Invalid input for formatLocalToMs: ' + localDateTime);
|
||||
}
|
||||
|
||||
// Mostly used as a utility function for the DateTime Electron component
|
||||
anythingToDateTime(o, defaultValue = null) {
|
||||
if (o && o.toDate) return o.toDate();
|
||||
if (!o) return defaultValue;
|
||||
let m = moment(o, time.dateTimeFormat());
|
||||
if (m.isValid()) return m.toDate();
|
||||
m = moment(o, time.dateFormat());
|
||||
return m.isValid() ? m.toDate() : defaultValue;
|
||||
}
|
||||
|
||||
msleep(ms) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
|
Loading…
Reference in New Issue
Block a user