You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ra...@apache.org on 2019/07/01 00:17:34 UTC
[incubator-annotator] branch master updated (64c22d6 -> a96b01c)
This is an automated email from the ASF dual-hosted git repository.
randall pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git.
from 64c22d6 Small copy-edits to demo
new 717e928 Clean up demo code
new c94ccda Refactor text quote affix disambiguation
new 4a303af Fix demo race condition
new a96b01c Remove demo error display
The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
demo/index.html | 7 ++--
demo/index.js | 81 +++++++++++++++++++++---------------------
packages/dom/src/text-quote.js | 75 +++++++++++++++++++-------------------
3 files changed, 79 insertions(+), 84 deletions(-)
[incubator-annotator] 01/04: Clean up demo code
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
randall pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit 717e9287a67a7f99f3105bdd2e446841e1f340a0
Author: Randall Leeds <ra...@apache.org>
AuthorDate: Sun Jun 30 12:07:46 2019 -0700
Clean up demo code
---
demo/index.js | 67 ++++++++++++++++++++++++++++++-----------------------------
1 file changed, 34 insertions(+), 33 deletions(-)
diff --git a/demo/index.js b/demo/index.js
index 0c13c4a..47fbe1d 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -15,63 +15,64 @@
/* global corpus, debug, module, selectable */
-import * as fragment from '@annotator/fragment-identifier';
+import {
+ parse as parseFragment,
+ stringify as stringifyFragment,
+ SyntaxError as FragmentSyntaxError,
+} from '@annotator/fragment-identifier';
import { describeTextQuoteByRange as describeRange } from '@annotator/dom';
import { mark } from './mark.js';
import { search } from './search.js';
-const refresh = async () => {
+function clear() {
corpus.innerHTML = selectable.innerHTML;
+}
- debug.classList.remove('error');
+const refresh = async () => {
+ clear();
const identifier = window.location.hash.slice(1);
if (!identifier) return;
try {
- const { selector } = fragment.parse(identifier);
+ const { selector } = parseFragment(identifier);
+ for await (const range of search(corpus, selector)) mark(range);
+ debug.classList.remove('error');
debug.innerText = JSON.stringify(selector, null, 2);
- const results = search(corpus, selector);
- const ranges = [];
- for await (let range of results) {
- ranges.push(range);
- }
- for (let range of ranges) {
- mark(range);
- }
} catch (e) {
debug.classList.add('error');
debug.innerText = JSON.stringify(e, null, 2);
- if (e instanceof fragment.SyntaxError) return;
+ if (e instanceof FragmentSyntaxError) return;
else throw e;
}
};
-async function onSelectionChange() {
+async function describeSelection() {
const selection = document.getSelection();
- if (selection === null || selection.isCollapsed) {
- return;
- }
+ if (selection.isCollapsed) return;
+
const range = selection.getRangeAt(0);
- if (!isWithinNode(range, selectable)) {
- return;
- }
- const selectableRange = document.createRange();
- selectableRange.selectNodeContents(selectable);
- const descriptor = await describeRange({ range, context: selectableRange });
- const nextFragment = fragment.stringify(descriptor);
- window.history.replaceState(descriptor, null, `#${nextFragment}`);
- refresh();
+ const context = document.createRange();
+ context.selectNodeContents(selectable);
+
+ if (!context.isPointInRange(range.startContainer, range.startOffset)) return;
+ if (!context.isPointInRange(range.endContainer, range.endOffset)) return;
+
+ return describeRange({ range, context });
}
-function isWithinNode(range, node) {
- const nodeRange = document.createRange();
- nodeRange.selectNode(node);
- return (
- range.compareBoundaryPoints(Range.START_TO_START, nodeRange) >= 0 &&
- range.compareBoundaryPoints(Range.END_TO_END, nodeRange) <= 0
- );
+async function onSelectionChange() {
+ const selector = await describeSelection();
+
+ if (selector) {
+ const fragment = stringifyFragment(selector);
+ window.history.replaceState(selector, null, `#${fragment}`);
+ } else {
+ window.history.replaceState(null, null, location.pathname);
+ }
+
+ refresh();
}
window.addEventListener('popstate', refresh);
[incubator-annotator] 03/04: Fix demo race condition
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
randall pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit 4a303af8ae0d721474fba760cbf779f6589c459f
Author: Randall Leeds <ra...@apache.org>
AuthorDate: Sun Jun 30 17:14:14 2019 -0700
Fix demo race condition
Concurrently highlighting and searching is not safe because it will
modify the nodes during iteration.
---
demo/index.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/demo/index.js b/demo/index.js
index 47fbe1d..290ba69 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -37,7 +37,16 @@ const refresh = async () => {
try {
const { selector } = parseFragment(identifier);
- for await (const range of search(corpus, selector)) mark(range);
+ const ranges = [];
+
+ for await (const range of search(corpus, selector)) {
+ ranges.push(range);
+ }
+
+ for (const range of ranges) {
+ mark(range);
+ }
+
debug.classList.remove('error');
debug.innerText = JSON.stringify(selector, null, 2);
} catch (e) {
[incubator-annotator] 02/04: Refactor text quote affix
disambiguation
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
randall pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit c94ccda2b90fb34b72d0f387e1314bddbfdff308
Author: Randall Leeds <ra...@apache.org>
AuthorDate: Sun Jun 30 16:45:21 2019 -0700
Refactor text quote affix disambiguation
---
packages/dom/src/text-quote.js | 75 ++++++++++++++++++++----------------------
1 file changed, 36 insertions(+), 39 deletions(-)
diff --git a/packages/dom/src/text-quote.js b/packages/dom/src/text-quote.js
index ddebfbc..bc56fa1 100644
--- a/packages/dom/src/text-quote.js
+++ b/packages/dom/src/text-quote.js
@@ -133,8 +133,7 @@ export async function describeTextQuoteByRange({ range, context }) {
: seek(iter, startNode);
const endIndex = startIndex + exact.length;
- const minSuffixes = [];
- const minPrefixes = [];
+ const affixLengthPairs = [[0, 0]];
for await (const match of selector(context)) {
const matchIter = createNodeIterator(root, SHOW_TEXT);
@@ -152,34 +151,33 @@ export async function describeTextQuoteByRange({ range, context }) {
}
// Determine how many prefix characters are shared.
- const prefixOverlap = overlapRight(
+ const prefixLength = overlapRight(
text.substring(0, matchStartIndex),
text.substring(0, startIndex),
);
// Determine how many suffix characters are shared.
- const suffixOverlap = overlap(
+ const suffixLength = overlap(
text.substring(matchEndIndex),
text.substring(endIndex),
);
- // Record the prefix or suffix lengths that would not have matched.
- minPrefixes.push(prefixOverlap + 1);
- minSuffixes.push(suffixOverlap + 1);
+ // Record the affix lengths that would have precluded this match.
+ affixLengthPairs.push([prefixLength + 1, suffixLength + 1]);
}
// Construct and return an unambiguous selector.
const result = { type: 'TextQuoteSelector', exact };
- if (minPrefixes.length > 0 || minSuffixes.length > 0) {
- const [minPrefix, minSuffix] = minimalSolution(minPrefixes, minSuffixes);
+ if (affixLengthPairs.length) {
+ const [prefixLength, suffixLength] = minimalSolution(affixLengthPairs);
- if (minPrefix > 0) {
- result.prefix = text.substring(startIndex - minPrefix, startIndex);
+ if (prefixLength > 0) {
+ result.prefix = text.substring(startIndex - prefixLength, startIndex);
}
- if (minSuffix > 0) {
- result.suffix = text.substring(endIndex, endIndex + minSuffix);
+ if (suffixLength > 0) {
+ result.suffix = text.substring(endIndex, endIndex + suffixLength);
}
}
@@ -188,44 +186,43 @@ export async function describeTextQuoteByRange({ range, context }) {
function overlap(text1, text2) {
let count = 0;
- while (text1[count] === text2[count]) {
+
+ while (count < text1.length && count < text2.length) {
+ const c1 = text1[count];
+ const c2 = text2[count];
+ if (c1 !== c2) break;
count++;
- if (count >= text1.length) {
- return Infinity;
- }
}
+
return count;
}
function overlapRight(text1, text2) {
let count = 0;
- while (text1[text1.length - 1 - count] === text2[text2.length - 1 - count]) {
+
+ while (count < text1.length && count < text2.length) {
+ const c1 = text1[text1.length - 1 - count];
+ const c2 = text2[text2.length - 1 - count];
+ if (c1 !== c2) break;
count++;
- if (count >= text1.length) {
- return Infinity;
- }
}
+
return count;
}
-function minimalSolution(reqs1, reqs2) {
- if (reqs1.length !== reqs2.length) {
- throw new Error('unequal lengths');
- }
- // Add 0 as an option to try.
- reqs1.push(0);
- reqs2.push(0);
- let bestResult = [Infinity, Infinity];
- for (let i = 0; i < reqs1.length; i++) {
- const req1 = reqs1[i];
- // The values to satisfy for req2, given the proposed req1.
- const reqsToSatisfy = reqs1.map((v, i) => (v > req1 ? reqs2[i] : 0));
- // Take the lowest value that satisfies them all.
- const req2 = Math.max(...reqsToSatisfy);
- // If this combination is the best so far, remember it.
- if (req1 + req2 < bestResult[0] + bestResult[1]) {
- bestResult = [req1, req2];
+function minimalSolution(requirements) {
+ // Build all the pairs and order them by their sums.
+ const pairs = requirements.flatMap(l => requirements.map(r => [l[0], r[1]]));
+ pairs.sort((a, b) => a[0] + a[1] - (b[0] + b[1]));
+
+ // Find the first pair that satisfies every requirement.
+ for (const pair of pairs) {
+ const [p0, p1] = pair;
+ if (requirements.every(([r0, r1]) => r0 <= p0 || r1 <= p1)) {
+ return pair;
}
}
- return bestResult;
+
+ // Return the largest pairing (unreachable).
+ return pairs[pairs.length - 1];
}
[incubator-annotator] 04/04: Remove demo error display
Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
randall pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git
commit a96b01cd5e058454f444611ef19907a9ad749430
Author: Randall Leeds <ra...@apache.org>
AuthorDate: Sun Jun 30 17:17:08 2019 -0700
Remove demo error display
---
demo/index.html | 7 ++-----
demo/index.js | 33 ++++++++++++---------------------
2 files changed, 14 insertions(+), 26 deletions(-)
diff --git a/demo/index.html b/demo/index.html
index a41a949..1b62b4c 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -28,14 +28,11 @@ specific language governing permissions and limitations under the License.
background-color: rgba(255, 255, 120, 0.5);
outline: 0.1px solid rgba(255, 100, 0, 0.8);
}
- #debug {
+ #selector {
color: #666;
background: #f8f8f8;
padding: 2em;
}
- #debug.error {
- color: red;
- }
#selectable, #corpus {
display: inline-block;
max-width: 15em;
@@ -73,7 +70,7 @@ specific language governing permissions and limitations under the License.
<a href="https://www.w3.org/TR/2017/NOTE-selectors-states-20170223/#frags"
target="_blank">as the fragment identifier</a>.</p>
<p>Here is the selector in JSON format:</p>
- <pre id="debug"></pre>
+ <pre id="parsed"></pre>
<p>Notice how, when the text of your selection appears multiple times, just
enough characters around it are stored in the selector to find the right
occurrence again.</p>
diff --git a/demo/index.js b/demo/index.js
index 290ba69..78f0d23 100644
--- a/demo/index.js
+++ b/demo/index.js
@@ -13,12 +13,11 @@
* the License.
*/
-/* global corpus, debug, module, selectable */
+/* global corpus, module, parsed, selectable */
import {
parse as parseFragment,
stringify as stringifyFragment,
- SyntaxError as FragmentSyntaxError,
} from '@annotator/fragment-identifier';
import { describeTextQuoteByRange as describeRange } from '@annotator/dom';
@@ -35,26 +34,18 @@ const refresh = async () => {
const identifier = window.location.hash.slice(1);
if (!identifier) return;
- try {
- const { selector } = parseFragment(identifier);
- const ranges = [];
-
- for await (const range of search(corpus, selector)) {
- ranges.push(range);
- }
-
- for (const range of ranges) {
- mark(range);
- }
-
- debug.classList.remove('error');
- debug.innerText = JSON.stringify(selector, null, 2);
- } catch (e) {
- debug.classList.add('error');
- debug.innerText = JSON.stringify(e, null, 2);
- if (e instanceof FragmentSyntaxError) return;
- else throw e;
+ const { selector } = parseFragment(identifier);
+ const ranges = [];
+
+ for await (const range of search(corpus, selector)) {
+ ranges.push(range);
+ }
+
+ for (const range of ranges) {
+ mark(range);
}
+
+ parsed.innerText = JSON.stringify(selector, null, 2);
};
async function describeSelection() {