this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Experimental Shortcuts settings import/export

+551 -144
+150 -130
package-lock.json
··· 10 10 "dependencies": { 11 11 "@formatjs/intl-localematcher": "~0.4.0", 12 12 "@github/text-expander-element": "~2.5.0", 13 - "@iconify-icons/mingcute": "~1.2.6", 13 + "@iconify-icons/mingcute": "~1.2.7", 14 14 "@justinribeiro/lite-youtube": "~1.5.0", 15 - "@szhsin/react-menu": "~4.0.2", 16 - "@uidotdev/usehooks": "~2.0.1", 15 + "@szhsin/react-menu": "~4.0.3", 16 + "@uidotdev/usehooks": "~2.1.0", 17 17 "dayjs": "~1.11.9", 18 18 "dayjs-twitter": "~0.5.0", 19 19 "fast-blurhash": "~1.1.2", 20 20 "fast-deep-equal": "~3.1.3", 21 21 "idb-keyval": "~6.2.1", 22 22 "just-debounce-it": "~3.2.0", 23 + "lz-string": "^1.5.0", 23 24 "masto": "~5.11.4", 24 25 "mem": "~9.0.2", 25 26 "p-retry": "~5.1.2", 26 27 "p-throttle": "~5.1.0", 27 - "preact": "~10.16.0", 28 + "preact": "~10.17.0", 28 29 "react-hotkeys-hook": "~4.4.1", 29 30 "react-intersection-observer": "~9.5.2", 30 31 "react-quick-pinch-zoom": "~4.9.0", ··· 42 43 "@preact/preset-vite": "~2.5.0", 43 44 "@trivago/prettier-plugin-sort-imports": "~4.2.0", 44 45 "postcss": "~8.4.27", 45 - "postcss-dark-theme-class": "~0.7.3", 46 - "postcss-preset-env": "~9.1.0", 46 + "postcss-dark-theme-class": "~0.8.0", 47 + "postcss-preset-env": "~9.1.1", 47 48 "twitter-text": "~3.1.0", 48 - "vite": "~4.4.7", 49 + "vite": "~4.4.9", 49 50 "vite-plugin-generate-file": "~0.0.4", 50 51 "vite-plugin-html-config": "~1.0.11", 51 52 "vite-plugin-pwa": "~0.16.4", ··· 1965 1966 } 1966 1967 }, 1967 1968 "node_modules/@csstools/media-query-list-parser": { 1968 - "version": "2.1.3", 1969 - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.3.tgz", 1970 - "integrity": "sha512-ATul1u+pic4aVpstgueqxEv4MsObEbszAxfTXpx9LHaeD3LAh+wFqdCteyegWmjk0k5rkSCAvIOaJe9U3DD09w==", 1969 + "version": "2.1.4", 1970 + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", 1971 + "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", 1971 1972 "dev": true, 1972 1973 "funding": [ 1973 1974 { ··· 2301 2302 } 2302 2303 }, 2303 2304 "node_modules/@csstools/postcss-media-minmax": { 2304 - "version": "1.0.6", 2305 - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.6.tgz", 2306 - "integrity": "sha512-BmwKkqEzzQz6D+5ctoacsiGrq4kVgd1PMEPwkwdR0qFaL2C2nguGsWG87xEw+HIts/2yxhIPTm7Jp3DQq+wn3Q==", 2305 + "version": "1.0.7", 2306 + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.7.tgz", 2307 + "integrity": "sha512-5LGLdu8cJgRPmvkjUNqOPKIKeHbyQmoGKooB5Rh0mp5mLaNI9bl+IjFZ2keY0cztZYsriJsGf6Lu8R5XetuwoQ==", 2307 2308 "dev": true, 2308 2309 "funding": [ 2309 2310 { ··· 2319 2320 "@csstools/css-calc": "^1.1.3", 2320 2321 "@csstools/css-parser-algorithms": "^2.3.1", 2321 2322 "@csstools/css-tokenizer": "^2.2.0", 2322 - "@csstools/media-query-list-parser": "^2.1.3" 2323 + "@csstools/media-query-list-parser": "^2.1.4" 2323 2324 }, 2324 2325 "engines": { 2325 2326 "node": "^14 || ^16 || >=18" ··· 2329 2330 } 2330 2331 }, 2331 2332 "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { 2332 - "version": "2.0.1", 2333 - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.1.tgz", 2334 - "integrity": "sha512-UvMYxXT3R011whbxzRwLx7d7eNGyVsnZo7waAmf10ZGnT34XidY+rsdFnk6OdFwuG6FYqw3/tptQEAZOmUgvLw==", 2333 + "version": "2.0.2", 2334 + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.2.tgz", 2335 + "integrity": "sha512-kQJR6NvTRidsaRjCdHGjra2+fLoFiDQOm5B2aZrhmXqng/hweXjruboKzB326rxQO2L0m0T+gCKbZgyuncyhLg==", 2335 2336 "dev": true, 2336 2337 "funding": [ 2337 2338 { ··· 2346 2347 "dependencies": { 2347 2348 "@csstools/css-parser-algorithms": "^2.3.1", 2348 2349 "@csstools/css-tokenizer": "^2.2.0", 2349 - "@csstools/media-query-list-parser": "^2.1.3" 2350 + "@csstools/media-query-list-parser": "^2.1.4" 2350 2351 }, 2351 2352 "engines": { 2352 2353 "node": "^14 || ^16 || >=18" ··· 3009 3010 } 3010 3011 }, 3011 3012 "node_modules/@iconify-icons/mingcute": { 3012 - "version": "1.2.6", 3013 - "resolved": "https://registry.npmjs.org/@iconify-icons/mingcute/-/mingcute-1.2.6.tgz", 3014 - "integrity": "sha512-ToWyd3IuI+bU+q51T0GMEv7utgVksEAlzVoTSEK9GmxYG6qvUX0rKo2wMrRA4X+cv9WqDRJqJZN0pue9uUszDQ==", 3013 + "version": "1.2.7", 3014 + "resolved": "https://registry.npmjs.org/@iconify-icons/mingcute/-/mingcute-1.2.7.tgz", 3015 + "integrity": "sha512-GObX5YACRhYunL6L8nJ3PGQ+vs9vzvsx8FBZSCs2S3awMwIPKpWPVnjIlx7urnyN5qJoHZGV0iiEVjOmdHDTuw==", 3015 3016 "dependencies": { 3016 3017 "@iconify/types": "*" 3017 3018 } ··· 3249 3250 } 3250 3251 }, 3251 3252 "node_modules/@szhsin/react-menu": { 3252 - "version": "4.0.2", 3253 - "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-4.0.2.tgz", 3254 - "integrity": "sha512-cYpktkWng7jCTPKog33w5iYldbaHQso5aJFd+7j3SkhInqYWjxiG0TtxUS0c5yFqLm6woGQEJHiBpiYHIaYMxg==", 3253 + "version": "4.0.3", 3254 + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-4.0.3.tgz", 3255 + "integrity": "sha512-TPsOKLEkesE79802evnLt2Mbv/+zwRJdX8776/vxK5ST9SK8SO0A8kRrus6JuxijLxZxFpmY/3VMdoyeCWQHKA==", 3255 3256 "dependencies": { 3256 3257 "prop-types": "^15.7.2", 3257 3258 "react-transition-state": "^2.1.0" ··· 3395 3396 "dev": true 3396 3397 }, 3397 3398 "node_modules/@uidotdev/usehooks": { 3398 - "version": "2.0.1", 3399 - "resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.0.1.tgz", 3400 - "integrity": "sha512-rJXxE3Y8g9utRbOS9Pj9tIvrnOdaakHIhLbMxBlErV8HydnGD0DveD82aLBfVTh1hBp5IXqpeHpMrPE9WIT7vQ==", 3399 + "version": "2.1.0", 3400 + "resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.1.0.tgz", 3401 + "integrity": "sha512-D7SJiNQC1BOHgtE2dy2KvOtnRNaLWTFFHvcBLg7lZ8Jz7YcimxdUY3spqpvf/mVkGCuUHee8i/79p5vVkBgsYQ==", 3401 3402 "engines": { 3402 3403 "node": ">=16" 3403 3404 }, ··· 3710 3711 } 3711 3712 }, 3712 3713 "node_modules/browserslist": { 3713 - "version": "4.21.9", 3714 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", 3715 - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", 3714 + "version": "4.21.10", 3715 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", 3716 + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", 3716 3717 "dev": true, 3717 3718 "funding": [ 3718 3719 { ··· 3729 3730 } 3730 3731 ], 3731 3732 "dependencies": { 3732 - "caniuse-lite": "^1.0.30001503", 3733 - "electron-to-chromium": "^1.4.431", 3734 - "node-releases": "^2.0.12", 3733 + "caniuse-lite": "^1.0.30001517", 3734 + "electron-to-chromium": "^1.4.477", 3735 + "node-releases": "^2.0.13", 3735 3736 "update-browserslist-db": "^1.0.11" 3736 3737 }, 3737 3738 "bin": { ··· 3781 3782 } 3782 3783 }, 3783 3784 "node_modules/caniuse-lite": { 3784 - "version": "1.0.30001512", 3785 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", 3786 - "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", 3785 + "version": "1.0.30001519", 3786 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", 3787 + "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", 3787 3788 "dev": true, 3788 3789 "funding": [ 3789 3790 { ··· 4139 4140 } 4140 4141 }, 4141 4142 "node_modules/electron-to-chromium": { 4142 - "version": "1.4.451", 4143 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.451.tgz", 4144 - "integrity": "sha512-YYbXHIBxAHe3KWvGOJOuWa6f3tgow44rBW+QAuwVp2DvGqNZeE//K2MowNdWS7XE8li5cgQDrX1LdBr41LufkA==", 4143 + "version": "1.4.490", 4144 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", 4145 + "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", 4145 4146 "dev": true 4146 4147 }, 4147 4148 "node_modules/es-abstract": { ··· 5280 5281 "node": ">=10" 5281 5282 } 5282 5283 }, 5284 + "node_modules/lz-string": { 5285 + "version": "1.5.0", 5286 + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", 5287 + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", 5288 + "bin": { 5289 + "lz-string": "bin/bin.js" 5290 + } 5291 + }, 5283 5292 "node_modules/magic-string": { 5284 5293 "version": "0.25.9", 5285 5294 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", ··· 5485 5494 } 5486 5495 }, 5487 5496 "node_modules/node-releases": { 5488 - "version": "2.0.12", 5489 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", 5490 - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", 5497 + "version": "2.0.13", 5498 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 5499 + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 5491 5500 "dev": true 5492 5501 }, 5493 5502 "node_modules/normalize-range": { ··· 5862 5871 } 5863 5872 }, 5864 5873 "node_modules/postcss-dark-theme-class": { 5865 - "version": "0.7.3", 5866 - "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-0.7.3.tgz", 5867 - "integrity": "sha512-M9vtfh8ORzQsVdT9BWb+xpEDAzC7nHBn7wVc988/JkEVLPupKcUnV0jw7RZ8sSj0ovpqN1POf6PLdt19JCHfhQ==", 5874 + "version": "0.8.0", 5875 + "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-0.8.0.tgz", 5876 + "integrity": "sha512-/zyywenvSJVlG1Ie/MLkQBhoh0sTOKPQa+3exaBVAmeITuGscGZu1NuJc5qpv2+ywIkBujL9OU26bj0DdUgY2Q==", 5868 5877 "dev": true, 5878 + "funding": [ 5879 + { 5880 + "type": "opencollective", 5881 + "url": "https://opencollective.com/postcss/" 5882 + }, 5883 + { 5884 + "type": "github", 5885 + "url": "https://github.com/sponsors/ai" 5886 + } 5887 + ], 5869 5888 "engines": { 5870 - "node": ">=12.0" 5871 - }, 5872 - "funding": { 5873 - "type": "opencollective", 5874 - "url": "https://opencollective.com/postcss/" 5889 + "node": ">=18.0" 5875 5890 }, 5876 5891 "peerDependencies": { 5877 5892 "postcss": "^8.2.14" ··· 6097 6112 } 6098 6113 }, 6099 6114 "node_modules/postcss-nesting": { 6100 - "version": "12.0.0", 6101 - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.0.tgz", 6102 - "integrity": "sha512-knqwW65kxssmyIFadRSimaiRyLVRd0MdwfabesKw6XvGLwSOCJ+4zfvNQQCOOYij5obwpZzDpODuGRv2PCyiUw==", 6115 + "version": "12.0.1", 6116 + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz", 6117 + "integrity": "sha512-6LCqCWP9pqwXw/njMvNK0hGY44Fxc4B2EsGbn6xDcxbNRzP8GYoxT7yabVVMLrX3quqOJ9hg2jYMsnkedOf8pA==", 6103 6118 "dev": true, 6104 6119 "funding": [ 6105 6120 { ··· 6204 6219 } 6205 6220 }, 6206 6221 "node_modules/postcss-preset-env": { 6207 - "version": "9.1.0", 6208 - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.1.0.tgz", 6209 - "integrity": "sha512-G+x9BD7jb9uHBB7o720emXV00CP+VdWeirJsHC5ERSpbTd2e6Xg7vHzT+a6UkxFyddALuV+Q8wJMgeTKaau+Pg==", 6222 + "version": "9.1.1", 6223 + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.1.1.tgz", 6224 + "integrity": "sha512-rMPEqyTLm8JLbvaHnDAdQg6SN4Z/NDOsm+CRefg4HmSOiNpTcBXaw4RAaQbfTNe8BB75l4NpoQ6sMdrutdEpdQ==", 6210 6225 "dev": true, 6211 6226 "funding": [ 6212 6227 { ··· 6231 6246 "@csstools/postcss-logical-float-and-clear": "^2.0.0", 6232 6247 "@csstools/postcss-logical-resize": "^2.0.0", 6233 6248 "@csstools/postcss-logical-viewport-units": "^2.0.1", 6234 - "@csstools/postcss-media-minmax": "^1.0.6", 6235 - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.1", 6249 + "@csstools/postcss-media-minmax": "^1.0.7", 6250 + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.2", 6236 6251 "@csstools/postcss-nested-calc": "^3.0.0", 6237 6252 "@csstools/postcss-normalize-display-values": "^3.0.0", 6238 6253 "@csstools/postcss-oklab-function": "^3.0.1", ··· 6244 6259 "@csstools/postcss-trigonometric-functions": "^3.0.1", 6245 6260 "@csstools/postcss-unset-value": "^3.0.0", 6246 6261 "autoprefixer": "^10.4.14", 6247 - "browserslist": "^4.21.9", 6262 + "browserslist": "^4.21.10", 6248 6263 "css-blank-pseudo": "^6.0.0", 6249 6264 "css-has-pseudo": "^6.0.0", 6250 6265 "css-prefers-color-scheme": "^9.0.0", ··· 6267 6282 "postcss-initial": "^4.0.1", 6268 6283 "postcss-lab-function": "^6.0.1", 6269 6284 "postcss-logical": "^7.0.0", 6270 - "postcss-nesting": "^12.0.0", 6285 + "postcss-nesting": "^12.0.1", 6271 6286 "postcss-opacity-percentage": "^2.0.0", 6272 6287 "postcss-overflow-shorthand": "^5.0.0", 6273 6288 "postcss-page-break": "^3.0.4", ··· 6357 6372 "dev": true 6358 6373 }, 6359 6374 "node_modules/preact": { 6360 - "version": "10.16.0", 6361 - "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", 6362 - "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==", 6375 + "version": "10.17.0", 6376 + "resolved": "https://registry.npmjs.org/preact/-/preact-10.17.0.tgz", 6377 + "integrity": "sha512-SNsI8cbaCcUS5tbv9nlXuCfIXnJ9ysBMWk0WnB6UWwcVA3qZ2O6FxqDFECMAMttvLQcW/HaNZUe2BLidyvrVYw==", 6363 6378 "funding": { 6364 6379 "type": "opencollective", 6365 6380 "url": "https://opencollective.com/preact" ··· 6699 6714 } 6700 6715 }, 6701 6716 "node_modules/rollup": { 6702 - "version": "3.26.2", 6703 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", 6704 - "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==", 6717 + "version": "3.28.0", 6718 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", 6719 + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", 6705 6720 "dev": true, 6706 6721 "bin": { 6707 6722 "rollup": "dist/bin/rollup" ··· 7362 7377 } 7363 7378 }, 7364 7379 "node_modules/vite": { 7365 - "version": "4.4.7", 7366 - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", 7367 - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", 7380 + "version": "4.4.9", 7381 + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 7382 + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 7368 7383 "dev": true, 7369 7384 "dependencies": { 7370 7385 "esbuild": "^0.18.10", 7371 - "postcss": "^8.4.26", 7372 - "rollup": "^3.25.2" 7386 + "postcss": "^8.4.27", 7387 + "rollup": "^3.27.1" 7373 7388 }, 7374 7389 "bin": { 7375 7390 "vite": "bin/vite.js" ··· 9230 9245 "dev": true 9231 9246 }, 9232 9247 "@csstools/media-query-list-parser": { 9233 - "version": "2.1.3", 9234 - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.3.tgz", 9235 - "integrity": "sha512-ATul1u+pic4aVpstgueqxEv4MsObEbszAxfTXpx9LHaeD3LAh+wFqdCteyegWmjk0k5rkSCAvIOaJe9U3DD09w==", 9248 + "version": "2.1.4", 9249 + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.4.tgz", 9250 + "integrity": "sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==", 9236 9251 "dev": true, 9237 9252 "requires": {} 9238 9253 }, ··· 9359 9374 } 9360 9375 }, 9361 9376 "@csstools/postcss-media-minmax": { 9362 - "version": "1.0.6", 9363 - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.6.tgz", 9364 - "integrity": "sha512-BmwKkqEzzQz6D+5ctoacsiGrq4kVgd1PMEPwkwdR0qFaL2C2nguGsWG87xEw+HIts/2yxhIPTm7Jp3DQq+wn3Q==", 9377 + "version": "1.0.7", 9378 + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.7.tgz", 9379 + "integrity": "sha512-5LGLdu8cJgRPmvkjUNqOPKIKeHbyQmoGKooB5Rh0mp5mLaNI9bl+IjFZ2keY0cztZYsriJsGf6Lu8R5XetuwoQ==", 9365 9380 "dev": true, 9366 9381 "requires": { 9367 9382 "@csstools/css-calc": "^1.1.3", 9368 9383 "@csstools/css-parser-algorithms": "^2.3.1", 9369 9384 "@csstools/css-tokenizer": "^2.2.0", 9370 - "@csstools/media-query-list-parser": "^2.1.3" 9385 + "@csstools/media-query-list-parser": "^2.1.4" 9371 9386 } 9372 9387 }, 9373 9388 "@csstools/postcss-media-queries-aspect-ratio-number-values": { 9374 - "version": "2.0.1", 9375 - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.1.tgz", 9376 - "integrity": "sha512-UvMYxXT3R011whbxzRwLx7d7eNGyVsnZo7waAmf10ZGnT34XidY+rsdFnk6OdFwuG6FYqw3/tptQEAZOmUgvLw==", 9389 + "version": "2.0.2", 9390 + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-2.0.2.tgz", 9391 + "integrity": "sha512-kQJR6NvTRidsaRjCdHGjra2+fLoFiDQOm5B2aZrhmXqng/hweXjruboKzB326rxQO2L0m0T+gCKbZgyuncyhLg==", 9377 9392 "dev": true, 9378 9393 "requires": { 9379 9394 "@csstools/css-parser-algorithms": "^2.3.1", 9380 9395 "@csstools/css-tokenizer": "^2.2.0", 9381 - "@csstools/media-query-list-parser": "^2.1.3" 9396 + "@csstools/media-query-list-parser": "^2.1.4" 9382 9397 } 9383 9398 }, 9384 9399 "@csstools/postcss-nested-calc": { ··· 9663 9678 } 9664 9679 }, 9665 9680 "@iconify-icons/mingcute": { 9666 - "version": "1.2.6", 9667 - "resolved": "https://registry.npmjs.org/@iconify-icons/mingcute/-/mingcute-1.2.6.tgz", 9668 - "integrity": "sha512-ToWyd3IuI+bU+q51T0GMEv7utgVksEAlzVoTSEK9GmxYG6qvUX0rKo2wMrRA4X+cv9WqDRJqJZN0pue9uUszDQ==", 9681 + "version": "1.2.7", 9682 + "resolved": "https://registry.npmjs.org/@iconify-icons/mingcute/-/mingcute-1.2.7.tgz", 9683 + "integrity": "sha512-GObX5YACRhYunL6L8nJ3PGQ+vs9vzvsx8FBZSCs2S3awMwIPKpWPVnjIlx7urnyN5qJoHZGV0iiEVjOmdHDTuw==", 9669 9684 "requires": { 9670 9685 "@iconify/types": "*" 9671 9686 } ··· 9865 9880 } 9866 9881 }, 9867 9882 "@szhsin/react-menu": { 9868 - "version": "4.0.2", 9869 - "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-4.0.2.tgz", 9870 - "integrity": "sha512-cYpktkWng7jCTPKog33w5iYldbaHQso5aJFd+7j3SkhInqYWjxiG0TtxUS0c5yFqLm6woGQEJHiBpiYHIaYMxg==", 9883 + "version": "4.0.3", 9884 + "resolved": "https://registry.npmjs.org/@szhsin/react-menu/-/react-menu-4.0.3.tgz", 9885 + "integrity": "sha512-TPsOKLEkesE79802evnLt2Mbv/+zwRJdX8776/vxK5ST9SK8SO0A8kRrus6JuxijLxZxFpmY/3VMdoyeCWQHKA==", 9871 9886 "requires": { 9872 9887 "prop-types": "^15.7.2", 9873 9888 "react-transition-state": "^2.1.0" ··· 9987 10002 "dev": true 9988 10003 }, 9989 10004 "@uidotdev/usehooks": { 9990 - "version": "2.0.1", 9991 - "resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.0.1.tgz", 9992 - "integrity": "sha512-rJXxE3Y8g9utRbOS9Pj9tIvrnOdaakHIhLbMxBlErV8HydnGD0DveD82aLBfVTh1hBp5IXqpeHpMrPE9WIT7vQ==", 10005 + "version": "2.1.0", 10006 + "resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.1.0.tgz", 10007 + "integrity": "sha512-D7SJiNQC1BOHgtE2dy2KvOtnRNaLWTFFHvcBLg7lZ8Jz7YcimxdUY3spqpvf/mVkGCuUHee8i/79p5vVkBgsYQ==", 9993 10008 "requires": {} 9994 10009 }, 9995 10010 "@vue/compiler-core": { ··· 10229 10244 } 10230 10245 }, 10231 10246 "browserslist": { 10232 - "version": "4.21.9", 10233 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", 10234 - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", 10247 + "version": "4.21.10", 10248 + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", 10249 + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", 10235 10250 "dev": true, 10236 10251 "requires": { 10237 - "caniuse-lite": "^1.0.30001503", 10238 - "electron-to-chromium": "^1.4.431", 10239 - "node-releases": "^2.0.12", 10252 + "caniuse-lite": "^1.0.30001517", 10253 + "electron-to-chromium": "^1.4.477", 10254 + "node-releases": "^2.0.13", 10240 10255 "update-browserslist-db": "^1.0.11" 10241 10256 } 10242 10257 }, ··· 10271 10286 } 10272 10287 }, 10273 10288 "caniuse-lite": { 10274 - "version": "1.0.30001512", 10275 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz", 10276 - "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==", 10289 + "version": "1.0.30001519", 10290 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001519.tgz", 10291 + "integrity": "sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==", 10277 10292 "dev": true 10278 10293 }, 10279 10294 "capital-case": { ··· 10505 10520 } 10506 10521 }, 10507 10522 "electron-to-chromium": { 10508 - "version": "1.4.451", 10509 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.451.tgz", 10510 - "integrity": "sha512-YYbXHIBxAHe3KWvGOJOuWa6f3tgow44rBW+QAuwVp2DvGqNZeE//K2MowNdWS7XE8li5cgQDrX1LdBr41LufkA==", 10523 + "version": "1.4.490", 10524 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", 10525 + "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", 10511 10526 "dev": true 10512 10527 }, 10513 10528 "es-abstract": { ··· 11357 11372 "yallist": "^4.0.0" 11358 11373 } 11359 11374 }, 11375 + "lz-string": { 11376 + "version": "1.5.0", 11377 + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", 11378 + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==" 11379 + }, 11360 11380 "magic-string": { 11361 11381 "version": "0.25.9", 11362 11382 "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", ··· 11507 11527 } 11508 11528 }, 11509 11529 "node-releases": { 11510 - "version": "2.0.12", 11511 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", 11512 - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", 11530 + "version": "2.0.13", 11531 + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", 11532 + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", 11513 11533 "dev": true 11514 11534 }, 11515 11535 "normalize-range": { ··· 11719 11739 } 11720 11740 }, 11721 11741 "postcss-dark-theme-class": { 11722 - "version": "0.7.3", 11723 - "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-0.7.3.tgz", 11724 - "integrity": "sha512-M9vtfh8ORzQsVdT9BWb+xpEDAzC7nHBn7wVc988/JkEVLPupKcUnV0jw7RZ8sSj0ovpqN1POf6PLdt19JCHfhQ==", 11742 + "version": "0.8.0", 11743 + "resolved": "https://registry.npmjs.org/postcss-dark-theme-class/-/postcss-dark-theme-class-0.8.0.tgz", 11744 + "integrity": "sha512-/zyywenvSJVlG1Ie/MLkQBhoh0sTOKPQa+3exaBVAmeITuGscGZu1NuJc5qpv2+ywIkBujL9OU26bj0DdUgY2Q==", 11725 11745 "dev": true, 11726 11746 "requires": {} 11727 11747 }, ··· 11814 11834 } 11815 11835 }, 11816 11836 "postcss-nesting": { 11817 - "version": "12.0.0", 11818 - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.0.tgz", 11819 - "integrity": "sha512-knqwW65kxssmyIFadRSimaiRyLVRd0MdwfabesKw6XvGLwSOCJ+4zfvNQQCOOYij5obwpZzDpODuGRv2PCyiUw==", 11837 + "version": "12.0.1", 11838 + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-12.0.1.tgz", 11839 + "integrity": "sha512-6LCqCWP9pqwXw/njMvNK0hGY44Fxc4B2EsGbn6xDcxbNRzP8GYoxT7yabVVMLrX3quqOJ9hg2jYMsnkedOf8pA==", 11820 11840 "dev": true, 11821 11841 "requires": { 11822 11842 "@csstools/selector-specificity": "^3.0.0", ··· 11856 11876 } 11857 11877 }, 11858 11878 "postcss-preset-env": { 11859 - "version": "9.1.0", 11860 - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.1.0.tgz", 11861 - "integrity": "sha512-G+x9BD7jb9uHBB7o720emXV00CP+VdWeirJsHC5ERSpbTd2e6Xg7vHzT+a6UkxFyddALuV+Q8wJMgeTKaau+Pg==", 11879 + "version": "9.1.1", 11880 + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-9.1.1.tgz", 11881 + "integrity": "sha512-rMPEqyTLm8JLbvaHnDAdQg6SN4Z/NDOsm+CRefg4HmSOiNpTcBXaw4RAaQbfTNe8BB75l4NpoQ6sMdrutdEpdQ==", 11862 11882 "dev": true, 11863 11883 "requires": { 11864 11884 "@csstools/postcss-cascade-layers": "^4.0.0", ··· 11873 11893 "@csstools/postcss-logical-float-and-clear": "^2.0.0", 11874 11894 "@csstools/postcss-logical-resize": "^2.0.0", 11875 11895 "@csstools/postcss-logical-viewport-units": "^2.0.1", 11876 - "@csstools/postcss-media-minmax": "^1.0.6", 11877 - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.1", 11896 + "@csstools/postcss-media-minmax": "^1.0.7", 11897 + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^2.0.2", 11878 11898 "@csstools/postcss-nested-calc": "^3.0.0", 11879 11899 "@csstools/postcss-normalize-display-values": "^3.0.0", 11880 11900 "@csstools/postcss-oklab-function": "^3.0.1", ··· 11886 11906 "@csstools/postcss-trigonometric-functions": "^3.0.1", 11887 11907 "@csstools/postcss-unset-value": "^3.0.0", 11888 11908 "autoprefixer": "^10.4.14", 11889 - "browserslist": "^4.21.9", 11909 + "browserslist": "^4.21.10", 11890 11910 "css-blank-pseudo": "^6.0.0", 11891 11911 "css-has-pseudo": "^6.0.0", 11892 11912 "css-prefers-color-scheme": "^9.0.0", ··· 11909 11929 "postcss-initial": "^4.0.1", 11910 11930 "postcss-lab-function": "^6.0.1", 11911 11931 "postcss-logical": "^7.0.0", 11912 - "postcss-nesting": "^12.0.0", 11932 + "postcss-nesting": "^12.0.1", 11913 11933 "postcss-opacity-percentage": "^2.0.0", 11914 11934 "postcss-overflow-shorthand": "^5.0.0", 11915 11935 "postcss-page-break": "^3.0.4", ··· 11962 11982 "dev": true 11963 11983 }, 11964 11984 "preact": { 11965 - "version": "10.16.0", 11966 - "resolved": "https://registry.npmjs.org/preact/-/preact-10.16.0.tgz", 11967 - "integrity": "sha512-XTSj3dJ4roKIC93pald6rWuB2qQJO9gO2iLLyTe87MrjQN+HklueLsmskbywEWqCHlclgz3/M4YLL2iBr9UmMA==" 11985 + "version": "10.17.0", 11986 + "resolved": "https://registry.npmjs.org/preact/-/preact-10.17.0.tgz", 11987 + "integrity": "sha512-SNsI8cbaCcUS5tbv9nlXuCfIXnJ9ysBMWk0WnB6UWwcVA3qZ2O6FxqDFECMAMttvLQcW/HaNZUe2BLidyvrVYw==" 11968 11988 }, 11969 11989 "prettier": { 11970 11990 "version": "2.8.0", ··· 12191 12211 "dev": true 12192 12212 }, 12193 12213 "rollup": { 12194 - "version": "3.26.2", 12195 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", 12196 - "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==", 12214 + "version": "3.28.0", 12215 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", 12216 + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", 12197 12217 "dev": true, 12198 12218 "requires": { 12199 12219 "fsevents": "~2.3.2" ··· 12662 12682 } 12663 12683 }, 12664 12684 "vite": { 12665 - "version": "4.4.7", 12666 - "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", 12667 - "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", 12685 + "version": "4.4.9", 12686 + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 12687 + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 12668 12688 "dev": true, 12669 12689 "requires": { 12670 12690 "esbuild": "^0.18.10", 12671 12691 "fsevents": "~2.3.2", 12672 - "postcss": "^8.4.26", 12673 - "rollup": "^3.25.2" 12692 + "postcss": "^8.4.27", 12693 + "rollup": "^3.27.1" 12674 12694 } 12675 12695 }, 12676 12696 "vite-plugin-generate-file": {
+8 -7
package.json
··· 12 12 "dependencies": { 13 13 "@formatjs/intl-localematcher": "~0.4.0", 14 14 "@github/text-expander-element": "~2.5.0", 15 - "@iconify-icons/mingcute": "~1.2.6", 15 + "@iconify-icons/mingcute": "~1.2.7", 16 16 "@justinribeiro/lite-youtube": "~1.5.0", 17 - "@szhsin/react-menu": "~4.0.2", 18 - "@uidotdev/usehooks": "~2.0.1", 17 + "@szhsin/react-menu": "~4.0.3", 18 + "@uidotdev/usehooks": "~2.1.0", 19 19 "dayjs": "~1.11.9", 20 20 "dayjs-twitter": "~0.5.0", 21 21 "fast-blurhash": "~1.1.2", 22 22 "fast-deep-equal": "~3.1.3", 23 23 "idb-keyval": "~6.2.1", 24 24 "just-debounce-it": "~3.2.0", 25 + "lz-string": "^1.5.0", 25 26 "masto": "~5.11.4", 26 27 "mem": "~9.0.2", 27 28 "p-retry": "~5.1.2", 28 29 "p-throttle": "~5.1.0", 29 - "preact": "~10.16.0", 30 + "preact": "~10.17.0", 30 31 "react-hotkeys-hook": "~4.4.1", 31 32 "react-intersection-observer": "~9.5.2", 32 33 "react-quick-pinch-zoom": "~4.9.0", ··· 44 45 "@preact/preset-vite": "~2.5.0", 45 46 "@trivago/prettier-plugin-sort-imports": "~4.2.0", 46 47 "postcss": "~8.4.27", 47 - "postcss-dark-theme-class": "~0.7.3", 48 - "postcss-preset-env": "~9.1.0", 48 + "postcss-dark-theme-class": "~0.8.0", 49 + "postcss-preset-env": "~9.1.1", 49 50 "twitter-text": "~3.1.0", 50 - "vite": "~4.4.7", 51 + "vite": "~4.4.9", 51 52 "vite-plugin-generate-file": "~0.0.4", 52 53 "vite-plugin-html-config": "~1.0.11", 53 54 "vite-plugin-pwa": "~0.16.4",
+5
src/app.css
··· 1417 1417 .tag.collapsed { 1418 1418 margin: 0; 1419 1419 } 1420 + .tag.insignificant { 1421 + border: 1px solid var(--outline-color); 1422 + color: var(--text-insignificant-color); 1423 + background-color: var(--bg-faded-color); 1424 + } 1420 1425 .tag.danger { 1421 1426 background-color: var(--red-color); 1422 1427 }
+5
src/components/icon.jsx
··· 90 90 announce: () => import('@iconify-icons/mingcute/announcement-line'), 91 91 alert: () => import('@iconify-icons/mingcute/alert-line'), 92 92 round: () => import('@iconify-icons/mingcute/round-fill'), 93 + 'arrow-up-circle': () => 94 + import('@iconify-icons/mingcute/arrow-up-circle-line'), 95 + 'arrow-down-circle': () => 96 + import('@iconify-icons/mingcute/arrow-down-circle-line'), 97 + clipboard: () => import('@iconify-icons/mingcute/clipboard-line'), 93 98 }; 94 99 95 100 function Icon({
+49
src/components/shortcuts-settings.css
··· 126 126 display: flex; 127 127 gap: 16px; 128 128 } 129 + 130 + /* Import/Export */ 131 + 132 + #import-export-container input[type='text'] { 133 + font-family: var(--monospace-font); 134 + } 135 + #import-export-container section { 136 + margin: 8px 0; 137 + background-color: var(--bg-faded-color); 138 + border-radius: 16px; 139 + padding: 8px; 140 + } 141 + #import-export-container section h3 { 142 + margin: 0 0 8px; 143 + } 144 + #import-export-container section h3 * { 145 + vertical-align: middle; 146 + } 147 + #import-export-container section p { 148 + margin: 8px 0; 149 + } 150 + #import-export-container section details > summary { 151 + cursor: pointer; 152 + } 153 + #import-export-container .import-settings-list { 154 + border-radius: 8px; 155 + overflow: hidden; 156 + margin: 8px 0 0; 157 + padding: 0; 158 + counter-reset: index; 159 + } 160 + #import-export-container .import-settings-list li { 161 + background-color: var(--bg-blur-color); 162 + margin: 0 0 2px; 163 + padding: 8px 4px; 164 + display: flex; 165 + gap: 4px; 166 + } 167 + #import-export-container .import-settings-list li::before { 168 + content: counter(index); 169 + counter-increment: index; 170 + display: inline-block; 171 + width: 1.2em; 172 + text-align: right; 173 + margin-right: 8px; 174 + color: var(--text-insignificant-color); 175 + font-size: 90%; 176 + flex-shrink: 0; 177 + }
+326 -5
src/components/shortcuts-settings.jsx
··· 1 1 import './shortcuts-settings.css'; 2 2 3 + import { 4 + compressToEncodedURIComponent, 5 + decompressFromEncodedURIComponent, 6 + } from 'lz-string'; 3 7 import mem from 'mem'; 4 - import { useEffect, useRef, useState } from 'preact/hooks'; 8 + import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; 5 9 import { useSnapshot } from 'valtio'; 6 10 7 11 import floatingButtonUrl from '../assets/floating-button.svg'; 8 12 import multiColumnUrl from '../assets/multi-column.svg'; 9 13 import tabMenuBarUrl from '../assets/tab-menu-bar.svg'; 10 14 import { api } from '../utils/api'; 15 + import showToast from '../utils/show-toast'; 11 16 import states from '../utils/states'; 12 17 13 18 import AsyncText from './AsyncText'; 14 19 import Icon from './icon'; 20 + import MenuConfirm from './menu-confirm'; 15 21 import Modal from './modal'; 16 22 17 23 const SHORTCUTS_LIMIT = 9; ··· 202 208 const [lists, setLists] = useState([]); 203 209 const [followedHashtags, setFollowedHashtags] = useState([]); 204 210 const [showForm, setShowForm] = useState(false); 211 + const [showImportExport, setShowImportExport] = useState(false); 205 212 206 213 useEffect(() => { 207 214 (async () => { ··· 432 439 </p> 433 440 </div> 434 441 )} 442 + <p class="insignificant"> 443 + {shortcuts.length >= SHORTCUTS_LIMIT && 444 + `Max ${SHORTCUTS_LIMIT} shortcuts`} 445 + </p> 435 446 <p 436 447 style={{ 437 448 display: 'flex', ··· 439 450 alignItems: 'center', 440 451 }} 441 452 > 442 - <span class="insignificant"> 443 - {shortcuts.length >= SHORTCUTS_LIMIT && 444 - `Max ${SHORTCUTS_LIMIT} shortcuts`} 445 - </span> 453 + <button 454 + type="button" 455 + class="light" 456 + onClick={() => setShowImportExport(true)} 457 + > 458 + Import/export 459 + </button> 446 460 <button 447 461 type="button" 448 462 disabled={shortcuts.length >= SHORTCUTS_LIMIT} ··· 475 489 } 476 490 }} 477 491 onClose={() => setShowForm(false)} 492 + /> 493 + </Modal> 494 + )} 495 + {showImportExport && ( 496 + <Modal 497 + class="light" 498 + onClick={(e) => { 499 + if (e.target === e.currentTarget) { 500 + setShowImportExport(false); 501 + } 502 + }} 503 + > 504 + <ImportExport 505 + shortcuts={shortcuts} 506 + onClose={() => setShowImportExport(false)} 478 507 /> 479 508 </Modal> 480 509 )} ··· 645 674 )} 646 675 </footer> 647 676 </form> 677 + </main> 678 + </div> 679 + ); 680 + } 681 + 682 + function ImportExport({ shortcuts, onClose }) { 683 + const shortcutsStr = useMemo( 684 + () => 685 + compressToEncodedURIComponent(JSON.stringify(shortcuts.filter(Boolean))), 686 + [shortcuts], 687 + ); 688 + const [importShortcutStr, setImportShortcutStr] = useState(''); 689 + const [importUIState, setImportUIState] = useState('default'); 690 + const parsedImportShortcutStr = useMemo(() => { 691 + if (!importShortcutStr) { 692 + setImportUIState('default'); 693 + return null; 694 + } 695 + try { 696 + const parsed = JSON.parse( 697 + decompressFromEncodedURIComponent(importShortcutStr), 698 + ); 699 + // Very basic validation, I know 700 + if (!Array.isArray(parsed)) throw new Error('Not an array'); 701 + setImportUIState('default'); 702 + return parsed; 703 + } catch (err) { 704 + // Fallback to JSON string parsing 705 + // There's a chance that someone might want to import a JSON string instead of the compressed version 706 + try { 707 + const parsed = JSON.parse(importShortcutStr); 708 + if (!Array.isArray(parsed)) throw new Error('Not an array'); 709 + setImportUIState('default'); 710 + return parsed; 711 + } catch (err) { 712 + setImportUIState('error'); 713 + return null; 714 + } 715 + } 716 + }, [importShortcutStr]); 717 + const hasCurrentSettings = states.shortcuts.length > 0; 718 + 719 + return ( 720 + <div id="import-export-container" class="sheet"> 721 + {!!onClose && ( 722 + <button type="button" class="sheet-close" onClick={onClose}> 723 + <Icon icon="x" /> 724 + </button> 725 + )} 726 + <header> 727 + <h2> 728 + Import/Export{' '} 729 + <small class="ib insignificant">Shortcuts settings</small> 730 + </h2> 731 + </header> 732 + <main tabindex="-1"> 733 + <section> 734 + <h3> 735 + <Icon icon="arrow-down-circle" size="l" class="insignificant" />{' '} 736 + <span>Import</span> 737 + </h3> 738 + <p> 739 + <input 740 + type="text" 741 + name="import" 742 + placeholder="Paste settings here" 743 + class="block" 744 + onInput={(e) => { 745 + setImportShortcutStr(e.target.value); 746 + }} 747 + /> 748 + </p> 749 + {!!parsedImportShortcutStr && 750 + Array.isArray(parsedImportShortcutStr) && ( 751 + <> 752 + <p> 753 + <b>{parsedImportShortcutStr.length}</b> shortcut 754 + {parsedImportShortcutStr.length > 1 ? 's' : ''}{' '} 755 + <small class="insignificant"> 756 + ({importShortcutStr.length} characters) 757 + </small> 758 + </p> 759 + <ol class="import-settings-list"> 760 + {parsedImportShortcutStr.map((shortcut) => ( 761 + <li> 762 + <span 763 + style={{ 764 + opacity: shortcuts.some((s) => 765 + // Compare all properties 766 + Object.keys(s).every( 767 + (key) => s[key] === shortcut[key], 768 + ), 769 + ) 770 + ? 1 771 + : 0, 772 + }} 773 + > 774 + * 775 + </span> 776 + <span> 777 + {TYPE_TEXT[shortcut.type]} 778 + {shortcut.type === 'list' && ' ⚠️'}{' '} 779 + {TYPE_PARAMS[shortcut.type]?.map?.( 780 + ({ text, name, type }) => 781 + shortcut[name] ? ( 782 + <> 783 + <span class="tag collapsed insignificant"> 784 + {text}:{' '} 785 + {type === 'checkbox' 786 + ? shortcut[name] === 'on' 787 + ? '✅' 788 + : '❌' 789 + : shortcut[name]} 790 + </span>{' '} 791 + </> 792 + ) : null, 793 + )} 794 + </span> 795 + </li> 796 + ))} 797 + </ol> 798 + <p> 799 + <small>* Exists in current settings</small> 800 + <br /> 801 + <small> 802 + ⚠️ List may not work if it's from a different account. 803 + </small> 804 + </p> 805 + </> 806 + )} 807 + {importUIState === 'error' && ( 808 + <p class="error"> 809 + <small>⚠️ Invalid settings format</small> 810 + </p> 811 + )} 812 + <p> 813 + {hasCurrentSettings && ( 814 + <> 815 + <MenuConfirm 816 + confirmLabel="Append these shortcuts to current settings?" 817 + menuFooter={ 818 + <div class="footer"> 819 + Only shortcuts that don’t exist in current settings will 820 + be appended. 821 + </div> 822 + } 823 + onClick={() => { 824 + // states.shortcuts = [ 825 + // ...states.shortcuts, 826 + // ...parsedImportShortcutStr, 827 + // ]; 828 + // Append non-unique shortcuts only 829 + const nonUniqueShortcuts = parsedImportShortcutStr.filter( 830 + (shortcut) => 831 + !states.shortcuts.some((s) => 832 + // Compare all properties 833 + Object.keys(s).every( 834 + (key) => s[key] === shortcut[key], 835 + ), 836 + ), 837 + ); 838 + if (!nonUniqueShortcuts.length) { 839 + showToast('No new shortcuts to import'); 840 + return; 841 + } 842 + let newShortcuts = [ 843 + ...states.shortcuts, 844 + ...nonUniqueShortcuts, 845 + ]; 846 + const exceededLimit = newShortcuts.length > SHORTCUTS_LIMIT; 847 + if (exceededLimit) { 848 + // If exceeded, trim it 849 + newShortcuts = newShortcuts.slice(0, SHORTCUTS_LIMIT); 850 + } 851 + states.shortcuts = newShortcuts; 852 + showToast( 853 + exceededLimit 854 + ? `Shortcuts settings imported. Exceeded max ${SHORTCUTS_LIMIT}, so the rest are not imported.` 855 + : 'Shortcuts settings imported', 856 + ); 857 + onClose?.(); 858 + }} 859 + > 860 + <button 861 + type="button" 862 + class="plain2" 863 + disabled={!parsedImportShortcutStr} 864 + > 865 + Import & append… 866 + </button> 867 + </MenuConfirm>{' '} 868 + </> 869 + )} 870 + <MenuConfirm 871 + confirmLabel={ 872 + hasCurrentSettings 873 + ? 'Override current settings?' 874 + : 'Import settings?' 875 + } 876 + menuItemClassName={hasCurrentSettings ? 'danger' : undefined} 877 + onClick={() => { 878 + states.shortcuts = parsedImportShortcutStr; 879 + showToast('Shortcuts settings imported'); 880 + onClose?.(); 881 + }} 882 + > 883 + <button 884 + type="button" 885 + class="plain2" 886 + disabled={!parsedImportShortcutStr} 887 + > 888 + {hasCurrentSettings ? 'or override…' : 'Import…'} 889 + </button> 890 + </MenuConfirm> 891 + </p> 892 + </section> 893 + <section> 894 + <h3> 895 + <Icon icon="arrow-up-circle" size="l" class="insignificant" />{' '} 896 + <span>Export</span> 897 + </h3> 898 + <p> 899 + <input 900 + style={{ width: '100%' }} 901 + type="text" 902 + value={shortcutsStr} 903 + readOnly 904 + onClick={(e) => { 905 + e.target.select(); 906 + // Copy url to clipboard 907 + try { 908 + navigator.clipboard.writeText(e.target.value); 909 + showToast('Shortcuts settings copied'); 910 + } catch (e) { 911 + console.error(e); 912 + showToast('Unable to copy shortcuts settings'); 913 + } 914 + }} 915 + /> 916 + </p> 917 + <p> 918 + <button 919 + type="button" 920 + class="plain2" 921 + onClick={() => { 922 + try { 923 + navigator.clipboard.writeText(shortcutsStr); 924 + showToast('Shortcut settings copied'); 925 + } catch (e) { 926 + console.error(e); 927 + showToast('Unable to copy shortcut settings'); 928 + } 929 + }} 930 + > 931 + <Icon icon="clipboard" /> <span>Copy</span> 932 + </button>{' '} 933 + {navigator?.share && 934 + navigator?.canShare?.({ 935 + text: shortcutsStr, 936 + }) && ( 937 + <button 938 + type="button" 939 + class="plain2" 940 + onClick={() => { 941 + try { 942 + navigator.share({ 943 + text: shortcutsStr, 944 + }); 945 + } catch (e) { 946 + console.error(e); 947 + alert("Sharing doesn't seem to work."); 948 + } 949 + }} 950 + > 951 + <Icon icon="share" /> <span>Share</span> 952 + </button> 953 + )}{' '} 954 + {shortcutsStr.length > 0 && ( 955 + <small class="insignificant"> 956 + {shortcutsStr.length} characters 957 + </small> 958 + )} 959 + </p> 960 + <details> 961 + <summary class="insignificant"> 962 + <small>Raw Shortcuts settings JSON</small> 963 + </summary> 964 + <textarea style={{ width: '100%' }} rows={10} readOnly> 965 + {JSON.stringify(shortcuts.filter(Boolean), null, 2)} 966 + </textarea> 967 + </details> 968 + </section> 648 969 </main> 649 970 </div> 650 971 );
+8 -2
src/index.css
··· 11 11 --main-width: 40em; 12 12 text-size-adjust: none; 13 13 --hairline-width: 1px; 14 + --monospace-font: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', 15 + Menlo, Courier, monospace; 14 16 15 17 --blue-color: royalblue; 16 18 --purple-color: blueviolet; ··· 288 290 padding: 12px; 289 291 } 290 292 293 + :is(input[type='text'], textarea, select).block { 294 + display: block; 295 + width: 100%; 296 + } 297 + 291 298 button.small { 292 299 font-size: 90%; 293 300 padding: 4px 8px; ··· 304 311 pre code, 305 312 code { 306 313 font-size: 90%; 307 - font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', 308 - Menlo, Courier, monospace; 314 + font-family: var(--monospace-font); 309 315 } 310 316 311 317 @media (prefers-color-scheme: dark) {