web-highlighter
Version:
✨A no-runtime dependency lib for text highlighting & persistence on any website ✨🖍️
431 lines (420 loc) • 23.2 kB
HTML
<!-- htmlcs-disable -->
<html lang="zh-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="keywords" content="highlighter,tool,lib,website,frontend">
<meta name="description" content="Web Highlighter is a mini tool for highlighting website text. This page shows what it is and how to use it.">
<style>
main {
max-width: 760px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.yellow-highlight {
background-color: #FF9;
}
.blue-highlight {
background-color: #9FF;
}
</style>
<title>Web Highlighter: a mini tool for highlighting website text</title>
</head>
<body>
<main>
<div>
<h1 align="center"><code>Web Highlighter</code> 🖍️</h1>
<p align="center">
<strong>✨ A no-dependency lib for text highlighting & persistence on any website ✨🖍️</strong>
</p>
<img src="https://raw.githubusercontent.com/alienzhou/web-highlighter/master/docs/img/logo.png">
<p align="center">
<a href="https://travis-ci.org/alienzhou/web-highlighter" target="_blank">
<img src="https://api.travis-ci.org/alienzhou/web-highlighter.svg?branch=master" alt="Build status" />
</a>
<a href="https://www.npmjs.com/package/web-highlighter" target="_blank">
<img src="https://img.shields.io/npm/v/web-highlighter.svg" alt="NPM version" />
</a>
<a href='https://coveralls.io/github/alienzhou/web-highlighter?branch=master'>
<img src='https://coveralls.io/repos/github/alienzhou/web-highlighter/badge.svg?branch=master' alt='Coverage Status' />
</a>
<a href="https://unpkg.com/web-highlighter" target="_blank">
<img src="https://img.badgesize.io/https://unpkg.com/web-highlighter/dist/web-highlighter.min.js?compression=gzip" alt="Gzip size" />
</a>
<a href="https://codebeat.co/projects/github-com-alienzhou-web-highlighter-master" target="_blank">
<img src="https://codebeat.co/badges/f5a18a9b-9765-420e-a17f-fa0b54b3a125" alt="Codebeat" />
</a>
<a href="https://opensource.org/licenses/mit-license.php" target="_blank">
<img src="https://img.shields.io/github/license/alienzhou/web-highlighter" alt="MIT Licence" />
</a>
</p>
</div>
<hr />
<p>English | <a href="https://github.com/alienzhou/web-highlighter/blob/master/README.zh_CN.md">简体中文</a></p>
<h2 id="background">Background</h2>
<p>It's from an idea: highlight texts on the website and save the highlighted areas just like what you do in PDF.</p>
<p>If you have ever visited <a href="http://medium.com">medium.com</a>, you must know the feature of highlighting notes: users select a text segment and click the 'highlight' button. Then the text will be highlighted with a shining background color. Besides, the highlighted areas will be saved and recovered when you visit it next time. It's like the simple demo bellow.</p>
<p><img src="https://raw.githubusercontent.com/alienzhou/web-highlighter/master/docs/img/sample.gif" alt="" /></p>
<p>This is a useful feature for readers. If you're a developer, you may want your website support it and attract more visits. If you're a user (like me), you may want a browser-plugin to do this.</p>
<p>For this reason, the repo (web-highlighter) aims to help you implement highlighting-note on any website quickly (e.g. blogs, document viewers, online books and so on). It contains the core abilities for note highlighting and persistence. And you can implement your own product by some easy-to-use APIs. It has been used for our sites in production.</p>
<h2 id="install">Install</h2>
<pre><code class="bash language-bash">npm i web-highlighter</code></pre>
<h2 id="usage">Usage</h2>
<p>Only two lines, highlighted when texts are selected.</p>
<pre><code class="JavaScript language-JavaScript">import Highlighter from 'web-highlighter';
(new Highlighter()).run();</code></pre>
<p>If you need persistence, four lines make it.</p>
<pre><code class="JavaScript language-JavaScript">import Highlighter from 'web-highlighter';
// 1. initialize
const highlighter = new Highlighter();
// 2. retrieve data from backend, then highlight it on the page
getRemoteData().then(s => highlighter.fromStore(s.startMeta, s.endMeta, s.id, s.text));
// 3. listen for highlight creating, then save to backend
highlighter.on(Highlighter.event.CREATE, ({sources}) => save(sources));
// 4. auto highlight
highlighter.run();</code></pre>
<h2 id="example">Example</h2>
<p>A more complex example</p>
<pre><code class="JavaScript language-JavaScript">import Highlighter from 'web-highlighter';
// won't highlight pre&code elements
const highlighter = new Highlighter({
exceptSelectors: ['pre', 'code']
});
// add some listeners to handle interaction, such as hover
highlighter
.on('selection:hover', ({id}) => {
// display different bg color when hover
highlighter.addClass('highlight-wrap-hover', id);
})
.on('selection:hover-out', ({id}) => {
// remove the hover effect when leaving
highlighter.removeClass('highlight-wrap-hover', id);
})
.on('selection:create', ({sources}) => {
sources = sources.map(hs => ({hs}));
// save to backend
store.save(sources);
});
// retrieve data from store, and display highlights on the website
store.getAll().forEach(
// hs is the same data saved by 'store.save(sources)'
({hs}) => highlighter.fromStore(hs.startMeta, hs.endMeta, hs.text, hs.id)
);
// auto-highlight selections
highlighter.run()</code></pre>
<p>Besides, there is an example in this repo (in <code>example</code> folder). To play with it, you just need ——</p>
<p>Firstly enter the repository and run</p>
<pre><code class="bash language-bash">npm i</code></pre>
<p>Then start the example</p>
<pre><code>npm start</code></pre>
<p>Finally visit <a href="http://127.0.0.1:8085/">http://127.0.0.1:8085/</a></p>
<hr />
<p>Another real product built with web-highlighter (for the highlighting area on the left):</p>
<p><img src="https://user-images.githubusercontent.com/9822789/64678049-632e8500-d4ab-11e9-99d6-f960bc90d17b.gif" alt="product sample" /></p>
<h2 id="how-it-works">How it works</h2>
<p>It will read the selected range by <a href="https://caniuse.com/#search=selection%20api"><code>Selection API</code></a>. Then the information of the range will be converted to a serializable data structure so that it can be store in backend. When users visit your page next time, these data will be returned and deserialized in your page. The data structure is tech stack independent. So you can use on any 'static' pages made with React / Vue / Angular / jQuery and others.</p>
<p>For more details, please read <a href="https://www.alienzhou.com/2019/04/21/web-note-highlight-in-js/">this article (in Chinese)</a>.</p>
<h2 id="apis">APIs</h2>
<h3 id="1-options">1. Options</h3>
<pre><code class="JavaScript language-JavaScript">const highlighter = new Highlighter([opts])</code></pre>
<p>Create a new <code>highlighter</code> instance.</p>
<p><code>opts</code> will be merged into the default options (shown bellow).</p>
<pre><code class="JavaScript language-JavaScript">{
$root: document.documentElement,
exceptSelectors: null,
wrapTag: 'span',
style: {
className: 'highlight-mengshou-wrap'
}
}</code></pre>
<p>All options:</p>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="type">type</th>
<th id="description">description</th>
<th id="required">required</th>
<th id="default">default</th>
</tr>
</thead>
<tbody>
<tr>
<td>$root</td>
<td><code>Document | HTMLElement</code></td>
<td>the container to enable highlighting</td>
<td>No</td>
<td><code>document</code></td>
</tr>
<tr>
<td>exceptSelectors</td>
<td><code>Array<string></code></td>
<td>if an element matches the selector, it won't be highlighted</td>
<td>No</td>
<td><code>null</code></td>
</tr>
<tr>
<td>wrapTag</td>
<td><code>string</code></td>
<td>the html tag used to wrap highlighted texts</td>
<td>No</td>
<td><code>span</code></td>
</tr>
<tr>
<td>verbose</td>
<td><code>boolean</code></td>
<td>dose it need to output (print) some warning and error message</td>
<td>No</td>
<td><code>false</code></td>
</tr>
<tr>
<td>style</td>
<td><code>Object</code></td>
<td>control highlighted areas style</td>
<td>No</td>
<td>details below</td>
</tr>
</tbody>
</table>
<p><code>style</code> field options:</p>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="type">type</th>
<th id="description">description</th>
<th id="required">required</th>
<th id="default">default</th>
</tr>
</thead>
<tbody>
<tr>
<td>className</td>
<td><code>string</code></td>
<td>the className for wrap element</td>
<td>No</td>
<td><code>highlight-mengshou-wrap</code></td>
</tr>
</tbody>
</table>
<p><code>exceptSelectors</code> needs <code>null</code> or <code>Array<string></code>. It supports id selectors, class selectors and tag selectors. For example, to skip h1 and <code>.title</code> elements:</p>
<pre><code class="JavaScript language-JavaScript">var highlighter = new Highlighter({
exceptSelectors: ['h1', '.title']
});</code></pre>
<h3 id="2-static-methods">2. Static Methods</h3>
<h4 id="highlighterishighlightsourcesource"><code>Highlighter.isHighlightSource(source)</code></h4>
<p>If the <code>source</code> is a highlight source object, it will return <code>true</code>, vice verse.</p>
<h4 id="highlighterishighlightwrapnodenode"><code>Highlighter.isHighlightWrapNode($node)</code></h4>
<p>If the <code>$node</code> is a highlight wrapper dom node, it will return <code>true</code>, vice verse.</p>
<h3 id="3-instance-methods">3. Instance Methods</h3>
<h4 id="highlighterrun"><code>highlighter.run()</code></h4>
<p>Start auto-highlighting. When the user select a text segment, a highlighting will be added to the text automatically.</p>
<h4 id="highlighterstop"><code>highlighter.stop()</code></h4>
<p>It will stop the auto-highlighting.</p>
<h4 id="highlighterdispose"><code>highlighter.dispose()</code></h4>
<p>When you don't want the highlighter anymore, remember to call it first. It will remove some listeners and do some cleanup.</p>
<h4 id="highlighterfromrangerange"><code>highlighter.fromRange(range)</code></h4>
<p>You can pass a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Range"><code>Range</code></a> object to it and then it will be highlighted. You can use <code>window.getSelection().getRangeAt(0)</code> to get a range object or use <code>document.createRange()</code> to create a new range.</p>
<p>Use it as bellow:</p>
<pre><code class="JavaScript language-JavaScript">const selection = window.getSelection();
if (!selection.isCollapsed) {
highlighter.fromRange(selection.getRangeAt(0));
}</code></pre>
<h4 id="highlighterfromstorestart-end-text-id"><code>highlighter.fromStore(start, end, text, id)</code></h4>
<p>Mostly, you use this api to highlight text by the persisted information stored from backend.</p>
<p>These four values are from the <code>HighlightSource</code> object. <code>HighlightSource</code> object is a special object created by web-highlighter when highlighted area created. For persistence in backend (database), it's necessary to find a data structure to represent a dom node. This structure is called <code>HighlightSource</code> in web-highlighter.</p>
<p>Four attributes' meanings:</p>
<ul>
<li>start <code>Object</code>: meta info about the beginning element</li>
<li>end <code>Object</code>: meta info about then end element</li>
<li>text <code>string</code>: text content</li>
<li>id <code>string</code>: unique id</li>
</ul>
<h4 id="highlighterremoveid"><code>highlighter.remove(id)</code></h4>
<p>Remove (clean) a highlighted area by it's unique id. The id will be generated by web-highlighter by default. You can also add a hook for your own rule. <a href="https://github.com/alienzhou/web-highlighter/blob/master/docs/ADVANCE.md">Hooks doc here</a>.</p>
<h4 id="highlighterremoveall"><code>highlighter.removeAll()</code></h4>
<p>Remove all highlighted areas belonging to the root.</p>
<h4 id="highlighteraddclassclassname-id"><code>highlighter.addClass(className, id)</code></h4>
<p>Add a className for highlighted areas (wrap elements) by unique id. You can change a highlighted area's style by using this api.</p>
<h4 id="highlighterremoveclassclassname-id"><code>highlighter.removeClass(className, id)</code></h4>
<p>Remove the className by unique id. It's <code>highlighter.addClass</code>'s inverse operation.</p>
<h4 id="highlightergetdomsid"><code>highlighter.getDoms([id])</code></h4>
<p>Get all the wrap nodes in a highlighted area. A highlighted area may contain many segments. It will return all the dom nodes wrapping these segments.</p>
<p>If the <code>id</code> is not passed, it will return all the areas' wrap nodes.</p>
<h4 id="highlightergetidbydomnode"><code>highlighter.getIdByDom(node)</code></h4>
<p>If you have a DOM node, it can return the unique highlight id for you. When passing a non-wrapper element, it will find the nearest ancestor wrapper node.</p>
<h4 id="highlightergetextraidbydomnode"><code>highlighter.getExtraIdByDom(node)</code></h4>
<p>If you have a DOM node, it can return the extra unique highlight id for you. When passing a non-wrapper element, it will find the nearest ancestor wrapper node.</p>
<h4 id="highlightersetoptionopt"><code>highlighter.setOption(opt)</code></h4>
<p>You can use this API to change the highlighter's options. The parameters' structure is the same as the constructor's. You can pass partial options.</p>
<h3 id="4-event-listener">4. Event Listener</h3>
<p>web-highlighter use listeners to handle the events.</p>
<p>e.g.</p>
<pre><code class="JavaScript language-JavaScript">var highlighter = new Highlighter();
highlighter.on(Highlighter.event.CREATE, function (data, inst, e) {
// ...
});</code></pre>
<p>The callback function will receive three parameters:</p>
<ul>
<li>data <code>any</code>: event data</li>
<li>inst <code>Highlighter</code>: current Highlighter instance</li>
<li>e <code>Event</code>: some event is triggered by the browser (such as click), web-highlighter will expose it</li>
</ul>
<p><code>Highlighter.event</code> is <code>EventType</code> type. It contains:</p>
<ul>
<li><code>EventType.CLICK</code>: click the highlighted area</li>
<li><code>EventType.HOVER</code>: mouse enter the highlighted area</li>
<li><code>EventType.HOVER_OUT</code>: mouse leave the highlighted area</li>
<li><code>EventType.CREATE</code>: a highlighted area is created</li>
<li><code>EventType.REMOVE</code>: a highlighted area is removed</li>
</ul>
<p>Different event has different <code>data</code>. Attributes below:</p>
<h4 id="eventtypeclick"><code>EventType.CLICK</code></h4>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="description">description</th>
<th id="type">type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>the highlight id</td>
<td>string</td>
</tr>
</tbody>
</table>
<h4 id="eventtypehover"><code>EventType.HOVER</code></h4>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="description">description</th>
<th id="type">type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>the highlight id</td>
<td>string</td>
</tr>
</tbody>
</table>
<h4 id="eventtypehover_out"><code>EventType.HOVER_OUT</code></h4>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="description">description</th>
<th id="type">type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>the highlight id</td>
<td>string</td>
</tr>
</tbody>
</table>
<h4 id="eventtypecreate"><code>EventType.CREATE</code></h4>
<blockquote>
<p>no parameter <code>e</code></p>
</blockquote>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="description">description</th>
<th id="type">type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>source</code></td>
<td><code>HighlightSource</code> object</td>
<td>Array<HighlightSource></td>
</tr>
<tr>
<td><code>type</code></td>
<td>the reason for creating</td>
<td>string</td>
</tr>
</tbody>
</table>
<p><code>source</code> is a <code>HighlightSource</code> object. It is an object created by web-highlighter when highlighted area created. For persistence in backend (database), it's necessary to use a data structure which can be serialized (<code>JSON.stringify()</code>) to represent a dom node in browsers. <code>HighlightSource</code> is the data structure designed for this.</p>
<p><code>type</code> explains why a highlighted area is be created. Now <code>type</code> has two possible values: <code>from-input</code> and <code>from-store</code>. <code>from-input</code> shows that a highlighted area is created because of user's selection. <code>from-store</code> means it from a storage.</p>
<h4 id="eventtyperemove"><code>EventType.REMOVE</code></h4>
<blockquote>
<p>no parameter <code>e</code></p>
</blockquote>
<table>
<thead>
<tr>
<th id="name">name</th>
<th id="description">description</th>
<th id="type">type</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ids</code></td>
<td>a list of the highlight id</td>
<td>Array<string></td>
</tr>
</tbody>
</table>
<h3 id="5-hooks">5. Hooks</h3>
<p>Hooks let you control the highlighting flow powerfully. You can almost customize any logic by hooks. See more in <a href="#Advance">'Advance' part</a>.</p>
<h2 id="compatibility">Compatibility</h2>
<blockquote>
<p>It depends on <a href="https://caniuse.com/#search=selection%20api">Selection API</a>.</p>
</blockquote>
<ul>
<li>IE 10、11</li>
<li>Edge</li>
<li>Firefox 52+</li>
<li>Chrome 15+</li>
<li>Safari 5.1+</li>
<li>Opera 15+</li>
</ul>
<p><em><strong>Mobile supports:</strong></em> automatically detect whether mobile devices and use touch events when on mobile devices.</p>
<h2 id="advance">Advance</h2>
<p>It provides some hooks for you so that the highlighting behaviour can be controlled better by your own.</p>
<p>To learn more about the hooks, read <a href="https://github.com/alienzhou/web-highlighter/blob/master/docs/ADVANCE.md">this doc</a>.</p>
<h2 id="license">License</h2>
<p><a href="./LICENCE">MIT</a></p>
</main>
<script>
void function () {
var img = document.getElementsByTagName('main')[0].getElementsByTagName('img')[0];
img.onerror = function() {
document.getElementById('js-title').setAttribute('style', 'display: block');
};
}();
</script>
<section class="op-panel">
<div>
<label class="op-name">auto-highlight:</label>
<label><input name="auto" type="radio" value="on" checked />enabled</label>
<label><input name="auto" type="radio" value="off" />disabled</label>
</div>
<div>
<label class="op-color">color:</label>
<label><input name="color" type="radio" value="yellow" checked />yellow</label>
<label><input name="color" type="radio" value="blue" />blue</label>
</div>
<button class="op-btn disabled" id="js-highlight" disabled >highlight manually</button>
<a href="https://github.com/alienzhou/web-highlighter" target="_blank">
<svg t="1555513182711" class="icon" style="" viewBox="0 0 1049 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1456" xmlns:xlink="http://www.w3.org/1999/xlink" width="131.125" height="128"><defs><style type="text/css"></style></defs><path d="M524.979332 0C234.676191 0 0 234.676191 0 524.979332c0 232.068678 150.366597 428.501342 358.967656 498.035028 26.075132 5.215026 35.636014-11.299224 35.636014-25.205961 0-12.168395-0.869171-53.888607-0.869171-97.347161-146.020741 31.290159-176.441729-62.580318-176.441729-62.580318-23.467619-60.841976-58.234462-76.487055-58.234463-76.487055-47.804409-32.15933 3.476684-32.15933 3.476685-32.15933 53.019436 3.476684 80.83291 53.888607 80.83291 53.888607 46.935238 79.963739 122.553122 57.365291 152.97411 43.458554 4.345855-33.897672 18.252593-57.365291 33.028501-70.402857-116.468925-12.168395-239.022047-57.365291-239.022047-259.012982 0-57.365291 20.860106-104.300529 53.888607-140.805715-5.215026-13.037566-23.467619-66.926173 5.215027-139.067372 0 0 44.327725-13.906737 144.282399 53.888607 41.720212-11.299224 86.917108-17.383422 131.244833-17.383422s89.524621 6.084198 131.244833 17.383422C756.178839 203.386032 800.506564 217.29277 800.506564 217.29277c28.682646 72.1412 10.430053 126.029806 5.215026 139.067372 33.897672 36.505185 53.888607 83.440424 53.888607 140.805715 0 201.64769-122.553122 245.975415-239.891218 259.012982 19.121764 16.514251 35.636014 47.804409 35.636015 97.347161 0 70.402857-0.869171 126.898978-0.869172 144.282399 0 13.906737 9.560882 30.420988 35.636015 25.205961 208.601059-69.533686 358.967656-265.96635 358.967655-498.035028C1049.958663 234.676191 814.413301 0 524.979332 0z" fill="#191717" p-id="1457"></path><path d="M199.040177 753.571326c-0.869171 2.607513-5.215026 3.476684-8.691711 1.738342s-6.084198-5.215026-4.345855-7.82254c0.869171-2.607513 5.215026-3.476684 8.691711-1.738342s5.215026 5.215026 4.345855 7.82254z m-6.953369-4.345856M219.900283 777.038945c-2.607513 2.607513-7.82254 0.869171-10.430053-2.607514-3.476684-3.476684-4.345855-8.691711-1.738342-11.299224 2.607513-2.607513 6.953369-0.869171 10.430053 2.607514 3.476684 4.345855 4.345855 9.560882 1.738342 11.299224z m-5.215026-5.215027M240.760389 807.459932c-3.476684 2.607513-8.691711 0-11.299224-4.345855-3.476684-4.345855-3.476684-10.430053 0-12.168395 3.476684-2.607513 8.691711 0 11.299224 4.345855 3.476684 4.345855 3.476684 9.560882 0 12.168395z m0 0M269.443034 837.011749c-2.607513 3.476684-8.691711 2.607513-13.906737-1.738342-4.345855-4.345855-6.084198-10.430053-2.607513-13.037566 2.607513-3.476684 8.691711-2.607513 13.906737 1.738342 4.345855 3.476684 5.215026 9.560882 2.607513 13.037566z m0 0M308.555733 853.526c-0.869171 4.345855-6.953369 6.084198-13.037566 4.345855-6.084198-1.738342-9.560882-6.953369-8.691711-10.430053 0.869171-4.345855 6.953369-6.084198 13.037566-4.345855 6.084198 1.738342 9.560882 6.084198 8.691711 10.430053z m0 0M351.145116 857.002684c0 4.345855-5.215026 7.82254-11.299224 7.82254-6.084198 0-11.299224-3.476684-11.299224-7.82254s5.215026-7.82254 11.299224-7.82254c6.084198 0 11.299224 3.476684 11.299224 7.82254z m0 0M391.126986 850.049315c0.869171 4.345855-3.476684 8.691711-9.560882 9.560882-6.084198 0.869171-11.299224-1.738342-12.168395-6.084197-0.869171-4.345855 3.476684-8.691711 9.560881-9.560882 6.084198-0.869171 11.299224 1.738342 12.168396 6.084197z m0 0" fill="#191717" p-id="1458"></path></svg>
</a>
</section>
</body>
</html>