@plannotator/web-highlighter 0.8.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.
Files changed (88) hide show
  1. package/.cursor/environment.json +6 -0
  2. package/.eslintrc.js +250 -0
  3. package/.prettierrc +9 -0
  4. package/.travis.yml +17 -0
  5. package/CHANGELOG.md +220 -0
  6. package/LICENSE +21 -0
  7. package/README.md +371 -0
  8. package/README.zh_CN.md +367 -0
  9. package/config/base.config.js +25 -0
  10. package/config/base.example.config.js +38 -0
  11. package/config/paths.js +22 -0
  12. package/config/server.config.js +17 -0
  13. package/config/webpack.config.dev.js +18 -0
  14. package/config/webpack.config.example.js +20 -0
  15. package/config/webpack.config.prod.js +28 -0
  16. package/dist/data/cache.d.ts +13 -0
  17. package/dist/index.d.ts +58 -0
  18. package/dist/model/range/dom.d.ts +6 -0
  19. package/dist/model/range/index.d.ts +20 -0
  20. package/dist/model/range/selection.d.ts +14 -0
  21. package/dist/model/source/dom.d.ts +23 -0
  22. package/dist/model/source/index.d.ts +18 -0
  23. package/dist/painter/dom.d.ts +22 -0
  24. package/dist/painter/index.d.ts +19 -0
  25. package/dist/painter/style.d.ts +1 -0
  26. package/dist/types/index.d.ts +102 -0
  27. package/dist/util/camel.d.ts +5 -0
  28. package/dist/util/const.d.ts +41 -0
  29. package/dist/util/deferred.d.ts +9 -0
  30. package/dist/util/dom.d.ts +32 -0
  31. package/dist/util/event.emitter.d.ts +13 -0
  32. package/dist/util/hook.d.ts +15 -0
  33. package/dist/util/interaction.d.ts +6 -0
  34. package/dist/util/is.mobile.d.ts +5 -0
  35. package/dist/util/tool.d.ts +4 -0
  36. package/dist/util/uuid.d.ts +4 -0
  37. package/dist/web-highlighter.min.js +3 -0
  38. package/dist/web-highlighter.min.js.map +1 -0
  39. package/docs/ADVANCE.md +113 -0
  40. package/docs/ADVANCE.zh_CN.md +111 -0
  41. package/docs/img/create-flow.jpg +0 -0
  42. package/docs/img/create-flow.zh_CN.jpg +0 -0
  43. package/docs/img/logo.png +0 -0
  44. package/docs/img/remove-flow.jpg +0 -0
  45. package/docs/img/remove-flow.zh_CN.jpg +0 -0
  46. package/docs/img/sample.gif +0 -0
  47. package/example/index.css +2 -0
  48. package/example/index.js +214 -0
  49. package/example/local.store.js +72 -0
  50. package/example/my.css +119 -0
  51. package/example/tpl.html +59 -0
  52. package/package.json +103 -0
  53. package/script/build.js +17 -0
  54. package/script/convet-md.js +25 -0
  55. package/script/dev.js +22 -0
  56. package/src/data/cache.ts +57 -0
  57. package/src/index.ts +285 -0
  58. package/src/model/range/dom.ts +94 -0
  59. package/src/model/range/index.ts +88 -0
  60. package/src/model/range/selection.ts +55 -0
  61. package/src/model/source/dom.ts +66 -0
  62. package/src/model/source/index.ts +54 -0
  63. package/src/painter/dom.ts +345 -0
  64. package/src/painter/index.ts +199 -0
  65. package/src/painter/style.ts +21 -0
  66. package/src/types/index.ts +118 -0
  67. package/src/util/camel.ts +6 -0
  68. package/src/util/const.ts +54 -0
  69. package/src/util/deferred.ts +37 -0
  70. package/src/util/dom.ts +155 -0
  71. package/src/util/event.emitter.ts +45 -0
  72. package/src/util/hook.ts +52 -0
  73. package/src/util/interaction.ts +20 -0
  74. package/src/util/is.mobile.ts +7 -0
  75. package/src/util/tool.ts +14 -0
  76. package/src/util/uuid.ts +10 -0
  77. package/test/api.spec.ts +555 -0
  78. package/test/event.spec.ts +284 -0
  79. package/test/fixtures/broken.json +32 -0
  80. package/test/fixtures/index.html +11 -0
  81. package/test/fixtures/source.json +47 -0
  82. package/test/hook.spec.ts +244 -0
  83. package/test/integrate.spec.ts +48 -0
  84. package/test/mobile.spec.ts +87 -0
  85. package/test/option.spec.ts +212 -0
  86. package/test/util.spec.ts +244 -0
  87. package/test-newlines.html +226 -0
  88. package/tsconfig.json +23 -0
