├── README.md
├── background.js
├── content-script.js
├── default_options.js
├── image
├── action.png
└── logo.png
├── manifest.json
├── options
├── index.css
├── index.html
└── index.js
└── urlpattern-polyfill.min.js
/README.md:
--------------------------------------------------------------------------------
1 | # auto-dev-favicon-chrome-plugin
2 |
3 | 不知道大家有没有碰到这样的问题:很多时候一个项目会存在多个环境,当浏览器标签页比较多的时候就完全分不清了
4 |
5 | 
6 |
7 | 其实这里是有3个开发环境的
8 |
9 | 
10 |
11 | 但是单独从 favicon 是没法快速区分哪个跟哪个的,为此,我做了一个 Chrome 插件可以很方便的解决这个问题,效果如下
12 |
13 | 
14 |
15 | 是不是非常清晰呢?
16 |
17 | ## 安装和使用
18 |
19 | 在 [Chrome 网上商店](https://chrome.google.com/webstore/category/extensions) 直接搜索 "auto dev favicon",或者直接访问这个链接 [https://chrome.google.com/webstore/detail/auto-dev-favicon/obgfnmomampmgjefiodpcknepcecgijg](https://chrome.google.com/webstore/detail/auto-dev-favicon/obgfnmomampmgjefiodpcknepcecgijg),如下
20 |
21 | 
22 |
23 | 成功安装后,需要进入到**配置页**,也就是可以自定义匹配域名的页面,有 3 个入口
24 |
25 | 1. 直接点击右上角插件图标(推荐)
26 |
27 | 2. 右键右上角插件图标,点击“选项”
28 |
29 | 3. 进入插件详情页,点击“扩展程序选项”
30 |
31 | 
32 |
33 | 下面就是一个简单的配置页面
34 |
35 | 
36 |
37 | 这里简单说明一下
38 |
39 | 1. **颜色**是指小标签的背景色,这里预置了 8 种颜色可供选择
40 | 1. **名称**是指小标签的文本内容,由于宽度有限,最多支持两个中文字符或3个英文字符
41 | 1. **匹配**是指域名匹配,这里仅匹配 hostname,匹配规则是“模式匹配”,用英文逗号分隔可以填写多个,比如这里的`dev*`表示匹配所有以`dev`开头的域名,具体规则可参考 [URL_Pattern_API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API#examples)
42 | 1. 操作完成之后记得点击一下保存,会自动同步到 Chrome 账号
43 |
44 | ## 其他说明
45 |
46 | 如果由于网络环境暂不可访问应用商店,可以在 github 获取源文件,通过开发者模式安装即可
47 |
48 | > [https://github.com/XboxYan/auto-dev-favicon-chrome-plugin](https://github.com/XboxYan/auto-dev-favicon-chrome-plugin)
49 |
50 | Edge 应用商店正在审核当中,请耐心等待
51 |
52 | 有任何问题或者意见可以提 [issue](https://github.com/XboxYan/auto-dev-favicon-chrome-plugin/issues) 或者与我联系:yanwenbin1991@live.com
53 |
54 | > Enjoy!
--------------------------------------------------------------------------------
/background.js:
--------------------------------------------------------------------------------
1 | chrome.action.onClicked.addListener(setOptions);
2 |
3 | function setOptions(tab) {
4 | chrome.tabs.create({
5 | url: chrome.runtime.getURL('options/index.html'),
6 | index: tab.index + 1
7 | });
8 | chrome.runtime.openOptionsPage();
9 | }
10 |
11 | async function img2Base64(url, cb) {
12 | try {
13 | const imgblob = await fetch(url).then(res => res.blob())
14 | const reader = new FileReader();
15 | reader.readAsDataURL(imgblob)
16 | reader.addEventListener("load", function () {
17 | cb(reader.result)
18 | }, false);
19 | } catch (error) {
20 | cb("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzODQgNTEyIj4gPHBhdGggZmlsbD0iIzRFNTA1NSIgZD0iTTIyNCAxMzZWMEgyNEMxMC43IDAgMCAxMC43IDAgMjR2NDY0YzAgMTMuMyAxMC43IDI0IDI0IDI0aDMzNmMxMy4zIDAgMjQtMTAuNyAyNC0yNFYxNjBIMjQ4Yy0xMy4yIDAtMjQtMTAuOC0yNC0yNHptMTYwLTE0LjF2Ni4xSDI1NlYwaDYuMWM2LjQgMCAxMi41IDIuNSAxNyA3bDk3LjkgOThjNC41IDQuNSA3IDEwLjYgNyAxNi45eiI+PC9wYXRoPiA8L3N2Zz4=");
21 | }
22 | }
23 |
24 | // 监听来自content-script的消息
25 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) =>{
26 | if (request.cmd === 'img2Base64') {
27 | img2Base64(request.url, sendResponse);
28 | return true
29 | }
30 | });
--------------------------------------------------------------------------------
/content-script.js:
--------------------------------------------------------------------------------
1 | function img2Base64(url) {
2 | return new Promise(resolve => {
3 | try {
4 | chrome.runtime.sendMessage({
5 | cmd: 'img2Base64',
6 | url: url
7 | }, (response) => {
8 | resolve(response)
9 | });
10 | } catch (error) {
11 | console.log(error)
12 | }
13 | })
14 | }
15 |
16 | function getLink(){
17 | const link = document.querySelector('link[rel~="icon"]');
18 | if (link) {
19 | return link
20 | } else {
21 | const link = document.createElement('link');
22 | link.rel = "icon";
23 | link.href = "/favicon.ico"
24 | document.head.append(link);
25 | return link
26 | }
27 | }
28 | async function setFavicon(env, color) {
29 | const link = getLink();
30 | const icon = await img2Base64(link.href);
31 | const favicon = `data:image/svg+xml;charset=utf-8,`.replace(/\n/g, '').replace(/\t/g, '').replace(/#/g, '%23')
67 | link.href= favicon;
68 | }
69 |
70 | // console.log('init')
71 |
72 | window.onload = () => {
73 | console.log('onload')
74 | chrome.storage.sync.get('options', ({options=default_options}) => {
75 | let isMatch = false;
76 | options.forEach(option => {
77 | option.matches.forEach(reg => {
78 | const pattern = new URLPattern({
79 | hostname: reg
80 | })
81 | if (pattern.test(location.href) && !isMatch) {
82 | isMatch = true;
83 | // console.log(option)
84 | setFavicon(option.name, option.color);
85 | }
86 | })
87 | })
88 | })
89 | }
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/default_options.js:
--------------------------------------------------------------------------------
1 | const colors = [
2 | '#ff9800',
3 | '#9c27b0',
4 | '#009688',
5 | '#f44336',
6 | '#3f51b5',
7 | '#2196f3',
8 | '#00bcd4',
9 | '#795548'
10 | ]
11 |
12 | const default_options = [
13 | {
14 | name: 'local',
15 | color: colors[0],
16 | matches: [
17 | 'localhost',
18 | '127.0.0.1'
19 | ]
20 | },
21 | {
22 | name: 'dev',
23 | color: colors[1],
24 | matches: [
25 | 'dev*',
26 | ]
27 | },
28 | {
29 | name: 'oa',
30 | color: colors[2],
31 | matches: [
32 | 'oa*',
33 | ]
34 | }
35 | ]
--------------------------------------------------------------------------------
/image/action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XboxYan/auto-dev-favicon-chrome-plugin/4a7c640eca7c010d1ca621c085ead5b033cd4846/image/action.png
--------------------------------------------------------------------------------
/image/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XboxYan/auto-dev-favicon-chrome-plugin/4a7c640eca7c010d1ca621c085ead5b033cd4846/image/logo.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto dev favicon",
3 | "description": "为不同的开发环境自动切换favicon",
4 | "version": "1.0.1",
5 | "manifest_version": 3,
6 | "permissions": [
7 | "storage",
8 | "activeTab",
9 | "scripting"
10 | ],
11 | "background": {
12 | "service_worker": "background.js"
13 | },
14 | "content_scripts": [
15 | {
16 | "matches": ["*://*/*"],
17 | "js": ["urlpattern-polyfill.min.js","default_options.js","content-script.js"],
18 | "exclude_matches": ["*://developer.mozilla.org/*", "*://developer.chrome.com/*"],
19 | "run_at": "document_end"
20 | }
21 | ],
22 | "action": {
23 | "default_title": "点击打开配置页",
24 | "default_icon": {
25 | "16": "/image/action.png",
26 | "32": "/image/action.png",
27 | "48": "/image/action.png",
28 | "128": "/image/action.png"
29 | }
30 | },
31 | "options_page": "options/index.html",
32 | "icons": {
33 | "16": "/image/logo.png",
34 | "32": "/image/logo.png",
35 | "48": "/image/logo.png",
36 | "128": "/image/logo.png"
37 | },
38 | "homepage_url": "https://github.com/XboxYan/auto-dev-favicon-chrome-plugin"
39 | }
40 |
--------------------------------------------------------------------------------
/options/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
4 |
5 | .title {
6 | font-size: 350%;
7 | margin: 1em 0;
8 | font-weight: lighter;
9 | }
10 |
11 | .title::first-letter {
12 | color: #B09AFE;
13 | }
14 |
15 | .config {
16 | width: 800px;
17 | margin: 0 auto;
18 | }
19 |
20 | .items {
21 | position: relative;
22 | display: flex;
23 | font-size: 20px;
24 | gap: 1em;
25 | margin-top: 1em;
26 | text-align: center;
27 | font-weight: 200;
28 | }
29 |
30 | .items::before {
31 | content: '';
32 | position: absolute;
33 | left: 0;
34 | right: -100px;
35 | top: 0;
36 | bottom: 0;
37 | z-index: 0;
38 | }
39 |
40 | .items-header {
41 | font-size: 24px;
42 | text-decoration: underline .5em #ff9800;
43 | text-underline-offset: -.4em;
44 | }
45 |
46 | .items-name,
47 | .items-color,
48 | .items-preview {
49 | width: 100px;
50 | flex-shrink: 0;
51 | position: relative;
52 | }
53 |
54 | .del {
55 | position: absolute;
56 | color: #f44336;
57 | cursor: pointer;
58 | font-size: 20px;
59 | line-height: 42px;
60 | left: 100%;
61 | white-space: nowrap;
62 | visibility: hidden;
63 | }
64 |
65 | .items:hover .del {
66 | visibility: visible;
67 | }
68 |
69 | .del:hover {
70 | text-decoration: underline;
71 | }
72 |
73 | .input {
74 | box-sizing: border-box;
75 | font-size: inherit;
76 | border: 2px solid #eee;
77 | border-radius: 4px;
78 | outline: 0;
79 | transition: .2s;
80 | line-height: 2;
81 | padding: 0 .5em;
82 | width: 100%;
83 | min-width: 0;
84 | text-align: left;
85 | word-break: break-all;
86 | font-family: 'Courier New', Courier, monospace;
87 | -webkit-user-modify: read-write-plaintext-only;
88 | }
89 |
90 | .input[invalid] {
91 | border-color: #f44336;
92 | }
93 |
94 | .input:not([invalid]):focus {
95 | border-color: royalblue;
96 | }
97 |
98 | ::-webkit-input-placeholder {
99 | color: rgb(200, 200, 200);
100 | }
101 |
102 | .input:empty::before {
103 | content: attr(placeholder);
104 | color: rgb(200, 200, 200);
105 | }
106 |
107 | .color-current {
108 | width: 30px;
109 | height: 30px;
110 | background-color: var(--color, #f44336);
111 | border-radius: 50%;
112 | margin: 7px auto;
113 | position: relative;
114 | cursor: pointer;
115 | }
116 |
117 | .color-current:focus,
118 | .color-pane:focus {
119 | outline: 8px solid #fff;
120 | outline-offset: -20px;
121 | }
122 |
123 | .color-current:focus-within .color-list {
124 | opacity: 1;
125 | visibility: visible;
126 | }
127 |
128 | .color-list {
129 | position: absolute;
130 | top: 100%;
131 | margin-top: .5em;
132 | left: -.5em;
133 | display: flex;
134 | gap: .5em;
135 | background-color: #fff;
136 | border-radius: 4px;
137 | padding: .5em;
138 | box-shadow: 5px 5px 10px rgb(0 0 0 / 10%);
139 | opacity: 0;
140 | visibility: hidden;
141 | z-index: 2;
142 | transition: .2s;
143 | cursor: default;
144 | }
145 |
146 | .color-pane {
147 | width: 30px;
148 | height: 30px;
149 | background-color: var(--color);
150 | border-radius: 50%;
151 | cursor: pointer;
152 | border: 0;
153 | }
154 |
155 | .list {
156 | padding-bottom: 100px;
157 | }
158 |
159 | .list:empty::before {
160 | content: '请添加规则';
161 | display: flex;
162 | padding: 10rem 0;
163 | font-size: 300%;
164 | justify-content: center;
165 | font-weight: lighter;
166 | }
167 |
168 | .items-match {
169 | flex: 1;
170 | position: relative;
171 | z-index: 1;
172 | }
173 |
174 | .list:empty+.action .save {
175 | display: none;
176 | }
177 |
178 | .input-tags {
179 | display: flex;
180 | gap: .5em;
181 | padding: .1em 0;
182 | }
183 |
184 | .icon {
185 | position: relative;
186 | width: 32px;
187 | height: 32px;
188 | margin: 5px auto;
189 | background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzODQgNTEyIj4gPHBhdGggZmlsbD0iIzRFNTA1NSIgZD0iTTIyNCAxMzZWMEgyNEMxMC43IDAgMCAxMC43IDAgMjR2NDY0YzAgMTMuMyAxMC43IDI0IDI0IDI0aDMzNmMxMy4zIDAgMjQtMTAuNyAyNC0yNFYxNjBIMjQ4Yy0xMy4yIDAtMjQtMTAuOC0yNC0yNHptMTYwLTE0LjF2Ni4xSDI1NlYwaDYuMWM2LjQgMCAxMi41IDIuNSAxNyA3bDk3LjkgOThjNC41IDQuNSA3IDEwLjYgNyAxNi45eiI+PC9wYXRoPiA8L3N2Zz4=") center no-repeat;
190 | background-size: auto 100%;
191 | }
192 |
193 | .env {
194 | position: absolute;
195 | bottom: 0;
196 | left: 50%;
197 | transform: translateX(-50%);
198 | text-transform: uppercase;
199 | background-color: var(--color, #f44336);
200 | color: #fff;
201 | padding: 0px 2px;
202 | border-radius: 2px;
203 | font-size: 12px;
204 | box-sizing: border-box;
205 | max-width: 100%;
206 | width: max-content;
207 | height: 16px;
208 | line-height: 16px;
209 | word-break: break-all;
210 | overflow: hidden;
211 | }
212 |
213 | .env:empty::after {
214 | content: 'dev';
215 | }
216 |
217 | .action {
218 | display: flex;
219 | position: fixed;
220 | bottom: 0;
221 | padding: 2em 0;
222 | gap: 1rem;
223 | }
224 |
225 | .button {
226 | width: 60px;
227 | height: 60px;
228 | border-radius: 50%;
229 | border: 0;
230 | outline: 0;
231 | background-color: royalblue;
232 | background-size: 40%;
233 | background-position: center;
234 | background-repeat: no-repeat;
235 | cursor: pointer;
236 | }
237 |
238 | .button:active {
239 | opacity: .85;
240 | }
241 |
242 | .add {
243 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E %3Cpath fill='%23fff' d='M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z'%3E%3C/path%3E %3C/svg%3E");
244 | }
245 |
246 | .save {
247 | background-color: #ff9800;
248 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'%3E %3Cpath fill='%23fff' d='M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM272 80v80H144V80h128zm122 352H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h42v104c0 13.255 10.745 24 24 24h176c13.255 0 24-10.745 24-24V83.882l78.243 78.243a6 6 0 0 1 1.757 4.243V426a6 6 0 0 1-6 6zM224 232c-48.523 0-88 39.477-88 88s39.477 88 88 88 88-39.477 88-88-39.477-88-88-88zm0 128c-22.056 0-40-17.944-40-40s17.944-40 40-40 40 17.944 40 40-17.944 40-40 40z'%3E%3C/path%3E %3C/svg%3E");
249 | }
250 |
251 | .messageInfo {
252 | position: absolute;
253 | top: 30px;
254 | left: 0;
255 | right: 0;
256 | text-align: center;
257 | font-size: 2rem;
258 | color: #fff;
259 | transition: .3s;
260 | transform: translateY(-100%);
261 | opacity: 0;
262 | }
263 |
264 | .messageInfo span {
265 | display: inline-block;
266 | background: var(--color, #009688);
267 | border-radius: 4px;
268 | padding: 10px 30px;
269 | border-radius: 100px;
270 | font-weight: lighter;
271 | }
272 |
273 | .messageInfo.show {
274 | opacity: 1;
275 | visibility: visible;
276 | transform: translateY(0);
277 | z-index: 9;
278 | }
279 |
280 | .item-check{
281 | display: block;
282 | appearance: none;
283 | width: 30px;
284 | height: 30px;
285 | border: 2px solid #eee;
286 | border-radius: 4px;
287 | margin: 7px auto;
288 | cursor: pointer;
289 | transition: .2s;
290 | }
291 |
292 | .item-check:checked{
293 | border-color: royalblue;
294 | background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23fff' d='M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z'%3E%3C/path%3E %3C/svg%3E") center / 60% no-repeat royalblue;
295 | }
--------------------------------------------------------------------------------
/options/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | options
8 |
9 |
10 |
11 |
12 |
13 |
Options
14 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/options/index.js:
--------------------------------------------------------------------------------
1 | const list = document.getElementById('list');
2 | const add = document.getElementById('add');
3 |
4 | function showMessage(txt, color='#009688'){
5 | this.timer && clearTimeout(this.timer);
6 | var oDiv = document.getElementById('messageInfo');
7 | if(!oDiv){
8 | oDiv = document.createElement('div');
9 | oDiv.className = 'messageInfo';
10 | oDiv.id = 'messageInfo';
11 | document.body.appendChild(oDiv);
12 | }
13 | oDiv.innerHTML = ''+txt+'';
14 | oDiv.offsetWidth;
15 | oDiv.style.setProperty('--color', color);
16 | oDiv.classList.add('show');
17 | this.timer = setTimeout(function(){
18 | oDiv.classList.remove('show');
19 | },2000)
20 | }
21 |
22 | const temp = (el={}) => `
23 |
24 |
25 | ${
26 | colors.map(color => '').join('')
27 | }
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
${el.matches||''}
36 |
37 |
38 |
39 | ${el.name||''}
40 |
41 |
42 | 移除`
43 |
44 | chrome.storage.sync.get('options', ({options=default_options}) => {
45 | list.innerHTML = options.map(el => `
46 | ${temp(el)}
47 |
`).join('')
48 | })
49 |
50 | list.addEventListener('click', function(ev){
51 | if (ev.target.className === 'color-pane') {
52 | const item = ev.target.closest('.items');
53 | item.style.setProperty('--color', ev.target.style.getPropertyValue('--color'))
54 | }
55 | if (ev.target.className === 'del') {
56 | const item = ev.target.closest('.items');
57 | item.remove();
58 | }
59 | })
60 |
61 | list.addEventListener('input', function(ev){
62 | if (ev.target.dataset.type === 'name') {
63 | ev.target.parentNode.parentNode.querySelector('.env').innerText = ev.target.value;
64 | }
65 | ev.target.toggleAttribute('invalid', false);
66 | })
67 |
68 | add.addEventListener('click', function(){
69 | const items = document.createElement('div');
70 | items.className = 'items';
71 | items.style.setProperty('--color', colors[list.childElementCount%colors.length])
72 | items.innerHTML = temp({
73 | name: 'e'+list.childElementCount
74 | });
75 | list.appendChild(items)
76 | })
77 |
78 | save.addEventListener('click', function(){
79 | const invalidElAll = [...document.querySelectorAll('input.input:invalid, div.input:empty')];
80 | if (invalidElAll.length) {
81 | invalidElAll.forEach(el => {
82 | el.toggleAttribute('invalid', true)
83 | })
84 | invalidElAll[0].focus();
85 | showMessage('⚠️ 内容不能为空', '#f44336')
86 | } else {
87 | const items = [...list.querySelectorAll('.items')];
88 | const options = items.map(item => {
89 | const color = item.style.getPropertyValue('--color').trim();
90 | const name = item.querySelector('input.input').value;
91 | const matches = item.querySelector('div.input').innerText.split(',').map(el => el.trim());
92 | return {color, name, matches}
93 | })
94 | chrome.storage.sync.set({options}, () => {
95 | console.log(options)
96 | showMessage('🎉 保存成功~')
97 | })
98 | }
99 | })
--------------------------------------------------------------------------------
/urlpattern-polyfill.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Minified by jsDelivr using Terser v5.10.0.
3 | * Original file: /npm/urlpattern-polyfill@4.0.3/dist/index.js
4 | */
5 | var regexIdentifierStart=/[$_\p{ID_Start}]/u,regexIdentifierPart=/[$_\u200C\u200D\p{ID_Continue}]/u;function isASCII(t,e){return(e?/^[\x00-\xFF]*$/:/^[\x00-\x7F]*$/).test(t)}function lexer(t,e=!1){const n=[];let r=0;for(;r{if(o{const t=p("MODIFIER");return t||p("ASTERISK")},u=t=>{const e=p(t);if(void 0!==e)return e;const{type:r,index:s}=n[o];throw new TypeError(`Unexpected ${r} at ${s}, expected ${t}`)},f=()=>{let t,e="";for(;t=p("CHAR")||p("ESCAPED_CHAR");)e+=t;return e},m=e.encodePart||(t=>t);for(;o)?(?!\?)/g;let r=0,s=n.exec(t.source);for(;s;)e.push({name:s[1]||r++,prefix:"",suffix:"",modifier:"",pattern:""}),s=n.exec(t.source);return t}function arrayToRegexp(t,e,n){const r=t.map((t=>pathToRegexp(t,e,n).source));return new RegExp(`(?:${r.join("|")})`,flags(n))}function stringToRegexp(t,e,n){return tokensToRegexp(parse(t,n),e,n)}function tokensToRegexp(t,e,n={}){const{strict:r=!1,start:s=!0,end:a=!0,encode:i=(t=>t)}=n,o=`[${escapeString(n.endsWith||"")}]|$`,h=`[${escapeString(n.delimiter||"/#?")}]`;let c=s?"^":"";for(const n of t)if("string"==typeof n)c+=escapeString(i(n));else{const t=escapeString(i(n.prefix)),r=escapeString(i(n.suffix));if(n.pattern)if(e&&e.push(n),t||r)if("+"===n.modifier||"*"===n.modifier){const e="*"===n.modifier?"?":"";c+=`(?:${t}((?:${n.pattern})(?:${r}${t}(?:${n.pattern}))*)${r})${e}`}else c+=`(?:${t}(${n.pattern})${r})${n.modifier}`;else"+"===n.modifier||"*"===n.modifier?c+=`((?:${n.pattern})${n.modifier})`:c+=`(${n.pattern})${n.modifier}`;else c+=`(?:${t}${r})${n.modifier}`}if(a)r||(c+=`${h}?`),c+=n.endsWith?`(?=${o})`:"$";else{const e=t[t.length-1],n="string"==typeof e?h.indexOf(e[e.length-1])>-1:void 0===e;r||(c+=`(?:${h}(?=${o}))?`),n||(c+=`(?=${h}|${o})`)}return new RegExp(c,flags(n))}function pathToRegexp(t,e,n){return t instanceof RegExp?regexpToRegexp(t,e):Array.isArray(t)?arrayToRegexp(t,e,n):stringToRegexp(t,e,n)}var DEFAULT_OPTIONS={delimiter:"",prefixes:"",sensitive:!0,strict:!0},HOSTNAME_OPTIONS={delimiter:".",prefixes:"",sensitive:!0,strict:!0},PATHNAME_OPTIONS={delimiter:"/",prefixes:"/",sensitive:!0,strict:!0};function isAbsolutePathname(t,e){return!!t.length&&("/"===t[0]||!!e&&(!(t.length<2)&&(("\\"==t[0]||"{"==t[0])&&"/"==t[1])))}function maybeStripPrefix(t,e){return t.startsWith(e)?t.substring(e.length,t.length):t}function maybeStripSuffix(t,e){return t.endsWith(e)?t.substr(0,t.length-e.length):t}function treatAsIPv6Hostname(t){return!(!t||t.length<2)&&("["===t[0]||("\\"===t[0]||"{"===t[0])&&"["===t[1])}var SPECIAL_SCHEMES=["ftp","file","http","https","ws","wss"];function isSpecialScheme(t){if(!t)return!0;for(const e of SPECIAL_SCHEMES)if(t.test(e))return!0;return!1}function canonicalizeHash(t,e){if(t=maybeStripPrefix(t,"#"),e||""===t)return t;const n=new URL("https://example.com");return n.hash=t,n.hash?n.hash.substring(1,n.hash.length):""}function canonicalizeSearch(t,e){if(t=maybeStripPrefix(t,"?"),e||""===t)return t;const n=new URL("https://example.com");return n.search=t,n.search?n.search.substring(1,n.search.length):""}function canonicalizeHostname(t,e){return e||""===t?t:treatAsIPv6Hostname(t)?ipv6HostnameEncodeCallback(t):hostnameEncodeCallback(t)}function canonicalizePassword(t,e){if(e||""===t)return t;const n=new URL("https://example.com");return n.password=t,n.password}function canonicalizeUsername(t,e){if(e||""===t)return t;const n=new URL("https://example.com");return n.username=t,n.username}function canonicalizePathname(t,e,n){if(n||""===t)return t;if(e&&!SPECIAL_SCHEMES.includes(e)){return new URL(`${e}:${t}`).pathname}const r="/"==t[0];return t=new URL(r?t:"/-"+t,"https://example.com").pathname,r||(t=t.substring(2,t.length)),t}function canonicalizePort(t,e,n){return defaultPortForProtocol(e)===t&&(t=""),n||""===t?t:portEncodeCallback(t)}function canonicalizeProtocol(t,e){return t=maybeStripSuffix(t,":"),e||""===t?t:protocolEncodeCallback(t)}function defaultPortForProtocol(t){switch(t){case"ws":case"http":return"80";case"wws":case"https":return"443";case"ftp":return"21";default:return""}}function protocolEncodeCallback(t){if(""===t)return t;if(/^[-+.A-Za-z0-9]*$/.test(t))return t.toLowerCase();throw new TypeError(`Invalid protocol '${t}'.`)}function usernameEncodeCallback(t){if(""===t)return t;const e=new URL("https://example.com");return e.username=t,e.username}function passwordEncodeCallback(t){if(""===t)return t;const e=new URL("https://example.com");return e.password=t,e.password}function hostnameEncodeCallback(t){if(""===t)return t;if(/[\t\n\r #%/:<>?@[\]^\\|]/g.test(t))throw new TypeError(`Invalid hostname '${t}'`);const e=new URL("https://example.com");return e.hostname=t,e.hostname}function ipv6HostnameEncodeCallback(t){if(""===t)return t;if(/[^0-9a-fA-F[\]:]/g.test(t))throw new TypeError(`Invalid IPv6 hostname '${t}'`);return t.toLowerCase()}function portEncodeCallback(t){if(""===t)return t;if(/^[0-9]*$/.test(t)&&parseInt(t)<=65535)return t;throw new TypeError(`Invalid port '${t}'.`)}function standardURLPathnameEncodeCallback(t){if(""===t)return t;const e=new URL("https://example.com");return e.pathname="/"!==t[0]?"/-"+t:t,"/"!==t[0]?e.pathname.substring(2,e.pathname.length):e.pathname}function pathURLPathnameEncodeCallback(t){if(""===t)return t;return new URL(`data:${t}`).pathname}function searchEncodeCallback(t){if(""===t)return t;const e=new URL("https://example.com");return e.search=t,e.search.substring(1,e.search.length)}function hashEncodeCallback(t){if(""===t)return t;const e=new URL("https://example.com");return e.hash=t,e.hash.substring(1,e.hash.length)}var Parser=class{constructor(t){this.tokenList=[],this.internalResult={},this.tokenIndex=0,this.tokenIncrement=1,this.componentStart=0,this.state=0,this.groupDepth=0,this.hostnameIPv6BracketDepth=0,this.shouldTreatAsStandardURL=!1,this.input=t}get result(){return this.internalResult}parse(){for(this.tokenList=lexer(this.input,!0);this.tokenIndex0){if(!this.isGroupClose())continue;this.groupDepth-=1}if(this.isGroupOpen())this.groupDepth+=1;else switch(this.state){case 0:this.isProtocolSuffix()&&(this.internalResult.username="",this.internalResult.password="",this.internalResult.hostname="",this.internalResult.port="",this.internalResult.pathname="",this.internalResult.search="",this.internalResult.hash="",this.rewindAndSetState(1));break;case 1:if(this.isProtocolSuffix()){this.computeShouldTreatAsStandardURL();let t=7,e=1;this.shouldTreatAsStandardURL&&(this.internalResult.pathname="/"),this.nextIsAuthoritySlashes()?(t=2,e=3):this.shouldTreatAsStandardURL&&(t=2),this.changeState(t,e)}break;case 2:this.isIdentityTerminator()?this.rewindAndSetState(3):(this.isPathnameStart()||this.isSearchPrefix()||this.isHashPrefix())&&this.rewindAndSetState(5);break;case 3:this.isPasswordPrefix()?this.changeState(4,1):this.isIdentityTerminator()&&this.changeState(5,1);break;case 4:this.isIdentityTerminator()&&this.changeState(5,1);break;case 5:this.isIPv6Open()?this.hostnameIPv6BracketDepth+=1:this.isIPv6Close()&&(this.hostnameIPv6BracketDepth-=1),this.isPortPrefix()&&!this.hostnameIPv6BracketDepth?this.changeState(6,1):this.isPathnameStart()?this.changeState(7,0):this.isSearchPrefix()?this.changeState(8,1):this.isHashPrefix()&&this.changeState(9,1);break;case 6:this.isPathnameStart()?this.changeState(7,0):this.isSearchPrefix()?this.changeState(8,1):this.isHashPrefix()&&this.changeState(9,1);break;case 7:this.isSearchPrefix()?this.changeState(8,1):this.isHashPrefix()&&this.changeState(9,1);break;case 8:this.isHashPrefix()&&this.changeState(9,1)}}}changeState(t,e){switch(this.state){case 0:case 2:case 10:break;case 1:this.internalResult.protocol=this.makeComponentString();break;case 3:this.internalResult.username=this.makeComponentString();break;case 4:this.internalResult.password=this.makeComponentString();break;case 5:this.internalResult.hostname=this.makeComponentString();break;case 6:this.internalResult.port=this.makeComponentString();break;case 7:this.internalResult.pathname=this.makeComponentString();break;case 8:this.internalResult.search=this.makeComponentString();break;case 9:this.internalResult.hash=this.makeComponentString()}this.changeStateWithoutSettingComponent(t,e)}changeStateWithoutSettingComponent(t,e){this.state=t,this.componentStart=this.tokenIndex+e,this.tokenIndex+=e,this.tokenIncrement=0}rewind(){this.tokenIndex=this.componentStart,this.tokenIncrement=0}rewindAndSetState(t){this.rewind(),this.state=t}safeToken(t){return t<0&&(t=this.tokenList.length-t),t=0&&(t.pathname=r.pathname.substring(0,e+1)+t.pathname)}t.pathname=canonicalizePathname(t.pathname,t.protocol,n)}return"string"==typeof e.search&&(t.search=canonicalizeSearch(e.search,n)),"string"==typeof e.hash&&(t.hash=canonicalizeHash(e.hash,n)),t}function escapePatternString(t){return t.replace(/([+*?:{}()\\])/g,"\\$1")}function escapeRegexpString(t){return t.replace(/([.+*?^${}()[\]|/\\])/g,"\\$1")}function tokensToPattern(t,e){const n=`[^${escapeRegexpString(e.delimiter||"/#?")}]+?`,r=/[$_\u200C\u200D\p{ID_Continue}]/u;let s="";for(let a=0;a0?t[a-1]:null,h=a0?h[0]:"";l=r.test(t)}else l="number"==typeof h.name;if(!l&&""===i.prefix&&o&&"string"==typeof o&&o.length>0){const t=o[o.length-1];l=p.includes(t)}l&&(s+="{"),s+=escapePatternString(i.prefix),c&&(s+=`:${i.name}`),".*"===i.pattern?c||o&&"string"!=typeof o&&!o.modifier&&!l&&""===i.prefix?s+="(.*)":s+="*":i.pattern===n?c||(s+=`(${n})`):s+=`(${i.pattern})`,i.pattern===n&&c&&""!==i.suffix&&r.test(i.suffix[0])&&(s+="\\"),s+=escapePatternString(i.suffix),l&&(s+="}"),s+=i.modifier}return s}var URLPattern=class{constructor(t={},e){this.regexp={},this.keys={},this.component_pattern={};try{if("string"==typeof t){const n=new Parser(t);if(n.parse(),t=n.result,e){if("string"!=typeof e)throw new TypeError("'baseURL' parameter is not of type 'string'.");t.baseURL=e}else if("string"!=typeof t.protocol)throw new TypeError("A base URL must be provided for a relative constructor string.")}else if(e)throw new TypeError("parameter 1 is not of type 'string'.");if(!t||"object"!=typeof t)throw new TypeError("parameter 1 is not of type 'string' and cannot convert to dictionary.");const n={pathname:DEFAULT_PATTERN,protocol:DEFAULT_PATTERN,username:DEFAULT_PATTERN,password:DEFAULT_PATTERN,hostname:DEFAULT_PATTERN,port:DEFAULT_PATTERN,search:DEFAULT_PATTERN,hash:DEFAULT_PATTERN};let r;for(r of(this.pattern=applyInit(n,t,!0),defaultPortForProtocol(this.pattern.protocol)===this.pattern.port&&(this.pattern.port=""),COMPONENTS)){if(!(r in this.pattern))continue;const t={},e=this.pattern[r];switch(this.keys[r]=[],r){case"protocol":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=protocolEncodeCallback;break;case"username":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=usernameEncodeCallback;break;case"password":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=passwordEncodeCallback;break;case"hostname":Object.assign(t,HOSTNAME_OPTIONS),treatAsIPv6Hostname(e)?t.encodePart=ipv6HostnameEncodeCallback:t.encodePart=hostnameEncodeCallback;break;case"port":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=portEncodeCallback;break;case"pathname":isSpecialScheme(this.regexp.protocol)?(Object.assign(t,PATHNAME_OPTIONS),t.encodePart=standardURLPathnameEncodeCallback):(Object.assign(t,DEFAULT_OPTIONS),t.encodePart=pathURLPathnameEncodeCallback);break;case"search":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=searchEncodeCallback;break;case"hash":Object.assign(t,DEFAULT_OPTIONS),t.encodePart=hashEncodeCallback}try{const n=parse(e,t);this.regexp[r]=tokensToRegexp(n,this.keys[r],t),this.component_pattern[r]=tokensToPattern(n,t)}catch{throw new TypeError(`invalid ${r} pattern '${this.pattern[r]}'.`)}}}catch(t){throw new TypeError(`Failed to construct 'URLPattern': ${t.message}`)}}test(t={},e){let n,r={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if("string"!=typeof t&&e)throw new TypeError("parameter 1 is not of type 'string'.");if(void 0===t)return!1;try{r=applyInit(r,"object"==typeof t?t:extractValues(t,e),!1)}catch(t){return!1}for(n in this.pattern)if(!this.regexp[n].exec(r[n]))return!1;return!0}exec(t={},e){let n={pathname:"",protocol:"",username:"",password:"",hostname:"",port:"",search:"",hash:""};if("string"!=typeof t&&e)throw new TypeError("parameter 1 is not of type 'string'.");if(void 0===t)return;try{n=applyInit(n,"object"==typeof t?t:extractValues(t,e),!1)}catch(t){return null}let r,s={};for(r in s.inputs=e?[t,e]:[t],this.pattern){let t=this.regexp[r].exec(n[r]);if(!t)return null;let e={};for(let[n,s]of this.keys[r].entries())if("string"==typeof s.name||"number"==typeof s.name){let r=t[n+1];e[s.name]=r}s[r]={input:n[r]||"",groups:e}}return s}get protocol(){return this.component_pattern.protocol}get username(){return this.component_pattern.username}get password(){return this.component_pattern.password}get hostname(){return this.component_pattern.hostname}get port(){return this.component_pattern.port}get pathname(){return this.component_pattern.pathname}get search(){return this.component_pattern.search}get hash(){return this.component_pattern.hash}};globalThis.URLPattern||(globalThis.URLPattern=URLPattern);
--------------------------------------------------------------------------------