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:
parent
3e9cb1d4fd
commit
a1423e4851
@ -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|
|
||||
|
@ -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'));
|
||||
|
@ -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);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
@ -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`);
|
||||
|
||||
|
@ -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; }
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user