diff --git a/ElectronClient/app/gui/MainScreen.jsx b/ElectronClient/app/gui/MainScreen.jsx index e83f48baa81..a98b2689ce4 100644 --- a/ElectronClient/app/gui/MainScreen.jsx +++ b/ElectronClient/app/gui/MainScreen.jsx @@ -16,6 +16,7 @@ const { _ } = require('lib/locale.js'); const layoutUtils = require('lib/layout-utils.js'); const { bridge } = require('electron').remote.require('./bridge'); const eventManager = require('../eventManager'); +const { NoteCountUtils } = require('lib/note-count-utils.js'); class MainScreenComponent extends React.Component { @@ -90,6 +91,7 @@ class MainScreenComponent extends React.Component { let folder = null; try { folder = await Folder.save({ title: answer }, { userSideValidation: true }); + await NoteCountUtils.refreshNotesCount(); } catch (error) { bridge().showErrorMessageBox(error.message); } diff --git a/ElectronClient/app/gui/NoteList.jsx b/ElectronClient/app/gui/NoteList.jsx index 93611b733ee..65dc80857a1 100644 --- a/ElectronClient/app/gui/NoteList.jsx +++ b/ElectronClient/app/gui/NoteList.jsx @@ -13,7 +13,7 @@ const InteropService = require('lib/services/InteropService'); const InteropServiceHelper = require('../InteropServiceHelper.js'); const Search = require('lib/models/Search'); const Mark = require('mark.js/dist/mark.min.js'); - +const { NoteCountUtils } = require('lib/note-count-utils.js'); class NoteListComponent extends React.Component { style() { @@ -126,8 +126,22 @@ class NoteListComponent extends React.Component { menu.append(new MenuItem({label: _('Delete'), click: async () => { const ok = bridge().showConfirmMessageBox(noteIds.length > 1 ? _('Delete notes?') : _('Delete note?')); - if (!ok) return; - await Note.batchDelete(noteIds); + if (!ok) return; + await Note.batchDelete(noteIds); + if(this.props.notesParentType === 'Tag' || this.props.notesParentType === 'Search'){ + await NoteCountUtils.refreshNotesCount(); + } else if(this.props.notesParentType === 'Folder'){ + let tempProps = Object.assign({},this.props); + let notesCount = []; + for(let key in tempProps.notesCount){ + notesCount[key] = tempProps.notesCount[key]; + } + notesCount[this.props.selectedFolderId] = +notesCount[this.props.selectedFolderId] - noteIds.length; + this.props.dispatch({ + type:'FOLDER_COUNT_UPDATE_ALL', + notesCount: notesCount + }) + } }})); menu.popup(bridge().window()); @@ -281,7 +295,9 @@ const mapStateToProps = (state) => { theme: state.settings.theme, notesParentType: state.notesParentType, searches: state.searches, - selectedSearchId: state.selectedSearchId, + selectedSearchId: state.selectedSearchId, + selectedFolderId: state.selectedFolderId, + notesCount: state.notesCount }; }; diff --git a/ElectronClient/app/gui/SideBar.jsx b/ElectronClient/app/gui/SideBar.jsx index 127166ec0a6..5de8bb0cff6 100644 --- a/ElectronClient/app/gui/SideBar.jsx +++ b/ElectronClient/app/gui/SideBar.jsx @@ -22,7 +22,7 @@ class SideBarComponent extends React.Component { this.onFolderDragStart_ = (event) => { const folderId = event.currentTarget.getAttribute('folderid'); if (!folderId) return; - + event.dataTransfer.setDragImage(new Image(), 1, 1); event.dataTransfer.clearData(); event.dataTransfer.setData('text/x-jop-folder-ids', JSON.stringify([folderId])); @@ -250,7 +250,7 @@ class SideBarComponent extends React.Component { ); } - if (itemType === BaseModel.TYPE_TAG) { + if (itemType === BaseModel.TYPE_TAG) { menu.append( new MenuItem({ label: _('Rename'), @@ -313,10 +313,15 @@ class SideBarComponent extends React.Component { const iconName = this.props.collapsedFolderIds.indexOf(folder.id) >= 0 ? 'fa-plus-square' : 'fa-minus-square'; const expandIcon = const expandLink = hasChildren ? {expandIcon} : {expandIcon} - + let count = '0'; + if (this.props.notesCount[folder.id] !== 'undefined' + && this.props.notesCount[folder.id] !== undefined) { + count = this.props.notesCount[folder.id]; + } return ( +
- { expandLink } + {expandLink} - {itemTitle} + {itemTitle + ' (' + count + ')'}
); @@ -485,6 +490,7 @@ const mapStateToProps = state => { locale: state.settings.locale, theme: state.settings.theme, collapsedFolderIds: state.collapsedFolderIds, + notesCount: state.notesCount }; }; diff --git a/ReactNativeClient/lib/BaseApplication.js b/ReactNativeClient/lib/BaseApplication.js index b5b22e94c70..8aa1f0d1c39 100644 --- a/ReactNativeClient/lib/BaseApplication.js +++ b/ReactNativeClient/lib/BaseApplication.js @@ -3,6 +3,7 @@ const { reducer, defaultState, stateUtils } = require('lib/reducer.js'); const { JoplinDatabase } = require('lib/joplin-database.js'); const { Database } = require('lib/database.js'); const { FoldersScreenUtils } = require('lib/folders-screen-utils.js'); +const { NoteCountUtils } = require('lib/note-count-utils.js'); const { DatabaseDriverNode } = require('lib/database-driver-node.js'); const BaseModel = require('lib/BaseModel.js'); const Folder = require('lib/models/Folder.js'); @@ -35,6 +36,7 @@ const EncryptionService = require('lib/services/EncryptionService'); const DecryptionWorker = require('lib/services/DecryptionWorker'); const BaseService = require('lib/services/BaseService'); + SyncTargetRegistry.addClass(SyncTargetFilesystem); SyncTargetRegistry.addClass(SyncTargetOneDrive); SyncTargetRegistry.addClass(SyncTargetOneDriveDev); @@ -356,6 +358,7 @@ class BaseApplication { this.store_ = createStore(this.reducer, applyMiddleware(this.generalMiddlewareFn())); BaseModel.dispatch = this.store().dispatch; FoldersScreenUtils.dispatch = this.store().dispatch; + NoteCountUtils.dispatch = this.store().dispatch; reg.dispatch = this.store().dispatch; BaseSyncTarget.dispatch = this.store().dispatch; DecryptionWorker.instance().dispatch = this.store().dispatch; diff --git a/ReactNativeClient/lib/folders-screen-utils.js b/ReactNativeClient/lib/folders-screen-utils.js index 1a6971da471..f73e2ffdf75 100644 --- a/ReactNativeClient/lib/folders-screen-utils.js +++ b/ReactNativeClient/lib/folders-screen-utils.js @@ -3,11 +3,18 @@ const Folder = require('lib/models/Folder.js'); class FoldersScreenUtils { static async refreshFolders() { - let initialFolders = await Folder.all({ includeConflictFolder: true }); - + let initialFolders = await Folder.all({ includeConflictFolder: true }); + let allFolderCountData = await Folder.allFolderNoteCount(); + let notesCount = []; + for (let n = 0; n < allFolderCountData.length; n++) { + let mFolder = allFolderCountData[n]; + let searchedFolder = allFolderCountData.find(folder => folder.parent_id === mFolder.id); + notesCount[mFolder.id] = (searchedFolder != undefined && searchedFolder != 'undefined') ? searchedFolder.total : 0; + } this.dispatch({ type: 'FOLDER_UPDATE_ALL', items: initialFolders, + notesCount: notesCount }); } diff --git a/ReactNativeClient/lib/models/Folder.js b/ReactNativeClient/lib/models/Folder.js index 4d24e9405dc..775e19295d7 100644 --- a/ReactNativeClient/lib/models/Folder.js +++ b/ReactNativeClient/lib/models/Folder.js @@ -65,7 +65,12 @@ class Folder extends BaseItem { static async noteCount(parentId) { let r = await this.db().selectOne('SELECT count(*) as total FROM notes WHERE is_conflict = 0 AND parent_id = ?', [parentId]); return r ? r.total : 0; - } + } + + static async allFolderNoteCount() { + let rows = await this.db().selectAll('SELECT count(*) as total, parent_id FROM notes WHERE is_conflict = 0 GROUP BY parent_id', []); + return rows; + } static markNotesAsConflict(parentId) { let query = Database.updateQuery('notes', { is_conflict: 1 }, { parent_id: parentId }); diff --git a/ReactNativeClient/lib/note-count-utils.js b/ReactNativeClient/lib/note-count-utils.js new file mode 100644 index 00000000000..2b8ed164e23 --- /dev/null +++ b/ReactNativeClient/lib/note-count-utils.js @@ -0,0 +1,22 @@ +const Folder = require('lib/models/Folder.js'); + +class NoteCountUtils { + static async refreshNotesCount() { + + let allFolderCountData = await Folder.allFolderNoteCount(); + let notesCount = []; + for (let n = 0; n < allFolderCountData.length; n++) { + let mFolder = allFolderCountData[n]; + let searchedFolder = allFolderCountData.find(folder => folder.parent_id === mFolder.id); + notesCount[mFolder.id] = (searchedFolder != undefined && searchedFolder != 'undefined') ? searchedFolder.total : 0; + } + this.dispatch({ + type: 'FOLDER_COUNT_UPDATE_ALL', + notesCount: notesCount + }); + } +} + +module.exports = { + NoteCountUtils +}; \ No newline at end of file diff --git a/ReactNativeClient/lib/reducer.js b/ReactNativeClient/lib/reducer.js index e3c40679020..3ca8b1907b9 100644 --- a/ReactNativeClient/lib/reducer.js +++ b/ReactNativeClient/lib/reducer.js @@ -1,7 +1,7 @@ const Note = require('lib/models/Note.js'); const Folder = require('lib/models/Folder.js'); const ArrayUtils = require('lib/ArrayUtils.js'); - +const { NoteCountUtils } = require('lib/note-count-utils.js'); const defaultState = { notes: [], notesSource: '', @@ -31,11 +31,12 @@ const defaultState = { startState: 'idle', port: null, }, + notesCount: [], }; const stateUtils = {}; -stateUtils.notesOrder = function(stateSettings) { +stateUtils.notesOrder = function (stateSettings) { return [{ by: stateSettings['notes.sortOrder.field'], dir: stateSettings['notes.sortOrder.reverse'] ? 'DESC' : 'ASC', @@ -294,7 +295,7 @@ const reducer = (state = defaultState, action) => { const modNote = action.note; - const noteIsInFolder = function(note, folderId) { + const noteIsInFolder = function (note, folderId) { if (note.is_conflict) return folderId === Folder.conflictFolderId(); if (!('parent_id' in modNote) || note.parent_id == folderId) return true; return false; @@ -343,6 +344,7 @@ const reducer = (state = defaultState, action) => { if (noteFolderHasChanged) { newState.selectedNoteIds = newNotes.length ? [newNotes[0].id] : []; } + NoteCountUtils.refreshNotesCount(); break; case 'NOTE_DELETE': @@ -359,6 +361,7 @@ const reducer = (state = defaultState, action) => { newState = Object.assign({}, state); newState.folders = action.items; + newState.notesCount = action.notesCount; break; case 'FOLDER_SET_COLLAPSED': @@ -500,7 +503,7 @@ const reducer = (state = defaultState, action) => { case 'SEARCH_DELETE': newState = handleItemDelete(state, action); - break; + break; case 'SEARCH_SELECT': @@ -538,8 +541,12 @@ const reducer = (state = defaultState, action) => { if ('startState' in action) clipperServer.startState = action.startState; if ('port' in action) clipperServer.port = action.port; newState.clipperServer = clipperServer; - break; + break; + case 'FOLDER_COUNT_UPDATE_ALL': + newState = Object.assign({}, state); + newState.notesCount = action.notesCount; + break; } } catch (error) { error.message = 'In reducer: ' + error.message + ' Action: ' + JSON.stringify(action);