@@ -0,0 +1,367 @@
1
+ <div>
2
+ <h1 align="center"><code>Web Highlighter</code>&nbsp;&nbsp;🖍️</h1>
3
+ <p align="center">
4
+ <strong>✨ 一个可以在任何网页上做高亮笔记前端库,支持高亮文本的持久化存储与还原 ✨🖍️</strong>
5
+ </p>
6
+ <img src="https://raw.githubusercontent.com/alienzhou/web-highlighter/master/docs/img/logo.png">
7
+ <p align="center">
8
+ <a href="https://travis-ci.org/alienzhou/web-highlighter" target="_blank">
9
+ <img src="https://api.travis-ci.org/alienzhou/web-highlighter.svg?branch=master" alt="Build status" />
10
+ </a>
11
+ <a href="https://www.npmjs.com/package/web-highlighter" target="_blank">
12
+ <img src="https://img.shields.io/npm/v/web-highlighter.svg" alt="NPM version" />
13
+ </a>
14
+ <a href='https://coveralls.io/github/alienzhou/web-highlighter?branch=master'>
15
+ <img src='https://coveralls.io/repos/github/alienzhou/web-highlighter/badge.svg?branch=master' alt='Coverage Status' />
16
+ </a>
17
+ <a href="https://unpkg.com/web-highlighter" target="_blank">
18
+ <img src="https://img.badgesize.io/https://unpkg.com/web-highlighter/dist/web-highlighter.min.js?compression=gzip" alt="Gzip size" />
19
+ </a>
20
+ <a href="https://codebeat.co/projects/github-com-alienzhou-web-highlighter-master" target="_blank">
21
+ <img src="https://codebeat.co/badges/f5a18a9b-9765-420e-a17f-fa0b54b3a125" alt="Codebeat" />
22
+ </a>
23
+ <a href="https://opensource.org/licenses/mit-license.php" target="_blank">
24
+ <img src="https://img.shields.io/github/license/alienzhou/web-highlighter" alt="MIT Licence" />
25
+ </a>
26
+ </p>
27
+ </div>
28
+
29
+ ---
30
+
31
+ [English](https://github.com/alienzhou/web-highlighter/blob/master/README.md) | 简体中文
32
+
33
+ ## 1. <a name=''></a>背景
34
+
35
+ 灵感来源:当有天我访问某个网页时,突然希望能够像在PDF上一样,对网页文本添加高亮笔记,并支持永久保存这些高亮笔记区域。
36
+
37
+ 如果你曾经访问过 [medium.com](http://medium.com) 这个网站,你一定见到或用过它的高亮在线笔记功能:用户选择一个文本片段然后点击“高亮”按钮,接着,这段文本就被涂上了背景颜色。此外,这些高亮区域还被存储了下来,当你下次访问时会自动恢复。就像下面这个简单的示例一样。
38
+
39
+ ![](./docs/img/sample.gif)
40
+
41
+ 对在线阅读器来说,这是一个非常有用的功能。如果你是开发者,也许你会希望在你的网页上支持该功能以吸引更多的读者;如果你是一名用户(像我一样),你可能会想要一个支持该功能的浏览器插件。
42
+
43
+ 因此,「web-highlighter」仓库的目标就是帮助你在任意的网页上快速地实现高亮笔记功能(例如博客网页、文档阅读器、在线图书等)。它包含了文本高亮笔记与高亮持久化下场景的核心能力,并且支持通过它简单易用的 API 来实现你自己的产品需求。「web-highlighter」已经被用在了我们网站的生产环境中。
44
+
45
+
46
+ ## 2. <a name='-1'></a>安装
47
+
48
+ ```bash
49
+ npm i web-highlighter
50
+ ```
51
+
52
+ ## 3. <a name='-1'></a>使用方式
53
+
54
+ 两行代码,即可开启文本选中时的自动高亮功能。
55
+
56
+ ```JavaScript
57
+ import Highlighter from 'web-highlighter';
58
+ (new Highlighter()).run();
59
+ ```
60
+
61
+ 四行代码,实现高亮区域持久化。
62
+
63
+ ```JavaScript
64
+ import Highlighter from 'web-highlighter';
65
+
66
+ // 1. 实例化
67
+ const highlighter = new Highlighter();
68
+
69
+ // 2. 从后端获取高亮信息,还原至网页
70
+ getRemoteData().then(s => highlighter.fromStore(s.startMeta, s.endMeta, s.id, s.text));
71
+
72
+ // 3. 监听高亮笔记创建事件,并将信息存至后端
73
+ highlighter.on(Highlighter.event.CREATE, ({sources}) => save(sources));
74
+
75
+ // 4. 开启自动划词高亮
76
+ highlighter.run();
77
+ ```
78
+
79
+ ## 4. <a name='-1'></a>示例
80
+
81
+ 一个更复杂的使用示例。
82
+
83
+ ```JavaScript
84
+ import Highlighter from 'web-highlighter';
85
+
86
+ // 不高亮 pre&code 元素
87
+ const highlighter = new Highlighter({
88
+ exceptSelectors: ['pre', 'code']
89
+ });
90
+
91
+ // 添加一些交互监听
92
+ highlighter
93
+ .on('selection:hover', ({id}) => {
94
+ // 通过添加 class,实现类似 hover 效果
95
+ highlighter.addClass('highlight-wrap-hover', id);
96
+ })
97
+ .on('selection:hover-out', ({id}) => {
98
+ // 鼠标离开时清除悬停样式
99
+ highlighter.removeClass('highlight-wrap-hover', id);
100
+ })
101
+ .on('selection:create', ({sources}) => {
102
+ sources = sources.map(hs => ({hs}));
103
+ // 存储
104
+ store.save(sources);
105
+ });
106
+
107
+ // 获取数据存储,将高亮区域还原展示在网页上
108
+ store.getAll().forEach(
109
+ ({hs}) => highlighter.fromStore(hs.startMeta, hs.endMeta, hs.text, hs.id)
110
+ );
111
+
112
+ highlighter.run()
113
+ ```
114
+
115
+ 此外,该仓库还自带了一个 DEMO 示例(在`example`文件夹中)。启动该 DEMO 只需要 ——
116
+
117
+ 首先安装运行依赖
118
+
119
+ ```bash
120
+ npm i
121
+ ```
122
+
123
+ 然后启动
124
+
125
+ ```bash
126
+ npm start
127
+ ```
128
+
129
+ 最后访问 http://127.0.0.1:8085/ 即可。
130
+
131
+ ---
132
+
133
+ 另一个使用 web-highlighter 开发的实际产品的例子(用于左侧高亮部分):
134
+
135
+ ![product sample](https://user-images.githubusercontent.com/9822789/64678049-632e8500-d4ab-11e9-99d6-f960bc90d17b.gif)
136
+
137
+ ## 5. <a name='-1'></a>工作原理
138
+
139
+ web-highlighter 会通过 [`Selection API`](https://caniuse.com/#search=selection%20api) 来读取被选择的文本范围。然后选区的信息会被转换为一个可序列化的数据结构,以便于能够发送并存储在后端。当用户再次访问你的页面时,这些存储的数据被返回然后在你的页面上进行反序列化。数据结构本身是技术栈无关的。所以你可以用在任意技术栈构建的页面上(例如 React、Vue、Angular 或者 jQuery 等等)。
140
+
141
+ 想要了解更多实现细节,可以阅读[这篇文章](https://www.alienzhou.com/2019/04/21/web-note-highlight-in-js/)。
142
+
143
+ ## 6. <a name='-1'></a>详细使用文档
144
+
145
+ ### 6.1. <a name='-1'></a>配置项
146
+
147
+ ```JavaScript
148
+ const highlighter = new Highlighter([opts])
149
+ ```
150
+
151
+ 创建一个新的 `highlighter` 实例.
152
+
153
+ `opts` 会合并至默认配置 (如下所示).
154
+
155
+ ```JavaScript
156
+ {
157
+ $root: document.documentElement,
158
+ exceptSelectors: null,
159
+ wrapTag: 'span',
160
+ style: {
161
+ className: 'highlight-mengshou-wrap'
162
+ }
163
+ }
164
+ ```
165
+
166
+ 配置说明:
167
+
168
+ | 参数名 | 类型 | 描述 | 是否必须 | 默认值 |
169
+ |---|---|---|---|---|
170
+ | $root | `Document | HTMLElement` | 高亮区域的根容器元素 | 否 | `document` |
171
+ | exceptSelectors | `Array<string>` | 过滤器,符合的元素将不会被高亮 | 否 | `null` |
172
+ | wrapTag | `string` | 用于包裹高亮文本的 HTML 标签名 | 否 | `span` |
173
+ | verbose | `boolean` | 是否需要输出警告和错误信息 | 否 | `false` |
174
+ | style | `Object` | 用于控制高亮区域的样式 | 否 | 详见下方 |
175
+
176
+ `style` 属性配置:
177
+
178
+ | 参数名 | 类型 | 描述 | 是否必须 | 默认值 |
179
+ |---|---|---|---|---|
180
+ | className | `string` | 高亮包裹元素的 className | 否 | `highlight-mengshou-wrap` |
181
+
182
+ `exceptSelectors` 为 `null` 或 `Array<string>`。 支持 ID 选择器、类选择器和标签选择器。例如,想要忽略标签为 h1 和 classname 为 `.title` 的元素:
183
+
184
+ ```JavaScript
185
+ var highlighter = new Highlighter({
186
+ exceptSelectors: ['h1', '.title']
187
+ });
188
+ ```
189
+
190
+ ### 6.2. <a name='-1'></a>静态方法
191
+
192
+ #### 6.2.1. <a name='Highlighter.isHighlightSourcesource'></a>`Highlighter.isHighlightSource(source)`
193
+
194
+ 用于判断 `source` 参数是否为一个 highlight source 对象。如果是则返回 `true`, 反之亦然.
195
+
196
+ #### 6.2.2. <a name='Highlighter.isHighlightWrapNodenode'></a>`Highlighter.isHighlightWrapNode($node)`
197
+
198
+ 用于判断 `$node` 参数是否为一个高亮包裹元素。如果是则返回 `true`, 反之亦然.
199
+
200
+ ### 6.3. <a name='-1'></a>实例方法
201
+
202
+ #### 6.3.1. <a name='highlighter.run'></a>`highlighter.run()`
203
+
204
+ 开启自动划词高亮。当用户选择了一段文本时,「web-highlighter」会自动为其添加高亮效果。
205
+
206
+ #### 6.3.2. <a name='highlighter.stop'></a>`highlighter.stop()`
207
+
208
+ 关闭自动划词高亮。
209
+
210
+ #### 6.3.3. <a name='highlighter.dispose'></a>`highlighter.dispose()`
211
+
212
+ 当你不再需要使用高亮功能时,需要先使用该方法来移除一些事件监听,回收一些资源。
213
+
214
+ #### 6.3.4. <a name='highlighter.fromRangerange'></a>`highlighter.fromRange(range)`
215
+
216
+ 该方法支持你传一个 [`Range`](https://developer.mozilla.org/en-US/docs/Web/API/Range),并基于该对象进行高亮笔记操作。你可以通过 `window.getSelection().getRangeAt(0)` 方法来获取一个 range 对象,或者使用 `document.createRange()` 方法来创建一个新的 range 对象。
217
+
218
+ 如下所示:
219
+
220
+ ```JavaScript
221
+ const selection = window.getSelection();
222
+ if (!selection.isCollapsed) {
223
+ highlighter.fromRange(selection.getRangeAt(0));
224
+ }
225
+ ```
226
+
227
+ #### 6.3.5. <a name='highlighter.fromStorestartendtextid'></a>`highlighter.fromStore(start, end, text, id)`
228
+
229
+ 大多数情况下,这个 API 用于通过后端的持久化信息还原出文本高亮效果。
230
+
231
+ 其中四个所需的参数来源于 `HighlightSource` 对象。`HighlightSource` 对象是一个特殊的对象,当高亮笔记被添加时会被创建。为了能在后端实现数据持久化,它需要找到一种能表示 dom 节点的数据结构。这个结构在 web-highlighter 内被称为`HighlightSource`。
232
+
233
+ 四个参数的含义如下:
234
+
235
+ - start `Object`: 开始节点的源信息
236
+ - end `Object`: 结束节点的源信息
237
+ - text `string`: 文本内容
238
+ - id `string`: 高亮的唯一 ID
239
+
240
+ #### 6.3.6. <a name='highlighter.removeid'></a>`highlighter.remove(id)`
241
+
242
+ 清除指定 id 的高亮区域。该 id 默认会由 web-highlighter 在创建高亮区域使生成。你也可以通过添加钩子来应用你自己的 id 生成规则。钩子相关文档可以[看这里](https://github.com/alienzhou/web-highlighter/blob/master/docs/ADVANCE.zh_CN.md)。
243
+
244
+ #### 6.3.7. <a name='highlighter.removeAll'></a>`highlighter.removeAll()`
245
+
246
+ 清除根节点下的所有高亮区域。
247
+
248
+ #### 6.3.8. <a name='highlighter.addClassclassnameid'></a>`highlighter.addClass(classname, id)`
249
+
250
+ 为某个 id 的高亮区域添加 CSS 类名。你可以通过这个 API 来改变某个高亮区域的样式。
251
+
252
+ #### 6.3.9. <a name='highlighter.removeClassclassnameid'></a>`highlighter.removeClass(classname, id)`
253
+
254
+ 移除某个 id 的高亮区域的指定 CSS 类名。类似于 `highlighter.addClass` 的逆操作。
255
+
256
+ #### 6.3.10. <a name='highlighter.getDomsid'></a>`highlighter.getDoms([id])`
257
+
258
+ 获取高亮区域内的所有包裹节点。一个高亮区域可能会包含多个片段。它会返回所有这些片段的包裹节点(DOM 节点)。
259
+
260
+ 如果 `id` 参数留空,它会返回根节点下的所有高亮区域中的包裹节点。
261
+
262
+ #### 6.3.11. <a name='highlighter.getIdByDomnode'></a>`highlighter.getIdByDom(node)`
263
+
264
+ 传入一个 DOM 节点,返回该节点对应的高亮区域的唯一 ID。支持传入非包裹元素。如果是非包裹,则会自动找到最近的祖先包裹元素。
265
+
266
+ #### 6.3.11. <a name='highlighter.getExtraIdByDomnode'></a>`highlighter.getExtraIdByDom(node)`
267
+
268
+ 传入一个 DOM 节点,返回该节点对应的高亮区域的额外 ID。支持传入非包裹元素。如果是非包裹,则会自动找到最近的祖先包裹元素。
269
+
270
+ #### 6.3.12. <a name='highlighter.setOptionopt'></a>`highlighter.setOption(opt)`
271
+
272
+ 可以使用该 API 改变实例的配置项,参数结构和构造函数中的一致,支持传入部分参数。
273
+
274
+ ### 6.4. <a name='EventListener'></a>`Event Listener`
275
+
276
+ web-highlighter 使用监听器方式来处理异步事件。
277
+
278
+ 例如下面这样,
279
+
280
+ ```JavaScript
281
+ var highlighter = new Highlighter();
282
+ highlighter.on(Highlighter.event.CREATE, function (data, inst, e) {
283
+ // ...
284
+ });
285
+ ```
286
+
287
+ 回调函数接受三个参数:
288
+
289
+ - data `any`: 事件触发时的具体数据
290
+ - inst `Highlighter`: 当前 Highlighter 类的实例
291
+ - e `Event`: 某些事件会有浏览器触发(例如点击), web-highlighter 会将浏览器原生 event 对象暴露出来
292
+
293
+ `Highlighter.event` 是内部的 `EventType` 类型. 它包含了如下这些事件:
294
+
295
+ - `EventType.CLICK`: 点击高亮区域
296
+ - `EventType.HOVER`: 鼠标移至高亮区域,类似 mouse enter
297
+ - `EventType.HOVER_OUT`: 鼠标移出高亮区域,类似 mouse leave
298
+ - `EventType.CREATE`: 高亮区域被创建
299
+ - `EventType.REMOVE`: 高亮区域被清除
300
+
301
+ 对于不同的事件类型,其 `data` 所包含的具体属性如下:
302
+
303
+ #### 6.4.1. <a name='EventType.CLICK'></a>`EventType.CLICK`
304
+
305
+ |name|description|type|
306
+ |---|---|---|
307
+ |`id`| 高亮区域唯一 ID |string|
308
+
309
+ #### 6.4.2. <a name='EventType.HOVER'></a>`EventType.HOVER`
310
+
311
+ |name|description|type|
312
+ |---|---|---|
313
+ |`id`| 高亮区域唯一 ID |string|
314
+
315
+ #### 6.4.3. <a name='EventType.HOVER_OUT'></a>`EventType.HOVER_OUT`
316
+
317
+ |name|description|type|
318
+ |---|---|---|
319
+ |`id`| 高亮区域唯一 ID |string|
320
+
321
+ #### 6.4.4. <a name='EventType.CREATE'></a>`EventType.CREATE`
322
+
323
+ > 不包含参数 `e`
324
+
325
+ |name|description|type|
326
+ |---|---|---|
327
+ |`source`|`HighlightSource` 对象|Array|
328
+ |`type`|高亮区域创建的来源|string|
329
+
330
+ `source` 是一个 `HighlightSource` 对象。该对象在高亮区域被创建时,会由 web-highlighter 创建并传给回调函数。为了能够在后端(数据库中)进行高亮数据的持久化,需要使用一个可以被序列化(`JSON.stringify()`)的数据结构来表示浏览器中的 DOM 节点。`HighlightSource` 就是 web-highlighter 提供的来用于持久化的数据对象。
331
+
332
+ `type` 用来告知开发者高亮区域被创建的原因。目前 `type` 包含两种可能的值:`from-input` 和 `from-store`。`from-input` 表明该高亮区域是通过用户操作(用户划词的选区)创建的;`from-store` 则表示该高亮区域是通过持久化的 `HighlightSource` 中的数据还原出来的。
333
+
334
+ #### 6.4.5. <a name='EventType.REMOVE'></a>`EventType.REMOVE`
335
+
336
+ > 不包含参数 `e`
337
+
338
+ |name|description|type|
339
+ |---|---|---|
340
+ |`ids`|一组高亮区域唯一 ID|Array<string>|
341
+
342
+ ### 6.5. <a name='Hooks'></a>Hooks(钩子)
343
+
344
+ 钩子可以用来更好地控制整个高亮流程。通过它你几乎可以实现任何自定义的逻辑。详细内容请参考[下面部分](#更多使用方式)。
345
+
346
+ ## 7. <a name='-1'></a>兼容性
347
+
348
+ > 依赖 [Selection API](https://caniuse.com/#search=selection%20api)。
349
+
350
+ - IE 11
351
+ - Edge
352
+ - Firefox 52+
353
+ - Chrome 15+
354
+ - Safari 5.1+
355
+ - Opera 15+
356
+
357
+ _**移动端支持:**_ 如果检测为移动端,则会自动使用相应的事件监听来替代 PC 端事件。
358
+
359
+ ## 8. <a name='-1'></a>更多使用方式
360
+
361
+ 为了便于开发者更好地控制相关的高亮行为,web-highlighter 提供一些内部的钩子。
362
+
363
+ 想了解内部钩子及其使用方式,可以阅读[这篇文档](https://github.com/alienzhou/web-highlighter/blob/master/docs/ADVANCE.zh_CN.md)。
364
+
365
+ ## 9. <a name='-1'></a>许可证
366
+
367
+ [MIT](./LICENCE)
@@ -0,0 +1,25 @@
1
+ const path = require('path');
2
+ const {basePath} = require('./paths');
3
+ const {TsconfigPathsPlugin} = require('tsconfig-paths-webpack-plugin');
4
+
5
+ module.exports = {
6
+ entry: [
7
+ path.resolve(basePath, 'src/index.ts')
8
+ ],
9
+ module: {
10
+ rules: [{
11
+ test: /.ts$/,
12
+ loader: 'ts-loader',
13
+ }]
14
+ },
15
+ output: {
16
+ path: path.resolve(basePath, 'dist'),
17
+ library: 'Highlighter',
18
+ libraryTarget: 'umd',
19
+ libraryExport: 'default'
20
+ },
21
+ resolve: {
22
+ plugins: [new TsconfigPathsPlugin()],
23
+ extensions: ['.ts', '.tsx', '.js']
24
+ }
25
+ };
@@ -0,0 +1,38 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const paths = require('./paths');
4
+ const {examplePath, staticPath} = require('./paths.js');
5
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
6
+ const TextReplaceHtmlWebpackPlugin = require('text-replace-html-webpack-plugin');
7
+ const merge = require('webpack-merge');
8
+ const baseConfig = require('./base.config');
9
+ const mdContent = fs.readFileSync(paths.exampleMdPath, 'utf-8');
10
+
11
+ const config = {
12
+ entry: [
13
+ path.resolve(examplePath, 'index.js')
14
+ ],
15
+ module: {
16
+ rules: [{
17
+ test: /\.css$/,
18
+ use: ['style-loader', 'css-loader']
19
+ }]
20
+ },
21
+ plugins: [
22
+ new HtmlWebpackPlugin({
23
+ template: path.resolve(examplePath, 'index.html')
24
+ }),
25
+ new TextReplaceHtmlWebpackPlugin({
26
+ replacementArray: [{
27
+ regex : /{{\$markdown}}/,
28
+ replace : mdContent
29
+ }]
30
+ })
31
+ ],
32
+ output: {
33
+ path: staticPath,
34
+ filename: 'index.js'
35
+ }
36
+ };
37
+
38
+ module.exports = merge(baseConfig, config);
@@ -0,0 +1,22 @@
1
+ const path = require('path');
2
+ const basePath = path.resolve(__dirname, '..');
3
+ const distDirname = 'dist';
4
+ const staticDirname = 'static';
5
+ const srcPath = path.resolve(basePath, 'src');
6
+ const examplePath = path.resolve(basePath, 'example');
7
+ const staticPath = path.resolve(examplePath, staticDirname);
8
+ const distPath = path.resolve(basePath, distDirname);
9
+ const exampleMdPath = path.resolve(examplePath, 'index.html');
10
+ const exampleTplPath = path.resolve(examplePath, 'tpl.html');
11
+
12
+ module.exports = {
13
+ basePath,
14
+ distDirname,
15
+ staticDirname,
16
+ examplePath,
17
+ staticPath,
18
+ distPath,
19
+ srcPath,
20
+ exampleMdPath,
21
+ exampleTplPath
22
+ };
@@ -0,0 +1,17 @@
1
+ const staticPath = require('./paths.js').staticPath;
2
+ const PORT = process.env.PORT || 8085;
3
+ const HOST = process.env.HOST || '0.0.0.0';
4
+
5
+ module.exports = {
6
+ disableHostCheck: true,
7
+ compress: true,
8
+ hot: true,
9
+ watchContentBase: true,
10
+ watchOptions: {
11
+ ignored: /node_modules/
12
+ },
13
+ host: HOST,
14
+ port: PORT,
15
+ contentBase: staticPath,
16
+ index: 'index.html'
17
+ };
@@ -0,0 +1,18 @@
1
+ // cSpell:ignore devtool
2
+ /**
3
+ * config for dev environment
4
+ */
5
+ const webpack = require('webpack');
6
+ const merge = require('webpack-merge');
7
+ const baseConfig = require('./base.example.config');
8
+
9
+ const config = {
10
+ mode: 'development',
11
+ devtool: 'source-map',
12
+ plugins: [
13
+ new webpack.NamedModulesPlugin(),
14
+ new webpack.HotModuleReplacementPlugin()
15
+ ]
16
+ };
17
+
18
+ module.exports = merge(baseConfig, config);
@@ -0,0 +1,20 @@
1
+ // cSpell:ignore devtool,contenthash
2
+ /**
3
+ * config for building example bundle (homepage)
4
+ */
5
+ const {staticDirname, examplePath} = require('./paths.js');
6
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
7
+ const merge = require('webpack-merge');
8
+ const baseConfig = require('./base.example.config');
9
+
10
+ const config = {
11
+ mode: 'production',
12
+ plugins: [
13
+ new CleanWebpackPlugin([staticDirname], {root: examplePath})
14
+ ],
15
+ output: {
16
+ filename: 'index.[contenthash:8].js'
17
+ }
18
+ };
19
+
20
+ module.exports = merge(baseConfig, config);
@@ -0,0 +1,28 @@
1
+ // cSpell:ignore devtool
2
+ /**
3
+ * config for production (lib bundle)
4
+ */
5
+ const webpack = require('webpack');
6
+ const {distDirname, basePath} = require('./paths.js');
7
+ const CleanWebpackPlugin = require('clean-webpack-plugin');
8
+ const merge = require('webpack-merge');
9
+ const baseConfig = require('./base.config');
10
+ const pkg = require('../package.json');
11
+ const name = pkg.name;
12
+ const version = pkg.version;
13
+ const repository = pkg.repository.url;
14
+ const bannerInfo = `${name} v${version} ${repository}`;
15
+
16
+ const config = {
17
+ mode: 'production',
18
+ devtool: 'source-map',
19
+ output: {
20
+ filename: 'web-highlighter.min.js'
21
+ },
22
+ plugins: [
23
+ new CleanWebpackPlugin([distDirname], {root: basePath}),
24
+ new webpack.BannerPlugin(bannerInfo)
25
+ ]
26
+ };
27
+
28
+ module.exports = merge(baseConfig, config);
@@ -0,0 +1,13 @@
1
+ import EventEmitter from '../util/event.emitter';
2
+ import type HighlightSource from '../model/source';
3
+ declare class Cache extends EventEmitter {
4
+ private _data;
5
+ get data(): HighlightSource[];
6
+ set data(map: HighlightSource[]);
7
+ save(source: HighlightSource | HighlightSource[]): void;
8
+ get(id: string): HighlightSource;
9
+ remove(id: string): void;
10
+ getAll(): HighlightSource[];
11
+ removeAll(): string[];
12
+ }
13
+ export default Cache;
@@ -0,0 +1,58 @@
1
+ import type { DomMeta, HookMap, HighlighterOptions } from './types';
2
+ import EventEmitter from './util/event.emitter';
3
+ import HighlightSource from './model/source';
4
+ import Cache from './data/cache';
5
+ import Painter from './painter';
6
+ import { EventType, CreateFrom } from './types';
7
+ interface EventHandlerMap {
8
+ [key: string]: (...args: any[]) => void;
9
+ [EventType.CLICK]: (data: {
10
+ id: string;
11
+ }, h: Highlighter, e: MouseEvent | TouchEvent) => void;
12
+ [EventType.HOVER]: (data: {
13
+ id: string;
14
+ }, h: Highlighter, e: MouseEvent | TouchEvent) => void;
15
+ [EventType.HOVER_OUT]: (data: {
16
+ id: string;
17
+ }, h: Highlighter, e: MouseEvent | TouchEvent) => void;
18
+ [EventType.CREATE]: (data: {
19
+ sources: HighlightSource[];
20
+ type: CreateFrom;
21
+ }, h: Highlighter) => void;
22
+ [EventType.REMOVE]: (data: {
23
+ ids: string[];
24
+ }, h: Highlighter) => void;
25
+ }
26
+ export default class Highlighter extends EventEmitter<EventHandlerMap> {
27
+ static event: typeof EventType;
28
+ static isHighlightWrapNode: ($node: HTMLElement) => boolean;
29
+ hooks: HookMap;
30
+ painter: Painter;
31
+ cache: Cache;
32
+ private _hoverId;
33
+ private options;
34
+ private readonly event;
35
+ constructor(options?: HighlighterOptions);
36
+ static isHighlightSource: (d: any) => boolean;
37
+ run: () => () => void;
38
+ stop: () => void;
39
+ addClass: (className: string, id?: string) => void;
40
+ removeClass: (className: string, id?: string) => void;
41
+ getIdByDom: ($node: HTMLElement) => string;
42
+ getExtraIdByDom: ($node: HTMLElement) => string[];
43
+ getDoms: (id?: string) => HTMLElement[];
44
+ dispose: () => void;
45
+ setOption: (options?: HighlighterOptions) => void;
46
+ fromRange: (range: Range) => HighlightSource;
47
+ fromStore: (start: DomMeta, end: DomMeta, text: string, id: string, extra?: unknown) => HighlightSource;
48
+ remove(id: string): void;
49
+ removeAll(): void;
50
+ private readonly _getHooks;
51
+ private readonly _highlightFromHRange;
52
+ private _highlightFromHSource;
53
+ private readonly _handleSelection;
54
+ private readonly _handleHighlightHover;
55
+ private readonly _handleError;
56
+ private readonly _handleHighlightClick;
57
+ }
58
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * some dom operations about HighlightRange
3
+ */
4
+ import type { DomMeta, DomNode } from '../../types';
5
+ export declare const getDomMeta: ($node: HTMLElement | Text, offset: number, $root: Document | HTMLElement) => DomMeta;
6
+ export declare const formatDomNode: (n: DomNode) => DomNode;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * the HighlightRange Class(HRange)
3
+ * It's a special object called HRange in Highlighter,
4
+ * represents for a piece of chosen dom
5
+ */
6
+ import type { DomNode, HookMap } from '../../types';
7
+ import type Hook from '../../util/hook';
8
+ import HighlightSource from '../source/index';
9
+ declare class HighlightRange {
10
+ static removeDomRange: () => void;
11
+ start: DomNode;
12
+ end: DomNode;
13
+ text: string;
14
+ id: string;
15
+ frozen: boolean;
16
+ constructor(start: DomNode, end: DomNode, text: string, id: string, frozen?: boolean);
17
+ static fromSelection(idHook: Hook<string>): HighlightRange;
18
+ serialize($root: Document | HTMLElement, hooks: HookMap): HighlightSource;
19
+ }
20
+ export default HighlightRange;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Something about the Selection/Range API in browsers.
3
+ * If you want to use Highlighter in some old browsers, you may use a polyfill.
4
+ * https://caniuse.com/#search=selection
5
+ */
6
+ export declare const getDomRange: () => Range;
7
+ export declare const removeSelection: () => void;
8
+ /**
9
+ * Get text from a Range with proper newline handling for block elements.
10
+ * Uses innerText on a cloned fragment to get clean output matching Selection.toString().
11
+ * Range.toString() includes HTML source whitespace, this method gives user-expected output.
12
+ * Falls back to range.toString() in environments where innerText isn't available (e.g. jsdom).
13
+ */
14
+ export declare const getTextFromRange: (range: Range) => string;