├── .gitignore ├── Extension ├── .prettierignore ├── README.md ├── _locales │ └── en │ │ └── messages.json ├── images │ └── .gitkeep ├── manifest.json ├── package-lock.json ├── package.json ├── public │ ├── popup.html │ └── settings.html ├── scripts │ ├── build.js │ ├── firefox-build.js │ ├── package-extension.js │ └── prod-build.js ├── src │ ├── background.ts │ ├── content.ts │ ├── models.ts │ ├── popup │ │ ├── popup.svelte │ │ └── popup.ts │ ├── settings-connector.ts │ └── settings │ │ ├── settings.svelte │ │ └── settings.ts └── tsconfig.json ├── LICENSE ├── README.md └── Safari ├── REPLACEME.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ ├── REPLACEME (iOS).xcscheme │ │ └── REPLACEME (macOS).xcscheme └── xcuserdata │ ├── kylenazario.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── tester.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── Shared (App) ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── Contents.json │ └── LargeIcon.imageset │ │ └── Contents.json ├── Models.swift └── Views.swift ├── Shared (Extension) └── SafariWebExtensionHandler.swift ├── iOS (App) ├── Info.plist ├── iOS AppView.swift └── iOS Assets.xcassets │ └── Contents.json ├── iOS (Extension) └── Info.plist ├── macOS (App) ├── AppDelegate.swift ├── REPLACEME.entitlements ├── SafariConnector.swift ├── macOS AppView.swift ├── macOS Assets.xcassets │ └── Contents.json └── main.swift └── macOS (Extension) ├── Info.plist └── REPLACEME.entitlements /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # web 5 | **/node_modules 6 | Extension/dist 7 | 8 | # safari 9 | **/xcdebugger 10 | Shared\ (Extension)/Resources/dist/ 11 | **/xcuserdata 12 | **/*.xcuserstate 13 | -------------------------------------------------------------------------------- /Extension/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /Extension/README.md: -------------------------------------------------------------------------------- 1 | # REPLACEME 2 | 3 | ## Building and running locally 4 | 5 | Build scripts are tested on **Mac**. These will probably work on Linux and Windows Subsystem for Linux, but I have not tested them. 6 | 7 | - Make sure you have Node.js 19.7.0 or later installed 8 | - Open the folder containing this README 9 | - Run `npm install` 10 | - Run `npm run build` 11 | 12 | Continue the process with the steps listed below. 13 | 14 | ### Firefox 15 | 16 | - Open Firefox's [debugging page](about:debugging#/runtime/this-firefox) (`about:debugging#/runtime/this-firefox`) 17 | - Click "Load Temporary Add-on..." 18 | - Navigate to this project's root and select `manifest.json` 19 | -------------------------------------------------------------------------------- /Extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension_name": { 3 | "message": "REPLACEME", 4 | "description": "The display name for the extension." 5 | }, 6 | "extension_description": { 7 | "message": "REPLACEME", 8 | "description": "Description of what the extension does." 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Extension/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyle-n/WebExtensionTemplate/d8e86218aa593ac667edf5758c541212eee7d56b/Extension/images/.gitkeep -------------------------------------------------------------------------------- /Extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "default_locale": "en", 4 | 5 | "name": "__MSG_extension_name__", 6 | "description": "__MSG_extension_description__", 7 | "version": "1.0.0", 8 | 9 | "icons": { 10 | "48": "images/app_icon_48px.png", 11 | "96": "images/app_icon_96px.png", 12 | "128": "images/app_icon_128px.png", 13 | "256": "images/app_icon_256px.png", 14 | "512": "images/app_icon_512px.png" 15 | }, 16 | 17 | "background": { 18 | "service_worker": "dist/background.js" 19 | }, 20 | 21 | "host_permissions": [], 22 | 23 | "content_scripts": [ 24 | { 25 | "js": ["dist/content.js"], 26 | "matches": ["*://example.com/*"] 27 | } 28 | ], 29 | 30 | "action": { 31 | "default_popup": "public/popup.html", 32 | "default_icon": { 33 | "16": "images/toolbar_16px.png", 34 | "19": "images/toolbar_19px.png", 35 | "32": "images/toolbar_32px.png", 36 | "38": "images/toolbar_38px.png", 37 | "48": "images/toolbar_48px.png", 38 | "72": "images/toolbar_72px.png" 39 | } 40 | }, 41 | 42 | "permissions": ["storage"], 43 | 44 | "options_ui": { 45 | "page": "public/settings.html", 46 | "open_in_tab": true 47 | }, 48 | 49 | "browser_specific_settings": { 50 | "gecko": { 51 | "id": "REPLACEME" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Extension/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "REPLACEME", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "REPLACEME", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "detect-browser": "^5.3.0", 13 | "svelte": "^5.25.7", 14 | "webextension-polyfill": "^0.12.0" 15 | }, 16 | "devDependencies": { 17 | "@tsconfig/svelte": "^5.0.4", 18 | "@types/webextension-polyfill": "^0.12.3", 19 | "esbuild": "^0.25.2", 20 | "esbuild-svelte": "^0.9.2", 21 | "fs-extra": "^11.3.0", 22 | "prettier": "^3.5.3", 23 | "prettier-plugin-svelte": "^3.3.3", 24 | "svelte-preprocess": "^6.0.3", 25 | "typescript": "^5.8.3", 26 | "zip-local": "^0.3.5" 27 | } 28 | }, 29 | "node_modules/@ampproject/remapping": { 30 | "version": "2.3.0", 31 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 32 | "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 33 | "license": "Apache-2.0", 34 | "dependencies": { 35 | "@jridgewell/gen-mapping": "^0.3.5", 36 | "@jridgewell/trace-mapping": "^0.3.24" 37 | }, 38 | "engines": { 39 | "node": ">=6.0.0" 40 | } 41 | }, 42 | "node_modules/@esbuild/aix-ppc64": { 43 | "version": "0.25.2", 44 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", 45 | "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", 46 | "cpu": [ 47 | "ppc64" 48 | ], 49 | "dev": true, 50 | "license": "MIT", 51 | "optional": true, 52 | "os": [ 53 | "aix" 54 | ], 55 | "engines": { 56 | "node": ">=18" 57 | } 58 | }, 59 | "node_modules/@esbuild/android-arm": { 60 | "version": "0.25.2", 61 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", 62 | "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", 63 | "cpu": [ 64 | "arm" 65 | ], 66 | "dev": true, 67 | "license": "MIT", 68 | "optional": true, 69 | "os": [ 70 | "android" 71 | ], 72 | "engines": { 73 | "node": ">=18" 74 | } 75 | }, 76 | "node_modules/@esbuild/android-arm64": { 77 | "version": "0.25.2", 78 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", 79 | "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", 80 | "cpu": [ 81 | "arm64" 82 | ], 83 | "dev": true, 84 | "license": "MIT", 85 | "optional": true, 86 | "os": [ 87 | "android" 88 | ], 89 | "engines": { 90 | "node": ">=18" 91 | } 92 | }, 93 | "node_modules/@esbuild/android-x64": { 94 | "version": "0.25.2", 95 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", 96 | "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", 97 | "cpu": [ 98 | "x64" 99 | ], 100 | "dev": true, 101 | "license": "MIT", 102 | "optional": true, 103 | "os": [ 104 | "android" 105 | ], 106 | "engines": { 107 | "node": ">=18" 108 | } 109 | }, 110 | "node_modules/@esbuild/darwin-arm64": { 111 | "version": "0.25.2", 112 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", 113 | "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", 114 | "cpu": [ 115 | "arm64" 116 | ], 117 | "dev": true, 118 | "license": "MIT", 119 | "optional": true, 120 | "os": [ 121 | "darwin" 122 | ], 123 | "engines": { 124 | "node": ">=18" 125 | } 126 | }, 127 | "node_modules/@esbuild/darwin-x64": { 128 | "version": "0.25.2", 129 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", 130 | "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", 131 | "cpu": [ 132 | "x64" 133 | ], 134 | "dev": true, 135 | "license": "MIT", 136 | "optional": true, 137 | "os": [ 138 | "darwin" 139 | ], 140 | "engines": { 141 | "node": ">=18" 142 | } 143 | }, 144 | "node_modules/@esbuild/freebsd-arm64": { 145 | "version": "0.25.2", 146 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", 147 | "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", 148 | "cpu": [ 149 | "arm64" 150 | ], 151 | "dev": true, 152 | "license": "MIT", 153 | "optional": true, 154 | "os": [ 155 | "freebsd" 156 | ], 157 | "engines": { 158 | "node": ">=18" 159 | } 160 | }, 161 | "node_modules/@esbuild/freebsd-x64": { 162 | "version": "0.25.2", 163 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", 164 | "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", 165 | "cpu": [ 166 | "x64" 167 | ], 168 | "dev": true, 169 | "license": "MIT", 170 | "optional": true, 171 | "os": [ 172 | "freebsd" 173 | ], 174 | "engines": { 175 | "node": ">=18" 176 | } 177 | }, 178 | "node_modules/@esbuild/linux-arm": { 179 | "version": "0.25.2", 180 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", 181 | "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", 182 | "cpu": [ 183 | "arm" 184 | ], 185 | "dev": true, 186 | "license": "MIT", 187 | "optional": true, 188 | "os": [ 189 | "linux" 190 | ], 191 | "engines": { 192 | "node": ">=18" 193 | } 194 | }, 195 | "node_modules/@esbuild/linux-arm64": { 196 | "version": "0.25.2", 197 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", 198 | "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", 199 | "cpu": [ 200 | "arm64" 201 | ], 202 | "dev": true, 203 | "license": "MIT", 204 | "optional": true, 205 | "os": [ 206 | "linux" 207 | ], 208 | "engines": { 209 | "node": ">=18" 210 | } 211 | }, 212 | "node_modules/@esbuild/linux-ia32": { 213 | "version": "0.25.2", 214 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", 215 | "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", 216 | "cpu": [ 217 | "ia32" 218 | ], 219 | "dev": true, 220 | "license": "MIT", 221 | "optional": true, 222 | "os": [ 223 | "linux" 224 | ], 225 | "engines": { 226 | "node": ">=18" 227 | } 228 | }, 229 | "node_modules/@esbuild/linux-loong64": { 230 | "version": "0.25.2", 231 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", 232 | "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", 233 | "cpu": [ 234 | "loong64" 235 | ], 236 | "dev": true, 237 | "license": "MIT", 238 | "optional": true, 239 | "os": [ 240 | "linux" 241 | ], 242 | "engines": { 243 | "node": ">=18" 244 | } 245 | }, 246 | "node_modules/@esbuild/linux-mips64el": { 247 | "version": "0.25.2", 248 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", 249 | "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", 250 | "cpu": [ 251 | "mips64el" 252 | ], 253 | "dev": true, 254 | "license": "MIT", 255 | "optional": true, 256 | "os": [ 257 | "linux" 258 | ], 259 | "engines": { 260 | "node": ">=18" 261 | } 262 | }, 263 | "node_modules/@esbuild/linux-ppc64": { 264 | "version": "0.25.2", 265 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", 266 | "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", 267 | "cpu": [ 268 | "ppc64" 269 | ], 270 | "dev": true, 271 | "license": "MIT", 272 | "optional": true, 273 | "os": [ 274 | "linux" 275 | ], 276 | "engines": { 277 | "node": ">=18" 278 | } 279 | }, 280 | "node_modules/@esbuild/linux-riscv64": { 281 | "version": "0.25.2", 282 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", 283 | "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", 284 | "cpu": [ 285 | "riscv64" 286 | ], 287 | "dev": true, 288 | "license": "MIT", 289 | "optional": true, 290 | "os": [ 291 | "linux" 292 | ], 293 | "engines": { 294 | "node": ">=18" 295 | } 296 | }, 297 | "node_modules/@esbuild/linux-s390x": { 298 | "version": "0.25.2", 299 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", 300 | "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", 301 | "cpu": [ 302 | "s390x" 303 | ], 304 | "dev": true, 305 | "license": "MIT", 306 | "optional": true, 307 | "os": [ 308 | "linux" 309 | ], 310 | "engines": { 311 | "node": ">=18" 312 | } 313 | }, 314 | "node_modules/@esbuild/linux-x64": { 315 | "version": "0.25.2", 316 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", 317 | "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", 318 | "cpu": [ 319 | "x64" 320 | ], 321 | "dev": true, 322 | "license": "MIT", 323 | "optional": true, 324 | "os": [ 325 | "linux" 326 | ], 327 | "engines": { 328 | "node": ">=18" 329 | } 330 | }, 331 | "node_modules/@esbuild/netbsd-arm64": { 332 | "version": "0.25.2", 333 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", 334 | "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", 335 | "cpu": [ 336 | "arm64" 337 | ], 338 | "dev": true, 339 | "license": "MIT", 340 | "optional": true, 341 | "os": [ 342 | "netbsd" 343 | ], 344 | "engines": { 345 | "node": ">=18" 346 | } 347 | }, 348 | "node_modules/@esbuild/netbsd-x64": { 349 | "version": "0.25.2", 350 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", 351 | "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", 352 | "cpu": [ 353 | "x64" 354 | ], 355 | "dev": true, 356 | "license": "MIT", 357 | "optional": true, 358 | "os": [ 359 | "netbsd" 360 | ], 361 | "engines": { 362 | "node": ">=18" 363 | } 364 | }, 365 | "node_modules/@esbuild/openbsd-arm64": { 366 | "version": "0.25.2", 367 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", 368 | "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", 369 | "cpu": [ 370 | "arm64" 371 | ], 372 | "dev": true, 373 | "license": "MIT", 374 | "optional": true, 375 | "os": [ 376 | "openbsd" 377 | ], 378 | "engines": { 379 | "node": ">=18" 380 | } 381 | }, 382 | "node_modules/@esbuild/openbsd-x64": { 383 | "version": "0.25.2", 384 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", 385 | "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", 386 | "cpu": [ 387 | "x64" 388 | ], 389 | "dev": true, 390 | "license": "MIT", 391 | "optional": true, 392 | "os": [ 393 | "openbsd" 394 | ], 395 | "engines": { 396 | "node": ">=18" 397 | } 398 | }, 399 | "node_modules/@esbuild/sunos-x64": { 400 | "version": "0.25.2", 401 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", 402 | "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", 403 | "cpu": [ 404 | "x64" 405 | ], 406 | "dev": true, 407 | "license": "MIT", 408 | "optional": true, 409 | "os": [ 410 | "sunos" 411 | ], 412 | "engines": { 413 | "node": ">=18" 414 | } 415 | }, 416 | "node_modules/@esbuild/win32-arm64": { 417 | "version": "0.25.2", 418 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", 419 | "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", 420 | "cpu": [ 421 | "arm64" 422 | ], 423 | "dev": true, 424 | "license": "MIT", 425 | "optional": true, 426 | "os": [ 427 | "win32" 428 | ], 429 | "engines": { 430 | "node": ">=18" 431 | } 432 | }, 433 | "node_modules/@esbuild/win32-ia32": { 434 | "version": "0.25.2", 435 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", 436 | "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", 437 | "cpu": [ 438 | "ia32" 439 | ], 440 | "dev": true, 441 | "license": "MIT", 442 | "optional": true, 443 | "os": [ 444 | "win32" 445 | ], 446 | "engines": { 447 | "node": ">=18" 448 | } 449 | }, 450 | "node_modules/@esbuild/win32-x64": { 451 | "version": "0.25.2", 452 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", 453 | "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", 454 | "cpu": [ 455 | "x64" 456 | ], 457 | "dev": true, 458 | "license": "MIT", 459 | "optional": true, 460 | "os": [ 461 | "win32" 462 | ], 463 | "engines": { 464 | "node": ">=18" 465 | } 466 | }, 467 | "node_modules/@jridgewell/gen-mapping": { 468 | "version": "0.3.8", 469 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", 470 | "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", 471 | "license": "MIT", 472 | "dependencies": { 473 | "@jridgewell/set-array": "^1.2.1", 474 | "@jridgewell/sourcemap-codec": "^1.4.10", 475 | "@jridgewell/trace-mapping": "^0.3.24" 476 | }, 477 | "engines": { 478 | "node": ">=6.0.0" 479 | } 480 | }, 481 | "node_modules/@jridgewell/resolve-uri": { 482 | "version": "3.1.2", 483 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 484 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 485 | "license": "MIT", 486 | "engines": { 487 | "node": ">=6.0.0" 488 | } 489 | }, 490 | "node_modules/@jridgewell/set-array": { 491 | "version": "1.2.1", 492 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 493 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 494 | "license": "MIT", 495 | "engines": { 496 | "node": ">=6.0.0" 497 | } 498 | }, 499 | "node_modules/@jridgewell/sourcemap-codec": { 500 | "version": "1.5.0", 501 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 502 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 503 | "license": "MIT" 504 | }, 505 | "node_modules/@jridgewell/trace-mapping": { 506 | "version": "0.3.25", 507 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 508 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 509 | "license": "MIT", 510 | "dependencies": { 511 | "@jridgewell/resolve-uri": "^3.1.0", 512 | "@jridgewell/sourcemap-codec": "^1.4.14" 513 | } 514 | }, 515 | "node_modules/@sveltejs/acorn-typescript": { 516 | "version": "1.0.5", 517 | "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", 518 | "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", 519 | "license": "MIT", 520 | "peerDependencies": { 521 | "acorn": "^8.9.0" 522 | } 523 | }, 524 | "node_modules/@tsconfig/svelte": { 525 | "version": "5.0.4", 526 | "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.4.tgz", 527 | "integrity": "sha512-BV9NplVgLmSi4mwKzD8BD/NQ8erOY/nUE/GpgWe2ckx+wIQF5RyRirn/QsSSCPeulVpc3RA/iJt6DpfTIZps0Q==", 528 | "dev": true, 529 | "license": "MIT" 530 | }, 531 | "node_modules/@types/estree": { 532 | "version": "1.0.7", 533 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", 534 | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", 535 | "license": "MIT" 536 | }, 537 | "node_modules/@types/webextension-polyfill": { 538 | "version": "0.12.3", 539 | "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.12.3.tgz", 540 | "integrity": "sha512-F58aDVSeN/MjUGazXo/cPsmR76EvqQhQ1v4x23hFjUX0cfAJYE+JBWwiOGW36/VJGGxoH74sVlRIF3z7SJCKyg==", 541 | "dev": true, 542 | "license": "MIT" 543 | }, 544 | "node_modules/acorn": { 545 | "version": "8.14.1", 546 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", 547 | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 548 | "license": "MIT", 549 | "bin": { 550 | "acorn": "bin/acorn" 551 | }, 552 | "engines": { 553 | "node": ">=0.4.0" 554 | } 555 | }, 556 | "node_modules/aria-query": { 557 | "version": "5.3.2", 558 | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", 559 | "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", 560 | "license": "Apache-2.0", 561 | "engines": { 562 | "node": ">= 0.4" 563 | } 564 | }, 565 | "node_modules/async": { 566 | "version": "1.5.2", 567 | "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", 568 | "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", 569 | "dev": true 570 | }, 571 | "node_modules/axobject-query": { 572 | "version": "4.1.0", 573 | "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", 574 | "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", 575 | "license": "Apache-2.0", 576 | "engines": { 577 | "node": ">= 0.4" 578 | } 579 | }, 580 | "node_modules/clsx": { 581 | "version": "2.1.1", 582 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", 583 | "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", 584 | "license": "MIT", 585 | "engines": { 586 | "node": ">=6" 587 | } 588 | }, 589 | "node_modules/detect-browser": { 590 | "version": "5.3.0", 591 | "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", 592 | "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", 593 | "license": "MIT" 594 | }, 595 | "node_modules/esbuild": { 596 | "version": "0.25.2", 597 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", 598 | "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", 599 | "dev": true, 600 | "hasInstallScript": true, 601 | "license": "MIT", 602 | "bin": { 603 | "esbuild": "bin/esbuild" 604 | }, 605 | "engines": { 606 | "node": ">=18" 607 | }, 608 | "optionalDependencies": { 609 | "@esbuild/aix-ppc64": "0.25.2", 610 | "@esbuild/android-arm": "0.25.2", 611 | "@esbuild/android-arm64": "0.25.2", 612 | "@esbuild/android-x64": "0.25.2", 613 | "@esbuild/darwin-arm64": "0.25.2", 614 | "@esbuild/darwin-x64": "0.25.2", 615 | "@esbuild/freebsd-arm64": "0.25.2", 616 | "@esbuild/freebsd-x64": "0.25.2", 617 | "@esbuild/linux-arm": "0.25.2", 618 | "@esbuild/linux-arm64": "0.25.2", 619 | "@esbuild/linux-ia32": "0.25.2", 620 | "@esbuild/linux-loong64": "0.25.2", 621 | "@esbuild/linux-mips64el": "0.25.2", 622 | "@esbuild/linux-ppc64": "0.25.2", 623 | "@esbuild/linux-riscv64": "0.25.2", 624 | "@esbuild/linux-s390x": "0.25.2", 625 | "@esbuild/linux-x64": "0.25.2", 626 | "@esbuild/netbsd-arm64": "0.25.2", 627 | "@esbuild/netbsd-x64": "0.25.2", 628 | "@esbuild/openbsd-arm64": "0.25.2", 629 | "@esbuild/openbsd-x64": "0.25.2", 630 | "@esbuild/sunos-x64": "0.25.2", 631 | "@esbuild/win32-arm64": "0.25.2", 632 | "@esbuild/win32-ia32": "0.25.2", 633 | "@esbuild/win32-x64": "0.25.2" 634 | } 635 | }, 636 | "node_modules/esbuild-svelte": { 637 | "version": "0.9.2", 638 | "resolved": "https://registry.npmjs.org/esbuild-svelte/-/esbuild-svelte-0.9.2.tgz", 639 | "integrity": "sha512-8Jq6+rh+g1E2mkBOZKdYZ8JtlbtDq2Fydwvn+/cBvUX9S0cdKv6AISZcEbErKQ0TpLC/Cv04l1vKaqXOBO8+VQ==", 640 | "dev": true, 641 | "license": "MIT", 642 | "dependencies": { 643 | "@jridgewell/trace-mapping": "^0.3.19" 644 | }, 645 | "engines": { 646 | "node": ">=18" 647 | }, 648 | "peerDependencies": { 649 | "esbuild": ">=0.17.0", 650 | "svelte": ">=4.2.1 <6" 651 | } 652 | }, 653 | "node_modules/esm-env": { 654 | "version": "1.2.2", 655 | "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", 656 | "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", 657 | "license": "MIT" 658 | }, 659 | "node_modules/esrap": { 660 | "version": "1.4.6", 661 | "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz", 662 | "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", 663 | "license": "MIT", 664 | "dependencies": { 665 | "@jridgewell/sourcemap-codec": "^1.4.15" 666 | } 667 | }, 668 | "node_modules/fs-extra": { 669 | "version": "11.3.0", 670 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", 671 | "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", 672 | "dev": true, 673 | "license": "MIT", 674 | "dependencies": { 675 | "graceful-fs": "^4.2.0", 676 | "jsonfile": "^6.0.1", 677 | "universalify": "^2.0.0" 678 | }, 679 | "engines": { 680 | "node": ">=14.14" 681 | } 682 | }, 683 | "node_modules/graceful-fs": { 684 | "version": "4.2.11", 685 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 686 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 687 | "dev": true 688 | }, 689 | "node_modules/is-reference": { 690 | "version": "3.0.3", 691 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", 692 | "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", 693 | "license": "MIT", 694 | "dependencies": { 695 | "@types/estree": "^1.0.6" 696 | } 697 | }, 698 | "node_modules/jsonfile": { 699 | "version": "6.1.0", 700 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 701 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 702 | "dev": true, 703 | "dependencies": { 704 | "universalify": "^2.0.0" 705 | }, 706 | "optionalDependencies": { 707 | "graceful-fs": "^4.1.6" 708 | } 709 | }, 710 | "node_modules/locate-character": { 711 | "version": "3.0.0", 712 | "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 713 | "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" 714 | }, 715 | "node_modules/pako": { 716 | "version": "1.0.11", 717 | "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", 718 | "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 719 | "dev": true 720 | }, 721 | "node_modules/prettier": { 722 | "version": "3.5.3", 723 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", 724 | "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", 725 | "dev": true, 726 | "license": "MIT", 727 | "bin": { 728 | "prettier": "bin/prettier.cjs" 729 | }, 730 | "engines": { 731 | "node": ">=14" 732 | }, 733 | "funding": { 734 | "url": "https://github.com/prettier/prettier?sponsor=1" 735 | } 736 | }, 737 | "node_modules/prettier-plugin-svelte": { 738 | "version": "3.3.3", 739 | "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", 740 | "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", 741 | "dev": true, 742 | "license": "MIT", 743 | "peerDependencies": { 744 | "prettier": "^3.0.0", 745 | "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" 746 | } 747 | }, 748 | "node_modules/q": { 749 | "version": "1.5.1", 750 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", 751 | "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", 752 | "dev": true, 753 | "engines": { 754 | "node": ">=0.6.0", 755 | "teleport": ">=0.2.0" 756 | } 757 | }, 758 | "node_modules/svelte": { 759 | "version": "5.25.7", 760 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.7.tgz", 761 | "integrity": "sha512-0fzXbXaKfSvFUs6Wxev2h4CoEhexZotbTF9EJ4+Cg7MHW64ZnZ9+xUedZyEpgj0Tt9HrYGv9aASHkqjn9b/cPw==", 762 | "license": "MIT", 763 | "dependencies": { 764 | "@ampproject/remapping": "^2.3.0", 765 | "@jridgewell/sourcemap-codec": "^1.5.0", 766 | "@sveltejs/acorn-typescript": "^1.0.5", 767 | "@types/estree": "^1.0.5", 768 | "acorn": "^8.12.1", 769 | "aria-query": "^5.3.1", 770 | "axobject-query": "^4.1.0", 771 | "clsx": "^2.1.1", 772 | "esm-env": "^1.2.1", 773 | "esrap": "^1.4.6", 774 | "is-reference": "^3.0.3", 775 | "locate-character": "^3.0.0", 776 | "magic-string": "^0.30.11", 777 | "zimmerframe": "^1.1.2" 778 | }, 779 | "engines": { 780 | "node": ">=18" 781 | } 782 | }, 783 | "node_modules/svelte-preprocess": { 784 | "version": "6.0.3", 785 | "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-6.0.3.tgz", 786 | "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", 787 | "dev": true, 788 | "hasInstallScript": true, 789 | "license": "MIT", 790 | "engines": { 791 | "node": ">= 18.0.0" 792 | }, 793 | "peerDependencies": { 794 | "@babel/core": "^7.10.2", 795 | "coffeescript": "^2.5.1", 796 | "less": "^3.11.3 || ^4.0.0", 797 | "postcss": "^7 || ^8", 798 | "postcss-load-config": ">=3", 799 | "pug": "^3.0.0", 800 | "sass": "^1.26.8", 801 | "stylus": ">=0.55", 802 | "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", 803 | "svelte": "^4.0.0 || ^5.0.0-next.100 || ^5.0.0", 804 | "typescript": "^5.0.0" 805 | }, 806 | "peerDependenciesMeta": { 807 | "@babel/core": { 808 | "optional": true 809 | }, 810 | "coffeescript": { 811 | "optional": true 812 | }, 813 | "less": { 814 | "optional": true 815 | }, 816 | "postcss": { 817 | "optional": true 818 | }, 819 | "postcss-load-config": { 820 | "optional": true 821 | }, 822 | "pug": { 823 | "optional": true 824 | }, 825 | "sass": { 826 | "optional": true 827 | }, 828 | "stylus": { 829 | "optional": true 830 | }, 831 | "sugarss": { 832 | "optional": true 833 | }, 834 | "typescript": { 835 | "optional": true 836 | } 837 | } 838 | }, 839 | "node_modules/svelte/node_modules/magic-string": { 840 | "version": "0.30.17", 841 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 842 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 843 | "license": "MIT", 844 | "dependencies": { 845 | "@jridgewell/sourcemap-codec": "^1.5.0" 846 | } 847 | }, 848 | "node_modules/typescript": { 849 | "version": "5.8.3", 850 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 851 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 852 | "dev": true, 853 | "license": "Apache-2.0", 854 | "bin": { 855 | "tsc": "bin/tsc", 856 | "tsserver": "bin/tsserver" 857 | }, 858 | "engines": { 859 | "node": ">=14.17" 860 | } 861 | }, 862 | "node_modules/universalify": { 863 | "version": "2.0.0", 864 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", 865 | "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", 866 | "dev": true, 867 | "engines": { 868 | "node": ">= 10.0.0" 869 | } 870 | }, 871 | "node_modules/webextension-polyfill": { 872 | "version": "0.12.0", 873 | "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.12.0.tgz", 874 | "integrity": "sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==", 875 | "license": "MPL-2.0" 876 | }, 877 | "node_modules/zimmerframe": { 878 | "version": "1.1.2", 879 | "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", 880 | "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", 881 | "license": "MIT" 882 | }, 883 | "node_modules/zip-local": { 884 | "version": "0.3.5", 885 | "resolved": "https://registry.npmjs.org/zip-local/-/zip-local-0.3.5.tgz", 886 | "integrity": "sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==", 887 | "dev": true, 888 | "license": "MIT", 889 | "dependencies": { 890 | "async": "^1.4.2", 891 | "graceful-fs": "^4.1.3", 892 | "jszip": "^2.6.1", 893 | "q": "^1.4.1" 894 | } 895 | }, 896 | "node_modules/zip-local/node_modules/jszip": { 897 | "version": "2.7.0", 898 | "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.7.0.tgz", 899 | "integrity": "sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==", 900 | "dev": true, 901 | "dependencies": { 902 | "pako": "~1.0.2" 903 | } 904 | } 905 | } 906 | } 907 | -------------------------------------------------------------------------------- /Extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "REPLACEME", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "background.js", 6 | "scripts": { 7 | "format": "prettier --write .", 8 | "build": "node scripts/build.js", 9 | "build:prod": "node scripts/prod-build.js", 10 | "build:chrome": "node scripts/package-extension.js", 11 | "build:firefox": "node scripts/firefox-build.js" 12 | }, 13 | "keywords": [], 14 | "author": "REPLACEME", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@tsconfig/svelte": "^5.0.4", 18 | "@types/webextension-polyfill": "^0.12.3", 19 | "esbuild": "^0.25.2", 20 | "esbuild-svelte": "^0.9.2", 21 | "fs-extra": "^11.3.0", 22 | "prettier": "^3.5.3", 23 | "prettier-plugin-svelte": "^3.3.3", 24 | "svelte-preprocess": "^6.0.3", 25 | "typescript": "^5.8.3", 26 | "zip-local": "^0.3.5" 27 | }, 28 | "prettier": { 29 | "tabWidth": 2, 30 | "semi": true, 31 | "singleQuote": true, 32 | "trailingComma": "none", 33 | "arrowParens": "avoid" 34 | }, 35 | "dependencies": { 36 | "detect-browser": "^5.3.0", 37 | "svelte": "^5.25.7", 38 | "webextension-polyfill": "^0.12.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Extension/public/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
15 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /Extension/public/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | REPLACEME 7 | 8 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /Extension/scripts/build.js: -------------------------------------------------------------------------------- 1 | const { build } = require('esbuild'); 2 | const sveltePlugin = require('esbuild-svelte'); 3 | const sveltePreprocess = require('svelte-preprocess'); 4 | 5 | const isProdBuild = process.argv.includes('--prod'); 6 | 7 | main(); 8 | 9 | async function main() { 10 | const commonConfig = { 11 | outbase: './src', 12 | platform: 'browser', 13 | external: [], 14 | bundle: true, 15 | sourcemap: !isProdBuild, 16 | minify: isProdBuild, 17 | tsconfig: './tsconfig.json', 18 | drop: isProdBuild ? ['console'] : undefined 19 | }; 20 | const contentJob = build({ 21 | ...commonConfig, 22 | entryPoints: ['./src/content.ts'], 23 | outfile: './dist/content.js' 24 | }); 25 | 26 | const backgroundJob = build({ 27 | ...commonConfig, 28 | entryPoints: ['./src/background.ts'], 29 | outfile: './dist/background.js' 30 | }); 31 | 32 | const popupJob = build({ 33 | ...commonConfig, 34 | entryPoints: ['./src/popup/popup.ts'], 35 | outbase: './src/popup', 36 | outdir: './dist', 37 | mainFields: ['svelte', 'module', 'main', 'browser'], 38 | plugins: [ 39 | sveltePlugin({ 40 | preprocess: sveltePreprocess() 41 | }) 42 | ] 43 | }); 44 | 45 | const settingsJob = build({ 46 | ...commonConfig, 47 | entryPoints: ['./src/settings/settings.ts'], 48 | outbase: './src/settings', 49 | outdir: './dist', 50 | mainFields: ['svelte', 'module', 'main', 'browser'], 51 | plugins: [ 52 | sveltePlugin({ 53 | preprocess: sveltePreprocess() 54 | }) 55 | ] 56 | }); 57 | 58 | return Promise.all([contentJob, backgroundJob, popupJob, settingsJob]).then( 59 | () => console.log('⚡ Compiled') 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /Extension/scripts/firefox-build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const util = require('util'); 3 | const exec = util.promisify(require('child_process').exec); 4 | const zipper = require('zip-local'); 5 | 6 | main(); 7 | 8 | async function main() { 9 | await exec('npm run build:chrome'); 10 | 11 | // Removes non-source files 12 | const tmpDir = '../tmp'; 13 | if (fs.existsSync(tmpDir)) { 14 | await fs.rm(tmpDir, { recursive: true }); 15 | } 16 | await fs.mkdir(tmpDir); 17 | await fs.move('node_modules', `${tmpDir}/node_modules`); 18 | await fs.move('packaged-extension.zip', `${tmpDir}/packaged-extension.zip`); 19 | 20 | zipper.sync.zip('.').compress().save('extension-source.zip'); 21 | 22 | // Move project files back 23 | await fs.move(`${tmpDir}/node_modules`, 'node_modules'); 24 | await fs.move(`${tmpDir}/packaged-extension.zip`, 'packaged-extension.zip'); 25 | 26 | console.log('Built for Firefox'); 27 | } 28 | -------------------------------------------------------------------------------- /Extension/scripts/package-extension.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const util = require('util'); 3 | const exec = util.promisify(require('child_process').exec); 4 | const zipper = require('zip-local'); 5 | 6 | main(); 7 | 8 | async function main() { 9 | await exec('npm run build:prod'); 10 | const packageDir = 'packaged-extension'; 11 | if (fs.existsSync(packageDir)) { 12 | await fs.rm(packageDir, { recursive: true }); 13 | } 14 | await fs.mkdir(packageDir); 15 | const zipContents = ['_locales', 'dist', 'images', 'public', 'manifest.json']; 16 | for await (const filename of zipContents) { 17 | await fs.copy(filename, `${packageDir}/${filename}`); 18 | } 19 | 20 | zipper.sync 21 | .zip(packageDir) 22 | .compress() 23 | .save(packageDir + '.zip'); 24 | 25 | await fs.rm(packageDir, { recursive: true }); 26 | console.log('Extension packaged'); 27 | } 28 | -------------------------------------------------------------------------------- /Extension/scripts/prod-build.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs/promises'); 2 | const util = require('util'); 3 | const exec = util.promisify(require('child_process').exec); 4 | 5 | main(); 6 | 7 | async function main() { 8 | await fs.rm('./dist', { recursive: true }); 9 | await fs.mkdir('./dist'); 10 | await exec('npm run build -- --prod'); 11 | console.log('Done'); 12 | } 13 | -------------------------------------------------------------------------------- /Extension/src/background.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | import { detect } from 'detect-browser'; 3 | import { 4 | type BrowserMessage, 5 | type BrowserMessageType, 6 | type ColorScheme 7 | } from './models'; 8 | import settingsConnector from './settings-connector'; 9 | 10 | console.log('background script running...'); 11 | 12 | browser.runtime.onMessage.addListener((message, sender, sendResponse) => { 13 | console.log('got message', message); 14 | switch (message.type as BrowserMessageType) { 15 | case 'gotColorScheme': { 16 | updateIcon(message.value as ColorScheme).then(sendResponse); 17 | return true; 18 | } 19 | } 20 | }); 21 | 22 | async function updateIcon(colorScheme: ColorScheme) { 23 | console.log('updating icon', colorScheme); 24 | // do work here 25 | } 26 | -------------------------------------------------------------------------------- /Extension/src/content.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | import { type BrowserMessageType, type ColorScheme } from './models'; 3 | 4 | browser.runtime.onMessage.addListener(message => { 5 | console.log('got message', message); 6 | switch (message.type as BrowserMessageType) { 7 | case 'getColorScheme': { 8 | return Promise.resolve(getColorScheme()); 9 | } 10 | } 11 | }); 12 | 13 | function getColorScheme() { 14 | let scheme: ColorScheme = 'light'; 15 | const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 16 | if (darkModeMediaQuery.matches) { 17 | scheme = 'dark'; 18 | } 19 | return scheme; 20 | } 21 | -------------------------------------------------------------------------------- /Extension/src/models.ts: -------------------------------------------------------------------------------- 1 | export type BrowserMessageType = 'getColorScheme' | 'gotColorScheme'; 2 | 3 | export type BrowserMessage = { 4 | type: BrowserMessageType; 5 | value?: any; 6 | }; 7 | 8 | export type AppSettings = { 9 | displayHelpMessage: boolean; 10 | }; 11 | 12 | export const DEFAULT_SETTINGS: AppSettings = { 13 | displayHelpMessage: true 14 | }; 15 | 16 | export type ColorScheme = 'light' | 'dark'; 17 | -------------------------------------------------------------------------------- /Extension/src/popup/popup.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

My New Web Extension

7 |
8 | 9 | 15 | -------------------------------------------------------------------------------- /Extension/src/popup/popup.ts: -------------------------------------------------------------------------------- 1 | import App from './popup.svelte'; 2 | new App({ target: document.getElementById('root') as HTMLDivElement }); 3 | -------------------------------------------------------------------------------- /Extension/src/settings-connector.ts: -------------------------------------------------------------------------------- 1 | import browser from 'webextension-polyfill'; 2 | import { type AppSettings, DEFAULT_SETTINGS } from './models'; 3 | 4 | class SettingsConnector { 5 | private static readonly settingsKey = 'settings'; 6 | async getAppSettings() { 7 | let settings = ( 8 | await browser.storage.sync.get(SettingsConnector.settingsKey) 9 | )[SettingsConnector.settingsKey] as Partial | undefined; 10 | if ( 11 | !settings || 12 | Object.keys(settings).length !== Object.keys(DEFAULT_SETTINGS).length 13 | ) { 14 | console.log('no settings found, using default settings'); 15 | await browser.storage.sync.set({ 16 | [SettingsConnector.settingsKey]: DEFAULT_SETTINGS 17 | }); 18 | settings = DEFAULT_SETTINGS; 19 | } 20 | return settings as AppSettings; 21 | } 22 | 23 | async updateSettings(newSettings: Partial) { 24 | const settings = await this.getAppSettings(); 25 | const updatedSettings = { ...settings, ...newSettings }; 26 | await browser.storage.sync.set({ 27 | [SettingsConnector.settingsKey]: updatedSettings 28 | }); 29 | return updatedSettings; 30 | } 31 | } 32 | 33 | const singleton = new SettingsConnector(); 34 | export default singleton; 35 | -------------------------------------------------------------------------------- /Extension/src/settings/settings.svelte: -------------------------------------------------------------------------------- 1 | 24 | 25 | {#if appSettings} 26 |

Settings

27 |
28 |
29 |
30 | 37 | 38 |
39 |
40 |
41 | {/if} 42 | 43 | 48 | -------------------------------------------------------------------------------- /Extension/src/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import App from './settings.svelte'; 2 | new App({ target: document.getElementById('root') as HTMLDivElement }); 3 | -------------------------------------------------------------------------------- /Extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2016", 5 | "module": "ES2022", 6 | "lib": ["ES2022", "dom"], 7 | "sourceMap": true, 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "strict": true, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "alwaysStrict": true, 14 | "resolveJsonModule": true, 15 | "importsNotUsedAsValues": "remove", 16 | "jsx": "react", 17 | "types": ["svelte"] 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["node_modules/*", "dist/*"] 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Kyle Nazario 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebExtensionTemplate 2 | 3 | Create a browser extension for Chrome, Firefox and Safari in no time. 4 | 5 | WebExtensionTemplate lets you skip the boilerplate and write a [Web Extension](https://developer.mozilla.org/en-US/docs/Glossary/WebExtensions) with [TypeScript](https://www.typescriptlang.org) and [Svelte](https://www.typescriptlang.org) or [React](https://react.dev). 6 | 7 | ## Features 8 | 9 | - Popup window written in Svelte ([or React](https://github.com/kyle-n/WebExtensionTemplate/tree/react)) when you click the extension toolbar icon 10 | - Extension settings page written in Svelte ([or React](https://github.com/kyle-n/WebExtensionTemplate/tree/react)) 11 | - Lightning-fast build process to compile source code with [esbuild](https://esbuild.github.io) 12 | - Build scripts to package the extension for Chrome, Firefox and Safari 13 | - Sourcemaps for local builds 14 | - Minifies production builds 15 | - iOS and macOS container apps written in SwiftUI for the Safari extension (no Swift / SwiftUI knowledge required to use) 16 | - [Prettier](https://prettier.io) formatting 17 | - [webextension-polyfill](https://www.npmjs.com/package/webextension-polyfill) to add a couple missing Promise-based APIs to Chrome 18 | 19 | ### Future roadmap 20 | 21 | - Separate branch with the popup and settings page written in React 22 | - Tests 23 | 24 | ## Setup 25 | 26 | First, fork this repository. Then, follow the steps below. By the end of this, you should be able to search across the project for `REPLACEME` and get no results. 27 | 28 | ### All browsers 29 | 30 | - Update `Extension/_locales/en/messages.json` with an extension name and description 31 | - Update `Extension/README.md` with your app's name 32 | - Open `Extension/package.json` and update... 33 | - `name` with your app's name 34 | - `author` with your name 35 | - `license` with the app's license 36 | - Open `Extension/public/settings.html` and update the `` with your app's name 37 | - Delete the `<link>` in `Extension/settings.html` if you don't like the provided CSS 38 | - Create a toolbar icon for your app 39 | - Should be a small, simple glyph that makes sense when monochromatic in Safari 40 | - Make versions in 16, 19, 32, 38, 48 and 72-pixel sizes saved to `Extension/images` 41 | - Should be named `toolbar_Qpx.png` where `Q` is 16, 19, etc 42 | - Make an app icon that is 1024 x 1024 43 | - Copy versions of it to `Extension/images` in 48, 96, 128, 256 and 512-pixel sizes 44 | - Should be named `app_icon_Qpx.png` , where `Q` is 48, 96, etc 45 | 46 | ### Firefox 47 | 48 | - Update `manifest.json` with a Firefox extension id (under `gecko` > `id`) in the format `appname@domain.com` 49 | 50 | ### Safari 51 | 52 | Do the following steps in Xcode. 53 | 54 | Note: “Open the project config” means double-click the app name at the top of the file view in Xcode. 55 | 56 | - [Change the Safari app name to your app’s name](https://stackoverflow.com/a/20418989) 57 | - Open `Shared (App)/Models.swift` and update `APP_NAME` with your app's name 58 | - Create a new bundle identifier in the format `com.domain.App-Name` 59 | - Open the project config and go to `AppName (iOS)` > Signing & Capabilities and update the bundle id 60 | - Repeat for the macOS app 61 | - Create a new bundle identifier. It should be your app bundle identifier with `.Extension` added onto the end. So if your app bundle ID is `com.domain.App-Name`, this should be `com.domain.App-Name.Extension` 62 | - Open the project config and go to `AppName Extension (iOS)` > Signing & Capabilities and update the bundle id with the extension bundle id 63 | - Repeat for the macOS extension 64 | - Update `MAC_EXTENSION_BUNDLE_ID` in `Shared (App)/Models.swift` with the extension bundle ID as well 65 | - Update `macOS (App)/AppDelegate.swift` with a help documentation link 66 | - Under project config > Signing & Capabilities, set the team for both apps and both extensions 67 | - Under project config > General, update the display name for iOS and macOS 68 | - Rename both files named `REPLACEME.entitlements` to be `Your App Name.entitlements` 69 | - Open the project config and to go to App Name (macOS) > Build Settings and find the setting for “Code Signing Entitlements.” Replace `REPLACEME.entitlements` with the name of your new entitlements file 70 | - Repeat for App Name Extension (macOS) > Build Settings > Code Signing Entitlements 71 | - Open the project config and go to App Name Extension (macOS) > Build Settings and find the setting for “Bundle Display Name.” Update its value with your app’s name 72 | - Repeat for App Name Extension (iOS) 73 | - Go to Product > Schemes > Manage Schemes… and update the iOS and macOS schemes with your app’s name 74 | - iOS app icon: 75 | - Add the app icon to `iOS (App)/iOS Assets` as `AppIcon` with all the required sizes 76 | - Add a copy of the app icon named `Icon.png` in `Shared (App)/Resources` 77 | - macOS app icon 78 | - Reduce the size of the app icon by 20% while keeping the canvas the same size 79 | - Add the app icon to `macOS (App)/macOS Assets` as `AppIcon` with all the required sizes 80 | 81 | ## Building your extension 82 | 83 | All `npm` commands should be run in `Extension`. Safari extensions should be built in Xcode. 84 | 85 | | Browser | Local | Production | 86 | | - | - | - | 87 | | Chrome | `npm run build` | `npm run build:chrome` | 88 | | Firefox | `npm run build` | `npm run build:firefox` | 89 | | Safari | Product > Build | Product > Archive | 90 | 91 | - `console.log` calls are stripped out of prod builds 92 | - The Chrome build script generates a zip that can be uploaded to the Chrome Web Store 93 | - The Firefox build script generates a zip for the Mozilla Add-On Store as well as a zip of the source code for the store review 94 | 95 | ## Other notes 96 | 97 | - Firefox [does not support service workers in the background](https://github.com/mozilla/web-ext/issues/2532#issuecomment-1285039773), so I would maintain a separate branch `firefox` that runs `dist/background.js` as a background script -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 308C56572A27BF1C005C001E /* iOS AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C56562A27BF1C005C001E /* iOS AppView.swift */; }; 11 | 308C56682A27BF1C005C001E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C56672A27BF1C005C001E /* AppDelegate.swift */; }; 12 | 308C56722A27BF1C005C001E /* REPLACEME Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 308C56712A27BF1C005C001E /* REPLACEME Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 13 | 308C567C2A27BF1C005C001E /* REPLACEME Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 308C567B2A27BF1C005C001E /* REPLACEME Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 14 | 308C568C2A27BF1C005C001E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 308C56432A27BF1C005C001E /* Assets.xcassets */; }; 15 | 308C568D2A27BF1C005C001E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 308C56432A27BF1C005C001E /* Assets.xcassets */; }; 16 | 308C568E2A27BF1C005C001E /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C56452A27BF1C005C001E /* SafariWebExtensionHandler.swift */; }; 17 | 308C568F2A27BF1C005C001E /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 308C56452A27BF1C005C001E /* SafariWebExtensionHandler.swift */; }; 18 | 30A8D0632A29467500FD77A6 /* iOS Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30A8D0622A29467500FD77A6 /* iOS Assets.xcassets */; }; 19 | 30A8D0642A29467500FD77A6 /* iOS Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30A8D0622A29467500FD77A6 /* iOS Assets.xcassets */; }; 20 | 30A8D0662A29469A00FD77A6 /* macOS Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30A8D0652A29469A00FD77A6 /* macOS Assets.xcassets */; }; 21 | 30A8D0672A29469A00FD77A6 /* macOS Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 30A8D0652A29469A00FD77A6 /* macOS Assets.xcassets */; }; 22 | 30DD362A2A55BF1D00361103 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DD36292A55BF1D00361103 /* Views.swift */; }; 23 | 30DD362B2A55BF1D00361103 /* Views.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DD36292A55BF1D00361103 /* Views.swift */; }; 24 | 30DD36392A55C01F00361103 /* public in Resources */ = {isa = PBXBuildFile; fileRef = 30DD362D2A55C01E00361103 /* public */; }; 25 | 30DD363A2A55C01F00361103 /* public in Resources */ = {isa = PBXBuildFile; fileRef = 30DD362D2A55C01E00361103 /* public */; }; 26 | 30DD363D2A55C01F00361103 /* images in Resources */ = {isa = PBXBuildFile; fileRef = 30DD362F2A55C01F00361103 /* images */; }; 27 | 30DD363E2A55C01F00361103 /* images in Resources */ = {isa = PBXBuildFile; fileRef = 30DD362F2A55C01F00361103 /* images */; }; 28 | 30DD363F2A55C01F00361103 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36302A55C01F00361103 /* dist */; }; 29 | 30DD36402A55C01F00361103 /* dist in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36302A55C01F00361103 /* dist */; }; 30 | 30DD36432A55C01F00361103 /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36322A55C01F00361103 /* _locales */; }; 31 | 30DD36442A55C01F00361103 /* _locales in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36322A55C01F00361103 /* _locales */; }; 32 | 30DD36472A55C01F00361103 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36342A55C01F00361103 /* manifest.json */; }; 33 | 30DD36482A55C01F00361103 /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 30DD36342A55C01F00361103 /* manifest.json */; }; 34 | 99B503372A4CC7A2006397D1 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B503362A4CC7A2006397D1 /* main.swift */; }; 35 | 99B503392A5224ED006397D1 /* macOS AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B503382A5224ED006397D1 /* macOS AppView.swift */; }; 36 | 99B5033B2A52250E006397D1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B5033A2A52250E006397D1 /* Models.swift */; }; 37 | 99B5033C2A52250E006397D1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B5033A2A52250E006397D1 /* Models.swift */; }; 38 | 99B5033D2A52250E006397D1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B5033A2A52250E006397D1 /* Models.swift */; }; 39 | 99B5033E2A52250E006397D1 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B5033A2A52250E006397D1 /* Models.swift */; }; 40 | 99E155EE2A522E1D005325D0 /* SafariConnector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E155ED2A522E1D005325D0 /* SafariConnector.swift */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | 308C56732A27BF1C005C001E /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = 308C56372A27BF1A005C001E /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = 308C56702A27BF1C005C001E; 49 | remoteInfo = "Mute Chat for BoardGameArena Extension (iOS)"; 50 | }; 51 | 308C567D2A27BF1C005C001E /* PBXContainerItemProxy */ = { 52 | isa = PBXContainerItemProxy; 53 | containerPortal = 308C56372A27BF1A005C001E /* Project object */; 54 | proxyType = 1; 55 | remoteGlobalIDString = 308C567A2A27BF1C005C001E; 56 | remoteInfo = "Mute Chat for BoardGameArena Extension (macOS)"; 57 | }; 58 | /* End PBXContainerItemProxy section */ 59 | 60 | /* Begin PBXCopyFilesBuildPhase section */ 61 | 308C56A52A27BF1C005C001E /* Embed Foundation Extensions */ = { 62 | isa = PBXCopyFilesBuildPhase; 63 | buildActionMask = 2147483647; 64 | dstPath = ""; 65 | dstSubfolderSpec = 13; 66 | files = ( 67 | 308C56722A27BF1C005C001E /* REPLACEME Extension.appex in Embed Foundation Extensions */, 68 | ); 69 | name = "Embed Foundation Extensions"; 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 308C56AC2A27BF1C005C001E /* Embed Foundation Extensions */ = { 73 | isa = PBXCopyFilesBuildPhase; 74 | buildActionMask = 2147483647; 75 | dstPath = ""; 76 | dstSubfolderSpec = 13; 77 | files = ( 78 | 308C567C2A27BF1C005C001E /* REPLACEME Extension.appex in Embed Foundation Extensions */, 79 | ); 80 | name = "Embed Foundation Extensions"; 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | /* End PBXCopyFilesBuildPhase section */ 84 | 85 | /* Begin PBXFileReference section */ 86 | 308C56432A27BF1C005C001E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 87 | 308C56452A27BF1C005C001E /* SafariWebExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariWebExtensionHandler.swift; sourceTree = "<group>"; }; 88 | 308C56532A27BF1C005C001E /* REPLACEME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = REPLACEME.app; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | 308C56562A27BF1C005C001E /* iOS AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "iOS AppView.swift"; sourceTree = "<group>"; }; 90 | 308C56602A27BF1C005C001E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 91 | 308C56652A27BF1C005C001E /* REPLACEME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = REPLACEME.app; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | 308C56672A27BF1C005C001E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 93 | 308C56712A27BF1C005C001E /* REPLACEME Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "REPLACEME Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | 308C56762A27BF1C005C001E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 95 | 308C567B2A27BF1C005C001E /* REPLACEME Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "REPLACEME Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | 308C56802A27BF1C005C001E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97 | 30A8D0622A29467500FD77A6 /* iOS Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "iOS Assets.xcassets"; sourceTree = "<group>"; }; 98 | 30A8D0652A29469A00FD77A6 /* macOS Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "macOS Assets.xcassets"; sourceTree = "<group>"; }; 99 | 30DD36292A55BF1D00361103 /* Views.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Views.swift; sourceTree = "<group>"; }; 100 | 30DD362D2A55C01E00361103 /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; name = public; path = ../../../Extension/public; sourceTree = "<group>"; }; 101 | 30DD362F2A55C01F00361103 /* images */ = {isa = PBXFileReference; lastKnownFileType = folder; name = images; path = ../../../Extension/images; sourceTree = "<group>"; }; 102 | 30DD36302A55C01F00361103 /* dist */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dist; path = ../../../Extension/dist; sourceTree = "<group>"; }; 103 | 30DD36322A55C01F00361103 /* _locales */ = {isa = PBXFileReference; lastKnownFileType = folder; name = _locales; path = ../../../Extension/_locales; sourceTree = "<group>"; }; 104 | 30DD36342A55C01F00361103 /* manifest.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = manifest.json; path = ../../../Extension/manifest.json; sourceTree = "<group>"; }; 105 | 99B503362A4CC7A2006397D1 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; }; 106 | 99B503382A5224ED006397D1 /* macOS AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "macOS AppView.swift"; sourceTree = "<group>"; }; 107 | 99B5033A2A52250E006397D1 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = "<group>"; }; 108 | 99E155ED2A522E1D005325D0 /* SafariConnector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariConnector.swift; sourceTree = "<group>"; }; 109 | 99E155EF2A5312DD005325D0 /* REPLACEME.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = REPLACEME.entitlements; sourceTree = "<group>"; }; 110 | 99E155F02A5313B1005325D0 /* REPLACEME.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = REPLACEME.entitlements; sourceTree = "<group>"; }; 111 | /* End PBXFileReference section */ 112 | 113 | /* Begin PBXFrameworksBuildPhase section */ 114 | 308C56502A27BF1C005C001E /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | 308C56622A27BF1C005C001E /* Frameworks */ = { 122 | isa = PBXFrameworksBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | ); 126 | runOnlyForDeploymentPostprocessing = 0; 127 | }; 128 | 308C566E2A27BF1C005C001E /* Frameworks */ = { 129 | isa = PBXFrameworksBuildPhase; 130 | buildActionMask = 2147483647; 131 | files = ( 132 | ); 133 | runOnlyForDeploymentPostprocessing = 0; 134 | }; 135 | 308C56782A27BF1C005C001E /* Frameworks */ = { 136 | isa = PBXFrameworksBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXFrameworksBuildPhase section */ 143 | 144 | /* Begin PBXGroup section */ 145 | 308C56362A27BF1A005C001E = { 146 | isa = PBXGroup; 147 | children = ( 148 | 308C563B2A27BF1A005C001E /* Shared (App) */, 149 | 308C56442A27BF1C005C001E /* Shared (Extension) */, 150 | 308C56552A27BF1C005C001E /* iOS (App) */, 151 | 308C56662A27BF1C005C001E /* macOS (App) */, 152 | 308C56752A27BF1C005C001E /* iOS (Extension) */, 153 | 308C567F2A27BF1C005C001E /* macOS (Extension) */, 154 | 308C56542A27BF1C005C001E /* Products */, 155 | ); 156 | sourceTree = "<group>"; 157 | }; 158 | 308C563B2A27BF1A005C001E /* Shared (App) */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 308C56432A27BF1C005C001E /* Assets.xcassets */, 162 | 99B5033A2A52250E006397D1 /* Models.swift */, 163 | 30DD36292A55BF1D00361103 /* Views.swift */, 164 | ); 165 | path = "Shared (App)"; 166 | sourceTree = "<group>"; 167 | }; 168 | 308C56442A27BF1C005C001E /* Shared (Extension) */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 30DD362C2A55BFDB00361103 /* Resources */, 172 | 308C56452A27BF1C005C001E /* SafariWebExtensionHandler.swift */, 173 | ); 174 | path = "Shared (Extension)"; 175 | sourceTree = "<group>"; 176 | }; 177 | 308C56542A27BF1C005C001E /* Products */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 308C56532A27BF1C005C001E /* REPLACEME.app */, 181 | 308C56652A27BF1C005C001E /* REPLACEME.app */, 182 | 308C56712A27BF1C005C001E /* REPLACEME Extension.appex */, 183 | 308C567B2A27BF1C005C001E /* REPLACEME Extension.appex */, 184 | ); 185 | name = Products; 186 | sourceTree = "<group>"; 187 | }; 188 | 308C56552A27BF1C005C001E /* iOS (App) */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | 308C56562A27BF1C005C001E /* iOS AppView.swift */, 192 | 308C56602A27BF1C005C001E /* Info.plist */, 193 | 30A8D0622A29467500FD77A6 /* iOS Assets.xcassets */, 194 | ); 195 | path = "iOS (App)"; 196 | sourceTree = "<group>"; 197 | }; 198 | 308C56662A27BF1C005C001E /* macOS (App) */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 308C56672A27BF1C005C001E /* AppDelegate.swift */, 202 | 99E155EF2A5312DD005325D0 /* REPLACEME.entitlements */, 203 | 30A8D0652A29469A00FD77A6 /* macOS Assets.xcassets */, 204 | 99B503362A4CC7A2006397D1 /* main.swift */, 205 | 99B503382A5224ED006397D1 /* macOS AppView.swift */, 206 | 99E155ED2A522E1D005325D0 /* SafariConnector.swift */, 207 | ); 208 | path = "macOS (App)"; 209 | sourceTree = "<group>"; 210 | }; 211 | 308C56752A27BF1C005C001E /* iOS (Extension) */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | 308C56762A27BF1C005C001E /* Info.plist */, 215 | ); 216 | path = "iOS (Extension)"; 217 | sourceTree = "<group>"; 218 | }; 219 | 308C567F2A27BF1C005C001E /* macOS (Extension) */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | 99E155F02A5313B1005325D0 /* REPLACEME.entitlements */, 223 | 308C56802A27BF1C005C001E /* Info.plist */, 224 | ); 225 | path = "macOS (Extension)"; 226 | sourceTree = "<group>"; 227 | }; 228 | 30DD362C2A55BFDB00361103 /* Resources */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | 30DD36322A55C01F00361103 /* _locales */, 232 | 30DD36302A55C01F00361103 /* dist */, 233 | 30DD362F2A55C01F00361103 /* images */, 234 | 30DD36342A55C01F00361103 /* manifest.json */, 235 | 30DD362D2A55C01E00361103 /* public */, 236 | ); 237 | path = Resources; 238 | sourceTree = "<group>"; 239 | }; 240 | /* End PBXGroup section */ 241 | 242 | /* Begin PBXNativeTarget section */ 243 | 308C56522A27BF1C005C001E /* REPLACEME (iOS) */ = { 244 | isa = PBXNativeTarget; 245 | buildConfigurationList = 308C56A62A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME (iOS)" */; 246 | buildPhases = ( 247 | 308C564F2A27BF1C005C001E /* Sources */, 248 | 308C56502A27BF1C005C001E /* Frameworks */, 249 | 308C56512A27BF1C005C001E /* Resources */, 250 | 308C56A52A27BF1C005C001E /* Embed Foundation Extensions */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | 308C56742A27BF1C005C001E /* PBXTargetDependency */, 256 | ); 257 | name = "REPLACEME (iOS)"; 258 | productName = "Mute Chat for BoardGameArena (iOS)"; 259 | productReference = 308C56532A27BF1C005C001E /* REPLACEME.app */; 260 | productType = "com.apple.product-type.application"; 261 | }; 262 | 308C56642A27BF1C005C001E /* REPLACEME (macOS) */ = { 263 | isa = PBXNativeTarget; 264 | buildConfigurationList = 308C56AD2A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME (macOS)" */; 265 | buildPhases = ( 266 | 308C56612A27BF1C005C001E /* Sources */, 267 | 308C56622A27BF1C005C001E /* Frameworks */, 268 | 308C56632A27BF1C005C001E /* Resources */, 269 | 308C56AC2A27BF1C005C001E /* Embed Foundation Extensions */, 270 | ); 271 | buildRules = ( 272 | ); 273 | dependencies = ( 274 | 308C567E2A27BF1C005C001E /* PBXTargetDependency */, 275 | ); 276 | name = "REPLACEME (macOS)"; 277 | productName = "Mute Chat for BoardGameArena (macOS)"; 278 | productReference = 308C56652A27BF1C005C001E /* REPLACEME.app */; 279 | productType = "com.apple.product-type.application"; 280 | }; 281 | 308C56702A27BF1C005C001E /* REPLACEME Extension (iOS) */ = { 282 | isa = PBXNativeTarget; 283 | buildConfigurationList = 308C56A22A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME Extension (iOS)" */; 284 | buildPhases = ( 285 | 308C566D2A27BF1C005C001E /* Sources */, 286 | 308C56B32A27C579005C001E /* Build Extension */, 287 | 308C56B42A27C5EC005C001E /* Build Extension (Production) */, 288 | 308C566E2A27BF1C005C001E /* Frameworks */, 289 | 308C566F2A27BF1C005C001E /* Resources */, 290 | ); 291 | buildRules = ( 292 | ); 293 | dependencies = ( 294 | ); 295 | name = "REPLACEME Extension (iOS)"; 296 | productName = "Mute Chat for BoardGameArena Extension (iOS)"; 297 | productReference = 308C56712A27BF1C005C001E /* REPLACEME Extension.appex */; 298 | productType = "com.apple.product-type.app-extension"; 299 | }; 300 | 308C567A2A27BF1C005C001E /* REPLACEME Extension (macOS) */ = { 301 | isa = PBXNativeTarget; 302 | buildConfigurationList = 308C56A92A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME Extension (macOS)" */; 303 | buildPhases = ( 304 | 308C56772A27BF1C005C001E /* Sources */, 305 | 308C56B52A27C651005C001E /* Build Extension */, 306 | 308C56B62A27C662005C001E /* Build Extension (Production) */, 307 | 308C56782A27BF1C005C001E /* Frameworks */, 308 | 308C56792A27BF1C005C001E /* Resources */, 309 | ); 310 | buildRules = ( 311 | ); 312 | dependencies = ( 313 | ); 314 | name = "REPLACEME Extension (macOS)"; 315 | productName = "Mute Chat for BoardGameArena Extension (macOS)"; 316 | productReference = 308C567B2A27BF1C005C001E /* REPLACEME Extension.appex */; 317 | productType = "com.apple.product-type.app-extension"; 318 | }; 319 | /* End PBXNativeTarget section */ 320 | 321 | /* Begin PBXProject section */ 322 | 308C56372A27BF1A005C001E /* Project object */ = { 323 | isa = PBXProject; 324 | attributes = { 325 | BuildIndependentTargetsInParallel = 1; 326 | LastSwiftUpdateCheck = 1430; 327 | LastUpgradeCheck = 1430; 328 | TargetAttributes = { 329 | 308C56522A27BF1C005C001E = { 330 | CreatedOnToolsVersion = 14.3; 331 | }; 332 | 308C56642A27BF1C005C001E = { 333 | CreatedOnToolsVersion = 14.3; 334 | }; 335 | 308C56702A27BF1C005C001E = { 336 | CreatedOnToolsVersion = 14.3; 337 | }; 338 | 308C567A2A27BF1C005C001E = { 339 | CreatedOnToolsVersion = 14.3; 340 | }; 341 | }; 342 | }; 343 | buildConfigurationList = 308C563A2A27BF1A005C001E /* Build configuration list for PBXProject "REPLACEME" */; 344 | compatibilityVersion = "Xcode 14.0"; 345 | developmentRegion = en; 346 | hasScannedForEncodings = 0; 347 | knownRegions = ( 348 | en, 349 | Base, 350 | ); 351 | mainGroup = 308C56362A27BF1A005C001E; 352 | productRefGroup = 308C56542A27BF1C005C001E /* Products */; 353 | projectDirPath = ""; 354 | projectRoot = ""; 355 | targets = ( 356 | 308C56522A27BF1C005C001E /* REPLACEME (iOS) */, 357 | 308C56642A27BF1C005C001E /* REPLACEME (macOS) */, 358 | 308C56702A27BF1C005C001E /* REPLACEME Extension (iOS) */, 359 | 308C567A2A27BF1C005C001E /* REPLACEME Extension (macOS) */, 360 | ); 361 | }; 362 | /* End PBXProject section */ 363 | 364 | /* Begin PBXResourcesBuildPhase section */ 365 | 308C56512A27BF1C005C001E /* Resources */ = { 366 | isa = PBXResourcesBuildPhase; 367 | buildActionMask = 2147483647; 368 | files = ( 369 | 308C568C2A27BF1C005C001E /* Assets.xcassets in Resources */, 370 | 30A8D0632A29467500FD77A6 /* iOS Assets.xcassets in Resources */, 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | 308C56632A27BF1C005C001E /* Resources */ = { 375 | isa = PBXResourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | 308C568D2A27BF1C005C001E /* Assets.xcassets in Resources */, 379 | 30A8D0662A29469A00FD77A6 /* macOS Assets.xcassets in Resources */, 380 | ); 381 | runOnlyForDeploymentPostprocessing = 0; 382 | }; 383 | 308C566F2A27BF1C005C001E /* Resources */ = { 384 | isa = PBXResourcesBuildPhase; 385 | buildActionMask = 2147483647; 386 | files = ( 387 | 30DD363D2A55C01F00361103 /* images in Resources */, 388 | 30DD363F2A55C01F00361103 /* dist in Resources */, 389 | 30DD36472A55C01F00361103 /* manifest.json in Resources */, 390 | 30A8D0642A29467500FD77A6 /* iOS Assets.xcassets in Resources */, 391 | 30DD36432A55C01F00361103 /* _locales in Resources */, 392 | 30DD36392A55C01F00361103 /* public in Resources */, 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | 308C56792A27BF1C005C001E /* Resources */ = { 397 | isa = PBXResourcesBuildPhase; 398 | buildActionMask = 2147483647; 399 | files = ( 400 | 30DD363E2A55C01F00361103 /* images in Resources */, 401 | 30DD36402A55C01F00361103 /* dist in Resources */, 402 | 30DD36482A55C01F00361103 /* manifest.json in Resources */, 403 | 30A8D0672A29469A00FD77A6 /* macOS Assets.xcassets in Resources */, 404 | 30DD36442A55C01F00361103 /* _locales in Resources */, 405 | 30DD363A2A55C01F00361103 /* public in Resources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXResourcesBuildPhase section */ 410 | 411 | /* Begin PBXShellScriptBuildPhase section */ 412 | 308C56B32A27C579005C001E /* Build Extension */ = { 413 | isa = PBXShellScriptBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | ); 417 | inputFileListPaths = ( 418 | ); 419 | inputPaths = ( 420 | ); 421 | name = "Build Extension"; 422 | outputFileListPaths = ( 423 | ); 424 | outputPaths = ( 425 | ); 426 | runOnlyForDeploymentPostprocessing = 0; 427 | shellPath = /bin/sh; 428 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd ../Extension\nnpm run build\n"; 429 | }; 430 | 308C56B42A27C5EC005C001E /* Build Extension (Production) */ = { 431 | isa = PBXShellScriptBuildPhase; 432 | buildActionMask = 8; 433 | files = ( 434 | ); 435 | inputFileListPaths = ( 436 | ); 437 | inputPaths = ( 438 | ); 439 | name = "Build Extension (Production)"; 440 | outputFileListPaths = ( 441 | ); 442 | outputPaths = ( 443 | ); 444 | runOnlyForDeploymentPostprocessing = 1; 445 | shellPath = /bin/sh; 446 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd ../Extension\nnpm run build:prod\n"; 447 | }; 448 | 308C56B52A27C651005C001E /* Build Extension */ = { 449 | isa = PBXShellScriptBuildPhase; 450 | buildActionMask = 2147483647; 451 | files = ( 452 | ); 453 | inputFileListPaths = ( 454 | ); 455 | inputPaths = ( 456 | ); 457 | name = "Build Extension"; 458 | outputFileListPaths = ( 459 | ); 460 | outputPaths = ( 461 | ); 462 | runOnlyForDeploymentPostprocessing = 0; 463 | shellPath = /bin/sh; 464 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd ../Extension\nnpm run build\n"; 465 | }; 466 | 308C56B62A27C662005C001E /* Build Extension (Production) */ = { 467 | isa = PBXShellScriptBuildPhase; 468 | buildActionMask = 8; 469 | files = ( 470 | ); 471 | inputFileListPaths = ( 472 | ); 473 | inputPaths = ( 474 | ); 475 | name = "Build Extension (Production)"; 476 | outputFileListPaths = ( 477 | ); 478 | outputPaths = ( 479 | ); 480 | runOnlyForDeploymentPostprocessing = 1; 481 | shellPath = /bin/sh; 482 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd ../Extension\nnpm run build:prod\n"; 483 | }; 484 | /* End PBXShellScriptBuildPhase section */ 485 | 486 | /* Begin PBXSourcesBuildPhase section */ 487 | 308C564F2A27BF1C005C001E /* Sources */ = { 488 | isa = PBXSourcesBuildPhase; 489 | buildActionMask = 2147483647; 490 | files = ( 491 | 99B5033B2A52250E006397D1 /* Models.swift in Sources */, 492 | 308C56572A27BF1C005C001E /* iOS AppView.swift in Sources */, 493 | 30DD362A2A55BF1D00361103 /* Views.swift in Sources */, 494 | ); 495 | runOnlyForDeploymentPostprocessing = 0; 496 | }; 497 | 308C56612A27BF1C005C001E /* Sources */ = { 498 | isa = PBXSourcesBuildPhase; 499 | buildActionMask = 2147483647; 500 | files = ( 501 | 99B503372A4CC7A2006397D1 /* main.swift in Sources */, 502 | 308C56682A27BF1C005C001E /* AppDelegate.swift in Sources */, 503 | 30DD362B2A55BF1D00361103 /* Views.swift in Sources */, 504 | 99E155EE2A522E1D005325D0 /* SafariConnector.swift in Sources */, 505 | 99B503392A5224ED006397D1 /* macOS AppView.swift in Sources */, 506 | 99B5033C2A52250E006397D1 /* Models.swift in Sources */, 507 | ); 508 | runOnlyForDeploymentPostprocessing = 0; 509 | }; 510 | 308C566D2A27BF1C005C001E /* Sources */ = { 511 | isa = PBXSourcesBuildPhase; 512 | buildActionMask = 2147483647; 513 | files = ( 514 | 308C568E2A27BF1C005C001E /* SafariWebExtensionHandler.swift in Sources */, 515 | 99B5033D2A52250E006397D1 /* Models.swift in Sources */, 516 | ); 517 | runOnlyForDeploymentPostprocessing = 0; 518 | }; 519 | 308C56772A27BF1C005C001E /* Sources */ = { 520 | isa = PBXSourcesBuildPhase; 521 | buildActionMask = 2147483647; 522 | files = ( 523 | 308C568F2A27BF1C005C001E /* SafariWebExtensionHandler.swift in Sources */, 524 | 99B5033E2A52250E006397D1 /* Models.swift in Sources */, 525 | ); 526 | runOnlyForDeploymentPostprocessing = 0; 527 | }; 528 | /* End PBXSourcesBuildPhase section */ 529 | 530 | /* Begin PBXTargetDependency section */ 531 | 308C56742A27BF1C005C001E /* PBXTargetDependency */ = { 532 | isa = PBXTargetDependency; 533 | target = 308C56702A27BF1C005C001E /* REPLACEME Extension (iOS) */; 534 | targetProxy = 308C56732A27BF1C005C001E /* PBXContainerItemProxy */; 535 | }; 536 | 308C567E2A27BF1C005C001E /* PBXTargetDependency */ = { 537 | isa = PBXTargetDependency; 538 | target = 308C567A2A27BF1C005C001E /* REPLACEME Extension (macOS) */; 539 | targetProxy = 308C567D2A27BF1C005C001E /* PBXContainerItemProxy */; 540 | }; 541 | /* End PBXTargetDependency section */ 542 | 543 | /* Begin XCBuildConfiguration section */ 544 | 308C56A02A27BF1C005C001E /* Debug */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | ALWAYS_SEARCH_USER_PATHS = NO; 548 | CLANG_ANALYZER_NONNULL = YES; 549 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 550 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 551 | CLANG_ENABLE_MODULES = YES; 552 | CLANG_ENABLE_OBJC_ARC = YES; 553 | CLANG_ENABLE_OBJC_WEAK = YES; 554 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 555 | CLANG_WARN_BOOL_CONVERSION = YES; 556 | CLANG_WARN_COMMA = YES; 557 | CLANG_WARN_CONSTANT_CONVERSION = YES; 558 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 559 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 560 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 561 | CLANG_WARN_EMPTY_BODY = YES; 562 | CLANG_WARN_ENUM_CONVERSION = YES; 563 | CLANG_WARN_INFINITE_RECURSION = YES; 564 | CLANG_WARN_INT_CONVERSION = YES; 565 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 566 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 567 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 568 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 569 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 570 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 571 | CLANG_WARN_STRICT_PROTOTYPES = YES; 572 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 573 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 574 | CLANG_WARN_UNREACHABLE_CODE = YES; 575 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 576 | COPY_PHASE_STRIP = NO; 577 | DEBUG_INFORMATION_FORMAT = dwarf; 578 | ENABLE_STRICT_OBJC_MSGSEND = YES; 579 | ENABLE_TESTABILITY = YES; 580 | GCC_C_LANGUAGE_STANDARD = gnu11; 581 | GCC_DYNAMIC_NO_PIC = NO; 582 | GCC_NO_COMMON_BLOCKS = YES; 583 | GCC_OPTIMIZATION_LEVEL = 0; 584 | GCC_PREPROCESSOR_DEFINITIONS = ( 585 | "DEBUG=1", 586 | "$(inherited)", 587 | ); 588 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 589 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 590 | GCC_WARN_UNDECLARED_SELECTOR = YES; 591 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 592 | GCC_WARN_UNUSED_FUNCTION = YES; 593 | GCC_WARN_UNUSED_VARIABLE = YES; 594 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 595 | MTL_FAST_MATH = YES; 596 | ONLY_ACTIVE_ARCH = YES; 597 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 598 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 599 | }; 600 | name = Debug; 601 | }; 602 | 308C56A12A27BF1C005C001E /* Release */ = { 603 | isa = XCBuildConfiguration; 604 | buildSettings = { 605 | ALWAYS_SEARCH_USER_PATHS = NO; 606 | CLANG_ANALYZER_NONNULL = YES; 607 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 608 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 609 | CLANG_ENABLE_MODULES = YES; 610 | CLANG_ENABLE_OBJC_ARC = YES; 611 | CLANG_ENABLE_OBJC_WEAK = YES; 612 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 613 | CLANG_WARN_BOOL_CONVERSION = YES; 614 | CLANG_WARN_COMMA = YES; 615 | CLANG_WARN_CONSTANT_CONVERSION = YES; 616 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 617 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 618 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 619 | CLANG_WARN_EMPTY_BODY = YES; 620 | CLANG_WARN_ENUM_CONVERSION = YES; 621 | CLANG_WARN_INFINITE_RECURSION = YES; 622 | CLANG_WARN_INT_CONVERSION = YES; 623 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 624 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 625 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 626 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 627 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 628 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 629 | CLANG_WARN_STRICT_PROTOTYPES = YES; 630 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 631 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 632 | CLANG_WARN_UNREACHABLE_CODE = YES; 633 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 634 | COPY_PHASE_STRIP = NO; 635 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 636 | ENABLE_NS_ASSERTIONS = NO; 637 | ENABLE_STRICT_OBJC_MSGSEND = YES; 638 | GCC_C_LANGUAGE_STANDARD = gnu11; 639 | GCC_NO_COMMON_BLOCKS = YES; 640 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 641 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 642 | GCC_WARN_UNDECLARED_SELECTOR = YES; 643 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 644 | GCC_WARN_UNUSED_FUNCTION = YES; 645 | GCC_WARN_UNUSED_VARIABLE = YES; 646 | MTL_ENABLE_DEBUG_INFO = NO; 647 | MTL_FAST_MATH = YES; 648 | SWIFT_COMPILATION_MODE = wholemodule; 649 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 650 | }; 651 | name = Release; 652 | }; 653 | 308C56A32A27BF1C005C001E /* Debug */ = { 654 | isa = XCBuildConfiguration; 655 | buildSettings = { 656 | CODE_SIGN_STYLE = Automatic; 657 | CURRENT_PROJECT_VERSION = 1; 658 | DEVELOPMENT_TEAM = ""; 659 | GENERATE_INFOPLIST_FILE = YES; 660 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 661 | INFOPLIST_KEY_CFBundleDisplayName = REPLACEME; 662 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 663 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 664 | LD_RUNPATH_SEARCH_PATHS = ( 665 | "$(inherited)", 666 | "@executable_path/Frameworks", 667 | "@executable_path/../../Frameworks", 668 | ); 669 | MARKETING_VERSION = 1.0.0; 670 | OTHER_LDFLAGS = ( 671 | "-framework", 672 | SafariServices, 673 | ); 674 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 675 | PRODUCT_NAME = "REPLACEME Extension"; 676 | SDKROOT = iphoneos; 677 | SKIP_INSTALL = YES; 678 | SWIFT_EMIT_LOC_STRINGS = YES; 679 | SWIFT_VERSION = 5.0; 680 | TARGETED_DEVICE_FAMILY = "1,2"; 681 | }; 682 | name = Debug; 683 | }; 684 | 308C56A42A27BF1C005C001E /* Release */ = { 685 | isa = XCBuildConfiguration; 686 | buildSettings = { 687 | CODE_SIGN_STYLE = Automatic; 688 | CURRENT_PROJECT_VERSION = 1; 689 | DEVELOPMENT_TEAM = ""; 690 | GENERATE_INFOPLIST_FILE = YES; 691 | INFOPLIST_FILE = "iOS (Extension)/Info.plist"; 692 | INFOPLIST_KEY_CFBundleDisplayName = REPLACEME; 693 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 694 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 695 | LD_RUNPATH_SEARCH_PATHS = ( 696 | "$(inherited)", 697 | "@executable_path/Frameworks", 698 | "@executable_path/../../Frameworks", 699 | ); 700 | MARKETING_VERSION = 1.0.0; 701 | OTHER_LDFLAGS = ( 702 | "-framework", 703 | SafariServices, 704 | ); 705 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 706 | PRODUCT_NAME = "REPLACEME Extension"; 707 | SDKROOT = iphoneos; 708 | SKIP_INSTALL = YES; 709 | SWIFT_EMIT_LOC_STRINGS = YES; 710 | SWIFT_VERSION = 5.0; 711 | TARGETED_DEVICE_FAMILY = "1,2"; 712 | VALIDATE_PRODUCT = YES; 713 | }; 714 | name = Release; 715 | }; 716 | 308C56A72A27BF1C005C001E /* Debug */ = { 717 | isa = XCBuildConfiguration; 718 | buildSettings = { 719 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 720 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 721 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 722 | CODE_SIGN_STYLE = Automatic; 723 | CURRENT_PROJECT_VERSION = 1; 724 | DEVELOPMENT_TEAM = ""; 725 | GENERATE_INFOPLIST_FILE = YES; 726 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 727 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 728 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 729 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 730 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 731 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 732 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 733 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 734 | LD_RUNPATH_SEARCH_PATHS = ( 735 | "$(inherited)", 736 | "@executable_path/Frameworks", 737 | ); 738 | MARKETING_VERSION = 1.0.0; 739 | OTHER_LDFLAGS = ( 740 | "-framework", 741 | SafariServices, 742 | "-framework", 743 | WebKit, 744 | ); 745 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 746 | PRODUCT_NAME = REPLACEME; 747 | SDKROOT = iphoneos; 748 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 749 | SUPPORTS_MACCATALYST = NO; 750 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 751 | SWIFT_EMIT_LOC_STRINGS = YES; 752 | SWIFT_VERSION = 5.0; 753 | TARGETED_DEVICE_FAMILY = "1,2"; 754 | }; 755 | name = Debug; 756 | }; 757 | 308C56A82A27BF1C005C001E /* Release */ = { 758 | isa = XCBuildConfiguration; 759 | buildSettings = { 760 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 761 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 762 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 763 | CODE_SIGN_STYLE = Automatic; 764 | CURRENT_PROJECT_VERSION = 1; 765 | DEVELOPMENT_TEAM = ""; 766 | GENERATE_INFOPLIST_FILE = YES; 767 | INFOPLIST_FILE = "iOS (App)/Info.plist"; 768 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 769 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 770 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 771 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 772 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 773 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 774 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 775 | LD_RUNPATH_SEARCH_PATHS = ( 776 | "$(inherited)", 777 | "@executable_path/Frameworks", 778 | ); 779 | MARKETING_VERSION = 1.0.0; 780 | OTHER_LDFLAGS = ( 781 | "-framework", 782 | SafariServices, 783 | "-framework", 784 | WebKit, 785 | ); 786 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 787 | PRODUCT_NAME = REPLACEME; 788 | SDKROOT = iphoneos; 789 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 790 | SUPPORTS_MACCATALYST = NO; 791 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; 792 | SWIFT_EMIT_LOC_STRINGS = YES; 793 | SWIFT_VERSION = 5.0; 794 | TARGETED_DEVICE_FAMILY = "1,2"; 795 | VALIDATE_PRODUCT = YES; 796 | }; 797 | name = Release; 798 | }; 799 | 308C56AA2A27BF1C005C001E /* Debug */ = { 800 | isa = XCBuildConfiguration; 801 | buildSettings = { 802 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/REPLACEME.entitlements"; 803 | CODE_SIGN_STYLE = Automatic; 804 | CURRENT_PROJECT_VERSION = 1; 805 | DEVELOPMENT_TEAM = ""; 806 | ENABLE_HARDENED_RUNTIME = YES; 807 | GENERATE_INFOPLIST_FILE = YES; 808 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 809 | INFOPLIST_KEY_CFBundleDisplayName = REPLACEME; 810 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 811 | LD_RUNPATH_SEARCH_PATHS = ( 812 | "$(inherited)", 813 | "@executable_path/../Frameworks", 814 | "@executable_path/../../../../Frameworks", 815 | ); 816 | MACOSX_DEPLOYMENT_TARGET = 10.14; 817 | MARKETING_VERSION = 1.0.0; 818 | OTHER_LDFLAGS = ( 819 | "-framework", 820 | SafariServices, 821 | ); 822 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 823 | PRODUCT_NAME = "REPLACEME Extension"; 824 | SDKROOT = macosx; 825 | SKIP_INSTALL = YES; 826 | SWIFT_EMIT_LOC_STRINGS = YES; 827 | SWIFT_VERSION = 5.0; 828 | }; 829 | name = Debug; 830 | }; 831 | 308C56AB2A27BF1C005C001E /* Release */ = { 832 | isa = XCBuildConfiguration; 833 | buildSettings = { 834 | CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/REPLACEME.entitlements"; 835 | CODE_SIGN_STYLE = Automatic; 836 | CURRENT_PROJECT_VERSION = 1; 837 | DEVELOPMENT_TEAM = ""; 838 | ENABLE_HARDENED_RUNTIME = YES; 839 | GENERATE_INFOPLIST_FILE = YES; 840 | INFOPLIST_FILE = "macOS (Extension)/Info.plist"; 841 | INFOPLIST_KEY_CFBundleDisplayName = REPLACEME; 842 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 843 | LD_RUNPATH_SEARCH_PATHS = ( 844 | "$(inherited)", 845 | "@executable_path/../Frameworks", 846 | "@executable_path/../../../../Frameworks", 847 | ); 848 | MACOSX_DEPLOYMENT_TARGET = 10.14; 849 | MARKETING_VERSION = 1.0.0; 850 | OTHER_LDFLAGS = ( 851 | "-framework", 852 | SafariServices, 853 | ); 854 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 855 | PRODUCT_NAME = "REPLACEME Extension"; 856 | SDKROOT = macosx; 857 | SKIP_INSTALL = YES; 858 | SWIFT_EMIT_LOC_STRINGS = YES; 859 | SWIFT_VERSION = 5.0; 860 | }; 861 | name = Release; 862 | }; 863 | 308C56AE2A27BF1C005C001E /* Debug */ = { 864 | isa = XCBuildConfiguration; 865 | buildSettings = { 866 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 867 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 868 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 869 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/REPLACEME.entitlements"; 870 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 871 | CODE_SIGN_STYLE = Automatic; 872 | CURRENT_PROJECT_VERSION = 1; 873 | DEVELOPMENT_TEAM = ""; 874 | ENABLE_HARDENED_RUNTIME = YES; 875 | GENERATE_INFOPLIST_FILE = YES; 876 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 877 | INFOPLIST_KEY_NSMainStoryboardFile = ""; 878 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 879 | LD_RUNPATH_SEARCH_PATHS = ( 880 | "$(inherited)", 881 | "@executable_path/../Frameworks", 882 | ); 883 | MACOSX_DEPLOYMENT_TARGET = 10.15; 884 | MARKETING_VERSION = 1.0.0; 885 | OTHER_LDFLAGS = ( 886 | "-framework", 887 | SafariServices, 888 | "-framework", 889 | WebKit, 890 | ); 891 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 892 | PRODUCT_NAME = REPLACEME; 893 | SDKROOT = macosx; 894 | SWIFT_EMIT_LOC_STRINGS = YES; 895 | SWIFT_VERSION = 5.0; 896 | }; 897 | name = Debug; 898 | }; 899 | 308C56AF2A27BF1C005C001E /* Release */ = { 900 | isa = XCBuildConfiguration; 901 | buildSettings = { 902 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 903 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 904 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 905 | CODE_SIGN_ENTITLEMENTS = "macOS (App)/REPLACEME.entitlements"; 906 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 907 | CODE_SIGN_STYLE = Automatic; 908 | CURRENT_PROJECT_VERSION = 1; 909 | DEVELOPMENT_TEAM = ""; 910 | ENABLE_HARDENED_RUNTIME = YES; 911 | GENERATE_INFOPLIST_FILE = YES; 912 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 913 | INFOPLIST_KEY_NSMainStoryboardFile = ""; 914 | INFOPLIST_KEY_NSPrincipalClass = NSApplication; 915 | LD_RUNPATH_SEARCH_PATHS = ( 916 | "$(inherited)", 917 | "@executable_path/../Frameworks", 918 | ); 919 | MACOSX_DEPLOYMENT_TARGET = 10.15; 920 | MARKETING_VERSION = 1.0.0; 921 | OTHER_LDFLAGS = ( 922 | "-framework", 923 | SafariServices, 924 | "-framework", 925 | WebKit, 926 | ); 927 | PRODUCT_BUNDLE_IDENTIFIER = REPLACEME; 928 | PRODUCT_NAME = REPLACEME; 929 | SDKROOT = macosx; 930 | SWIFT_EMIT_LOC_STRINGS = YES; 931 | SWIFT_VERSION = 5.0; 932 | }; 933 | name = Release; 934 | }; 935 | /* End XCBuildConfiguration section */ 936 | 937 | /* Begin XCConfigurationList section */ 938 | 308C563A2A27BF1A005C001E /* Build configuration list for PBXProject "REPLACEME" */ = { 939 | isa = XCConfigurationList; 940 | buildConfigurations = ( 941 | 308C56A02A27BF1C005C001E /* Debug */, 942 | 308C56A12A27BF1C005C001E /* Release */, 943 | ); 944 | defaultConfigurationIsVisible = 0; 945 | defaultConfigurationName = Release; 946 | }; 947 | 308C56A22A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME Extension (iOS)" */ = { 948 | isa = XCConfigurationList; 949 | buildConfigurations = ( 950 | 308C56A32A27BF1C005C001E /* Debug */, 951 | 308C56A42A27BF1C005C001E /* Release */, 952 | ); 953 | defaultConfigurationIsVisible = 0; 954 | defaultConfigurationName = Release; 955 | }; 956 | 308C56A62A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME (iOS)" */ = { 957 | isa = XCConfigurationList; 958 | buildConfigurations = ( 959 | 308C56A72A27BF1C005C001E /* Debug */, 960 | 308C56A82A27BF1C005C001E /* Release */, 961 | ); 962 | defaultConfigurationIsVisible = 0; 963 | defaultConfigurationName = Release; 964 | }; 965 | 308C56A92A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME Extension (macOS)" */ = { 966 | isa = XCConfigurationList; 967 | buildConfigurations = ( 968 | 308C56AA2A27BF1C005C001E /* Debug */, 969 | 308C56AB2A27BF1C005C001E /* Release */, 970 | ); 971 | defaultConfigurationIsVisible = 0; 972 | defaultConfigurationName = Release; 973 | }; 974 | 308C56AD2A27BF1C005C001E /* Build configuration list for PBXNativeTarget "REPLACEME (macOS)" */ = { 975 | isa = XCConfigurationList; 976 | buildConfigurations = ( 977 | 308C56AE2A27BF1C005C001E /* Debug */, 978 | 308C56AF2A27BF1C005C001E /* Release */, 979 | ); 980 | defaultConfigurationIsVisible = 0; 981 | defaultConfigurationName = Release; 982 | }; 983 | /* End XCConfigurationList section */ 984 | }; 985 | rootObject = 308C56372A27BF1A005C001E /* Project object */; 986 | } 987 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Workspace 3 | version = "1.0"> 4 | <FileRef 5 | location = "self:/Users/kylenazario/Developer/WebExtensionTemplate/Safari/REPLACEME.xcodeproj"> 6 | </FileRef> 7 | </Workspace> 8 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>IDEDidComputeMac32BitWarning</key> 6 | <true/> 7 | </dict> 8 | </plist> 9 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/xcshareddata/xcschemes/REPLACEME (iOS).xcscheme: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Scheme 3 | LastUpgradeVersion = "1430" 4 | version = "1.7"> 5 | <BuildAction 6 | parallelizeBuildables = "YES" 7 | buildImplicitDependencies = "YES"> 8 | <BuildActionEntries> 9 | <BuildActionEntry 10 | buildForTesting = "YES" 11 | buildForRunning = "YES" 12 | buildForProfiling = "YES" 13 | buildForArchiving = "YES" 14 | buildForAnalyzing = "YES"> 15 | <BuildableReference 16 | BuildableIdentifier = "primary" 17 | BlueprintIdentifier = "308C56522A27BF1C005C001E" 18 | BuildableName = "REPLACEME.app" 19 | BlueprintName = "REPLACEME (iOS)" 20 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 21 | </BuildableReference> 22 | </BuildActionEntry> 23 | </BuildActionEntries> 24 | </BuildAction> 25 | <TestAction 26 | buildConfiguration = "Debug" 27 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 28 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 29 | shouldUseLaunchSchemeArgsEnv = "YES" 30 | shouldAutocreateTestPlan = "YES"> 31 | </TestAction> 32 | <LaunchAction 33 | buildConfiguration = "Debug" 34 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 35 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 36 | launchStyle = "0" 37 | useCustomWorkingDirectory = "NO" 38 | ignoresPersistentStateOnLaunch = "NO" 39 | debugDocumentVersioning = "YES" 40 | debugServiceExtension = "internal" 41 | allowLocationSimulation = "YES"> 42 | <BuildableProductRunnable 43 | runnableDebuggingMode = "0"> 44 | <BuildableReference 45 | BuildableIdentifier = "primary" 46 | BlueprintIdentifier = "308C56522A27BF1C005C001E" 47 | BuildableName = "REPLACEME.app" 48 | BlueprintName = "REPLACEME (iOS)" 49 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 50 | </BuildableReference> 51 | </BuildableProductRunnable> 52 | </LaunchAction> 53 | <ProfileAction 54 | buildConfiguration = "Release" 55 | shouldUseLaunchSchemeArgsEnv = "YES" 56 | savedToolIdentifier = "" 57 | useCustomWorkingDirectory = "NO" 58 | debugDocumentVersioning = "YES"> 59 | <BuildableProductRunnable 60 | runnableDebuggingMode = "0"> 61 | <BuildableReference 62 | BuildableIdentifier = "primary" 63 | BlueprintIdentifier = "308C56522A27BF1C005C001E" 64 | BuildableName = "REPLACEME.app" 65 | BlueprintName = "REPLACEME (iOS)" 66 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 67 | </BuildableReference> 68 | </BuildableProductRunnable> 69 | </ProfileAction> 70 | <AnalyzeAction 71 | buildConfiguration = "Debug"> 72 | </AnalyzeAction> 73 | <ArchiveAction 74 | buildConfiguration = "Release" 75 | revealArchiveInOrganizer = "YES"> 76 | </ArchiveAction> 77 | </Scheme> 78 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/xcshareddata/xcschemes/REPLACEME (macOS).xcscheme: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <Scheme 3 | LastUpgradeVersion = "1430" 4 | version = "1.7"> 5 | <BuildAction 6 | parallelizeBuildables = "YES" 7 | buildImplicitDependencies = "YES"> 8 | <BuildActionEntries> 9 | <BuildActionEntry 10 | buildForTesting = "YES" 11 | buildForRunning = "YES" 12 | buildForProfiling = "YES" 13 | buildForArchiving = "YES" 14 | buildForAnalyzing = "YES"> 15 | <BuildableReference 16 | BuildableIdentifier = "primary" 17 | BlueprintIdentifier = "308C56642A27BF1C005C001E" 18 | BuildableName = "REPLACEME.app" 19 | BlueprintName = "REPLACEME (macOS)" 20 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 21 | </BuildableReference> 22 | </BuildActionEntry> 23 | </BuildActionEntries> 24 | </BuildAction> 25 | <TestAction 26 | buildConfiguration = "Debug" 27 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 28 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 29 | shouldUseLaunchSchemeArgsEnv = "YES" 30 | shouldAutocreateTestPlan = "YES"> 31 | </TestAction> 32 | <LaunchAction 33 | buildConfiguration = "Debug" 34 | selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" 35 | selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" 36 | launchStyle = "0" 37 | useCustomWorkingDirectory = "NO" 38 | ignoresPersistentStateOnLaunch = "NO" 39 | debugDocumentVersioning = "YES" 40 | debugServiceExtension = "internal" 41 | allowLocationSimulation = "YES"> 42 | <BuildableProductRunnable 43 | runnableDebuggingMode = "0"> 44 | <BuildableReference 45 | BuildableIdentifier = "primary" 46 | BlueprintIdentifier = "308C56642A27BF1C005C001E" 47 | BuildableName = "REPLACEME.app" 48 | BlueprintName = "REPLACEME (macOS)" 49 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 50 | </BuildableReference> 51 | </BuildableProductRunnable> 52 | </LaunchAction> 53 | <ProfileAction 54 | buildConfiguration = "Release" 55 | shouldUseLaunchSchemeArgsEnv = "YES" 56 | savedToolIdentifier = "" 57 | useCustomWorkingDirectory = "NO" 58 | debugDocumentVersioning = "YES"> 59 | <BuildableProductRunnable 60 | runnableDebuggingMode = "0"> 61 | <BuildableReference 62 | BuildableIdentifier = "primary" 63 | BlueprintIdentifier = "308C56642A27BF1C005C001E" 64 | BuildableName = "REPLACEME.app" 65 | BlueprintName = "REPLACEME (macOS)" 66 | ReferencedContainer = "container:REPLACEME.xcodeproj"> 67 | </BuildableReference> 68 | </BuildableProductRunnable> 69 | </ProfileAction> 70 | <AnalyzeAction 71 | buildConfiguration = "Debug"> 72 | </AnalyzeAction> 73 | <ArchiveAction 74 | buildConfiguration = "Release" 75 | revealArchiveInOrganizer = "YES"> 76 | </ArchiveAction> 77 | </Scheme> 78 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/xcuserdata/kylenazario.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>SchemeUserState</key> 6 | <dict> 7 | <key>Mute Chat for BoardGameArena (iOS).xcscheme_^#shared#^_</key> 8 | <dict> 9 | <key>orderHint</key> 10 | <integer>1</integer> 11 | </dict> 12 | <key>Mute Chat for BoardGameArena (macOS).xcscheme_^#shared#^_</key> 13 | <dict> 14 | <key>orderHint</key> 15 | <integer>0</integer> 16 | </dict> 17 | </dict> 18 | </dict> 19 | </plist> 20 | -------------------------------------------------------------------------------- /Safari/REPLACEME.xcodeproj/xcuserdata/tester.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>SchemeUserState</key> 6 | <dict> 7 | <key>Mute Chat for BoardGameArena (iOS).xcscheme_^#shared#^_</key> 8 | <dict> 9 | <key>orderHint</key> 10 | <integer>1</integer> 11 | </dict> 12 | <key>Mute Chat for BoardGameArena (macOS).xcscheme_^#shared#^_</key> 13 | <dict> 14 | <key>orderHint</key> 15 | <integer>0</integer> 16 | </dict> 17 | </dict> 18 | </dict> 19 | </plist> 20 | -------------------------------------------------------------------------------- /Safari/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Safari/Shared (App)/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Safari/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "3x" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Safari/Shared (App)/Models.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Models.swift 3 | // REPLACEME 4 | // 5 | // Created by Kyle Nazario on 7/2/23. 6 | // 7 | 8 | import Foundation 9 | 10 | let MAC_WINDOW_SIZE: CGFloat = 400 11 | let MAC_EXTENSION_BUNDLE_ID = "REPLACEME" 12 | let APP_NAME = "REPLACEME" 13 | -------------------------------------------------------------------------------- /Safari/Shared (App)/Views.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Views.swift 3 | // Beet Engine 4 | // 5 | // Created by tester on 7/5/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | #if os(iOS) 11 | import UIKit 12 | typealias PlatformImage = UIImage 13 | #endif 14 | #if os(macOS) 15 | import AppKit 16 | typealias PlatformImage = NSImage 17 | #endif 18 | 19 | struct AppIconView: View { 20 | private let size: CGFloat = 100 21 | let appIcon = PlatformImage(named: "AppIcon")! 22 | 23 | var body: some View { 24 | image 25 | .resizable() 26 | .frame(width: size, height: size) 27 | } 28 | 29 | private var image: Image { 30 | #if os(iOS) 31 | return Image(uiImage: appIcon) 32 | #else 33 | return Image(nsImage: appIcon) 34 | #endif 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Safari/Shared (Extension)/SafariWebExtensionHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariWebExtensionHandler.swift 3 | // Shared (Extension) 4 | // 5 | // Created by tester on 5/31/23. 6 | // 7 | 8 | import SafariServices 9 | import os.log 10 | 11 | let SFExtensionMessageKey = "message" 12 | 13 | class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling { 14 | 15 | func beginRequest(with context: NSExtensionContext) { 16 | let item = context.inputItems[0] as! NSExtensionItem 17 | let message = item.userInfo?[SFExtensionMessageKey] 18 | os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg) 19 | 20 | let response = NSExtensionItem() 21 | response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ] 22 | 23 | context.completeRequest(returningItems: [response], completionHandler: nil) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Safari/iOS (App)/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>UIApplicationSceneManifest</key> 6 | <dict> 7 | <key>UIApplicationSupportsMultipleScenes</key> 8 | <false/> 9 | <key>UISceneConfigurations</key> 10 | <dict> 11 | <key>UIWindowSceneSessionRoleApplication</key> 12 | <array> 13 | <dict> 14 | <key>UISceneConfigurationName</key> 15 | <string>Default Configuration</string> 16 | <key>UISceneDelegateClassName</key> 17 | <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string> 18 | </dict> 19 | </array> 20 | </dict> 21 | </dict> 22 | </dict> 23 | </plist> 24 | -------------------------------------------------------------------------------- /Safari/iOS (App)/iOS AppView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iOS (App) 4 | // 5 | // Created by tester on 5/31/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct AppView: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | InstructionsView() 15 | } 16 | } 17 | } 18 | 19 | struct InstructionsView: View { 20 | var body: some View { 21 | VStack { 22 | AppIconView() 23 | .cornerRadius(15) 24 | .padding(.bottom) 25 | .padding(.bottom) 26 | ForEach(steps, id: \.self) { step in 27 | Text(step) 28 | .font(.system(size: 20)) 29 | .padding(.bottom) 30 | } 31 | } 32 | } 33 | 34 | private let steps = [ 35 | "Open **Settings**", 36 | "Tap **Safari**", 37 | "Tap **Extensions**", 38 | "Tap **\(APP_NAME)**", 39 | "Enable **\(APP_NAME)**" 40 | ].map { try! AttributedString(markdown: $0) } 41 | } 42 | -------------------------------------------------------------------------------- /Safari/iOS (App)/iOS Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Safari/iOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>NSExtension</key> 6 | <dict> 7 | <key>NSExtensionPointIdentifier</key> 8 | <string>com.apple.Safari.web-extension</string> 9 | <key>NSExtensionPrincipalClass</key> 10 | <string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string> 11 | </dict> 12 | </dict> 13 | </plist> 14 | -------------------------------------------------------------------------------- /Safari/macOS (App)/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // macOS (App) 4 | // 5 | // Created by tester on 5/31/23. 6 | // 7 | 8 | import Cocoa 9 | import SwiftUI 10 | 11 | class AppDelegate: NSObject, NSApplicationDelegate { 12 | 13 | private var window: NSWindow! 14 | 15 | func applicationDidFinishLaunching(_ notification: Notification) { 16 | // Override point for customization after application launch. 17 | NSApplication.shared.helpMenu?.items.first?.action = #selector(openHelpPage) 18 | 19 | let contentRect = NSRect(x: 0, y: 0, width: 400, height: 400) 20 | let styleMask: NSWindow.StyleMask = [.miniaturizable, .closable, .resizable, .titled] 21 | let backing = NSWindow.BackingStoreType.buffered 22 | let deferVal = false 23 | window = NSWindow(contentRect: contentRect, styleMask: styleMask, backing: backing, defer: deferVal) 24 | window.center() 25 | window.makeKeyAndOrderFront(nil) 26 | 27 | window.contentView = NSHostingView(rootView: AppView().frame(width: MAC_WINDOW_SIZE, height: MAC_WINDOW_SIZE)) 28 | 29 | initAppMenu() 30 | } 31 | 32 | private func initAppMenu() { 33 | let mainMenu = NSMenu() 34 | NSApp.mainMenu = mainMenu 35 | 36 | let appMenuItem = NSMenuItem() 37 | mainMenu.addItem(appMenuItem) 38 | let appMenu = NSMenu() 39 | appMenuItem.submenu = appMenu 40 | appMenu.addItem(withTitle:"Quit \(APP_NAME)", action:#selector(NSApplication.terminate), keyEquivalent: "q") 41 | 42 | let helpMenuItem = NSMenuItem() 43 | mainMenu.addItem(helpMenuItem) 44 | let helpMenu = NSMenu(title: "Help") 45 | helpMenuItem.submenu = helpMenu 46 | helpMenu.addItem(withTitle: "\(APP_NAME) Help", action: #selector(openHelpPage), keyEquivalent: "") 47 | } 48 | 49 | @objc 50 | private func openHelpPage() { 51 | let helpLink = URL(string: "REPLACEME")! 52 | NSWorkspace.shared.open(helpLink) 53 | } 54 | 55 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 56 | return true 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Safari/macOS (App)/REPLACEME.entitlements: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>com.apple.security.app-sandbox</key> 6 | <true/> 7 | <key>com.apple.security.files.user-selected.read-only</key> 8 | <true/> 9 | <key>com.apple.security.network.client</key> 10 | <false/> 11 | </dict> 12 | </plist> 13 | -------------------------------------------------------------------------------- /Safari/macOS (App)/SafariConnector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SafariConnector.swift 3 | // REPLACEME (macOS) 4 | // 5 | // Created by Kyle Nazario on 7/2/23. 6 | // 7 | 8 | import Foundation 9 | import SafariServices 10 | 11 | enum SafariConnector { 12 | static func extensionIsEnabled() async -> Bool { 13 | do { 14 | return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Bool, Error>) in 15 | SFSafariExtensionManager.getStateOfSafariExtension(withIdentifier: MAC_EXTENSION_BUNDLE_ID) { state, error in 16 | if let error { 17 | continuation.resume(throwing: error) 18 | } else { 19 | continuation.resume(returning: state?.isEnabled ?? false) 20 | } 21 | } 22 | } 23 | } catch { 24 | return false 25 | } 26 | } 27 | 28 | static func openExtensionPrefs() async { 29 | do { 30 | return try await withCheckedThrowingContinuation({ continuation in 31 | SFSafariApplication.showPreferencesForExtension(withIdentifier: MAC_EXTENSION_BUNDLE_ID) { error in 32 | if let error { 33 | continuation.resume(throwing: error) 34 | } else { 35 | continuation.resume() 36 | } 37 | } 38 | }) 39 | } catch { 40 | return 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Safari/macOS (App)/macOS AppView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppView.swift 3 | // REPLACEME (macOS) 4 | // 5 | // Created by Kyle Nazario on 7/2/23. 6 | // 7 | 8 | import SwiftUI 9 | import SafariServices 10 | 11 | struct AppView: View { 12 | 13 | private let appIcon = NSImage(named: "AppIcon") 14 | var body: some View { 15 | VStack { 16 | AppIconView() 17 | EnabledMessageView() 18 | .padding(.vertical) 19 | SafariPrefsButton() 20 | } 21 | } 22 | } 23 | 24 | struct EnabledMessageView: View { 25 | @State private var enabled = false 26 | 27 | var body: some View { 28 | Text(message) 29 | .onAppear(perform: loadEnabled) 30 | } 31 | 32 | private var message: String { 33 | enabled ? "\(APP_NAME) is enabled" : "\(APP_NAME) is disabled. Enable it in Safari Preferences." 34 | } 35 | 36 | private func loadEnabled() { 37 | Task { 38 | self.enabled = await SafariConnector.extensionIsEnabled() 39 | } 40 | } 41 | } 42 | 43 | struct SafariPrefsButton: View { 44 | var body: some View { 45 | Button("Quit and Open Safari Preferences", action: openPrefs) 46 | } 47 | 48 | private func openPrefs() { 49 | Task { 50 | await SafariConnector.openExtensionPrefs() 51 | await NSApplication.shared.terminate(nil) 52 | } 53 | } 54 | } 55 | 56 | struct AppView_Previews: PreviewProvider { 57 | static var previews: some View { 58 | AppView() 59 | .frame(width: MAC_WINDOW_SIZE, height: MAC_WINDOW_SIZE) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Safari/macOS (App)/macOS Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Safari/macOS (App)/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // REPLACEME (macOS) 4 | // 5 | // Created by Kyle Nazario on 6/28/23. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | let app = NSApplication.shared 12 | let delegate = AppDelegate() 13 | app.delegate = delegate 14 | 15 | _ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv) 16 | -------------------------------------------------------------------------------- /Safari/macOS (Extension)/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>NSExtension</key> 6 | <dict> 7 | <key>NSExtensionPointIdentifier</key> 8 | <string>com.apple.Safari.web-extension</string> 9 | <key>NSExtensionPrincipalClass</key> 10 | <string>$(PRODUCT_MODULE_NAME).SafariWebExtensionHandler</string> 11 | </dict> 12 | </dict> 13 | </plist> 14 | -------------------------------------------------------------------------------- /Safari/macOS (Extension)/REPLACEME.entitlements: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>com.apple.security.app-sandbox</key> 6 | <true/> 7 | <key>com.apple.security.files.user-selected.read-only</key> 8 | <true/> 9 | </dict> 10 | </plist> 11 | --------------------------------------------------------------------------------