Skip to content

Commit c9ca81a

Browse files
authored
Merge pull request #44 from alienzhou/feat/deserialize-hook
Feat/deserialize hook
2 parents 27af33b + b350aeb commit c9ca81a

21 files changed

+312
-39
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,10 @@ If the `id` is not passed, it will return all the areas' wrap nodes.
266266

267267
If you have a wrap node, it can return the unique highlight id for you.
268268

269+
#### `highlighter.getExtraIdByDom(node)`
270+
271+
If you have a wrap node, it can return the extra unique highlight id for you.
272+
269273
### 4. Event Listener
270274

271275
web-highlighter use listeners to handle the events.

README.zh_CN.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,11 @@ if (selection.isCollapsed) {
261261

262262
#### 6.3.11. <a name='highlighter.getIdByDomnode'></a>`highlighter.getIdByDom(node)`
263263

264-
传入一个包裹节点,返回该节点对应的高亮区域去的唯一 ID。
264+
传入一个包裹节点,返回该节点对应的高亮区域的唯一 ID。
265+
266+
#### 6.3.11. <a name='highlighter.getExtraIdByDomnode'></a>`highlighter.getExtraIdByDom(node)`
267+
268+
传入一个包裹节点,返回该节点对应的高亮区域的额外 ID。
265269

266270
### 6.4. <a name='EventListener'></a>`Event Listener`
267271

docs/ADVANCE.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ highlighter.hooks.Render.UUID.tap(function (start, end, text) {
3434

3535
Hook into the process of generating a unique id.
3636

37-
**arguements:**
37+
**arguments:**
3838

3939
- start: DOM info of the start node
4040
- end: DOM info of the end node
@@ -48,7 +48,7 @@ Hook into the process of generating a unique id.
4848

4949
Process all the text nodes in the highlighted area and return a new list by using this hook.
5050

51-
**arguements:**
51+
**arguments:**
5252

5353
- id: id of the highlighted area
5454
- selectedNodes: all the text nodes in the highlighted area
@@ -61,7 +61,7 @@ Process all the text nodes in the highlighted area and return a new list by usin
6161

6262
Process the wrapping elements.
6363

64-
**arguements:**
64+
**arguments:**
6565

6666
- id: id of the highlighted area
6767
- node: the wrapping elements
@@ -70,11 +70,23 @@ Process the wrapping elements.
7070

7171
- the wrapping elements
7272

73+
### `Serialize.Restore`
74+
75+
Customize your own restoring method. When you tap this hook, the `HighlightSource` instance will use the function you pass to calculate the start and end nodes' info.
76+
77+
**arguments:**
78+
79+
- hs: the current `HighlightSource` instance
80+
81+
**return value needed:**
82+
83+
- an array: the first element is the start info and the second is the end
84+
7385
### `Serialize.RecordInfo`
7486

7587
Add or modify some extra info when serialize the `HighlightRange` object.
7688

77-
**arguements:**
89+
**arguments:**
7890

7991
- start: meta info of the start node
8092
- end: meta info of the end node
@@ -88,7 +100,7 @@ Add or modify some extra info when serialize the `HighlightRange` object.
88100

89101
Process the affected wrapping elements when remove highlighted areas.
90102

91-
**arguements:**
103+
**arguments:**
92104

93105
- id: id of the highlighted area
94106
- nodes: the affected wrapping elements

docs/ADVANCE.zh_CN.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ highlighter.hooks.Render.UUID.tap(function (start, end, text) {
7070

7171
- 高亮包裹的节点
7272

73+
### `Serialize.Restore`
74+
75+
自定义你的还原(反序列化)方法。当你使用这个钩子时,`HighlightSource` 实例会调用你注册的方法来计算高亮选区的首尾节点信息。
76+
77+
**参数:**
78+
79+
- hs: 当前的 `HighlightSource` 实例
80+
81+
**所需的返回值:**
82+
83+
- 一个数组对象: 数组的第一个元素是选区开始的节点信息,第二个元素是结束的节点信息
84+
7385
### `Serialize.RecordInfo`
7486

7587
为选区序列化时的持久化数据生成额外信息

docs/img/create-flow.jpg

-46.3 KB
Loading

docs/img/create-flow.zh_CN.jpg

-42.6 KB
Loading

docs/img/remove-flow.jpg

-57.2 KB
Loading

docs/img/remove-flow.zh_CN.jpg

-48.1 KB
Loading

example/index.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,25 @@ highlighter
8686
* attach hooks
8787
*/
8888
highlighter.hooks.Render.SelectedNodes.tap(
89-
(id, selectedNodes) => selectedNodes.filter(n => n.$node.textContent)
89+
(id, selectedNodes) => selectedNodes.filter(n => n.$node.textContent)
9090
);
9191

92+
highlighter.hooks.Serialize.Restore.tap(
93+
source => log('Serialize.Restore hook -', source)
94+
);
95+
96+
highlighter.hooks.Serialize.RecordInfo.tap(() => {
97+
const extraInfo = Math.random().toFixed(4);
98+
log('Serialize.RecordInfo hook -', extraInfo)
99+
return extraInfo;
100+
});
101+
92102
/**
93103
* retrieve from local store
94104
*/
95105
const storeInfos = store.getAll();
96106
storeInfos.forEach(
97-
({hs}) => highlighter.fromStore(hs.startMeta, hs.endMeta, hs.text, hs.id)
107+
({hs}) => highlighter.fromStore(hs.startMeta, hs.endMeta, hs.text, hs.id, hs.extra)
98108
);
99109

100110
let autoStatus;

example/local.store.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,6 @@ class LocalStore {
4747
this.jsonToStore(stores);
4848
}
4949

50-
get(id) {
51-
const list = this.storeToJson()
52-
.filter(store => store.hs.id === id)
53-
.map(store => ({
54-
hs: new HighlightSource(
55-
store.hs.startMeta,
56-
store.hs.endMeta,
57-
store.hs.text,
58-
store.hs.id
59-
),
60-
info: store.info
61-
}));
62-
return list[0];
63-
}
64-
6550
remove(id) {
6651
const stores = this.storeToJson();
6752
let index = null;

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web-highlighter",
3-
"version": "0.5.2",
3+
"version": "0.6.0",
44
"description": "✨A no-runtime dependency lib for text highlighting & persistence on any website ✨🖍️",
55
"main": "dist/web-highlighter.min.js",
66
"browser": "dist/web-highlighter.min.js",
@@ -17,6 +17,11 @@
1717
"build": "export target=dist && node script/build.js",
1818
"prepublishOnly": "npm run build"
1919
},
20+
"husky": {
21+
"hooks": {
22+
"pre-commit": "npm run test"
23+
}
24+
},
2025
"homepage": "https://alienzhou.github.io/web-highlighter",
2126
"repository": {
2227
"type": "git",
@@ -49,6 +54,7 @@
4954
"fs-extra": "^7.0.1",
5055
"html-webpack-plugin": "^3.2.0",
5156
"http-server": "^0.11.1",
57+
"husky": "^4.2.5",
5258
"jsdom": "^16.2.2",
5359
"jsdom-global": "^3.0.2",
5460
"mocha": "^7.1.2",

src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
removeClass,
2727
isHighlightWrapNode,
2828
getHighlightById,
29+
getExtraHighlightId,
2930
getHighlightsByRoot,
3031
getHighlightId,
3132
addEventListener,
@@ -65,6 +66,7 @@ export default class Highlighter extends EventEmitter {
6566
WrapNode: new Hook('Render.WrapNode')
6667
},
6768
Serialize: {
69+
Restore: new Hook('Serialize.Restore'),
6870
RecordInfo: new Hook('Serialize.RecordInfo')
6971
},
7072
Remove: {
@@ -143,6 +145,7 @@ export default class Highlighter extends EventEmitter {
143145
removeClass = (className: string, id?: string) => this.getDoms(id).forEach($n => removeClass($n, className));
144146

145147
getIdByDom = ($node: HTMLElement): string => getHighlightId($node);
148+
getExtraIdByDom = ($node: HTMLElement): string[] => getExtraHighlightId($node);
146149
getDoms = (id?: string): Array<HTMLElement> => id
147150
? getHighlightById(this.options.$root, id, this.options.wrapTag)
148151
: getHighlightsByRoot(this.options.$root, this.options.wrapTag);
@@ -191,9 +194,9 @@ export default class Highlighter extends EventEmitter {
191194
return this._highlightFromHRange(hRange);
192195
}
193196

194-
fromStore = (start: DomMeta, end: DomMeta, text, id): HighlightSource => {
197+
fromStore = (start: DomMeta, end: DomMeta, text: string, id: string, extra?: unknown): HighlightSource => {
195198
try {
196-
const hs = new HighlightSource(start, end, text, id);
199+
const hs = new HighlightSource(start, end, text, id, extra);
197200
this._highlightFromHSource(hs);
198201
return hs;
199202
}

src/model/source/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Also it has the ability for persistence.
55
*/
66

7-
import {DomMeta} from '../../types';
7+
import {DomMeta, HookMap, DomNode} from '../../types';
88
import HighlightRange from '../range/index';
99
import {queryElementNode, getTextChildByOffset} from './dom';
1010

@@ -13,15 +13,15 @@ class HighlightSource {
1313
endMeta: DomMeta;
1414
text: string;
1515
id: string;
16-
extra?: any;
17-
__isHighlightSource: any;
16+
extra?: unknown;
17+
__isHighlightSource: unknown;
1818

1919
constructor(
2020
startMeta: DomMeta,
2121
endMeta: DomMeta,
2222
text: string,
2323
id: string,
24-
extra?: any
24+
extra?: unknown
2525
) {
2626
this.startMeta = startMeta;
2727
this.endMeta = endMeta;
@@ -33,10 +33,16 @@ class HighlightSource {
3333
}
3434
}
3535

36-
deSerialize($root: HTMLElement | Document): HighlightRange {
36+
deSerialize($root: HTMLElement | Document, hooks: HookMap): HighlightRange {
3737
const {start, end} = queryElementNode(this, $root);
38-
const startInfo = getTextChildByOffset(start, this.startMeta.textOffset);
39-
const endInfo = getTextChildByOffset(end, this.endMeta.textOffset);
38+
let startInfo = getTextChildByOffset(start, this.startMeta.textOffset);
39+
let endInfo = getTextChildByOffset(end, this.endMeta.textOffset);
40+
41+
if (!hooks.Serialize.Restore.isEmpty()) {
42+
const res: DomNode[] = hooks.Serialize.Restore.call(this, startInfo, endInfo) || [];
43+
startInfo = res[0] || startInfo;
44+
endInfo = res[1] || endInfo;
45+
}
4046

4147
const range = new HighlightRange(
4248
startInfo,

src/painter/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default class Painter {
7373
});
7474
return;
7575
}
76-
const range = s.deSerialize(this.options.$root);
76+
const range = s.deSerialize(this.options.$root, this.hooks);
7777
const $nodes = this.highlightRange(range);
7878
if ($nodes.length > 0) {
7979
renderedSources.push(s);

src/types/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export interface DomMeta {
6969
parentTagName: string;
7070
parentIndex: number;
7171
textOffset: number;
72-
extra?: any;
72+
extra?: unknown;
7373
}
7474

7575
export interface DomNode {
@@ -95,6 +95,7 @@ export type HookMap = {
9595
WrapNode: Hook;
9696
};
9797
Serialize: {
98+
Restore: Hook;
9899
RecordInfo: Hook;
99100
};
100101
Remove: {

src/util/dom.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ export const getHighlightId = ($node: HTMLElement): string => {
2727
return '';
2828
};
2929

30+
/**
31+
* get extra highlight id by wrapping node
32+
*/
33+
export const getExtraHighlightId = ($node: HTMLElement): string[] => {
34+
if (isHighlightWrapNode($node)) {
35+
const extraId = $node.dataset[CAMEL_DATASET_IDENTIFIER_EXTRA];
36+
return extraId.split(ID_DIVISION).filter(i => i);
37+
}
38+
return [];
39+
};
40+
3041
/**
3142
* get all highlight wrapping nodes nodes from a root node
3243
*/

src/util/event.emitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* modify from mitt
44
*/
55

6-
type EventHandler = (event?: any) => void;
6+
type EventHandler = (event?: unknown) => void;
77
type EventHandlerList = Array<EventHandler>;
88
type HandlersMap = {[propName: string]: EventHandlerList};
99

src/util/hook.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@ class Hook {
1111
this.name = name;
1212
}
1313

14-
tap(cb: Function) {
15-
this.ops.push(cb);
16-
return this;
14+
tap(cb: Function): Function {
15+
if (this.ops.indexOf(cb) < 0) {
16+
this.ops.push(cb);
17+
}
18+
return () => this.remove(cb);
19+
}
20+
21+
remove(cb: Function): void {
22+
const idx = this.ops.indexOf(cb);
23+
if (idx < 0) {
24+
return;
25+
}
26+
this.ops.splice(idx, 1);
1727
}
1828

1929
isEmpty(): boolean {

test/api.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,29 @@ describe('Highlighter API', function () {
412412
});
413413
});
414414

415+
describe('#getExtraIdByDom', () => {
416+
beforeEach(() => {
417+
highlighter.removeAll();
418+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
419+
});
420+
421+
it('should return the correct id', () => {
422+
const id = sources[0].id;
423+
const dom = highlighter.getDoms(id)[2];
424+
const ids = highlighter.getExtraIdByDom(dom);
425+
expect(ids.sort()).to.deep.equal([sources[0].id, sources[1].id].sort());
426+
});
427+
428+
it('should return empty array when there is no extra id', () => {
429+
const dom = highlighter.getDoms(sources[0].id)[0];
430+
expect(highlighter.getExtraIdByDom(dom)).to.deep.equal([]);
431+
});
432+
433+
it('should return empty array when the dom is not a wrapper', () => {
434+
expect(highlighter.getExtraIdByDom(document.querySelector('img'))).to.deep.equal([]);
435+
});
436+
});
437+
415438
afterEach(() => {
416439
cleanup();
417440
});

0 commit comments

Comments
 (0)