mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
* Implement "show all notes" feature. * Ensure middleware is completely flushed and shutdown before continuing tests.
This commit is contained in:
parent
0f28060795
commit
fbedc6b29b
@ -19,7 +19,10 @@ module.exports = {
|
|||||||
'expect': 'readonly',
|
'expect': 'readonly',
|
||||||
'describe': 'readonly',
|
'describe': 'readonly',
|
||||||
'it': 'readonly',
|
'it': 'readonly',
|
||||||
|
'beforeAll': 'readonly',
|
||||||
|
'afterAll': 'readonly',
|
||||||
'beforeEach': 'readonly',
|
'beforeEach': 'readonly',
|
||||||
|
'afterEach': 'readonly',
|
||||||
'jasmine': 'readonly',
|
'jasmine': 'readonly',
|
||||||
|
|
||||||
// React Native variables
|
// React Native variables
|
||||||
|
117
CliClient/tests/integration_SmartFilters.js
Normal file
117
CliClient/tests/integration_SmartFilters.js
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
require('app-module-path').addPath(__dirname);
|
||||||
|
const { setupDatabaseAndSynchronizer, switchClient, asyncTest, TestApp } = require('test-utils.js');
|
||||||
|
const Setting = require('lib/models/Setting.js');
|
||||||
|
const Folder = require('lib/models/Folder.js');
|
||||||
|
const Note = require('lib/models/Note.js');
|
||||||
|
const Tag = require('lib/models/Tag.js');
|
||||||
|
const { time } = require('lib/time-utils.js');
|
||||||
|
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids.js');
|
||||||
|
|
||||||
|
//
|
||||||
|
// The integration tests are to test the integration of the core system, comprising the
|
||||||
|
// base application with middleware, reducer and models in response to dispatched events.
|
||||||
|
//
|
||||||
|
// The general strategy for each integration test is:
|
||||||
|
// - create a starting application state,
|
||||||
|
// - inject the event to be tested
|
||||||
|
// - check the resulting application state
|
||||||
|
//
|
||||||
|
// In particular, this file contains integration tests for smart filter features.
|
||||||
|
//
|
||||||
|
|
||||||
|
async function createNTestFolders(n) {
|
||||||
|
let folders = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let folder = await Folder.save({ title: 'folder' });
|
||||||
|
folders.push(folder);
|
||||||
|
}
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNTestNotes(n, folder) {
|
||||||
|
let notes = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let note = await Note.save({ title: 'note', parent_id: folder.id, is_conflict: 0 });
|
||||||
|
notes.push(note);
|
||||||
|
}
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNTestTags(n) {
|
||||||
|
let tags = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
let tag = await Tag.save({ title: 'tag' });
|
||||||
|
tags.push(tag);
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use this until Javascript arr.flat() function works in Travis
|
||||||
|
function flatten(arr) {
|
||||||
|
return (arr.reduce((acc, val) => acc.concat(val), []));
|
||||||
|
}
|
||||||
|
|
||||||
|
let testApp = null;
|
||||||
|
|
||||||
|
describe('integration_SmartFilters', function() {
|
||||||
|
|
||||||
|
beforeEach(async (done) => {
|
||||||
|
testApp = new TestApp();
|
||||||
|
await testApp.start(['--no-welcome']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async (done) => {
|
||||||
|
if (testApp !== null) await testApp.destroy();
|
||||||
|
testApp = null;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show notes in a folder', asyncTest(async () => {
|
||||||
|
let folders = await createNTestFolders(2);
|
||||||
|
let notes = [];
|
||||||
|
for (let i = 0; i < folders.length; i++) {
|
||||||
|
notes.push(await createNTestNotes(3, folders[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
testApp.dispatch({
|
||||||
|
type: 'FOLDER_SELECT',
|
||||||
|
id: folders[1].id,
|
||||||
|
});
|
||||||
|
await time.msleep(100);
|
||||||
|
|
||||||
|
let state = testApp.store().getState();
|
||||||
|
|
||||||
|
expect(state.notesParentType).toEqual('Folder');
|
||||||
|
expect(state.selectedFolderId).toEqual(folders[1].id);
|
||||||
|
|
||||||
|
let expectedNoteIds = notes[1].map(n => n.id).sort();
|
||||||
|
let noteIds = state.notes.map(n => n.id).sort();
|
||||||
|
expect(noteIds).toEqual(expectedNoteIds);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should show all notes', asyncTest(async () => {
|
||||||
|
let folders = await createNTestFolders(2);
|
||||||
|
let notes = [];
|
||||||
|
for (let i = 0; i < folders.length; i++) {
|
||||||
|
notes.push(await createNTestNotes(3, folders[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
testApp.dispatch({
|
||||||
|
type: 'SMART_FILTER_SELECT',
|
||||||
|
id: ALL_NOTES_FILTER_ID,
|
||||||
|
});
|
||||||
|
await time.msleep(100);
|
||||||
|
|
||||||
|
let state = testApp.store().getState();
|
||||||
|
|
||||||
|
expect(state.notesParentType).toEqual('SmartFilter');
|
||||||
|
expect(state.selectedSmartFilterId).toEqual(ALL_NOTES_FILTER_ID);
|
||||||
|
|
||||||
|
// let expectedNoteIds = notes.map(n => n.map(o => o.id)).flat().sort();
|
||||||
|
let expectedNoteIds = flatten(notes.map(n => n.map(o => o.id))).sort();
|
||||||
|
let noteIds = state.notes.map(n => n.id).sort();
|
||||||
|
expect(noteIds).toEqual(expectedNoteIds);
|
||||||
|
}));
|
||||||
|
});
|
@ -3,6 +3,7 @@
|
|||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
const { JoplinDatabase } = require('lib/joplin-database.js');
|
const { JoplinDatabase } = require('lib/joplin-database.js');
|
||||||
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
const { DatabaseDriverNode } = require('lib/database-driver-node.js');
|
||||||
|
const { BaseApplication }= require('lib/BaseApplication.js');
|
||||||
const BaseModel = require('lib/BaseModel.js');
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
const Folder = require('lib/models/Folder.js');
|
const Folder = require('lib/models/Folder.js');
|
||||||
const Note = require('lib/models/Note.js');
|
const Note = require('lib/models/Note.js');
|
||||||
@ -412,4 +413,49 @@ async function allSyncTargetItemsEncrypted() {
|
|||||||
return totalCount === encryptedCount;
|
return totalCount === encryptedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { kvStore, resourceService, allSyncTargetItemsEncrypted, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest };
|
class TestApp extends BaseApplication {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.middlewareCalls_ = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(argv) {
|
||||||
|
argv = await super.start(argv);
|
||||||
|
this.initRedux();
|
||||||
|
await setupDatabaseAndSynchronizer(1);
|
||||||
|
await switchClient(1);
|
||||||
|
Setting.dispatchUpdateAll();
|
||||||
|
await time.msleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generalMiddleware(store, next, action) {
|
||||||
|
this.middlewareCalls_.push(true);
|
||||||
|
try {
|
||||||
|
await super.generalMiddleware(store, next, action);
|
||||||
|
} finally {
|
||||||
|
this.middlewareCalls_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForMiddleware_() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const iid = setInterval(() => {
|
||||||
|
if (!this.middlewareCalls_.length) {
|
||||||
|
clearInterval(iid);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy() {
|
||||||
|
this.deinitRedux();
|
||||||
|
await this.waitForMiddleware_();
|
||||||
|
await super.destroy();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { kvStore, resourceService, allSyncTargetItemsEncrypted, setupDatabase, revisionService, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync, encryptionService, loadEncryptionMasterKey, fileContentEqual, decryptionWorker, asyncTest, TestApp };
|
||||||
|
|
||||||
|
@ -1621,7 +1621,7 @@ class NoteTextComponent extends React.Component {
|
|||||||
|
|
||||||
createToolbarItems(note, editorIsVisible) {
|
createToolbarItems(note, editorIsVisible) {
|
||||||
const toolbarItems = [];
|
const toolbarItems = [];
|
||||||
if (note && this.state.folder && ['Search', 'Tag'].includes(this.props.notesParentType)) {
|
if (note && this.state.folder && ['Search', 'Tag', 'SmartFilter'].includes(this.props.notesParentType)) {
|
||||||
toolbarItems.push({
|
toolbarItems.push({
|
||||||
title: _('In: %s', substrWithEllipsis(this.state.folder.title, 0, 16)),
|
title: _('In: %s', substrWithEllipsis(this.state.folder.title, 0, 16)),
|
||||||
iconName: 'fa-book',
|
iconName: 'fa-book',
|
||||||
|
@ -14,6 +14,7 @@ const Menu = bridge().Menu;
|
|||||||
const MenuItem = bridge().MenuItem;
|
const MenuItem = bridge().MenuItem;
|
||||||
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
const InteropServiceHelper = require('../InteropServiceHelper.js');
|
||||||
const { substrWithEllipsis } = require('lib/string-utils');
|
const { substrWithEllipsis } = require('lib/string-utils');
|
||||||
|
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||||
|
|
||||||
class SideBarComponent extends React.Component {
|
class SideBarComponent extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -89,6 +90,7 @@ class SideBarComponent extends React.Component {
|
|||||||
this.tagItemsOrder_ = [];
|
this.tagItemsOrder_ = [];
|
||||||
|
|
||||||
this.onKeyDown = this.onKeyDown.bind(this);
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
|
this.onAllNotesClick_ = this.onAllNotesClick_.bind(this);
|
||||||
|
|
||||||
this.rootRef = React.createRef();
|
this.rootRef = React.createRef();
|
||||||
|
|
||||||
@ -570,6 +572,9 @@ class SideBarComponent extends React.Component {
|
|||||||
let isExpanded = this.state[toggleKey];
|
let isExpanded = this.state[toggleKey];
|
||||||
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75, marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125 }}></i>;
|
toggleIcon = <i className={`fa ${isExpanded ? 'fa-chevron-down' : 'fa-chevron-left'}`} style={{ fontSize: style.fontSize * 0.75, marginRight: 12, marginLeft: 5, marginTop: style.fontSize * 0.125 }}></i>;
|
||||||
}
|
}
|
||||||
|
if (extraProps.selected) {
|
||||||
|
style.backgroundColor =this.style().listItemSelected.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
const ref = this.anchorItemRef('headers', key);
|
const ref = this.anchorItemRef('headers', key);
|
||||||
|
|
||||||
@ -696,6 +701,13 @@ class SideBarComponent extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAllNotesClick_() {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'SMART_FILTER_SELECT',
|
||||||
|
id: ALL_NOTES_FILTER_ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
synchronizeButton(type) {
|
synchronizeButton(type) {
|
||||||
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
|
const style = Object.assign({}, this.style().button, { marginBottom: 5 });
|
||||||
const iconName = 'fa-refresh';
|
const iconName = 'fa-refresh';
|
||||||
@ -732,6 +744,13 @@ class SideBarComponent extends React.Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let items = [];
|
let items = [];
|
||||||
|
items.push(
|
||||||
|
this.makeHeader('allNotesHeader', _('All notes'), 'fa-clone', {
|
||||||
|
onClick: this.onAllNotesClick_,
|
||||||
|
selected: this.props.notesParentType === 'SmartFilter' && this.props.selectedSmartFilterId === ALL_NOTES_FILTER_ID,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
|
this.makeHeader('folderHeader', _('Notebooks'), 'fa-book', {
|
||||||
onDrop: this.onFolderDrop_,
|
onDrop: this.onFolderDrop_,
|
||||||
@ -821,6 +840,7 @@ const mapStateToProps = state => {
|
|||||||
selectedFolderId: state.selectedFolderId,
|
selectedFolderId: state.selectedFolderId,
|
||||||
selectedTagId: state.selectedTagId,
|
selectedTagId: state.selectedTagId,
|
||||||
selectedSearchId: state.selectedSearchId,
|
selectedSearchId: state.selectedSearchId,
|
||||||
|
selectedSmartFilterId: state.selectedSmartFilterId,
|
||||||
notesParentType: state.notesParentType,
|
notesParentType: state.notesParentType,
|
||||||
locale: state.settings.locale,
|
locale: state.settings.locale,
|
||||||
theme: state.settings.theme,
|
theme: state.settings.theme,
|
||||||
|
@ -54,6 +54,18 @@ class BaseApplication {
|
|||||||
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
|
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = this.decryptionWorker_resourceMetadataButNotBlobDecrypted.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async destroy() {
|
||||||
|
await FoldersScreenUtils.cancelTimers();
|
||||||
|
await SearchEngine.instance().cancelTimers();
|
||||||
|
await DecryptionWorker.instance().cancelTimers();
|
||||||
|
await reg.cancelTimers();
|
||||||
|
|
||||||
|
this.logger_ = null;
|
||||||
|
this.dbLogger_ = null;
|
||||||
|
this.eventEmitter_ = null;
|
||||||
|
this.decryptionWorker_resourceMetadataButNotBlobDecrypted = null;
|
||||||
|
}
|
||||||
|
|
||||||
logger() {
|
logger() {
|
||||||
return this.logger_;
|
return this.logger_;
|
||||||
}
|
}
|
||||||
@ -217,6 +229,9 @@ class BaseApplication {
|
|||||||
} else if (parentType === 'Search') {
|
} else if (parentType === 'Search') {
|
||||||
parentId = state.selectedSearchId;
|
parentId = state.selectedSearchId;
|
||||||
parentType = BaseModel.TYPE_SEARCH;
|
parentType = BaseModel.TYPE_SEARCH;
|
||||||
|
} else if (parentType === 'SmartFilter') {
|
||||||
|
parentId = state.selectedSmartFilterId;
|
||||||
|
parentType = BaseModel.TYPE_SMART_FILTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger().debug('Refreshing notes:', parentType, parentId);
|
this.logger().debug('Refreshing notes:', parentType, parentId);
|
||||||
@ -243,6 +258,8 @@ class BaseApplication {
|
|||||||
} else if (parentType === BaseModel.TYPE_SEARCH) {
|
} else if (parentType === BaseModel.TYPE_SEARCH) {
|
||||||
const search = BaseModel.byId(state.searches, parentId);
|
const search = BaseModel.byId(state.searches, parentId);
|
||||||
notes = await SearchEngineUtils.notesForQuery(search.query_pattern);
|
notes = await SearchEngineUtils.notesForQuery(search.query_pattern);
|
||||||
|
} else if (parentType === BaseModel.TYPE_SMART_FILTER) {
|
||||||
|
notes = await Note.previews(parentId, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,6 +452,11 @@ class BaseApplication {
|
|||||||
refreshNotes = true;
|
refreshNotes = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action.type == 'SMART_FILTER_SELECT') {
|
||||||
|
refreshNotes = true;
|
||||||
|
refreshNotesUseSelectedNoteId = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
if (action.type == 'TAG_SELECT' || action.type === 'TAG_DELETE') {
|
||||||
refreshNotes = true;
|
refreshNotes = true;
|
||||||
}
|
}
|
||||||
@ -493,7 +515,6 @@ class BaseApplication {
|
|||||||
await FoldersScreenUtils.scheduleRefreshFolders();
|
await FoldersScreenUtils.scheduleRefreshFolders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,6 +536,16 @@ class BaseApplication {
|
|||||||
ResourceFetcher.instance().dispatch = this.store().dispatch;
|
ResourceFetcher.instance().dispatch = this.store().dispatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinitRedux() {
|
||||||
|
this.store_ = null;
|
||||||
|
BaseModel.dispatch = function() {};
|
||||||
|
FoldersScreenUtils.dispatch = function() {};
|
||||||
|
reg.dispatch = function() {};
|
||||||
|
BaseSyncTarget.dispatch = function() {};
|
||||||
|
DecryptionWorker.instance().dispatch = function() {};
|
||||||
|
ResourceFetcher.instance().dispatch = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
async readFlagsFromFile(flagPath) {
|
async readFlagsFromFile(flagPath) {
|
||||||
if (!fs.existsSync(flagPath)) return {};
|
if (!fs.existsSync(flagPath)) return {};
|
||||||
let flagContent = fs.readFileSync(flagPath, 'utf8');
|
let flagContent = fs.readFileSync(flagPath, 'utf8');
|
||||||
@ -670,7 +701,6 @@ class BaseApplication {
|
|||||||
Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : '');
|
Setting.setValue('activeFolderId', currentFolder ? currentFolder.id : '');
|
||||||
|
|
||||||
await MigrationService.instance().run();
|
await MigrationService.instance().run();
|
||||||
|
|
||||||
return argv;
|
return argv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,7 +552,7 @@ class BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseModel.typeEnum_ = [['TYPE_NOTE', 1], ['TYPE_FOLDER', 2], ['TYPE_SETTING', 3], ['TYPE_RESOURCE', 4], ['TYPE_TAG', 5], ['TYPE_NOTE_TAG', 6], ['TYPE_SEARCH', 7], ['TYPE_ALARM', 8], ['TYPE_MASTER_KEY', 9], ['TYPE_ITEM_CHANGE', 10], ['TYPE_NOTE_RESOURCE', 11], ['TYPE_RESOURCE_LOCAL_STATE', 12], ['TYPE_REVISION', 13], ['TYPE_MIGRATION', 14]];
|
BaseModel.typeEnum_ = [['TYPE_NOTE', 1], ['TYPE_FOLDER', 2], ['TYPE_SETTING', 3], ['TYPE_RESOURCE', 4], ['TYPE_TAG', 5], ['TYPE_NOTE_TAG', 6], ['TYPE_SEARCH', 7], ['TYPE_ALARM', 8], ['TYPE_MASTER_KEY', 9], ['TYPE_ITEM_CHANGE', 10], ['TYPE_NOTE_RESOURCE', 11], ['TYPE_RESOURCE_LOCAL_STATE', 12], ['TYPE_REVISION', 13], ['TYPE_MIGRATION', 14], ['TYPE_SMART_FILTER', 15]];
|
||||||
|
|
||||||
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
for (let i = 0; i < BaseModel.typeEnum_.length; i++) {
|
||||||
const e = BaseModel.typeEnum_[i];
|
const e = BaseModel.typeEnum_[i];
|
||||||
|
@ -34,22 +34,40 @@ class FoldersScreenUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async refreshFolders() {
|
static async refreshFolders() {
|
||||||
|
FoldersScreenUtils.refreshCalls_.push(true);
|
||||||
|
try {
|
||||||
const folders = await this.allForDisplay({ includeConflictFolder: true });
|
const folders = await this.allForDisplay({ includeConflictFolder: true });
|
||||||
|
|
||||||
this.dispatch({
|
this.dispatch({
|
||||||
type: 'FOLDER_UPDATE_ALL',
|
type: 'FOLDER_UPDATE_ALL',
|
||||||
items: folders,
|
items: folders,
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
FoldersScreenUtils.refreshCalls_.pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static scheduleRefreshFolders() {
|
static scheduleRefreshFolders() {
|
||||||
if (this.scheduleRefreshFoldersIID_) clearTimeout(this.scheduleRefreshFoldersIID_);
|
if (this.scheduleRefreshFoldersIID_) clearTimeout(this.scheduleRefreshFoldersIID_);
|
||||||
|
|
||||||
this.scheduleRefreshFoldersIID_ = setTimeout(() => {
|
this.scheduleRefreshFoldersIID_ = setTimeout(() => {
|
||||||
this.scheduleRefreshFoldersIID_ = null;
|
this.scheduleRefreshFoldersIID_ = null;
|
||||||
this.refreshFolders();
|
this.refreshFolders();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async cancelTimers() {
|
||||||
|
if (this.scheduleRefreshFoldersIID_) clearTimeout(this.scheduleRefreshFoldersIID_);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const iid = setInterval(() => {
|
||||||
|
if (!FoldersScreenUtils.refreshCalls_.length) {
|
||||||
|
clearInterval(iid);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FoldersScreenUtils.refreshCalls_ = [];
|
||||||
|
|
||||||
module.exports = { FoldersScreenUtils };
|
module.exports = { FoldersScreenUtils };
|
||||||
|
@ -12,6 +12,7 @@ const ArrayUtils = require('lib/ArrayUtils.js');
|
|||||||
const lodash = require('lodash');
|
const lodash = require('lodash');
|
||||||
const urlUtils = require('lib/urlUtils.js');
|
const urlUtils = require('lib/urlUtils.js');
|
||||||
const { MarkupToHtml } = require('lib/joplin-renderer');
|
const { MarkupToHtml } = require('lib/joplin-renderer');
|
||||||
|
const { ALL_NOTES_FILTER_ID } = require('lib/reserved-ids');
|
||||||
|
|
||||||
class Note extends BaseItem {
|
class Note extends BaseItem {
|
||||||
static tableName() {
|
static tableName() {
|
||||||
@ -275,7 +276,7 @@ class Note extends BaseItem {
|
|||||||
options.conditions.push('is_conflict = 1');
|
options.conditions.push('is_conflict = 1');
|
||||||
} else {
|
} else {
|
||||||
options.conditions.push('is_conflict = 0');
|
options.conditions.push('is_conflict = 0');
|
||||||
if (parentId) {
|
if (parentId && parentId !== ALL_NOTES_FILTER_ID) {
|
||||||
options.conditions.push('parent_id = ?');
|
options.conditions.push('parent_id = ?');
|
||||||
options.conditionsParams.push(parentId);
|
options.conditionsParams.push(parentId);
|
||||||
}
|
}
|
||||||
|
13
ReactNativeClient/lib/models/SmartFilter.js
Normal file
13
ReactNativeClient/lib/models/SmartFilter.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const BaseModel = require('lib/BaseModel.js');
|
||||||
|
|
||||||
|
class SmartFilter extends BaseModel {
|
||||||
|
static tableName() {
|
||||||
|
throw new Error('Not using database');
|
||||||
|
}
|
||||||
|
|
||||||
|
static modelType() {
|
||||||
|
return BaseModel.TYPE_SMART_FILTER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SmartFilter;
|
@ -399,6 +399,12 @@ const reducer = (state = defaultState, action) => {
|
|||||||
newState.selectedNoteIds = newState.notes.map(n => n.id);
|
newState.selectedNoteIds = newState.notes.map(n => n.id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'SMART_FILTER_SELECT':
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
newState.notesParentType = 'SmartFilter';
|
||||||
|
newState.selectedSmartFilterId = action.id;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'FOLDER_SELECT':
|
case 'FOLDER_SELECT':
|
||||||
newState = changeSelectedFolder(state, action, { clearSelectedNoteIds: true });
|
newState = changeSelectedFolder(state, action, { clearSelectedNoteIds: true });
|
||||||
break;
|
break;
|
||||||
|
@ -52,7 +52,7 @@ reg.syncTarget = (syncTargetId = null) => {
|
|||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
reg.scheduleSync = async (delay = null, syncOptions = null) => {
|
reg.scheduleSync_ = async (delay = null, syncOptions = null) => {
|
||||||
if (delay === null) delay = 1000 * 10;
|
if (delay === null) delay = 1000 * 10;
|
||||||
if (syncOptions === null) syncOptions = {};
|
if (syncOptions === null) syncOptions = {};
|
||||||
|
|
||||||
@ -152,6 +152,15 @@ reg.scheduleSync = async (delay = null, syncOptions = null) => {
|
|||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reg.scheduleSync = async (delay = null, syncOptions = null) => {
|
||||||
|
reg.syncCalls_.push(true);
|
||||||
|
try {
|
||||||
|
await reg.scheduleSync_(delay, syncOptions);
|
||||||
|
} finally {
|
||||||
|
reg.syncCalls_.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
reg.setupRecurrentSync = () => {
|
reg.setupRecurrentSync = () => {
|
||||||
if (reg.recurrentSyncId_) {
|
if (reg.recurrentSyncId_) {
|
||||||
shim.clearInterval(reg.recurrentSyncId_);
|
shim.clearInterval(reg.recurrentSyncId_);
|
||||||
@ -183,4 +192,18 @@ reg.db = () => {
|
|||||||
return reg.db_;
|
return reg.db_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reg.cancelTimers = async () => {
|
||||||
|
if (this.recurrentSyncId_) clearTimeout(this.recurrentSyncId_);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const iid = setInterval(() => {
|
||||||
|
if (!reg.syncCalls_.length) {
|
||||||
|
clearInterval(iid);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reg.syncCalls_ = [];
|
||||||
|
|
||||||
module.exports = { reg };
|
module.exports = { reg };
|
||||||
|
6
ReactNativeClient/lib/reserved-ids.js
Normal file
6
ReactNativeClient/lib/reserved-ids.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
module.exports = Object.freeze({
|
||||||
|
|
||||||
|
ALL_NOTES_FILTER_ID: 'c3176726992c11e9ac940492261af972',
|
||||||
|
|
||||||
|
});
|
@ -16,6 +16,8 @@ class DecryptionWorker {
|
|||||||
this.eventEmitter_ = new EventEmitter();
|
this.eventEmitter_ = new EventEmitter();
|
||||||
this.kvStore_ = null;
|
this.kvStore_ = null;
|
||||||
this.maxDecryptionAttempts_ = 2;
|
this.maxDecryptionAttempts_ = 2;
|
||||||
|
|
||||||
|
this.startCalls_ = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogger(l) {
|
setLogger(l) {
|
||||||
@ -92,7 +94,7 @@ class DecryptionWorker {
|
|||||||
this.dispatch(action);
|
this.dispatch(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(options = null) {
|
async start_(options = null) {
|
||||||
if (options === null) options = {};
|
if (options === null) options = {};
|
||||||
if (!('masterKeyNotLoadedHandler' in options)) options.masterKeyNotLoadedHandler = 'throw';
|
if (!('masterKeyNotLoadedHandler' in options)) options.masterKeyNotLoadedHandler = 'throw';
|
||||||
if (!('errorHandler' in options)) options.errorHandler = 'log';
|
if (!('errorHandler' in options)) options.errorHandler = 'log';
|
||||||
@ -238,6 +240,27 @@ class DecryptionWorker {
|
|||||||
this.scheduleStart();
|
this.scheduleStart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async start(options) {
|
||||||
|
this.startCalls_.push(true);
|
||||||
|
try {
|
||||||
|
await this.start_(options);
|
||||||
|
} finally {
|
||||||
|
this.startCalls_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelTimers() {
|
||||||
|
if (this.scheduleId_) clearTimeout(this.scheduleId_);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const iid = setInterval(() => {
|
||||||
|
if (!this.startCalls_.length) {
|
||||||
|
clearInterval(iid);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = DecryptionWorker;
|
module.exports = DecryptionWorker;
|
||||||
|
@ -14,6 +14,7 @@ class SearchEngine {
|
|||||||
this.logger_ = new Logger();
|
this.logger_ = new Logger();
|
||||||
this.db_ = null;
|
this.db_ = null;
|
||||||
this.isIndexing_ = false;
|
this.isIndexing_ = false;
|
||||||
|
this.syncCalls_ = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static instance() {
|
static instance() {
|
||||||
@ -95,7 +96,7 @@ class SearchEngine {
|
|||||||
return this.syncTables();
|
return this.syncTables();
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncTables() {
|
async syncTables_() {
|
||||||
if (this.isIndexing_) return;
|
if (this.isIndexing_) return;
|
||||||
|
|
||||||
this.isIndexing_ = true;
|
this.isIndexing_ = true;
|
||||||
@ -176,6 +177,15 @@ class SearchEngine {
|
|||||||
this.isIndexing_ = false;
|
this.isIndexing_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async syncTables() {
|
||||||
|
this.syncCalls_.push(true);
|
||||||
|
try {
|
||||||
|
await this.syncTables_();
|
||||||
|
} finally {
|
||||||
|
this.syncCalls_.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async countRows() {
|
async countRows() {
|
||||||
const sql = 'SELECT count(*) as total FROM notes_fts';
|
const sql = 'SELECT count(*) as total FROM notes_fts';
|
||||||
const row = await this.db().selectOne(sql);
|
const row = await this.db().selectOne(sql);
|
||||||
@ -402,6 +412,18 @@ class SearchEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async cancelTimers() {
|
||||||
|
if (this.scheduleSyncTablesIID_) clearTimeout(this.scheduleSyncTablesIID_);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const iid = setInterval(() => {
|
||||||
|
if (!this.syncCalls_.length) {
|
||||||
|
clearInterval(iid);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SearchEngine;
|
module.exports = SearchEngine;
|
||||||
|
Loading…
Reference in New Issue
Block a user