|
11 | 11 | var ua = navigator.userAgent; |
12 | 12 |
|
13 | 13 | var browser = (function () { |
| 14 | + var isBrave = !!(navigator.brave && typeof navigator.brave.isBrave === 'function'); |
| 15 | + if (isBrave) return { name: 'Brave', version: (ua.match(/Chrome\/([\d.]+)/) || [])[1] }; |
14 | 16 | if (/Edg\//.test(ua)) return { name: 'Edge', version: (ua.match(/Edg\/([\d.]+)/) || [])[1] }; |
15 | 17 | if (/OPR\//.test(ua)) return { name: 'Opera', version: (ua.match(/OPR\/([\d.]+)/) || [])[1] }; |
16 | 18 | if (/Chrome\//.test(ua)) return { name: 'Chrome', version: (ua.match(/Chrome\/([\d.]+)/) || [])[1] }; |
|
21 | 23 | })(); |
22 | 24 |
|
23 | 25 | // Minimum supported versions |
24 | | - var MIN_VERSIONS = { Chrome: 90, Firefox: 88, Safari: 14, Edge: 90, Opera: 76 }; |
| 26 | + var MIN_VERSIONS = { Chrome: 90, Firefox: 88, Safari: 14, Edge: 90, Opera: 76, Brave: 90 }; |
25 | 27 |
|
26 | 28 | var majorVersion = parseInt((browser.version || '0').split('.')[0], 10); |
27 | 29 | var isSupported = browser.name !== 'IE' && |
28 | 30 | (!(browser.name in MIN_VERSIONS) || majorVersion >= MIN_VERSIONS[browser.name]); |
29 | 31 | var isIE = browser.name === 'IE'; |
| 32 | + var isSafari = browser.name === 'Safari'; |
| 33 | + var isMobileSafari = /iPhone|iPad|iPod/.test(ua); |
30 | 34 |
|
31 | 35 | // --------------------------------------------------------------------------- |
32 | 36 | // Feature detection |
|
50 | 54 | clipboard: !!(navigator.clipboard && navigator.clipboard.writeText), |
51 | 55 | notifications: typeof Notification !== 'undefined', |
52 | 56 | serviceWorker: 'serviceWorker' in navigator, |
| 57 | + abortController: typeof AbortController !== 'undefined', |
| 58 | + promiseAllSettled:typeof Promise.allSettled !== 'undefined', |
| 59 | + stringReplaceAll: typeof String.prototype.replaceAll !== 'undefined', |
53 | 60 | }; |
54 | 61 |
|
55 | 62 | // --------------------------------------------------------------------------- |
|
120 | 127 | window.cancelAnimationFrame = clearTimeout; |
121 | 128 | } |
122 | 129 |
|
| 130 | + // Promise.allSettled |
| 131 | + if (!features.promiseAllSettled) { |
| 132 | + Promise.allSettled = function (promises) { |
| 133 | + return Promise.all(promises.map(function (p) { |
| 134 | + return Promise.resolve(p).then( |
| 135 | + function (val) { return { status: 'fulfilled', value: val }; }, |
| 136 | + function (err) { return { status: 'rejected', reason: err }; } |
| 137 | + ); |
| 138 | + })); |
| 139 | + }; |
| 140 | + } |
| 141 | + |
| 142 | + // AbortController (basic polyfill) |
| 143 | + if (!features.abortController) { |
| 144 | + function AbortSignal() { this.aborted = false; this.onabort = null; } |
| 145 | + function AbortController() { this.signal = new AbortSignal(); } |
| 146 | + AbortController.prototype.abort = function () { |
| 147 | + this.signal.aborted = true; |
| 148 | + if (typeof this.signal.onabort === 'function') this.signal.onabort(); |
| 149 | + }; |
| 150 | + window.AbortController = AbortController; |
| 151 | + window.AbortSignal = AbortSignal; |
| 152 | + } |
| 153 | + |
| 154 | + // String.prototype.replaceAll |
| 155 | + if (!features.stringReplaceAll) { |
| 156 | + String.prototype.replaceAll = function (search, replacement) { |
| 157 | + if (search instanceof RegExp && !search.global) { |
| 158 | + throw new TypeError('replaceAll must be called with a global RegExp'); |
| 159 | + } |
| 160 | + return this.split(search).join(replacement); |
| 161 | + }; |
| 162 | + } |
| 163 | + |
123 | 164 | // --------------------------------------------------------------------------- |
124 | 165 | // CSS fallbacks for browsers without custom properties |
125 | 166 | // --------------------------------------------------------------------------- |
|
190 | 231 | ); |
191 | 232 | } |
192 | 233 |
|
| 234 | + if (!features.localStorage) { |
| 235 | + showWarning( |
| 236 | + 'LocalStorage is disabled or not supported. This might be due to Private Browsing mode. ' + |
| 237 | + 'Settings and session data will not be saved.', |
| 238 | + 'compat-storage-warning' |
| 239 | + ); |
| 240 | + } |
| 241 | + |
193 | 242 | // --------------------------------------------------------------------------- |
194 | 243 | // Browser-specific fixes |
195 | 244 | // --------------------------------------------------------------------------- |
196 | 245 |
|
| 246 | + // Safari: 100vh height fix for mobile |
| 247 | + if (isMobileSafari) { |
| 248 | + var setVH = function () { |
| 249 | + var vh = window.innerHeight * 0.01; |
| 250 | + document.documentElement.style.setProperty('--vh', vh + 'px'); |
| 251 | + }; |
| 252 | + window.addEventListener('resize', setVH); |
| 253 | + setVH(); |
| 254 | + } |
| 255 | + |
| 256 | + // Chrome / Edge / Brave: custom scrollbar styling if supported |
| 257 | + var isChromium = /Chrome/.test(browser.name) || /Edge/.test(browser.name) || /Brave/.test(browser.name); |
| 258 | + if (isChromium) { |
| 259 | + var scrollStyle = document.createElement('style'); |
| 260 | + scrollStyle.textContent = [ |
| 261 | + '::-webkit-scrollbar{width:8px;height:8px}', |
| 262 | + '::-webkit-scrollbar-track{background:#1a1a2e}', |
| 263 | + '::-webkit-scrollbar-thumb{background:#4b4b7c;border-radius:4px}', |
| 264 | + '::-webkit-scrollbar-thumb:hover{background:#6366f1}', |
| 265 | + ].join(''); |
| 266 | + document.head.appendChild(scrollStyle); |
| 267 | + } |
| 268 | + |
197 | 269 | // Safari: passive touch-event listeners to avoid scroll-blocking warnings |
198 | 270 | // and fix 300ms tap delay on older iOS Safari (< 13). |
199 | | - var isSafari = browser.name === 'Safari'; |
200 | | - var isMobileSafari = /iPhone|iPad|iPod/.test(ua); |
201 | 271 | if (isSafari || isMobileSafari) { |
202 | 272 | // Ensure touch-action is set so iOS Safari doesn't delay click events |
203 | 273 | var safariStyle = document.createElement('style'); |
|
275 | 345 | fixes: { |
276 | 346 | safariTouchAction: isSafari || isMobileSafari, |
277 | 347 | iosTouchMove: isMobileSafari, |
| 348 | + safariVHFix: isMobileSafari, |
| 349 | + chromiumScrollbars: isChromium, |
278 | 350 | firefoxFocusVisible: firefoxMajor < 85, |
279 | 351 | safariSmoothScroll: safariMajor < 15, |
280 | 352 | dialogPolyfill: typeof HTMLDialogElement === 'undefined', |
| 353 | + abortController: !features.abortController, |
| 354 | + promiseAllSettled: !features.promiseAllSettled, |
| 355 | + stringReplaceAll: !features.stringReplaceAll, |
281 | 356 | }, |
282 | 357 | }; |
283 | 358 |
|
|
0 commit comments