This is an automated email from the ASF dual-hosted git repository. randall pushed a commit to branch eliminate-extraneous-interfaces in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit 372187bdd9936cfda8664ccd5b0d764ab7ef578c Author: Randall Leeds <[email protected]> AuthorDate: Sun Sep 6 15:52:25 2020 -0700 --wip-- [skip ci] --- packages/dom/src/range/index.ts | 23 ++++++ packages/dom/src/range/match.ts | 7 +- packages/dom/src/range/{index.ts => selector.ts} | 6 +- packages/selector/src/index.ts | 91 +++++++++++++++++------- web/demo/index.js | 23 +++--- 5 files changed, 103 insertions(+), 47 deletions(-) diff --git a/packages/dom/src/range/index.ts b/packages/dom/src/range/index.ts index 011e994..be44f30 100644 --- a/packages/dom/src/range/index.ts +++ b/packages/dom/src/range/index.ts @@ -18,4 +18,27 @@ * under the License. */ +import type { RangeSelector } from './selector'; +import { makeCreateRangeSelectorMatcher } from './match'; + export * from './match'; +export * from './selector'; + +export function withRange<T>( + createMatcher: (selector: T) => (scope: Range) => AsyncIterable<Range>, +): (selector: T | RangeSelector<T>) => (scope: Range) => AsyncIterable<Range> { + const createRangeMatcher = makeCreateRangeSelectorMatcher(createMatcher); + return function createMatcherWithRange(selector) { + if ('type' in selector && selector.type === 'RangeSelector') { + return createRangeMatcher(selector); + } + return createMatcher(selector as T); + }; +} + +export function withRangeRecursive<T>( + createMatcher: (selector: T) => (scope: Range) => AsyncIterable<Range>, +): (selector: T | RangeSelector<T>) => (scope: Range) => AsyncIterable<Range> { + const inner = withRange(createMatcher); + return withRange(inner); +} diff --git a/packages/dom/src/range/match.ts b/packages/dom/src/range/match.ts index bc2d601..cfa297b 100644 --- a/packages/dom/src/range/match.ts +++ b/packages/dom/src/range/match.ts @@ -19,12 +19,7 @@ */ import { product } from './cartesian'; - -export interface RangeSelector<T> { - type: 'RangeSelector'; - startSelector: T; - endSelector: T; -} +import type { RangeSelector } from './selector'; export function makeCreateRangeSelectorMatcher<T>( createMatcher: (selector: T) => (scope: Range) => AsyncIterable<Range>, diff --git a/packages/dom/src/range/index.ts b/packages/dom/src/range/selector.ts similarity index 89% copy from packages/dom/src/range/index.ts copy to packages/dom/src/range/selector.ts index 011e994..7690e76 100644 --- a/packages/dom/src/range/index.ts +++ b/packages/dom/src/range/selector.ts @@ -18,4 +18,8 @@ * under the License. */ -export * from './match'; +export interface RangeSelector<T> { + type: 'RangeSelector'; + startSelector: T; + endSelector: T; +} diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts index 99c2840..deca7b1 100644 --- a/packages/selector/src/index.ts +++ b/packages/selector/src/index.ts @@ -18,46 +18,83 @@ * under the License. */ +type Selector = { type: string }; + +type Matcher<TScope, TMatch> = (scope: TScope) => AsyncIterable<TMatch>; +type MatcherCreator<TSelector extends Selector, TScope, TMatch> = ( + selector: TSelector, +) => Matcher<TScope, TMatch>; + +type Plugin<TSelector extends Selector, TScope, TMatch> = ( + next: MatcherCreator<TSelector, TScope, TMatch>, + recurse: MatcherCreator<TSelector, TScope, TMatch>, +) => MatcherCreator<TSelector, TScope, TMatch>; + +export function composeMatcherCreator< + TSelector extends Selector, + TScope, + TMatch extends TScope +>( + ...plugins: Array<Plugin<TSelector, TScope, TMatch>> +): MatcherCreator<TSelector, TScope, TMatch> { + function innerMatcherCreator(selector: TSelector): Matcher<TScope, TMatch> { + throw new TypeError(`Unhandled selector. Selector type: ${selector.type}`); + } + + function outerMatcherCreator(selector: TSelector): Matcher<TScope, TMatch> { + return composedMatcherCreator(selector); + } + + const composedMatcherCreator = plugins.reduceRight( + (matcherCreator, plugin) => plugin(matcherCreator, outerMatcherCreator), + innerMatcherCreator, + ); + + return outerMatcherCreator; +} + +type MatcherCreatorMap<TSelector extends Selector, TScope, TMatch> = { + [K: string]: MatcherCreator<TSelector, TScope, TMatch>; +}; + +export function mapSelectorTypes<TSelector extends Selector, TScope, TMatch>( + matcherCreators: MatcherCreatorMap<TSelector, TScope, TMatch>, +): Plugin<TSelector, TScope, TMatch> { + return function mapSelectorTypesPlugin(next) { + return function (selector) { + const matcherCreator = matcherCreators[selector.type]; + + if (matcherCreator) { + return matcherCreator(selector); + } + + return next(selector); + }; + }; +} + export function withRefinement< - TSelector, - TSelectorScope, - TRefinement, - TRefinementScope, + TSelector extends Selector & { refinedBy?: TSelector }, + TScope, TMatch >( - createMatcher: ( - selector: TSelector, - ) => (scope: TSelectorScope) => AsyncIterable<TRefinementScope>, - createRefiner: ( - selector: TRefinement, - ) => (scope: TRefinementScope) => AsyncIterable<TMatch>, -): ( - selector: TSelector & { refinedBy?: TRefinement }, -) => (scope: TSelectorScope) => AsyncIterable<TRefinementScope | TMatch> { + next: (selector: TSelector) => (scope: TScope) => AsyncIterable<TScope>, + recurse: (selector: TSelector) => (scope: TScope) => AsyncIterable<TMatch>, +): MatcherCreator<TSelector, TScope, TMatch> { return function createMatcherWithRefinement(selector) { const { refinedBy } = selector; + const matcher = next(selector); if (refinedBy) { - const match = createMatcher(selector); - const refine = createRefiner(refinedBy); + const refine = recurse(refinedBy); return async function* matchAll(scope) { - for await (const subScope of match(scope)) { + for await (const subScope of matcher(scope)) { yield* refine(subScope); } }; } - return createMatcher(selector); + return matcher; }; } - -export function withRecursiveRefinement<TSelector, TScope>( - createMatcher: ( - selector: TSelector & { refinedBy?: TSelector }, - ) => (scope: TScope) => AsyncIterable<TScope>, -): ( - selector: TSelector & { refinedBy?: TSelector }, -) => (scope: TScope) => AsyncIterable<TScope> { - return withRefinement(createMatcher, createMatcher); -} diff --git a/web/demo/index.js b/web/demo/index.js index 18c9983..a7f2816 100644 --- a/web/demo/index.js +++ b/web/demo/index.js @@ -21,12 +21,15 @@ /* global info, module, source, target */ import { - makeCreateRangeSelectorMatcher, createTextQuoteSelectorMatcher, describeTextQuote, highlightRange, } from '@annotator/dom'; -import { withRecursiveRefinement } from '@annotator/selector'; +import { + composeMatcherCreator, + mapSelectorTypes, + withRefinement, +} from '@annotator/selector'; const EXAMPLE_SELECTORS = [ { @@ -91,18 +94,12 @@ function cleanup() { target.normalize(); } -const createMatcher = withRecursiveRefinement((selector) => { - const innerCreateMatcher = { +const createMatcher = composeMatcherCreator( + withRefinement, + mapSelectorTypes({ TextQuoteSelector: createTextQuoteSelectorMatcher, - RangeSelector: makeCreateRangeSelectorMatcher(createMatcher), - }[selector.type]; - - if (!innerCreateMatcher) { - throw new Error(`Unsupported selector type: ${selector.type}`); - } - - return innerCreateMatcher(selector); -}); + }), +); async function anchor(selector) { const scope = document.createRange();
