├── 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 | ![image-20220507145845604](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zulzgojaj218e0hkq48.jpg) 6 | 7 | 其实这里是有3个开发环境的 8 | 9 | ![image-20220507150944407](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zuxcwyqxj218e0mo76r.jpg) 10 | 11 | 但是单独从 favicon 是没法快速区分哪个跟哪个的,为此,我做了一个 Chrome 插件可以很方便的解决这个问题,效果如下 12 | 13 | ![image-20220507151813352](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zv66dhv7j218g0gkq47.jpg) 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 | ![image-20220507153210100](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zvkoskj6j21ms0syacl.jpg) 22 | 23 | 成功安装后,需要进入到**配置页**,也就是可以自定义匹配域名的页面,有 3 个入口 24 | 25 | 1. 直接点击右上角插件图标(推荐) 26 | 27 | 2. 右键右上角插件图标,点击“选项” 28 | 29 | 3. 进入插件详情页,点击“扩展程序选项” 30 | 31 | ![image-20220507153910905](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zvrz8el6j218e0fy757.jpg) 32 | 33 | 下面就是一个简单的配置页面 34 | 35 | ![image-20220507155723294](https://tva1.sinaimg.cn/large/e6c9d24egy1h1zwaxcl8dj21a80u0768.jpg) 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, 32 | 64 | ${env} 65 | 66 | `.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 |
15 |
16 | 颜色 17 |
18 |
19 | 名称 20 |
21 |
22 | 匹配 23 |
24 |
25 | 预览 26 |
27 |
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); --------------------------------------------------------------------------------