summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/readme-parser.ts2
-rwxr-xr-xbuild/verify-test-urls.sh7
-rw-r--r--package-lock.json155
-rw-r--r--package.json12
-rw-r--r--source/background.ts8
-rw-r--r--source/feature-manager.tsx15
-rw-r--r--source/features/bugs-tab.tsx72
-rw-r--r--source/features/clean-conversation-filters.tsx19
-rw-r--r--source/features/clean-repo-tabs.tsx36
-rw-r--r--source/features/closing-remarks.tsx23
-rw-r--r--source/features/github-actions-indicators.tsx41
-rw-r--r--source/features/highlight-collaborators-and-own-conversations.tsx19
-rw-r--r--source/features/link-to-changelog-file.tsx50
-rw-r--r--source/features/list-prs-for-branch.tsx4
-rw-r--r--source/features/list-prs-for-file.tsx41
-rw-r--r--source/features/mark-private-orgs.tsx15
-rw-r--r--source/features/pinned-issues-update-time.tsx13
-rw-r--r--source/features/pr-commit-lines-changed.tsx21
-rw-r--r--source/features/pr-filters.tsx13
-rw-r--r--source/features/profile-gists-link.tsx17
-rw-r--r--source/features/releases-dropdown.tsx15
-rw-r--r--source/features/releases-tab.tsx28
-rw-r--r--source/features/repo-age.tsx23
-rw-r--r--source/features/rgh-feature-descriptions.tsx5
-rw-r--r--source/features/rgh-sponsor-button.tsx119
-rw-r--r--source/features/show-associated-branch-prs-on-fork.tsx41
-rw-r--r--source/features/show-open-prs-of-forks.tsx33
-rw-r--r--source/features/tags-on-commits-list.tsx10
-rw-r--r--source/features/toggle-files-button.tsx10
-rw-r--r--source/features/unreleased-commits.tsx49
-rw-r--r--source/features/user-local-time.tsx73
-rw-r--r--source/features/user-profile-follower-badge.tsx30
-rw-r--r--source/features/warn-pr-from-master.tsx4
-rw-r--r--source/github-helpers/get-default-branch.ts18
-rw-r--r--source/helpers/bisect.tsx16
-rw-r--r--source/helpers/clear-cache-handler.ts4
-rw-r--r--source/helpers/hotfix.tsx62
-rw-r--r--source/options.tsx10
-rw-r--r--source/refined-github.ts1
-rw-r--r--tsconfig.json1
-rw-r--r--webpack.config.ts6
41 files changed, 545 insertions, 596 deletions
diff --git a/build/readme-parser.ts b/build/readme-parser.ts
index 5e6e36de..45db66c8 100644
--- a/build/readme-parser.ts
+++ b/build/readme-parser.ts
@@ -1,4 +1,4 @@
-/// <reference types="../source/globals" />
+/// <reference types="../source/globals.js" />
import regexJoin from 'regex-join';
import {readFileSync} from 'node:fs';
diff --git a/build/verify-test-urls.sh b/build/verify-test-urls.sh
index dfbfd65b..3433ec6e 100755
--- a/build/verify-test-urls.sh
+++ b/build/verify-test-urls.sh
@@ -48,12 +48,11 @@ for FILE in "$@"; do
fi
done
-echo ::group::GITHUB ANNOTATIONS
-cat "$ANNOTATIONS"
-echo ::endgroup:::
-
# Don't fail PRs that edit a large number of files
if [ "$ERRORS" -ge 1 ] && [ "$ERRORS" -le 3 ]; then
+ echo ::group::GITHUB ANNOTATIONS
+ cat $ANNOTATIONS
+ echo ::endgroup:::
echo
echo VERIFICATION FAILED, "Test URLs" MISSING
exit $ERRORS
diff --git a/package-lock.json b/package-lock.json
index c340ef1d..c1d74ae7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"dom-chef": "^5.1.0",
"dom-loaded": "^3.0.0",
"doma": "^3.0.2",
- "element-ready": "^6.2.1",
+ "element-ready": "^6.2.2",
"filter-altered-clicks": "^2.0.0",
"fit-textarea": "^2.0.0",
"flat-zip": "^1.0.1",
@@ -34,7 +34,7 @@
"pretty-bytes": "^6.1.0",
"push-form": "^1.0.1",
"regex-join": "^2.0.0",
- "select-dom": "^7.1.1",
+ "select-dom": "^8.0.0",
"shorten-repo-url": "^3.0.0",
"strip-indent": "^4.0.0",
"text-field-edit": "^3.1.9001",
@@ -46,7 +46,7 @@
"webext-domain-permission-toggle": "^4.0.1",
"webext-dynamic-content-scripts": "^9.2.0",
"webext-options-sync-per-domain": "^4.0.0",
- "webext-storage-cache": "^6.0.0-2",
+ "webext-storage-cache": "^6.0.0-5",
"webextension-polyfill-global": "^0.10.1-1",
"zip-text-nodes": "^1.0.0"
},
@@ -76,10 +76,10 @@
"terser-webpack-plugin": "^5.3.6",
"tsx": "^3.12.7",
"type-fest": "^3.5.7",
- "typed-query-selector": "^2.9.1",
+ "typed-query-selector": "^2.11.0",
"typescript": "^4.9.5",
"vitest": "^0.28.4",
- "webpack": "^5.82.0",
+ "webpack": "^5.88.1",
"webpack-cli": "^5.0.1",
"xo": "^0.53.1"
},
@@ -1643,9 +1643,9 @@
}
},
"node_modules/acorn-import-assertions": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
- "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+ "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"peerDependencies": {
"acorn": "^8"
@@ -2911,9 +2911,9 @@
"dev": true
},
"node_modules/element-ready": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/element-ready/-/element-ready-6.2.1.tgz",
- "integrity": "sha512-C+3oy1RwVLO1GpOHhFerYuLp7PQ3btochVt/A5X4HEZOvIZ8LfWfguMo1boIREyV++sjyi1Zcb7QsvSjic7dpQ==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/element-ready/-/element-ready-6.2.2.tgz",
+ "integrity": "sha512-aERDpVjwsI6uSuOOwEaayPuHK6NAW3IuH11gaaquwIcwGblb9NseughqtB5yqAOsw2C65vApDecfREJZXJ8QGQ==",
"dependencies": {
"deferred-async-iterator": "^2.0.0",
"many-keys-map": "^1.0.3",
@@ -2955,9 +2955,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.13.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz",
- "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -8516,11 +8516,17 @@
"dev": true
},
"node_modules/select-dom": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-7.1.1.tgz",
- "integrity": "sha512-W4052AVb0TkSU8MgiNLiVio4WqCLpDSWPYz5RI6C5MoXfjo6b8p0v/Ld2iK9F/FO2FUqn6N0jFlBGyl2z5dzbA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-8.0.0.tgz",
+ "integrity": "sha512-U0/vlppYeRVFNv3N78I9Kj+wNfm5szZGCsFsoF8rmAOhXF9kXKdtJJeXYS/ghubvRblqjJDyRI/1aOoiJgw+0g==",
"dependencies": {
- "typed-query-selector": "^2.4.1"
+ "typed-query-selector": "^2.11.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fregante"
}
},
"node_modules/semver": {
@@ -9563,10 +9569,9 @@
}
},
"node_modules/type-fest": {
- "version": "3.5.7",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.5.7.tgz",
- "integrity": "sha512-6J4bYzb4sdkcLBty4XW7F18VPI66M4boXNE+CY40532oq2OJe6AVMB5NmjOp6skt/jw5mRjz/hLRpuglz0U+FA==",
- "dev": true,
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.11.1.tgz",
+ "integrity": "sha512-aCuRNRERRVh33lgQaJRlUxZqzfhzwTrsE98Mc3o3VXqmiaQdHacgUtJ0esp+7MvZ92qhtzKPeusaX6vIEcoreA==",
"engines": {
"node": ">=14.16"
},
@@ -9589,9 +9594,9 @@
}
},
"node_modules/typed-query-selector": {
- "version": "2.10.1",
- "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.10.1.tgz",
- "integrity": "sha512-VyNeetDghQngbLWOVyxUFB95nZF3z3QIrvdsWmuDwwYbePEup9hjZbNT1IK5YP3t8LqicnYNnDpQPW3vq7iCTQ=="
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.11.0.tgz",
+ "integrity": "sha512-qBs4sfmnLlPOyo2oSdvHbIFHe2CPgU54/1UGfSNceb7LARpIEVxUaeRX0Doje6oKpuySS2stqy90R3YrynR8Kg=="
},
"node_modules/typescript": {
"version": "4.9.5",
@@ -10062,13 +10067,17 @@
}
},
"node_modules/webext-storage-cache": {
- "version": "6.0.0-2",
- "resolved": "https://registry.npmjs.org/webext-storage-cache/-/webext-storage-cache-6.0.0-2.tgz",
- "integrity": "sha512-fez9KD5dhx7KNi9CumHlZnLcTkn1i6h1hc+fUHKC4w8s2PnEINJOXR+jHeuu/EyCkZrW7dpzahDWLKjX9+tPZw==",
+ "version": "6.0.0-5",
+ "resolved": "https://registry.npmjs.org/webext-storage-cache/-/webext-storage-cache-6.0.0-5.tgz",
+ "integrity": "sha512-uqnJz/PP6X6/Nbfag9CT7VOh1EKtE1JHmwINX6OJFSvmZPKJQR5U1hn82BBhWY6DtxOvKjZ3j+/kYCYfgsCZ9g==",
"dependencies": {
"@sindresorhus/to-milliseconds": "^2.0.0",
- "webext-detect-page": "^4.0.1",
- "webext-polyfill-kinda": "^1.0.0"
+ "type-fest": "^3.11.0",
+ "webext-detect-page": "^4.1.0",
+ "webext-polyfill-kinda": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/fregante"
@@ -10110,9 +10119,9 @@
}
},
"node_modules/webpack": {
- "version": "5.82.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.0.tgz",
- "integrity": "sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==",
+ "version": "5.88.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz",
+ "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@@ -10121,10 +10130,10 @@
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
- "acorn-import-assertions": "^1.7.6",
+ "acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.13.0",
+ "enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@@ -10134,7 +10143,7 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
- "schema-utils": "^3.1.2",
+ "schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
@@ -10256,9 +10265,9 @@
}
},
"node_modules/webpack/node_modules/schema-utils": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
- "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
@@ -12548,9 +12557,9 @@
}
},
"acorn-import-assertions": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
- "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
+ "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"dev": true,
"requires": {}
},
@@ -13485,9 +13494,9 @@
"dev": true
},
"element-ready": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/element-ready/-/element-ready-6.2.1.tgz",
- "integrity": "sha512-C+3oy1RwVLO1GpOHhFerYuLp7PQ3btochVt/A5X4HEZOvIZ8LfWfguMo1boIREyV++sjyi1Zcb7QsvSjic7dpQ==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/element-ready/-/element-ready-6.2.2.tgz",
+ "integrity": "sha512-aERDpVjwsI6uSuOOwEaayPuHK6NAW3IuH11gaaquwIcwGblb9NseughqtB5yqAOsw2C65vApDecfREJZXJ8QGQ==",
"requires": {
"deferred-async-iterator": "^2.0.0",
"many-keys-map": "^1.0.3",
@@ -13517,9 +13526,9 @@
}
},
"enhanced-resolve": {
- "version": "5.13.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz",
- "integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==",
+ "version": "5.15.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz",
+ "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@@ -17419,11 +17428,11 @@
}
},
"select-dom": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-7.1.1.tgz",
- "integrity": "sha512-W4052AVb0TkSU8MgiNLiVio4WqCLpDSWPYz5RI6C5MoXfjo6b8p0v/Ld2iK9F/FO2FUqn6N0jFlBGyl2z5dzbA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-8.0.0.tgz",
+ "integrity": "sha512-U0/vlppYeRVFNv3N78I9Kj+wNfm5szZGCsFsoF8rmAOhXF9kXKdtJJeXYS/ghubvRblqjJDyRI/1aOoiJgw+0g==",
"requires": {
- "typed-query-selector": "^2.4.1"
+ "typed-query-selector": "^2.11.0"
}
},
"semver": {
@@ -18201,10 +18210,9 @@
"dev": true
},
"type-fest": {
- "version": "3.5.7",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.5.7.tgz",
- "integrity": "sha512-6J4bYzb4sdkcLBty4XW7F18VPI66M4boXNE+CY40532oq2OJe6AVMB5NmjOp6skt/jw5mRjz/hLRpuglz0U+FA==",
- "dev": true
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.11.1.tgz",
+ "integrity": "sha512-aCuRNRERRVh33lgQaJRlUxZqzfhzwTrsE98Mc3o3VXqmiaQdHacgUtJ0esp+7MvZ92qhtzKPeusaX6vIEcoreA=="
},
"typed-array-length": {
"version": "1.0.4",
@@ -18218,9 +18226,9 @@
}
},
"typed-query-selector": {
- "version": "2.10.1",
- "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.10.1.tgz",
- "integrity": "sha512-VyNeetDghQngbLWOVyxUFB95nZF3z3QIrvdsWmuDwwYbePEup9hjZbNT1IK5YP3t8LqicnYNnDpQPW3vq7iCTQ=="
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.11.0.tgz",
+ "integrity": "sha512-qBs4sfmnLlPOyo2oSdvHbIFHe2CPgU54/1UGfSNceb7LARpIEVxUaeRX0Doje6oKpuySS2stqy90R3YrynR8Kg=="
},
"typescript": {
"version": "4.9.5",
@@ -18543,13 +18551,14 @@
"integrity": "sha512-rqQUKeBTOicej0tjDJWDQlOTnDcm9yYJTzgI+7rMdyYV4QHmYMRm+yjkcVgECkg/Wu9MboZ4lYeBPdp1Ep9WgQ=="
},
"webext-storage-cache": {
- "version": "6.0.0-2",
- "resolved": "https://registry.npmjs.org/webext-storage-cache/-/webext-storage-cache-6.0.0-2.tgz",
- "integrity": "sha512-fez9KD5dhx7KNi9CumHlZnLcTkn1i6h1hc+fUHKC4w8s2PnEINJOXR+jHeuu/EyCkZrW7dpzahDWLKjX9+tPZw==",
+ "version": "6.0.0-5",
+ "resolved": "https://registry.npmjs.org/webext-storage-cache/-/webext-storage-cache-6.0.0-5.tgz",
+ "integrity": "sha512-uqnJz/PP6X6/Nbfag9CT7VOh1EKtE1JHmwINX6OJFSvmZPKJQR5U1hn82BBhWY6DtxOvKjZ3j+/kYCYfgsCZ9g==",
"requires": {
"@sindresorhus/to-milliseconds": "^2.0.0",
- "webext-detect-page": "^4.0.1",
- "webext-polyfill-kinda": "^1.0.0"
+ "type-fest": "^3.11.0",
+ "webext-detect-page": "^4.1.0",
+ "webext-polyfill-kinda": "^1.0.2"
}
},
"webext-tools": {
@@ -18582,9 +18591,9 @@
"dev": true
},
"webpack": {
- "version": "5.82.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.0.tgz",
- "integrity": "sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==",
+ "version": "5.88.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.1.tgz",
+ "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
@@ -18593,10 +18602,10 @@
"@webassemblyjs/wasm-edit": "^1.11.5",
"@webassemblyjs/wasm-parser": "^1.11.5",
"acorn": "^8.7.1",
- "acorn-import-assertions": "^1.7.6",
+ "acorn-import-assertions": "^1.9.0",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^5.13.0",
+ "enhanced-resolve": "^5.15.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
@@ -18606,7 +18615,7 @@
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
- "schema-utils": "^3.1.2",
+ "schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.7",
"watchpack": "^2.4.0",
@@ -18630,9 +18639,9 @@
"dev": true
},
"schema-utils": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz",
- "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
diff --git a/package.json b/package.json
index 1990b90e..441b1395 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"lint:js": "xo",
"pack:safari": "xcodebuild -project 'safari/Refined GitHub.xcodeproj' -scheme 'Refined GitHub (macOS)'",
"prepare:safari": "bash build/prepare-safari-release.sh",
- "test": "run-p vitest lint:* build:* test:features",
+ "test": "run-p vitest lint:* build:* test:features --continue-on-error",
"test:features": "tsx build/verify-features.ts",
"watch": "run-p watch:* --continue-on-error",
"watch:typescript": "tsc --noEmit --watch --preserveWatchOutput",
@@ -52,7 +52,7 @@
"dom-chef": "^5.1.0",
"dom-loaded": "^3.0.0",
"doma": "^3.0.2",
- "element-ready": "^6.2.1",
+ "element-ready": "^6.2.2",
"filter-altered-clicks": "^2.0.0",
"fit-textarea": "^2.0.0",
"flat-zip": "^1.0.1",
@@ -70,7 +70,7 @@
"pretty-bytes": "^6.1.0",
"push-form": "^1.0.1",
"regex-join": "^2.0.0",
- "select-dom": "^7.1.1",
+ "select-dom": "^8.0.0",
"shorten-repo-url": "^3.0.0",
"strip-indent": "^4.0.0",
"text-field-edit": "^3.1.9001",
@@ -82,7 +82,7 @@
"webext-domain-permission-toggle": "^4.0.1",
"webext-dynamic-content-scripts": "^9.2.0",
"webext-options-sync-per-domain": "^4.0.0",
- "webext-storage-cache": "^6.0.0-2",
+ "webext-storage-cache": "^6.0.0-5",
"webextension-polyfill-global": "^0.10.1-1",
"zip-text-nodes": "^1.0.0"
},
@@ -112,10 +112,10 @@
"terser-webpack-plugin": "^5.3.6",
"tsx": "^3.12.7",
"type-fest": "^3.5.7",
- "typed-query-selector": "^2.9.1",
+ "typed-query-selector": "^2.11.0",
"typescript": "^4.9.5",
"vitest": "^0.28.4",
- "webpack": "^5.82.0",
+ "webpack": "^5.88.1",
"webpack-cli": "^5.0.1",
"xo": "^0.53.1"
},
diff --git a/source/background.ts b/source/background.ts
index fae088cd..209f722e 100644
--- a/source/background.ts
+++ b/source/background.ts
@@ -1,7 +1,7 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import {type Runtime} from 'webextension-polyfill';
import 'webext-dynamic-content-scripts';
-import cache from 'webext-storage-cache'; // Also needed to regularly clear the cache
+import {globalCache} from 'webext-storage-cache'; // Also needed to regularly clear the cache
import {isSafari} from 'webext-detect-page';
import {objectKeys} from 'ts-extras';
import addDomainPermissionToggle from 'webext-domain-permission-toggle';
@@ -89,11 +89,7 @@ browser.runtime.onInstalled.addListener(async ({reason}) => {
});
}
- // Hope that the feature was fixed in this version
- await cache.delete('hotfixes:');
- await cache.delete('style-hotfixes:');
-
if (isDevelopmentVersion()) {
- await cache.clear();
+ await globalCache.clear();
}
});
diff --git a/source/feature-manager.tsx b/source/feature-manager.tsx
index 0d951544..0855be0e 100644
--- a/source/feature-manager.tsx
+++ b/source/feature-manager.tsx
@@ -14,11 +14,10 @@ import {isFeaturePrivate} from './helpers/feature-utils.js';
import optionsStorage, {isFeatureDisabled, RGHOptions} from './options-storage.js';
import {
applyStyleHotfixes,
- getStyleHotfix,
+ styleHotfixes,
getLocalHotfixesAsOptions,
- getLocalStrings,
- updateHotfixes,
- updateLocalStrings,
+ preloadSyncLocalStrings,
+ brokenFeatures,
_,
} from './helpers/hotfix.js';
@@ -107,7 +106,7 @@ const globalReady = new Promise<RGHOptions>(async resolve => {
optionsStorage.getAll(),
getLocalHotfixesAsOptions(),
bisectFeatures(),
- getLocalStrings(),
+ preloadSyncLocalStrings(),
]);
await waitFor(() => document.body);
@@ -130,20 +129,18 @@ const globalReady = new Promise<RGHOptions>(async resolve => {
document.documentElement.classList.add('refined-github');
- void getStyleHotfix(version).then(applyStyleHotfixes);
+ void styleHotfixes.get(version).then(applyStyleHotfixes);
if (options.customCSS.trim().length > 0) {
// Review #5857 and #5493 before making changes
document.head.append(<style>{options.customCSS}</style>);
}
- void updateLocalStrings();
-
if (bisectedFeatures) {
Object.assign(options, bisectedFeatures);
} else {
// If features are remotely marked as "seriously breaking" by the maintainers, disable them without having to wait for proper updates to propagate #3529
- void updateHotfixes(version);
+ void brokenFeatures.get();
Object.assign(options, localHotfixes);
}
diff --git a/source/features/bugs-tab.tsx b/source/features/bugs-tab.tsx
index 201bd1ff..86fb7e42 100644
--- a/source/features/bugs-tab.tsx
+++ b/source/features/bugs-tab.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {BugIcon} from '@primer/octicons-react';
import elementReady from 'element-ready';
@@ -7,16 +7,18 @@ import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
import api from '../github-helpers/api.js';
-import {cacheByRepo, getRepo} from '../github-helpers/index.js';
+import {cacheByRepo} from '../github-helpers/index.js';
import SearchQuery from '../github-helpers/search-query.js';
import abbreviateNumber from '../helpers/abbreviate-number.js';
import {highlightTab, unhighlightTab} from '../helpers/dom-utils.js';
import isBugLabel from '../github-helpers/bugs-label.js';
-const getBugLabelCacheKey = (): string => 'bugs-label:' + getRepo()!.nameWithOwner;
-const getBugLabel = async (): Promise<string | undefined> => cache.get<string>(getBugLabelCacheKey());
+type Bugs = {
+ label: string;
+ count: number;
+};
-async function countBugsWithUnknownLabel(): Promise<number> {
+async function countBugs(): Promise<Bugs> {
const {repository} = await api.v4(`
repository() {
labels(query: "bug", first: 10) {
@@ -30,45 +32,32 @@ async function countBugsWithUnknownLabel(): Promise<number> {
}
`);
- const label: AnyObject | undefined = repository.labels.nodes
- .find((label: AnyObject) => isBugLabel(label.name));
- if (!label) {
- return 0;
+ // Prefer native "bug" label
+ for (const label of repository.labels.nodes) {
+ if (label.name === 'bug') {
+ return {label: 'bug', count: label.issues.totalCount ?? 0};
+ }
}
- void cache.set(getBugLabelCacheKey(), label.name ?? false);
- return label.issues.totalCount ?? 0;
-}
-
-async function countIssuesWithLabel(label: string): Promise<number> {
- const {repository} = await api.v4(`
- query bugIssueCount($owner: String!, $name: String!, $label: String!) {
- repository(owner: $owner, name: $name) {
- label(name: $label) {
- issues(states: OPEN) {
- totalCount
- }
- }
- }
+ for (const label of repository.labels.nodes) {
+ if (isBugLabel(label.name)) {
+ return {label: label.name, count: label.issues.totalCount ?? 0};
}
- `, {variables: {label}});
+ }
- return repository.label?.issues.totalCount ?? 0;
+ return {label: '', count: 0};
}
-const countBugs = cache.function('bugs', async (): Promise<number> => {
- const bugLabel = await getBugLabel();
- return bugLabel
- ? countIssuesWithLabel(bugLabel)
- : countBugsWithUnknownLabel();
-}, {
+const bugs = new CachedFunction('bugs', {
+ updater: countBugs,
maxAge: {minutes: 30},
staleWhileRevalidate: {days: 4},
cacheKey: cacheByRepo,
});
async function getSearchQueryBugLabel(): Promise<string> {
- return 'label:' + SearchQuery.escapeValue(await getBugLabel() ?? 'bug');
+ const {label} = await bugs.getCached() ?? {};
+ return 'label:' + SearchQuery.escapeValue(label ?? 'bug');
}
async function isBugsListing(): Promise<boolean> {
@@ -77,15 +66,18 @@ async function isBugsListing(): Promise<boolean> {
async function addBugsTab(): Promise<void | false> {
// Query API as early as possible, even if it's not necessary on archived repos
- const countPromise = countBugs();
+ const bugsPromise = bugs.get();
// On a label:bug listing:
// - always show the tab, as soon as possible
// - update the count later
// On other pages:
// - only show the tab if needed
- if (!await isBugsListing() && await countPromise === 0) {
- return false;
+ if (!await isBugsListing()) {
+ const {count} = await bugsPromise;
+ if (count === 0) {
+ return false;
+ }
}
const issuesTab = await elementReady('a.UnderlineNav-item[data-hotkey="g i"]', {waitForChildren: false});
@@ -130,7 +122,7 @@ async function addBugsTab(): Promise<void | false> {
// Update bugs count
try {
- const bugCount = await countPromise;
+ const {count: bugCount} = await bugsPromise;
bugsCounter.textContent = abbreviateNumber(bugCount);
bugsCounter.title = bugCount > 999 ? String(bugCount) : '';
} catch (error) {
@@ -151,13 +143,13 @@ async function removePinnedIssues(): Promise<void> {
}
async function updateBugsTagHighlighting(): Promise<void | false> {
- if (await countBugs() === 0) {
+ const {count, label} = await bugs.get();
+ if (count === 0) {
return false;
}
- const bugLabel = await getBugLabel() ?? 'bug';
if (
- (pageDetect.isRepoTaxonomyIssueOrPRList() && location.href.endsWith('/labels/' + encodeURIComponent(bugLabel)))
+ (pageDetect.isRepoTaxonomyIssueOrPRList() && location.href.endsWith('/labels/' + encodeURIComponent(label)))
|| (pageDetect.isRepoIssueList() && await isBugsListing())
) {
void removePinnedIssues();
@@ -165,7 +157,7 @@ async function updateBugsTagHighlighting(): Promise<void | false> {
return;
}
- if (pageDetect.isIssue() && await elementReady(`#partial-discussion-sidebar .IssueLabel[data-name="${bugLabel}"]`)) {
+ if (pageDetect.isIssue() && await elementReady(`#partial-discussion-sidebar .IssueLabel[data-name="${label}"]`)) {
highlightBugsTab();
return;
}
diff --git a/source/features/clean-conversation-filters.tsx b/source/features/clean-conversation-filters.tsx
index e921ef80..80c4b9b4 100644
--- a/source/features/clean-conversation-filters.tsx
+++ b/source/features/clean-conversation-filters.tsx
@@ -1,4 +1,4 @@
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
@@ -7,9 +7,10 @@ import features from '../feature-manager.js';
import api, {expectTokenScope} from '../github-helpers/api.js';
import {cacheByRepo} from '../github-helpers/index.js';
-const hasAnyProjects = cache.function('has-projects', async (): Promise<boolean> => {
- await expectTokenScope('read:project');
- const {repository, organization} = await api.v4(`
+const hasAnyProjects = new CachedFunction('has-projects', {
+ async updater(): Promise<boolean> {
+ await expectTokenScope('read:project');
+ const {repository, organization} = await api.v4(`
query hasAnyProjects($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
projects { totalCount }
@@ -21,14 +22,14 @@ const hasAnyProjects = cache.function('has-projects', async (): Promise<boolean>
}
}
`, {
- allowErrors: true,
- });
+ allowErrors: true,
+ });
- return Boolean(repository.projects.totalCount)
+ return Boolean(repository.projects.totalCount)
|| Boolean(repository.projectsV2.totalCount)
|| Boolean(organization?.projects?.totalCount)
|| Boolean(organization?.projectsV2?.totalCount);
-}, {
+ },
maxAge: {days: 1},
staleWhileRevalidate: {days: 20},
cacheKey: cacheByRepo,
@@ -58,7 +59,7 @@ async function hasProjects(): Promise<boolean> {
return false;
}
- return hasAnyProjects();
+ return hasAnyProjects.get();
}
async function hideProjects(): Promise<void> {
diff --git a/source/features/clean-repo-tabs.tsx b/source/features/clean-repo-tabs.tsx
index 0fe52514..ab1b1b8d 100644
--- a/source/features/clean-repo-tabs.tsx
+++ b/source/features/clean-repo-tabs.tsx
@@ -1,4 +1,4 @@
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
@@ -46,23 +46,25 @@ function onlyShowInDropdown(id: string): void {
select('.UnderlineNav-actions ul')!.append(menuItem);
}
-const getWikiPageCount = cache.function('wiki-page-count', async (): Promise<number> => {
- const dom = await fetchDom(buildRepoURL('wiki'));
- const counter = dom.querySelector('#wiki-pages-box .Counter');
+const wikiPageCount = new CachedFunction('wiki-page-count', {
+ async updater(): Promise<number> {
+ const dom = await fetchDom(buildRepoURL('wiki'));
+ const counter = dom.querySelector('#wiki-pages-box .Counter');
- if (counter) {
- return looseParseInt(counter);
- }
+ if (counter) {
+ return looseParseInt(counter);
+ }
- return dom.querySelectorAll('#wiki-content > .Box .Box-row').length;
-}, {
+ return dom.querySelectorAll('#wiki-content > .Box .Box-row').length;
+ },
maxAge: {hours: 1},
staleWhileRevalidate: {days: 5},
cacheKey: cacheByRepo,
});
-const getWorkflowsCount = cache.function('workflows-count', async (): Promise<number> => {
- const {repository: {workflowFiles}} = await api.v4(`
+const workflowCount = new CachedFunction('workflows-count', {
+ async updater(): Promise<number> {
+ const {repository: {workflowFiles}} = await api.v4(`
repository() {
workflowFiles: object(expression: "HEAD:.github/workflows") {
... on Tree { entries { oid } }
@@ -70,8 +72,8 @@ const getWorkflowsCount = cache.function('workflows-count', async (): Promise<nu
}
`);
- return workflowFiles?.entries.length ?? 0;
-}, {
+ return workflowFiles?.entries.length ?? 0;
+ },
maxAge: {days: 1},
staleWhileRevalidate: {days: 10},
cacheKey: cacheByRepo,
@@ -83,9 +85,9 @@ async function updateWikiTab(): Promise<void | false> {
return false;
}
- const wikiPageCount = await getWikiPageCount();
- if (wikiPageCount > 0) {
- setTabCounter(wikiTab, wikiPageCount);
+ const count = await wikiPageCount.get();
+ if (count > 0) {
+ setTabCounter(wikiTab, count);
} else {
onlyShowInDropdown('wiki-tab');
}
@@ -93,7 +95,7 @@ async function updateWikiTab(): Promise<void | false> {
async function updateActionsTab(): Promise<void | false> {
const actionsTab = await elementReady('[data-hotkey="g a"]');
- if (!actionsTab || mustKeepTab(actionsTab) || await getWorkflowsCount() > 0) {
+ if (!actionsTab || mustKeepTab(actionsTab) || await workflowCount.get() > 0) {
return false;
}
diff --git a/source/features/closing-remarks.tsx b/source/features/closing-remarks.tsx
index a38f12ad..e97fd418 100644
--- a/source/features/closing-remarks.tsx
+++ b/source/features/closing-remarks.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {TagIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
@@ -12,20 +12,21 @@ import TimelineItem from '../github-helpers/timeline-item.js';
import attachElement from '../helpers/attach-element.js';
import {canEditEveryComment} from './quick-comment-edit.js';
import {buildRepoURL, getRepo, isRefinedGitHubRepo} from '../github-helpers/index.js';
-import {getReleaseCount} from './releases-tab.js';
+import {releasesCount} from './releases-tab.js';
import observe from '../helpers/selector-observer.js';
// TODO: Not an exact match; Moderators can edit comments but not create releases
const canCreateRelease = canEditEveryComment;
-const getFirstTag = cache.function('first-tag', async (commit: string): Promise<string | undefined> => {
- const firstTag = await fetchDom(
- buildRepoURL('branch_commits', commit),
- 'ul.branches-tag-list li:last-child a',
- );
+const firstTag = new CachedFunction('first-tag', {
+ async updater(commit: string): Promise<string | false> {
+ const firstTag = await fetchDom(
+ buildRepoURL('branch_commits', commit),
+ 'ul.branches-tag-list li:last-child a',
+ );
- return firstTag?.textContent ?? undefined;
-}, {
+ return firstTag?.textContent ?? false;
+ },
cacheKey: ([commit]) => [getRepo()!.nameWithOwner, commit].join(':'),
});
@@ -43,7 +44,7 @@ function createReleaseUrl(): string | undefined {
async function init(signal: AbortSignal): Promise<void> {
const mergeCommit = select(`.TimelineItem.js-details-container.Details a[href^="/${getRepo()!.nameWithOwner}/commit/" i] > code`)!.textContent!;
- const tagName = await getFirstTag(mergeCommit);
+ const tagName = await firstTag.get(mergeCommit);
if (tagName) {
const tagUrl = buildRepoURL('releases/tag', tagName);
@@ -90,7 +91,7 @@ function addExistingTagLinkFooter(tagName: string, tagUrl: string): void {
}
async function addReleaseBanner(text = 'Now you can release this change'): Promise<void> {
- if (await getReleaseCount() === 0) {
+ if (await releasesCount.get(getRepo()!.nameWithOwner) === 0) {
return;
}
diff --git a/source/features/github-actions-indicators.tsx b/source/features/github-actions-indicators.tsx
index 7d2ffe8f..f90b7a70 100644
--- a/source/features/github-actions-indicators.tsx
+++ b/source/features/github-actions-indicators.tsx
@@ -1,4 +1,4 @@
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import React from 'dom-chef';
import select from 'select-dom';
import {StopIcon, PlayIcon} from '@primer/octicons-react';
@@ -72,30 +72,31 @@ async function getFilesInWorkflowPath(): Promise<Record<string, string>> {
return result;
}
-const getWorkflowsDetails = cache.function('workflows-details', async (): Promise<Record<string, Workflow & WorkflowDetails>> => {
- const [workflows, workflowFiles] = await Promise.all([getWorkflows(), getFilesInWorkflowPath()]);
+const workflowDetails = new CachedFunction('workflows-details', {
+ async updater(): Promise<Record<string, Workflow & WorkflowDetails>> {
+ const [workflows, workflowFiles] = await Promise.all([getWorkflows(), getFilesInWorkflowPath()]);
- const details: Record<string, Workflow & WorkflowDetails> = {};
+ const details: Record<string, Workflow & WorkflowDetails> = {};
- for (const workflow of workflows) {
- const workflowYaml = workflowFiles[workflow.name];
+ for (const workflow of workflows) {
+ const workflowYaml = workflowFiles[workflow.name];
- if (workflowYaml === undefined) {
+ if (workflowYaml === undefined) {
// Cannot find workflow yaml; workflow removed.
- continue;
- }
+ continue;
+ }
- const cron = /schedule[:\s-]+cron[:\s'"]+([^'"\n]+)/m.exec(workflowYaml);
+ const cron = /schedule[:\s-]+cron[:\s'"]+([^'"\n]+)/m.exec(workflowYaml);
- details[workflow.name] = {
- ...workflow,
- schedule: cron?.[1],
- manuallyDispatchable: workflowYaml.includes('workflow_dispatch:'),
- };
- }
+ details[workflow.name] = {
+ ...workflow,
+ schedule: cron?.[1],
+ manuallyDispatchable: workflowYaml.includes('workflow_dispatch:'),
+ };
+ }
- return details;
-}, {
+ return details;
+ },
maxAge: {days: 1},
staleWhileRevalidate: {days: 10},
cacheKey: cacheByRepo,
@@ -108,7 +109,7 @@ async function addIndicators(workflowListItem: HTMLAnchorElement): Promise<void>
}
// Called in `init`, memoized
- const workflows = await getWorkflowsDetails();
+ const workflows = await workflowDetails.get();
const workflowName = workflowListItem.href.split('/').pop()!;
const workflow = workflows[workflowName];
if (!workflow) {
@@ -152,7 +153,7 @@ async function addIndicators(workflowListItem: HTMLAnchorElement): Promise<void>
async function init(signal: AbortSignal): Promise<false | void> {
// Do it as soon as possible, before the page loads
- const workflows = await getWorkflowsDetails();
+ const workflows = await workflowDetails.get();
if (!workflows) {
return false;
}
diff --git a/source/features/highlight-collaborators-and-own-conversations.tsx b/source/features/highlight-collaborators-and-own-conversations.tsx
index fba1491c..388c0aac 100644
--- a/source/features/highlight-collaborators-and-own-conversations.tsx
+++ b/source/features/highlight-collaborators-and-own-conversations.tsx
@@ -1,5 +1,5 @@
import './highlight-collaborators-and-own-conversations.css';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import domLoaded from 'dom-loaded';
import * as pageDetect from 'github-url-detection';
@@ -8,22 +8,23 @@ import features from '../feature-manager.js';
import fetchDom from '../helpers/fetch-dom.js';
import {buildRepoURL, cacheByRepo, getUsername} from '../github-helpers/index.js';
-const getCollaborators = cache.function('repo-collaborators', async (): Promise<string[]> => {
- const dom = await fetchDom(buildRepoURL('issues/show_menu_content?partial=issues/filters/authors_content'));
- return select
- .all('.SelectMenu-item img[alt]', dom)
- .map(avatar => avatar.alt.slice(1));
-}, {
+const collaborators = new CachedFunction('repo-collaborators', {
+ async updater(): Promise<string[]> {
+ const dom = await fetchDom(buildRepoURL('issues/show_menu_content?partial=issues/filters/authors_content'));
+ return select
+ .all('.SelectMenu-item img[alt]', dom)
+ .map(avatar => avatar.alt.slice(1));
+ },
maxAge: {days: 1},
staleWhileRevalidate: {days: 20},
cacheKey: cacheByRepo,
});
async function highlightCollaborators(): Promise<void> {
- const collaborators = await getCollaborators();
+ const list = await collaborators.get();
await domLoaded;
for (const author of select.all('.js-issue-row [data-hovercard-type="user"]')) {
- if (collaborators.includes(author.textContent!.trim())) {
+ if (list.includes(author.textContent!.trim())) {
author.classList.add('rgh-collaborator');
}
}
diff --git a/source/features/link-to-changelog-file.tsx b/source/features/link-to-changelog-file.tsx
index e41a4544..f28125c1 100644
--- a/source/features/link-to-changelog-file.tsx
+++ b/source/features/link-to-changelog-file.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {BookIcon} from '@primer/octicons-react';
import elementReady from 'element-ready';
@@ -8,15 +8,13 @@ import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
import api from '../github-helpers/api.js';
import {wrapAll} from '../helpers/dom-utils.js';
-import {buildRepoURL, cacheByRepo} from '../github-helpers/index.js';
+import {buildRepoURL, getRepo} from '../github-helpers/index.js';
type FileType = {
name: string;
type: string;
};
-const cacheName = 'changelog';
-
const changelogFiles = /^(changelog|news|changes|history|release|whatsnew)(\.(mdx?|mkdn?|mdwn|mdown|markdown|litcoffee|txt|rst))?$/i;
function findChangelogName(files: string[]): string | false {
return files.find(name => changelogFiles.test(name)) ?? false;
@@ -24,12 +22,17 @@ function findChangelogName(files: string[]): string | false {
function parseFromDom(): false {
const files = select.all('[aria-labelledby="files"] .js-navigation-open[href*="/blob/"').map(file => file.title);
- void cache.set(cacheName + ':' + cacheByRepo(), findChangelogName(files));
+ void changelogName.applyOverride(
+ [findChangelogName(files) as string] /* TODO: Type mistake */,
+ getRepo()!.nameWithOwner,
+ );
return false;
}
-const getChangelogName = cache.function(cacheName, async (): Promise<string | false> => {
- const {repository} = await api.v4(`
+const changelogName = new CachedFunction('changelog', {
+ async updater(nameWithOwner: string): Promise<string | false> {
+ const [owner, name] = nameWithOwner.split('/');
+ const {repository} = await api.v4(`
repository() {
object(expression: "HEAD:") {
...on Tree {
@@ -40,22 +43,23 @@ const getChangelogName = cache.function(cacheName, async (): Promise<string | fa
}
}
}
- `);
+ `, {
+ variables: {name, owner},
+ });
- const files: string[] = [];
- for (const entry of repository.object.entries as FileType[]) {
- if (entry.type === 'blob') {
- files.push(entry.name);
+ const files: string[] = [];
+ for (const entry of repository.object.entries as FileType[]) {
+ if (entry.type === 'blob') {
+ files.push(entry.name);
+ }
}
- }
- return findChangelogName(files);
-}, {
- cacheKey: cacheByRepo,
+ return findChangelogName(files);
+ },
});
async function init(): Promise<void | false> {
- const changelog = await getChangelogName();
+ const changelog = await changelogName.get(getRepo()!.nameWithOwner);
if (!changelog) {
return false;
}
@@ -100,3 +104,15 @@ void features.add(import.meta.url, {
awaitDomReady: true, // Does not affect current visit
init: parseFromDom,
});
+
+/*
+
+Test URLs:
+
+- CHANGELOG.md: https://github.com/nodeca/js-yaml/releases/tag/4.0.0)
+- CHANGELOG.rst: https://github.com/pyca/cryptography/releases)
+- CHANGES: https://github.com/sphinx-doc/sphinx/releases)
+- news: https://github.com/pypa/pip/releases)
+- HISTORY.md: https://github.com/psf/requests/releases)
+
+*/
diff --git a/source/features/list-prs-for-branch.tsx b/source/features/list-prs-for-branch.tsx
index ae61b6f4..d4994139 100644
--- a/source/features/list-prs-for-branch.tsx
+++ b/source/features/list-prs-for-branch.tsx
@@ -3,7 +3,7 @@ import React from 'dom-chef';
import features from '../feature-manager.js';
import getCurrentGitRef from '../github-helpers/get-current-git-ref.js';
import isDefaultBranch from '../github-helpers/is-default-branch.js';
-import {getPullRequestsAssociatedWithBranch, stateIcon} from './show-associated-branch-prs-on-fork.js';
+import {pullRequestsAssociatedWithBranch, stateIcon} from './show-associated-branch-prs-on-fork.js';
import {addAfterBranchSelector, isPermalink, isRepoCommitListRoot} from '../github-helpers/index.js';
import observe from '../helpers/selector-observer.js';
import api from '../github-helpers/api.js';
@@ -18,7 +18,7 @@ const stateColorMap = {
};
async function add(branchSelectorParent: HTMLDetailsElement): Promise<void | false> {
- const getPr = await getPullRequestsAssociatedWithBranch();
+ const getPr = await pullRequestsAssociatedWithBranch.get();
const currentBranch = getCurrentGitRef()!;
const prInfo = getPr[currentBranch];
if (!prInfo) {
diff --git a/source/features/list-prs-for-file.tsx b/source/features/list-prs-for-file.tsx
index 40d51fdf..eb84bf54 100644
--- a/source/features/list-prs-for-file.tsx
+++ b/source/features/list-prs-for-file.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import {isFirefox} from 'webext-detect-page';
import * as pageDetect from 'github-url-detection';
import {AlertIcon, GitPullRequestIcon} from '@primer/octicons-react';
@@ -57,26 +57,27 @@ function getDropdown(prs: number[]): HTMLElement {
/**
@returns prsByFile {"filename1": [10, 3], "filename2": [2]}
*/
-const getPrsByFile = cache.function('files-with-prs', async (): Promise<Record<string, number[]>> => {
- const {repository} = await api.v4(listPrsForFileQuery, {
- variables: {
- defaultBranch: await getDefaultBranch(),
- },
- });
-
- const files: Record<string, number[]> = {};
-
- for (const pr of repository.pullRequests.nodes) {
- for (const {path} of pr.files.nodes) {
- files[path] = files[path] ?? [];
- if (files[path].length < 10) {
- files[path].push(pr.number);
+const getPrsByFile = new CachedFunction('files-with-prs', {
+ async updater(): Promise<Record<string, number[]>> {
+ const {repository} = await api.v4(listPrsForFileQuery, {
+ variables: {
+ defaultBranch: await getDefaultBranch(),
+ },
+ });
+
+ const files: Record<string, number[]> = {};
+
+ for (const pr of repository.pullRequests.nodes) {
+ for (const {path} of pr.files.nodes) {
+ files[path] = files[path] ?? [];
+ if (files[path].length < 10) {
+ files[path].push(pr.number);
+ }
}
}
- }
- return files;
-}, {
+ return files;
+ },
maxAge: {hours: 2},
staleWhileRevalidate: {days: 9},
cacheKey: cacheByRepo,
@@ -84,7 +85,7 @@ const getPrsByFile = cache.function('files-with-prs', async (): Promise<Record<s
async function addToSingleFile(moreFileActionsDropdown: HTMLElement): Promise<void> {
const path = new GitHubURL(location.href).filePath;
- const prsByFile = await getPrsByFile();
+ const prsByFile = await getPrsByFile.get();
const prs = prsByFile[path];
if (prs) {
@@ -99,7 +100,7 @@ async function addToSingleFile(moreFileActionsDropdown: HTMLElement): Promise<vo
async function addToEditingFile(saveButton: HTMLElement): Promise<false | void> {
const path = new GitHubURL(location.href).filePath;
- const prsByFile = await getPrsByFile();
+ const prsByFile = await getPrsByFile.get();
let prs = prsByFile[path];
if (!prs) {
diff --git a/source/features/mark-private-orgs.tsx b/source/features/mark-private-orgs.tsx
index ad0a2dfa..60c0ccea 100644
--- a/source/features/mark-private-orgs.tsx
+++ b/source/features/mark-private-orgs.tsx
@@ -1,6 +1,6 @@
import './mark-private-orgs.css';
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {EyeClosedIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
@@ -9,12 +9,13 @@ import features from '../feature-manager.js';
import api from '../github-helpers/api.js';
import {getUsername} from '../github-helpers/index.js';
-const getPublicOrganizationsNames = cache.function('public-organizations', async (username: string): Promise<string[]> => {
+const publicOrganizationsNames = new CachedFunction('public-organizations', {
+ async updater(username: string): Promise<string[]> {
// API v4 seems to *require* `org:read` permission AND it includes private organizations as well, which defeats the purpose. There's no way to filter them.
// GitHub's API explorer inexplicably only includes public organizations.
- const response = await api.v3(`/users/${username}/orgs`);
- return response.map((organization: AnyObject) => organization.login);
-}, {
+ const response = await api.v3(`/users/${username}/orgs`);
+ return response.map((organization: AnyObject) => organization.login);
+ },
maxAge: {hours: 6},
staleWhileRevalidate: {days: 10},
});
@@ -25,9 +26,9 @@ async function init(): Promise<false | void> {
return false;
}
- const publicOrganizationsNames = await getPublicOrganizationsNames(getUsername()!);
+ const organizations = await publicOrganizationsNames.get(getUsername()!);
for (const org of orgs) {
- if (!publicOrganizationsNames.includes(org.pathname.replace(/^\/(organizations\/)?/, ''))) {
+ if (!organizations.includes(org.pathname.replace(/^\/(organizations\/)?/, ''))) {
org.classList.add('rgh-private-org');
org.append(<EyeClosedIcon/>);
}
diff --git a/source/features/pinned-issues-update-time.tsx b/source/features/pinned-issues-update-time.tsx
index e4b617af..8b22c085 100644
--- a/source/features/pinned-issues-update-time.tsx
+++ b/source/features/pinned-issues-update-time.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import batchedFunction from 'batched-function';
import * as pageDetect from 'github-url-detection';
@@ -14,8 +14,9 @@ type IssueInfo = {
updatedAt: string;
};
-const getLastUpdated = cache.function('last-updated', async (issueNumbers: number[]): Promise<Record<string, IssueInfo>> => {
- const {repository} = await api.v4(`
+const getLastUpdated = new CachedFunction('last-updated', {
+ async updater(issueNumbers: number[]): Promise<Record<string, IssueInfo>> {
+ const {repository} = await api.v4(`
repository() {
${issueNumbers.map(number => `
${api.escapeKey(number)}: issue(number: ${number}) {
@@ -25,8 +26,8 @@ const getLastUpdated = cache.function('last-updated', async (issueNumbers: numbe
}
`);
- return repository;
-}, {
+ return repository;
+ },
maxAge: {minutes: 30},
cacheKey: ([issues]) => `${getRepo()!.nameWithOwner}:${String(issues)}`,
});
@@ -36,7 +37,7 @@ function getPinnedIssueNumber(pinnedIssue: HTMLElement): number {
}
const update = batchedFunction(async (pinnedIssues: HTMLElement[]): Promise<void | false> => {
- const lastUpdated: Record<string, IssueInfo> = await getLastUpdated(pinnedIssues.map(issue => getPinnedIssueNumber(issue)));
+ const lastUpdated: Record<string, IssueInfo> = await getLastUpdated.get(pinnedIssues.map(issue => getPinnedIssueNumber(issue)));
for (const pinnedIssue of pinnedIssues) {
const issueNumber = getPinnedIssueNumber(pinnedIssue);
const {updatedAt} = lastUpdated[api.escapeKey(issueNumber)];
diff --git a/source/features/pr-commit-lines-changed.tsx b/source/features/pr-commit-lines-changed.tsx
index 58bfddb0..7930ce8a 100644
--- a/source/features/pr-commit-lines-changed.tsx
+++ b/source/features/pr-commit-lines-changed.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
@@ -7,8 +7,9 @@ import features from '../feature-manager.js';
import api from '../github-helpers/api.js';
import pluralize from '../helpers/pluralize.js';
-const getCommitChanges = cache.function('commit-changes', async (commit: string): Promise<[additions: number, deletions: number]> => {
- const {repository} = await api.v4(`
+const commitChanges = new CachedFunction('commit-changes', {
+ async updater(commit: string): Promise<[additions: number, deletions: number]> {
+ const {repository} = await api.v4(`
query getCommitChanges($owner: String!, $name: String!, $commit: String!) {
repository(owner: $owner, name: $name) {
object(expression: $commit) {
@@ -20,17 +21,17 @@ const getCommitChanges = cache.function('commit-changes', async (commit: string)
}
}
`, {
- variables: {
- commit,
- },
- });
+ variables: {
+ commit,
+ },
+ });
- return [repository.object.additions, repository.object.deletions];
-});
+ return [repository.object.additions, repository.object.deletions];
+ }});
async function init(): Promise<void> {
const commitSha = location.pathname.split('/').pop()!;
- const [additions, deletions] = await getCommitChanges(commitSha);
+ const [additions, deletions] = await commitChanges.get(commitSha);
const tooltip = pluralize(additions + deletions, '1 line changed', '$$ lines changed');
const diffstat = await elementReady('.diffstat', {waitForChildren: false});
diffstat!.replaceWith(
diff --git a/source/features/pr-filters.tsx b/source/features/pr-filters.tsx
index 9303189b..b740ede0 100644
--- a/source/features/pr-filters.tsx
+++ b/source/features/pr-filters.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {CheckIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
@@ -52,8 +52,9 @@ function addDraftFilter(dropdown: HTMLElement): void {
addDropdownItem(dropdown, 'Not ready for review (Draft PR)', 'draft', 'true');
}
-const hasChecks = cache.function('has-checks', async (): Promise<boolean> => {
- const {repository} = await api.v4(`
+const hasChecks = new CachedFunction('has-checks', {
+ async updater(): Promise<boolean> {
+ const {repository} = await api.v4(`
repository() {
head: object(expression: "HEAD") {
... on Commit {
@@ -69,14 +70,14 @@ const hasChecks = cache.function('has-checks', async (): Promise<boolean> => {
}
`);
- return repository.head.history.nodes.some((commit: AnyObject) => commit.statusCheckRollup);
-}, {
+ return repository.head.history.nodes.some((commit: AnyObject) => commit.statusCheckRollup);
+ },
maxAge: {days: 3},
cacheKey: cacheByRepo,
});
async function addChecksFilter(reviewsFilter: HTMLElement): Promise<void> {
- if (!await hasChecks()) {
+ if (!await hasChecks.get()) {
return;
}
diff --git a/source/features/profile-gists-link.tsx b/source/features/profile-gists-link.tsx
index f5c3ddbc..faaaa0cb 100644
--- a/source/features/profile-gists-link.tsx
+++ b/source/features/profile-gists-link.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import * as pageDetect from 'github-url-detection';
import {CodeSquareIcon} from '@primer/octicons-react';
@@ -10,8 +10,9 @@ import {getCleanPathname} from '../github-helpers/index.js';
import createDropdownItem from '../github-helpers/create-dropdown-item.js';
import observe from '../helpers/selector-observer.js';
-const getGistCount = cache.function('gist-count', async (username: string): Promise<number> => {
- const {user} = await api.v4(`
+const gistCount = new CachedFunction('gist-count', {
+ async updater(username: string): Promise<number> {
+ const {user} = await api.v4(`
query getGistCount($username: String!) {
user(login: $username) {
gists(first: 0) {
@@ -20,10 +21,10 @@ const getGistCount = cache.function('gist-count', async (username: string): Prom
}
}
`, {
- variables: {username},
- });
- return user.gists.totalCount;
-}, {
+ variables: {username},
+ });
+ return user.gists.totalCount;
+ },
maxAge: {days: 1},
staleWhileRevalidate: {days: 3},
});
@@ -62,7 +63,7 @@ async function appendTab(navigationBar: Element): Promise<void> {
);
}
- const count = await getGistCount(user.name);
+ const count = await gistCount.get(user.name);
if (count > 0) {
link.append(<span className="Counter">{count}</span>);
}
diff --git a/source/features/releases-dropdown.tsx b/source/features/releases-dropdown.tsx
index bb666de5..f9614d85 100644
--- a/source/features/releases-dropdown.tsx
+++ b/source/features/releases-dropdown.tsx
@@ -1,7 +1,7 @@
import React from 'dom-chef';
import * as pageDetect from 'github-url-detection';
import delegate, {DelegateEvent} from 'delegate-it';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import api from '../github-helpers/api.js';
import features from '../feature-manager.js';
@@ -18,10 +18,11 @@ const gql = `
}
`;
-const getReleases = cache.function('releases', async (): Promise<string[]> => {
- const {repository} = await api.v4(gql);
- return repository.releases.nodes.map(({tagName}: {tagName: string}) => tagName);
-}, {
+const getReleases = new CachedFunction('releases', {
+ async updater(): Promise<string[]> {
+ const {repository} = await api.v4(gql);
+ return repository.releases.nodes.map(({tagName}: {tagName: string}) => tagName);
+ },
maxAge: {hours: 1},
staleWhileRevalidate: {days: 4},
cacheKey: cacheByRepo,
@@ -31,7 +32,7 @@ const getReleases = cache.function('releases', async (): Promise<string[]> => {
async function selectionHandler(event: DelegateEvent<Event, HTMLInputElement>): Promise<void> {
const field = event.delegateTarget;
const selectedTag = field.value;
- const releases = await getReleases(); // Expected to be in cache
+ const releases = await getReleases.get(); // Expected to be in cache
if (!('inputType' in event) && releases.includes(selectedTag)) {
location.href = buildRepoURL('releases/tag', encodeURIComponent(selectedTag));
field.value = ''; // Can't call `preventDefault`, the `input` event is not cancelable
@@ -39,7 +40,7 @@ async function selectionHandler(event: DelegateEvent<Event, HTMLInputElement>):
}
async function addList(searchField: HTMLInputElement): Promise<void> {
- const releases = await getReleases();
+ const releases = await getReleases.get();
if (releases.length === 0) {
return;
}
diff --git a/source/features/releases-tab.tsx b/source/features/releases-tab.tsx
index d5b97ff1..db390e53 100644
--- a/source/features/releases-tab.tsx
+++ b/source/features/releases-tab.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import {TagIcon} from '@primer/octicons-react';
import elementReady from 'element-ready';
@@ -11,13 +11,11 @@ import api from '../github-helpers/api.js';
import looseParseInt from '../helpers/loose-parse-int.js';
import abbreviateNumber from '../helpers/abbreviate-number.js';
import createDropdownItem from '../github-helpers/create-dropdown-item.js';
-import {buildRepoURL, cacheByRepo} from '../github-helpers/index.js';
+import {buildRepoURL, cacheByRepo, getRepo} from '../github-helpers/index.js';
import {releasesSidebarSelector} from './clean-repo-sidebar.js';
import {appendBefore, highlightTab, unhighlightTab} from '../helpers/dom-utils.js';
import {underlineNavDropdownUl} from '../github-helpers/selectors.js';
-const cacheName = 'releases-count';
-
async function parseCountFromDom(): Promise<number> {
const moreReleasesCountElement = await elementReady(releasesSidebarSelector + ' .Counter');
if (moreReleasesCountElement) {
@@ -27,14 +25,17 @@ async function parseCountFromDom(): Promise<number> {
return 0;
}
-async function fetchFromApi(): Promise<number> {
+async function fetchFromApi(nameWithOwner: string): Promise<number> {
+ const [owner, name] = nameWithOwner.split('/');
const {repository} = await api.v4(`
- repository() {
+ repository(owner: $owner, name: $name) {
releases {
totalCount
}
}
- `);
+ `, {
+ variables: {name, owner},
+ });
return repository.releases.totalCount;
}
@@ -43,19 +44,20 @@ async function fetchFromApi(): Promise<number> {
// - It is disabled by repository owner on the home page (release DOM element won't be there)
// - It only contains pre-releases (count badge won't be shown)
// For this reason, if we can't find a count from the DOM, we ask the API instead (see #6298)
-export const getReleaseCount = cache.function(cacheName, async () => await parseCountFromDom() || fetchFromApi(), {
+export const releasesCount = new CachedFunction('releases-count', {
+ updater: async (nameWithOwner: string) => await parseCountFromDom() || fetchFromApi(nameWithOwner),
maxAge: {hours: 1},
staleWhileRevalidate: {days: 3},
cacheKey: cacheByRepo,
});
async function addReleasesTab(): Promise<false | void> {
- // Always prefer the information in the DOM
- if (pageDetect.isRepoRoot()) {
- await cache.delete(cacheName + ':' + cacheByRepo());
- }
+ const repo = getRepo()!.nameWithOwner;
+ const count = pageDetect.isRepoRoot()
+ // Always prefer the information in the DOM
+ ? await releasesCount.getFresh(repo)
+ : await releasesCount.get(repo);
- const count = await getReleaseCount();
if (count === 0) {
return false;
}
diff --git a/source/features/repo-age.tsx b/source/features/repo-age.tsx
index e132f7a5..b250d515 100644
--- a/source/features/repo-age.tsx
+++ b/source/features/repo-age.tsx
@@ -1,5 +1,5 @@
import twas from 'twas';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import React from 'dom-chef';
import {RepoIcon} from '@primer/octicons-react';
import elementReady from 'element-ready';
@@ -72,8 +72,9 @@ async function getRepoAge(commitSha: string, commitsCount: number): Promise<[com
return [committedDate, resourcePath];
}
-const getFirstCommit = cache.function('first-commit', async (): Promise<[committedDate: string, resourcePath: string]> => {
- const {repository} = await api.v4(`
+const firstCommit = new CachedFunction('first-commit', {
+ async updater(): Promise<[committedDate: string, resourcePath: string]> {
+ const {repository} = await api.v4(`
repository() {
defaultBranchRef {
target {
@@ -90,19 +91,19 @@ const getFirstCommit = cache.function('first-commit', async (): Promise<[committ
}
`);
- const {oid: commitSha, history, committedDate, resourcePath} = repository.defaultBranchRef.target as CommitTarget;
- const commitsCount = history.totalCount;
- if (commitsCount === 1) {
- return [committedDate, resourcePath];
- }
+ const {oid: commitSha, history, committedDate, resourcePath} = repository.defaultBranchRef.target as CommitTarget;
+ const commitsCount = history.totalCount;
+ if (commitsCount === 1) {
+ return [committedDate, resourcePath];
+ }
- return getRepoAge(commitSha, commitsCount);
-}, {
+ return getRepoAge(commitSha, commitsCount);
+ },
cacheKey: cacheByRepo,
});
async function init(): Promise<void> {
- const [firstCommitDate, firstCommitHref] = await getFirstCommit()!;
+ const [firstCommitDate, firstCommitHref] = await firstCommit.get()!;
const birthday = new Date(firstCommitDate);
// `twas` could also return `an hour ago` or `just now`
diff --git a/source/features/rgh-feature-descriptions.tsx b/source/features/rgh-feature-descriptions.tsx
index 455ecdcf..6fed4cc9 100644
--- a/source/features/rgh-feature-descriptions.tsx
+++ b/source/features/rgh-feature-descriptions.tsx
@@ -2,14 +2,13 @@ import './rgh-feature-descriptions.css';
import React from 'dom-chef';
import * as pageDetect from 'github-url-detection';
import {AlertIcon, CopyIcon, InfoIcon} from '@primer/octicons-react';
-import cache from 'webext-storage-cache';
import features from '../feature-manager.js';
import {featuresMeta} from '../../readme.md';
import optionsStorage, {getNewFeatureName, isFeatureDisabled} from '../options-storage.js';
import {isRefinedGitHubRepo} from '../github-helpers/index.js';
import observe from '../helpers/selector-observer.js';
-import {HotfixStorage} from '../helpers/hotfix.js';
+import {brokenFeatures} from '../helpers/hotfix.js';
import {createRghIssueLink} from '../helpers/rgh-issue-link.js';
import openOptions from '../helpers/open-options.js';
import createBanner from '../github-helpers/banner.js';
@@ -86,7 +85,7 @@ function addDescription(infoBanner: HTMLElement, id: string, meta: FeatureMeta |
async function getDisabledReason(id: string): Promise<JSX.Element | undefined> {
const classes = ['mb-3'];
// Skip dev check present in `getLocalHotfixes`, we want to see this even when developing
- const hotfixes = await cache.get<HotfixStorage>('hotfixes:') ?? [];
+ const hotfixes = await brokenFeatures.get() ?? [];
const hotfixed = hotfixes.find(([feature]) => feature === id);
if (hotfixed) {
const [_name, issue, unaffectedVersion] = hotfixed;
diff --git a/source/features/rgh-sponsor-button.tsx b/source/features/rgh-sponsor-button.tsx
deleted file mode 100644
index c6d03f5f..00000000
--- a/source/features/rgh-sponsor-button.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
-
- .. :
- . . . . .
- . . . .. . . *
- * . .. .
- . . . : . . . .
- . . . . . .
- . . *:. . .
-. . . . .. . .
- . . . . ... . .
- . . . . . . . . .
- . . . ... .. . . .
- . . . *. . .
- . :. . .
- . . . .
- . . . ./|\
- . .. :. . | . .
- . ... . |
- . :. . . *. | . .
- . *. You are here.
- . . . . *. .
-
-*/
-import cache from 'webext-storage-cache';
-import select from 'select-dom';
-import * as pageDetect from 'github-url-detection';
-import delegate, {DelegateEvent} from 'delegate-it';
-
-import features from '../feature-manager.js';
-import {getRepo, getUsername} from '../github-helpers/index.js';
-
-async function wiggleWiggleWiggle(): Promise<void> {
- await cache.set('did-it-wiggle', 'yup', {days: 7});
- select('#sponsor-button-repo')?.animate({
- transform: [
- 'none',
- 'rotate(-2deg) scale(1.05)',
- 'rotate(2deg) scale(1.1)',
- 'rotate(-2deg) scale(1.1)',
- 'rotate(2deg) scale(1.1)',
- 'rotate(-2deg) scale(1.1)',
- 'rotate(2deg) scale(1.05)',
- 'none',
- ],
- }, 600);
-}
-
-async function suchLove({delegateTarget}: DelegateEvent): Promise<void> {
- const heart = select('.octicon-heart', delegateTarget);
-
- // .closest ensures that clicking the lightbox’ background doesn't also trigger the animation
- if (!heart || delegateTarget.closest('details[open]')) {
- return;
- }
-
- const rect = heart.getBoundingClientRect();
- const love = heart.cloneNode(true);
- Object.assign(love.style, {
- position: 'fixed',
- zIndex: '9999999999',
- left: `${rect.x}px`,
- top: `${rect.y}px`,
- });
-
- document.body.append(love);
-
- await love.animate({
- transform: [
- 'translateZ(0)',
- 'translateZ(0) scale(80)',
- ],
- opacity: [
- 1,
- 0,
- ],
- }, {
- duration: 600,
- easing: 'ease-out',
- }).finished;
-
- love.remove(); // 💔
-}
-
-async function handleNewIssue(signal: AbortSignal): Promise<false> {
- if (getRepo()!.owner !== getUsername() && !await cache.get('did-it-wiggle')) {
- select([
- '.btn-primary[href$="/issues/new/choose"]',
- '.btn-primary[href$="/issues/new"]',
- ])
- ?.addEventListener('mouseenter', wiggleWiggleWiggle, {once: true, signal});
- }
-
- return false;
-}
-
-function handleSponsorButton(signal: AbortSignal): void {
- delegate('#sponsor-button-repo, #sponsor-profile-button, [aria-label^="Sponsor @"]', 'click', suchLove, {signal});
-}
-
-void features.add(import.meta.url, {
- include: [
- pageDetect.isIssue,
- pageDetect.isRepoIssueList,
- ],
- awaitDomReady: true, // 🤷‍♂️
- init: handleNewIssue,
-}, {
- include: [
- pageDetect.hasRepoHeader,
- pageDetect.isUserProfile,
- pageDetect.isOrganizationProfile,
- ],
- exclude: [
- pageDetect.isOwnUserProfile,
- pageDetect.isPrivateUserProfile,
- ],
- init: handleSponsorButton,
-});
diff --git a/source/features/show-associated-branch-prs-on-fork.tsx b/source/features/show-associated-branch-prs-on-fork.tsx
index 2712915c..3720ede1 100644
--- a/source/features/show-associated-branch-prs-on-fork.tsx
+++ b/source/features/show-associated-branch-prs-on-fork.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import * as pageDetect from 'github-url-detection';
import {GitMergeIcon, GitPullRequestIcon, GitPullRequestClosedIcon, GitPullRequestDraftIcon} from '@primer/octicons-react';
@@ -18,8 +18,9 @@ type PullRequest = {
url: string;
};
-export const getPullRequestsAssociatedWithBranch = cache.function('associatedBranchPullRequests', async (): Promise<Record<string, PullRequest>> => {
- const {repository} = await api.v4(`
+export const pullRequestsAssociatedWithBranch = new CachedFunction('associatedBranchPullRequests', {
+ async updater(): Promise<Record<string, PullRequest>> {
+ const {repository} = await api.v4(`
repository() {
refs(refPrefix: "refs/heads/", last: 100) {
nodes {
@@ -42,19 +43,19 @@ export const getPullRequestsAssociatedWithBranch = cache.function('associatedBra
}
`);
- const pullRequests: Record<string, PullRequest> = {};
- for (const {name, associatedPullRequests} of repository.refs.nodes) {
- const [prInfo] = associatedPullRequests.nodes as PullRequest[];
- // Check if the ref was deleted, since the result includes pr's that are not in fact related to this branch but rather to the branch name.
- const headRefWasDeleted = prInfo?.timelineItems.nodes[0]?.__typename === 'HeadRefDeletedEvent';
- if (prInfo && !headRefWasDeleted) {
- prInfo.state = prInfo.isDraft && prInfo.state === 'OPEN' ? 'DRAFT' : prInfo.state;
- pullRequests[name] = prInfo;
+ const pullRequests: Record<string, PullRequest> = {};
+ for (const {name, associatedPullRequests} of repository.refs.nodes) {
+ const [prInfo] = associatedPullRequests.nodes as PullRequest[];
+ // Check if the ref was deleted, since the result includes pr's that are not in fact related to this branch but rather to the branch name.
+ const headRefWasDeleted = prInfo?.timelineItems.nodes[0]?.__typename === 'HeadRefDeletedEvent';
+ if (prInfo && !headRefWasDeleted) {
+ prInfo.state = prInfo.isDraft && prInfo.state === 'OPEN' ? 'DRAFT' : prInfo.state;
+ pullRequests[name] = prInfo;
+ }
}
- }
- return pullRequests;
-}, {
+ return pullRequests;
+ },
maxAge: {hours: 1},
staleWhileRevalidate: {days: 4},
cacheKey: cacheByRepo,
@@ -92,9 +93,9 @@ function addAssociatedPRLabel(branchCompareLink: Element, prInfo: PullRequest):
}
async function addLink(branchCompareLink: Element): Promise<void> {
- const associatedPullRequests = await getPullRequestsAssociatedWithBranch();
+ const prs = await pullRequestsAssociatedWithBranch.get();
const branchName = branchCompareLink.closest('[branch]')!.getAttribute('branch')!;
- const prInfo = associatedPullRequests[branchName];
+ const prInfo = prs[branchName];
if (prInfo) {
addAssociatedPRLabel(branchCompareLink, prInfo);
}
@@ -113,3 +114,11 @@ void features.add(import.meta.url, {
],
init,
});
+
+/*
+
+Test URLs:
+
+https://github.com/pnarielwala/create-react-app-ts/branches
+
+*/
diff --git a/source/features/show-open-prs-of-forks.tsx b/source/features/show-open-prs-of-forks.tsx
index ea341d04..94a83850 100644
--- a/source/features/show-open-prs-of-forks.tsx
+++ b/source/features/show-open-prs-of-forks.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import select from 'select-dom';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
@@ -13,8 +13,9 @@ function getLinkCopy(count: number): string {
return pluralize(count, 'one open pull request', 'at least $$ open pull requests');
}
-const countPRs = cache.function('prs-on-forked-repo', async (forkedRepo: string): Promise<[prCount: number, singlePrNumber?: number]> => {
- const {search} = await api.v4(`
+const countPRs = new CachedFunction('prs-on-forked-repo', {
+ async updater(forkedRepo: string): Promise<{count: number; firstPr?: number}> {
+ const {search} = await api.v4(`
query getPRs($query: String!) {
search(
first: 100,
@@ -32,21 +33,21 @@ const countPRs = cache.function('prs-on-forked-repo', async (forkedRepo: string)
}
}
`, {
- variables: {
- query: `is:pr is:open archived:false repo:${forkedRepo} author:${getUsername()!}`,
- },
- });
+ variables: {
+ query: `is:pr is:open archived:false repo:${forkedRepo} author:${getUsername()!}`,
+ },
+ });
- // Only show PRs originated from the current repo
- const prs = search.nodes.filter((pr: AnyObject) => pr.headRepository.nameWithOwner === getRepo()!.nameWithOwner);
+ // Only show PRs originated from the current repo
+ const prs = search.nodes.filter((pr: AnyObject) => pr.headRepository.nameWithOwner === getRepo()!.nameWithOwner);
- // If only one is found, pass the PR number so we can link to the PR directly
- if (prs.length === 1) {
- return [1, prs[0].number];
- }
+ // If only one is found, pass the PR number so we can link to the PR directly
+ if (prs.length === 1) {
+ return {count: 1, firstPr: prs[0].number};
+ }
- return [prs.length];
-}, {
+ return {count: prs.length};
+ },
maxAge: {hours: 1},
staleWhileRevalidate: {days: 2},
cacheKey: ([forkedRepo]): string => `${forkedRepo}:${getRepo()!.nameWithOwner}`,
@@ -61,7 +62,7 @@ async function getPRs(): Promise<[prCount: number, url: string] | []> {
}
const forkedRepo = getForkedRepo()!;
- const [count, firstPr] = await countPRs(forkedRepo);
+ const {count, firstPr} = await countPRs.get(forkedRepo);
if (count === 1) {
return [count, `/${forkedRepo}/pull/${firstPr!}`];
}
diff --git a/source/features/tags-on-commits-list.tsx b/source/features/tags-on-commits-list.tsx
index e9ac6809..199979f7 100644
--- a/source/features/tags-on-commits-list.tsx
+++ b/source/features/tags-on-commits-list.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import cache from 'webext-storage-cache/legacy.js';
import select from 'select-dom';
import {TagIcon} from '@primer/octicons-react';
import * as pageDetect from 'github-url-detection';
@@ -176,3 +176,11 @@ void features.add(import.meta.url, {
deduplicate: 'has-rgh-inner',
init,
});
+
+/*
+
+Test URLs:
+
+https://github.com/refined-github/refined-github/commits/19.5.21.1921
+
+*/
diff --git a/source/features/toggle-files-button.tsx b/source/features/toggle-files-button.tsx
index f592690a..947bac75 100644
--- a/source/features/toggle-files-button.tsx
+++ b/source/features/toggle-files-button.tsx
@@ -1,6 +1,6 @@
import './toggle-files-button.css';
import select from 'select-dom';
-import cache from 'webext-storage-cache';
+import {CachedValue} from 'webext-storage-cache';
import React from 'dom-chef';
import delegate, {DelegateEvent} from 'delegate-it';
import * as pageDetect from 'github-url-detection';
@@ -10,7 +10,7 @@ import features from '../feature-manager.js';
import observe from '../helpers/selector-observer.js';
import {isHasSelectorSupported} from '../helpers/select-has.js';
-const cacheKey = 'files-hidden';
+const wereFilesHidden = new CachedValue<boolean>('files-hidden');
const toggleButtonClass = 'rgh-toggle-files';
function addButton(filesBox: HTMLElement): void {
@@ -50,7 +50,7 @@ async function toggleList(): Promise<void> {
firstCollapseOnDesktop(targets);
if (window.matchMedia('(min-width: 768px)').matches) {
// We just hid the file list, no further action is necessary on desktop
- await cache.set<boolean>(cacheKey, true);
+ void wereFilesHidden.set(true);
return;
}
@@ -64,14 +64,14 @@ async function toggleList(): Promise<void> {
async function updateView(anchor: HTMLHeadingElement): Promise<void> {
const filesBox = anchor.parentElement!;
addButton(filesBox);
- if (await cache.get<boolean>(cacheKey)) {
+ if (await wereFilesHidden.get()) {
// This only applies on desktop; Mobile already always starts collapsed and we're not changing that
firstCollapseOnDesktop();
}
}
async function recordToggle({detail}: DelegateEvent<CustomEvent>): Promise<void> {
- await cache.set<boolean>(cacheKey, !detail.open);
+ await wereFilesHidden.set(!detail.open);
}
async function init(signal: AbortSignal): Promise<void> {
diff --git a/source/features/unreleased-commits.tsx b/source/features/unreleased-commits.tsx
index a4addd07..a28f35a5 100644
--- a/source/features/unreleased-commits.tsx
+++ b/source/features/unreleased-commits.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import * as pageDetect from 'github-url-detection';
import {TagIcon} from '@primer/octicons-react';
@@ -29,8 +29,9 @@ type Tags = {
export const undeterminableAheadBy = Number.MAX_SAFE_INTEGER; // For when the branch is ahead by more than 20 commits #5505
-export const getRepoPublishState = cache.function('tag-ahead-by', async (): Promise<RepoPublishState> => {
- const {repository} = await api.v4(`
+export const repoPublishState = new CachedFunction('tag-ahead-by', {
+ async updater(): Promise<RepoPublishState> {
+ const {repository} = await api.v4(`
repository() {
refs(first: 20, refPrefix: "refs/tags/", orderBy: {
field: TAG_COMMIT_DATE,
@@ -62,36 +63,36 @@ export const getRepoPublishState = cache.function('tag-ahead-by', async (): Prom
}
`);
- if (repository.refs.nodes.length === 0) {
- return {
- latestTag: false,
- aheadBy: 0,
- };
- }
+ if (repository.refs.nodes.length === 0) {
+ return {
+ latestTag: false,
+ aheadBy: 0,
+ };
+ }
- const tags = new Map<string, string>();
- for (const node of repository.refs.nodes as Tags[]) {
- tags.set(node.name, node.tag.commit?.oid ?? node.tag.oid);
- }
+ const tags = new Map<string, string>();
+ for (const node of repository.refs.nodes as Tags[]) {
+ tags.set(node.name, node.tag.commit?.oid ?? node.tag.oid);
+ }
- // If this logic ever gets dropped or becomes simpler, consider using the native "compare" API
- // https://github.com/refined-github/refined-github/issues/6094
- const latestTag = getLatestVersionTag([...tags.keys()]);
- const latestTagOid = tags.get(latestTag)!;
- const aheadBy = repository.defaultBranchRef.target.history.nodes.findIndex((node: AnyObject) => node.oid === latestTagOid);
+ // If this logic ever gets dropped or becomes simpler, consider using the native "compare" API
+ // https://github.com/refined-github/refined-github/issues/6094
+ const latestTag = getLatestVersionTag([...tags.keys()]);
+ const latestTagOid = tags.get(latestTag)!;
+ const aheadBy = repository.defaultBranchRef.target.history.nodes.findIndex((node: AnyObject) => node.oid === latestTagOid);
- return {
- latestTag,
- aheadBy: aheadBy === -1 ? undeterminableAheadBy : aheadBy,
- };
-}, {
+ return {
+ latestTag,
+ aheadBy: aheadBy === -1 ? undeterminableAheadBy : aheadBy,
+ };
+ },
maxAge: {hours: 1},
staleWhileRevalidate: {days: 2},
cacheKey: cacheByRepo,
});
async function add(branchSelectorParent: HTMLDetailsElement): Promise<void> {
- const {latestTag, aheadBy} = await getRepoPublishState();
+ const {latestTag, aheadBy} = await repoPublishState.get();
const isAhead = aheadBy > 0;
if (!latestTag || !isAhead) {
diff --git a/source/features/user-local-time.tsx b/source/features/user-local-time.tsx
index 8e52a705..070e1cc6 100644
--- a/source/features/user-local-time.tsx
+++ b/source/features/user-local-time.tsx
@@ -2,7 +2,7 @@
import './user-local-time.css';
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import delay from 'delay';
import select from 'select-dom';
import {ClockIcon} from '@primer/octicons-react';
@@ -28,42 +28,43 @@ async function loadCommitPatch(commitUrl: string): Promise<string> {
return textContent;
}
-const getLastCommitDate = cache.function('last-commit', async (login: string): Promise<string | false> => {
- for await (const page of api.v3paginated(`/users/${login}/events`)) {
- for (const event of page as any) {
- if (event.type !== 'PushEvent') {
- continue;
- }
-
- // Start from the latest commit, which is the last one in the list
- for (const commit of event.payload.commits.reverse() as Commit[]) {
- const response = await api.v3(commit.url, {ignoreHTTPStatus: true});
- // Commits might not exist anymore even if they are listed in the events
- // This can happen if the repository was deleted so we can also skip all other commits
- if (response.httpStatus === 404) {
- break;
- }
-
- if (!response.ok) {
- throw await api.getError(response);
- }
-
- // `response.author` only appears if GitHub can match the email to a GitHub user
- if (response.author?.id !== event.actor.id) {
+const lastCommitDate = new CachedFunction('last-commit', {
+ async updater(login: string): Promise<string | false> {
+ for await (const page of api.v3paginated(`/users/${login}/events`)) {
+ for (const event of page as any) {
+ if (event.type !== 'PushEvent') {
continue;
}
- const patch = await loadCommitPatch(commit.url);
- // The patch of merge commits doesn't include the commit sha so the date might be from another user
- if (patch.startsWith(`From ${commit.sha} `)) {
- return /^Date: (.*)$/m.exec(patch)?.[1] ?? false;
+ // Start from the latest commit, which is the last one in the list
+ for (const commit of event.payload.commits.reverse() as Commit[]) {
+ const response = await api.v3(commit.url, {ignoreHTTPStatus: true});
+ // Commits might not exist anymore even if they are listed in the events
+ // This can happen if the repository was deleted so we can also skip all other commits
+ if (response.httpStatus === 404) {
+ break;
+ }
+
+ if (!response.ok) {
+ throw await api.getError(response);
+ }
+
+ // `response.author` only appears if GitHub can match the email to a GitHub user
+ if (response.author?.id !== event.actor.id) {
+ continue;
+ }
+
+ const patch = await loadCommitPatch(commit.url);
+ // The patch of merge commits doesn't include the commit sha so the date might be from another user
+ if (patch.startsWith(`From ${commit.sha} `)) {
+ return /^Date: (.*)$/m.exec(patch)?.[1] ?? false;
+ }
}
}
}
- }
- return false;
-}, {
+ return false;
+ },
maxAge: {days: 10},
staleWhileRevalidate: {days: 20},
});
@@ -122,7 +123,7 @@ async function insertUserLocalTime(hovercardContainer: Element): Promise<void> {
return;
}
- const datePromise = getLastCommitDate(login);
+ const datePromise = lastCommitDate.get(login);
const race = await Promise.race([delay(300), datePromise]);
if (race === false) {
// The timezone was undeterminable and this resolved "immediately" (or was cached), so don't add the icon at all
@@ -167,3 +168,13 @@ function init(signal: AbortSignal): void {
void features.add(import.meta.url, {
init,
});
+
+/*
+
+Test URLs:
+
+1. Open https://github.com/sindresorhus/np/releases/tag/v8.0.4
+2. Hover over the username "sindresorhus" in the sidebar
+3. Notice his local time in the hovercard
+
+*/
diff --git a/source/features/user-profile-follower-badge.tsx b/source/features/user-profile-follower-badge.tsx
index ee3ae41e..8c6ea519 100644
--- a/source/features/user-profile-follower-badge.tsx
+++ b/source/features/user-profile-follower-badge.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import elementReady from 'element-ready';
import * as pageDetect from 'github-url-detection';
@@ -8,17 +8,18 @@ import api from '../github-helpers/api.js';
import {getUsername, getCleanPathname} from '../github-helpers/index.js';
import attachElement from '../helpers/attach-element.js';
-const doesUserFollow = cache.function('user-follows', async (userA: string, userB: string): Promise<boolean> => {
- const {httpStatus} = await api.v3(`/users/${userA}/following/${userB}`, {
- json: false,
- ignoreHTTPStatus: true,
- });
+const doesUserFollow = new CachedFunction('user-follows', {
+ async updater(userA: string, userB: string): Promise<boolean> {
+ const {httpStatus} = await api.v3(`/users/${userA}/following/${userB}`, {
+ json: false,
+ ignoreHTTPStatus: true,
+ });
- return httpStatus === 204;
-});
+ return httpStatus === 204;
+ }});
async function init(): Promise<void> {
- if (!await doesUserFollow(getCleanPathname(), getUsername()!)) {
+ if (!await doesUserFollow.get(getCleanPathname(), getUsername()!)) {
return;
}
@@ -40,3 +41,14 @@ void features.add(import.meta.url, {
],
init,
});
+
+/*
+
+Test URLs:
+
+1. Visit your own profile
+2. Click on "X followers" below your profile picture
+3. Click on a follower
+4. Look for a "Follows you" badge below their profile picture
+
+*/
diff --git a/source/features/warn-pr-from-master.tsx b/source/features/warn-pr-from-master.tsx
index 11e9e744..4c600ad7 100644
--- a/source/features/warn-pr-from-master.tsx
+++ b/source/features/warn-pr-from-master.tsx
@@ -3,14 +3,14 @@ import select from 'select-dom';
import * as pageDetect from 'github-url-detection';
import features from '../feature-manager.js';
-import getDefaultBranch, {getDefaultBranchOfRepo} from '../github-helpers/get-default-branch.js';
+import getDefaultBranch, {defaultBranchOfRepo} from '../github-helpers/get-default-branch.js';
import {getRepo} from '../github-helpers/index.js';
async function init(): Promise<false | void> {
let defaultBranch;
if (select.exists('.is-cross-repo')) {
const forkedRepository = getRepo(select('[title^="head: "]')!.textContent!)!;
- defaultBranch = await getDefaultBranchOfRepo(forkedRepository);
+ defaultBranch = await defaultBranchOfRepo.get(forkedRepository);
} else {
defaultBranch = await getDefaultBranch();
}
diff --git a/source/github-helpers/get-default-branch.ts b/source/github-helpers/get-default-branch.ts
index 1467ee16..f5e0c5db 100644
--- a/source/github-helpers/get-default-branch.ts
+++ b/source/github-helpers/get-default-branch.ts
@@ -1,4 +1,4 @@
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import elementReady from 'element-ready';
import {type RepositoryInfo} from 'github-url-detection';
@@ -41,8 +41,8 @@ async function fromAPI(repository: RepositoryInfo): Promise<string> {
// DO NOT use optional arguments/defaults in "cached functions" because they can't be memoized effectively
// https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1864
-export const getDefaultBranchOfRepo = cache.function('default-branch',
- async (repository: RepositoryInfo): Promise<string> => {
+export const defaultBranchOfRepo = new CachedFunction('default-branch', {
+ async updater(repository: RepositoryInfo): Promise<string> {
if (!repository) {
throw new Error('getDefaultBranch was called on a non-repository page');
}
@@ -50,13 +50,13 @@ export const getDefaultBranchOfRepo = cache.function('default-branch',
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- Wrong, the type is `false | undefined`
return (isCurrentRepo(repository) && await fromDOM()) || fromAPI(repository);
},
- {
- maxAge: {hours: 1},
- staleWhileRevalidate: {days: 20},
- cacheKey: ([repository]) => repository.nameWithOwner,
- },
+
+ maxAge: {hours: 1},
+ staleWhileRevalidate: {days: 20},
+ cacheKey: ([repository]) => repository.nameWithOwner,
+},
);
export default async function getDefaultBranch(): Promise<string> {
- return getDefaultBranchOfRepo(getRepo()!);
+ return defaultBranchOfRepo.get(getRepo()!);
}
diff --git a/source/helpers/bisect.tsx b/source/helpers/bisect.tsx
index d7014ca5..c4fc39f0 100644
--- a/source/helpers/bisect.tsx
+++ b/source/helpers/bisect.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedValue} from 'webext-storage-cache';
import select from 'select-dom';
import elementReady from 'element-ready';
@@ -7,6 +7,8 @@ import pluralize from './pluralize.js';
import featureLink from './feature-link.js';
import {importedFeatures} from '../../readme.md';
+export const state = new CachedValue<FeatureID[]>('bisect', {maxAge: {minutes: 15}});
+
// Split current list of features in half and create an options-like object to be applied on load
// Bisecting 4 features: enable 2
// Bisecting 3 features: enable 1
@@ -16,10 +18,10 @@ const getMiddleStep = (list: any[]): number => Math.floor(list.length / 2);
async function onChoiceButtonClick({currentTarget: button}: React.MouseEvent<HTMLButtonElement>): Promise<void> {
const answer = button.value;
- const bisectedFeatures = (await cache.get<FeatureID[]>('bisect'))!;
+ const bisectedFeatures = (await state.get())!;
if (bisectedFeatures.length > 1) {
- await cache.set('bisect', answer === 'yes'
+ await state.set(answer === 'yes'
? bisectedFeatures.slice(0, getMiddleStep(bisectedFeatures))
: bisectedFeatures.slice(getMiddleStep(bisectedFeatures)),
);
@@ -42,12 +44,12 @@ async function onChoiceButtonClick({currentTarget: button}: React.MouseEvent<HTM
createMessageBox(<>The change or issue is caused by {feature}.</>);
}
- await cache.delete('bisect');
+ await state.delete();
window.removeEventListener('visibilitychange', hideMessage);
}
async function onEndButtonClick(): Promise<void> {
- await cache.delete('bisect');
+ await state.delete();
location.reload();
}
@@ -65,14 +67,14 @@ function createMessageBox(message: Element | string, extraButtons?: Element): vo
}
async function hideMessage(): Promise<void> {
- if (!await cache.get<FeatureID[]>('bisect')) {
+ if (!await state.get()) {
createMessageBox('Process completed in another tab');
}
}
export default async function bisectFeatures(): Promise<Record<string, boolean> | void> {
// `bisect` stores the list of features to be split in half
- const bisectedFeatures = await cache.get<FeatureID[]>('bisect');
+ const bisectedFeatures = await state.get();
if (!bisectedFeatures) {
return;
}
diff --git a/source/helpers/clear-cache-handler.ts b/source/helpers/clear-cache-handler.ts
index 3571afad..7e427d0a 100644
--- a/source/helpers/clear-cache-handler.ts
+++ b/source/helpers/clear-cache-handler.ts
@@ -1,7 +1,7 @@
-import cache from 'webext-storage-cache';
+import {globalCache} from 'webext-storage-cache';
export default async function clearCacheHandler(event: MouseEvent): Promise<void> {
- await cache.clear();
+ await globalCache.clear();
const button = event.target as HTMLButtonElement;
const initialText = button.textContent;
button.textContent = 'Cache cleared!';
diff --git a/source/helpers/hotfix.tsx b/source/helpers/hotfix.tsx
index 7ac7657d..6c6765fb 100644
--- a/source/helpers/hotfix.tsx
+++ b/source/helpers/hotfix.tsx
@@ -1,5 +1,5 @@
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
+import {CachedFunction} from 'webext-storage-cache';
import {isEnterprise} from 'github-url-detection';
import compareVersions from 'tiny-version-compare';
import {any as concatenateTemplateLiteralTag} from 'code-tag';
@@ -7,6 +7,8 @@ import {any as concatenateTemplateLiteralTag} from 'code-tag';
import {RGHOptions} from '../options-storage.js';
import isDevelopmentVersion from './is-development-version.js';
+const {version: currentVersion} = browser.runtime.getManifest();
+
function parseCsv(content: string): string[][] {
const lines = [];
const [_header, ...rawLines] = content.trim().split('\n');
@@ -33,35 +35,35 @@ async function fetchHotfix(path: string): Promise<string> {
return '';
}
-export type HotfixStorage = Array<[FeatureID, string, string]>;
+type HotfixStorage = Array<[FeatureID, string, string]>;
-export const updateHotfixes = cache.function('hotfixes', async (version: string): Promise<HotfixStorage> => {
- const content = await fetchHotfix('broken-features.csv');
- if (!content) {
- return [];
- }
+export const brokenFeatures = new CachedFunction('broken-features', {
+ async updater(): Promise<HotfixStorage> {
+ const content = await fetchHotfix('broken-features.csv');
+ if (!content) {
+ return [];
+ }
- const storage: HotfixStorage = [];
- for (const [featureID, relatedIssue, unaffectedVersion] of parseCsv(content)) {
- if (featureID && relatedIssue && (!unaffectedVersion || compareVersions(unaffectedVersion, version) > 0)) {
- storage.push([featureID as FeatureID, relatedIssue, unaffectedVersion]);
+ const storage: HotfixStorage = [];
+ for (const [featureID, relatedIssue, unaffectedVersion] of parseCsv(content)) {
+ if (featureID && relatedIssue && (!unaffectedVersion || compareVersions(unaffectedVersion, currentVersion) > 0)) {
+ storage.push([featureID as FeatureID, relatedIssue, unaffectedVersion]);
+ }
}
- }
- return storage;
-}, {
+ return storage;
+ },
maxAge: {hours: 6},
staleWhileRevalidate: {days: 30},
- cacheKey: () => '',
});
-export const getStyleHotfix = cache.function('style-hotfixes',
- async (version: string): Promise<string> => fetchHotfix(`style/${version}.css`),
- {
- maxAge: {hours: 6},
- staleWhileRevalidate: {days: 300},
- cacheKey: () => '',
- },
+export const styleHotfixes = new CachedFunction('style-hotfixes', {
+ updater: async (version: string): Promise<string> => fetchHotfix(`style/${version}.css`),
+
+ maxAge: {hours: 6},
+ staleWhileRevalidate: {days: 300},
+ cacheKey: () => '',
+},
);
export async function getLocalHotfixes(): Promise<HotfixStorage> {
@@ -71,7 +73,7 @@ export async function getLocalHotfixes(): Promise<HotfixStorage> {
return [];
}
- return await cache.get<HotfixStorage>('hotfixes:') ?? [];
+ return await brokenFeatures.get() ?? [];
}
export async function getLocalHotfixesAsOptions(): Promise<Partial<RGHOptions>> {
@@ -92,7 +94,6 @@ export async function applyStyleHotfixes(style: string): Promise<void> {
document.body.prepend(<style>{style}</style>);
}
-const stringHotfixesKey = 'strings-hotfixes';
let localStrings: Record<string, string> = {};
export function _(...arguments_: Parameters<typeof concatenateTemplateLiteralTag>): string {
const original = concatenateTemplateLiteralTag(...arguments_);
@@ -100,18 +101,19 @@ export function _(...arguments_: Parameters<typeof concatenateTemplateLiteralTag
}
// Updates the local object from the storage to enable synchronous access
-export async function getLocalStrings(): Promise<void> {
+export async function preloadSyncLocalStrings(): Promise<void> {
if (isDevelopmentVersion() || isEnterprise()) {
return;
}
- localStrings = await cache.get<Record<string, string>>(stringHotfixesKey + ':') ?? {};
+ localStrings = await localStringsHotfix.get() ?? {};
}
-export const updateLocalStrings = cache.function(stringHotfixesKey, async (): Promise<Record<string, string>> => {
- const json = await fetchHotfix('strings.json');
- return json ? JSON.parse(json) : {};
-}, {
+export const localStringsHotfix = new CachedFunction('strings-hotfixes', {
+ async updater(): Promise<Record<string, string>> {
+ const json = await fetchHotfix('strings.json');
+ return json ? JSON.parse(json) : {};
+ },
maxAge: {hours: 6},
staleWhileRevalidate: {days: 30},
});
diff --git a/source/options.tsx b/source/options.tsx
index 6fe62807..277f6950 100644
--- a/source/options.tsx
+++ b/source/options.tsx
@@ -1,7 +1,6 @@
import 'webext-base-css/webext-base.css';
import './options.css';
import React from 'dom-chef';
-import cache from 'webext-storage-cache';
import domify from 'doma';
import select from 'select-dom';
import fitTextarea from 'fit-textarea';
@@ -14,13 +13,14 @@ import {isEnterprise} from 'github-url-detection';
import featureLink from './helpers/feature-link.js';
import clearCacheHandler from './helpers/clear-cache-handler.js';
-import {getLocalHotfixes} from './helpers/hotfix.js';
+import {getLocalHotfixes, styleHotfixes} from './helpers/hotfix.js';
import {createRghIssueLink} from './helpers/rgh-issue-link.js';
import {importedFeatures, featuresMeta} from '../readme.md';
import getStorageBytesInUse from './helpers/used-storage.js';
import {perDomainOptions} from './options-storage.js';
import isDevelopmentVersion from './helpers/is-development-version.js';
import {doesBrowserActionOpenOptions} from './helpers/feature-utils.js';
+import {state as bisectState} from './helpers/bisect.js';
type Status = {
error?: true;
@@ -28,6 +28,8 @@ type Status = {
scopes?: string[];
};
+const {version} = browser.runtime.getManifest();
+
function reportStatus({error, text, scopes}: Status): void {
const tokenStatus = select('#validation')!;
tokenStatus.textContent = text ?? '';
@@ -162,7 +164,7 @@ async function findFeatureHandler(event: Event): Promise<void> {
// TODO: Add support for GHE
const options = await perDomainOptions.getOptionsForOrigin().getAll();
const enabledFeatures = importedFeatures.filter(featureId => options['feature:' + featureId]);
- await cache.set<FeatureID[]>('bisect', enabledFeatures, {minutes: 5});
+ await bisectState.set(enabledFeatures);
const button = event.target as HTMLButtonElement;
button.disabled = true;
@@ -243,7 +245,7 @@ function updateRateLink(): void {
}
async function showStoredCssHotfixes(): Promise<void> {
- const cachedCSS = await cache.get<string>('style-hotfixes:');
+ const cachedCSS = await styleHotfixes.getCached(version);
select('#hotfixes-field')!.textContent
= isDevelopmentVersion()
? 'Hotfixes are not applied in the development version.'
diff --git a/source/refined-github.ts b/source/refined-github.ts
index 0235d210..89100161 100644
--- a/source/refined-github.ts
+++ b/source/refined-github.ts
@@ -175,7 +175,6 @@ import './features/rgh-feature-descriptions.js';
import './features/wait-for-attachments.js';
import './features/archive-forks-link.js';
import './features/link-to-changelog-file.js';
-import './features/rgh-sponsor-button.js';
import './features/rgh-linkify-features.js';
import './features/conversation-activity-filter.js';
import './features/select-all-notifications-shortcut.js';
diff --git a/tsconfig.json b/tsconfig.json
index 27431088..886d8e70 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,7 +3,6 @@
"compilerOptions": {
"target": "es2022",
"module": "es2022",
- "moduleResolution": "Node",
"noUncheckedIndexedAccess": false,
"noPropertyAccessFromIndexSignature": false,
"lib": [
diff --git a/webpack.config.ts b/webpack.config.ts
index 91d52a56..2c9fd70a 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -1,4 +1,4 @@
-/// <reference types="./source/globals" />
+/// <reference types="./source/globals.js" />
import path from 'node:path';
import SizePlugin from 'size-plugin';
@@ -18,7 +18,7 @@ const config: Configuration = {
'background',
'options',
'resolve-conflicts',
- ].map(name => [name, `./${name}`])),
+ ].map(name => [name, `./${name}.js`])),
context: path.resolve('source'),
output: {
path: path.resolve('distribution/assets'),
@@ -71,8 +71,6 @@ const config: Configuration = {
react: 'dom-chef',
},
extensions: [
- '.tsx',
- '.ts',
'.js',
],
extensionAlias: {