-
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
789 additions
and
888 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { generateLexArray } from 'query/lex'; | ||
import { parseTokens } from 'query/parse'; | ||
import { parseTerm } from 'query/term'; | ||
|
||
function parseSearch(query: string) { | ||
const tokens = generateLexArray(query, parseTerm); | ||
const matcher = parseTokens(tokens); | ||
|
||
return { | ||
hitsImage(image: HTMLElement) { | ||
return matcher(image); | ||
} | ||
}; | ||
} | ||
|
||
export default parseSearch; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { AstMatcher } from './types'; | ||
|
||
export function matchAny(...matchers: AstMatcher[]): AstMatcher { | ||
return (e: HTMLElement) => { | ||
for (const matcher of matchers) { | ||
if (matcher(e)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
} | ||
|
||
export function matchAll(...matchers: AstMatcher[]): AstMatcher { | ||
return (e: HTMLElement) => { | ||
for (const matcher of matchers) { | ||
if (!matcher(e)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
} | ||
|
||
export function matchNot(matcher: AstMatcher): AstMatcher { | ||
return (e: HTMLElement) => { | ||
return !matcher(e); | ||
}; | ||
} | ||
|
||
export function matchNone(): AstMatcher { | ||
return () => { | ||
return false; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
import { FieldMatcher, RangeEqualQualifier } from './types'; | ||
|
||
type Year = number; | ||
type Month = number; | ||
type Day = number; | ||
type Hours = number; | ||
type Minutes = number; | ||
type Seconds = number; | ||
type AbsoluteDate = [Year, Month, Day, Hours, Minutes, Seconds]; | ||
type TimeZoneOffset = [Hours, Minutes]; | ||
type PosixTimeMs = number; | ||
|
||
function makeMatcher(bottomDate: PosixTimeMs, topDate: PosixTimeMs, qual: RangeEqualQualifier): FieldMatcher { | ||
// The open-left, closed-right date range specified by the | ||
// date/time format limits the types of comparisons that are | ||
// done compared to numeric ranges. | ||
switch (qual) { | ||
case 'lte': | ||
return v => new Date(v).getTime() < topDate; | ||
case 'gte': | ||
return v => new Date(v).getTime() >= bottomDate; | ||
case 'lt': | ||
return v => new Date(v).getTime() < bottomDate; | ||
case 'gt': | ||
return v => new Date(v).getTime() >= topDate; | ||
case 'eq': | ||
default: | ||
return v => { | ||
const t = new Date(v).getTime(); | ||
return t >= bottomDate && t < topDate; | ||
}; | ||
} | ||
} | ||
|
||
function makeRelativeDateMatcher(dateVal: string, qual: RangeEqualQualifier): FieldMatcher { | ||
const match = /(\d+) (second|minute|hour|day|week|month|year)s? ago/.exec(dateVal); | ||
const bounds: Record<string, number> = { | ||
second: 1000, | ||
minute: 60000, | ||
hour: 3600000, | ||
day: 86400000, | ||
week: 604800000, | ||
month: 2592000000, | ||
year: 31536000000 | ||
}; | ||
|
||
if (match) { | ||
const amount = parseInt(match[1], 10); | ||
const scale = bounds[match[2]]; | ||
|
||
const now = new Date().getTime(); | ||
const bottomDate = new Date(now - amount * scale).getTime(); | ||
const topDate = new Date(now - (amount - 1) * scale).getTime(); | ||
|
||
return makeMatcher(bottomDate, topDate, qual); | ||
} | ||
|
||
throw new Error(`Cannot parse date string: ${dateVal}`); | ||
|
||
} | ||
|
||
function makeAbsoluteDateMatcher(dateVal: string, qual: RangeEqualQualifier): FieldMatcher { | ||
const parseRes: RegExp[] = [ | ||
/^(\d{4})/, | ||
/^-(\d{2})/, | ||
/^-(\d{2})/, | ||
/^(?:\s+|T|t)(\d{2})/, | ||
/^:(\d{2})/, | ||
/^:(\d{2})/ | ||
]; | ||
const timeZoneOffset: TimeZoneOffset = [0, 0]; | ||
const timeData: AbsoluteDate = [0, 0, 1, 0, 0, 0]; | ||
|
||
const origDateVal: string = dateVal; | ||
let localDateVal = origDateVal; | ||
|
||
const offsetMatch = /([+-])(\d{2}):(\d{2})$/.exec(localDateVal); | ||
if (offsetMatch) { | ||
timeZoneOffset[0] = parseInt(offsetMatch[2], 10); | ||
timeZoneOffset[1] = parseInt(offsetMatch[3], 10); | ||
if (offsetMatch[1] === '-') { | ||
timeZoneOffset[0] *= -1; | ||
timeZoneOffset[1] *= -1; | ||
} | ||
localDateVal = localDateVal.substr(0, localDateVal.length - 6); | ||
} | ||
else { | ||
localDateVal = localDateVal.replace(/[Zz]$/, ''); | ||
} | ||
|
||
let matchIndex = 0; | ||
for (; matchIndex < parseRes.length; matchIndex += 1) { | ||
if (localDateVal.length === 0) { | ||
break; | ||
} | ||
|
||
const componentMatch = parseRes[matchIndex].exec(localDateVal); | ||
if (componentMatch) { | ||
if (matchIndex === 1) { | ||
// Months are offset by 1. | ||
timeData[matchIndex] = parseInt(componentMatch[1], 10) - 1; | ||
} | ||
else { | ||
// All other components are not offset. | ||
timeData[matchIndex] = parseInt(componentMatch[1], 10); | ||
} | ||
|
||
// Slice string. | ||
localDateVal = localDateVal.substr( | ||
componentMatch[0].length, localDateVal.length - componentMatch[0].length | ||
); | ||
} | ||
else { | ||
throw new Error(`Cannot parse date string: ${origDateVal}`); | ||
} | ||
} | ||
|
||
if (localDateVal.length > 0) { | ||
throw new Error(`Cannot parse date string: ${origDateVal}`); | ||
} | ||
|
||
// Apply the user-specified time zone offset. The JS Date constructor | ||
// is very flexible here. | ||
timeData[3] -= timeZoneOffset[0]; | ||
timeData[4] -= timeZoneOffset[1]; | ||
|
||
const asPosix = (data: AbsoluteDate) => { | ||
return new Date(Date.UTC.apply(Date, data)).getTime(); | ||
}; | ||
|
||
const bottomDate = asPosix(timeData); | ||
timeData[matchIndex - 1] += 1; | ||
const topDate = asPosix(timeData); | ||
|
||
return makeMatcher(bottomDate, topDate, qual); | ||
} | ||
|
||
export function makeDateMatcher(dateVal: string, qual: RangeEqualQualifier): FieldMatcher { | ||
try { | ||
return makeAbsoluteDateMatcher(dateVal, qual); | ||
} | ||
catch (_) { | ||
return makeRelativeDateMatcher(dateVal, qual); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { FieldName } from './types'; | ||
|
||
type AttributeName = string; | ||
|
||
export const numberFields: FieldName[] = | ||
['id', 'width', 'height', 'aspect_ratio', | ||
'comment_count', 'score', 'upvotes', 'downvotes', | ||
'faves', 'tag_count']; | ||
|
||
export const dateFields: FieldName[] = ['created_at']; | ||
|
||
export const literalFields = | ||
['tags', 'orig_sha512_hash', 'sha512_hash', | ||
'score', 'uploader', 'source_url', 'description']; | ||
|
||
export const termSpaceToImageField: Record<FieldName, AttributeName> = { | ||
tags: 'data-image-tag-aliases', | ||
score: 'data-score', | ||
upvotes: 'data-upvotes', | ||
downvotes: 'data-downvotes', | ||
uploader: 'data-uploader', | ||
// Yeah, I don't think this is reasonably supportable. | ||
// faved_by: 'data-faved-by', | ||
id: 'data-image-id', | ||
width: 'data-width', | ||
height: 'data-height', | ||
/* eslint-disable camelcase */ | ||
aspect_ratio: 'data-aspect-ratio', | ||
comment_count: 'data-comment-count', | ||
tag_count: 'data-tag-count', | ||
source_url: 'data-source-url', | ||
faves: 'data-faves', | ||
sha512_hash: 'data-sha512', | ||
orig_sha512_hash: 'data-orig-sha512', | ||
created_at: 'data-created-at' | ||
/* eslint-enable camelcase */ | ||
}; |
Oops, something went wrong.