@samuelbradshaw/highlight-helper-js 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +5 -5
- package/README.md +45 -28
- package/demo.html +96 -50
- package/highlight-helper.js +1394 -1247
- package/package.json +1 -1
|
@@ -17,18 +17,18 @@ jobs:
|
|
|
17
17
|
- name: Update package.json
|
|
18
18
|
run: npm version "${{ steps.get_version.outputs.RELEASE_VERSION }}" --no-git-tag-version
|
|
19
19
|
- name: Commit package.json changes
|
|
20
|
-
uses: stefanzweifel/git-auto-commit-action@
|
|
20
|
+
uses: stefanzweifel/git-auto-commit-action@v7
|
|
21
21
|
with:
|
|
22
22
|
commit_message: "Update package.json version to ${{ steps.get_version.outputs.RELEASE_VERSION }}"
|
|
23
23
|
branch: main
|
|
24
24
|
- name: Set up Node.js
|
|
25
|
-
uses: actions/setup-node@
|
|
25
|
+
uses: actions/setup-node@v6
|
|
26
26
|
with:
|
|
27
|
-
node-version: '
|
|
27
|
+
node-version: '24.x'
|
|
28
28
|
registry-url: 'https://registry.npmjs.org'
|
|
29
29
|
- name: Install dependencies
|
|
30
30
|
run: npm ci
|
|
31
|
+
- name: Build
|
|
32
|
+
run: npm run build --if-present
|
|
31
33
|
- name: Push to npmjs
|
|
32
34
|
run: npm publish --provenance --access public
|
|
33
|
-
env:
|
|
34
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HighlightHelper.js
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**HighlightHelper.js** is a JavaScript tool that enables highlighting or underlining text in an HTML page (such as a digital book or article). Highlighting text in an HTML document can be difficult, especially in cases where a highlight starts in one element but ends in another.
|
|
4
4
|
|
|
5
|
-
Behind the scenes,
|
|
5
|
+
Behind the scenes, HighlightHelper.js supports three different mechanisms for drawing highlights:
|
|
6
6
|
|
|
7
7
|
1. [SVG shapes](https://developer.mozilla.org/en-US/docs/Web/SVG) drawn behind text (default).
|
|
8
8
|
2. The [CSS Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API) (experimental*).
|
|
9
9
|
3. Inserted [HTML mark elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/mark).
|
|
10
10
|
|
|
11
|
-
An HTML demo page that shows basic functionality can be found here: [
|
|
11
|
+
An HTML demo page that shows basic functionality can be found here: [HighlightHelper.js Demo](https://samuelbradshaw.github.io/highlight-helper-js/demo.html). Source code for the demo is in **demo.html**. HighlightHelper.js itself is in **highlight-helper.js**.
|
|
12
12
|
|
|
13
|
-
*The Custom Highlight API requires Safari 17.2+ or Chrome 105+, and may hang when rendering a large number of highlights. Additionally, only a few CSS styles are supported (see [Styling Highlights](https://www.w3.org/TR/css-pseudo-4/#highlight-styling)). Browser support and loading speed will hopefully improve in the future. See also the [load test page](https://samuelbradshaw.github.io/highlight-helper-js/test-load.html) for
|
|
13
|
+
*The Custom Highlight API requires Safari 17.2+ or Chrome 105+, and may hang when rendering a large number of highlights. Additionally, only a few CSS styles are supported (see [Styling Highlights](https://www.w3.org/TR/css-pseudo-4/#highlight-styling)). Browser support and loading speed will hopefully improve in the future. See also the [load test page](https://samuelbradshaw.github.io/highlight-helper-js/test-load.html) for HighlightHelper.js.
|
|
14
14
|
|
|
15
15
|
### Documentation:
|
|
16
16
|
- [Known issues](#known-issues)
|
|
@@ -28,7 +28,7 @@ There are a few known issues:
|
|
|
28
28
|
|
|
29
29
|
- **Chrome on Android** doesn’t set focus (and therefore doesn’t show text selection handles) when text is programmatically selected (such as from a disambiguation panel), unless the selection is initiated directly by a user’s tap. However, if you’re working in a native app with a webview, you may be able to focus the webview from Android (see [StackOverflow](https://stackoverflow.com/a/6372903/1349044) and [GitHub](https://github.com/lawlsausage/save_gfy/pull/1)).
|
|
30
30
|
- **Safari on iOS** doesn’t respect the text selection color set via CSS, so when a highlight is active for editing it has a blue overlay. However, if you’re working in a native app with a webview, you may be able to set colors natively with [tintColor](https://stackoverflow.com/a/60510743/1349044), [highlightView](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195471-highlightview), and/or [handleViews](https://developer.apple.com/documentation/uikit/uitextselectiondisplayinteraction/4195470-handleviews).
|
|
31
|
-
- **Safari on iOS** loses focus and doesn’t send a `visibilitychange` event if the device screen locks after a period of inactivity. This can cause selection handles to not show when unlocking your device and tapping on an existing highlight. Going to another app and back or manually triggering a `visibilitychange` event will allow
|
|
31
|
+
- **Safari on iOS** loses focus and doesn’t send a `visibilitychange` event if the device screen locks after a period of inactivity. This can cause selection handles to not show when unlocking your device and tapping on an existing highlight. Going to another app and back or manually triggering a `visibilitychange` event will allow HighlightHelper.js to set focus again. Focus will also be set again if the user long-presses to select text.
|
|
32
32
|
- **Safari on iOS and macOS** doesn’t allow setting underline thickness on a ::highlight() pseudo-element, so underline highlights drawn by the Custom Highlight API are thin and hard to see (see [StackOverflow](https://stackoverflow.com/q/79060854/1349044) and [WebKit Bugzilla](https://bugs.webkit.org/show_bug.cgi?id=282027)). This will hopefully be fixed in a future version of Safari.
|
|
33
33
|
|
|
34
34
|
|
|
@@ -40,67 +40,82 @@ In the source of demo.html, you’ll see CSS styles, followed by the HTML body,
|
|
|
40
40
|
1. Links to highlight-helper.js,
|
|
41
41
|
2. Initializes a “Highlighter” instance with options,
|
|
42
42
|
3. Pre-loads and draws a few existing highlights,
|
|
43
|
-
3. Sets up logic to call
|
|
44
|
-
4. Sets up listeners to respond to custom event messages that come back from
|
|
43
|
+
3. Sets up logic to call HighlightHelper.js methods when buttons on the demo page are tapped, and
|
|
44
|
+
4. Sets up listeners to respond to custom event messages that come back from HighlightHelper.js.
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
### <a name="installation-options"></a>Installation options
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
HighlightHelper.js is available as classic JavaScript (highlight-helper.js) or as a [JavaScript module](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) (highlight-helper.mjs).
|
|
50
|
+
|
|
51
|
+
#### Classic JavaScript
|
|
52
|
+
|
|
53
|
+
Download highlight-helper.js from GitHub and reference it locally in your HTML file:
|
|
50
54
|
```html
|
|
51
55
|
<script src="scripts/highlight-helper.js"></script>
|
|
52
56
|
```
|
|
53
57
|
|
|
54
|
-
Or
|
|
58
|
+
Or load it from [jsDelivr](https://www.jsdelivr.com/package/gh/samuelbradshaw/highlight-helper-js) CDN:
|
|
55
59
|
```html
|
|
56
60
|
<script src="https://cdn.jsdelivr.net/gh/samuelbradshaw/highlight-helper-js@main/highlight-helper.min.js"></script>
|
|
57
61
|
```
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
#### JavaScript Module
|
|
64
|
+
|
|
65
|
+
Download highlight-helper.js and highlight-helper.mjs from GitHub and import as a JavaScript module:
|
|
66
|
+
```javascript
|
|
67
|
+
import { Highlighter } from './highlight-helper.mjs';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or load it from [jsDelivr](https://www.jsdelivr.com/package/gh/samuelbradshaw/highlight-helper-js) CDN:
|
|
71
|
+
```javascript
|
|
72
|
+
import { Highlighter } from 'https://cdn.jsdelivr.net/gh/samuelbradshaw/highlight-helper-js@main/highlight-helper.min.mjs';
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
You can also install it using [npm](https://www.npmjs.com/package/@samuelbradshaw/highlight-helper-js):
|
|
60
76
|
```bash
|
|
61
77
|
% cd /your/project/folder
|
|
62
78
|
% npm i @samuelbradshaw/highlight-helper-js
|
|
63
79
|
```
|
|
64
80
|
|
|
65
|
-
|
|
66
81
|
## <a name="methods-options-and-custom-events"></a>Methods, options, and custom events
|
|
67
82
|
|
|
68
83
|
### <a name="methods"></a>Methods
|
|
69
84
|
|
|
70
|
-
- **loadHighlights(highlights)** – Load an array of existing highlights into
|
|
85
|
+
- **loadHighlights(highlights)** – Load an array of existing highlights into HighlightHelper.js. Each highlight should have one or more of the properties described under “Highlight attributes” below. Omitted or invalid properties will fall back to previous or default values. If highlights are already loaded, this method will replace the loaded highlights by diffing for changes, then adding, removing, or updating individual highlights as needed.
|
|
71
86
|
- **createOrUpdateHighlight(attributes, triggeredByUserAction)** – Create or update an individual highlight. `attributes` is an object with one or more of the properties described under “Highlight attributes” below. If a highlight ID isn’t passed in, the currently-active highlight will be updated, if there is one, otherwise a new highlight will be created. If it’s a new highlight and start and end bounds aren’t passed in, the highlight will be created based on selected text. `triggeredByUserAction` is an optional boolean (default: `true`). If this is set to false, the highlight won’t be activated after editing, and the color and style won’t be saved as the default for the next highlight.
|
|
72
|
-
- **drawHighlights(highlightIds)** – Draw or redraw highlights on the page, given an array of highlight IDs. If no highlight IDs are passed, all highlights will be drawn. This shouldn’t need to be called frequently (
|
|
87
|
+
- **drawHighlights(highlightIds)** – Draw or redraw highlights on the page, given an array of highlight IDs. If no highlight IDs are passed, all highlights will be drawn. This shouldn’t need to be called frequently (HighlightHelper.js will usually draw or redraw highlights automatically).
|
|
73
88
|
- **activateHighlight(highlightId)** – Activate a highlight for editing, given its `highlightId`. Only one highlight can be active at a time (because it’s tied to text selection, and there can only be one text selection on a page). Normally, a highlight activates automatically when the user taps it; however, if there are overlapping highlights or if `autoTapToActivate` is set to `false`, this will need to be called manually.
|
|
74
89
|
- **activateHyperlink(position)** – Activate the specified hyperlink (i.e. open the link). `position` is the 0-based position of the link in the annotatable area of the page, relative to other links. Normally, a link opens automatically when the user taps it; however, if a highlight overlaps the hyperlink or if `autoTapToActivate` is set to `false`, this will need to be called manually.
|
|
75
90
|
- **deactivateHighlights()** – Clear the text selection and deactivate any active highlights. Normally, highlights are deactivated automatically when the user taps away.
|
|
76
91
|
- **removeHighlights(highlightIds)** – Remove the specified highlights. If `highlightIds` isn’t provided, all highlights will be removed.
|
|
77
92
|
- **getActiveHighlightId()** – Get the ID of the currently-active highlight (if there is one).
|
|
78
93
|
- **getHighlightInfo(highlightIds, paragraphId)** – Get highlight information for an array of highlight IDs. If no highlight IDs are passed, information for all relevant highlights will be returned. If `paragraphId` is provided, only highlights that start in the specified paragraph will be returned. Highlights will be sorted based on their position on the page.
|
|
79
|
-
- **
|
|
94
|
+
- **setOptions(optionsToUpdate)** – Change one or more options. `optionsToUpdate` is an object with one or more of the option keys described below.
|
|
80
95
|
- **getOptions()** – Get the initialized options, including defaults for any options that weren’t explicitly set.
|
|
81
96
|
- **removeHighlighter()** – Removes the current Highlighter instance and all of its highlights from the page. If a new Highlighter instance is created with the same container (as defined by the `containerSelector` option), the previous instance for that container will be removed automatically (there can be multiple Highlighter instances on a page, but each one needs to have a different container).
|
|
82
97
|
|
|
83
98
|
|
|
84
99
|
### <a name="options"></a>Options
|
|
85
100
|
|
|
86
|
-
Options can be provided when
|
|
101
|
+
Options can be provided when HighlightHelper.js is initialized. They can be updated after initialization using `setOptions()`.
|
|
87
102
|
|
|
88
103
|
- **containerSelector** – CSS selector for the section of the page that should be annotatable. Default: `body`.
|
|
89
104
|
- **paragraphSelector** – CSS selector for the paragraphs (or other block elements) on the page that should be annotatable. Each paragraph is expected to have an ID attribute in the HTML, which is used to keep track of where a highlight starts and ends. Default: `h1[id], h2[id], h3[id], h4[id], h5[id], h6[id], p[id], ol[id], ul[id], dl[id], tr[id]`.
|
|
90
105
|
- **colors** – Object that describes available highlight colors. Keys are color names, and values are [CSS color values](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). Default: red, orange, yellow, green, blue (see full default values at the bottom of highlight-helper.js).
|
|
91
106
|
- **styles** – Object that describes available highlight styles. Keys are style names, and there are four properties for each style: `css`, `svg`, `css-active` (optional), and `svg-active` (optional). `css` is a CSS string used for styling highlights in `highlight-api` and `mark-elements` drawing modes, as well as for read-only highlights. `svg` is an SVG string (one or more SVG shapes) to represent the highlight in `svg` drawing mode. `css-active` and `svg-active` are alternative styles to be used when a highlight is active. The CSS custom property `var(--hh-color)` can be used to reference the highlight color value. For SVG highlights, the following variables (if present) will be replaced with relevant values from the highlight’s DOMRect: `{x}`, `{y}`, `{width}`, `{height}`, `{top}`, `{bottom}`, `{left}`, `{right}`. Default: fill, single-underline, double-underline, colored-text, redacted (see full default values at the bottom of highlight-helper.js).
|
|
92
|
-
- **wrappers** – Object that describes available highlight wrappers. Keys are wrapper names, and values are objects with two optional properties: `start` (HTML string to be inserted at the beginning of the highlight), and `end` (HTML string to be inserted at the end of the highlight).
|
|
107
|
+
- **wrappers** – Object that describes available highlight wrappers. Keys are wrapper names, and values are objects with two optional properties: `start` (HTML string to be inserted at the beginning of the highlight), and `end` (HTML string to be inserted at the end of the highlight). `var(--hh-color)` can be used to reference the highlight color value. Variables surrounded by curly brackets will be replaced with variables stored in the `wrapperVariables` attribute of the highlight, if applicable. See default values at the bottom of highlight-helper.js.
|
|
93
108
|
- **selectionHandles** – Object that describes custom selection handles that a user can drag to resize a selection or highlight. Because most touch devices have built-in selection handles, custom selection handles will only show when using a mouse or trackpad. There are two properties: `left` (HTML string to be used for the left selection handle) and `right` (HTML string to be used for the right selection handle). `var(--hh-color)` can be used to reference the highlight color value. See default values at the bottom of highlight-helper.js.
|
|
94
109
|
- **rememberStyle** – Whether the most recent color, style, and wrapper should be remembered and used by default the next time the user creates a highlight. Boolean. Default: `true`.
|
|
95
110
|
- **snapToWord** – Whether highlights should snap to the nearest word boundary. Spaces and dashes are considered word boundaries (this option may not work correctly in all languages). Boolean. Default: `false`.
|
|
96
|
-
- **autoTapToActivate** – Whether
|
|
97
|
-
- **longPressTimeout** – Duration in milliseconds before a tap turns into a long-press. On long-press,
|
|
111
|
+
- **autoTapToActivate** – Whether HighlightHelper.js should automatically activate highlights and hyperlinks when they’re tapped. If set to false, you’ll need to listen for the `hh:tap` event and call `activateHighlight()` or `activateHyperlink()` manually when needed. Boolean. Default: `true`.
|
|
112
|
+
- **longPressTimeout** – Duration in milliseconds before a tap turns into a long-press. On long-press, HighlightHelper.js will send an `hh:tap` event with `isLongPress: true`. If this option is set to 0, HighlightHelper.js will treat long-presses the same as regular taps, sending the `hh:tap` event after the user lifts their finger. If you have access to system APIs (such as in a mobile app), you may be able to get the system long-press duration to use for this value (which may vary based on accessibility settings). For example, Android has a [getLongPressTimeout()](https://developer.android.com/reference/kotlin/android/view/ViewConfiguration#getlongpresstimeout) method and iOS has a [minimumPressDuration](https://developer.apple.com/documentation/uikit/uilongpressgesturerecognizer/minimumpressduration) property. Default: `500`.
|
|
98
113
|
- **pointerMode** – Mode for responding to pointer events. Options are `simple` (create highlights by selecting text, then tapping a color or style); `live` (create highlights immediately when selecting text, without needing to tap a button); and `auto` (`simple` for touch and mouse input, but `live` when a stylus or Apple Pencil is detected). Default: `auto`.
|
|
99
114
|
- **drawingMode** – Mode for drawing highlights on the page. Options are `svg` (SVG shapes), `highlight-api` (Custom Highlight API), and `mark-elements` (inserted HTML mark elements). For faster performance, read-only highlights will always be drawn as mark elements, even if a different drawing mode is set. Default: `svg`.
|
|
100
115
|
- **defaultColor** – Key of the default highlight color. Default: `yellow`.
|
|
101
116
|
- **defaultStyle** – Key of the default highlight style. Default: `fill`.
|
|
102
117
|
- **defaultWrapper** – Key of the default highlight wrapper. Default: `none`.
|
|
103
|
-
- **highlightIdFunction** – Identifier of a function that provides unique IDs for new highlights. Default: `
|
|
118
|
+
- **highlightIdFunction** – Identifier of a function that provides unique IDs for new highlights. Default: `_getNewHighlightId`.
|
|
104
119
|
- **showCustomSelectionHandlesForActiveHighlights** – Whether custom selection handles should show for active highlights. Default: `true`.
|
|
105
120
|
- **showCustomSelectionHandlesForTextSelection** – Whether custom selection handles should show for text selection. Default: `false`.
|
|
106
121
|
- **showCustomSelectionHandlesOnTouch** – Whether custom selection handles should show on touch devices. Default: `false`.
|
|
@@ -108,23 +123,23 @@ Options can be provided when Highlight Helper is initialized. They can also be s
|
|
|
108
123
|
|
|
109
124
|
### <a name="custom-events"></a>Custom events
|
|
110
125
|
|
|
111
|
-
|
|
126
|
+
HighlightHelper.js sends [custom events](https://developer.mozilla.org/en-US/docs/Web/Events/Creating_and_triggering_events) to the container element (defined in the `containerSelector` option) that can be listened for and responded to:
|
|
112
127
|
|
|
113
128
|
- **hh:highlightsload** – Sent when an array of highlights loads. Includes the number of highlights added, removed, or updated, the total number of loaded highlights, and the time they took to load (in milliseconds).
|
|
114
129
|
- **hh:highlightcreate** – Sent when a new highlight is created. Includes information about the highlight.
|
|
115
130
|
- **hh:highlightupdate** – Sent when the highlight changes. Includes information about the highlight, and a list of the attributes that changed.
|
|
116
131
|
- **hh:highlightremove** – Sent when a highlight is removed. Includes the ID of the removed highlight.
|
|
117
|
-
- **hh:tap** – Sent when a user taps in the annotatable container (potentially trying to
|
|
132
|
+
- **hh:tap** – Sent when a user taps in the annotatable container (potentially trying to tap a highlight, wrapper, or link). This will only be sent if there isn’t currently an active highlight. Includes the location of the tap, and the highlight, wrapper, or hyperlink that was tapped, if any.
|
|
118
133
|
- **hh:selectionupdate** – Sent when the selection color or style changes. Can be used to update custom selection UI, such as the color of selection handles. Includes the key of the color and style.
|
|
119
134
|
- **hh:highlightactivate** – Sent when a highlight is activated. Includes information about the activated highlight.
|
|
120
135
|
- **hh:highlightdeactivate** – Sent when a highlight is deactivated. Includes the ID of the deactivated highlight.
|
|
121
|
-
- **hh:ambiguousaction** – Sent when a user taps on overlapping highlights
|
|
122
|
-
- **selectionchange** – This is a [standard JavaScript event](https://developer.mozilla.org/en-US/docs/Web/API/Document/selectionchange_event) (not specific to
|
|
136
|
+
- **hh:ambiguousaction** – Sent when a user taps on overlapping highlights, wrappers, and/or links. Includes the location of the tap, and the highlight(s), wrapper(s), and hyperlink(s) that were tapped. This event will not be sent if `autoTapToActivate` is set to `false`.
|
|
137
|
+
- **selectionchange** – This is a [standard JavaScript event](https://developer.mozilla.org/en-US/docs/Web/API/Document/selectionchange_event) (not specific to HighlightHelper.js), but it’s worth mentioning here because it can be useful for adding custom selection UI around the selected text (such as a floating annotation menu). When this sends, you can call `window.getSelection()` to get the current [Selection object](https://developer.mozilla.org/en-US/docs/Web/API/Selection).
|
|
123
138
|
|
|
124
139
|
|
|
125
140
|
## <a name="highlight-attributes"></a>Highlight attributes
|
|
126
141
|
|
|
127
|
-
These are the attributes that
|
|
142
|
+
These are the attributes that HighlightHelper.js stores for each highlight. You can update a highlight by calling `createOrUpdateHighlight(attributes)`, where `attributes` is an object that includes the keys to be updated.
|
|
128
143
|
|
|
129
144
|
- **highlightId** (read-only after creation) – The ID of the highlight.
|
|
130
145
|
- **color** – The color of the highlight (key from the `colors` object in the initialized options). Example: `red`.
|
|
@@ -133,11 +148,13 @@ These are the attributes that Highlight Helper stores for each highlight. You ca
|
|
|
133
148
|
- **wrapperVariables** – Variables used in wrappers. Example: `{ marker: 'a', }`.
|
|
134
149
|
- **readOnly** – Whether the highlight should be read-only (prevents a user from changing its color, bounds, or other attributes). Boolean. Example: `true`.
|
|
135
150
|
- **startParagraphId** – ID of the paragraph where the highlight starts. Example: `p1`.
|
|
136
|
-
- **startParagraphOffset** – Character offset where the highlight starts, relative to the beginning of the paragraph
|
|
151
|
+
- **startParagraphOffset** – Character offset where the highlight starts, relative to the beginning of the paragraph.* Example: 12.
|
|
137
152
|
- **endParagraphId** – ID of the paragraph where the highlight ends. Example: `p1`.
|
|
138
|
-
- **endParagraphOffset** – Character offset where the highlight ends, relative to the beginning of the paragraph
|
|
139
|
-
- **escapedHighlightId** (read-only) – Escaped highlight ID used as a CSS identifier (this is used internally by
|
|
153
|
+
- **endParagraphOffset** – Character offset where the highlight ends, relative to the beginning of the paragraph.* Example: 14.
|
|
154
|
+
- **escapedHighlightId** (read-only) – Escaped highlight ID used as a CSS identifier (this is used internally by HighlightHelper.js).
|
|
140
155
|
- **rangeText** (read-only) – Text content in the highlighted range.
|
|
141
156
|
- **rangeHtml** (read-only) – HTML content in the highlighted range.
|
|
142
157
|
- **rangeParagraphIds** (read-only) – IDs of paragraphs in the highlighted range.
|
|
143
158
|
- **rangeObj** (read-only) – The [Range](https://developer.mozilla.org/en-US/docs/Web/API/Range) object that represents where the highlight is drawn.
|
|
159
|
+
|
|
160
|
+
*If you have dynamic elements that need to be inserted into an annotatable paragraph temporarily, you can mark them with the `data-hh-ignore` attribute. HighlightHelper.js will skip over elements with this attribute when calculating character offsets and drawing highlights.
|
package/demo.html
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en" translate="no">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
|
-
<title>
|
|
5
|
+
<title>HighlightHelper.js Demo</title>
|
|
6
6
|
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1.0">
|
|
7
7
|
<meta name="google" content="notranslate">
|
|
8
8
|
<style>
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
--gray-90: hsl(0, 0%, 30%);
|
|
20
20
|
--gray-50: hsl(0, 0%, 70%);
|
|
21
21
|
}}
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
body {
|
|
24
24
|
background-color: var(--page-bg);
|
|
25
25
|
color: var(--text-color);
|
|
26
26
|
line-height: 1.4;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
main { margin-right: 2em; }
|
|
29
29
|
a { color: var(--gray-50); }
|
|
30
30
|
button {
|
|
31
31
|
cursor: pointer;
|
|
32
32
|
user-select: none;
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
#annotation-menu {
|
|
36
36
|
font-family: "Helvetica", sans-serif;
|
|
37
37
|
font-size: 0.8em;
|
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
button[name="hh-color"][value="yellow"] { background-color: hsl(50, 98%, 61%); }
|
|
55
55
|
button[name="hh-color"][value="green"] { background-color: hsl(75, 70%, 49%); }
|
|
56
56
|
button[name="hh-color"][value="blue"] { background-color: hsl(182, 86%, 47%); }
|
|
57
|
-
button[name="hh-style"], button[name="hh-wrapper"] {
|
|
57
|
+
button[name="hh-style"], button[name="hh-wrapper"] {
|
|
58
|
+
position: relative;
|
|
59
|
+
color: inherit; border: 2px solid var(--gray-50);
|
|
60
|
+
}
|
|
58
61
|
button[name="hh-remove"] { background-color: var(--gray-50); color: var(--page-bg); }
|
|
59
62
|
.highlight-placeholder {
|
|
60
63
|
background-color: var(--gray-90);
|
|
@@ -68,10 +71,25 @@
|
|
|
68
71
|
font-size: smaller;
|
|
69
72
|
line-height: 0.5em;
|
|
70
73
|
&::before {
|
|
71
|
-
content: attr(data-
|
|
74
|
+
content: attr(data-content) / attr(data-accessibility-label);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
.note-container {
|
|
78
|
+
display: inline-block;
|
|
79
|
+
visibility: visible;
|
|
80
|
+
font-style: normal;
|
|
81
|
+
height: 1em;
|
|
82
|
+
}
|
|
83
|
+
.note {
|
|
84
|
+
position: absolute;
|
|
85
|
+
right: 0.5rem;
|
|
86
|
+
font-size: 1.2rem;
|
|
87
|
+
margin-top: -0.2rem;
|
|
88
|
+
&::before {
|
|
89
|
+
content: attr(data-content) / attr(data-accessibility-label);
|
|
72
90
|
}
|
|
73
91
|
}
|
|
74
|
-
|
|
92
|
+
|
|
75
93
|
#disambiguation-panel {
|
|
76
94
|
color: inherit;
|
|
77
95
|
background-color: inherit;
|
|
@@ -105,14 +123,13 @@
|
|
|
105
123
|
</head>
|
|
106
124
|
<body>
|
|
107
125
|
<main>
|
|
108
|
-
<h1 id="title1">
|
|
109
|
-
<
|
|
110
|
-
<p id="p1"><b>Highlight Helper</b> enables highlighting or underlining text in an HTML page. Highlights are drawn via <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG shapes</a>, the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API">Custom Highlight API</a>, or <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/mark">HTML mark elements</a>. Highlights can be drawn across multiple paragraphs or <b>inline <i>style</i></b> elements—they can even overlap!</p>
|
|
126
|
+
<h1 id="title1">HighlightHelper.js Demo</h1>
|
|
127
|
+
<p id="p1"><b>HighlightHelper.js</b> enables highlighting or underlining text in an HTML page. Highlights are drawn via <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/SVG">SVG shapes</a>, the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API">Custom Highlight API</a>, or <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/mark">HTML mark elements</a>. Highlights can be drawn across multiple paragraphs or <b>inline <i>style</i></b> elements—they can even overlap!</p>
|
|
111
128
|
<p id="p2">Select text, then choose a color and style. Tap away when you’re done. Tap to activate it again for editing. Secondary elements like images (right) and Japanese ruby/furigana (below) are highlighted around.</p>
|
|
112
129
|
<p id="p3">わたしニーファイは<ruby><rb>善</rb><rt>よ</rt></ruby>い<ruby><rb>両</rb><rt>りょう</rt></ruby><ruby><rb>親</rb><rt>しん</rt></ruby>から<ruby><rb>生</rb><rt>う</rt></ruby>まれたので,<ruby><rb>父</rb><rt>ちち</rt></ruby>が<ruby><rb>学</rb><rt>まな</rt></ruby>んだすべてのことの<ruby><rb>中</rb><rt>なか</rt></ruby>から<ruby><rb>幾</rb><rt>いく</rt></ruby>らかの<ruby><rb>教</rb><rt>おし</rt></ruby>えを<ruby><rb>受</rb><rt>う</rt></ruby>けた。</p>
|
|
113
130
|
<p id="p4" style="direction: rtl;">فِي الْبَدْءِ خَلَقَ اللهُ السَّمَاوَاتِ وَالأَرْضَ. وَكَانَتِ الأَرْضُ خَرِبَةً وَخَالِيَةً، وَعَلَى وَجْهِ الْغَمْرِ ظُلْمَةٌ، وَرُوحُ اللهِ يَرِفُّ عَلَى وَجْهِ الْمِيَاهِ. وَقَالَ اللهُ: «لِيَكُنْ نُورٌ»، فَكَانَ نُورٌ.</p>
|
|
114
131
|
</main>
|
|
115
|
-
|
|
132
|
+
|
|
116
133
|
<hr>
|
|
117
134
|
<aside id="annotation-menu">
|
|
118
135
|
<div class="buttons-area">
|
|
@@ -127,9 +144,10 @@
|
|
|
127
144
|
<button name="hh-style" value="colored-text"> Colored text</button>
|
|
128
145
|
<button name="hh-style" value="redacted"> Redacted</button>
|
|
129
146
|
<button name="hh-remove">Remove</button>
|
|
130
|
-
<
|
|
131
|
-
|
|
132
|
-
<button name="hh-wrapper" value="
|
|
147
|
+
<button name="hh-wrapper" value="none" title="No wrapper"><span class="highlight-placeholder">…</span></button>
|
|
148
|
+
<button name="hh-wrapper" value="footnote" title="Footnote"><span class="footnote-marker" data-content="a" data-accessibility-label="Footnote [a]"></span><span class="highlight-placeholder">…</span></button>
|
|
149
|
+
<button name="hh-wrapper" value="margin-note" title="Margin note"><span class="note-container"><span class="note" data-content="🙂" data-accessibility-label="Smile emoji"></span></span><span class="highlight-placeholder">…</span></button>
|
|
150
|
+
<button name="hh-wrapper" value="margin-note" title="Margin note"><span class="note-container"><span class="note" data-content="✅" data-accessibility-label="Checkmark emoji"></span></span><span class="highlight-placeholder">…</span></button>
|
|
133
151
|
</div>
|
|
134
152
|
<p>
|
|
135
153
|
<label><input type="checkbox" id="hh-remember-style" checked> Remember most recent color and style</label><br>
|
|
@@ -164,8 +182,11 @@
|
|
|
164
182
|
<div id="disambiguation-buttons"></div>
|
|
165
183
|
<button id="disambiguation-cancel">Cancel</button>
|
|
166
184
|
</dialog>
|
|
167
|
-
|
|
168
|
-
|
|
185
|
+
<p>
|
|
186
|
+
← <a href="https://github.com/samuelbradshaw/highlight-helper-js">Back to GitHub</a>
|
|
187
|
+
</p>
|
|
188
|
+
|
|
189
|
+
<!-- Link to the HighlightHelper.js JavaScript file -->
|
|
169
190
|
<script src="highlight-helper.js"></script>
|
|
170
191
|
<script>
|
|
171
192
|
// Example highlights
|
|
@@ -173,20 +194,20 @@
|
|
|
173
194
|
{
|
|
174
195
|
highlightId: 'hh-1729991847207',
|
|
175
196
|
color: 'green', style: 'fill',
|
|
176
|
-
startParagraphId: 'p1', startParagraphOffset:
|
|
177
|
-
endParagraphId: 'p1', endParagraphOffset:
|
|
197
|
+
startParagraphId: 'p1', startParagraphOffset: 27,
|
|
198
|
+
endParagraphId: 'p1', endParagraphOffset: 59,
|
|
178
199
|
},
|
|
179
200
|
{
|
|
180
201
|
highlightId: 'hh-1729991847300',
|
|
181
|
-
color: 'blue', style: 'fill',
|
|
202
|
+
color: 'blue', style: 'fill', wrapper: 'margin-note',
|
|
203
|
+
wrapperVariables: { content: '🙂', accessibilityLabel: 'Smile emoji', },
|
|
182
204
|
startParagraphId: 'p2', startParagraphOffset: 27,
|
|
183
205
|
endParagraphId: 'p2', endParagraphOffset: 42,
|
|
184
206
|
},
|
|
185
207
|
{
|
|
186
208
|
highlightId: 'hh-1729991847468',
|
|
187
|
-
color: 'red', style: '
|
|
188
|
-
wrapperVariables: {
|
|
189
|
-
readOnly: true,
|
|
209
|
+
color: 'red', style: 'single-underline', wrapper: 'footnote',
|
|
210
|
+
wrapperVariables: { content: 'a', accessibilityLabel: 'Footnote [a]', },
|
|
190
211
|
startParagraphId: 'p2', startParagraphOffset: 119,
|
|
191
212
|
endParagraphId: 'p2', endParagraphOffset: 127,
|
|
192
213
|
},
|
|
@@ -199,14 +220,14 @@
|
|
|
199
220
|
{
|
|
200
221
|
highlightId: 'hh-1735379727958',
|
|
201
222
|
color: 'yellow', style: 'fill',
|
|
202
|
-
startParagraphId: 'p1', startParagraphOffset:
|
|
203
|
-
endParagraphId: 'p1', endParagraphOffset:
|
|
223
|
+
startParagraphId: 'p1', startParagraphOffset: 248,
|
|
224
|
+
endParagraphId: 'p1', endParagraphOffset: 261,
|
|
204
225
|
},
|
|
205
226
|
{
|
|
206
227
|
highlightId: 'hh-1735379721523',
|
|
207
228
|
color: 'red', style: 'fill',
|
|
208
|
-
startParagraphId: 'p1', startParagraphOffset:
|
|
209
|
-
endParagraphId: 'p1', endParagraphOffset:
|
|
229
|
+
startParagraphId: 'p1', startParagraphOffset: 239,
|
|
230
|
+
endParagraphId: 'p1', endParagraphOffset: 252,
|
|
210
231
|
},
|
|
211
232
|
{
|
|
212
233
|
highlightId: 'hh-1735479727958',
|
|
@@ -215,18 +236,19 @@
|
|
|
215
236
|
endParagraphId: 'p4', endParagraphOffset: 207,
|
|
216
237
|
},
|
|
217
238
|
];
|
|
218
|
-
|
|
239
|
+
|
|
219
240
|
// Initialize a Highlighter instance, then load highlights
|
|
220
241
|
const highlighter = new Highlighter({
|
|
221
242
|
containerSelector: 'main',
|
|
222
243
|
paragraphSelector: 'main [id]',
|
|
223
244
|
wrappers: {
|
|
224
|
-
'footnote': { start: '<span class="footnote-marker" data-
|
|
245
|
+
'footnote': { start: '<span class="footnote-marker" data-content="{content}" data-accessibility-label="{accessibilityLabel}"></span>', end: '', },
|
|
246
|
+
'margin-note': { start: '<span class="note-container"><span class="note" data-content="{content}" data-accessibility-label="{accessibilityLabel}"></span></span>', end: '', },
|
|
225
247
|
},
|
|
226
248
|
});
|
|
227
249
|
highlighter.loadHighlights(highlights);
|
|
228
|
-
|
|
229
|
-
// Call
|
|
250
|
+
|
|
251
|
+
// Call HighlightHelper.js methods when color, style, wrapper, or remove buttons on this demo page are tapped
|
|
230
252
|
const colorButtons = document.getElementsByName('hh-color');
|
|
231
253
|
for (const colorButton of colorButtons) {
|
|
232
254
|
colorButton.addEventListener('click', () => { highlighter.createOrUpdateHighlight({ color: colorButton.value }); });
|
|
@@ -237,7 +259,13 @@
|
|
|
237
259
|
}
|
|
238
260
|
const wrapperButtons = document.getElementsByName('hh-wrapper');
|
|
239
261
|
for (const wrapperButton of wrapperButtons) {
|
|
240
|
-
wrapperButton.
|
|
262
|
+
const contentSpan = wrapperButton.querySelector('[data-content]');
|
|
263
|
+
wrapperButton.addEventListener('click', () => {
|
|
264
|
+
highlighter.createOrUpdateHighlight({
|
|
265
|
+
wrapper: wrapperButton.value === 'none' ? null : wrapperButton.value,
|
|
266
|
+
wrapperVariables: { content: contentSpan?.dataset?.content ?? '', accessibilityLabel: contentSpan?.dataset?.accessibilityLabel ?? '', },
|
|
267
|
+
});
|
|
268
|
+
});
|
|
241
269
|
}
|
|
242
270
|
const removeButton = document.getElementsByName('hh-remove')[0];
|
|
243
271
|
removeButton.addEventListener('click', () => { highlighter.removeHighlights([highlighter.getActiveHighlightId()]); });
|
|
@@ -245,8 +273,8 @@
|
|
|
245
273
|
function respondToKeyPress(event) {
|
|
246
274
|
if (event.key === 'Backspace') highlighter.removeHighlights([highlighter.getActiveHighlightId()]);
|
|
247
275
|
}
|
|
248
|
-
|
|
249
|
-
// Call
|
|
276
|
+
|
|
277
|
+
// Call HighlightHelper.js methods when options are changed
|
|
250
278
|
const rememberStyleCheckbox = document.getElementById('hh-remember-style');
|
|
251
279
|
const snapToWordCheckbox = document.getElementById('hh-snap-to-word');
|
|
252
280
|
const pointerModeRadioButtons = document.getElementsByName('hh-pointer-mode');
|
|
@@ -275,11 +303,11 @@
|
|
|
275
303
|
selectionHandlesTextSelectionCheckbox.addEventListener('change', (event) => { highlighter.setOption('showCustomSelectionHandlesForTextSelection', event.currentTarget.checked); });
|
|
276
304
|
const selectionHandlesOnTouchCheckbox = document.getElementById('hh-selection-handles-touch');
|
|
277
305
|
selectionHandlesOnTouchCheckbox.addEventListener('change', (event) => { highlighter.setOption('showCustomSelectionHandlesOnTouch', event.currentTarget.checked); });
|
|
278
|
-
|
|
306
|
+
|
|
279
307
|
const container = document.querySelector(highlighter.getOptions().containerSelector);
|
|
280
308
|
const snippetDiv = document.getElementById('snippet');
|
|
281
309
|
const infoDiv = document.getElementById('highlight-info');
|
|
282
|
-
|
|
310
|
+
|
|
283
311
|
// Respond to highlight update and highlight activate event
|
|
284
312
|
container.addEventListener('hh:highlightupdate', updateHighlightInfo);
|
|
285
313
|
container.addEventListener('hh:highlightactivate', updateHighlightInfo);
|
|
@@ -297,6 +325,9 @@
|
|
|
297
325
|
for (const annotatedParagraph of annotatedParagraphs) {
|
|
298
326
|
const snippetParagraph = annotatedParagraph.cloneNode(true);
|
|
299
327
|
snippetParagraph.id = `${snippetParagraph.id}_${event.detail.highlight.highlightId}`;
|
|
328
|
+
for (const wrapperEl of snippetParagraph.querySelectorAll('[data-highlight-id]')) {
|
|
329
|
+
wrapperEl.dataset.highlightId = wrapperEl.dataset.highlightId + '_snippet';
|
|
330
|
+
}
|
|
300
331
|
snippetDiv.append(snippetParagraph);
|
|
301
332
|
}
|
|
302
333
|
const snippetHighlights = [
|
|
@@ -314,15 +345,23 @@
|
|
|
314
345
|
});
|
|
315
346
|
snippetHighlighter.loadHighlights(snippetHighlights);
|
|
316
347
|
}
|
|
317
|
-
|
|
348
|
+
|
|
318
349
|
// Respond to highlight deactivate event
|
|
319
350
|
container.addEventListener('hh:highlightdeactivate', clearHighlightInfo);
|
|
320
351
|
function clearHighlightInfo(event) {
|
|
321
352
|
snippetDiv.innerHTML = '';
|
|
322
353
|
infoDiv.innerHTML = 'N/A';
|
|
323
354
|
}
|
|
324
|
-
|
|
325
|
-
// Respond to
|
|
355
|
+
|
|
356
|
+
// Respond to tap
|
|
357
|
+
container.addEventListener('hh:tap', respondToTap);
|
|
358
|
+
function respondToTap(event) {
|
|
359
|
+
if (event.detail.wrappers.length === 1) {
|
|
360
|
+
highlighter.activateHighlight(event.detail.wrappers[0].highlightInfo.highlightId);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Respond to ambiguous action events (when a user taps on overlapping highlights, wrappers, and/or links)
|
|
326
365
|
const disambiguationPanel = document.getElementById('disambiguation-panel');
|
|
327
366
|
const disambiguationButtonsDiv = document.getElementById('disambiguation-buttons');
|
|
328
367
|
const disambiguationCancelButton = document.getElementById('disambiguation-cancel');
|
|
@@ -330,7 +369,11 @@
|
|
|
330
369
|
container.addEventListener('hh:ambiguousaction', handleAmbiguousAction);
|
|
331
370
|
function handleAmbiguousAction(event) {
|
|
332
371
|
disambiguationButtonsDiv.innerHTML = '';
|
|
333
|
-
|
|
372
|
+
const highlightIds = new Set();
|
|
373
|
+
for (const highlight of event.detail.highlights) highlightIds.add(highlight.highlightId);
|
|
374
|
+
for (const wrapper of event.detail.wrappers) highlightIds.add(wrapper.highlightInfo.highlightId);
|
|
375
|
+
const highlights = highlighter.getHighlightInfo(highlightIds);
|
|
376
|
+
for (const highlight of highlights) {
|
|
334
377
|
let buttonStyle = highlighter.getOptions().styles[highlight.style]['css']
|
|
335
378
|
let buttonHtml = `
|
|
336
379
|
<button data-color="${highlight.color}" onclick="disambiguationPanel.close();highlighter.activateHighlight('${highlight.highlightId}');">
|
|
@@ -351,7 +394,20 @@
|
|
|
351
394
|
// TODO: Waiting 10 milliseconds before showing the disambiguation panel is a workaround for an issue in Android Chrome where a click event fires right after the pointerup event that triggered hh:ambiguousaction. If the click event happens to be at the same position as a button in the panel, the button will receive the click, dismissing the panel before the user sees it.
|
|
352
395
|
setTimeout(function() { disambiguationPanel.showModal(); }, 10);
|
|
353
396
|
}
|
|
354
|
-
|
|
397
|
+
|
|
398
|
+
// Update margin note position when a highlight with a start wrapper is active
|
|
399
|
+
function updateMarginNotePosition(event) {
|
|
400
|
+
const activeHighlightId = highlighter.getActiveHighlightId();
|
|
401
|
+
if (!activeHighlightId) return;
|
|
402
|
+
const note = container.querySelector(`.hh-wrapper-start[data-highlight-id="${activeHighlightId}"] .note`);
|
|
403
|
+
if (!note) return;
|
|
404
|
+
const selection = getSelection();
|
|
405
|
+
const startRect = selection.type === 'Range' ? selection.getRangeAt(0).getBoundingClientRect() : null;
|
|
406
|
+
if (startRect) note.style.top = (startRect.top + window.scrollY - 1) + 'px';
|
|
407
|
+
}
|
|
408
|
+
container.addEventListener('hh:highlightactivate', updateMarginNotePosition);
|
|
409
|
+
document.addEventListener('selectionchange', updateMarginNotePosition);
|
|
410
|
+
|
|
355
411
|
// Log events for debugging
|
|
356
412
|
container.addEventListener('hh:highlightcreate', console.info)
|
|
357
413
|
container.addEventListener('hh:highlightupdate', console.info)
|
|
@@ -362,16 +418,6 @@
|
|
|
362
418
|
container.addEventListener('hh:selectionupdate', console.info)
|
|
363
419
|
container.addEventListener('hh:ambiguousaction', console.info)
|
|
364
420
|
container.addEventListener('hh:tap', console.info)
|
|
365
|
-
|
|
366
|
-
// snippetDiv.addEventListener('hh:highlightcreate', console.warn)
|
|
367
|
-
// snippetDiv.addEventListener('hh:highlightupdate', console.warn)
|
|
368
|
-
// snippetDiv.addEventListener('hh:highlightremove', console.warn)
|
|
369
|
-
// snippetDiv.addEventListener('hh:highlightsload', console.warn)
|
|
370
|
-
// snippetDiv.addEventListener('hh:highlightactivate', console.warn)
|
|
371
|
-
// snippetDiv.addEventListener('hh:highlightdeactivate', console.warn)
|
|
372
|
-
// snippetDiv.addEventListener('hh:selectionupdate', console.warn)
|
|
373
|
-
// snippetDiv.addEventListener('hh:ambiguousaction', console.warn)
|
|
374
|
-
// snippetDiv.addEventListener('hh:tap', console.warn)
|
|
375
421
|
</script>
|
|
376
422
|
</body>
|
|
377
423
|
</html>
|