1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

All: Filter "notebook" can now be negated (#4651)

This commit is contained in:
Naveen M V 2021-03-11 20:12:39 +05:30 committed by GitHub
parent 3e9cb1d4fd
commit a1423e4851
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 12 deletions

View File

@ -386,7 +386,7 @@ You can also use search filters to further restrict the search.
|**any:**|Return notes that satisfy any/all of the required conditions. `any:0` is the default, which means all conditions must be satisfied.|`any:1 cat dog` will return notes that have the word `cat` or `dog`.<br>`any:0 cat dog` will return notes with both the words `cat` and `dog`. |
| **title:** <br> **body:**|Restrict your search to just the title or the body field.|`title:"hello world"` searches for notes whose title contains `hello` and `world`.<br>`title:hello -body:world` searches for notes whose title contains `hello` and body does not contain `world`.
| **tag:** |Restrict the search to the notes with the specified tags.|`tag:office` searches for all notes having tag office.<br>`tag:office tag:important` searches for all notes having both office and important tags.<br>`tag:office -tag:spam` searches for notes having tag `office` which do not have tag `spam`.<br>`any:1 tag:office tag:spam` searches for notes having tag `office` or tag `spam`.<br>`tag:be*ful` does a search with wildcards.<br>`tag:*` returns all notes with tags.<br>`-tag:*` returns all notes without tags.|
| **notebook:** | Restrict the search to the specified notebook(s). It cannot be negated. |`notebook:books` limits the search scope within `books` and all its subnotebooks.<br>`notebook:wheel*time` does a wildcard search.|
| **notebook:** | Restrict the search to the specified notebook(s). |`notebook:books` limits the search scope within `books` and all its subnotebooks.<br>`notebook:wheel*time` does a wildcard search.|
| **created:** <br> **updated:** | Searches for notes created/updated on dates specified using YYYYMMDD format. You can also search relative to the current day, week, month, or year. | `created:20201218` will return notes created on or after December 18, 2020.<br>`-updated:20201218` will return notes updated before December 18, 2020.<br>`created:20200118 -created:20201215` will return notes created between January 18, 2020, and before December 15, 2020.<br>`created:202001 -created:202003` will return notes created on or after January and before March 2020.<br>`updated:1997 -updated:2020` will return all notes updated between the years 1997 and 2019.<br>`created:day-2` searches for all notes created in the past two days.<br>`updated:year-0` searches all notes updated in the current year.
| **type:** |Restrict the search to either notes or todos. | `type:note` to return all notes<br>`type:todo` to return all todos |
| **iscompleted:** | Restrict the search to either completed or uncompleted todos. | `iscompleted:1` to return all completed todos<br>`iscompleted:0` to return all uncompleted todos|

View File

@ -152,8 +152,6 @@ describe('filterParser should be correct filter for keyword', () => {
searchString = 'iscompleted:blah';
expect(() => filterParser(searchString)).toThrow(new Error('The value of filter "iscompleted" must be "1" or "0"'));
searchString = '-notebook:n1';
expect(() => filterParser(searchString)).toThrow(new Error('notebook can\'t be negated'));
searchString = '-iscompleted:1';
expect(() => filterParser(searchString)).toThrow(new Error('iscompleted can\'t be negated'));

View File

@ -742,4 +742,52 @@ describe('services_SearchFilter', function() {
}));
it('should support negating notebooks', (async () => {
const folder1 = await Folder.save({ title: 'folder1' });
let n1 = await Note.save({ title: 'task1', body: 'foo', parent_id: folder1.id });
let n2 = await Note.save({ title: 'task2', body: 'bar', parent_id: folder1.id });
const folder2 = await Folder.save({ title: 'folder2' });
let n3 = await Note.save({ title: 'task3', body: 'baz', parent_id: folder2.id });
let n4 = await Note.save({ title: 'task4', body: 'blah', parent_id: folder2.id });
await engine.syncTables();
let rows = await engine.search('-notebook:folder1');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n3.id);
expect(ids(rows)).toContain(n4.id);
rows = await engine.search('-notebook:folder2');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
}));
it('should support both inclusion and exclusion of notebooks together', (async () => {
const parentFolder = await Folder.save({ title: 'parent' });
let n1 = await Note.save({ title: 'task1', body: 'foo', parent_id: parentFolder.id });
let n2 = await Note.save({ title: 'task2', body: 'bar', parent_id: parentFolder.id });
const subFolder = await Folder.save({ title: 'child', parent_id: parentFolder.id });
let n3 = await Note.save({ title: 'task3', body: 'baz', parent_id: subFolder.id });
let n4 = await Note.save({ title: 'task4', body: 'blah', parent_id: subFolder.id });
await engine.syncTables();
let rows = await engine.search('notebook:parent -notebook:child');
expect(rows.length).toBe(2);
expect(ids(rows)).toContain(n1.id);
expect(ids(rows)).toContain(n2.id);
}));
});

View File

@ -118,7 +118,7 @@ const parseQuery = (query: string): Term[] => {
}
// validation
let incorrect = result.filter(term => term.name === 'type' || term.name === 'iscompleted' || term.name === 'notebook')
let incorrect = result.filter(term => term.name === 'type' || term.name === 'iscompleted')
.find(x => x.negated);
if (incorrect) throw new Error(`${incorrect.name} can't be negated`);

View File

@ -21,18 +21,19 @@ enum Requirement {
INCLUSION = 'INCLUSION',
}
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[]) => {
const notebooks = terms.filter(x => x.name === 'notebook' && !x.negated).map(x => x.value);
const _notebookFilter = (notebooks: string[], requirement: Requirement, conditions: string[], params: string[], withs: string[]) => {
if (notebooks.length === 0) return;
const likes = [];
for (let i = 0; i < notebooks.length; i++) {
likes.push('folders.title LIKE ?');
}
const relevantFolders = likes.join(' OR ');
const viewName = requirement === Requirement.EXCLUSION ? 'notebooks_not_in_scope' : 'notebooks_in_scope';
const withInNotebook = `
notebooks_in_scope(id)
${viewName}(id)
AS (
SELECT folders.id
FROM folders
@ -45,22 +46,35 @@ const notebookFilter = (terms: Term[], conditions: string[], params: string[], w
UNION ALL
SELECT folders.id
FROM folders
JOIN notebooks_in_scope
ON folders.parent_id=notebooks_in_scope.id
JOIN ${viewName}
ON folders.parent_id=${viewName}.id
)`;
const where = `
AND ROWID IN (
AND ROWID ${requirement === Requirement.EXCLUSION ? 'NOT' : ''} IN (
SELECT notes_normalized.ROWID
FROM notebooks_in_scope
FROM ${viewName}
JOIN notes_normalized
ON notebooks_in_scope.id=notes_normalized.parent_id
ON ${viewName}.id=notes_normalized.parent_id
)`;
withs.push(withInNotebook);
params.push(...notebooks);
conditions.push(where);
};
const notebookFilter = (terms: Term[], conditions: string[], params: string[], withs: string[]) => {
const notebooksToInclude = terms.filter(x => x.name === 'notebook' && !x.negated).map(x => x.value);
_notebookFilter(notebooksToInclude, Requirement.INCLUSION, conditions, params, withs);
const notebooksToExclude = terms.filter(x => x.name === 'notebook' && x.negated).map(x => x.value);
_notebookFilter(notebooksToExclude, Requirement.EXCLUSION, conditions, params, withs);
};
const getOperator = (requirement: Requirement, relation: Relation): Operation => {
if (relation === 'AND' && requirement === 'INCLUSION') { return Operation.INTERSECT; } else { return Operation.UNION; }
};