├── .cjsescache
├── .editorconfig
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── LICENSE
├── README.md
├── demo
├── demo-large-content.html
├── demo-no-script.html
└── demo.html
├── dist
└── linkify-plus-plus.user.js
├── eslint.config.mjs
├── package-lock.json
├── package.json
├── rollup.config.mjs
└── src
├── extension
├── background.js
├── content.js
├── dialog.js
└── options.js
├── lib
├── extension-pref.js
├── main.mjs
├── pref-body.js
├── pref-default.js
└── triggers
│ ├── cache.mjs
│ ├── click.mjs
│ ├── hover.mjs
│ ├── index.mjs
│ ├── load.mjs
│ ├── mutation.mjs
│ └── util.mjs
├── static
├── _locales
│ ├── en
│ │ └── messages.json
│ └── nl
│ │ └── messages.json
├── css
│ ├── dialog.css
│ └── options.css
├── dialog.html
├── icon-light.svg
├── icon.svg
├── manifest.json
└── options.html
└── userscript
└── index.js
/.cjsescache:
--------------------------------------------------------------------------------
1 | [
2 | "node_modules/event-lite/event-lite.mjs",
3 | "node_modules/webext-pref/lib/promisify.js",
4 | "node_modules/webextension-polyfill/dist/browser-polyfill.js",
5 | "src/lib/extension-pref.js",
6 | "src/lib/pref-body.js",
7 | "src/lib/pref-default.js",
8 | "src/lib/triggers/click.mjs",
9 | "src/lib/triggers/hover.mjs",
10 | "src/lib/triggers/index.mjs",
11 | "src/lib/triggers/load.mjs",
12 | "src/lib/triggers/mutation.mjs"
13 | ]
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_size = 2
3 | indent_style = space
4 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | test:
6 | runs-on: ubuntu-latest
7 |
8 | steps:
9 | - uses: actions/checkout@v3
10 | - uses: actions/setup-node@v3
11 | with:
12 | node-version: '18'
13 | - run: npm ci
14 | - run: npm run build
15 | - run: npm test
16 | # - uses: codecov/codecov-action@v3
17 | # with:
18 | # fail_ci_if_error: true
19 | # verbose: true
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | web-ext-artifacts
4 | .eslintcache
5 | dist-extension
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011, Anthony Lieuallen
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * Neither the name of Anthony Lieuallen nor the names of its contributors may
13 | be used to endorse or promote products derived from this software without
14 | specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Linkify Plus Plus
2 | =================
3 |
4 | [](https://travis-ci.com/eight04/linkify-plus-plus)
5 |
6 | A userscript/extension that can linkify almost everything. Based on Linkify Plus.
7 |
8 | See also [linkify-plus-plus-core](https://github.com/eight04/linkify-plus-plus-core).
9 |
10 | Features
11 | --------
12 |
13 | * Detect text url and convert them into links.
14 | * Support dynamic content.
15 | * Support unicode characters.
16 | * Support custom rules.
17 | * Custom whitelist, blacklist.
18 | * Embed images.
19 | * Multiple methods to trigger the conversion.
20 |
21 | Installation
22 | ------------
23 |
24 | ### Userscript
25 |
26 | [Install from Greasy Fork](https://greasyfork.org/scripts/4255-linkify-plus-plus).
27 |
28 | ### Firefox extension
29 |
30 | [Install from Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/linkify-plus-plus/).
31 |
32 | ### Chrome extension
33 |
34 | This extension can be installed on Chrome. However, it is not hosted on Chrome Webstore. You have to download the source code and [load the extension as an unpacked extension](https://developer.chrome.com/extensions/getstarted#manifest).
35 |
36 | 1. Go to [release page](https://github.com/eight04/linkify-plus-plus/releases), download and extract the ZIP file.
37 | 2. Navigate to `chrome://extensions/`.
38 | 3. Enable "Developer mode".
39 | 4. Click "LOAD UNPACKED" button and select the folder containing `manifest.json` that is previously extracted.
40 |
41 | Testcase
42 | --------
43 |
44 | *
57 | Mozilla(http://www.mozilla.org/) started as a project of next generation www-browser of Netscape(http://www.netscape.com/). See http://www.mozilla.org/src-faq.html#1. Mozilla were planned to be released as "Netscape Communicator 5.0", but the new layout engine "NGLayout" ttp://www.mozilla.org/newlayout/gecko.html prevented it. Netscape 6 ttp://ftp.netscape.com/pub/netscape6/ was released after 2 years from the deciding.And now, the Firefox(h**p://www.mozilla.com/firefox/) web browser is released from the Mozilla Corporation(h++p://www.mozilla.com/).
58 |
60 | Now, TextLink can parse splitted text nodes as joined texts. Mozilla(http://www.mozilla.org/) started as a project of next generation www-browser of Netscape(http://www.netscape.com/). See http://www.mozilla.org/src-faq.html#1. Mozilla were planned to be released as "Netscape Communicator 5.0", but the new layout engine "NGLayout" ttp://www.mozilla.org/newlayout/gecko.html prevented it. Netscape 6 ttp://ftp.netscape.com/pub/netscape6/ was released after 2 years from the deciding.
61 |
63 | There are relative links. Firefox www.mozilla.org/products/firefox/ is a new type www-browser and it very extendable. You can find many extensions from update.mozilla.org. I also release some extensions. TBE ./tabextensions/index.html.en and PopupALT _popupalt.html.en are parts of them. (Text Link ignores relative pathes which includes only a filename like "_popupalt.html.en", because strings with the pattern usually indicates only filenames, not relative pathes.) See the parent page ../xul/xul.html to find other extensions in this website.
64 |
66 | There are links including multi-byte characters. The URI of this website is "http://piro.sakura.ne.jp/", but you can use the secondary URI, "ttp://www98.sakura.ne.jp/ ̄piro/" too.
67 |
68 | Also I don't want these: http://ptt.cc/1234WITHUNICODE, http://example.org/, http://example.org/,http://example.org, 127.0.0.1, http://127.0.0.1, telnet://ptt.cc, AFTERUNICODEhttp://example.org, I'm so happy www, www.sohappywwwhaha, www....
69 |
76 | ABOUT
77 | about:blank
78 | about:config
79 |
80 | MAILTO
81 | mailto:example@example.com
82 |
83 | HTTP
84 | http://www.example.com
85 | http://www.example.com.
86 | http://www.example.com/test/
87 | http://www.example.com/test/.
88 | http://www.example.com/test/index.html
89 | http://www.example.com/test/index.html.
90 | http://user@www.example.com
91 | http://user@www.example.com.
92 | http://user@www.example.com/test/
93 | http://user@www.example.com/test/.
94 | http://user@www.example.com/test/index.html
95 | http://user@www.example.com/test/index.html.
96 | http://user:password@www.example.com
97 | http://user:password@www.example.com.
98 | http://user:password@www.example.com/test/
99 | http://user:password@www.example.com/test/.
100 | http://user:password@www.example.com/test/index.html
101 | http://user:password@www.example.com/test/index.html.
102 | http://192.168.0.1
103 | http://192.168.0.1.
104 | http://192.168.0.1/test/
105 | http://192.168.0.1/test/.
106 | http://192.168.0.1/test/index.html
107 | http://192.168.0.1/test/index.html.
108 | http://user@192.168.0.1
109 | http://user@192.168.0.1.
110 | http://user@192.168.0.1/test/
111 | http://user@192.168.0.1/test/.
112 | http://user@192.168.0.1/test/index.html
113 | http://user@192.168.0.1/test/index.html.
114 | http://user:password@192.168.0.1
115 | http://user:password@192.168.0.1.
116 | http://user:password@192.168.0.1/test/
117 | http://user:password@192.168.0.1/test/.
118 | http://user:password@192.168.0.1/test/index.html
119 | http://user:password@192.168.0.1/test/index.html.
120 |
121 | HTTPS
122 | https://www.example.com
123 | https://www.example.com.
124 | https://www.example.com/test/
125 | https://www.example.com/test/.
126 | https://www.example.com/test/index.html
127 | https://www.example.com/test/index.html.
128 | https://user@www.example.com
129 | https://user@www.example.com.
130 | https://user@www.example.com/test/
131 | https://user@www.example.com/test/.
132 | https://user@www.example.com/test/index.html
133 | https://user@www.example.com/test/index.html.
134 | https://user:password@www.example.com
135 | https://user:password@www.example.com.
136 | https://user:password@www.example.com/test/
137 | https://user:password@www.example.com/test/.
138 | https://user:password@www.example.com/test/index.html
139 | https://user:password@www.example.com/test/index.html.
140 | https://192.168.0.1
141 | https://192.168.0.1.
142 | https://192.168.0.1/test/
143 | https://192.168.0.1/test/.
144 | https://192.168.0.1/test/index.html
145 | https://192.168.0.1/test/index.html.
146 | https://user@192.168.0.1
147 | https://user@192.168.0.1.
148 | https://user@192.168.0.1/test/
149 | https://user@192.168.0.1/test/.
150 | https://user@192.168.0.1/test/index.html
151 | https://user@192.168.0.1/test/index.html.
152 | https://user:password@192.168.0.1
153 | https://user:password@192.168.0.1.
154 | https://user:password@192.168.0.1/test/
155 | https://user:password@192.168.0.1/test/.
156 | https://user:password@192.168.0.1/test/index.html
157 | https://user:password@192.168.0.1/test/index.html.
158 |
159 | FTP
160 | ftp://www.example.com
161 | ftp://www.example.com.
162 | ftp://www.example.com/test/
163 | ftp://www.example.com/test/.
164 | ftp://www.example.com/test/index.html
165 | ftp://www.example.com/test/index.html.
166 | ftp://user@www.example.com
167 | ftp://user@www.example.com.
168 | ftp://user@www.example.com/test/
169 | ftp://user@www.example.com/test/.
170 | ftp://user@www.example.com/test/index.html
171 | ftp://user@www.example.com/test/index.html.
172 | ftp://user:password@www.example.com
173 | ftp://user:password@www.example.com.
174 | ftp://user:password@www.example.com/test/
175 | ftp://user:password@www.example.com/test/.
176 | ftp://user:password@www.example.com/test/index.html
177 | ftp://user:password@www.example.com/test/index.html.
178 | ftp://192.168.0.1
179 | ftp://192.168.0.1.
180 | ftp://192.168.0.1/test/
181 | ftp://192.168.0.1/test/.
182 | ftp://192.168.0.1/test/index.html
183 | ftp://192.168.0.1/test/index.html.
184 | ftp://user@192.168.0.1
185 | ftp://user@192.168.0.1.
186 | ftp://user@192.168.0.1/test/
187 | ftp://user@192.168.0.1/test/.
188 | ftp://user@192.168.0.1/test/index.html
189 | ftp://user@192.168.0.1/test/index.html.
190 | ftp://user:password@192.168.0.1
191 | ftp://user:password@192.168.0.1.
192 | ftp://user:password@192.168.0.1/test/
193 | ftp://user:password@192.168.0.1/test/.
194 | ftp://user:password@192.168.0.1/test/index.html
195 | ftp://user:password@192.168.0.1/test/index.html.
196 |
197 | NNTP
198 | nntp://www.example.com
199 | nntp://www.example.com.
200 | nntp://www.example.com/test/
201 | nntp://www.example.com/test/.
202 | nntp://www.example.com/test/index.html
203 | nntp://www.example.com/test/index.html.
204 | nntp://user@www.example.com
205 | nntp://user@www.example.com.
206 | nntp://user@www.example.com/test/
207 | nntp://user@www.example.com/test/.
208 | nntp://user@www.example.com/test/index.html
209 | nntp://user@www.example.com/test/index.html.
210 | nntp://user:password@www.example.com
211 | nntp://user:password@www.example.com.
212 | nntp://user:password@www.example.com/test/
213 | nntp://user:password@www.example.com/test/.
214 | nntp://user:password@www.example.com/test/index.html
215 | nntp://user:password@www.example.com/test/index.html.
216 | nntp://192.168.0.1
217 | nntp://192.168.0.1.
218 | nntp://192.168.0.1/test/
219 | nntp://192.168.0.1/test/.
220 | nntp://192.168.0.1/test/index.html
221 | nntp://192.168.0.1/test/index.html.
222 | nntp://user@192.168.0.1
223 | nntp://user@192.168.0.1.
224 | nntp://user@192.168.0.1/test/
225 | nntp://user@192.168.0.1/test/.
226 | nntp://user@192.168.0.1/test/index.html
227 | nntp://user@192.168.0.1/test/index.html.
228 | nntp://user:password@192.168.0.1
229 | nntp://user:password@192.168.0.1.
230 | nntp://user:password@192.168.0.1/test/
231 | nntp://user:password@192.168.0.1/test/.
232 | nntp://user:password@192.168.0.1/test/index.html
233 | nntp://user:password@192.168.0.1/test/index.html.
234 |
235 | NEWS
236 | news://www.example.com
237 | news://www.example.com.
238 | news://www.example.com/test/
239 | news://www.example.com/test/.
240 | news://www.example.com/test/index.html
241 | news://www.example.com/test/index.html.
242 | news://user@www.example.com
243 | news://user@www.example.com.
244 | news://user@www.example.com/test/
245 | news://user@www.example.com/test/.
246 | news://user@www.example.com/test/index.html
247 | news://user@www.example.com/test/index.html.
248 | news://user:password@www.example.com
249 | news://user:password@www.example.com.
250 | news://user:password@www.example.com/test/
251 | news://user:password@www.example.com/test/.
252 | news://user:password@www.example.com/test/index.html
253 | news://user:password@www.example.com/test/index.html.
254 | news://192.168.0.1
255 | news://192.168.0.1.
256 | news://192.168.0.1/test/
257 | news://192.168.0.1/test/.
258 | news://192.168.0.1/test/index.html
259 | news://192.168.0.1/test/index.html.
260 | news://user@192.168.0.1
261 | news://user@192.168.0.1.
262 | news://user@192.168.0.1/test/
263 | news://user@192.168.0.1/test/.
264 | news://user@192.168.0.1/test/index.html
265 | news://user@192.168.0.1/test/index.html.
266 | news://user:password@192.168.0.1
267 | news://user:password@192.168.0.1.
268 | news://user:password@192.168.0.1/test/
269 | news://user:password@192.168.0.1/test/.
270 | news://user:password@192.168.0.1/test/index.html
271 | news://user:password@192.168.0.1/test/index.html.
272 |
273 | TELNET
274 | telnet://www.example.com
275 | telnet://www.example.com.
276 | telnet://www.example.com/test/
277 | telnet://www.example.com/test/.
278 | telnet://www.example.com/test/index.html
279 | telnet://www.example.com/test/index.html.
280 | telnet://user@www.example.com
281 | telnet://user@www.example.com.
282 | telnet://user@www.example.com/test/
283 | telnet://user@www.example.com/test/.
284 | telnet://user@www.example.com/test/index.html
285 | telnet://user@www.example.com/test/index.html.
286 | telnet://user:password@www.example.com
287 | telnet://user:password@www.example.com.
288 | telnet://user:password@www.example.com/test/
289 | telnet://user:password@www.example.com/test/.
290 | telnet://user:password@www.example.com/test/index.html
291 | telnet://user:password@www.example.com/test/index.html.
292 | telnet://192.168.0.1
293 | telnet://192.168.0.1.
294 | telnet://192.168.0.1/test/
295 | telnet://192.168.0.1/test/.
296 | telnet://192.168.0.1/test/index.html
297 | telnet://192.168.0.1/test/index.html.
298 | telnet://user@192.168.0.1
299 | telnet://user@192.168.0.1.
300 | telnet://user@192.168.0.1/test/
301 | telnet://user@192.168.0.1/test/.
302 | telnet://user@192.168.0.1/test/index.html
303 | telnet://user@192.168.0.1/test/index.html.
304 | telnet://user:password@192.168.0.1
305 | telnet://user:password@192.168.0.1.
306 | telnet://user:password@192.168.0.1/test/
307 | telnet://user:password@192.168.0.1/test/.
308 | telnet://user:password@192.168.0.1/test/index.html
309 | telnet://user:password@192.168.0.1/test/index.html.
310 |
311 | IRC
312 | irc://www.example.com
313 | irc://www.example.com.
314 | irc://www.example.com/test/
315 | irc://www.example.com/test/.
316 | irc://www.example.com/test/index.html
317 | irc://www.example.com/test/index.html.
318 | irc://user@www.example.com
319 | irc://user@www.example.com.
320 | irc://user@www.example.com/test/
321 | irc://user@www.example.com/test/.
322 | irc://user@www.example.com/test/index.html
323 | irc://user@www.example.com/test/index.html.
324 | irc://user:password@www.example.com
325 | irc://user:password@www.example.com.
326 | irc://user:password@www.example.com/test/
327 | irc://user:password@www.example.com/test/.
328 | irc://user:password@www.example.com/test/index.html
329 | irc://user:password@www.example.com/test/index.html.
330 | irc://192.168.0.1
331 | irc://192.168.0.1.
332 | irc://192.168.0.1/test/
333 | irc://192.168.0.1/test/.
334 | irc://192.168.0.1/test/index.html
335 | irc://192.168.0.1/test/index.html.
336 | irc://user@192.168.0.1
337 | irc://user@192.168.0.1.
338 | irc://user@192.168.0.1/test/
339 | irc://user@192.168.0.1/test/.
340 | irc://user@192.168.0.1/test/index.html
341 | irc://user@192.168.0.1/test/index.html.
342 | irc://user:password@192.168.0.1
343 | irc://user:password@192.168.0.1.
344 | irc://user:password@192.168.0.1/test/
345 | irc://user:password@192.168.0.1/test/.
346 | irc://user:password@192.168.0.1/test/index.html
347 | irc://user:password@192.168.0.1/test/index.html.
348 |
349 | CUSTOM
350 | hxxp://www.example.com
351 | hxxp://www.example.com.
352 | hxxp://www.example.com/test/
353 | hxxp://www.example.com/test/.
354 | hxxp://www.example.com/test/index.html
355 | hxxp://www.example.com/test/index.html.
356 | hxxp://user@www.example.com
357 | hxxp://user@www.example.com.
358 | hxxp://user@www.example.com/test/
359 | hxxp://user@www.example.com/test/.
360 | hxxp://user@www.example.com/test/index.html
361 | hxxp://user@www.example.com/test/index.html.
362 | hxxp://user:password@www.example.com
363 | hxxp://user:password@www.example.com.
364 | hxxp://user:password@www.example.com/test/
365 | hxxp://user:password@www.example.com/test/.
366 | hxxp://user:password@www.example.com/test/index.html
367 | hxxp://user:password@www.example.com/test/index.html.
368 | hxxp://192.168.0.1
369 | hxxp://192.168.0.1.
370 | hxxp://192.168.0.1/test/
371 | hxxp://192.168.0.1/test/.
372 | hxxp://192.168.0.1/test/index.html
373 | hxxp://192.168.0.1/test/index.html.
374 | hxxp://user@192.168.0.1
375 | hxxp://user@192.168.0.1.
376 | hxxp://user@192.168.0.1/test/
377 | hxxp://user@192.168.0.1/test/.
378 | hxxp://user@192.168.0.1/test/index.html
379 | hxxp://user@192.168.0.1/test/index.html.
380 | hxxp://user:password@192.168.0.1
381 | hxxp://user:password@192.168.0.1.
382 | hxxp://user:password@192.168.0.1/test/
383 | hxxp://user:password@192.168.0.1/test/.
384 | hxxp://user:password@192.168.0.1/test/index.html
385 | hxxp://user:password@192.168.0.1/test/index.html.
386 |
387 | CUSTOM
388 | h**p://www.example.com
389 | h**p://www.example.com.
390 | h**p://www.example.com/test/
391 | h**p://www.example.com/test/.
392 | h**p://www.example.com/test/index.html
393 | h**p://www.example.com/test/index.html.
394 | h**p://user@www.example.com
395 | h**p://user@www.example.com.
396 | h**p://user@www.example.com/test/
397 | h**p://user@www.example.com/test/.
398 | h**p://user@www.example.com/test/index.html
399 | h**p://user@www.example.com/test/index.html.
400 | h**p://user:password@www.example.com
401 | h**p://user:password@www.example.com.
402 | h**p://user:password@www.example.com/test/
403 | h**p://user:password@www.example.com/test/.
404 | h**p://user:password@www.example.com/test/index.html
405 | h**p://user:password@www.example.com/test/index.html.
406 | h**p://192.168.0.1
407 | h**p://192.168.0.1.
408 | h**p://192.168.0.1/test/
409 | h**p://192.168.0.1/test/.
410 | h**p://192.168.0.1/test/index.html
411 | h**p://192.168.0.1/test/index.html.
412 | h**p://user@192.168.0.1
413 | h**p://user@192.168.0.1.
414 | h**p://user@192.168.0.1/test/
415 | h**p://user@192.168.0.1/test/.
416 | h**p://user@192.168.0.1/test/index.html
417 | h**p://user@192.168.0.1/test/index.html.
418 | h**p://user:password@192.168.0.1
419 | h**p://user:password@192.168.0.1.
420 | h**p://user:password@192.168.0.1/test/
421 | h**p://user:password@192.168.0.1/test/.
422 | h**p://user:password@192.168.0.1/test/index.html
423 | h**p://user:password@192.168.0.1/test/index.html.
424 |
425 | WWW (no protocol)
426 | www.example.com
427 | www.example.com.
428 | www.example.com/test/
429 | www.example.com/test/.
430 | www.example.com/test/index.html
431 | www.example.com/test/index.html.
432 | user@www.example.com (ambiguous, but recognized subdomain. not an e-mail address)
433 | user@www.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
434 | user@www.example.com/test/
435 | user@www.example.com/test/.
436 | user@www.example.com/test/index.html
437 | user@www.example.com/test/index.html.
438 | user:password@www.example.com
439 | user:password@www.example.com.
440 | user:password@www.example.com/test/
441 | user:password@www.example.com/test/.
442 | user:password@www.example.com/test/index.html
443 | user:password@www.example.com/test/index.html.
444 |
445 | FTP (no protocol)
446 | ftp.example.com
447 | ftp.example.com.
448 | ftp.example.com/test/
449 | ftp.example.com/test/.
450 | ftp.example.com/test/index.html
451 | ftp.example.com/test/index.html.
452 | user@ftp.example.com (ambiguous, but recognized subdomain. not an e-mail address)
453 | user@ftp.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
454 | user@ftp.example.com/test/
455 | user@ftp.example.com/test/.
456 | user@ftp.example.com/test/index.html
457 | user@ftp.example.com/test/index.html.
458 | user:password@ftp.example.com
459 | user:password@ftp.example.com.
460 | user:password@ftp.example.com/test/
461 | user:password@ftp.example.com/test/.
462 | user:password@ftp.example.com/test/index.html
463 | user:password@ftp.example.com/test/index.html.
464 |
465 | IRC (no protocol)
466 | irc.example.com
467 | irc.example.com.
468 | irc.example.com/test/
469 | irc.example.com/test/.
470 | irc.example.com/test/index.html
471 | irc.example.com/test/index.html.
472 | user@irc.example.com (ambiguous, but recognized subdomain. not an e-mail address)
473 | user@irc.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
474 | user@irc.example.com/test/
475 | user@irc.example.com/test/.
476 | user@irc.example.com/test/index.html
477 | user@irc.example.com/test/index.html.
478 | user:password@irc.example.com
479 | user:password@irc.example.com.
480 | user:password@irc.example.com/test/
481 | user:password@irc.example.com/test/.
482 | user:password@irc.example.com/test/index.html
483 | user:password@irc.example.com/test/index.html.
484 | #test-name@irc.example.com
485 | #test-name@irc.example.com.
486 | irc.example.com#test-name
487 | irc.example.com#test-name.
488 |
489 | IP (no protocol)
490 | 192.168.0.1 (not linkified; pattern too common)
491 | 192.168.0.1. (not linkified; pattern too common)
492 | 192.168.0.1/test/
493 | 192.168.0.1/test/.
494 | 192.168.0.1/test/index.html
495 | 192.168.0.1/test/index.html.
496 | user@192.168.0.1 (ambiguous; should be recognized as e-mail)
497 | user@192.168.0.1. (ambiguous; should be recognized as e-mail)
498 | user@192.168.0.1/test/
499 | user@192.168.0.1/test/.
500 | user@192.168.0.1/test/index.html
501 | user@192.168.0.1/test/index.html.
502 | user:password@192.168.0.1
503 | user:password@192.168.0.1.
504 | user:password@192.168.0.1/test/
505 | user:password@192.168.0.1/test/.
506 | user:password@192.168.0.1/test/index.html
507 | user:password@192.168.0.1/test/index.html.
508 |
509 | OTHER (no protocol)
510 | subdomain.example.com (not linkified; pattern too common)
511 | subdomain.example.com. (not linkified; pattern too common)
512 | subdomain.example.com/test/
513 | subdomain.example.com/test/.
514 | subdomain.example.com/test/index.html
515 | subdomain.example.com/test/index.html.
516 | user@subdomain.example.com (ambiguous; should be recognized as e-mail)
517 | user@subdomain.example.com. (ambiguous; should be recognized as e-mail)
518 | user@subdomain.example.com/test/
519 | user@subdomain.example.com/test/.
520 | user@subdomain.example.com/test/index.html
521 | user@subdomain.example.com/test/index.html.
522 | user:password@subdomain.example.com
523 | user:password@subdomain.example.com.
524 | user:password@subdomain.example.com/test/
525 | user:password@subdomain.example.com/test/.
526 | user:password@subdomain.example.com/test/index.html
527 | user:password@subdomain.example.com/test/index.html.
528 |
529 | EMAIL
530 | test@example.com
531 | test@example.com.
532 | test.test@test.example.com
533 | test.test@test.example.com.
534 | test@192.168.0.1
535 | test@192.168.0.1.
536 | test.test@192.168.0.1
537 | test.test@192.168.0.1.
538 |
539 | IMAGE
540 | http://www.example.com/image.jpg
541 | http://www.example.com/image.jpeg
542 | http://www.example.com/image.png
543 | http://www.example.com/image.gif
544 | http://www.example.com/image.bmp
545 | http://www.example.com/image.jpg.test (not an image)
546 | http://www.example.com/image.jpeg.test (not an image)
547 | http://www.example.com/image.png.test (not an image)
548 | http://www.example.com/image.gif.test (not an image)
549 | http://www.example.com/image.bmp.test (not an image)
550 | http://www.example.com/image.jpg?test
551 | http://www.example.com/image.jpeg?test
552 | http://www.example.com/image.png?test
553 | http://www.example.com/image.gif?test
554 | http://www.example.com/image.bmp?test
555 | http://www.example.com/image.test?jpg (not an image)
556 | http://www.example.com/image.test?jpeg (not an image)
557 | http://www.example.com/image.test?png (not an image)
558 | http://www.example.com/image.test?gif (not an image)
559 | http://www.example.com/image.test?bmp (not an image)
560 | http://www.example.com/image.jpg#test
561 | http://www.example.com/image.jpeg#test
562 | http://www.example.com/image.png#test
563 | http://www.example.com/image.gif#test
564 | http://www.example.com/image.bmp#test
565 | http://www.example.com/image.test#jpg (not an image)
566 | http://www.example.com/image.test#jpeg (not an image)
567 | http://www.example.com/image.test#png (not an image)
568 | http://www.example.com/image.test#gif (not an image)
569 | http://www.example.com/image.test#bmp (not an image)
570 | https://greasyfork.org/assets/blacklogo96-0596aff6108f83c3073764496d7768ec.png
571 | http://i.imgur.com/25zhGbg.jpg
572 | https://f061172b00c7bca1e36fdd56f00f238cf2545831.googledrive.com/host/0B_P4A1paVEPbb1UxSUdua3Fwc1k/Vorago_chathead.png
573 | https://secure.runescape.com/m=weblogin/logout.ws?.png
574 |
575 | Dots
576 | http://www.example.com
577 | http://www.example.com.
578 | http://www.example.com..
579 | http://www.example.com...
580 | http://www.example.com/
581 | http://www.example.com/.
582 | http://www.example.com/..
583 | http://www.example.com/...
584 | http://www.example.com/
585 | http://www.example.com/.
586 | http://www.example.com/./.
587 | http://www.example.com/../.
588 |
595 | %
596 | % Regular links
597 | %
598 | My http://example.com site
599 | My http://example.com/ site
600 | http://example.com/foo_bar/
601 | http://user:pass@example.com:8080
602 | http://user@example.com
603 | http://user@example.com:8080
604 | http://user:pass@example.com
605 | [https](https://www.ibm.com)[mailto](mailto:someone@ibm.com) % should not catch as auth (before @ in big link)
606 | http://example.com:8080
607 | http://example.com/?foo=bar
608 | http://example.com?foo=bar
609 | http://example.com/#foo=bar
610 | http://example.com#foo=bar
611 | http://a.in
612 | HTTP://GOOGLE.COM
613 | http://example.invalid % don't restrict root domain when schema exists
614 | http://inrgess2 % Allow local domains to end with digit
615 | http://999 % ..and start with digit, and have digits only
616 | http://host-name % local domain with dash
617 | >>example.com % markdown blockquote
618 | >>http://example.com % markdown blockquote
619 | http://lyricstranslate.com/en/someone-you-നിന്നെ-പോലൊരാള്.html % With control character
620 |
621 | %
622 | % localhost (only with protocol allowed)
623 | %
624 | //localhost
625 | //test.123
626 | http://localhost:8000?
627 |
628 | %
629 | % Other protocols
630 | %
631 | My ssl https://example.com site
632 | My ftp://example.com site
633 |
634 | %
635 | % Neutral proto
636 | %
637 | My ssl //example.com site
638 |
639 | %
640 | % IPs
641 | %
642 | 4.4.4.4
643 | 192.168.1.1/abc
644 |
645 | %
646 | % Fuzzy
647 | %
648 | test.example@http://vk.com
649 | text:http://example.com/
650 | google.com
651 | google.com: // no port
652 | s.l.o.w.io
653 | a-b.com
654 | GOOGLE.COM.
655 | google.xxx // known tld
656 |
657 | %
658 | % Correct termination for . , ! ? [] {} () "" ''
659 | %
660 | (Scoped http://example.com/foo_bar)
661 | http://example.com/foo_bar_(wiki)
662 | http://foo.com/blah_blah_[other]
663 | http://foo.com/blah_blah_{I'm_king}
664 | http://foo.com/blah_blah_I'm_king
665 | http://www.kmart.com/bestway-10'-x-30inch-steel-pro-frame-pool/p-004W007538417001P
666 | http://foo.com/blah_blah_"doublequoted"
667 | http://foo.com/blah_blah_'singlequoted'
668 | (Scoped like http://example.com/foo_bar)
669 | [Scoped like http://example.com/foo_bar]
670 | {Scoped like http://example.com/foo_bar}
671 | "Quoted like http://example.com/foo_bar"
672 | 'Quoted like http://example.com/foo_bar'
673 | [example.com/foo_bar.jpg)]
674 | http://example.com/foo_bar.jpg.
675 | http://example.com/foo_bar/.
676 | http://example.com/foo_bar,
677 | https://github.com/markdown-it/linkify-it/compare/360b13a733f521a8d4903d3a5e1e46c357e9d3ce...f580766349525150a80a32987bb47c2d592efc33
678 | http://example.com/foo_bar...
679 | http://172.26.142.48/viewerjs/#../0529/slides.pdf
680 | http://example.com/foo_bar..
681 | http://example.com/foo_bar?p=10.
682 | https://www.google.ru/maps/@59.9393895,30.3165389,15z?hl=ru
683 | https://www.google.com/maps/place/New+York,+NY,+USA/@40.702271,-73.9968471,11z/data=!4m2!3m1!1s0x89c24fa5d33f083b:0xc80b8f06e177fe62?hl=en
684 | https://www.google.com/analytics/web/?hl=ru&pli=1#report/visitors-overview/a26895874w20458057p96934174/
685 | http://business.timesonline.co.uk/article/0,,9065-2473189,00.html
686 | http://example.com/123!
687 | http://example.com/foo--bar
688 |
689 | % some sites have links with trailing dashes
690 | http://www.bloomberg.com/news/articles/2015-06-26/from-deutsche-bank-to-siemens-what-s-troubling-germany-inc-
691 | http://example.com/foo-with-trailing-dash-dot-.
692 |
800 | {{someVar}}
801 | {{someVar.com.tw}}
802 | {{"http://example.com"}}
803 |
808 | http://free-group.eu/
809 | http://free-group.eu:8080/
810 | http://free-group.eu:8080/?search&follow#id
811 | http://free-group.eu:8080/dash-in-path?search&follow#id
812 |
816 | 110.110.110.110
817 | 12345.124.12.1
818 | 001.000.000.000
819 | 0.0.0.1
820 | 127.0.0.1
821 | 127.0.0.01
822 | 1271.0.0.1
823 | 0.0.0.256
824 | 0.0.0.255
825 |
829 | http://time.com/money/3305393/new-t
836 | (http://www.example.com/)
837 | (Some text... http://www.example.com/)
838 | http://www.example.com/(Some text...)
839 | http://www.example.com/(Some)
840 | http://en.wikipedia.org/wiki/Darwin_(operating_system)
841 | (http://www.foobar.com/test)
842 | http://www.foobar.com/test).
843 | http://www.asianewsphoto.com/(S(neugxif4twuizg551ywh3f55))
844 |
848 | https://github.com/gorhill/uBlock/wiki/Does-µBlock-blocks-ads-or-just-hide-them%3F
857 | http://www.example.com/test,example.html
858 | http://www.example.com/test.html, (comma not to be linkified)
859 | http://www.example.com/test,example.html, (second comma not to be linkified)
860 | http://www.tomshardware.com/reviews/caselabs-ama-recap-jan-2015,4029.html
861 |
865 | http://www.example.com/
866 |
870 | http://www.example.com/
876 | http://www.example.com/
877 |
881 | http://example.com/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.~!$&*+;=:@%/?#(),'[]
882 |
886 | is it not foobar.com?
887 |
891 | [img]http://example.com/test.png[/img]
898 | www.vice.news
911 | www.example.free
920 |
921 |
922 |
923 |
51 | Mozilla(http://www.mozilla.org/) started as a project of next generation www-browser of Netscape(http://www.netscape.com/). See http://www.mozilla.org/src-faq.html#1. Mozilla were planned to be released as "Netscape Communicator 5.0", but the new layout engine "NGLayout" ttp://www.mozilla.org/newlayout/gecko.html prevented it. Netscape 6 ttp://ftp.netscape.com/pub/netscape6/ was released after 2 years from the deciding.And now, the Firefox(h**p://www.mozilla.com/firefox/) web browser is released from the Mozilla Corporation(h++p://www.mozilla.com/).
52 |
54 | Now, TextLink can parse splitted text nodes as joined texts. Mozilla(http://www.mozilla.org/) started as a project of next generation www-browser of Netscape(http://www.netscape.com/). See http://www.mozilla.org/src-faq.html#1. Mozilla were planned to be released as "Netscape Communicator 5.0", but the new layout engine "NGLayout" ttp://www.mozilla.org/newlayout/gecko.html prevented it. Netscape 6 ttp://ftp.netscape.com/pub/netscape6/ was released after 2 years from the deciding.
55 |
57 | There are relative links. Firefox www.mozilla.org/products/firefox/ is a new type www-browser and it very extendable. You can find many extensions from update.mozilla.org. I also release some extensions. TBE ./tabextensions/index.html.en and PopupALT _popupalt.html.en are parts of them. (Text Link ignores relative pathes which includes only a filename like "_popupalt.html.en", because strings with the pattern usually indicates only filenames, not relative pathes.) See the parent page ../xul/xul.html to find other extensions in this website.
58 |
60 | There are links including multi-byte characters. The URI of this website is "http://piro.sakura.ne.jp/", but you can use the secondary URI, "ttp://www98.sakura.ne.jp/ ̄piro/" too.
61 |
62 | Also I don't want these: http://ptt.cc/1234WITHUNICODE, http://example.org/, http://example.org/,http://example.org, 127.0.0.1, http://127.0.0.1, telnet://ptt.cc, AFTERUNICODEhttp://example.org, I'm so happy www, www.sohappywwwhaha, www....
63 |
70 | ABOUT
71 | about:blank
72 | about:config
73 |
74 | MAILTO
75 | mailto:example@example.com
76 |
77 | HTTP
78 | http://www.example.com
79 | http://www.example.com.
80 | http://www.example.com/test/
81 | http://www.example.com/test/.
82 | http://www.example.com/test/index.html
83 | http://www.example.com/test/index.html.
84 | http://user@www.example.com
85 | http://user@www.example.com.
86 | http://user@www.example.com/test/
87 | http://user@www.example.com/test/.
88 | http://user@www.example.com/test/index.html
89 | http://user@www.example.com/test/index.html.
90 | http://user:password@www.example.com
91 | http://user:password@www.example.com.
92 | http://user:password@www.example.com/test/
93 | http://user:password@www.example.com/test/.
94 | http://user:password@www.example.com/test/index.html
95 | http://user:password@www.example.com/test/index.html.
96 | http://192.168.0.1
97 | http://192.168.0.1.
98 | http://192.168.0.1/test/
99 | http://192.168.0.1/test/.
100 | http://192.168.0.1/test/index.html
101 | http://192.168.0.1/test/index.html.
102 | http://user@192.168.0.1
103 | http://user@192.168.0.1.
104 | http://user@192.168.0.1/test/
105 | http://user@192.168.0.1/test/.
106 | http://user@192.168.0.1/test/index.html
107 | http://user@192.168.0.1/test/index.html.
108 | http://user:password@192.168.0.1
109 | http://user:password@192.168.0.1.
110 | http://user:password@192.168.0.1/test/
111 | http://user:password@192.168.0.1/test/.
112 | http://user:password@192.168.0.1/test/index.html
113 | http://user:password@192.168.0.1/test/index.html.
114 |
115 | HTTPS
116 | https://www.example.com
117 | https://www.example.com.
118 | https://www.example.com/test/
119 | https://www.example.com/test/.
120 | https://www.example.com/test/index.html
121 | https://www.example.com/test/index.html.
122 | https://user@www.example.com
123 | https://user@www.example.com.
124 | https://user@www.example.com/test/
125 | https://user@www.example.com/test/.
126 | https://user@www.example.com/test/index.html
127 | https://user@www.example.com/test/index.html.
128 | https://user:password@www.example.com
129 | https://user:password@www.example.com.
130 | https://user:password@www.example.com/test/
131 | https://user:password@www.example.com/test/.
132 | https://user:password@www.example.com/test/index.html
133 | https://user:password@www.example.com/test/index.html.
134 | https://192.168.0.1
135 | https://192.168.0.1.
136 | https://192.168.0.1/test/
137 | https://192.168.0.1/test/.
138 | https://192.168.0.1/test/index.html
139 | https://192.168.0.1/test/index.html.
140 | https://user@192.168.0.1
141 | https://user@192.168.0.1.
142 | https://user@192.168.0.1/test/
143 | https://user@192.168.0.1/test/.
144 | https://user@192.168.0.1/test/index.html
145 | https://user@192.168.0.1/test/index.html.
146 | https://user:password@192.168.0.1
147 | https://user:password@192.168.0.1.
148 | https://user:password@192.168.0.1/test/
149 | https://user:password@192.168.0.1/test/.
150 | https://user:password@192.168.0.1/test/index.html
151 | https://user:password@192.168.0.1/test/index.html.
152 |
153 | FTP
154 | ftp://www.example.com
155 | ftp://www.example.com.
156 | ftp://www.example.com/test/
157 | ftp://www.example.com/test/.
158 | ftp://www.example.com/test/index.html
159 | ftp://www.example.com/test/index.html.
160 | ftp://user@www.example.com
161 | ftp://user@www.example.com.
162 | ftp://user@www.example.com/test/
163 | ftp://user@www.example.com/test/.
164 | ftp://user@www.example.com/test/index.html
165 | ftp://user@www.example.com/test/index.html.
166 | ftp://user:password@www.example.com
167 | ftp://user:password@www.example.com.
168 | ftp://user:password@www.example.com/test/
169 | ftp://user:password@www.example.com/test/.
170 | ftp://user:password@www.example.com/test/index.html
171 | ftp://user:password@www.example.com/test/index.html.
172 | ftp://192.168.0.1
173 | ftp://192.168.0.1.
174 | ftp://192.168.0.1/test/
175 | ftp://192.168.0.1/test/.
176 | ftp://192.168.0.1/test/index.html
177 | ftp://192.168.0.1/test/index.html.
178 | ftp://user@192.168.0.1
179 | ftp://user@192.168.0.1.
180 | ftp://user@192.168.0.1/test/
181 | ftp://user@192.168.0.1/test/.
182 | ftp://user@192.168.0.1/test/index.html
183 | ftp://user@192.168.0.1/test/index.html.
184 | ftp://user:password@192.168.0.1
185 | ftp://user:password@192.168.0.1.
186 | ftp://user:password@192.168.0.1/test/
187 | ftp://user:password@192.168.0.1/test/.
188 | ftp://user:password@192.168.0.1/test/index.html
189 | ftp://user:password@192.168.0.1/test/index.html.
190 |
191 | NNTP
192 | nntp://www.example.com
193 | nntp://www.example.com.
194 | nntp://www.example.com/test/
195 | nntp://www.example.com/test/.
196 | nntp://www.example.com/test/index.html
197 | nntp://www.example.com/test/index.html.
198 | nntp://user@www.example.com
199 | nntp://user@www.example.com.
200 | nntp://user@www.example.com/test/
201 | nntp://user@www.example.com/test/.
202 | nntp://user@www.example.com/test/index.html
203 | nntp://user@www.example.com/test/index.html.
204 | nntp://user:password@www.example.com
205 | nntp://user:password@www.example.com.
206 | nntp://user:password@www.example.com/test/
207 | nntp://user:password@www.example.com/test/.
208 | nntp://user:password@www.example.com/test/index.html
209 | nntp://user:password@www.example.com/test/index.html.
210 | nntp://192.168.0.1
211 | nntp://192.168.0.1.
212 | nntp://192.168.0.1/test/
213 | nntp://192.168.0.1/test/.
214 | nntp://192.168.0.1/test/index.html
215 | nntp://192.168.0.1/test/index.html.
216 | nntp://user@192.168.0.1
217 | nntp://user@192.168.0.1.
218 | nntp://user@192.168.0.1/test/
219 | nntp://user@192.168.0.1/test/.
220 | nntp://user@192.168.0.1/test/index.html
221 | nntp://user@192.168.0.1/test/index.html.
222 | nntp://user:password@192.168.0.1
223 | nntp://user:password@192.168.0.1.
224 | nntp://user:password@192.168.0.1/test/
225 | nntp://user:password@192.168.0.1/test/.
226 | nntp://user:password@192.168.0.1/test/index.html
227 | nntp://user:password@192.168.0.1/test/index.html.
228 |
229 | NEWS
230 | news://www.example.com
231 | news://www.example.com.
232 | news://www.example.com/test/
233 | news://www.example.com/test/.
234 | news://www.example.com/test/index.html
235 | news://www.example.com/test/index.html.
236 | news://user@www.example.com
237 | news://user@www.example.com.
238 | news://user@www.example.com/test/
239 | news://user@www.example.com/test/.
240 | news://user@www.example.com/test/index.html
241 | news://user@www.example.com/test/index.html.
242 | news://user:password@www.example.com
243 | news://user:password@www.example.com.
244 | news://user:password@www.example.com/test/
245 | news://user:password@www.example.com/test/.
246 | news://user:password@www.example.com/test/index.html
247 | news://user:password@www.example.com/test/index.html.
248 | news://192.168.0.1
249 | news://192.168.0.1.
250 | news://192.168.0.1/test/
251 | news://192.168.0.1/test/.
252 | news://192.168.0.1/test/index.html
253 | news://192.168.0.1/test/index.html.
254 | news://user@192.168.0.1
255 | news://user@192.168.0.1.
256 | news://user@192.168.0.1/test/
257 | news://user@192.168.0.1/test/.
258 | news://user@192.168.0.1/test/index.html
259 | news://user@192.168.0.1/test/index.html.
260 | news://user:password@192.168.0.1
261 | news://user:password@192.168.0.1.
262 | news://user:password@192.168.0.1/test/
263 | news://user:password@192.168.0.1/test/.
264 | news://user:password@192.168.0.1/test/index.html
265 | news://user:password@192.168.0.1/test/index.html.
266 |
267 | TELNET
268 | telnet://www.example.com
269 | telnet://www.example.com.
270 | telnet://www.example.com/test/
271 | telnet://www.example.com/test/.
272 | telnet://www.example.com/test/index.html
273 | telnet://www.example.com/test/index.html.
274 | telnet://user@www.example.com
275 | telnet://user@www.example.com.
276 | telnet://user@www.example.com/test/
277 | telnet://user@www.example.com/test/.
278 | telnet://user@www.example.com/test/index.html
279 | telnet://user@www.example.com/test/index.html.
280 | telnet://user:password@www.example.com
281 | telnet://user:password@www.example.com.
282 | telnet://user:password@www.example.com/test/
283 | telnet://user:password@www.example.com/test/.
284 | telnet://user:password@www.example.com/test/index.html
285 | telnet://user:password@www.example.com/test/index.html.
286 | telnet://192.168.0.1
287 | telnet://192.168.0.1.
288 | telnet://192.168.0.1/test/
289 | telnet://192.168.0.1/test/.
290 | telnet://192.168.0.1/test/index.html
291 | telnet://192.168.0.1/test/index.html.
292 | telnet://user@192.168.0.1
293 | telnet://user@192.168.0.1.
294 | telnet://user@192.168.0.1/test/
295 | telnet://user@192.168.0.1/test/.
296 | telnet://user@192.168.0.1/test/index.html
297 | telnet://user@192.168.0.1/test/index.html.
298 | telnet://user:password@192.168.0.1
299 | telnet://user:password@192.168.0.1.
300 | telnet://user:password@192.168.0.1/test/
301 | telnet://user:password@192.168.0.1/test/.
302 | telnet://user:password@192.168.0.1/test/index.html
303 | telnet://user:password@192.168.0.1/test/index.html.
304 |
305 | IRC
306 | irc://www.example.com
307 | irc://www.example.com.
308 | irc://www.example.com/test/
309 | irc://www.example.com/test/.
310 | irc://www.example.com/test/index.html
311 | irc://www.example.com/test/index.html.
312 | irc://user@www.example.com
313 | irc://user@www.example.com.
314 | irc://user@www.example.com/test/
315 | irc://user@www.example.com/test/.
316 | irc://user@www.example.com/test/index.html
317 | irc://user@www.example.com/test/index.html.
318 | irc://user:password@www.example.com
319 | irc://user:password@www.example.com.
320 | irc://user:password@www.example.com/test/
321 | irc://user:password@www.example.com/test/.
322 | irc://user:password@www.example.com/test/index.html
323 | irc://user:password@www.example.com/test/index.html.
324 | irc://192.168.0.1
325 | irc://192.168.0.1.
326 | irc://192.168.0.1/test/
327 | irc://192.168.0.1/test/.
328 | irc://192.168.0.1/test/index.html
329 | irc://192.168.0.1/test/index.html.
330 | irc://user@192.168.0.1
331 | irc://user@192.168.0.1.
332 | irc://user@192.168.0.1/test/
333 | irc://user@192.168.0.1/test/.
334 | irc://user@192.168.0.1/test/index.html
335 | irc://user@192.168.0.1/test/index.html.
336 | irc://user:password@192.168.0.1
337 | irc://user:password@192.168.0.1.
338 | irc://user:password@192.168.0.1/test/
339 | irc://user:password@192.168.0.1/test/.
340 | irc://user:password@192.168.0.1/test/index.html
341 | irc://user:password@192.168.0.1/test/index.html.
342 |
343 | CUSTOM
344 | hxxp://www.example.com
345 | hxxp://www.example.com.
346 | hxxp://www.example.com/test/
347 | hxxp://www.example.com/test/.
348 | hxxp://www.example.com/test/index.html
349 | hxxp://www.example.com/test/index.html.
350 | hxxp://user@www.example.com
351 | hxxp://user@www.example.com.
352 | hxxp://user@www.example.com/test/
353 | hxxp://user@www.example.com/test/.
354 | hxxp://user@www.example.com/test/index.html
355 | hxxp://user@www.example.com/test/index.html.
356 | hxxp://user:password@www.example.com
357 | hxxp://user:password@www.example.com.
358 | hxxp://user:password@www.example.com/test/
359 | hxxp://user:password@www.example.com/test/.
360 | hxxp://user:password@www.example.com/test/index.html
361 | hxxp://user:password@www.example.com/test/index.html.
362 | hxxp://192.168.0.1
363 | hxxp://192.168.0.1.
364 | hxxp://192.168.0.1/test/
365 | hxxp://192.168.0.1/test/.
366 | hxxp://192.168.0.1/test/index.html
367 | hxxp://192.168.0.1/test/index.html.
368 | hxxp://user@192.168.0.1
369 | hxxp://user@192.168.0.1.
370 | hxxp://user@192.168.0.1/test/
371 | hxxp://user@192.168.0.1/test/.
372 | hxxp://user@192.168.0.1/test/index.html
373 | hxxp://user@192.168.0.1/test/index.html.
374 | hxxp://user:password@192.168.0.1
375 | hxxp://user:password@192.168.0.1.
376 | hxxp://user:password@192.168.0.1/test/
377 | hxxp://user:password@192.168.0.1/test/.
378 | hxxp://user:password@192.168.0.1/test/index.html
379 | hxxp://user:password@192.168.0.1/test/index.html.
380 |
381 | CUSTOM
382 | h**p://www.example.com
383 | h**p://www.example.com.
384 | h**p://www.example.com/test/
385 | h**p://www.example.com/test/.
386 | h**p://www.example.com/test/index.html
387 | h**p://www.example.com/test/index.html.
388 | h**p://user@www.example.com
389 | h**p://user@www.example.com.
390 | h**p://user@www.example.com/test/
391 | h**p://user@www.example.com/test/.
392 | h**p://user@www.example.com/test/index.html
393 | h**p://user@www.example.com/test/index.html.
394 | h**p://user:password@www.example.com
395 | h**p://user:password@www.example.com.
396 | h**p://user:password@www.example.com/test/
397 | h**p://user:password@www.example.com/test/.
398 | h**p://user:password@www.example.com/test/index.html
399 | h**p://user:password@www.example.com/test/index.html.
400 | h**p://192.168.0.1
401 | h**p://192.168.0.1.
402 | h**p://192.168.0.1/test/
403 | h**p://192.168.0.1/test/.
404 | h**p://192.168.0.1/test/index.html
405 | h**p://192.168.0.1/test/index.html.
406 | h**p://user@192.168.0.1
407 | h**p://user@192.168.0.1.
408 | h**p://user@192.168.0.1/test/
409 | h**p://user@192.168.0.1/test/.
410 | h**p://user@192.168.0.1/test/index.html
411 | h**p://user@192.168.0.1/test/index.html.
412 | h**p://user:password@192.168.0.1
413 | h**p://user:password@192.168.0.1.
414 | h**p://user:password@192.168.0.1/test/
415 | h**p://user:password@192.168.0.1/test/.
416 | h**p://user:password@192.168.0.1/test/index.html
417 | h**p://user:password@192.168.0.1/test/index.html.
418 |
419 | WWW (no protocol)
420 | www.example.com
421 | www.example.com.
422 | www.example.com/test/
423 | www.example.com/test/.
424 | www.example.com/test/index.html
425 | www.example.com/test/index.html.
426 | user@www.example.com (ambiguous, but recognized subdomain. not an e-mail address)
427 | user@www.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
428 | user@www.example.com/test/
429 | user@www.example.com/test/.
430 | user@www.example.com/test/index.html
431 | user@www.example.com/test/index.html.
432 | user:password@www.example.com
433 | user:password@www.example.com.
434 | user:password@www.example.com/test/
435 | user:password@www.example.com/test/.
436 | user:password@www.example.com/test/index.html
437 | user:password@www.example.com/test/index.html.
438 |
439 | FTP (no protocol)
440 | ftp.example.com
441 | ftp.example.com.
442 | ftp.example.com/test/
443 | ftp.example.com/test/.
444 | ftp.example.com/test/index.html
445 | ftp.example.com/test/index.html.
446 | user@ftp.example.com (ambiguous, but recognized subdomain. not an e-mail address)
447 | user@ftp.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
448 | user@ftp.example.com/test/
449 | user@ftp.example.com/test/.
450 | user@ftp.example.com/test/index.html
451 | user@ftp.example.com/test/index.html.
452 | user:password@ftp.example.com
453 | user:password@ftp.example.com.
454 | user:password@ftp.example.com/test/
455 | user:password@ftp.example.com/test/.
456 | user:password@ftp.example.com/test/index.html
457 | user:password@ftp.example.com/test/index.html.
458 |
459 | IRC (no protocol)
460 | irc.example.com
461 | irc.example.com.
462 | irc.example.com/test/
463 | irc.example.com/test/.
464 | irc.example.com/test/index.html
465 | irc.example.com/test/index.html.
466 | user@irc.example.com (ambiguous, but recognized subdomain. not an e-mail address)
467 | user@irc.example.com. (ambiguous, but recognized subdomain. not an e-mail address)
468 | user@irc.example.com/test/
469 | user@irc.example.com/test/.
470 | user@irc.example.com/test/index.html
471 | user@irc.example.com/test/index.html.
472 | user:password@irc.example.com
473 | user:password@irc.example.com.
474 | user:password@irc.example.com/test/
475 | user:password@irc.example.com/test/.
476 | user:password@irc.example.com/test/index.html
477 | user:password@irc.example.com/test/index.html.
478 | #test-name@irc.example.com
479 | #test-name@irc.example.com.
480 | irc.example.com#test-name
481 | irc.example.com#test-name.
482 |
483 | IP (no protocol)
484 | 192.168.0.1 (not linkified; pattern too common)
485 | 192.168.0.1. (not linkified; pattern too common)
486 | 192.168.0.1/test/
487 | 192.168.0.1/test/.
488 | 192.168.0.1/test/index.html
489 | 192.168.0.1/test/index.html.
490 | user@192.168.0.1 (ambiguous; should be recognized as e-mail)
491 | user@192.168.0.1. (ambiguous; should be recognized as e-mail)
492 | user@192.168.0.1/test/
493 | user@192.168.0.1/test/.
494 | user@192.168.0.1/test/index.html
495 | user@192.168.0.1/test/index.html.
496 | user:password@192.168.0.1
497 | user:password@192.168.0.1.
498 | user:password@192.168.0.1/test/
499 | user:password@192.168.0.1/test/.
500 | user:password@192.168.0.1/test/index.html
501 | user:password@192.168.0.1/test/index.html.
502 |
503 | OTHER (no protocol)
504 | subdomain.example.com (not linkified; pattern too common)
505 | subdomain.example.com. (not linkified; pattern too common)
506 | subdomain.example.com/test/
507 | subdomain.example.com/test/.
508 | subdomain.example.com/test/index.html
509 | subdomain.example.com/test/index.html.
510 | user@subdomain.example.com (ambiguous; should be recognized as e-mail)
511 | user@subdomain.example.com. (ambiguous; should be recognized as e-mail)
512 | user@subdomain.example.com/test/
513 | user@subdomain.example.com/test/.
514 | user@subdomain.example.com/test/index.html
515 | user@subdomain.example.com/test/index.html.
516 | user:password@subdomain.example.com
517 | user:password@subdomain.example.com.
518 | user:password@subdomain.example.com/test/
519 | user:password@subdomain.example.com/test/.
520 | user:password@subdomain.example.com/test/index.html
521 | user:password@subdomain.example.com/test/index.html.
522 |
523 | EMAIL
524 | test@example.com
525 | test@example.com.
526 | test.test@test.example.com
527 | test.test@test.example.com.
528 | test@192.168.0.1
529 | test@192.168.0.1.
530 | test.test@192.168.0.1
531 | test.test@192.168.0.1.
532 |
533 | IMAGE
534 | http://www.example.com/image.jpg
535 | http://www.example.com/image.jpeg
536 | http://www.example.com/image.png
537 | http://www.example.com/image.gif
538 | http://www.example.com/image.bmp
539 | http://www.example.com/image.jpg.test (not an image)
540 | http://www.example.com/image.jpeg.test (not an image)
541 | http://www.example.com/image.png.test (not an image)
542 | http://www.example.com/image.gif.test (not an image)
543 | http://www.example.com/image.bmp.test (not an image)
544 | http://www.example.com/image.jpg?test
545 | http://www.example.com/image.jpeg?test
546 | http://www.example.com/image.png?test
547 | http://www.example.com/image.gif?test
548 | http://www.example.com/image.bmp?test
549 | http://www.example.com/image.test?jpg (not an image)
550 | http://www.example.com/image.test?jpeg (not an image)
551 | http://www.example.com/image.test?png (not an image)
552 | http://www.example.com/image.test?gif (not an image)
553 | http://www.example.com/image.test?bmp (not an image)
554 | http://www.example.com/image.jpg#test
555 | http://www.example.com/image.jpeg#test
556 | http://www.example.com/image.png#test
557 | http://www.example.com/image.gif#test
558 | http://www.example.com/image.bmp#test
559 | http://www.example.com/image.test#jpg (not an image)
560 | http://www.example.com/image.test#jpeg (not an image)
561 | http://www.example.com/image.test#png (not an image)
562 | http://www.example.com/image.test#gif (not an image)
563 | http://www.example.com/image.test#bmp (not an image)
564 | https://greasyfork.org/assets/blacklogo96-0596aff6108f83c3073764496d7768ec.png
565 | http://i.imgur.com/25zhGbg.jpg
566 | https://f061172b00c7bca1e36fdd56f00f238cf2545831.googledrive.com/host/0B_P4A1paVEPbb1UxSUdua3Fwc1k/Vorago_chathead.png
567 | https://secure.runescape.com/m=weblogin/logout.ws?.png
568 |
569 | Dots
570 | http://www.example.com
571 | http://www.example.com.
572 | http://www.example.com..
573 | http://www.example.com...
574 | http://www.example.com/
575 | http://www.example.com/.
576 | http://www.example.com/..
577 | http://www.example.com/...
578 | http://www.example.com/
579 | http://www.example.com/.
580 | http://www.example.com/./.
581 | http://www.example.com/../.
582 |
589 | %
590 | % Regular links
591 | %
592 | My http://example.com site
593 | My http://example.com/ site
594 | http://example.com/foo_bar/
595 | http://user:pass@example.com:8080
596 | http://user@example.com
597 | http://user@example.com:8080
598 | http://user:pass@example.com
599 | [https](https://www.ibm.com)[mailto](mailto:someone@ibm.com) % should not catch as auth (before @ in big link)
600 | http://example.com:8080
601 | http://example.com/?foo=bar
602 | http://example.com?foo=bar
603 | http://example.com/#foo=bar
604 | http://example.com#foo=bar
605 | http://a.in
606 | HTTP://GOOGLE.COM
607 | http://example.invalid % don't restrict root domain when schema exists
608 | http://inrgess2 % Allow local domains to end with digit
609 | http://999 % ..and start with digit, and have digits only
610 | http://host-name % local domain with dash
611 | >>example.com % markdown blockquote
612 | >>http://example.com % markdown blockquote
613 | http://lyricstranslate.com/en/someone-you-നിന്നെ-പോലൊരാള്.html % With control character
614 |
615 | %
616 | % localhost (only with protocol allowed)
617 | %
618 | //localhost
619 | //test.123
620 | http://localhost:8000?
621 |
622 | %
623 | % Other protocols
624 | %
625 | My ssl https://example.com site
626 | My ftp://example.com site
627 |
628 | %
629 | % Neutral proto
630 | %
631 | My ssl //example.com site
632 |
633 | %
634 | % IPs
635 | %
636 | 4.4.4.4
637 | 192.168.1.1/abc
638 |
639 | %
640 | % Fuzzy
641 | %
642 | test.example@http://vk.com
643 | text:http://example.com/
644 | google.com
645 | google.com: // no port
646 | s.l.o.w.io
647 | a-b.com
648 | GOOGLE.COM.
649 | google.xxx // known tld
650 |
651 | %
652 | % Correct termination for . , ! ? [] {} () "" ''
653 | %
654 | (Scoped http://example.com/foo_bar)
655 | http://example.com/foo_bar_(wiki)
656 | http://foo.com/blah_blah_[other]
657 | http://foo.com/blah_blah_{I'm_king}
658 | http://foo.com/blah_blah_I'm_king
659 | http://www.kmart.com/bestway-10'-x-30inch-steel-pro-frame-pool/p-004W007538417001P
660 | http://foo.com/blah_blah_"doublequoted"
661 | http://foo.com/blah_blah_'singlequoted'
662 | (Scoped like http://example.com/foo_bar)
663 | [Scoped like http://example.com/foo_bar]
664 | {Scoped like http://example.com/foo_bar}
665 | "Quoted like http://example.com/foo_bar"
666 | 'Quoted like http://example.com/foo_bar'
667 | [example.com/foo_bar.jpg)]
668 | http://example.com/foo_bar.jpg.
669 | http://example.com/foo_bar/.
670 | http://example.com/foo_bar,
671 | https://github.com/markdown-it/linkify-it/compare/360b13a733f521a8d4903d3a5e1e46c357e9d3ce...f580766349525150a80a32987bb47c2d592efc33
672 | http://example.com/foo_bar...
673 | http://172.26.142.48/viewerjs/#../0529/slides.pdf
674 | http://example.com/foo_bar..
675 | http://example.com/foo_bar?p=10.
676 | https://www.google.ru/maps/@59.9393895,30.3165389,15z?hl=ru
677 | https://www.google.com/maps/place/New+York,+NY,+USA/@40.702271,-73.9968471,11z/data=!4m2!3m1!1s0x89c24fa5d33f083b:0xc80b8f06e177fe62?hl=en
678 | https://www.google.com/analytics/web/?hl=ru&pli=1#report/visitors-overview/a26895874w20458057p96934174/
679 | http://business.timesonline.co.uk/article/0,,9065-2473189,00.html
680 | http://example.com/123!
681 | http://example.com/foo--bar
682 |
683 | % some sites have links with trailing dashes
684 | http://www.bloomberg.com/news/articles/2015-06-26/from-deutsche-bank-to-siemens-what-s-troubling-germany-inc-
685 | http://example.com/foo-with-trailing-dash-dot-.
686 |
794 | {{someVar}}
795 | {{someVar.com.tw}}
796 | {{"http://example.com"}}
797 |
802 | http://free-group.eu/
803 | http://free-group.eu:8080/
804 | http://free-group.eu:8080/?search&follow#id
805 | http://free-group.eu:8080/dash-in-path?search&follow#id
806 |
810 | 110.110.110.110
811 | 12345.124.12.1
812 | 001.000.000.000
813 | 0.0.0.1
814 | 127.0.0.1
815 | 127.0.0.01
816 | 1271.0.0.1
817 | 0.0.0.256
818 | 0.0.0.255
819 |
823 | http://time.com/money/3305393/new-t
830 | (http://www.example.com/)
831 | (Some text... http://www.example.com/)
832 | http://www.example.com/(Some text...)
833 | http://www.example.com/(Some)
834 | http://en.wikipedia.org/wiki/Darwin_(operating_system)
835 | (http://www.foobar.com/test)
836 | http://www.foobar.com/test).
837 | http://www.asianewsphoto.com/(S(neugxif4twuizg551ywh3f55))
838 |
842 | https://github.com/gorhill/uBlock/wiki/Does-µBlock-blocks-ads-or-just-hide-them%3F
851 | http://www.example.com/test,example.html
852 | http://www.example.com/test.html, (comma not to be linkified)
853 | http://www.example.com/test,example.html, (second comma not to be linkified)
854 | http://www.tomshardware.com/reviews/caselabs-ama-recap-jan-2015,4029.html
855 |
859 | http://www.example.com/
860 |
864 | http://www.example.com/
870 | http://www.example.com/
871 |
875 | http://example.com/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-.~!$&*+;=:@%/?#(),'[]
876 |
880 | is it not foobar.com?
881 |
885 | [img]http://example.com/test.png[/img]
892 | www.vice.news
905 | www.example.free
913 |
914 |
915 | Linkify Plus Plus Test Page
50 |
51 | Random test 1
53 |
54 | Original from http://piro.sakura.ne.jp/xul/textlink/index.html.en#testcases
55 |
56 | Random test 2
72 |
73 | Original from http://yellow5.us/firefox/testcases.txt
74 |
75 | Random test 3
591 |
592 | Original from http://markdown-it.github.io/linkify-it/, licensed under MIT
593 |
594 | Conflict with Angular
795 |
797 | Should ignore any links in
799 | {{...}}
798 | Domain may contain dash
807 | Support IP address
815 | Dealing with
828 | wbr
tagDealing with parenthesis
835 | Unicode issue
847 |
849 | http://www.bücher.ch
850 | http://www.example.com/exämple/
851 | http://www.example.com/example/exämple.php?test=täst
852 | http://www.example.com/example/example.php?test=täst
853 | Comma
856 | Blacklist
864 | Whitelist
869 |
871 | http://www.example.com/
872 | Contenteditable
875 | Valid characters
880 | Question mark
885 | BBCode
890 |
892 | [url]http://example.com/test.png[/url]
893 | http://example.com/test.png[b]something-else[/b]
894 | Random tests
897 |
899 | http://forum.gamer.com.tw/C.php?bsn=12259&snA=264382&tnum=6&subbsn=18
900 | _www.example.com
901 | onenote:#Books§ion-id={F1580D31-86DD-4975-9169-CBB0C3846D9D}&page-id={39F02142-9AAA-49C6-AD26-E47114E2BB1C}&end&base-path=https://d.docs.live.net/dc516d79aca53670/OneNote/@Home/Tab9.one
902 | evernote:///view/[userId]/[shardId]/[noteGuid]/[noteGuid]/
903 | magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a&dn
904 | "3.141592653589793238462643383279502884197169399375105820974944592.com" or "yesno.wtf"
905 |
906 | -http://example.com
907 | Bad TLDs
910 |
912 | www.example.zip
913 | www.example.call
914 | www.example.constructor
915 | www.example.onion we actually treat onion as TLD
916 | Linkify Plus Plus Test Page
44 |
45 | Random test 1
47 |
48 | Original from http://piro.sakura.ne.jp/xul/textlink/index.html.en#testcases
49 |
50 | Random test 2
66 |
67 | Original from http://yellow5.us/firefox/testcases.txt
68 |
69 | Random test 3
585 |
586 | Original from http://markdown-it.github.io/linkify-it/, licensed under MIT
587 |
588 | Conflict with Angular
789 |
791 | Should ignore any links in
793 | {{...}}
792 | Domain may contain dash
801 | Support IP address
809 | Dealing with
822 | wbr
tagDealing with parenthesis
829 | Unicode issue
841 |
843 | http://www.bücher.ch
844 | http://www.example.com/exämple/
845 | http://www.example.com/example/exämple.php?test=täst
846 | http://www.example.com/example/example.php?test=täst
847 | Comma
850 | Blacklist
858 | Whitelist
863 |
865 | http://www.example.com/
866 | Contenteditable
869 | Valid characters
874 | Question mark
879 | BBCode
884 |
886 | [url]http://example.com/test.png[/url]
887 | http://example.com/test.png[b]something-else[/b]
888 | Random tests
891 |
893 | http://forum.gamer.com.tw/C.php?bsn=12259&snA=264382&tnum=6&subbsn=18
894 | _www.example.com
895 | onenote:#Books§ion-id={F1580D31-86DD-4975-9169-CBB0C3846D9D}&page-id={39F02142-9AAA-49C6-AD26-E47114E2BB1C}&end&base-path=https://d.docs.live.net/dc516d79aca53670/OneNote/@Home/Tab9.one
896 | evernote:///view/[userId]/[shardId]/[noteGuid]/[noteGuid]/
897 | magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a&dn
898 | "3.141592653589793238462643383279502884197169399375105820974944592.com" or "yesno.wtf"
899 |
900 | -http://example.com
901 | Bad TLDs
904 |
906 | www.example.zip
907 | www.example.call
908 | www.example.constructor
909 |
`) 52 | }, 53 | { 54 | test: /background\.js$/, 55 | target: "dist-extension/manifest.json", 56 | handle: (content, {scripts}) => { 57 | content.background.scripts = scripts; 58 | return content; 59 | } 60 | }, 61 | { 62 | test: /content\.js$/, 63 | target: "dist-extension/manifest.json", 64 | handle: (content, {scripts}) => { 65 | content.content_scripts[0].js = scripts; 66 | content.content_scripts[0].exclude_globs = usm.getMeta().exclude; 67 | return content; 68 | } 69 | } 70 | ]), 71 | !DEV && terser({module: false}) 72 | ] 73 | }, 74 | { 75 | input: { 76 | "linkify-plus-plus.user": "src/userscript/index.js" 77 | }, 78 | output: { 79 | format: "es", 80 | banner: metaDataBlock(), 81 | dir: "dist" 82 | }, 83 | plugins: [ 84 | cleanMessages(), 85 | ...commonPlugins(false), 86 | ] 87 | } 88 | ]; 89 | 90 | function metaDataBlock() { 91 | const meta = usm.getMeta(); 92 | meta.icon = dataurl.format({ 93 | data: fs.readFileSync("src/static/icon.svg"), 94 | mimetype: "image/svg+xml" 95 | }); 96 | return usm.stringify(meta); 97 | } 98 | 99 | function cleanMessages() { 100 | return { 101 | name: "clean-messages", 102 | transform: (code, id) => { 103 | if (!/messages.json/.test(id)) return; 104 | 105 | const message = JSON.parse(code); 106 | const newMessage = {}; 107 | for (const [key, value] of Object.entries(message)) { 108 | if (value.placeholders) { 109 | value.message = value.message.replace(/\$\w+\$/g, m => { 110 | const name = m.slice(1, -1).toLowerCase(); 111 | return value.placeholders[name].content; 112 | }); 113 | } 114 | if (/^options/.test(key)) { 115 | newMessage[key] = value.message; 116 | } else if (/^pref/.test(key)) { 117 | newMessage[key[4].toLowerCase() + key.slice(5)] = value.message; 118 | } 119 | } 120 | return JSON.stringify(newMessage, null, 2); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/extension/background.js: -------------------------------------------------------------------------------- 1 | import browser from "webextension-polyfill" 2 | 3 | let domain = ""; 4 | const ports = new Set; 5 | 6 | browser.runtime.onConnect.addListener(port => { 7 | if (port.name !== "optionsPage" || port.error) { 8 | return; 9 | } 10 | ports.add(port); 11 | port.onDisconnect.addListener(() => ports.delete(port)); 12 | port.postMessage({method: "domainChange", domain}); 13 | }); 14 | 15 | browser.browserAction.onClicked.addListener(tab => { 16 | const url = new URL(tab.url); 17 | domain = url.hostname; 18 | for (const port of ports) { 19 | port.postMessage({method: "domainChange", domain}); 20 | } 21 | // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1949504 22 | browser.runtime.openOptionsPage(); 23 | }); 24 | 25 | if (/Chrome\/\d+/.test(navigator.userAgent)) { 26 | browser.browserAction.setIcon({ 27 | path: "/icon.svg" 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/extension/content.js: -------------------------------------------------------------------------------- 1 | const {startLinkifyPlusPlus} = require("../lib/main"); 2 | const pref = require("../lib/extension-pref"); 3 | 4 | startLinkifyPlusPlus(async () => { 5 | await pref.ready; 6 | await pref.setCurrentScope(location.hostname); 7 | return pref; 8 | }); 9 | -------------------------------------------------------------------------------- /src/extension/dialog.js: -------------------------------------------------------------------------------- 1 | require("webext-dialog/popup"); 2 | -------------------------------------------------------------------------------- /src/extension/options.js: -------------------------------------------------------------------------------- 1 | const browser = require("webextension-polyfill"); 2 | const {createUI, createBinding} = require("webext-pref-ui"); 3 | const {createDialogService} = require("webext-dialog"); 4 | 5 | const prefBody = require("../lib/pref-body"); 6 | const pref = require("../lib/extension-pref"); 7 | 8 | const dialog = createDialogService({ 9 | path: "dialog.html", 10 | getMessage: key => browser.i18n.getMessage(`dialog${cap(key)}`) 11 | }); 12 | 13 | function cap(s) { 14 | return s[0].toUpperCase() + s.slice(1); 15 | } 16 | 17 | pref.ready.then(() => { 18 | let domain = ""; 19 | 20 | const root = document.querySelector(".pref-root"); 21 | 22 | const getMessage = (key, params) => browser.i18n.getMessage(`pref${cap(key)}`, params); 23 | 24 | root.append(createUI({ 25 | body: prefBody(browser.i18n.getMessage), 26 | getMessage 27 | })); 28 | 29 | createBinding({ 30 | pref, 31 | root, 32 | getNewScope: () => domain, 33 | getMessage, 34 | alert: dialog.alert, 35 | confirm: dialog.confirm, 36 | prompt: dialog.prompt 37 | }); 38 | 39 | const port = browser.runtime.connect({ 40 | name: "optionsPage" 41 | }); 42 | port.onMessage.addListener(message => { 43 | if (message.method === "domainChange") { 44 | domain = message.domain; 45 | pref.setCurrentScope(pref.getScopeList().includes(domain) ? domain : "global"); 46 | } 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/lib/extension-pref.js: -------------------------------------------------------------------------------- 1 | const {createPref, createWebextStorage} = require("webext-pref"); 2 | const prefDefault = require("./pref-default"); 3 | 4 | const pref = createPref(prefDefault()); 5 | pref.ready = pref.connect(createWebextStorage()); 6 | 7 | module.exports = pref; 8 | -------------------------------------------------------------------------------- /src/lib/main.mjs: -------------------------------------------------------------------------------- 1 | import {UrlMatcher, INVALID_TAGS} from "linkify-plus-plus-core"; 2 | 3 | import triggers from "./triggers/index.mjs"; 4 | import {processedNodes} from "./triggers/cache.mjs"; 5 | 6 | function createValidator({includeElement, excludeElement}) { 7 | const f = function(node) { 8 | if (processedNodes.has(node)) { 9 | return false; 10 | } 11 | 12 | if (node.isContentEditable) { 13 | return false; 14 | } 15 | 16 | if (node.matches) { 17 | if (includeElement && node.matches(includeElement)) { 18 | return true; 19 | } 20 | if (excludeElement && node.matches(excludeElement)) { 21 | return false; 22 | } 23 | } 24 | return true; 25 | }; 26 | f.isIncluded = node => { 27 | return includeElement && node.matches(includeElement); 28 | }; 29 | f.isExcluded = node => { 30 | if (INVALID_TAGS[node.localName]) { 31 | return true; 32 | } 33 | return excludeElement && node.matches(excludeElement); 34 | }; 35 | return f; 36 | } 37 | 38 | function stringToList(value) { 39 | value = value.trim(); 40 | if (!value) { 41 | return []; 42 | } 43 | return value.split(/\s*\n\s*/g); 44 | } 45 | 46 | function createOptions(pref) { 47 | const options = {}; 48 | pref.on("change", update); 49 | update(pref.getAll()); 50 | return options; 51 | 52 | function update(changes) { 53 | Object.assign(options, changes); 54 | options.validator = createValidator(options); 55 | if (typeof options.customRules === "string") { 56 | options.customRules = stringToList(options.customRules); 57 | } 58 | options.matcher = new UrlMatcher(options); 59 | options.onlink = options.embedImageExcludeElement ? onlink : null; 60 | } 61 | 62 | function onlink({link, range, content}) { 63 | if (link.childNodes[0].localName !== "img" || !options.embedImageExcludeElement) { 64 | return; 65 | } 66 | 67 | var parent = range.startContainer; 68 | // it might be a text node 69 | if (!parent.closest) { 70 | parent = parent.parentNode; 71 | } 72 | if (!parent.closest(options.embedImageExcludeElement)) return; 73 | // remove image 74 | link.innerHTML = ""; 75 | link.appendChild(content); 76 | } 77 | } 78 | 79 | export async function startLinkifyPlusPlus(getPref) { 80 | // Limit contentType to specific content type 81 | if ( 82 | document.contentType && 83 | !["text/plain", "text/html", "application/xhtml+xml"].includes(document.contentType) 84 | ) { 85 | return; 86 | } 87 | 88 | const pref = await getPref(); 89 | const options = createOptions(pref); 90 | for (const trigger of triggers) { 91 | if (pref.get(trigger.key)) { 92 | trigger.enable(options); 93 | } 94 | } 95 | pref.on("change", changes => { 96 | for (const trigger of triggers) { 97 | if (changes[trigger.key] === true) { 98 | trigger.enable(options); 99 | } 100 | if (changes[trigger.key] === false) { 101 | trigger.disable(); 102 | } 103 | } 104 | }); 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/lib/pref-body.js: -------------------------------------------------------------------------------- 1 | module.exports = getMessage => { 2 | return [ 3 | { 4 | type: "section", 5 | label: getMessage("optionsUrlMatcherLabel"), 6 | children: [ 7 | { 8 | key: "fuzzyIp", 9 | type: "checkbox", 10 | label: getMessage("optionsFuzzyIpLabel") 11 | }, 12 | { 13 | key: "ignoreMustache", 14 | type: "checkbox", 15 | label: getMessage("optionsIgnoreMustacheLabel") 16 | }, 17 | { 18 | key: "unicode", 19 | type: "checkbox", 20 | label: getMessage("optionsUnicodeLabel") 21 | }, 22 | { 23 | key: "mail", 24 | type: "checkbox", 25 | label: getMessage("optionsMailLabel") 26 | }, 27 | { 28 | key: "standalone", 29 | type: "checkbox", 30 | label: getMessage("optionsStandaloneLabel"), 31 | children: [ 32 | { 33 | key: "boundaryLeft", 34 | type: "text", 35 | label: getMessage("optionsBoundaryLeftLabel") 36 | }, 37 | { 38 | key: "boundaryRight", 39 | type: "text", 40 | label: getMessage("optionsBoundaryRightLabel") 41 | } 42 | ] 43 | }, 44 | { 45 | key: "customRules", 46 | type: "textarea", 47 | label: getMessage("optionsCustomRulesLabel"), 48 | learnMore: "https://github.com/eight04/linkify-plus-plus?tab=readme-ov-file#custom-rules" 49 | }, 50 | 51 | ] 52 | }, 53 | { 54 | type: "section", 55 | label: getMessage("optionsLinkifierLabel"), 56 | children: [ 57 | { 58 | key: "triggerByPageLoad", 59 | type: "checkbox", 60 | label: getMessage("optionsTriggerByPageLoadLabel") 61 | }, 62 | { 63 | key: "triggerByNewNode", 64 | type: "checkbox", 65 | label: getMessage("optionsTriggerByNewNodeLabel") 66 | }, 67 | { 68 | key: "triggerByHover", 69 | type: "checkbox", 70 | label: getMessage("optionsTriggerByHoverLabel") 71 | }, 72 | { 73 | key: "triggerByClick", 74 | type: "checkbox", 75 | label: getMessage("optionsTriggerByClickLabel") 76 | }, 77 | { 78 | key: "embedImage", 79 | type: "checkbox", 80 | label: getMessage("optionsEmbedImageLabel"), 81 | children: [ 82 | { 83 | key: "embedImageExcludeElement", 84 | type: "textarea", 85 | label: getMessage("optionsEmbedImageExcludeElementLabel"), 86 | validate: validateSelector 87 | } 88 | ] 89 | }, 90 | { 91 | key: "newTab", 92 | type: "checkbox", 93 | label: getMessage("optionsNewTabLabel") 94 | }, 95 | { 96 | key: "excludeElement", 97 | type: "textarea", 98 | label: getMessage("optionsExcludeElementLabel"), 99 | validate: validateSelector 100 | }, 101 | { 102 | key: "includeElement", 103 | type: "textarea", 104 | label: getMessage("optionsIncludeElementLabel"), 105 | validate: validateSelector 106 | }, 107 | { 108 | key: "timeout", 109 | type: "number", 110 | label: getMessage("optionsTimeoutLabel"), 111 | help: getMessage("optionsTimeoutHelp") 112 | }, 113 | { 114 | key: "maxRunTime", 115 | type: "number", 116 | label: getMessage("optionsMaxRunTimeLabel"), 117 | help: getMessage("optionsMaxRunTimeHelp") 118 | }, 119 | ] 120 | }, 121 | ]; 122 | 123 | function validateSelector(value) { 124 | if (value) { 125 | document.documentElement.matches(value); 126 | } 127 | } 128 | }; 129 | -------------------------------------------------------------------------------- /src/lib/pref-default.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return { 3 | fuzzyIp: true, 4 | embedImage: true, 5 | embedImageExcludeElement: ".hljs, .highlight, .brush\\:", 6 | ignoreMustache: false, 7 | unicode: false, 8 | mail: true, 9 | newTab: false, 10 | standalone: false, 11 | boundaryLeft: "{[(\"'", 12 | boundaryRight: "'\")]},.;?!", 13 | excludeElement: ".highlight, .editbox, .brush\\:, .bdsug, .spreadsheetinfo", 14 | includeElement: "", 15 | timeout: 10000, 16 | triggerByPageLoad: false, 17 | triggerByNewNode: false, 18 | triggerByHover: true, 19 | triggerByClick: !supportHover(), 20 | maxRunTime: 100, 21 | customRules: "", 22 | }; 23 | }; 24 | 25 | function supportHover() { 26 | return window.matchMedia("(hover)").matches; 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/triggers/cache.mjs: -------------------------------------------------------------------------------- 1 | export const processedNodes = new WeakSet; 2 | export const nodeValidationCache = new WeakMap; // Node -> boolean 3 | 4 | -------------------------------------------------------------------------------- /src/lib/triggers/click.mjs: -------------------------------------------------------------------------------- 1 | import {linkify} from "linkify-plus-plus-core"; 2 | import {processedNodes} from "./cache.mjs"; 3 | import {validRoot} from "./util.mjs"; 4 | 5 | let options; 6 | 7 | const EVENTS = [ 8 | ["click", handle, {passive: true}], 9 | ] 10 | 11 | function handle(e) { 12 | const el = e.target; 13 | if (validRoot(el, options.validator)) { 14 | processedNodes.add(el); 15 | linkify({...options, root: el, recursive: false}); 16 | } 17 | } 18 | 19 | function enable(_options) { 20 | options = _options; 21 | for (const [event, handler, options] of EVENTS) { 22 | document.addEventListener(event, handler, options); 23 | } 24 | } 25 | 26 | function disable() { 27 | for (const [event, handler, options] of EVENTS) { 28 | document.removeEventListener(event, handler, options); 29 | } 30 | } 31 | 32 | export default { 33 | key: "triggerByClick", 34 | enable, 35 | disable 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/lib/triggers/hover.mjs: -------------------------------------------------------------------------------- 1 | import {linkify} from "linkify-plus-plus-core"; 2 | import {processedNodes} from "./cache.mjs"; 3 | import {validRoot} from "./util.mjs"; 4 | 5 | let options; 6 | 7 | const EVENTS = [ 8 | // catch the first mousemove event since mouseover doesn't fire at page refresh 9 | ["mousemove", handle, {passive: true, once: true}], 10 | ["mouseover", handle, {passive: true}] 11 | ] 12 | 13 | function handle(e) { 14 | const el = e.target; 15 | if (validRoot(el, options.validator)) { 16 | processedNodes.add(el); 17 | linkify({...options, root: el, recursive: false}); 18 | } 19 | } 20 | 21 | function enable(_options) { 22 | options = _options; 23 | for (const [event, handler, options] of EVENTS) { 24 | document.addEventListener(event, handler, options); 25 | } 26 | } 27 | 28 | function disable() { 29 | for (const [event, handler, options] of EVENTS) { 30 | document.removeEventListener(event, handler, options); 31 | } 32 | } 33 | 34 | export default { 35 | key: "triggerByHover", 36 | enable, 37 | disable 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/triggers/index.mjs: -------------------------------------------------------------------------------- 1 | import load from "./load.mjs" 2 | import click from "./click.mjs" 3 | import hover from "./hover.mjs" 4 | import mutation from "./mutation.mjs"; 5 | 6 | export default [load, click, hover, mutation]; 7 | -------------------------------------------------------------------------------- /src/lib/triggers/load.mjs: -------------------------------------------------------------------------------- 1 | import {linkifyRoot, prepareDocument} from "./util.mjs"; 2 | // import {processedNodes} from "./cache.mjs"; 3 | 4 | export default { 5 | key: "triggerByPageLoad", 6 | enable: async options => { 7 | await prepareDocument(); 8 | await linkifyRoot(document.body, options); 9 | }, 10 | disable: () => {} 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/triggers/mutation.mjs: -------------------------------------------------------------------------------- 1 | import {prepareDocument, linkifyRoot} from "./util.mjs"; 2 | import {processedNodes} from "./cache.mjs"; 3 | 4 | const MAX_PROCESSES = 100; 5 | let processes = 0; 6 | let observer; 7 | 8 | async function enable(options) { 9 | await prepareDocument(); 10 | observer = new MutationObserver(function(mutations){ 11 | // Filter out mutations generated by LPP 12 | var lastRecord = mutations[mutations.length - 1], 13 | nodes = lastRecord.addedNodes, 14 | i; 15 | 16 | if (nodes.length >= 2) { 17 | for (i = 0; i < 2; i++) { 18 | if (nodes[i].className == "linkifyplus") { 19 | return; 20 | } 21 | } 22 | } 23 | 24 | for (var record of mutations) { 25 | for (const node of record.addedNodes) { 26 | if (node.nodeType === 1 && !processedNodes.has(node)) { 27 | if (processes >= MAX_PROCESSES) { 28 | throw new Error("Too many processes"); 29 | } 30 | processes++; 31 | linkifyRoot(node, options) 32 | .finally(() => { 33 | processes--; 34 | }); 35 | } 36 | } 37 | } 38 | }); 39 | observer.observe(document.body, { 40 | childList: true, 41 | subtree: true 42 | }); 43 | } 44 | 45 | async function disable() { 46 | await prepareDocument(); 47 | observer && observer.disconnect(); 48 | } 49 | 50 | export default { 51 | key: "triggerByNewNode", 52 | enable, 53 | disable 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/triggers/util.mjs: -------------------------------------------------------------------------------- 1 | import {linkify} from "linkify-plus-plus-core"; 2 | 3 | import { processedNodes, nodeValidationCache } from "./cache.mjs"; 4 | 5 | export async function linkifyRoot(root, options, useIncludeElement = true) { 6 | if (validRoot(root, options.validator)) { 7 | processedNodes.add(root); 8 | await linkify({...options, root, recursive: true}); 9 | } 10 | if (options.includeElement && useIncludeElement) { 11 | for (const el of root.querySelectorAll(options.includeElement)) { 12 | await linkifyRoot(el, options, false); 13 | } 14 | } 15 | } 16 | 17 | export function validRoot(node, validator) { 18 | if (processedNodes.has(node)) { 19 | return false; 20 | } 21 | return getValidation(node); 22 | 23 | function getValidation(p) { 24 | if (!p.parentNode) { 25 | return false; 26 | } 27 | let r = nodeValidationCache.get(p); 28 | if (r === undefined) { 29 | if (validator.isIncluded(p)) { 30 | r = true; 31 | } else if (validator.isExcluded(p)) { 32 | r = false; 33 | } else if (p.parentNode != document.documentElement) { 34 | r = getValidation(p.parentNode); 35 | } else { 36 | r = true; 37 | } 38 | nodeValidationCache.set(p, r); 39 | } 40 | return r; 41 | } 42 | } 43 | 44 | export function prepareDocument() { 45 | // wait till everything is ready 46 | return prepareBody().then(prepareApp); 47 | 48 | function prepareApp() { 49 | const appRoot = document.querySelector("[data-server-rendered]"); 50 | if (!appRoot) { 51 | return; 52 | } 53 | return new Promise(resolve => { 54 | const onChange = () => { 55 | if (!appRoot.hasAttribute("data-server-rendered")) { 56 | resolve(); 57 | observer.disconnect(); 58 | } 59 | }; 60 | const observer = new MutationObserver(onChange); 61 | observer.observe(appRoot, {attributes: true}); 62 | }); 63 | } 64 | 65 | function prepareBody() { 66 | if (document.readyState !== "loading") { 67 | return Promise.resolve(); 68 | } 69 | return new Promise(resolve => { 70 | // https://github.com/Tampermonkey/tampermonkey/issues/485 71 | document.addEventListener("DOMContentLoaded", resolve, {once: true}); 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/static/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "dialogOk": { 3 | "message": "OK" 4 | }, 5 | "dialogCancel": { 6 | "message": "Cancel" 7 | }, 8 | "extensionOptions": { 9 | "message": "Linkify Plus Plus Options" 10 | }, 11 | "extensionDescription": { 12 | "message": "Based on Linkify Plus. Turn plain text URLs into links." 13 | }, 14 | "optionsFuzzyIpLabel": { 15 | "message": "Match ambiguous IP addresses." 16 | }, 17 | "optionsIgnoreMustacheLabel": { 18 | "message": "Ignore URLs inside mustaches e.g. {{ ... }}." 19 | }, 20 | "optionsEmbedImageLabel": { 21 | "message": "Create an image element if the URL looks like an image file." 22 | }, 23 | "optionsEmbedImageExcludeElementLabel": { 24 | "message": "Exclude following elements. (CSS selector)" 25 | }, 26 | "optionsUnicodeLabel": { 27 | "message": "Match unicode characters." 28 | }, 29 | "optionsMailLabel": { 30 | "message": "Match email address." 31 | }, 32 | "optionsNewTabLabel": { 33 | "message": "Open links in new tabs." 34 | }, 35 | "optionsStandaloneLabel": { 36 | "message": "The URL must be surrounded by whitespaces." 37 | }, 38 | "optionsLinkifierLabel": { 39 | "message": "Linkifier" 40 | }, 41 | "optionsTriggerByPageLoadLabel": { 42 | "message": "Trigger on page load." 43 | }, 44 | "optionsTriggerByNewNodeLabel": { 45 | "message": "Trigger on dynamically created elements." 46 | }, 47 | "optionsTriggerByHoverLabel": { 48 | "message": "Trigger on mouse over." 49 | }, 50 | "optionsTriggerByClickLabel": { 51 | "message": "Trigger on mouse click." 52 | }, 53 | "optionsBoundaryLeftLabel": { 54 | "message": "Allowed characters between the whitespace and the link. (left side)" 55 | }, 56 | "optionsBoundaryRightLabel": { 57 | "message": "Allowed characters between the whitespace and the link. (right side)" 58 | }, 59 | "optionsExcludeElementLabel": { 60 | "message": "Do not linkify following elements. (CSS selector)" 61 | }, 62 | "optionsIncludeElementLabel": { 63 | "message": "Always linkify following elements. Override above. (CSS selector)" 64 | }, 65 | "optionsTimeoutLabel": { 66 | "message": "Max execution time. (ms)" 67 | }, 68 | "optionsTimeoutHelp": { 69 | "message": "The script will terminate if it takes too long to convert the entire page." 70 | }, 71 | "optionsMaxRunTimeLabel": { 72 | "message": "Max script run time. (ms)" 73 | }, 74 | "optionsMaxRunTimeHelp": { 75 | "message": "If the script takes too long to run, the process would be splitted into small chunks to avoid browser freeze." 76 | }, 77 | "optionsUrlMatcherLabel": { 78 | "message": "URL matcher" 79 | }, 80 | "optionsCustomRulesLabel": { 81 | "message": "Custom rules. (RegExp per line)" 82 | }, 83 | "prefCurrentScopeLabel": { 84 | "message": "Current domain" 85 | }, 86 | "prefAddScopeLabel": { 87 | "message": "Add new domain" 88 | }, 89 | "prefAddScopePrompt": { 90 | "message": "Add new domain" 91 | }, 92 | "prefDeleteScopeLabel": { 93 | "message": "Delete current domain" 94 | }, 95 | "prefDeleteScopeConfirm": { 96 | "message": "Delete domain $DOMAIN$?", 97 | "placeholders": { 98 | "domain": { 99 | "content": "$1", 100 | "example": "www.example.com" 101 | } 102 | } 103 | }, 104 | "prefLearnMoreButton": { 105 | "message": "Learn more" 106 | }, 107 | "prefImportButton": { 108 | "message": "Import" 109 | }, 110 | "prefImportPrompt": { 111 | "message": "Paste settings" 112 | }, 113 | "prefExportButton": { 114 | "message": "Export" 115 | }, 116 | "prefExportPrompt": { 117 | "message": "Copy settings" 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/static/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "dialogOk": { 3 | "message": "Oké" 4 | }, 5 | "dialogCancel": { 6 | "message": "Annuleren" 7 | }, 8 | "extensionOptions": { 9 | "message": "Linkify Plus Plus-instellingen" 10 | }, 11 | "extensionDescription": { 12 | "message": "Gebaseerd op Linkify Plus. Zet platte tekst-url's om in links." 13 | }, 14 | "optionsFuzzyIpLabel": { 15 | "message": "IP-adressen met 4 tekens omzetten" 16 | }, 17 | "optionsIgnoreMustacheLabel": { 18 | "message": "URL's binnen accolades, {{ … }}, negeren" 19 | }, 20 | "optionsEmbedImageLabel": { 21 | "message": "Afbeeldingen insluiten" 22 | }, 23 | "optionsEmbedImageExcludeElementLabel": { 24 | "message": "Elementen uitsluiten (css-selectie):" 25 | }, 26 | "optionsUnicodeLabel": { 27 | "message": "Unicode-tekens omzetten" 28 | }, 29 | "optionsMailLabel": { 30 | "message": "E-mailadressen omzetten" 31 | }, 32 | "optionsNewTabLabel": { 33 | "message": "Links openen op nieuwe tabbladen" 34 | }, 35 | "optionsStandaloneLabel": { 36 | "message": "Alleen links met witruimte rondom omzetten" 37 | }, 38 | "optionsBoundaryLeftLabel": { 39 | "message": "Toegestane tekens tussen witruimtes en links (linkerzijde)" 40 | }, 41 | "optionsBoundaryRightLabel": { 42 | "message": "Toegestane tekens tussen witruimtes en links (rechterzijde)" 43 | }, 44 | "optionsExcludeElementLabel": { 45 | "message": "Elementen uitsluiten (css-selectie):" 46 | }, 47 | "optionsIncludeElementLabel": { 48 | "message": "Elementen die bovenstaande negeren (css-selectie):" 49 | }, 50 | "optionsTimeoutLabel": { 51 | "message": "Max. uitvoertijd (in ms):" 52 | }, 53 | "optionsTimeoutHelp": { 54 | "message": "Het script wordt onderbroken als het te lang duurt om alle links op de gehele pagina om te zetten." 55 | }, 56 | "optionsMaxRunTimeLabel": { 57 | "message": "Max. scriptuitvoertijd (in ms)" 58 | }, 59 | "optionsMaxRunTimeHelp": { 60 | "message": "Verdeel het proces in kleinere stukken om te voorkomen dat de browser vastloopt." 61 | }, 62 | "optionsCustomRulesLabel": { 63 | "message": "Eigen regels (één reguliere uitdrukking per regel):" 64 | }, 65 | "prefCurrentScopeLabel": { 66 | "message": "Huidig domein" 67 | }, 68 | "prefAddScopeLabel": { 69 | "message": "Domein toevoegen" 70 | }, 71 | "prefAddScopePrompt": { 72 | "message": "Domein toevoegen" 73 | }, 74 | "prefDeleteScopeLabel": { 75 | "message": "Huidig domein verwijderen" 76 | }, 77 | "prefDeleteScopeConfirm": { 78 | "message": "Weet je zeker dat je $DOMAIN$ wilt verwijderen?", 79 | "placeholders": { 80 | "domain": { 81 | "content": "$1", 82 | "example": "www.voorbeeld.nl" 83 | } 84 | } 85 | }, 86 | "prefLearnMoreButton": { 87 | "message": "Meer informatie" 88 | }, 89 | "prefImportButton": { 90 | "message": "Importeren" 91 | }, 92 | "prefImportPrompt": { 93 | "message": "Instellingen plakken" 94 | }, 95 | "prefExportButton": { 96 | "message": "Exporteren" 97 | }, 98 | "prefExportPrompt": { 99 | "message": "Instellingen kopiëren" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/static/css/dialog.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 16px; 3 | } 4 | textarea, button { 5 | font-size: inherit; 6 | } 7 | -------------------------------------------------------------------------------- /src/static/css/options.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 6px; 3 | } 4 | 5 | button.browser-style, 6 | select.browser-style { 7 | font-size: inherit; 8 | height: auto; 9 | padding: 4px 8px; 10 | } 11 | 12 | /* root */ 13 | .pref-root { 14 | font-size: 15px; 15 | line-height: 1.5; 16 | } 17 | 18 | /* toolbar */ 19 | .webext-pref-toolbar { 20 | display: flex; 21 | justify-content: center; 22 | margin-bottom: 16px; 23 | } 24 | .webext-pref-toolbar button { 25 | min-width: 8em; 26 | text-align: center; 27 | } 28 | .webext-pref-toolbar button:not(:first-child) { 29 | margin-left: 8px; 30 | } 31 | 32 | /* nav */ 33 | .webext-pref-nav { 34 | display: flex; 35 | margin-bottom: 12px; 36 | } 37 | .webext-pref-nav select { 38 | flex-grow: 1; 39 | } 40 | .webext-pref-nav select, 41 | .webext-pref-nav button { 42 | min-width: 30px; 43 | text-align: center; 44 | text-align-last: center; 45 | } 46 | .webext-pref-nav button { 47 | margin-left: 8px; 48 | } 49 | 50 | /* checkbox */ 51 | .webext-pref-checkbox { 52 | margin: 8px 0; 53 | padding-left: 24px; 54 | } 55 | .webext-pref-checkbox::before { 56 | content: ""; 57 | margin-left: -24px; 58 | } 59 | .webext-pref-checkbox > input { 60 | font: inherit; 61 | width: 1em; 62 | height: 1em; 63 | margin: 0 calc(24px - 1em) 0 0; 64 | vertical-align: middle; 65 | } 66 | .webext-pref-checkbox > label { 67 | vertical-align: middle; 68 | } 69 | .webext-pref-checkbox-children { 70 | margin: 6px 0; 71 | padding: 0; 72 | border-width: 0; 73 | } 74 | .webext-pref-checkbox-children[disabled] { 75 | opacity: 0.5; 76 | } 77 | .webext-pref-checkbox-children > :first-child { 78 | margin-top: 0; 79 | } 80 | .webext-pref-checkbox-children > :last-child { 81 | margin-bottom: 0; 82 | } 83 | .webext-pref-checkbox-children > :last-child > :last-child { 84 | margin-bottom: 0; 85 | } 86 | 87 | /* text */ 88 | .webext-pref-text, 89 | .webext-pref-number, 90 | .webext-pref-textarea { 91 | margin: 8px 0; 92 | } 93 | .webext-pref-text input, 94 | .webext-pref-number input, 95 | .webext-pref-textarea textarea { 96 | padding: 3px 6px; 97 | display: block; 98 | width: 100%; 99 | font: inherit; 100 | box-sizing: border-box; 101 | } 102 | .webext-pref-textarea textarea { 103 | height: 6em; 104 | } 105 | 106 | /* help */ 107 | .webext-pref-help { 108 | margin: 4px 0 6px; 109 | color: #737373; 110 | } 111 | -------------------------------------------------------------------------------- /src/static/dialog.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |
11 |