You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Electron, Mobile: Created alarm service and drivers
This commit is contained in:
		| @@ -1,40 +0,0 @@ | |||||||
| const { time } = require('lib/time-utils.js'); |  | ||||||
| const { setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient } = require('test-utils.js'); |  | ||||||
| const { Folder } = require('lib/models/folder.js'); |  | ||||||
| const { Note } = require('lib/models/note.js'); |  | ||||||
| const { Setting } = require('lib/models/setting.js'); |  | ||||||
| const { BaseItem } = require('lib/models/base-item.js'); |  | ||||||
| const { BaseModel } = require('lib/base-model.js'); |  | ||||||
|  |  | ||||||
| process.on('unhandledRejection', (reason, p) => { |  | ||||||
| 	console.error('Unhandled promise rejection at: Promise', p, 'reason:', reason); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| describe('BaseItem', function() { |  | ||||||
|  |  | ||||||
| 	beforeEach( async (done) => { |  | ||||||
| 		await setupDatabaseAndSynchronizer(1); |  | ||||||
| 		switchClient(1); |  | ||||||
| 		done(); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	it('should create a deleted_items record', async (done) => { |  | ||||||
| 		let folder1 = await Folder.save({ title: 'folder1' }); |  | ||||||
| 		let folder2 = await Folder.save({ title: 'folder2' }); |  | ||||||
|  |  | ||||||
| 		await Folder.delete(folder1.id); |  | ||||||
|  |  | ||||||
| 		let items = await BaseItem.deletedItems(); |  | ||||||
|  |  | ||||||
| 		expect(items.length).toBe(1); |  | ||||||
| 		expect(items[0].item_id).toBe(folder1.id); |  | ||||||
| 		expect(items[0].item_type).toBe(folder1.type_); |  | ||||||
|  |  | ||||||
| 		let folders = await Folder.all(); |  | ||||||
|  |  | ||||||
| 		expect(folders.length).toBe(1); |  | ||||||
|  |  | ||||||
| 		done(); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| }); |  | ||||||
| @@ -19,6 +19,7 @@ const { time } = require('lib/time-utils.js'); | |||||||
| const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); | const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); | ||||||
| const SyncTargetMemory = require('lib/SyncTargetMemory.js'); | const SyncTargetMemory = require('lib/SyncTargetMemory.js'); | ||||||
| const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); | const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); | ||||||
|  | const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); | ||||||
|  |  | ||||||
| let databases_ = []; | let databases_ = []; | ||||||
| let synchronizers_ = []; | let synchronizers_ = []; | ||||||
| @@ -34,6 +35,7 @@ fs.mkdirpSync(logDir, 0o755); | |||||||
|  |  | ||||||
| SyncTargetRegistry.addClass(SyncTargetMemory); | SyncTargetRegistry.addClass(SyncTargetMemory); | ||||||
| SyncTargetRegistry.addClass(SyncTargetFilesystem); | SyncTargetRegistry.addClass(SyncTargetFilesystem); | ||||||
|  | SyncTargetRegistry.addClass(SyncTargetOneDrive); | ||||||
|  |  | ||||||
| const syncTargetId_ = SyncTargetRegistry.nameToId('memory'); | const syncTargetId_ = SyncTargetRegistry.nameToId('memory'); | ||||||
| const syncDir = __dirname + '/../tests/sync'; | const syncDir = __dirname + '/../tests/sync'; | ||||||
|   | |||||||
| @@ -51,7 +51,8 @@ class ElectronAppWrapper { | |||||||
| 			slashes: true | 			slashes: true | ||||||
| 		})) | 		})) | ||||||
|  |  | ||||||
| 		//if (this.env_ === 'dev') this.win_.webContents.openDevTools(); | 		// Uncomment this to view errors if the application does not start | ||||||
|  | 		// if (this.env_ === 'dev') this.win_.webContents.openDevTools(); | ||||||
|  |  | ||||||
| 		this.win_.on('close', (event) => { | 		this.win_.on('close', (event) => { | ||||||
| 			if (this.willQuitApp_ || process.platform !== 'darwin') { | 			if (this.willQuitApp_ || process.platform !== 'darwin') { | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ const { JoplinDatabase } = require('lib/joplin-database.js'); | |||||||
| const { DatabaseDriverNode } = require('lib/database-driver-node.js'); | const { DatabaseDriverNode } = require('lib/database-driver-node.js'); | ||||||
| const { ElectronAppWrapper } = require('./ElectronAppWrapper'); | const { ElectronAppWrapper } = require('./ElectronAppWrapper'); | ||||||
| const { defaultState } = require('lib/reducer.js'); | const { defaultState } = require('lib/reducer.js'); | ||||||
|  | const AlarmService = require('lib/services/AlarmService.js'); | ||||||
|  | const AlarmServiceDriverNode = require('lib/services/AlarmServiceDriverNode'); | ||||||
|  |  | ||||||
| const { bridge } = require('electron').remote.require('./bridge'); | const { bridge } = require('electron').remote.require('./bridge'); | ||||||
| const Menu = bridge().Menu; | const Menu = bridge().Menu; | ||||||
| @@ -135,6 +137,10 @@ class Application extends BaseApplication { | |||||||
| 			if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(); | 			if (!await reg.syncTarget().syncStarted()) reg.scheduleSync(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (['EVENT_NOTE_ALARM_FIELD_CHANGE', 'NOTE_DELETE'].indexOf(action.type) >= 0) { | ||||||
|  | 			await AlarmService.updateNoteNotification(action.id, action.type === 'NOTE_DELETE'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		const result = await super.generalMiddleware(store, next, action); | 		const result = await super.generalMiddleware(store, next, action); | ||||||
| 		const newState = store.getState(); | 		const newState = store.getState(); | ||||||
|  |  | ||||||
| @@ -305,6 +311,9 @@ class Application extends BaseApplication { | |||||||
| 	async start(argv) { | 	async start(argv) { | ||||||
| 		argv = await super.start(argv); | 		argv = await super.start(argv); | ||||||
|  |  | ||||||
|  | 		AlarmService.setDriver(new AlarmServiceDriverNode()); | ||||||
|  | 		AlarmService.setLogger(reg.logger()); | ||||||
|  |  | ||||||
| 		if (Setting.value('openDevTools')) { | 		if (Setting.value('openDevTools')) { | ||||||
| 			bridge().window().webContents.openDevTools(); | 			bridge().window().webContents.openDevTools(); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -175,6 +175,38 @@ class MainScreenComponent extends React.Component { | |||||||
| 								id: searchId, | 								id: searchId, | ||||||
| 							}); | 							}); | ||||||
| 						} | 						} | ||||||
|  | 						this.setState({ promptOptions: null }); | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 			}); | ||||||
|  | 		} else if (command.name === 'editAlarm') { | ||||||
|  | 			const note = await Note.load(command.noteId); | ||||||
|  |  | ||||||
|  | 			this.setState({ | ||||||
|  | 				promptOptions: { | ||||||
|  | 					label: _('Set or clear alarm:'), | ||||||
|  | 					inputType: 'datetime', | ||||||
|  | 					buttons: ['ok', 'cancel', 'clear'], | ||||||
|  | 					value: note.todo_due ? new Date(note.todo_due) : null, | ||||||
|  | 					onClose: async (answer, buttonType) => { | ||||||
|  | 						let newNote = null; | ||||||
|  |  | ||||||
|  | 						if (buttonType === 'clear') { | ||||||
|  | 							newNote = { | ||||||
|  | 								id: note.id, | ||||||
|  | 								todo_due: 0, | ||||||
|  | 							}; | ||||||
|  | 						} else if (answer !== null) { | ||||||
|  | 							newNote = { | ||||||
|  | 								id: note.id, | ||||||
|  | 								todo_due: answer.getTime(), | ||||||
|  | 							}; | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						if (newNote) { | ||||||
|  | 							await Note.save(newNote); | ||||||
|  | 						} | ||||||
|  |  | ||||||
| 						this.setState({ promptOptions: null }); | 						this.setState({ promptOptions: null }); | ||||||
| 					} | 					} | ||||||
| 				}, | 				}, | ||||||
| @@ -274,10 +306,12 @@ class MainScreenComponent extends React.Component { | |||||||
| 					value={promptOptions && promptOptions.value ? promptOptions.value : ''} | 					value={promptOptions && promptOptions.value ? promptOptions.value : ''} | ||||||
| 					theme={this.props.theme} | 					theme={this.props.theme} | ||||||
| 					style={promptStyle} | 					style={promptStyle} | ||||||
| 					onClose={(answer) => promptOptions.onClose(answer)} | 					onClose={(answer, buttonType) => promptOptions.onClose(answer, buttonType)} | ||||||
| 					label={promptOptions ? promptOptions.label : ''} | 					label={promptOptions ? promptOptions.label : ''} | ||||||
| 					description={promptOptions ? promptOptions.description : null} | 					description={promptOptions ? promptOptions.description : null} | ||||||
| 					visible={!!this.state.promptOptions} /> | 					visible={!!this.state.promptOptions} | ||||||
|  | 					buttons={promptOptions && ('buttons' in promptOptions) ? promptOptions.buttons : null} | ||||||
|  | 					inputType={promptOptions && ('inputType' in promptOptions) ? promptOptions.inputType : null} /> | ||||||
| 				<Header style={headerStyle} showBackButton={false} buttons={headerButtons} /> | 				<Header style={headerStyle} showBackButton={false} buttons={headerButtons} /> | ||||||
| 				<SideBar style={sideBarStyle} /> | 				<SideBar style={sideBarStyle} /> | ||||||
| 				<NoteList style={noteListStyle} /> | 				<NoteList style={noteListStyle} /> | ||||||
|   | |||||||
| @@ -336,6 +336,14 @@ class NoteTextComponent extends React.Component { | |||||||
| 			} | 			} | ||||||
| 		}})); | 		}})); | ||||||
|  |  | ||||||
|  | 		menu.append(new MenuItem({label: _('Set or clear alarm'), click: async () => { | ||||||
|  | 			this.props.dispatch({ | ||||||
|  | 				type: 'WINDOW_COMMAND', | ||||||
|  | 				name: 'editAlarm', | ||||||
|  | 				noteId: noteId, | ||||||
|  | 			}); | ||||||
|  | 		}})); | ||||||
|  |  | ||||||
| 		menu.popup(bridge().window()); | 		menu.popup(bridge().window()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ const React = require('react'); | |||||||
| const { connect } = require('react-redux'); | const { connect } = require('react-redux'); | ||||||
| const { _ } = require('lib/locale.js'); | const { _ } = require('lib/locale.js'); | ||||||
| const { themeStyle } = require('../theme.js'); | const { themeStyle } = require('../theme.js'); | ||||||
|  | const Datetime = require('react-datetime'); | ||||||
|  |  | ||||||
| class PromptDialog extends React.Component { | class PromptDialog extends React.Component { | ||||||
|  |  | ||||||
| @@ -32,6 +33,7 @@ class PromptDialog extends React.Component { | |||||||
| 	render() { | 	render() { | ||||||
| 		const style = this.props.style; | 		const style = this.props.style; | ||||||
| 		const theme = themeStyle(this.props.theme); | 		const theme = themeStyle(this.props.theme); | ||||||
|  | 		const buttonTypes = this.props.buttons ? this.props.buttons : ['ok', 'cancel']; | ||||||
|  |  | ||||||
| 		const modalLayerStyle = { | 		const modalLayerStyle = { | ||||||
| 			zIndex: 9999, | 			zIndex: 9999, | ||||||
| @@ -76,8 +78,8 @@ class PromptDialog extends React.Component { | |||||||
| 			marginTop: 10, | 			marginTop: 10, | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		const onClose = (accept) => { | 		const onClose = (accept, buttonType) => { | ||||||
| 			if (this.props.onClose) this.props.onClose(accept ? this.state.answer : null); | 			if (this.props.onClose) this.props.onClose(accept ? this.state.answer : null, buttonType); | ||||||
| 			this.setState({ visible: false, answer: '' }); | 			this.setState({ visible: false, answer: '' }); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @@ -85,6 +87,10 @@ class PromptDialog extends React.Component { | |||||||
| 			this.setState({ answer: event.target.value }); | 			this.setState({ answer: event.target.value }); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		const onDateTimeChange = (momentObject) => { | ||||||
|  | 			this.setState({ answer: momentObject.toDate() }); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		const onKeyDown = (event) => { | 		const onKeyDown = (event) => { | ||||||
| 			if (event.key === 'Enter') { | 			if (event.key === 'Enter') { | ||||||
| 				onClose(true); | 				onClose(true); | ||||||
| @@ -95,23 +101,41 @@ class PromptDialog extends React.Component { | |||||||
|  |  | ||||||
| 		const descComp = this.props.description ? <div style={descStyle}>{this.props.description}</div> : null; | 		const descComp = this.props.description ? <div style={descStyle}>{this.props.description}</div> : null; | ||||||
|  |  | ||||||
|  | 		let inputComp = null; | ||||||
|  |  | ||||||
|  | 		if (this.props.inputType === 'datetime') { | ||||||
|  | 			inputComp = <Datetime | ||||||
|  | 				value={this.state.answer} | ||||||
|  | 				dateFormat="DD/MM/YYYY" | ||||||
|  | 				timeFormat="HH:mm" | ||||||
|  | 				onChange={(momentObject) => onDateTimeChange(momentObject)} | ||||||
|  | 			/> | ||||||
|  | 		} else { | ||||||
|  | 			inputComp = <input | ||||||
|  | 				style={inputStyle} | ||||||
|  | 				ref={input => this.answerInput_ = input} | ||||||
|  | 				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={buttonStyle} onClick={() => onClose(true, 'ok')}>{_('OK')}</button>); | ||||||
|  | 		if (buttonTypes.indexOf('cancel') >= 0) buttonComps.push(<button key="cancel" style={buttonStyle} onClick={() => onClose(false, 'cancel')}>{_('Cancel')}</button>); | ||||||
|  | 		if (buttonTypes.indexOf('clear') >= 0) buttonComps.push(<button key="clear" style={buttonStyle} onClick={() => onClose(false, 'clear')}>{_('Clear')}</button>); | ||||||
|  |  | ||||||
| 		return ( | 		return ( | ||||||
| 			<div style={modalLayerStyle}> | 			<div style={modalLayerStyle}> | ||||||
| 				<div style={promptDialogStyle}> | 				<div style={promptDialogStyle}> | ||||||
| 					<label style={labelStyle}>{this.props.label ? this.props.label : ''}</label> | 					<label style={labelStyle}>{this.props.label ? this.props.label : ''}</label> | ||||||
| 					<div style={{display: 'inline-block'}}> | 					<div style={{display: 'inline-block'}}> | ||||||
| 						<input | 						{inputComp} | ||||||
| 							style={inputStyle} |  | ||||||
| 							ref={input => this.answerInput_ = input} |  | ||||||
| 							value={this.state.answer} |  | ||||||
| 							type="text" |  | ||||||
| 							onChange={(event) => onChange(event)} |  | ||||||
| 							onKeyDown={(event) => onKeyDown(event)} /> |  | ||||||
| 						{descComp} | 						{descComp} | ||||||
| 					</div> | 					</div> | ||||||
| 					<div style={{ textAlign: 'right', marginTop: 10 }}> | 					<div style={{ textAlign: 'right', marginTop: 10 }}> | ||||||
| 						<button style={buttonStyle} onClick={() => onClose(true)}>OK</button> | 						{buttonComps} | ||||||
| 						<button style={buttonStyle} onClick={() => onClose(false)}>Cancel</button> |  | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| 		<title>Joplin</title> | 		<title>Joplin</title> | ||||||
| 		<link rel="stylesheet" href="style.css"> | 		<link rel="stylesheet" href="style.css"> | ||||||
| 		<link rel="stylesheet" href="css/font-awesome.min.css"> | 		<link rel="stylesheet" href="css/font-awesome.min.css"> | ||||||
|  | 		<link rel="stylesheet" href="node_modules/react-datetime/css/react-datetime.css"> | ||||||
| 	</head> | 	</head> | ||||||
| 	<body> | 	<body> | ||||||
| 		<div id="react-root"></div> | 		<div id="react-root"></div> | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								ElectronClient/app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										33
									
								
								ElectronClient/app/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1080,6 +1080,16 @@ | |||||||
|         "capture-stack-trace": "1.0.0" |         "capture-stack-trace": "1.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "create-react-class": { | ||||||
|  |       "version": "15.6.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", | ||||||
|  |       "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", | ||||||
|  |       "requires": { | ||||||
|  |         "fbjs": "0.8.16", | ||||||
|  |         "loose-envify": "1.3.1", | ||||||
|  |         "object-assign": "4.1.1" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "cross-spawn": { |     "cross-spawn": { | ||||||
|       "version": "5.1.0", |       "version": "5.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", |       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", | ||||||
| @@ -3561,6 +3571,24 @@ | |||||||
|         "prop-types": "15.6.0" |         "prop-types": "15.6.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "react-datetime": { | ||||||
|  |       "version": "2.11.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/react-datetime/-/react-datetime-2.11.0.tgz", | ||||||
|  |       "integrity": "sha512-LfToQVZrFjH9b+R4PWemo/jJcWawHWHL+eF1HFWGZ9zGXXgy7d/X1+PzJUBYiZfteFM+VSdaHvYyqReqP/nGaQ==", | ||||||
|  |       "requires": { | ||||||
|  |         "create-react-class": "15.6.2", | ||||||
|  |         "object-assign": "3.0.0", | ||||||
|  |         "prop-types": "15.6.0", | ||||||
|  |         "react-onclickoutside": "6.7.0" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "object-assign": { | ||||||
|  |           "version": "3.0.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", | ||||||
|  |           "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "react-dom": { |     "react-dom": { | ||||||
|       "version": "16.1.1", |       "version": "16.1.1", | ||||||
|       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.1.tgz", |       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.1.1.tgz", | ||||||
| @@ -3572,6 +3600,11 @@ | |||||||
|         "prop-types": "15.6.0" |         "prop-types": "15.6.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "react-onclickoutside": { | ||||||
|  |       "version": "6.7.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.0.tgz", | ||||||
|  |       "integrity": "sha512-IBivBP7xayM7SbbVlAnKgHgoWdfCVqnNBNgQRY5x9iFQm55tFdolR02hX1fCJJtTEKnbaL1stB72/TZc6+p2+Q==" | ||||||
|  |     }, | ||||||
|     "react-redux": { |     "react-redux": { | ||||||
|       "version": "5.0.6", |       "version": "5.0.6", | ||||||
|       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", |       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.6.tgz", | ||||||
|   | |||||||
| @@ -69,6 +69,7 @@ | |||||||
|     "query-string": "^5.0.1", |     "query-string": "^5.0.1", | ||||||
|     "react": "^16.0.0", |     "react": "^16.0.0", | ||||||
|     "react-ace": "^5.5.0", |     "react-ace": "^5.5.0", | ||||||
|  |     "react-datetime": "^2.11.0", | ||||||
|     "react-dom": "^16.0.0", |     "react-dom": "^16.0.0", | ||||||
|     "react-redux": "^5.0.6", |     "react-redux": "^5.0.6", | ||||||
|     "redux": "^3.7.2", |     "redux": "^3.7.2", | ||||||
|   | |||||||
| @@ -354,7 +354,7 @@ class BaseApplication { | |||||||
| 		this.logger_.info('Profile directory: ' + profileDir); | 		this.logger_.info('Profile directory: ' + profileDir); | ||||||
|  |  | ||||||
| 		this.database_ = new JoplinDatabase(new DatabaseDriverNode()); | 		this.database_ = new JoplinDatabase(new DatabaseDriverNode()); | ||||||
| 		//this.database_.setLogExcludedQueryTypes(['SELECT']); | 		this.database_.setLogExcludedQueryTypes(['SELECT']); | ||||||
| 		this.database_.setLogger(this.dbLogger_); | 		this.database_.setLogger(this.dbLogger_); | ||||||
| 		await this.database_.open({ name: profileDir + '/database.sqlite' }); | 		await this.database_.open({ name: profileDir + '/database.sqlite' }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -286,7 +286,7 @@ class BaseModel { | |||||||
|  |  | ||||||
| 		return this.db().transactionExecBatch(queries).then(() => { | 		return this.db().transactionExecBatch(queries).then(() => { | ||||||
| 			o = Object.assign({}, o); | 			o = Object.assign({}, o); | ||||||
| 			o.id = modelId; | 			if (modelId) o.id = modelId; | ||||||
| 			if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; | 			if ('updated_time' in saveQuery.modObject) o.updated_time = saveQuery.modObject.updated_time; | ||||||
| 			if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; | 			if ('created_time' in saveQuery.modObject) o.created_time = saveQuery.modObject.created_time; | ||||||
| 			if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; | 			if ('user_updated_time' in saveQuery.modObject) o.user_updated_time = saveQuery.modObject.user_updated_time; | ||||||
| @@ -360,6 +360,7 @@ BaseModel.TYPE_RESOURCE = 4; | |||||||
| BaseModel.TYPE_TAG = 5; | BaseModel.TYPE_TAG = 5; | ||||||
| BaseModel.TYPE_NOTE_TAG = 6; | BaseModel.TYPE_NOTE_TAG = 6; | ||||||
| BaseModel.TYPE_SEARCH = 7; | BaseModel.TYPE_SEARCH = 7; | ||||||
|  | BaseModel.TYPE_ALARM = 8; | ||||||
|  |  | ||||||
| BaseModel.db_ = null; | BaseModel.db_ = null; | ||||||
| BaseModel.dispatch = function(o) {}; | BaseModel.dispatch = function(o) {}; | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-pi | |||||||
| const ImageResizer = require('react-native-image-resizer').default; | const ImageResizer = require('react-native-image-resizer').default; | ||||||
| const shared = require('lib/components/shared/note-screen-shared.js'); | const shared = require('lib/components/shared/note-screen-shared.js'); | ||||||
| const ImagePicker = require('react-native-image-picker'); | const ImagePicker = require('react-native-image-picker'); | ||||||
|  | const AlarmService = require('lib/services/AlarmService.js'); | ||||||
| const { SelectDateTimeDialog } = require('lib/components/select-date-time-dialog.js'); | const { SelectDateTimeDialog } = require('lib/components/select-date-time-dialog.js'); | ||||||
|  |  | ||||||
| class NoteScreenComponent extends BaseScreenComponent { | class NoteScreenComponent extends BaseScreenComponent { | ||||||
| @@ -347,12 +348,9 @@ class NoteScreenComponent extends BaseScreenComponent { | |||||||
| 		let newNote = Object.assign({}, this.state.note); | 		let newNote = Object.assign({}, this.state.note); | ||||||
| 		newNote.todo_due = date ? date.getTime() : 0; | 		newNote.todo_due = date ? date.getTime() : 0; | ||||||
|  |  | ||||||
| 		this.setState({ | 		await this.saveOneProperty('todo_due', date ? date.getTime() : 0); | ||||||
| 			alarmDialogShown: false, |  | ||||||
| 			note: newNote, | 		this.setState({ alarmDialogShown: false }); | ||||||
| 		}); |  | ||||||
| 		//await this.saveOneProperty('todo_due', date ? date.getTime() : 0); |  | ||||||
| 		//this.forceUpdate(); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	onAlarmDialogReject() { | 	onAlarmDialogReject() { | ||||||
| @@ -389,14 +387,12 @@ class NoteScreenComponent extends BaseScreenComponent { | |||||||
| 			output.push({ title: _('Attach image'), onPress: () => { this.attachImage_onPress(); } }); | 			output.push({ title: _('Attach image'), onPress: () => { this.attachImage_onPress(); } }); | ||||||
| 			output.push({ title: _('Attach any other file'), onPress: () => { this.attachFile_onPress(); } }); | 			output.push({ title: _('Attach any other file'), onPress: () => { this.attachFile_onPress(); } }); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (isTodo) { | ||||||
|  | 			output.push({ title: _('Set or clear alarm'), onPress: () => { this.setState({ alarmDialogShown: true }) }});; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		output.push({ title: _('Delete note'), onPress: () => { this.deleteNote_onPress(); } }); | 		output.push({ title: _('Delete note'), onPress: () => { this.deleteNote_onPress(); } }); | ||||||
| 		output.push({ title: _('Alarm'), onPress: () => { this.setState({ alarmDialogShown: true }) }});; |  | ||||||
|  |  | ||||||
| 		// if (isTodo) { |  | ||||||
| 		// 	let text = note.todo_due ? _('Edit/Clear alarm') : _('Set an alarm'); |  | ||||||
| 		// 	output.push({ title: text, onPress: () => { this.setAlarm_onPress(); } }); |  | ||||||
| 		// } |  | ||||||
|  |  | ||||||
| 		output.push({ title: isTodo ? _('Convert to regular note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } }); | 		output.push({ title: isTodo ? _('Convert to regular note') : _('Convert to todo'), onPress: () => { this.toggleIsTodo_onPress(); } }); | ||||||
| 		if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } }); | 		if (this.props.showAdvancedOptions) output.push({ title: this.state.showNoteMetadata ? _('Hide metadata') : _('Show metadata'), onPress: () => { this.showMetadata_onPress(); } }); | ||||||
| 		output.push({ title: _('View location on map'), onPress: () => { this.showOnMap_onPress(); } }); | 		output.push({ title: _('View location on map'), onPress: () => { this.showOnMap_onPress(); } }); | ||||||
|   | |||||||
| @@ -33,12 +33,22 @@ shared.saveNoteButton_press = async function(comp) { | |||||||
| 	if (isNew && !note.title) { | 	if (isNew && !note.title) { | ||||||
| 		note.title = Note.defaultTitle(note); | 		note.title = Note.defaultTitle(note); | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| 	note = await Note.save(note); | 	// Save only the properties that have changed | ||||||
|  | 	const diff = BaseModel.diffObjects(comp.state.lastSavedNote, note); | ||||||
|  | 	diff.type_ = note.type_; | ||||||
|  | 	diff.id = note.id; | ||||||
|  |  | ||||||
|  | 	const savedNote = await Note.save(diff); | ||||||
|  |  | ||||||
|  | 	// Re-assign any property that might have changed during saving (updated_time, etc.) | ||||||
|  | 	note = Object.assign(note, savedNote); | ||||||
|  |  | ||||||
| 	comp.setState({ | 	comp.setState({ | ||||||
| 		lastSavedNote: Object.assign({}, note), | 		lastSavedNote: Object.assign({}, note), | ||||||
| 		note: note, | 		note: note, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	if (isNew) Note.updateGeolocation(note.id); | 	if (isNew) Note.updateGeolocation(note.id); | ||||||
| 	comp.refreshNoteMetadata(); | 	comp.refreshNoteMetadata(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -67,6 +67,10 @@ class DatabaseDriverNode { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	lastInsertId() { | ||||||
|  | 		throw new Error('NOT IMPLEMENTED'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { DatabaseDriverNode }; | module.exports = { DatabaseDriverNode }; | ||||||
| @@ -2,6 +2,10 @@ const SQLite = require('react-native-sqlite-storage'); | |||||||
|  |  | ||||||
| class DatabaseDriverReactNative { | class DatabaseDriverReactNative { | ||||||
|  |  | ||||||
|  | 	constructor() { | ||||||
|  | 		this.lastInsertId_ = null; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	open(options) { | 	open(options) { | ||||||
| 		//SQLite.DEBUG(true); | 		//SQLite.DEBUG(true); | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| @@ -45,6 +49,7 @@ class DatabaseDriverReactNative { | |||||||
| 	exec(sql, params = null) { | 	exec(sql, params = null) { | ||||||
| 		return new Promise((resolve, reject) => { | 		return new Promise((resolve, reject) => { | ||||||
| 			this.db_.executeSql(sql, params, (r) => { | 			this.db_.executeSql(sql, params, (r) => { | ||||||
|  | 				if ('insertId' in r) this.lastInsertId_ = r.insertId; | ||||||
| 				resolve(r); | 				resolve(r); | ||||||
| 			}, (error) => { | 			}, (error) => { | ||||||
| 				reject(error); | 				reject(error); | ||||||
| @@ -52,6 +57,10 @@ class DatabaseDriverReactNative { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	lastInsertId() { | ||||||
|  | 		return this.lastInsertId_; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { DatabaseDriverReactNative }; | module.exports = { DatabaseDriverReactNative }; | ||||||
| @@ -78,8 +78,10 @@ class Database { | |||||||
| 			} catch (error) { | 			} catch (error) { | ||||||
| 				if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) { | 				if (error && (error.code == 'SQLITE_IOERR' || error.code == 'SQLITE_BUSY')) { | ||||||
| 					if (totalWaitTime >= 20000) throw this.sqliteErrorToJsError(error, sql, params); | 					if (totalWaitTime >= 20000) throw this.sqliteErrorToJsError(error, sql, params); | ||||||
| 					this.logger().warn(sprintf('Error %s: will retry in %s milliseconds', error.code, waitTime)); | 					// NOTE: don't put logger statements here because it might log to the database, which | ||||||
| 					this.logger().warn('Error was: ' + error.toString()); | 					// could result in an error being thrown again. | ||||||
|  | 					// this.logger().warn(sprintf('Error %s: will retry in %s milliseconds', error.code, waitTime)); | ||||||
|  | 					// this.logger().warn('Error was: ' + error.toString()); | ||||||
| 					await time.msleep(waitTime); | 					await time.msleep(waitTime); | ||||||
| 					totalWaitTime += waitTime; | 					totalWaitTime += waitTime; | ||||||
| 					waitTime *= 1.5; | 					waitTime *= 1.5; | ||||||
|   | |||||||
| @@ -160,6 +160,7 @@ class JoplinDatabase extends Database { | |||||||
| 				let tableName = tableRows[i].name; | 				let tableName = tableRows[i].name; | ||||||
| 				if (tableName == 'android_metadata') continue; | 				if (tableName == 'android_metadata') continue; | ||||||
| 				if (tableName == 'table_fields') continue; | 				if (tableName == 'table_fields') continue; | ||||||
|  | 				if (tableName == 'sqlite_sequence') continue; | ||||||
| 				chain.push(() => { | 				chain.push(() => { | ||||||
| 					return this.selectAll('PRAGMA table_info("' + tableName + '")').then((pragmas) => { | 					return this.selectAll('PRAGMA table_info("' + tableName + '")').then((pragmas) => { | ||||||
| 						for (let i = 0; i < pragmas.length; i++) { | 						for (let i = 0; i < pragmas.length; i++) { | ||||||
| @@ -201,7 +202,7 @@ class JoplinDatabase extends Database { | |||||||
| 		// default value and thus might cause problems. In that case, the default value | 		// default value and thus might cause problems. In that case, the default value | ||||||
| 		// must be set in the synchronizer too. | 		// must be set in the synchronizer too. | ||||||
|  |  | ||||||
| 		const existingDatabaseVersions = [0, 1, 2, 3, 4, 5]; | 		const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6]; | ||||||
|  |  | ||||||
| 		let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion); | 		let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion); | ||||||
| 		if (currentVersionIndex == existingDatabaseVersions.length - 1) return false; | 		if (currentVersionIndex == existingDatabaseVersions.length - 1) return false; | ||||||
| @@ -253,6 +254,11 @@ class JoplinDatabase extends Database { | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			if (targetVersion == 6) { | ||||||
|  | 				queries.push('CREATE TABLE alarms (id INTEGER PRIMARY KEY AUTOINCREMENT, note_id TEXT NOT NULL, trigger_time INT NOT NULL)'); | ||||||
|  | 				queries.push('CREATE INDEX alarm_note_id ON alarms (note_id)'); | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] }); | 			queries.push({ sql: 'UPDATE version SET version = ?', params: [targetVersion] }); | ||||||
| 			await this.transactionExecBatch(queries); | 			await this.transactionExecBatch(queries); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								ReactNativeClient/lib/models/Alarm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ReactNativeClient/lib/models/Alarm.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | const { BaseModel } = require('lib/base-model.js'); | ||||||
|  |  | ||||||
|  | class Alarm extends BaseModel { | ||||||
|  |  | ||||||
|  | 	static tableName() { | ||||||
|  | 		return 'alarms'; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static modelType() { | ||||||
|  | 		return BaseModel.TYPE_ALARM; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static byNoteId(noteId) { | ||||||
|  | 		return this.modelSelectOne('SELECT * FROM alarms WHERE note_id = ?', [noteId]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static async garbageCollect() { | ||||||
|  | 		// Delete alarms that have already been triggered | ||||||
|  | 		await this.db().exec('DELETE FROM alarms WHERE trigger_time <= ?', [Date.now()]); | ||||||
|  |  | ||||||
|  | 		// Delete alarms that correspond to non-existent notes | ||||||
|  | 		// https://stackoverflow.com/a/4967229/561309 | ||||||
|  | 		await this.db().exec('DELETE FROM alarms WHERE id IN (SELECT alarms.id FROM alarms LEFT JOIN notes ON alarms.note_id = notes.id WHERE notes.id IS NULL)'); | ||||||
|  |  | ||||||
|  | 		// TODO: Check for duplicate alarms for a note | ||||||
|  | 		// const rows = await this.db().exec('SELECT count(*) as note_count, note_id from alarms group by note_id having note_count >= 2'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = Alarm; | ||||||
| @@ -389,6 +389,13 @@ class Note extends BaseItem { | |||||||
| 				type: 'NOTE_UPDATE_ONE', | 				type: 'NOTE_UPDATE_ONE', | ||||||
| 				note: note, | 				note: note, | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|  | 			if ('todo_due' in o || 'todo_completed' in o || 'is_todo' in o || 'is_conflict' in o) { | ||||||
|  | 				this.dispatch({ | ||||||
|  | 					type: 'EVENT_NOTE_ALARM_FIELD_CHANGE', | ||||||
|  | 					id: note.id, | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
| 			 | 			 | ||||||
| 			return note; | 			return note; | ||||||
| 		}); | 		}); | ||||||
| @@ -414,6 +421,14 @@ class Note extends BaseItem { | |||||||
| 		return result; | 		return result; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	static dueNotes() { | ||||||
|  | 		return this.modelSelectAll('SELECT id, title, body, todo_due FROM notes WHERE is_conflict = 0 AND is_todo = 1 AND todo_completed = 0 AND todo_due > ?', [time.unixMs()]); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static needAlarm(note) { | ||||||
|  | 		return note.is_todo && !note.todo_completed && note.todo_due >= time.unixMs() && !note.is_conflict; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Tells whether the conflict between the local and remote note can be ignored. | 	// Tells whether the conflict between the local and remote note can be ignored. | ||||||
| 	static mustHandleConflict(localNote, remoteNote) { | 	static mustHandleConflict(localNote, remoteNote) { | ||||||
| 		// That shouldn't happen so throw an exception | 		// That shouldn't happen so throw an exception | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								ReactNativeClient/lib/services/AlarmService.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								ReactNativeClient/lib/services/AlarmService.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | const { Note } = require('lib/models/note.js'); | ||||||
|  | const Alarm = require('lib/models/Alarm.js'); | ||||||
|  |  | ||||||
|  | class AlarmService { | ||||||
|  |  | ||||||
|  | 	static setDriver(v) { | ||||||
|  | 		this.driver_ = v; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static driver() { | ||||||
|  | 		if (!this.driver_) throw new Error('AlarmService driver not set!'); | ||||||
|  | 		return this.driver_; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static setLogger(v) { | ||||||
|  | 		this.logger_ = v; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static logger() { | ||||||
|  | 		return this.logger_; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static async updateNoteNotification(noteId, isDeleted = false) { | ||||||
|  | 		const note = await Note.load(noteId); | ||||||
|  | 		if (!note && !isDeleted) return; | ||||||
|  |  | ||||||
|  | 		let alarm = await Alarm.byNoteId(note.id); | ||||||
|  | 		let clearAlarm = false; | ||||||
|  |  | ||||||
|  | 		if (isDeleted || | ||||||
|  | 		    !Note.needAlarm(note) || | ||||||
|  | 		    (alarm && alarm.trigger_time !== note.todo_due)) | ||||||
|  | 		{ | ||||||
|  | 			clearAlarm = !!alarm; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!clearAlarm && alarm) return; // Alarm already exists and set at the right time | ||||||
|  |  | ||||||
|  | 		if (clearAlarm) { | ||||||
|  | 			this.logger().info('Clearing notification for note ' + noteId); | ||||||
|  | 			await this.driver().clearNotification(alarm.id); | ||||||
|  | 			await Alarm.delete(alarm.id); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (isDeleted || !Note.needAlarm(note)) return; | ||||||
|  |  | ||||||
|  | 		await Alarm.save({ | ||||||
|  | 			note_id: note.id, | ||||||
|  | 			trigger_time: note.todo_due, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		// Reload alarm to get its ID | ||||||
|  | 		alarm = await Alarm.byNoteId(note.id); | ||||||
|  |  | ||||||
|  | 		const notification = { | ||||||
|  | 			id: alarm.id, | ||||||
|  | 			date: new Date(note.todo_due), | ||||||
|  | 			title: note.title, | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		if (note.body) notification.body = note.body; | ||||||
|  |  | ||||||
|  | 		this.logger().info('Scheduling notification for note ' + note.id, notification); | ||||||
|  | 		await this.driver().scheduleNotification(notification); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// TODO: inner notifications (when app is active) | ||||||
|  | 	// TODO: locale-dependent format | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = AlarmService; | ||||||
							
								
								
									
										23
									
								
								ReactNativeClient/lib/services/AlarmServiceDriver.android.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ReactNativeClient/lib/services/AlarmServiceDriver.android.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | const PushNotification = require('react-native-push-notification'); | ||||||
|  |  | ||||||
|  | class AlarmServiceDriver { | ||||||
|  |  | ||||||
|  | 	async clearNotification(id) { | ||||||
|  | 		PushNotification.cancelLocalNotifications({ id: id }); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	async scheduleNotification(notification) { | ||||||
|  | 		const androidNotification = { | ||||||
|  | 			id: notification.id, | ||||||
|  | 			message: notification.title.substr(0, 100), // No idea what the limits are for title and body but set something reasonable anyway | ||||||
|  | 			date: notification.date, | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		if ('body' in notification) androidNotification.body = notification.body.substr(0, 512); | ||||||
|  |  | ||||||
|  | 		PushNotification.localNotificationSchedule(androidNotification); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = AlarmServiceDriver; | ||||||
							
								
								
									
										23
									
								
								ReactNativeClient/lib/services/AlarmServiceDriverNode.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ReactNativeClient/lib/services/AlarmServiceDriverNode.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | class AlarmServiceDriverNode { | ||||||
|  |  | ||||||
|  | 	async clearNotification(id) { | ||||||
|  | 		console.info('AlarmServiceDriverNode::clearNotification', id); | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	async scheduleNotification(notification) { | ||||||
|  | 		console.info('AlarmServiceDriverNode::scheduleNotification', notification); | ||||||
|  |  | ||||||
|  | 		// const androidNotification = { | ||||||
|  | 		// 	id: notification.id, | ||||||
|  | 		// 	message: notification.title.substr(0, 100), // No idea what the limits are for title and body but set something reasonable anyway | ||||||
|  | 		// 	date: notification.date, | ||||||
|  | 		// }; | ||||||
|  |  | ||||||
|  | 		// if ('body' in notification) androidNotification.body = notification.body.substr(0, 512); | ||||||
|  |  | ||||||
|  | 		// PushNotification.localNotificationSchedule(androidNotification); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = AlarmServiceDriverNode; | ||||||
| @@ -419,8 +419,20 @@ class Synchronizer { | |||||||
| 						} | 						} | ||||||
| 						content = await BaseItem.unserialize(content); | 						content = await BaseItem.unserialize(content); | ||||||
| 						let ItemClass = BaseItem.itemClass(content); | 						let ItemClass = BaseItem.itemClass(content); | ||||||
|  | 						content = ItemClass.filter(content); | ||||||
|  |  | ||||||
|  | 						let newContent = null; | ||||||
|  |  | ||||||
|  | 						if (action === 'createLocal') { | ||||||
|  | 							newContent = Object.assign({}, content); | ||||||
|  | 						} else if (action === 'updateLocal') { | ||||||
|  | 							newContent = BaseModel.diffObjects(local, content); | ||||||
|  | 							newContent.type_ = content.type_; | ||||||
|  | 							newContent.id = content.id; | ||||||
|  | 						} else { | ||||||
|  | 							throw new Error('Unknown action: ' + action); | ||||||
|  | 						} | ||||||
|  |  | ||||||
| 						let newContent = Object.assign({}, content); |  | ||||||
| 						let options = { | 						let options = { | ||||||
| 							autoTimestamp: false, | 							autoTimestamp: false, | ||||||
| 							nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), | 							nextQueries: BaseItem.updateSyncTimeQueries(syncTargetId, newContent, time.unixMs()), | ||||||
|   | |||||||
| @@ -3,11 +3,11 @@ const moment = require('moment'); | |||||||
| let time = { | let time = { | ||||||
|  |  | ||||||
| 	unix() { | 	unix() { | ||||||
| 		return Math.floor((new Date()).getTime() / 1000); | 		return Math.floor(Date.now() / 1000); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	unixMs() { | 	unixMs() { | ||||||
| 		return (new Date()).getTime(); | 		return Date.now(); | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	unixMsToObject(ms) { | 	unixMsToObject(ms) { | ||||||
|   | |||||||
| @@ -1,7 +1,9 @@ | |||||||
| const React = require('react'); const Component = React.Component; | const React = require('react'); const Component = React.Component; | ||||||
| const { Keyboard, NativeModules, PushNotificationIOS } = require('react-native'); | const { Keyboard, NativeModules } = require('react-native'); | ||||||
| const { connect, Provider } = require('react-redux'); | const { connect, Provider } = require('react-redux'); | ||||||
| const { BackButtonService } = require('lib/services/back-button.js'); | const { BackButtonService } = require('lib/services/back-button.js'); | ||||||
|  | const AlarmService = require('lib/services/AlarmService.js'); | ||||||
|  | const AlarmServiceDriver = require('lib/services/AlarmServiceDriver'); | ||||||
| const { createStore, applyMiddleware } = require('redux'); | const { createStore, applyMiddleware } = require('redux'); | ||||||
| const { shimInit } = require('lib/shim-init-react.js'); | const { shimInit } = require('lib/shim-init-react.js'); | ||||||
| const { Log } = require('lib/log.js'); | const { Log } = require('lib/log.js'); | ||||||
| @@ -37,7 +39,6 @@ const { _, setLocale, closestSupportedLocale, defaultLocale } = require('lib/loc | |||||||
| const RNFetchBlob = require('react-native-fetch-blob').default; | const RNFetchBlob = require('react-native-fetch-blob').default; | ||||||
| const { PoorManIntervals } = require('lib/poor-man-intervals.js'); | const { PoorManIntervals } = require('lib/poor-man-intervals.js'); | ||||||
| const { reducer, defaultState } = require('lib/reducer.js'); | const { reducer, defaultState } = require('lib/reducer.js'); | ||||||
| const PushNotification = require('react-native-push-notification'); |  | ||||||
|  |  | ||||||
| const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); | const SyncTargetRegistry = require('lib/SyncTargetRegistry.js'); | ||||||
| const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); | const SyncTargetOneDrive = require('lib/SyncTargetOneDrive.js'); | ||||||
| @@ -290,6 +291,9 @@ async function initialize(dispatch, backButtonHandler) { | |||||||
| 	BaseItem.loadClass('Tag', Tag); | 	BaseItem.loadClass('Tag', Tag); | ||||||
| 	BaseItem.loadClass('NoteTag', NoteTag); | 	BaseItem.loadClass('NoteTag', NoteTag); | ||||||
|  |  | ||||||
|  | 	AlarmService.setDriver(new AlarmServiceDriver()); | ||||||
|  | 	AlarmService.setLogger(mainLogger); | ||||||
|  |  | ||||||
| 	try { | 	try { | ||||||
| 		if (Setting.value('env') == 'prod') { | 		if (Setting.value('env') == 'prod') { | ||||||
| 			await db.open({ name: 'joplin.sqlite' }) | 			await db.open({ name: 'joplin.sqlite' }) | ||||||
| @@ -366,13 +370,14 @@ async function initialize(dispatch, backButtonHandler) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	reg.logger().info('Scheduling iOS notification'); |  | ||||||
|  |  | ||||||
| 	PushNotificationIOS.scheduleLocalNotification({ | 	//reg.logger().info('Scheduling iOS notification'); | ||||||
| 		alertTitle: "From Joplin", |  | ||||||
| 		alertBody : "Testing notification on iOS", | 	// PushNotificationIOS.scheduleLocalNotification({ | ||||||
| 		fireDate: new Date(Date.now() + (10 * 1000)), | 	// 	alertTitle: "From Joplin", | ||||||
| 	}); | 	// 	alertBody : "Testing notification on iOS", | ||||||
|  | 	// 	fireDate: new Date(Date.now() + (10 * 1000)), | ||||||
|  | 	// }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user