Add convenience matcher functions to dom package
- createAnySelectorMatcher mimics the signature of existing matcher creators.
- matchSelector makes the common use case easy.
diff --git a/packages/dom/src/index.ts b/packages/dom/src/index.ts
index 6969ea9..5073811 100644
--- a/packages/dom/src/index.ts
+++ b/packages/dom/src/index.ts
@@ -21,6 +21,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+export * from './match.js';
export * from './css.js';
export * from './range/index.js';
export * from './text-quote/index.js';
diff --git a/packages/dom/src/match.ts b/packages/dom/src/match.ts
new file mode 100644
index 0000000..d0038f8
--- /dev/null
+++ b/packages/dom/src/match.ts
@@ -0,0 +1,101 @@
+/**
+ * @license
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-FileCopyrightText: The Apache Software Foundation
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { asArray, OneOrMore } from "@apache-annotator/annotation";
+import type {
+ CssSelector,
+ TextQuoteSelector,
+ TextPositionSelector,
+ RangeSelector,
+ Matcher,
+ Refinable,
+} from "@apache-annotator/selector";
+import { makeRefinable } from "@apache-annotator/selector";
+import { createCssSelectorMatcher } from "./css";
+import { makeCreateRangeSelectorMatcher } from "./range";
+import { createTextPositionSelectorMatcher } from "./text-position";
+import { createTextQuoteSelectorMatcher } from "./text-quote";
+
+export const supportedSelectorTypes = [
+ "CssSelector",
+ "TextQuoteSelector",
+ "TextPositionSelector",
+ "RangeSelector",
+];
+
+export type SupportedSelector = Refinable<
+ | CssSelector
+ | TextQuoteSelector
+ | TextPositionSelector
+ | RangeSelector<SupportedSelector>
+>;
+
+export type DomScope = Node | Range;
+export type DomMatch = Element | Range;
+export type DomMatcher = Matcher<DomScope, DomMatch>;
+
+const createMatcher: (
+ selector: SupportedSelector
+) => DomMatcher = makeRefinable<SupportedSelector, DomScope, DomMatch>(
+ (selector: SupportedSelector) => {
+ if (selector.type === "CssSelector")
+ return createCssSelectorMatcher(selector);
+ if (selector.type === "TextQuoteSelector")
+ return createTextQuoteSelectorMatcher(selector);
+ if (selector.type === "TextPositionSelector")
+ return createTextPositionSelectorMatcher(selector);
+ if (selector.type === "RangeSelector")
+ return makeCreateRangeSelectorMatcher(
+ // @ts-ignore (needless type error; bug in TypeScript?)
+ createMatcher
+ )(selector);
+ throw new Error(`Unsupported selector type: ${(selector as any)?.type}`);
+ }
+);
+
+export function createAnySelectorMatcher(
+ oneOrMoreSelectors: OneOrMore<SupportedSelector>
+): DomMatcher {
+ const selectors = asArray(oneOrMoreSelectors);
+ // Use the first selector we understand. (“Multiple Selectors SHOULD select the same content”)
+ // TODO Take the more precise one; retry with others if the first fails; perhaps combine e.g. Position+Quote for speedup.
+ const selector = selectors.find(
+ (selector) =>
+ selector.type && supportedSelectorTypes.includes(selector.type)
+ );
+ if (!selector) throw new Error(`Unsupported selector type: ${asArray(selectors).map(s => s.type)}`);
+ const matcher = createMatcher(selector as SupportedSelector)
+ return matcher;
+}
+
+export async function matchSelector(
+ selectors: OneOrMore<SupportedSelector>,
+ scope: DomScope = window.document
+) {
+ const matchGenerator = createAnySelectorMatcher(selectors)(scope);
+ const matches: DomMatch[] = [];
+ for await (const match of matchGenerator) {
+ matches.push(match);
+ }
+ return matches;
+}
diff --git a/web/index.js b/web/index.js
index 7917798..bdaa31c 100644
--- a/web/index.js
+++ b/web/index.js
@@ -24,14 +24,11 @@
/* global info, module, source, target, form */
import {
- makeCreateRangeSelectorMatcher,
- createTextQuoteSelectorMatcher,
+ matchSelector,
describeTextQuote,
- createTextPositionSelectorMatcher,
describeTextPosition,
highlightText,
} from '@apache-annotator/dom';
-import { makeRefinable } from '@apache-annotator/selector';
const EXAMPLE_SELECTORS = [
{
@@ -99,29 +96,8 @@
info.innerText = '';
}
-const createMatcher = makeRefinable((selector) => {
- const innerCreateMatcher = {
- 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);
- const ranges = [];
-
- // First collect all matches, and only then highlight them; to avoid
- // modifying the DOM while the matcher is running.
- for await (const range of matchAll(target)) {
- ranges.push(range);
- }
+ const ranges = matchSelector(selector, target);
for (const range of ranges) {
const removeHighlight = highlightText(range);