You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Change folder from RN
This commit is contained in:
		| @@ -260,8 +260,8 @@ class Application { | ||||
| 		} else if (syncTarget == 'memory') { | ||||
| 			fileApi = new FileApi('joplin', new FileApiDriverMemory()); | ||||
| 			fileApi.setLogger(this.logger_); | ||||
| 		} else if (syncTarget == 'file') { | ||||
| 			let syncDir = Setting.value('sync.local.path'); | ||||
| 		} else if (syncTarget == 'filesystem') { | ||||
| 			let syncDir = Setting.value('sync.filesystem.path'); | ||||
| 			if (!syncDir) syncDir = Setting.value('profileDir') + '/sync'; | ||||
| 			this.vorpal().log(_('Synchronizing with directory "%s"', syncDir)); | ||||
| 			await fs.mkdirp(syncDir, 0o755); | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import { BaseCommand } from './base-command.js'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { Setting } from 'lib/models/setting.js'; | ||||
| import { _ } from 'lib/locale.js'; | ||||
| import { ReportService } from 'lib/services/report.js'; | ||||
|  | ||||
| @@ -14,7 +16,7 @@ class Command extends BaseCommand { | ||||
|  | ||||
| 	async action(args) { | ||||
| 		let service = new ReportService(); | ||||
| 		let report = await service.status(); | ||||
| 		let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target'))); | ||||
|  | ||||
| 		for (let i = 0; i < report.length; i++) { | ||||
| 			let section = report[i]; | ||||
|   | ||||
| @@ -42,7 +42,7 @@ async function createClients() { | ||||
| 	for (let clientId = 0; clientId < 2; clientId++) { | ||||
| 		let client = createClient(clientId); | ||||
| 		promises.push(fs.remove(client.profileDir)); | ||||
| 		promises.push(execCommand(client, 'config sync.target local').then(() => { return execCommand(client, 'config sync.local.path ' + syncDir); })); | ||||
| 		promises.push(execCommand(client, 'config sync.target filesystem').then(() => { return execCommand(client, 'config sync.filesystem.path ' + syncDir); })); | ||||
| 		output.push(client); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ | ||||
|     "url": "https://github.com/laurent22/joplin" | ||||
|   }, | ||||
|   "url": "git://github.com/laurent22/joplin.git", | ||||
|   "version": "0.8.43", | ||||
|   "version": "0.8.44", | ||||
|   "bin": { | ||||
|     "joplin": "./main_launcher.js" | ||||
|   }, | ||||
|   | ||||
| @@ -75,8 +75,6 @@ describe('Synchronizer', function() { | ||||
| 		let note = await Note.save({ title: "un", parent_id: folder.id }); | ||||
| 		await synchronizer().start(); | ||||
|  | ||||
| 		await sleep(0.1); | ||||
|  | ||||
| 		await Note.save({ title: "un UPDATE", id: note.id }); | ||||
|  | ||||
| 		let all = await allItems(); | ||||
|   | ||||
| @@ -90,8 +90,8 @@ android { | ||||
| 		applicationId "net.cozic.joplin" | ||||
| 		minSdkVersion 16 | ||||
| 		targetSdkVersion 22 | ||||
| 		versionCode 16 | ||||
| 		versionName "0.9.3" | ||||
| 		versionCode 18 | ||||
| 		versionName "0.9.5" | ||||
| 		ndk { | ||||
| 			abiFilters "armeabi-v7a", "x86" | ||||
| 		} | ||||
|   | ||||
| @@ -218,6 +218,7 @@ class BaseModel { | ||||
| 		} | ||||
|  | ||||
| 		query.id = modelId; | ||||
| 		query.modObject = o; | ||||
|  | ||||
| 		return query; | ||||
| 	} | ||||
| @@ -241,6 +242,8 @@ class BaseModel { | ||||
| 		return this.db().transactionExecBatch(queries).then(() => { | ||||
| 			o = Object.assign({}, o); | ||||
| 			o.id = modelId; | ||||
| 			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; | ||||
| 			o = this.addModelMd(o); | ||||
| 			return this.filter(o); | ||||
| 		}).catch((error) => { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import { connect } from 'react-redux' | ||||
| import { View, Text, Button, StyleSheet, TouchableOpacity } from 'react-native'; | ||||
| import { View, Text, Button, StyleSheet, TouchableOpacity, Picker } from 'react-native'; | ||||
| import { Log } from 'lib/log.js'; | ||||
| import { Menu, MenuOptions, MenuOption, MenuTrigger } from 'react-native-popup-menu'; | ||||
| import { _ } from 'lib/locale.js'; | ||||
| @@ -150,14 +150,33 @@ class ScreenHeaderComponent extends Component { | ||||
| 				<Text>{_('Status')}</Text> | ||||
| 			</MenuOption>); | ||||
|  | ||||
| 		let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName); | ||||
| 		const createTitleComponent = () => { | ||||
| 			const p = this.props.titlePicker; | ||||
| 			if (p) { | ||||
| 				let items = []; | ||||
| 				for (let i = 0; i < p.items.length; i++) { | ||||
| 					let item = p.items[i]; | ||||
| 					items.push(<Picker.Item label={item.label} value={item.value} key={item.value} />); | ||||
| 				} | ||||
| 				return ( | ||||
| 					<Picker style={{height: 30, flex:1}} selectedValue={p.selectedValue} onValueChange={(itemValue, itemIndex) => { if (p.onValueChange) p.onValueChange(itemValue, itemIndex); }}> | ||||
| 						{ items } | ||||
| 					</Picker> | ||||
| 				); | ||||
| 			} else { | ||||
| 				let title = 'title' in this.props && this.props.title !== null ? this.props.title : _(this.props.navState.routeName); | ||||
| 				return <Text style={{ flex:1, marginLeft: 10 }}>{title}</Text> | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		const titleComp = createTitleComponent(); | ||||
|  | ||||
| 		return ( | ||||
| 			<View style={{ flexDirection: 'row', paddingLeft: 10, paddingTop: 10, paddingBottom: 10, paddingRight: 0, backgroundColor: '#ffffff', alignItems: 'center' }} > | ||||
| 				{ sideMenuButton(styles, () => this.sideMenuButton_press()) } | ||||
| 				{ backButton(styles, () => this.backButton_press(), !this.props.historyCanGoBack) } | ||||
| 				{ saveButton(styles, () => { if (this.props.onSaveButtonPress) this.props.onSaveButtonPress() }, this.props.saveButtonDisabled === true, this.props.showSaveButton === true) } | ||||
| 				<Text style={{ flex:1, marginLeft: 10 }} >{title}</Text> | ||||
| 				{ titleComp }				 | ||||
| 			    <Menu onSelect={(value) => this.menu_select(value)}> | ||||
| 					<MenuTrigger> | ||||
| 						<Text style={{ fontSize: 25 }}>      ⋮  </Text> | ||||
|   | ||||
| @@ -103,8 +103,12 @@ class NoteScreenComponent extends BaseScreenComponent { | ||||
| 		return Folder.load(folderId); | ||||
| 	} | ||||
|  | ||||
| 	async refreshFolder() { | ||||
| 		this.setState({ folder: await this.currentFolder() }); | ||||
| 	async refreshFolder(folderId = null) { | ||||
| 		if (!folderId) { | ||||
| 			this.setState({ folder: await this.currentFolder() }); | ||||
| 		} else { | ||||
| 			this.setState({ folder: await Folder.load(folderId) }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	noteComponent_change(propName, propValue) { | ||||
| @@ -183,23 +187,27 @@ class NoteScreenComponent extends BaseScreenComponent { | ||||
| 		]; | ||||
| 	} | ||||
|  | ||||
| 	async todoCheckbox_change(checked) { | ||||
| 	async saveOneProperty(name, value) { | ||||
| 		let note = Object.assign({}, this.state.note); | ||||
|  | ||||
| 		const todoCompleted = checked ? time.unixMs() : 0; | ||||
|  | ||||
| 		if (note.id) { | ||||
| 			note = await Note.save({ id: note.id, todo_completed: todoCompleted }); | ||||
| 			let toSave = { id: note.id }; | ||||
| 			toSave[name] = value; | ||||
| 			toSave = await Note.save(toSave); | ||||
| 			note[name] = toSave[name]; | ||||
|  | ||||
| 			this.setState({ | ||||
| 				lastSavedNote: Object.assign({}, note), | ||||
| 				note: note, | ||||
| 			}); | ||||
| 		} else { | ||||
| 			note.todo_completed = todoCompleted; | ||||
| 			note[name] = value; | ||||
| 			this.setState({	note: note }); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async todoCheckbox_change(checked) { | ||||
| 		return this.saveOneProperty('todo_completed', checked ? time.unixMs() : 0); | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| @@ -271,9 +279,6 @@ class NoteScreenComponent extends BaseScreenComponent { | ||||
| 			); | ||||
| 		} | ||||
|  | ||||
| 		let headerTitle = '' | ||||
| 		if (folder) headerTitle = folder.title; | ||||
|  | ||||
| 		const renderActionButton = () => { | ||||
| 			let buttons = []; | ||||
|  | ||||
| @@ -290,6 +295,15 @@ class NoteScreenComponent extends BaseScreenComponent { | ||||
| 			return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} /> | ||||
| 		} | ||||
|  | ||||
| 		const titlePickerItems = () => { | ||||
| 			let output = []; | ||||
| 			for (let i = 0; i < this.props.folders.length; i++) { | ||||
| 				let f = this.props.folders[i]; | ||||
| 				output.push({ label: f.title, value: f.id }); | ||||
| 			} | ||||
| 			return output; | ||||
| 		} | ||||
|  | ||||
| 		const actionButtonComp = renderActionButton(); | ||||
|  | ||||
| 		let showSaveButton = this.state.mode == 'edit'; | ||||
| @@ -298,7 +312,23 @@ class NoteScreenComponent extends BaseScreenComponent { | ||||
| 		return ( | ||||
| 			<View style={this.styles().screen}> | ||||
| 				<ScreenHeader | ||||
| 					title={headerTitle} | ||||
| 					titlePicker={{ | ||||
| 						items: titlePickerItems(), | ||||
| 						selectedValue: folder ? folder.id : null, | ||||
| 						onValueChange: async (itemValue, itemIndex) => { | ||||
| 							let note = Object.assign({}, this.state.note); | ||||
| 							if (note.id) await Note.moveToFolder(note.id, itemValue); | ||||
| 							note.parent_id = itemValue; | ||||
|  | ||||
| 							const folder = await Folder.load(note.parent_id); | ||||
|  | ||||
| 							this.setState({ | ||||
| 								lastSavedNote: Object.assign({}, note), | ||||
| 								note: note, | ||||
| 								folder: folder, | ||||
| 							}); | ||||
| 						} | ||||
| 					}} | ||||
| 					navState={this.props.navigation.state} | ||||
| 					menuOptions={this.menuOptions()} | ||||
| 					showSaveButton={showSaveButton} | ||||
| @@ -324,6 +354,7 @@ const NoteScreen = connect( | ||||
| 			noteId: state.selectedNoteId, | ||||
| 			folderId: state.selectedFolderId, | ||||
| 			itemType: state.selectedItemType, | ||||
| 			folders: state.folders, | ||||
| 		}; | ||||
| 	} | ||||
| )(NoteScreenComponent) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import React, { Component } from 'react'; | ||||
| import { ListView, View, Text, Button } from 'react-native'; | ||||
| import { Setting } from 'lib/models/setting.js'; | ||||
| import { connect } from 'react-redux' | ||||
| import { Log } from 'lib/log.js' | ||||
| import { reg } from 'lib/registry.js' | ||||
| @@ -7,6 +8,7 @@ import { ScreenHeader } from 'lib/components/screen-header.js'; | ||||
| import { time } from 'lib/time-utils' | ||||
| import { Logger } from 'lib/logger.js'; | ||||
| import { BaseItem } from 'lib/models/base-item.js'; | ||||
| import { Database } from 'lib/database.js'; | ||||
| import { Folder } from 'lib/models/folder.js'; | ||||
| import { ReportService } from 'lib/services/report.js'; | ||||
| import { _ } from 'lib/locale.js'; | ||||
| @@ -31,7 +33,7 @@ class StatusScreenComponent extends BaseScreenComponent { | ||||
|  | ||||
| 	async resfreshScreen() { | ||||
| 		let service = new ReportService(); | ||||
| 		let report = await service.status(); | ||||
| 		let report = await service.status(Database.enumId('syncTarget', Setting.value('sync.target'))); | ||||
| 		this.setState({ report: report }); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -151,7 +151,7 @@ class Database { | ||||
| 		} | ||||
| 		if (type == 'syncTarget') { | ||||
| 			if (s == 'memory') return 1; | ||||
| 			if (s == 'file') return 2; | ||||
| 			if (s == 'filesystem') return 2; | ||||
| 			if (s == 'onedrive') return 3; | ||||
| 		} | ||||
| 		throw new Error('Unknown enum type or value: ' + type + ', ' + s); | ||||
| @@ -160,7 +160,7 @@ class Database { | ||||
| 	static enumName(type, id) { | ||||
| 		if (type == 'syncTarget') { | ||||
| 			if (id === 1) return 'memory'; | ||||
| 			if (id === 2) return 'file'; | ||||
| 			if (id === 2) return 'filesystem'; | ||||
| 			if (id === 3) return 'onedrive'; | ||||
| 		} | ||||
| 		throw new Error('Unknown enum type or id: ' + type + ', ' + id); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ class FileApiDriverLocal { | ||||
| 	} | ||||
|  | ||||
| 	syncTargetName() { | ||||
| 		return 'file'; | ||||
| 		return 'filesystem'; | ||||
| 	} | ||||
|  | ||||
| 	fsErrorToJsError_(error) { | ||||
|   | ||||
| @@ -192,6 +192,17 @@ class JoplinDatabase extends Database { | ||||
|  | ||||
| 		for (let initLoopCount = 1; initLoopCount <= 2; initLoopCount++) { | ||||
| 			try { | ||||
| 				// await this.exec('DROP TABLE folders'); | ||||
| 				// await this.exec('DROP TABLE notes'); | ||||
| 				// await this.exec('DROP TABLE deleted_items'); | ||||
| 				// await this.exec('DROP TABLE tags'); | ||||
| 				// await this.exec('DROP TABLE note_tags'); | ||||
| 				// await this.exec('DROP TABLE resources'); | ||||
| 				// await this.exec('DROP TABLE settings'); | ||||
| 				// await this.exec('DROP TABLE table_fields'); | ||||
| 				// await this.exec('DROP TABLE version'); | ||||
| 				// await this.exec('DROP TABLE sync_items'); | ||||
|  | ||||
| 				let row = await this.selectOne('SELECT * FROM version LIMIT 1'); | ||||
| 				this.logger().info('Current database version', row); | ||||
|  | ||||
|   | ||||
| @@ -32,14 +32,14 @@ class BaseItem extends BaseModel { | ||||
| 		throw new Error('Invalid class name: ' + name); | ||||
| 	} | ||||
|  | ||||
| 	static async syncedCount() { | ||||
| 		// TODO | ||||
| 		return 0; | ||||
| 		// const ItemClass = this.itemClass(this.modelType()); | ||||
| 		// let sql = 'SELECT count(*) as total FROM `' + ItemClass.tableName() + '` WHERE updated_time <= sync_time'; | ||||
| 		// if (this.modelType() == BaseModel.TYPE_NOTE) sql += ' AND is_conflict = 0'; | ||||
| 		// const r = await this.db().selectOne(sql); | ||||
| 		// return r.total; | ||||
| 	static async syncedCount(syncTarget) { | ||||
| 		const ItemClass = this.itemClass(this.modelType()); | ||||
| 		const itemType = ItemClass.modelType(); | ||||
| 		// The fact that we don't check if the item_id still exist in the corresponding item table, means | ||||
| 		// that the returned number might be innaccurate (for example if a sync operation was cancelled) | ||||
| 		const sql = 'SELECT count(*) as total FROM sync_items WHERE sync_target = ? AND item_type = ?'; | ||||
| 		const r = await this.db().selectOne(sql, [ syncTarget, itemType ]); | ||||
| 		return r.total; | ||||
| 	} | ||||
|  | ||||
| 	static systemPath(itemOrId) { | ||||
| @@ -279,23 +279,6 @@ class BaseItem extends BaseModel { | ||||
| 		} | ||||
|  | ||||
| 		throw new Error('Unreachable'); | ||||
|  | ||||
| 		//return this.modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit); | ||||
|  | ||||
| 		// let items = await this.getClass('Folder').modelSelectAll('SELECT * FROM folders WHERE sync_time < updated_time LIMIT ' + limit); | ||||
| 		// if (items.length) return { hasMore: true, items: items }; | ||||
|  | ||||
| 		// items = await this.getClass('Resource').modelSelectAll('SELECT * FROM resources WHERE sync_time < updated_time LIMIT ' + limit); | ||||
| 		// if (items.length) return { hasMore: true, items: items }; | ||||
|  | ||||
| 		// items = await this.getClass('Note').modelSelectAll('SELECT * FROM notes WHERE sync_time < updated_time AND is_conflict = 0 LIMIT ' + limit); | ||||
| 		// if (items.length) return { hasMore: true, items: items }; | ||||
|  | ||||
| 		// items = await this.getClass('Tag').modelSelectAll('SELECT * FROM tags WHERE sync_time < updated_time LIMIT ' + limit); | ||||
| 		// if (items.length) return { hasMore: true, items: items }; | ||||
|  | ||||
| 		// items = await this.getClass('NoteTag').modelSelectAll('SELECT * FROM note_tags WHERE sync_time < updated_time LIMIT ' + limit); | ||||
| 		// return { hasMore: items.length >= limit, items: items }; | ||||
| 	} | ||||
|  | ||||
| 	static syncItemClassNames() { | ||||
| @@ -341,7 +324,10 @@ class BaseItem extends BaseModel { | ||||
| 			const className = classNames[i]; | ||||
| 			const ItemClass = this.getClass(className); | ||||
|  | ||||
| 			queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (SELECT id FROM ' + ItemClass.tableName() + ')'); | ||||
| 			let selectSql = 'SELECT id FROM ' + ItemClass.tableName(); | ||||
| 			if (ItemClass.modelType() == this.TYPE_NOTE) selectSql += ' WHERE is_conflict = 0'; | ||||
|  | ||||
| 			queries.push('DELETE FROM sync_items WHERE item_type = ' + ItemClass.modelType() + ' AND item_id NOT IN (' + selectSql + ')'); | ||||
| 		} | ||||
|  | ||||
| 		await this.db().transactionExecBatch(queries); | ||||
|   | ||||
| @@ -146,7 +146,7 @@ class Setting extends BaseModel { | ||||
| Setting.defaults_ = { | ||||
| 	'activeFolderId': { value: '', type: 'string', public: false }, | ||||
| 	'sync.onedrive.auth': { value: '', type: 'string', public: false }, | ||||
| 	'sync.local.path': { value: '', type: 'string', public: true },	 | ||||
| 	'sync.filesystem.path': { value: '', type: 'string', public: true },	 | ||||
| 	'sync.target': { value: 'onedrive', type: 'string', public: true }, | ||||
| 	'editor': { value: '', type: 'string', public: true }, | ||||
| }; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import { _ } from 'lib/locale.js'; | ||||
|  | ||||
| class ReportService { | ||||
|  | ||||
| 	async syncStatus() { | ||||
| 	async syncStatus(syncTarget) { | ||||
| 		let output = { | ||||
| 			items: {}, | ||||
| 			total: {}, | ||||
| @@ -19,8 +19,7 @@ class ReportService { | ||||
| 			let ItemClass = BaseItem.getClass(d.className); | ||||
| 			let o = { | ||||
| 				total: await ItemClass.count(), | ||||
| 				// synced: await ItemClass.syncedCount(), // TODO | ||||
| 				synced: 0, | ||||
| 				synced: await ItemClass.syncedCount(syncTarget), | ||||
| 			}; | ||||
| 			output.items[d.className] = o; | ||||
| 			itemCount += o.total; | ||||
| @@ -47,8 +46,8 @@ class ReportService { | ||||
| 		return output; | ||||
| 	} | ||||
|  | ||||
| 	async status() { | ||||
| 		let r = await this.syncStatus(); | ||||
| 	async status(syncTarget) { | ||||
| 		let r = await this.syncStatus(syncTarget); | ||||
| 		let sections = []; | ||||
| 		let section = {}; | ||||
|  | ||||
|   | ||||
| @@ -68,6 +68,17 @@ function historyCanGoBackTo(route) { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| function reducerActionsAreSame(a1, a2) { | ||||
| 	if (Object.getOwnPropertyNames(a1).length !== Object.getOwnPropertyNames(a2).length) return false; | ||||
|  | ||||
| 	for (let n in a1) { | ||||
| 		if (!a1.hasOwnProperty(n)) continue; | ||||
| 		if (a1[n] !== a2[n]) return false; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| const reducer = (state = defaultState, action) => { | ||||
| 	reg.logger().info('Reducer action', action.type); | ||||
|  | ||||
| @@ -116,10 +127,16 @@ const reducer = (state = defaultState, action) => { | ||||
| 					newState.selectedItemType = action.itemType; | ||||
| 				} | ||||
|  | ||||
| 				newState.route = action; | ||||
|  | ||||
| 				// If the route *name* is the same (even if the other parameters are different), we | ||||
| 				// overwrite the last route in the history with the current one. If the route name | ||||
| 				// is different, we push a new history entry. | ||||
|  | ||||
| 				if (currentRouteName == action.routeName) { | ||||
| 					if (navHistory.length) navHistory[navHistory.length - 1] = action; | ||||
| 					// If the current screen is already the requested screen, don't do anything | ||||
| 				} else { | ||||
| 					newState.route = action; | ||||
| 					if (action.routeName == 'Welcome') navHistory = []; | ||||
| 					navHistory.push(action); | ||||
| 				} | ||||
| @@ -151,20 +168,27 @@ const reducer = (state = defaultState, action) => { | ||||
| 			// update it within the note array if it already exists. | ||||
| 			case 'NOTES_UPDATE_ONE': | ||||
|  | ||||
| 				if (action.note.parent_id != state.selectedFolderId) break; | ||||
| 				const modNote = action.note; | ||||
|  | ||||
| 				let newNotes = state.notes.splice(0); | ||||
| 				var found = false; | ||||
| 				for (let i = 0; i < newNotes.length; i++) { | ||||
| 					let n = newNotes[i]; | ||||
| 					if (n.id == action.note.id) { | ||||
| 						newNotes[i] = Object.assign(newNotes[i], action.note); | ||||
| 					if (n.id == modNote.id) { | ||||
|  | ||||
| 						if (!('parent_id' in modNote) || modNote.parent_id == n.parent_id) { | ||||
| 							// Merge the properties that have changed (in modNote) into | ||||
| 							// the object we already have. | ||||
| 							newNotes[i] = Object.assign(newNotes[i], action.note); | ||||
| 						} else { | ||||
| 							newNotes.splice(i, 1); | ||||
| 						} | ||||
| 						found = true; | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				if (!found) newNotes.push(action.note); | ||||
| 				if (!found && ('parent_id' in modNote) && modNote.parent_id == state.selectedFolderId) newNotes.push(modNote); | ||||
|  | ||||
| 				newNotes = Note.sortNotes(newNotes, state.notesOrder); | ||||
| 				newState = Object.assign({}, state); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user