Merge branch 'master' into simpler-matcher-creation
diff --git a/packages/dom/src/range/match.ts b/packages/dom/src/range/match.ts
index d6891c6..d0b6943 100644
--- a/packages/dom/src/range/match.ts
+++ b/packages/dom/src/range/match.ts
@@ -20,8 +20,10 @@
import type {
Matcher,
+ Plugin,
RangeSelector,
Selector,
+ MatcherCreator,
} from '@apache-annotator/selector';
import { ownerDocument } from '../owner-document';
import { toRange } from '../to-range';
@@ -93,9 +95,7 @@
* @public
*/
export function makeCreateRangeSelectorMatcher(
- createMatcher: <T extends Selector, TMatch extends Node | Range>(
- selector: T,
- ) => Matcher<Node | Range, TMatch>,
+ createMatcher: MatcherCreator<Node | Range, Node | Range>,
): (selector: RangeSelector) => Matcher<Node | Range, Range> {
return function createRangeSelectorMatcher(selector) {
const startMatcher = createMatcher(selector.startSelector);
@@ -122,3 +122,17 @@
};
};
}
+
+export const supportRangeSelector: Plugin<Node | Range, Node | Range> = function supportRangeSelectorPlugin(
+ next,
+ recurse,
+) {
+ const createRangeSelectorMatcher = makeCreateRangeSelectorMatcher(recurse);
+ return function (selector: Selector) {
+ if (selector.type === 'RangeSelector') {
+ return createRangeSelectorMatcher(selector as RangeSelector);
+ } else {
+ return next(selector);
+ }
+ };
+};
diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts
index e0a5e48..7c294ae 100644
--- a/packages/selector/src/index.ts
+++ b/packages/selector/src/index.ts
@@ -18,47 +18,77 @@
* under the License.
*/
-import type { Matcher, Selector } from './types';
+import type { Matcher, Selector, SelectorType, MatcherCreator, Plugin } from './types';
-export type { Matcher, Selector } from './types';
-export type {
- CssSelector,
- RangeSelector,
- TextPositionSelector,
- TextQuoteSelector,
-} from './types';
export * from './text';
+export * from './types';
+
+interface TypeToMatcherCreatorMap<TScope, TMatch> {
+ // [K: SelectorType]: MatcherCreator<TScope, TMatch>; // Gives errors further down. TypeScript’s fault?
+ [K: string]: MatcherCreator<TScope, TMatch> | undefined;
+}
+
+export function composeMatcherCreator<TScope, TMatch extends TScope>(
+ ...plugins: Array<Plugin<TScope, TMatch>>
+): MatcherCreator<TScope, TMatch> {
+ function innerMatcherCreator(selector: Selector): Matcher<TScope, TMatch> {
+ throw new TypeError(`Unhandled selector. Selector type: ${selector.type}`);
+ }
+
+ function outerMatcherCreator(selector: Selector): Matcher<TScope, TMatch> {
+ return composedMatcherCreator(selector);
+ }
+
+ const composedMatcherCreator = plugins.reduceRight(
+ (
+ matcherCreator: MatcherCreator<TScope, TMatch>,
+ plugin: Plugin<TScope, TMatch>
+ ) => plugin(matcherCreator, outerMatcherCreator),
+ innerMatcherCreator,
+ );
+
+ return outerMatcherCreator;
+}
+
+// A plugin with parameters (i.e. a function that returns a plugin)
+// Invokes the matcher implementation corresponding to the selector’s type.
+export function mapSelectorTypes<TScope, TMatch extends TScope>(
+ typeToMatcherCreator: TypeToMatcherCreatorMap<TScope, TMatch>,
+): Plugin<TScope, TMatch> {
+ return function mapSelectorTypesPlugin(next, recurse): MatcherCreator<TScope, TMatch> {
+ return function(selector: Selector): Matcher<TScope, TMatch> {
+ const type = selector.type;
+ if (type !== undefined) {
+ const matcherCreator = typeToMatcherCreator[type];
+ if (matcherCreator !== undefined)
+ return matcherCreator(selector);
+ }
+ // Not a know selector type; continue down the plugin chain.
+ return next(selector);
+ }
+ }
+}
/**
- * Wrap a matcher creation function so that it supports refinement of selection.
+ * A plugin to support the Selector’s refinedBy field.
*
* See {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#refinement-of-selection
* | §4.2.9 Refinement of Selection} in the Web Annotation Data Model.
*
- * @param matcherCreator - The function to wrap; it will be executed both for
- * {@link Selector}s passed to the returned wrapper function, and for any
- * refining Selector those might contain (and any refinement of that, etc.).
- *
* @public
*/
-export function makeRefinable<
- // Any subtype of Selector can be made refinable; but note we limit the value
- // of refinedBy because it must also be accepted by matcherCreator.
- TSelector extends Selector & { refinedBy: TSelector },
- TScope,
- // To enable refinement, the implementation’s Match object must be usable as a
- // Scope object itself.
- TMatch extends TScope
->(
- matcherCreator: (selector: TSelector) => Matcher<TScope, TMatch>,
-): (selector: TSelector) => Matcher<TScope, TMatch> {
+export const supportRefinement: Plugin<any, any> =
+ function supportRefinementPlugin<TScope, TMatch extends TScope>(
+ next: MatcherCreator<TScope, TMatch>,
+ recurse: MatcherCreator<TScope, TMatch>,
+ ) {
return function createMatcherWithRefinement(
- sourceSelector: TSelector,
+ sourceSelector: Selector,
): Matcher<TScope, TMatch> {
- const matcher = matcherCreator(sourceSelector);
+ const matcher = next(sourceSelector);
if (sourceSelector.refinedBy) {
- const refiningSelector = createMatcherWithRefinement(
+ const refiningSelector = recurse(
sourceSelector.refinedBy,
);
@@ -67,8 +97,8 @@
yield* refiningSelector(match);
}
};
+ } else {
+ return matcher;
}
-
- return matcher;
};
}
diff --git a/packages/selector/src/types.ts b/packages/selector/src/types.ts
index fd59dfb..fff5199 100644
--- a/packages/selector/src/types.ts
+++ b/packages/selector/src/types.ts
@@ -35,9 +35,13 @@
*
* Corresponds to RDF property {@link http://www.w3.org/ns/oa#refinedBy}
*/
- refinedBy?: Selector;
+ refinedBy?: this;
+
+ type?: SelectorType;
}
+export type SelectorType = string; // not enumerating known options: we allow extensibility.
+
/**
* The {@link https://www.w3.org/TR/2017/REC-annotation-model-20170223/#css-selector
* | CssSelector} of the Web Annotation Data Model.
@@ -103,3 +107,20 @@
export interface Matcher<TScope, TMatch> {
(scope: TScope): AsyncGenerator<TMatch, void, void>;
}
+
+export type MatcherCreator<TScope, TMatch> = (selector: Selector) => Matcher<TScope, TMatch>;
+
+export type Plugin<TScope, TMatch> =
+ (
+ next: MatcherCreator<TScope, TMatch>,
+ recurse: MatcherCreator<TScope, TMatch>,
+ ) => typeof next;
+
+
+// Basic form of a plugin: (two equivalents)
+// const identity: Plugin<any, any> = (next, recurse) => next;
+// const identity: Plugin<any, any> = (next, recurse) => {
+// return function (selector: Selector): Matcher<any, any> {
+// return next(selector); // or do something else here.
+// };
+// };
diff --git a/web/index.js b/web/index.js
index abb9a8b..d0c33f8 100644
--- a/web/index.js
+++ b/web/index.js
@@ -21,14 +21,18 @@
/* global info, module, source, target, form */
import {
- makeCreateRangeSelectorMatcher,
createTextQuoteSelectorMatcher,
describeTextQuote,
+ supportRangeSelector,
createTextPositionSelectorMatcher,
describeTextPosition,
highlightRange,
} from '@apache-annotator/dom';
-import { makeRefinable } from '@apache-annotator/selector';
+import {
+ composeMatcherCreator,
+ mapSelectorTypes,
+ supportRefinement,
+} from '@apache-annotator/selector';
const EXAMPLE_SELECTORS = [
{
@@ -96,19 +100,14 @@
info.innerText = '';
}
-const createMatcher = makeRefinable((selector) => {
- const innerCreateMatcher = {
+const createMatcher = composeMatcherCreator(
+ supportRefinement, // this plugin must come first: it needs to access the result of the ones below.
+ supportRangeSelector,
+ mapSelectorTypes({
TextQuoteSelector: createTextQuoteSelectorMatcher,
TextPositionSelector: createTextPositionSelectorMatcher,
- RangeSelector: makeCreateRangeSelectorMatcher(createMatcher),
- }[selector.type];
-
- if (!innerCreateMatcher) {
- throw new Error(`Unsupported selector type: ${selector.type}`);
- }
-
- return innerCreateMatcher(selector);
-});
+ }),
+);
async function anchor(selector) {
const matchAll = createMatcher(selector);