Add date (since: yesterday, before: last week) handling to search bar

This commit is contained in:
Ben Gotow 2018-06-24 23:49:27 -05:00
parent 9c9c9fa93f
commit af0f389503
6 changed files with 106 additions and 0 deletions

View file

@ -47,6 +47,7 @@ function isPastDate(inputDateObj, currentDate) {
return inputMoment.isBefore(currentMoment);
}
let _chronoPast = null;
let _chronoFuture = null;
let _chrono = null;
@ -93,6 +94,42 @@ function getChronoFuture() {
return _chronoFuture;
}
function getChronoPast() {
if (_chronoPast) {
return _chronoPast;
}
const chrono = getChrono();
const EnforcePastDate = new chrono.Refiner();
EnforcePastDate.refine = (text, results) => {
results.forEach(result => {
const current = Object.assign({}, result.start.knownValues, result.start.impliedValues);
if (result.start.isCertain('weekday') && !result.start.isCertain('day')) {
if (!isPastDate(current, result.ref)) {
result.start.imply('day', result.start.impliedValues.day - 7);
}
}
if (result.start.isCertain('day') && !result.start.isCertain('month')) {
if (!isPastDate(current, result.ref)) {
result.start.imply('month', result.start.impliedValues.month - 1);
}
}
if (result.start.isCertain('month') && !result.start.isCertain('year')) {
if (!isPastDate(current, result.ref)) {
result.start.imply('year', result.start.impliedValues.year - 1);
}
}
});
return results;
};
_chronoPast = new chrono.Chrono(chrono.options.casualOption());
_chronoPast.refiners.push(EnforcePastDate);
return _chronoPast;
}
const DateUtils = {
// Localized format: ddd, MMM D, YYYY h:mmA
DATE_FORMAT_LONG: 'llll',
@ -187,6 +224,8 @@ const DateUtils = {
return morning(now.add(1, 'month').date(1));
},
getChronoPast,
parseDateString(dateLikeString) {
const parsed = getChrono().parse(dateLikeString);
const gotTime = { start: false, end: false };

View file

@ -19,6 +19,9 @@ class SearchQueryExpressionVisitor {
visitFrom(node) {
throw new Error('Abstract function not implemented!', node);
}
visitDate(node) {
throw new Error('Abstract function not implemented!', node);
}
visitTo(node) {
throw new Error('Abstract function not implemented!', node);
}
@ -147,6 +150,29 @@ class FromQueryExpression extends QueryExpression {
}
}
class DateQueryExpression extends QueryExpression {
constructor(text, direction = 'before') {
super();
this.text = text;
this.direction = direction;
}
accept(visitor) {
visitor.visitDate(this);
}
_computeIsMatchCompatible() {
return false;
}
equals(other) {
if (!(other instanceof DateQueryExpression)) {
return false;
}
return this.text.equals(other.text);
}
}
class ToQueryExpression extends QueryExpression {
constructor(text) {
super();
@ -368,5 +394,6 @@ module.exports = {
StarredStatusQueryExpression,
MatchQueryExpression,
InQueryExpression,
DateQueryExpression,
HasAttachmentQueryExpression,
};

View file

@ -116,6 +116,10 @@ class IMAPSearchQueryExpressionVisitor extends SearchQueryExpressionVisitor {
this._result = ['FROM', text];
}
visitDate(node) {
throw new Error('Function not implemented!', node);
}
visitTo(node) {
const text = this.visitAndGetResult(node.text);
this._result = ['TO', text];

View file

@ -2,11 +2,13 @@ import {
SearchQueryExpressionVisitor,
OrQueryExpression,
AndQueryExpression,
DateQueryExpression,
UnreadStatusQueryExpression,
StarredStatusQueryExpression,
HasAttachmentQueryExpression,
MatchQueryExpression,
} from './search-query-ast';
import { DateUtils } from 'mailspring-exports';
/*
* This class visits a match-compatible subtree and condenses it into a single
@ -37,6 +39,8 @@ class MatchQueryExpressionVisitor extends SearchQueryExpressionVisitor {
this._result = `(${lhs} OR ${rhs})`;
}
visitDate(node) {}
visitFrom(node) {
const text = this.visitAndGetResult(node.text);
this._result = `(from_ : "${text}"*)`;
@ -144,6 +148,10 @@ class MatchCompatibleQueryCondenser extends SearchQueryExpressionVisitor {
this._result = new UnreadStatusQueryExpression(node.status);
}
visitDate(node) {
this._result = new DateQueryExpression(node.text, node.direction);
}
visitStarred(node) {
this._result = new StarredStatusQueryExpression(node.status);
}
@ -218,6 +226,17 @@ class StructuredSearchQueryVisitor extends SearchQueryExpressionVisitor {
this._result = `(\`${this._className}\`.\`data\` LIKE '%"has_attachments":true%')`;
}
visitDate(node) {
const comparator = node.direction === 'before' ? '<' : '>';
const date = DateUtils.getChronoPast().parseDate(node.text.token.s);
if (!date) {
this._result = '';
return;
}
const ts = Math.floor(date.getTime() / 1000);
this._result = `(\`${this._className}\`.\`lastMessageReceivedTimestamp\` ${comparator} ${ts})`;
}
visitMatch(node) {
const searchTable = `${this._className}Search`;

View file

@ -9,6 +9,7 @@ import {
TextQueryExpression,
UnreadStatusQueryExpression,
StarredStatusQueryExpression,
DateQueryExpression,
InQueryExpression,
HasAttachmentQueryExpression,
} from './search-query-ast';
@ -66,6 +67,9 @@ const reserved = [
'in',
'has',
'attachment',
'before',
'since',
'after',
];
const mightBeReserved = text => {
@ -272,6 +276,18 @@ const parseSimpleQuery = text => {
}
}
if (tok.s.toUpperCase() === 'SINCE' || tok.s.toUpperCase() === 'AFTER') {
const afterColon = consumeExpectedToken(afterTok, ':');
const [txt, afterTxt] = parseText(afterColon);
return [new DateQueryExpression(txt, 'after'), afterTxt];
}
if (tok.s.toUpperCase() === 'BEFORE') {
const afterColon = consumeExpectedToken(afterTok, ':');
const [txt, afterTxt] = parseText(afterColon);
return [new DateQueryExpression(txt, 'before'), afterTxt];
}
if (tok.s.toUpperCase() === 'IN') {
const afterColon = consumeExpectedToken(afterTok, ':');
const [txt, afterTxt] = parseText(afterColon);

View file

@ -64,6 +64,7 @@
}
.search-accessory {
&.search {
position: absolute;
top: 8px;