@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.
- package/.cursor/environment.json +6 -0
- package/.eslintrc.js +250 -0
- package/.prettierrc +9 -0
- package/.travis.yml +17 -0
- package/CHANGELOG.md +220 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/README.zh_CN.md +367 -0
- package/config/base.config.js +25 -0
- package/config/base.example.config.js +38 -0
- package/config/paths.js +22 -0
- package/config/server.config.js +17 -0
- package/config/webpack.config.dev.js +18 -0
- package/config/webpack.config.example.js +20 -0
- package/config/webpack.config.prod.js +28 -0
- package/dist/data/cache.d.ts +13 -0
- package/dist/index.d.ts +58 -0
- package/dist/model/range/dom.d.ts +6 -0
- package/dist/model/range/index.d.ts +20 -0
- package/dist/model/range/selection.d.ts +14 -0
- package/dist/model/source/dom.d.ts +23 -0
- package/dist/model/source/index.d.ts +18 -0
- package/dist/painter/dom.d.ts +22 -0
- package/dist/painter/index.d.ts +19 -0
- package/dist/painter/style.d.ts +1 -0
- package/dist/types/index.d.ts +102 -0
- package/dist/util/camel.d.ts +5 -0
- package/dist/util/const.d.ts +41 -0
- package/dist/util/deferred.d.ts +9 -0
- package/dist/util/dom.d.ts +32 -0
- package/dist/util/event.emitter.d.ts +13 -0
- package/dist/util/hook.d.ts +15 -0
- package/dist/util/interaction.d.ts +6 -0
- package/dist/util/is.mobile.d.ts +5 -0
- package/dist/util/tool.d.ts +4 -0
- package/dist/util/uuid.d.ts +4 -0
- package/dist/web-highlighter.min.js +3 -0
- package/dist/web-highlighter.min.js.map +1 -0
- package/docs/ADVANCE.md +113 -0
- package/docs/ADVANCE.zh_CN.md +111 -0
- package/docs/img/create-flow.jpg +0 -0
- package/docs/img/create-flow.zh_CN.jpg +0 -0
- package/docs/img/logo.png +0 -0
- package/docs/img/remove-flow.jpg +0 -0
- package/docs/img/remove-flow.zh_CN.jpg +0 -0
- package/docs/img/sample.gif +0 -0
- package/example/index.css +2 -0
- package/example/index.js +214 -0
- package/example/local.store.js +72 -0
- package/example/my.css +119 -0
- package/example/tpl.html +59 -0
- package/package.json +103 -0
- package/script/build.js +17 -0
- package/script/convet-md.js +25 -0
- package/script/dev.js +22 -0
- package/src/data/cache.ts +57 -0
- package/src/index.ts +285 -0
- package/src/model/range/dom.ts +94 -0
- package/src/model/range/index.ts +88 -0
- package/src/model/range/selection.ts +55 -0
- package/src/model/source/dom.ts +66 -0
- package/src/model/source/index.ts +54 -0
- package/src/painter/dom.ts +345 -0
- package/src/painter/index.ts +199 -0
- package/src/painter/style.ts +21 -0
- package/src/types/index.ts +118 -0
- package/src/util/camel.ts +6 -0
- package/src/util/const.ts +54 -0
- package/src/util/deferred.ts +37 -0
- package/src/util/dom.ts +155 -0
- package/src/util/event.emitter.ts +45 -0
- package/src/util/hook.ts +52 -0
- package/src/util/interaction.ts +20 -0
- package/src/util/is.mobile.ts +7 -0
- package/src/util/tool.ts +14 -0
- package/src/util/uuid.ts +10 -0
- package/test/api.spec.ts +555 -0
- package/test/event.spec.ts +284 -0
- package/test/fixtures/broken.json +32 -0
- package/test/fixtures/index.html +11 -0
- package/test/fixtures/source.json +47 -0
- package/test/hook.spec.ts +244 -0
- package/test/integrate.spec.ts +48 -0
- package/test/mobile.spec.ts +87 -0
- package/test/option.spec.ts +212 -0
- package/test/util.spec.ts +244 -0
- package/test-newlines.html +226 -0
- package/tsconfig.json +23 -0
package/example/index.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import './index.css';
|
|
2
|
+
import './my.css';
|
|
3
|
+
import Highlighter from '../src/index';
|
|
4
|
+
import LocalStore from './local.store';
|
|
5
|
+
|
|
6
|
+
const highlighter = new Highlighter({
|
|
7
|
+
wrapTag: 'i',
|
|
8
|
+
exceptSelectors: ['.my-remove-tip', 'pre', 'code']
|
|
9
|
+
});
|
|
10
|
+
highlighter.setOption({
|
|
11
|
+
style: {
|
|
12
|
+
className: 'yellow-highlight',
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
const store = new LocalStore();
|
|
16
|
+
const log = console.log.bind(console, '[highlighter]');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* create a delete tip
|
|
20
|
+
*/
|
|
21
|
+
const createTag = (top, left, id) => {
|
|
22
|
+
const $span = document.createElement('span');
|
|
23
|
+
$span.style.left = `${left - 20}px`;
|
|
24
|
+
$span.style.top = `${top - 25}px`;
|
|
25
|
+
$span.dataset['id'] = id;
|
|
26
|
+
$span.textContent = 'delete';
|
|
27
|
+
$span.classList.add('my-remove-tip');
|
|
28
|
+
document.body.appendChild($span);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* toggle auto highlighting & button status
|
|
33
|
+
*/
|
|
34
|
+
const switchAuto = auto => {
|
|
35
|
+
auto === 'on' ? highlighter.run() : highlighter.stop();
|
|
36
|
+
const $btn = document.getElementById('js-highlight');
|
|
37
|
+
if (auto === 'on') {
|
|
38
|
+
$btn.classList.add('disabled');
|
|
39
|
+
$btn.setAttribute('disabled', true);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
$btn.classList.remove('disabled');
|
|
43
|
+
$btn.removeAttribute('disabled');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const switchColor = color => highlighter.setOption({
|
|
48
|
+
style: {
|
|
49
|
+
className: color === 'yellow'
|
|
50
|
+
? 'yellow-highlight'
|
|
51
|
+
: 'blue-highlight',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function getPosition($node) {
|
|
56
|
+
let offset = {
|
|
57
|
+
top: 0,
|
|
58
|
+
left: 0
|
|
59
|
+
};
|
|
60
|
+
while ($node) {
|
|
61
|
+
offset.top += $node.offsetTop;
|
|
62
|
+
offset.left += $node.offsetLeft;
|
|
63
|
+
$node = $node.offsetParent;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return offset;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* highlighter event listener
|
|
71
|
+
*/
|
|
72
|
+
highlighter
|
|
73
|
+
.on(Highlighter.event.CLICK, ({id}) => {
|
|
74
|
+
log('click -', id);
|
|
75
|
+
})
|
|
76
|
+
.on(Highlighter.event.HOVER, ({id}) => {
|
|
77
|
+
log('hover -', id);
|
|
78
|
+
highlighter.addClass('highlight-wrap-hover', id);
|
|
79
|
+
})
|
|
80
|
+
.on(Highlighter.event.HOVER_OUT, ({id}) => {
|
|
81
|
+
log('hover out -', id);
|
|
82
|
+
highlighter.removeClass('highlight-wrap-hover', id);
|
|
83
|
+
})
|
|
84
|
+
.on(Highlighter.event.CREATE, ({sources}) => {
|
|
85
|
+
log('create -', sources);
|
|
86
|
+
sources.forEach(s => {
|
|
87
|
+
const position = getPosition(highlighter.getDoms(s.id)[0]);
|
|
88
|
+
createTag(position.top, position.left, s.id);
|
|
89
|
+
});
|
|
90
|
+
sources = sources.map(hs => ({hs}));
|
|
91
|
+
store.save(sources);
|
|
92
|
+
})
|
|
93
|
+
.on(Highlighter.event.REMOVE, ({ids}) => {
|
|
94
|
+
log('remove -', ids);
|
|
95
|
+
ids.forEach(id => store.remove(id));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* FIXME: avoid re-highlighting the existing selection
|
|
100
|
+
*/
|
|
101
|
+
// function getIds(selected) {
|
|
102
|
+
// if (!selected || !selected.$node || !selected.$node.parentNode) {
|
|
103
|
+
// return [];
|
|
104
|
+
// }
|
|
105
|
+
// return [
|
|
106
|
+
// highlighter.getIdByDom(selected.$node.parentNode),
|
|
107
|
+
// ...highlighter.getExtraIdByDom(selected.$node.parentNode)
|
|
108
|
+
// ].filter(i => i)
|
|
109
|
+
// }
|
|
110
|
+
// function getIntersection(arrA, arrB) {
|
|
111
|
+
// const record = {};
|
|
112
|
+
// const intersection = [];
|
|
113
|
+
// arrA.forEach(i => record[i] = true);
|
|
114
|
+
// arrB.forEach(i => record[i] && intersection.push(i) && (record[i] = false));
|
|
115
|
+
// return intersection;
|
|
116
|
+
// }
|
|
117
|
+
// highlighter.hooks.Render.SelectedNodes.tap((id, selectedNodes) => {
|
|
118
|
+
// selectedNodes = selectedNodes.filter(n => n.$node.textContent);
|
|
119
|
+
// if (selectedNodes.length === 0) {
|
|
120
|
+
// return [];
|
|
121
|
+
// }
|
|
122
|
+
|
|
123
|
+
// const candidates = selectedNodes.slice(1).reduce(
|
|
124
|
+
// (left, selected) => getIntersection(left, getIds(selected)),
|
|
125
|
+
// getIds(selectedNodes[0])
|
|
126
|
+
// );
|
|
127
|
+
// for (let i = 0; i < candidates.length; i++) {
|
|
128
|
+
// if (highlighter.getDoms(candidates[i]).length === selectedNodes.length) {
|
|
129
|
+
// return [];
|
|
130
|
+
// }
|
|
131
|
+
// }
|
|
132
|
+
|
|
133
|
+
// return selectedNodes;
|
|
134
|
+
// });
|
|
135
|
+
|
|
136
|
+
highlighter.hooks.Serialize.Restore.tap(
|
|
137
|
+
source => log('Serialize.Restore hook -', source)
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
highlighter.hooks.Serialize.RecordInfo.tap(() => {
|
|
141
|
+
const extraInfo = Math.random().toFixed(4);
|
|
142
|
+
log('Serialize.RecordInfo hook -', extraInfo)
|
|
143
|
+
return extraInfo;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* retrieve from local store
|
|
148
|
+
*/
|
|
149
|
+
const storeInfos = store.getAll();
|
|
150
|
+
storeInfos.forEach(
|
|
151
|
+
({hs}) => highlighter.fromStore(hs.startMeta, hs.endMeta, hs.text, hs.id, hs.extra)
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
let autoStatus;
|
|
155
|
+
document.querySelectorAll('[name="auto"]').forEach($n => {
|
|
156
|
+
if ($n.checked) {
|
|
157
|
+
autoStatus = $n.value;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
switchAuto(autoStatus);
|
|
161
|
+
|
|
162
|
+
let colorStatus = 'yellow';
|
|
163
|
+
document.addEventListener('click', e => {
|
|
164
|
+
const $ele = e.target;
|
|
165
|
+
|
|
166
|
+
// delete highlight
|
|
167
|
+
if ($ele.classList.contains('my-remove-tip')) {
|
|
168
|
+
const id = $ele.dataset.id;
|
|
169
|
+
log('*click remove-tip*', id);
|
|
170
|
+
highlighter.removeClass('highlight-wrap-hover', id);
|
|
171
|
+
highlighter.remove(id);
|
|
172
|
+
$ele.parentNode.removeChild($ele);
|
|
173
|
+
}
|
|
174
|
+
// toggle auto highlighting switch
|
|
175
|
+
else if ($ele.getAttribute('name') === 'auto') {
|
|
176
|
+
const val = $ele.value;
|
|
177
|
+
if (autoStatus !== val) {
|
|
178
|
+
switchAuto(val);
|
|
179
|
+
autoStatus = val;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// toggle highlighting color
|
|
183
|
+
else if ($ele.getAttribute('name') === 'color') {
|
|
184
|
+
const val = $ele.value;
|
|
185
|
+
if (colorStatus !== val) {
|
|
186
|
+
switchColor(val);
|
|
187
|
+
colorStatus = val;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// highlight range manually
|
|
191
|
+
else if ($ele.id === 'js-highlight') {
|
|
192
|
+
const selection = window.getSelection();
|
|
193
|
+
if (selection.isCollapsed) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
highlighter.fromRange(selection.getRangeAt(0));
|
|
197
|
+
window.getSelection().removeAllRanges();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
let hoveredTipId;
|
|
202
|
+
document.addEventListener('mouseover', e => {
|
|
203
|
+
const $ele = e.target;
|
|
204
|
+
// toggle highlight hover state
|
|
205
|
+
if ($ele.classList.contains('my-remove-tip') && hoveredTipId !== $ele.dataset.id) {
|
|
206
|
+
hoveredTipId = $ele.dataset.id;
|
|
207
|
+
highlighter.removeClass('highlight-wrap-hover');
|
|
208
|
+
highlighter.addClass('highlight-wrap-hover', hoveredTipId);
|
|
209
|
+
}
|
|
210
|
+
else if (!$ele.classList.contains('my-remove-tip') && !$ele.classList.contains('highlight-mengshou-wrap')) {
|
|
211
|
+
highlighter.removeClass('highlight-wrap-hover', hoveredTipId);
|
|
212
|
+
hoveredTipId = null;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class LocalStore {
|
|
2
|
+
constructor(id) {
|
|
3
|
+
this.key = id !== undefined ? `highlight-mengshou-${id}` : 'highlight-mengshou';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
storeToJson() {
|
|
7
|
+
const store = localStorage.getItem(this.key);
|
|
8
|
+
let sources;
|
|
9
|
+
try {
|
|
10
|
+
sources = JSON.parse(store) || [];
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
sources = [];
|
|
14
|
+
}
|
|
15
|
+
return sources;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
jsonToStore(stores) {
|
|
19
|
+
localStorage.setItem(this.key, JSON.stringify(stores));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
save(data) {
|
|
23
|
+
const stores = this.storeToJson();
|
|
24
|
+
const map = {};
|
|
25
|
+
stores.forEach((store, idx) => map[store.hs.id] = idx);
|
|
26
|
+
|
|
27
|
+
if (!Array.isArray(data)) {
|
|
28
|
+
data = [data];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
data.forEach(store => {
|
|
32
|
+
// update
|
|
33
|
+
if (map[store.hs.id] !== undefined) {
|
|
34
|
+
stores[map[store.hs.id]] = store;
|
|
35
|
+
}
|
|
36
|
+
// append
|
|
37
|
+
else {
|
|
38
|
+
stores.push(store);
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
this.jsonToStore(stores);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
forceSave(store) {
|
|
45
|
+
const stores = this.storeToJson();
|
|
46
|
+
stores.push(store);
|
|
47
|
+
this.jsonToStore(stores);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
remove(id) {
|
|
51
|
+
const stores = this.storeToJson();
|
|
52
|
+
let index = null;
|
|
53
|
+
for (let i = 0; i < stores.length; i++) {
|
|
54
|
+
if (stores[i].hs.id === id) {
|
|
55
|
+
index = i;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
stores.splice(index, 1);
|
|
60
|
+
this.jsonToStore(stores);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getAll() {
|
|
64
|
+
return this.storeToJson();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
removeAll() {
|
|
68
|
+
this.jsonToStore([]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default LocalStore;
|
package/example/my.css
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
h1:first-child {
|
|
2
|
+
color: #3c86c7;
|
|
3
|
+
padding-bottom: 0;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
h1 + blockquote {
|
|
7
|
+
box-shadow: inset 5px 0 #ddd;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
h1 + blockquote p {
|
|
11
|
+
margin-top: 10px;
|
|
12
|
+
color: #aaa;
|
|
13
|
+
margin-bottom: 40px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
main .highlight-wrap-hover {
|
|
17
|
+
background: #f7aaaa;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.my-remove-tip {
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
position: absolute;
|
|
23
|
+
border: 1px solid #fff;
|
|
24
|
+
border-radius: 3px;
|
|
25
|
+
height: 20px;
|
|
26
|
+
width: 40px;
|
|
27
|
+
color: #fff;
|
|
28
|
+
background: #444;
|
|
29
|
+
text-align: center;
|
|
30
|
+
font-size: 12px;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
line-height: 18px;
|
|
33
|
+
overflow: visible;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.my-remove-tip::after {
|
|
37
|
+
content: '';
|
|
38
|
+
position: absolute;
|
|
39
|
+
left: 16px;
|
|
40
|
+
bottom: -4px;
|
|
41
|
+
border-color: #444 transparent transparent;
|
|
42
|
+
border-width: 4px 4px 0;
|
|
43
|
+
border-style: solid;
|
|
44
|
+
height: 0;
|
|
45
|
+
width: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.op-panel {
|
|
49
|
+
box-sizing: border-box;
|
|
50
|
+
padding: 1px 5px 6px 8px;
|
|
51
|
+
position: fixed;
|
|
52
|
+
top: 50px;
|
|
53
|
+
left: 15px;
|
|
54
|
+
background: rgba(0, 0, 0, .1);
|
|
55
|
+
border-radius: 3px;
|
|
56
|
+
border: 1px dashed #aaa;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.op-panel a {
|
|
60
|
+
position: absolute;
|
|
61
|
+
right: 10px;
|
|
62
|
+
bottom: 12px;
|
|
63
|
+
height: 18px;
|
|
64
|
+
width: 18px;
|
|
65
|
+
opacity: .7;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.op-panel a:hover {
|
|
69
|
+
opacity: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.op-panel svg {
|
|
73
|
+
height: 18px;
|
|
74
|
+
width: 18px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.op-panel input[type="radio"] {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
width: auto;
|
|
80
|
+
position: static;
|
|
81
|
+
line-height: normal;
|
|
82
|
+
height: 15px;
|
|
83
|
+
vertical-align: middle;
|
|
84
|
+
margin-right: 5px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.op-panel .op-name {
|
|
88
|
+
margin-right: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.op-panel label {
|
|
92
|
+
font-size: 12px;
|
|
93
|
+
margin-right: 2px;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.op-panel .op-btn {
|
|
97
|
+
display: block;
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.op-panel .op-btn.disabled {
|
|
102
|
+
cursor: not-allowed;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@media screen and (max-width: 1150px) {
|
|
106
|
+
main {
|
|
107
|
+
padding: 0 15px;
|
|
108
|
+
overflow-x: hidden;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.op-panel {
|
|
112
|
+
right: 5vw;
|
|
113
|
+
bottom: 5vh;
|
|
114
|
+
left: auto;
|
|
115
|
+
top: auto;
|
|
116
|
+
background-color: rgba(0, 0, 0, .9);
|
|
117
|
+
color: #fff;
|
|
118
|
+
}
|
|
119
|
+
}
|
package/example/tpl.html
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!-- htmlcs-disable -->
|
|
2
|
+
<!DOCTYPE html>
|
|
3
|
+
<html lang="zh-Hans">
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
8
|
+
<meta name="keywords" content="highlighter,tool,lib,website,frontend">
|
|
9
|
+
<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.">
|
|
10
|
+
<style>
|
|
11
|
+
main {
|
|
12
|
+
max-width: 760px;
|
|
13
|
+
margin: 20px auto;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
img {
|
|
17
|
+
max-width: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.yellow-highlight {
|
|
21
|
+
background-color: #FF9;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.blue-highlight {
|
|
25
|
+
background-color: #9FF;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
<title>Web Highlighter: a mini tool for highlighting website text</title>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<main>
|
|
32
|
+
{{$markdown}}
|
|
33
|
+
</main>
|
|
34
|
+
<script>
|
|
35
|
+
void function () {
|
|
36
|
+
var img = document.getElementsByTagName('main')[0].getElementsByTagName('img')[0];
|
|
37
|
+
img.onerror = function() {
|
|
38
|
+
document.getElementById('js-title').setAttribute('style', 'display: block');
|
|
39
|
+
};
|
|
40
|
+
}();
|
|
41
|
+
</script>
|
|
42
|
+
<section class="op-panel">
|
|
43
|
+
<div>
|
|
44
|
+
<label class="op-name">auto-highlight:</label>
|
|
45
|
+
<label><input name="auto" type="radio" value="on" checked />enabled</label>
|
|
46
|
+
<label><input name="auto" type="radio" value="off" />disabled</label>
|
|
47
|
+
</div>
|
|
48
|
+
<div>
|
|
49
|
+
<label class="op-color">color:</label>
|
|
50
|
+
<label><input name="color" type="radio" value="yellow" checked />yellow</label>
|
|
51
|
+
<label><input name="color" type="radio" value="blue" />blue</label>
|
|
52
|
+
</div>
|
|
53
|
+
<button class="op-btn disabled" id="js-highlight" disabled >highlight manually</button>
|
|
54
|
+
<a href="https://github.com/alienzhou/web-highlighter" target="_blank">
|
|
55
|
+
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><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>
|
|
56
|
+
</a>
|
|
57
|
+
</section>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plannotator/web-highlighter",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "A no-runtime dependency lib for text highlighting & persistence on any website. Fork with selection fixes.",
|
|
5
|
+
"main": "dist/web-highlighter.min.js",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"browser": "dist/web-highlighter.min.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./src/index.ts",
|
|
12
|
+
"require": "./dist/web-highlighter.min.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"lint": "eslint \"src/**/*.ts\" --fix",
|
|
18
|
+
"test": "mocha -r ts-node/register -r tsconfig-paths/register test/**.spec.ts",
|
|
19
|
+
"coverage": "nyc -r lcov -e .ts -x \"test/**/*.ts\" npm run test",
|
|
20
|
+
"serve-example": "http-server example/static",
|
|
21
|
+
"serve": "http-server -p 8081 ./dist",
|
|
22
|
+
"watch": "webpack --config ./config/webpack.config.prod.js --watch",
|
|
23
|
+
"build-example": "target=example node script/build.js",
|
|
24
|
+
"static": "run-p watch serve",
|
|
25
|
+
"start": "node script/dev.js",
|
|
26
|
+
"build:types": "tscpaths -p tsconfig.json -s ./src -o ./dist",
|
|
27
|
+
"build:code": "target=dist node script/build.js",
|
|
28
|
+
"build": "run-s build:code build:types",
|
|
29
|
+
"prepublishOnly": "run-s lint build test"
|
|
30
|
+
},
|
|
31
|
+
"husky": {
|
|
32
|
+
"hooks": {
|
|
33
|
+
"pre-commit": "lint-staged && npm run test"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"lint-staged": {
|
|
37
|
+
"src/**/*.ts": [
|
|
38
|
+
"prettier --write",
|
|
39
|
+
"eslint --fix"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/backnotprop/web-highlighter",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/backnotprop/web-highlighter"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"text",
|
|
49
|
+
"highlight",
|
|
50
|
+
"web",
|
|
51
|
+
"persistence",
|
|
52
|
+
"lightweight",
|
|
53
|
+
"selection",
|
|
54
|
+
"range"
|
|
55
|
+
],
|
|
56
|
+
"author": "alienzhou <alienzhou16@163.com>",
|
|
57
|
+
"license": "MIT",
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/chai": "^4.2.11",
|
|
60
|
+
"@types/jsdom": "^16.2.3",
|
|
61
|
+
"@types/jsdom-global": "^3.0.2",
|
|
62
|
+
"@types/mocha": "^7.0.2",
|
|
63
|
+
"@types/node": "^14.18.63",
|
|
64
|
+
"@types/sinon": "^9.0.1",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
|
66
|
+
"@typescript-eslint/parser": "^4.13.0",
|
|
67
|
+
"better-opn": "^1.0.0",
|
|
68
|
+
"chai": "^4.2.0",
|
|
69
|
+
"chalk": "^2.4.2",
|
|
70
|
+
"clean-webpack-plugin": "^1.0.0",
|
|
71
|
+
"coveralls": "^3.1.0",
|
|
72
|
+
"css-loader": "^1.0.1",
|
|
73
|
+
"eslint": "^7.18.0",
|
|
74
|
+
"eslint-config-prettier": "^7.1.0",
|
|
75
|
+
"eslint-plugin-import": "^2.22.1",
|
|
76
|
+
"eslint-plugin-prettier": "^3.3.1",
|
|
77
|
+
"fs-extra": "^7.0.1",
|
|
78
|
+
"html-webpack-plugin": "^3.2.0",
|
|
79
|
+
"http-server": "^0.11.1",
|
|
80
|
+
"husky": "^4.3.8",
|
|
81
|
+
"jsdom": "^16.2.2",
|
|
82
|
+
"jsdom-global": "^3.0.2",
|
|
83
|
+
"lint-staged": "^10.5.3",
|
|
84
|
+
"mocha": "^7.1.2",
|
|
85
|
+
"npm-run-all": "^4.1.5",
|
|
86
|
+
"nyc": "^15.0.1",
|
|
87
|
+
"prettier": "^2.2.1",
|
|
88
|
+
"showdown": "^1.9.0",
|
|
89
|
+
"sinon": "^9.0.2",
|
|
90
|
+
"style-loader": "^0.23.1",
|
|
91
|
+
"text-replace-html-webpack-plugin": "^1.0.3",
|
|
92
|
+
"ts-loader": "^5.3.0",
|
|
93
|
+
"ts-node": "^8.10.1",
|
|
94
|
+
"tsconfig-paths": "^3.9.0",
|
|
95
|
+
"tsconfig-paths-webpack-plugin": "^3.5.1",
|
|
96
|
+
"tscpaths": "0.0.9",
|
|
97
|
+
"typescript": "^4.1.3",
|
|
98
|
+
"webpack": "^4.25.1",
|
|
99
|
+
"webpack-cli": "^3.1.2",
|
|
100
|
+
"webpack-dev-server": ">=3.1.11",
|
|
101
|
+
"webpack-merge": "^4.1.4"
|
|
102
|
+
}
|
|
103
|
+
}
|
package/script/build.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const convert = require('./convet-md');
|
|
2
|
+
if (process.env.target === 'example') {
|
|
3
|
+
convert();
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const webpack = require('webpack');
|
|
7
|
+
const configPath = process.env.target === 'example' ? '../config/webpack.config.example.js' : '../config/webpack.config.prod.js';
|
|
8
|
+
const config = require(configPath);
|
|
9
|
+
webpack(config, (err, stats) => {
|
|
10
|
+
if (err || stats.hasErrors() || stats.hasWarnings()) {
|
|
11
|
+
console.log(err);
|
|
12
|
+
console.log(stats.toJson({colors: true}).errors)
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
console.log(stats.toString({colors: true}));
|
|
16
|
+
process.exit(0);
|
|
17
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const showdown = require('showdown');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const paths = require('../config/paths');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const g = chalk.green;
|
|
7
|
+
|
|
8
|
+
const log = (...args) => console.log.apply(console, [g('[convert]'), ...args.map(s => g(s))]);
|
|
9
|
+
|
|
10
|
+
module.exports = function () {
|
|
11
|
+
const mdPath = path.resolve(paths.basePath, 'README.md');
|
|
12
|
+
|
|
13
|
+
log(mdPath, '-', 'converting...');
|
|
14
|
+
|
|
15
|
+
const md = fs.readFileSync(mdPath, 'utf-8');
|
|
16
|
+
showdown.setFlavor('github');
|
|
17
|
+
const converter = new showdown.Converter();
|
|
18
|
+
const html = converter.makeHtml(md);
|
|
19
|
+
const tpl = fs.readFileSync(paths.exampleTplPath, 'utf-8');
|
|
20
|
+
fs.outputFileSync(paths.exampleMdPath, tpl.replace(/{{\$markdown}}/, html), 'utf-8');
|
|
21
|
+
|
|
22
|
+
log(mdPath, '-', 'convert md to html success!');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.argv[1] === __filename && module.exports();
|
package/script/dev.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const opn = require('better-opn');
|
|
2
|
+
const webpack = require('webpack');
|
|
3
|
+
const WebpackDevServer = require('webpack-dev-server');
|
|
4
|
+
|
|
5
|
+
const convert = require('./convet-md');
|
|
6
|
+
convert();
|
|
7
|
+
|
|
8
|
+
const config = require('../config/webpack.config.dev.js');
|
|
9
|
+
const serverConfig = require('../config/server.config.js');
|
|
10
|
+
|
|
11
|
+
WebpackDevServer.addDevServerEntrypoints(config, serverConfig);
|
|
12
|
+
const compiler = webpack(config);
|
|
13
|
+
const {port, host} = serverConfig;
|
|
14
|
+
|
|
15
|
+
const devServer = new WebpackDevServer(compiler, serverConfig);
|
|
16
|
+
devServer.listen(port, host, err => {
|
|
17
|
+
if (err) {
|
|
18
|
+
return console.log(err);
|
|
19
|
+
}
|
|
20
|
+
console.log('Starting the development server...\n');
|
|
21
|
+
opn(`http://${host}:${port}`);
|
|
22
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import EventEmitter from '@src/util/event.emitter';
|
|
2
|
+
import type HighlightSource from '@src/model/source';
|
|
3
|
+
import { ERROR } from '@src/types';
|
|
4
|
+
|
|
5
|
+
class Cache extends EventEmitter {
|
|
6
|
+
private _data: Map<string, HighlightSource> = new Map();
|
|
7
|
+
|
|
8
|
+
get data() {
|
|
9
|
+
return this.getAll();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
set data(map) {
|
|
13
|
+
throw ERROR.CACHE_SET_ERROR;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
save(source: HighlightSource | HighlightSource[]): void {
|
|
17
|
+
if (!Array.isArray(source)) {
|
|
18
|
+
this._data.set(source.id, source);
|
|
19
|
+
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
source.forEach(s => this._data.set(s.id, s));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get(id: string): HighlightSource {
|
|
27
|
+
return this._data.get(id);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
remove(id: string): void {
|
|
31
|
+
this._data.delete(id);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getAll(): HighlightSource[] {
|
|
35
|
+
const list: HighlightSource[] = [];
|
|
36
|
+
|
|
37
|
+
for (const pair of this._data) {
|
|
38
|
+
list.push(pair[1]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return list;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
removeAll(): string[] {
|
|
45
|
+
const ids: string[] = [];
|
|
46
|
+
|
|
47
|
+
for (const pair of this._data) {
|
|
48
|
+
ids.push(pair[0]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this._data = new Map();
|
|
52
|
+
|
|
53
|
+
return ids;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default Cache;
|