diff options
133 files changed, 528 insertions, 529 deletions
diff --git a/contributing.md b/contributing.md index e98c2e1f..d542024a 100644 --- a/contributing.md +++ b/contributing.md @@ -46,7 +46,7 @@ Here's an example using all of the possible `feature.add` options: ```tsx import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; diff --git a/package-lock.json b/package-lock.json index 0eb09b64..63347bc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,7 @@ "pretty-bytes": "^6.1.1", "push-form": "^1.0.1", "regex-join": "^2.0.0", - "select-dom": "^8.0.0", + "select-dom": "^9.0.0", "shorten-repo-url": "^3.0.0", "strip-indent": "^4.0.0", "text-field-edit": "^3.2.0", @@ -8552,9 +8552,9 @@ "dev": true }, "node_modules/select-dom": { - "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-9.0.0.tgz", + "integrity": "sha512-AD1NfASSwJQc1Sy713R1vT9CMmUSUo2tg0j1FtkRGIxllTphDrkuH2pYYvhRlGaoq3pS6tcKkKS/diEZLy+7BQ==", "dependencies": { "typed-query-selector": "^2.11.0" }, @@ -17030,9 +17030,9 @@ } }, "select-dom": { - "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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-9.0.0.tgz", + "integrity": "sha512-AD1NfASSwJQc1Sy713R1vT9CMmUSUo2tg0j1FtkRGIxllTphDrkuH2pYYvhRlGaoq3pS6tcKkKS/diEZLy+7BQ==", "requires": { "typed-query-selector": "^2.11.0" } diff --git a/package.json b/package.json index a74612fb..4baf268f 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "pretty-bytes": "^6.1.1", "push-form": "^1.0.1", "regex-join": "^2.0.0", - "select-dom": "^8.0.0", + "select-dom": "^9.0.0", "shorten-repo-url": "^3.0.0", "strip-indent": "^4.0.0", "text-field-edit": "^3.2.0", diff --git a/source/feature-manager.tsx b/source/feature-manager.tsx index e1b02136..6ec2f361 100644 --- a/source/feature-manager.tsx +++ b/source/feature-manager.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import domLoaded from 'dom-loaded'; import stripIndent from 'strip-indent'; import {Promisable} from 'type-fest'; @@ -112,7 +112,7 @@ const globalReady = new Promise<RGHOptions>(async resolve => { return; } - if (select.exists('[refined-github]')) { + if (elementExists('[refined-github]')) { console.warn(stripIndent(` Refined GitHub has been loaded twice. This may be because: @@ -147,7 +147,7 @@ const globalReady = new Promise<RGHOptions>(async resolve => { log.info = options.logging ? console.log : () => {/* No logging */}; log.http = options.logHTTP ? console.log : () => {/* No logging */}; - if (select.exists('body.logged-out')) { + if (elementExists('body.logged-out')) { console.warn('Refined GitHub is only expected to work when you’re logged in to GitHub. Errors will not be shown.'); features.log.error = () => {/* No logging */}; } @@ -266,7 +266,7 @@ async function add(url: string, ...loaders: FeatureLoader[]): Promise<void> { } document.addEventListener('turbo:render', () => { - if (!deduplicate || !select.exists(deduplicate)) { + if (!deduplicate || !elementExists(deduplicate)) { void setupPageLoad(id, details); } }); @@ -311,9 +311,9 @@ void add('rgh-deduplicator' as FeatureID, { async init() { // `await` kicks it to the next tick, after the other features have checked for 'has-rgh', so they can run once. await Promise.resolve(); - select('has-rgh')?.remove(); // https://github.com/refined-github/refined-github/issues/6568 - select(_`#js-repo-pjax-container, #js-pjax-container`)?.append(<has-rgh/>); - select(_`turbo-frame`)?.append(<has-rgh-inner/>); // #4567 + $('has-rgh')?.remove(); // https://github.com/refined-github/refined-github/issues/6568 + $(_`#js-repo-pjax-container, #js-pjax-container`)?.append(<has-rgh/>); + $(_`turbo-frame`)?.append(<has-rgh-inner/>); // #4567 }, }); diff --git a/source/features/action-used-by-link.tsx b/source/features/action-used-by-link.tsx index 04ec9269..6c3d0bc4 100644 --- a/source/features/action-used-by-link.tsx +++ b/source/features/action-used-by-link.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {SearchIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -19,7 +19,7 @@ function init(): void { o: 'desc', }).toString(); - select('.d-block.mb-2[href^="/contact"]')!.after( + $('.d-block.mb-2[href^="/contact"]')!.after( <a href={actionURL.href} className="d-block mb-2"> <SearchIcon width={14} className="color-fg-default mr-2"/>Usage examples </a>, diff --git a/source/features/actionable-pr-view-file.tsx b/source/features/actionable-pr-view-file.tsx index 6fe2f4fd..5a5af0e6 100644 --- a/source/features/actionable-pr-view-file.tsx +++ b/source/features/actionable-pr-view-file.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -25,9 +25,9 @@ void features.add(import.meta.url, { exclude: [ // Editing files doesn't make sense after a PR is closed/merged pageDetect.isClosedPR, - () => select('.head-ref')!.title === 'This repository has been deleted', + () => $('.head-ref')!.title === 'This repository has been deleted', // If you're viewing changes from partial commits, ensure you're on the latest one. - () => select.exists('.js-commits-filtered') && !select.exists('[aria-label="You are viewing the latest commit"]'), + () => elementExists('.js-commits-filtered') && !elementExists('[aria-label="You are viewing the latest commit"]'), ], awaitDomReady: true, // DOM-based filters, feature is invisible and inactive until dropdown is opened init, diff --git a/source/features/avoid-accidental-submissions.tsx b/source/features/avoid-accidental-submissions.tsx index 2aa93d13..13d6324d 100644 --- a/source/features/avoid-accidental-submissions.tsx +++ b/source/features/avoid-accidental-submissions.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -14,7 +14,7 @@ function onKeyDown(event: DelegateEvent<KeyboardEvent, HTMLInputElement>): void || event.ctrlKey || event.metaKey || event.isComposing // #4323 - || select.exists([ + || elementExists([ '.suggester', // GitHub’s autocomplete dropdown '.rgh-avoid-accidental-submissions', ], form) @@ -22,7 +22,7 @@ function onKeyDown(event: DelegateEvent<KeyboardEvent, HTMLInputElement>): void return; } - if (select.exists('.btn-primary[type="submit"]:disabled', form)) { + if (elementExists('.btn-primary[type="submit"]:disabled', form)) { return; } diff --git a/source/features/batch-mark-files-as-viewed.tsx b/source/features/batch-mark-files-as-viewed.tsx index dc691b1b..02431523 100644 --- a/source/features/batch-mark-files-as-viewed.tsx +++ b/source/features/batch-mark-files-as-viewed.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import debounceFn from 'debounce-fn'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -31,7 +31,7 @@ const batchToggle = debounceFn((event: DelegateEvent<MouseEvent, HTMLFormElement event.stopImmediatePropagation(); - const files = select.all('.js-file'); + const files = $$('.js-file'); const thisFile = event.delegateTarget.closest('.js-file')!; const isThisBeingFileChecked = !isChecked(thisFile); // Flip it because the value hasn't changed yet @@ -39,7 +39,7 @@ const batchToggle = debounceFn((event: DelegateEvent<MouseEvent, HTMLFormElement const selectedFiles = getItemsBetween(files, previousFile, thisFile); for (const file of selectedFiles) { if (file !== thisFile && isChecked(file) !== isThisBeingFileChecked) { - select('.js-reviewed-checkbox', file)!.click(); + $('.js-reviewed-checkbox', file)!.click(); } } diff --git a/source/features/bugs-tab.tsx b/source/features/bugs-tab.tsx index 001da2d4..88d02039 100644 --- a/source/features/bugs-tab.tsx +++ b/source/features/bugs-tab.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import {BugIcon} from '@primer/octicons-react'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -87,13 +87,13 @@ async function addBugsTab(): Promise<void | false> { bugsTab.removeAttribute('id'); // Update its appearance - const bugsTabTitle = select('[data-content]', bugsTab)!; + const bugsTabTitle = $('[data-content]', bugsTab)!; bugsTabTitle.dataset.content = 'Bugs'; bugsTabTitle.textContent = 'Bugs'; - select('.octicon', bugsTab)!.replaceWith(<BugIcon className="UnderlineNav-octicon d-none d-sm-inline"/>); + $('.octicon', bugsTab)!.replaceWith(<BugIcon className="UnderlineNav-octicon d-none d-sm-inline"/>); // Set temporary counter - const bugsCounter = select('.Counter', bugsTab)!; + const bugsCounter = $('.Counter', bugsTab)!; bugsCounter.textContent = '0'; bugsCounter.title = ''; @@ -123,8 +123,8 @@ async function addBugsTab(): Promise<void | false> { // TODO: Use native highlighting https://github.com/refined-github/refined-github/pull/6909#discussion_r1322607091 function highlightBugsTab(): void { // Remove highlighting from "Issues" tab - unhighlightTab(select('.UnderlineNav-item[data-hotkey="g i"]')!); - highlightTab(select('.rgh-bugs-tab')!); + unhighlightTab($('.UnderlineNav-item[data-hotkey="g i"]')!); + highlightTab($('.rgh-bugs-tab')!); } async function removePinnedIssues(): Promise<void> { @@ -156,7 +156,7 @@ async function updateBugsTagHighlighting(): Promise<void | false> { } async function init(): Promise<void | false> { - if (!select.exists('.rgh-bugs-tab')) { + if (!elementExists('.rgh-bugs-tab')) { await addBugsTab(); } diff --git a/source/features/clean-conversation-filters.tsx b/source/features/clean-conversation-filters.tsx index 9d4c72b0..8853a6ce 100644 --- a/source/features/clean-conversation-filters.tsx +++ b/source/features/clean-conversation-filters.tsx @@ -1,5 +1,5 @@ import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -15,7 +15,7 @@ const hasAnyProjects = new CachedFunction('has-projects', { return true; } - const isOrganization = select.exists('[rel=author][data-hovercard-type="organization"]'); + const isOrganization = elementExists('[rel=author][data-hovercard-type="organization"]'); if (!activeProjectsCounter && !isOrganization) { // No tab = Projects disabled in repo // No organization = no Projects in organization diff --git a/source/features/clean-conversation-headers.tsx b/source/features/clean-conversation-headers.tsx index 7d9249c8..7476a1ca 100644 --- a/source/features/clean-conversation-headers.tsx +++ b/source/features/clean-conversation-headers.tsx @@ -1,6 +1,6 @@ import './clean-conversation-headers.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import elementReady from 'element-ready'; import {ArrowLeftIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -14,7 +14,7 @@ async function cleanIssueHeader(byline: HTMLElement): Promise<void> { // Shows on issues: octocat opened this issue on 1 Jan · [1 comments] // Removes on issues: octocat opened this issue on 1 Jan [·] 1 comments - const commentCount = select('relative-time', byline)!.nextSibling!; + const commentCount = $('relative-time', byline)!.nextSibling!; commentCount.replaceWith(<span>{commentCount.textContent.replace('·', '')}</span>); } @@ -23,13 +23,13 @@ async function cleanPrHeader(byline: HTMLElement): Promise<void> { // Extra author name is only shown on `isPRConversation` // Hide if it's the same as the opener (always) or merger - const shouldHideAuthor = pageDetect.isPRConversation() && select('.author', byline)!.textContent === (await elementReady('.TimelineItem .author'))!.textContent; + const shouldHideAuthor = pageDetect.isPRConversation() && $('.author', byline)!.textContent === (await elementReady('.TimelineItem .author'))!.textContent; if (shouldHideAuthor) { byline.classList.add('rgh-clean-conversation-headers-hide-author'); } - const base = select('.commit-ref', byline)!; - const baseBranchDropdown = select('.commit-ref-dropdown', byline); + const base = $('.commit-ref', byline)!; + const baseBranchDropdown = $('.commit-ref-dropdown', byline); // Shows on PRs: main [←] feature const arrowIcon = <ArrowLeftIcon className="v-align-middle mx-1"/>; diff --git a/source/features/clean-conversation-sidebar.tsx b/source/features/clean-conversation-sidebar.tsx index 8a4aa0dd..22906be6 100644 --- a/source/features/clean-conversation-sidebar.tsx +++ b/source/features/clean-conversation-sidebar.tsx @@ -1,6 +1,6 @@ import './clean-conversation-sidebar.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import onetime from 'onetime'; import * as pageDetect from 'github-url-detection'; @@ -10,7 +10,7 @@ import observe from '../helpers/selector-observer.js'; import {removeTextNodeContaining} from '../helpers/dom-utils.js'; import {isHasSelectorSupported} from '../helpers/select-has.js'; -const canEditSidebar = onetime((): boolean => select.exists('.discussion-sidebar-item [data-hotkey="l"]')); +const canEditSidebar = onetime((): boolean => elementExists('.discussion-sidebar-item [data-hotkey="l"]')); function getNodesAfter(node: Node): Range { const range = new Range(); @@ -20,12 +20,12 @@ function getNodesAfter(node: Node): Range { } async function cleanReviewers(): Promise<void> { - const possibleReviewers = select('[src$="/suggested-reviewers"]'); + const possibleReviewers = $('[src$="/suggested-reviewers"]'); if (possibleReviewers) { await onElementRemoval(possibleReviewers); } - const content = select('[aria-label="Select reviewers"] > .css-truncate')!; + const content = $('[aria-label="Select reviewers"] > .css-truncate')!; if (!content.firstElementChild) { removeTextNodeContaining(content, 'No reviews'); } @@ -47,7 +47,7 @@ Expected DOM: @param selector Element that contains `details` or `.discussion-sidebar-heading` or distinctive element inside it */ function cleanSection(selector: string): boolean { - const container = select(`:is(form, .discussion-sidebar-item):has(${selector})`); + const container = $(`:is(form, .discussion-sidebar-item):has(${selector})`); if (!container) { return false; } @@ -59,7 +59,7 @@ function cleanSection(selector: string): boolean { '[aria-label="Select projects"] .Link--primary', ]; - const heading = select([ + const heading = $([ 'details:has(> .discussion-sidebar-heading)', // Can edit sidebar, has a dropdown '.discussion-sidebar-heading', // Cannot editor sidebar, has a plain heading ], container)!; @@ -79,17 +79,17 @@ function cleanSection(selector: string): boolean { } async function cleanSidebar(): Promise<void> { - select('#partial-discussion-sidebar')!.classList.add('rgh-clean-sidebar'); + $('#partial-discussion-sidebar')!.classList.add('rgh-clean-sidebar'); // Assignees - const assignees = select('.js-issue-assignees')!; + const assignees = $('.js-issue-assignees')!; if (assignees.children.length === 0) { assignees.closest('.discussion-sidebar-item')!.remove(); } else { - const assignYourself = select('.js-issue-assign-self'); + const assignYourself = $('.js-issue-assign-self'); if (assignYourself) { removeTextNodeContaining(assignYourself.previousSibling!, 'No one—'); - select('[aria-label="Select assignees"] summary')!.append( + $('[aria-label="Select assignees"] summary')!.append( <span style={{fontWeight: 'normal'}}> – {assignYourself}</span>, ); assignees.closest('.discussion-sidebar-item')!.classList.add('rgh-clean-sidebar'); @@ -104,20 +104,20 @@ async function cleanSidebar(): Promise<void> { // Labels if (!cleanSection('.js-issue-labels') && !canEditSidebar()) { // Hide heading in any case except `canEditSidebar` - select('.discussion-sidebar-item:has(.js-issue-labels) .discussion-sidebar-heading')! + $('.discussion-sidebar-item:has(.js-issue-labels) .discussion-sidebar-heading')! .remove(); } // Development (linked issues/PRs) - const developmentHint = select('[aria-label="Link issues"] p'); + const developmentHint = $('[aria-label="Link issues"] p'); if (developmentHint) { // This may not exist if issues are disabled removeTextNodeContaining(developmentHint, /No branches or pull requests|Successfully merging/); } - const createBranchLink = select('button[data-action="click:create-issue-branch#openDialog"]'); + const createBranchLink = $('button[data-action="click:create-issue-branch#openDialog"]'); if (createBranchLink) { createBranchLink.classList.add('Link--muted'); - select('[aria-label="Link issues"] summary')!.append( + $('[aria-label="Link issues"] summary')!.append( <span style={{fontWeight: 'normal'}}> – {createBranchLink}</span>, ); } diff --git a/source/features/clean-repo-filelist-actions.tsx b/source/features/clean-repo-filelist-actions.tsx index 11bc1add..1c47242c 100644 --- a/source/features/clean-repo-filelist-actions.tsx +++ b/source/features/clean-repo-filelist-actions.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {CodeIcon, PlusIcon, SearchIcon} from '@primer/octicons-react'; @@ -38,11 +38,11 @@ function cleanFilelistActions(searchButton: Element): void { return; } - const codeDropdownButton = select('get-repo summary')!; + const codeDropdownButton = $('get-repo summary')!; addTooltipToSummary(codeDropdownButton, 'Clone, open or download'); - const label = select('.Button-label', codeDropdownButton)!; - if (!select.exists('.octicon-code', codeDropdownButton)) { + const label = $('.Button-label', codeDropdownButton)!; + if (!elementExists('.octicon-code', codeDropdownButton)) { // The icon is missing for users without Codespaces https://github.com/refined-github/refined-github/pull/5074#issuecomment-983251719 label.before(<span className="Button-visual Button-leadingVisual"><CodeIcon/></span>); } diff --git a/source/features/clean-repo-sidebar.tsx b/source/features/clean-repo-sidebar.tsx index 313a3b06..a941d3b1 100644 --- a/source/features/clean-repo-sidebar.tsx +++ b/source/features/clean-repo-sidebar.tsx @@ -1,5 +1,5 @@ import './clean-repo-sidebar.css'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import domLoaded from 'dom-loaded'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -15,7 +15,7 @@ async function cleanReleases(): Promise<void> { } const releasesSection = sidebarReleases.closest('.BorderGrid-cell')!; - if (!select.exists('.octicon-tag', releasesSection)) { + if (!elementExists('.octicon-tag', releasesSection)) { // Hide the whole section if there's no releases releasesSection.hidden = true; return; @@ -39,7 +39,7 @@ async function hideEmptyPackages(): Promise<void> { async function hideLanguageHeader(): Promise<void> { await domLoaded; - const lastSidebarHeader = select('.Layout-sidebar .BorderGrid-row:last-of-type h2'); + const lastSidebarHeader = $('.Layout-sidebar .BorderGrid-row:last-of-type h2'); if (lastSidebarHeader?.textContent === 'Languages') { lastSidebarHeader.hidden = true; } @@ -50,17 +50,17 @@ async function hideEmptyMeta(): Promise<void> { await domLoaded; if (!pageDetect.canUserEditRepo()) { - select('.Layout-sidebar .BorderGrid-cell > .text-italic')?.remove(); + $('.Layout-sidebar .BorderGrid-cell > .text-italic')?.remove(); } } async function moveReportLink(): Promise<void> { await domLoaded; - const reportLink = select('.Layout-sidebar a[href^="/contact/report-content"]')?.parentElement; + const reportLink = $('.Layout-sidebar a[href^="/contact/report-content"]')?.parentElement; if (reportLink) { // Your own repos don't include this link - select('.Layout-sidebar .BorderGrid-row:last-of-type .BorderGrid-cell')!.append(reportLink); + $('.Layout-sidebar .BorderGrid-row:last-of-type .BorderGrid-cell')!.append(reportLink); } } diff --git a/source/features/clean-repo-tabs.tsx b/source/features/clean-repo-tabs.tsx index 9b9b45f6..8ba3e550 100644 --- a/source/features/clean-repo-tabs.tsx +++ b/source/features/clean-repo-tabs.tsx @@ -1,5 +1,5 @@ import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -27,24 +27,24 @@ function mustKeepTab(tab: HTMLElement): boolean { } function setTabCounter(tab: HTMLElement, count: number): void { - const tabCounter = select('.Counter', tab)!; + const tabCounter = $('.Counter', tab)!; tabCounter.textContent = abbreviateNumber(count); tabCounter.title = count > 999 ? String(count) : ''; } function onlyShowInDropdown(id: string): void { - const tabItem = select(`[data-tab-item$="${id}"]`); + const tabItem = $(`[data-tab-item$="${id}"]`); if (!tabItem && pageDetect.isEnterprise()) { // GHE #3962 return; } (tabItem!.closest('li') ?? tabItem!.closest('.UnderlineNav-item'))!.classList.add('d-none'); - const menuItem = select(`[data-menu-item$="${id}"]`)!; + const menuItem = $(`[data-menu-item$="${id}"]`)!; menuItem.removeAttribute('data-menu-item'); menuItem.hidden = false; // The item has to be moved somewhere else because the overflow nav is order-dependent - select('.UnderlineNav-actions ul')!.append(menuItem); + $('.UnderlineNav-actions ul')!.append(menuItem); } const wikiPageCount = new CachedFunction('wiki-page-count', { diff --git a/source/features/clear-pr-merge-commit-message.tsx b/source/features/clear-pr-merge-commit-message.tsx index 275c111d..ee1819b0 100644 --- a/source/features/clear-pr-merge-commit-message.tsx +++ b/source/features/clear-pr-merge-commit-message.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -51,7 +51,7 @@ void features.add(import.meta.url, { ], exclude: [ // Don't clear 1-commit PRs #3140 - () => select.all('.TimelineItem.js-commit').length === 1, + () => $$('.TimelineItem.js-commit').length === 1, ], awaitDomReady: true, // Appears near the end of the page anyway init, diff --git a/source/features/close-out-of-view-modals.tsx b/source/features/close-out-of-view-modals.tsx index 47eec3bc..0fd98065 100644 --- a/source/features/close-out-of-view-modals.tsx +++ b/source/features/close-out-of-view-modals.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import onetime from 'onetime'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -38,7 +38,7 @@ function menuActivatedHandler(event: DelegateEvent): void { lastOpen = Date.now(); - const modals = select.all([ + const modals = $$([ ':scope > details-menu', // "Watch repo" dropdown ':scope > details-dialog', // "Watch repo" dropdown ':scope > div > .dropdown-menu', // "Clone or download" and "Repo nav overflow" diff --git a/source/features/closing-remarks.tsx b/source/features/closing-remarks.tsx index 39049f31..03858779 100644 --- a/source/features/closing-remarks.tsx +++ b/source/features/closing-remarks.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {TagIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -43,7 +43,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 mergeCommit = $(`.TimelineItem.js-details-container.Details a[href^="/${getRepo()!.nameWithOwner}/commit/" i] > code`)!.textContent; const tagName = await firstTag.get(mergeCommit); if (tagName) { diff --git a/source/features/comment-fields-keyboard-shortcuts.tsx b/source/features/comment-fields-keyboard-shortcuts.tsx index b765cb3c..e81ff0be 100644 --- a/source/features/comment-fields-keyboard-shortcuts.tsx +++ b/source/features/comment-fields-keyboard-shortcuts.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; import filterAlteredClicks from 'filter-altered-clicks'; @@ -9,7 +9,7 @@ import {onCommentFieldKeydown} from '../github-events/on-field-keydown.js'; function handleEscapeKey(event: DelegateEvent<KeyboardEvent, HTMLTextAreaElement>, targetField: HTMLTextAreaElement): void { // Cancel buttons have different classes for inline comments and editable comments - const cancelButton = select(` + const cancelButton = $(` button.js-hide-inline-comment-form, button.js-comment-cancel-button `, targetField.form!); @@ -32,13 +32,13 @@ function handleArrowUpKey(targetField: HTMLTextAreaElement): void { '#all_commit_comments', // Single commit comments at the bottom ])!; - const lastOwnComment = select - .all('.js-comment.current-user', currentConversationContainer) - .reverse() - .find(comment => { - const collapsible = comment.closest('details'); - return !collapsible || collapsible.open; - }); + const lastOwnComment + = $$('.js-comment.current-user', currentConversationContainer) + .reverse() + .find(comment => { + const collapsible = comment.closest('details'); + return !collapsible || collapsible.open; + }); if (!lastOwnComment) { return; @@ -56,7 +56,7 @@ function handleArrowUpKey(targetField: HTMLTextAreaElement): void { // Move caret to end of the field requestAnimationFrame(() => { - select('textarea.js-comment-field', lastOwnComment)!.selectionStart = Number.MAX_SAFE_INTEGER; + $('textarea.js-comment-field', lastOwnComment)!.selectionStart = Number.MAX_SAFE_INTEGER; }); } diff --git a/source/features/comments-time-machine-links.tsx b/source/features/comments-time-machine-links.tsx index 6ea729d4..2c932650 100644 --- a/source/features/comments-time-machine-links.tsx +++ b/source/features/comments-time-machine-links.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -17,7 +17,7 @@ async function updateURLtoDatedSha(url: GitHubFileURL, date: string): Promise<vo const {repository} = await api.v4(GetCommitAtDate, {variables: {date, branch: url.branch}}); const [{oid}] = repository.ref.target.history.nodes; - select('a.rgh-link-date')!.pathname = url.assign({branch: oid}).pathname; + $('a.rgh-link-date')!.pathname = url.assign({branch: oid}).pathname; } async function showTimeMachineBar(): Promise<void | false> { @@ -65,7 +65,7 @@ async function showTimeMachineBar(): Promise<void | false> { function addInlineLinks(menu: HTMLElement, timestamp: string): void { const comment = menu.closest('.js-comment')!; // TODO: Move selector directly to observer - const links = select.all(` + const links = $$(` a[href^="${location.origin}"][href*="/blob/"]:not(.${linkifiedURLClass}), a[href^="${location.origin}"][href*="/tree/"]:not(.${linkifiedURLClass}) `, comment); @@ -86,7 +86,7 @@ function addInlineLinks(menu: HTMLElement, timestamp: string): void { } function addDropdownLink(menu: HTMLElement, timestamp: string): void { - select('.show-more-popover', menu.parentElement!)!.append( + $('.show-more-popover', menu.parentElement!)!.append( <div className="dropdown-divider"/>, <a href={buildRepoURL(`tree/HEAD@{${timestamp}}`)} diff --git a/source/features/conversation-activity-filter.tsx b/source/features/conversation-activity-filter.tsx index 22157c27..2525d27b 100644 --- a/source/features/conversation-activity-filter.tsx +++ b/source/features/conversation-activity-filter.tsx @@ -1,7 +1,7 @@ import './conversation-activity-filter.css'; import delay from 'delay'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {CheckIcon, EyeClosedIcon, EyeIcon, XIcon} from '@primer/octicons-react'; @@ -26,7 +26,7 @@ const collapsedClassName = 'rgh-conversation-activity-collapsed'; function processTimelineEvent(item: HTMLElement): void { // Don't hide commits in PR conversation timelines #5581 - if (pageDetect.isPR() && select.exists('.TimelineItem-badge .octicon-git-commit', item)) { + if (pageDetect.isPR() && elementExists('.TimelineItem-badge .octicon-git-commit', item)) { return; } @@ -35,7 +35,7 @@ function processTimelineEvent(item: HTMLElement): void { function processSimpleComment(item: HTMLElement): void { // Hide comments marked as resolved/hidden - if (select.exists('.minimized-comment > details', item)) { + if (elementExists('.minimized-comment > details', item)) { item.classList.add(collapsedClassName); } } @@ -44,19 +44,19 @@ function processDissmissedReviewEvent(item: HTMLElement): void { item.classList.add(hiddenClassName); // Find and hide stale reviews referenced by dismissed review events - for (const {hash: staleReviewId} of select.all<HTMLAnchorElement>('.TimelineItem-body > [href^="#pullrequestreview-"]', item)) { - select(staleReviewId)! + for (const {hash: staleReviewId} of $$('.TimelineItem-body > a[href^="#pullrequestreview-"]', item)) { + $(staleReviewId)! .closest('.js-timeline-item')! .classList.add(collapsedClassName); } } function processReview(review: HTMLElement): void { - const hasMainComment = select.exists('.js-comment[id^=pullrequestreview] .timeline-comment', review); + const hasMainComment = elementExists('.js-comment[id^=pullrequestreview] .timeline-comment', review); // Don't combine the selectors or use early returns without understanding what a thread or thread comment is - const unresolvedThreads = select.all('.js-resolvable-timeline-thread-container[data-resolved="false"]', review); - const unresolvedThreadComments = select.all('.timeline-comment-group:not(.minimized-comment)', review); + const unresolvedThreads = $$('.js-resolvable-timeline-thread-container[data-resolved="false"]', review); + const unresolvedThreadComments = $$('.timeline-comment-group:not(.minimized-comment)', review); if (!hasMainComment && (unresolvedThreads.length === 0 || unresolvedThreadComments.length === 0)) { review.classList.add(collapsedClassName); // The whole review is essentially resolved @@ -73,15 +73,15 @@ function processReview(review: HTMLElement): void { function processItem(item: HTMLElement): void { // Exclude deep-linked comment - if (location.hash.startsWith('#issuecomment-') && select.exists(location.hash, item)) { + if (location.hash.startsWith('#issuecomment-') && elementExists(location.hash, item)) { return; } - if (select.exists('.js-comment[id^=pullrequestreview]', item)) { + if (elementExists('.js-comment[id^=pullrequestreview]', item)) { processReview(item); - } else if (select.exists('.TimelineItem-badge .octicon-x', item)) { + } else if (elementExists('.TimelineItem-badge .octicon-x', item)) { processDissmissedReviewEvent(item); - } else if (select.exists('.comment-body', item)) { + } else if (elementExists('.comment-body', item)) { processSimpleComment(item); } else { processTimelineEvent(item); @@ -92,12 +92,12 @@ async function handleSelection({target}: Event): Promise<void> { // The event is fired before the DOM is updated. Extensions can't access the event’s `detail` where the widget would normally specify which element was selected await delay(1); - const state = select('[aria-checked="true"]', target as Element)!.dataset.value as State; + const state = $('[aria-checked="true"]', target as Element)!.dataset.value as State; applyState(state); } function applyState(state: State): void { - const container = select('.js-issues-results')!; + const container = $('.js-issues-results')!; container.classList.toggle( 'rgh-conversation-activity-is-filtered', state !== 'default', @@ -108,11 +108,11 @@ function applyState(state: State): void { ); // Update the state of the dropdowns - for (const dropdownItem of select.all(`.${dropdownClass} [aria-checked="false"][data-value="${state}"]`)) { + for (const dropdownItem of $$(`.${dropdownClass} [aria-checked="false"][data-value="${state}"]`)) { dropdownItem.setAttribute('aria-checked', 'true'); } - for (const dropdownItem of select.all(`.${dropdownClass} [aria-checked="true"]:not([data-value="${state}"])`)) { + for (const dropdownItem of $$(`.${dropdownClass} [aria-checked="true"]:not([data-value="${state}"])`)) { dropdownItem.setAttribute('aria-checked', 'false'); } } @@ -194,12 +194,12 @@ const minorFixesIssuePages = [ function uncollapseTargetedComment(): void { if (location.hash.startsWith('#issuecomment-')) { - select(`.${collapsedClassName} ${location.hash}`)?.closest('.js-timeline-item')?.classList.remove(collapsedClassName); + $(`.${collapsedClassName} ${location.hash}`)?.closest('.js-timeline-item')?.classList.remove(collapsedClassName); } } function switchToNextFilter(): void { - const state = select(`.${dropdownClass} [aria-checked="true"]`)!.dataset.value as State; + const state = $(`.${dropdownClass} [aria-checked="true"]`)!.dataset.value as State; // eslint-disable-next-line default-case switch (state) { case 'default': { diff --git a/source/features/conversation-links-on-repo-lists.tsx b/source/features/conversation-links-on-repo-lists.tsx index 2f1f688b..69dba775 100644 --- a/source/features/conversation-links-on-repo-lists.tsx +++ b/source/features/conversation-links-on-repo-lists.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {GitPullRequestIcon, IssueOpenedIcon} from '@primer/octicons-react'; @@ -11,11 +11,11 @@ function addConversationLinks(repositoryLink: HTMLAnchorElement): void { const repository = repositoryLink.closest('li')!; // Remove the "X issues need help" link - select('[href*="issues?q=label%3A%22help+wanted"]', repository)?.remove(); + $('[href*="issues?q=label%3A%22help+wanted"]', repository)?.remove(); // Place before the update date assertNodeContent( - select('relative-time', repository)!.previousSibling, + $('relative-time', repository)!.previousSibling, 'Updated', ).before( <a diff --git a/source/features/convert-pr-to-draft-improvements.tsx b/source/features/convert-pr-to-draft-improvements.tsx index def1efbc..cd377ed9 100644 --- a/source/features/convert-pr-to-draft-improvements.tsx +++ b/source/features/convert-pr-to-draft-improvements.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -13,14 +13,14 @@ function closeModal({delegateTarget: button}: DelegateEvent<MouseEvent, HTMLButt } function addConvertToDraftButton(alternativeActions: Element): void { - const existingButton = select('[data-url$="/convert_to_draft"]'); + const existingButton = $('[data-url$="/convert_to_draft"]'); // Needs to check the existence of both to guarantee the non-draft state - if (!existingButton || select.exists('[action$="/ready_for_review"]')) { + if (!existingButton || elementExists('[action$="/ready_for_review"]')) { return; } const convertToDraft = existingButton.closest('details')!.cloneNode(true); - select('.Link--muted', convertToDraft)!.classList.remove('Link--muted'); + $('.Link--muted', convertToDraft)!.classList.remove('Link--muted'); alternativeActions.prepend(convertToDraft); } diff --git a/source/features/convert-release-to-draft.tsx b/source/features/convert-release-to-draft.tsx index ed576ecb..47c5f3be 100644 --- a/source/features/convert-release-to-draft.tsx +++ b/source/features/convert-release-to-draft.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate from 'delegate-it'; @@ -21,7 +21,7 @@ async function convertToDraft(): Promise<void> { }, }); - select(getReleaseEditLinkSelector())!.click(); // Visit "Edit release" page + $(getReleaseEditLinkSelector())!.click(); // Visit "Edit release" page } const confirmMessage = 'The release will be effectively deleted and a new draft will be created.'; @@ -29,7 +29,7 @@ const confirmMessageWithReactions = 'Existing user reactions will be lost.'; const confirmMessageQuestion = 'Continue?'; async function onConvertClick(): Promise<void> { - const message = select.exists('.js-reaction-group-button') + const message = elementExists('.js-reaction-group-button') ? [confirmMessage, confirmMessageWithReactions, confirmMessageQuestion] : [confirmMessage, confirmMessageQuestion]; if (!confirm(message.join(' '))) { @@ -44,7 +44,7 @@ async function onConvertClick(): Promise<void> { } function attachButton(editButton: HTMLAnchorElement): void { - if (select.exists('[title="Draft"]')) { + if (elementExists('[title="Draft"]')) { return; } diff --git a/source/features/cross-deleted-pr-branches.tsx b/source/features/cross-deleted-pr-branches.tsx index da4bfd7b..49ba577b 100644 --- a/source/features/cross-deleted-pr-branches.tsx +++ b/source/features/cross-deleted-pr-branches.tsx @@ -1,15 +1,15 @@ import './cross-deleted-pr-branches.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, lastElement} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; import features from '../feature-manager.js'; function init(): void | false { - const lastBranchAction = select.last('.TimelineItem-body .user-select-contain.commit-ref'); + const lastBranchAction = lastElement('.TimelineItem-body .user-select-contain.commit-ref'); - const headReferenceLink = select('.head-ref a'); + const headReferenceLink = $('.head-ref a'); if (!headReferenceLink && !lastBranchAction) { return; // Don't return false, This feature’s CSS already takes care of this } @@ -20,7 +20,7 @@ function init(): void | false { const deletedBranchName = lastBranchAction.textContent.trim(); const repoRootUrl = headReferenceLink?.href.split('/', 5).join('/'); - for (const element of select.all('.commit-ref')) { + for (const element of $$('.commit-ref')) { const branchName = element.textContent.trim(); if (branchName === deletedBranchName) { element.title = 'This branch has been deleted'; @@ -30,7 +30,7 @@ function init(): void | false { } if (element.classList.contains('head-ref')) { - select('a', element)!.href = repoRootUrl!; + $('a', element)!.href = repoRootUrl!; } else { wrap(element, <a href={repoRootUrl}/>); } diff --git a/source/features/deep-reblame.tsx b/source/features/deep-reblame.tsx index 78fb90e7..3402794a 100644 --- a/source/features/deep-reblame.tsx +++ b/source/features/deep-reblame.tsx @@ -1,7 +1,7 @@ import './deep-reblame.css'; import mem from 'mem'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import {VersionsIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -45,13 +45,13 @@ async function redirectToBlameCommit(event: DelegateEvent<MouseEvent, HTMLAnchor blameElement.blur(); // Hide tooltip after click, it’s shown on :focus const blameHunk = blameElement.closest('.blame-hunk')!; - const prNumbers = select.all('.issue-link', blameHunk).map(pr => looseParseInt(pr)); - const prCommit = select('a.message', blameHunk)!.pathname.split('/').pop()!; + const prNumbers = $$('.issue-link', blameHunk).map(pr => looseParseInt(pr)); + const prCommit = $('a.message', blameHunk)!.pathname.split('/').pop()!; const blameUrl = new GitHubFileURL(location.href); await showToast(async () => { blameUrl.branch = await getPullRequestBlameCommit(prCommit, prNumbers, blameUrl.filePath); - blameUrl.hash = 'L' + select('.js-line-number', blameHunk)!.textContent; + blameUrl.hash = 'L' + $('.js-line-number', blameHunk)!.textContent; location.href = blameUrl.href; }, { message: 'Fetching pull request', @@ -62,12 +62,12 @@ async function redirectToBlameCommit(event: DelegateEvent<MouseEvent, HTMLAnchor function addButton(pullRequest: HTMLElement): void { const hunk = pullRequest.closest('.blame-hunk')!; - const reblameLink = select('.reblame-link', hunk); + const reblameLink = $('.reblame-link', hunk); if (reblameLink) { reblameLink.setAttribute('aria-label', 'View blame prior to this change. Hold `Alt` to extract commits from this PR first'); reblameLink.classList.add('rgh-deep-reblame'); } else { - select('.blob-reblame', hunk)!.append( + $('.blob-reblame', hunk)!.append( <button type="button" aria-label="View blame prior to this change (extracts commits from this PR first)" diff --git a/source/features/dim-bots.tsx b/source/features/dim-bots.tsx index 53093b57..36c8a3a7 100644 --- a/source/features/dim-bots.tsx +++ b/source/features/dim-bots.tsx @@ -1,5 +1,5 @@ import './dim-bots.css'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -43,7 +43,7 @@ function undimBots(event: DelegateEvent): void { } const resetScroll = preserveScroll(target); - for (const bot of select.all(dimBots.selector)) { + for (const bot of $$(dimBots.selector)) { bot.classList.add('rgh-interacted'); } @@ -51,14 +51,14 @@ function undimBots(event: DelegateEvent): void { } function init(signal: AbortSignal): void { - for (const bot of select.all(commitSelectors)) { + for (const bot of $$(commitSelectors)) { // Exclude co-authored commits - if (select.all('a', bot.parentElement!).every(link => link.matches(commitSelectors))) { + if ($$('a', bot.parentElement!).every(link => link.matches(commitSelectors))) { bot.closest('.commit, .Box-row')!.classList.add(dimBots.class); } } - for (const bot of select.all(prSelectors)) { + for (const bot of $$(prSelectors)) { bot.closest('.commit, .Box-row')!.classList.add(dimBots.class); } diff --git a/source/features/easy-toggle-commit-messages.tsx b/source/features/easy-toggle-commit-messages.tsx index 3c40d38f..d38078b1 100644 --- a/source/features/easy-toggle-commit-messages.tsx +++ b/source/features/easy-toggle-commit-messages.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -16,7 +16,7 @@ function toggleCommitMessage(event: DelegateEvent<MouseEvent>): void { return; } - select('.ellipsis-expander', event.delegateTarget)?.dispatchEvent( + $('.ellipsis-expander', event.delegateTarget)?.dispatchEvent( new MouseEvent('click', {bubbles: true, altKey: event.altKey}), ); } diff --git a/source/features/easy-toggle-files.tsx b/source/features/easy-toggle-files.tsx index a6a30c48..db6d0381 100644 --- a/source/features/easy-toggle-files.tsx +++ b/source/features/easy-toggle-files.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import delegate, {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -10,7 +10,7 @@ function toggleFile(event: DelegateEvent<MouseEvent>): void { // The clicked element is either the bar itself or one of its 2 children if (elementClicked === headerBar || elementClicked.parentElement === headerBar) { - select('[aria-label="Toggle diff contents"]', headerBar)! + $('[aria-label="Toggle diff contents"]', headerBar)! .dispatchEvent(new MouseEvent('click', {bubbles: true, altKey: event.altKey})); } } diff --git a/source/features/embed-gist-via-iframe.tsx b/source/features/embed-gist-via-iframe.tsx index ffd037dc..570fdcdb 100644 --- a/source/features/embed-gist-via-iframe.tsx +++ b/source/features/embed-gist-via-iframe.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import onetime from 'onetime'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -16,12 +16,12 @@ async function init(): Promise<void> { // Set required content embedViaIframe.setAttribute('aria-checked', 'false'); embedViaIframe.value = `<iframe src="${location.origin}${location.pathname}.pibb"></iframe>`; - select('.select-menu-item-heading', embedViaIframe)!.textContent = 'Embed via <iframe>'; - select('.description', embedViaIframe)!.textContent = 'Embed this gist in your website via <iframe>.'; + $('.select-menu-item-heading', embedViaIframe)!.textContent = 'Embed via <iframe>'; + $('.description', embedViaIframe)!.textContent = 'Embed this gist in your website via <iframe>.'; // Modify description of the original embed type to distinguish the two items - select('.select-menu-item-heading', embedViaScript)!.textContent = 'Embed via <script>'; - select('.description', embedViaScript)!.textContent = 'Embed this gist in your website via <script>.'; + $('.select-menu-item-heading', embedViaScript)!.textContent = 'Embed via <script>'; + $('.description', embedViaScript)!.textContent = 'Embed this gist in your website via <script>.'; embedViaScript!.after(embedViaIframe); } diff --git a/source/features/esc-to-cancel.tsx b/source/features/esc-to-cancel.tsx index f22cfb31..307df683 100644 --- a/source/features/esc-to-cancel.tsx +++ b/source/features/esc-to-cancel.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -7,7 +7,7 @@ import {onConversationTitleFieldKeydown} from '../github-events/on-field-keydown function handleEscPress(event: DelegateEvent<KeyboardEvent>): void { if (event.key === 'Escape') { - select('.js-cancel-issue-edit')!.click(); + $('.js-cancel-issue-edit')!.click(); event.stopImmediatePropagation(); event.preventDefault(); diff --git a/source/features/expand-all-hidden-comments.tsx b/source/features/expand-all-hidden-comments.tsx index 8f65fac4..017c3e53 100644 --- a/source/features/expand-all-hidden-comments.tsx +++ b/source/features/expand-all-hidden-comments.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import oneEvent from 'one-event'; import delegate, {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -21,7 +21,7 @@ async function expandHidden(paginationButton: HTMLButtonElement | undefined) { wrapper = wrapper.lastElementChild!; } - paginationButton = select(`:scope > ${paginationButtonSelector}`, wrapper); + paginationButton = $(`:scope > ${paginationButtonSelector}`, wrapper); paginationButton?.click(); } } diff --git a/source/features/extend-conversation-status-filters.tsx b/source/features/extend-conversation-status-filters.tsx index af04f4d3..8ccce012 100644 --- a/source/features/extend-conversation-status-filters.tsx +++ b/source/features/extend-conversation-status-filters.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import {CheckIcon} from '@primer/octicons-react'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -15,7 +15,7 @@ function addMergeLink(): void { // The links in `.table-list-header-toggle` are either: // 1 Open | 1 Closed // 1 Total // Apparently appears with is:merged/is:unmerged - for (const lastLink of select.all('.table-list-header-toggle.states a:last-child')) { + for (const lastLink of $$('.table-list-header-toggle.states a:last-child')) { const lastLinkQuery = SearchQuery.from(lastLink); if (lastLinkQuery.includes('is:merged')) { @@ -40,8 +40,8 @@ function addMergeLink(): void { } function togglableFilters(): void { - for (const link of select.all('.table-list-header-toggle.states a')) { - select('.octicon', link)?.remove(); + for (const link of $$('.table-list-header-toggle.states a')) { + $('.octicon', link)?.remove(); if (link.classList.contains('selected')) { link.prepend(<CheckIcon/>); link.href = SearchQuery diff --git a/source/features/extend-diff-expander.tsx b/source/features/extend-diff-expander.tsx index 2c6de210..510040d3 100644 --- a/source/features/extend-diff-expander.tsx +++ b/source/features/extend-diff-expander.tsx @@ -1,5 +1,5 @@ import './extend-diff-expander.css'; -import select from 'select-dom'; +import {$} from 'select-dom'; import delegate, {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -8,7 +8,7 @@ import features from '../feature-manager.js'; function expandDiff(event: DelegateEvent): void { // Skip if the user clicked directly on the icon if (!(event.target as Element).closest('.js-expand')) { - select('.js-expand', event.delegateTarget)!.click(); + $('.js-expand', event.delegateTarget)!.click(); } } diff --git a/source/features/github-actions-indicators.tsx b/source/features/github-actions-indicators.tsx index 8d6e2e57..e3ae4034 100644 --- a/source/features/github-actions-indicators.tsx +++ b/source/features/github-actions-indicators.tsx @@ -1,6 +1,6 @@ import {CachedFunction} from 'webext-storage-cache'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import {StopIcon, PlayIcon} from '@primer/octicons-react'; import {parseCron} from '@cheap-glitch/mi-cron'; import * as pageDetect from 'github-url-detection'; @@ -90,7 +90,7 @@ const workflowDetails = new CachedFunction('workflows-details', { async function addIndicators(workflowListItem: HTMLAnchorElement): Promise<void> { // There might be a disabled indicator already - if (select.exists('.octicon-stop', workflowListItem)) { + if (elementExists('.octicon-stop', workflowListItem)) { return; } @@ -125,7 +125,7 @@ async function addIndicators(workflowListItem: HTMLAnchorElement): Promise<void> } const relativeTime = <relative-time datetime={String(nextTime)}/>; - select('.ActionList-item-label', workflowListItem)!.append( + $('.ActionList-item-label', workflowListItem)!.append( <em> ({relativeTime}) </em>, diff --git a/source/features/global-conversation-list-filters.tsx b/source/features/global-conversation-list-filters.tsx index b324bfb6..d9734294 100644 --- a/source/features/global-conversation-list-filters.tsx +++ b/source/features/global-conversation-list-filters.tsx @@ -1,6 +1,6 @@ import './global-conversation-list-filters.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -29,11 +29,11 @@ function addLinks(container: HTMLElement): void { const isCurrentPage = SearchQuery.from(location).includes(query); // Highlight it, if that's the current page - if (isCurrentPage && !select.exists('.subnav-links .selected')) { + if (isCurrentPage && !elementExists('.subnav-links .selected')) { link.classList.add('selected'); // Other links will keep the current query, that's not what we want - for (const otherLink of select.all('.subnav-links a')) { + for (const otherLink of $$('.subnav-links a')) { otherLink.href = SearchQuery.from(otherLink).remove(query).href; } } diff --git a/source/features/hidden-review-comments-indicator.tsx b/source/features/hidden-review-comments-indicator.tsx index 7533cc44..97f9652e 100644 --- a/source/features/hidden-review-comments-indicator.tsx +++ b/source/features/hidden-review-comments-indicator.tsx @@ -1,7 +1,7 @@ import './hidden-review-comments-indicator.css'; import mem from 'mem'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import {CommentIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -45,7 +45,7 @@ const indicatorToggleObserver = new MutationObserver(mutations => { const wasVisible = mutation.oldValue!.includes('show-inline-notes'); const isHidden = !file.classList.contains('show-inline-notes'); if (wasVisible && isHidden) { - for (const thread of select.all('tr.inline-comments', file)) { + for (const thread of $$('tr.inline-comments', file)) { addIndicator(thread); } } diff --git a/source/features/hide-inactive-deployments.tsx b/source/features/hide-inactive-deployments.tsx index 4f762c29..44d1f76f 100644 --- a/source/features/hide-inactive-deployments.tsx +++ b/source/features/hide-inactive-deployments.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -6,12 +6,12 @@ import features from '../feature-manager.js'; // This feature doesn't need an active observer function init(): void { // Selects all the deployments first so that we can leave the last one on the page - const deployments = select.all('.js-socket-channel[data-url*="/partials/deployed_event/"]'); + const deployments = $$('.js-socket-channel[data-url*="/partials/deployed_event/"]'); deployments.pop(); // Don't hide the last deployment, even if it is inactive for (const deployment of deployments) { // TODO: Rewrite with :has selector, CSS-only feature - if (select.exists('[title="Deployment Status Label: Inactive"]', deployment)) { + if (elementExists('[title="Deployment Status Label: Inactive"]', deployment)) { deployment.remove(); } } diff --git a/source/features/hide-issue-list-autocomplete.tsx b/source/features/hide-issue-list-autocomplete.tsx index acad714b..c30a9c0a 100644 --- a/source/features/hide-issue-list-autocomplete.tsx +++ b/source/features/hide-issue-list-autocomplete.tsx @@ -1,10 +1,10 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; function init(): void { - select('.subnav-search')!.setAttribute('autocomplete', 'off'); + $('.subnav-search')!.setAttribute('autocomplete', 'off'); } void features.add(import.meta.url, { diff --git a/source/features/hide-low-quality-comments.tsx b/source/features/hide-low-quality-comments.tsx index 43d1ad0b..5271c3ee 100644 --- a/source/features/hide-low-quality-comments.tsx +++ b/source/features/hide-low-quality-comments.tsx @@ -1,7 +1,7 @@ import './hide-low-quality-comments.css'; import delay from 'delay'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -11,18 +11,18 @@ import isLowQualityComment from '../helpers/is-low-quality-comment.js'; export const singleParagraphCommentSelector = '.comment-body > p:only-child'; async function unhide(event: DelegateEvent): Promise<void> { - for (const comment of select.all('.rgh-hidden-comment')) { + for (const comment of $$('.rgh-hidden-comment')) { comment.hidden = false; } await delay(10); // "Similar comments" aren't expanded without this in Safari #3830 // Expand all "similar comments" boxes - for (const similarCommentsExpandButton of select.all('.rgh-hidden-comment > summary')) { + for (const similarCommentsExpandButton of $$('.rgh-hidden-comment > summary')) { similarCommentsExpandButton.click(); } - select('.rgh-hidden-comment')!.scrollIntoView(); + $('.rgh-hidden-comment')!.scrollIntoView(); event.delegateTarget.parentElement!.remove(); } @@ -32,13 +32,13 @@ function hideComment(comment: HTMLElement): void { } function init(): void { - for (const similarCommentsBox of select.all('.js-discussion .Details-element:not([data-body-version])')) { + for (const similarCommentsBox of $$('.js-discussion .Details-element:not([data-body-version])')) { hideComment(similarCommentsBox); } - const linkedComment = location.hash.startsWith('#issuecomment-') ? select(`${location.hash} ${singleParagraphCommentSelector}`) : undefined; + const linkedComment = location.hash.startsWith('#issuecomment-') ? $(`${location.hash} ${singleParagraphCommentSelector}`) : undefined; - for (const commentText of select.all(singleParagraphCommentSelector)) { + for (const commentText of $$(singleParagraphCommentSelector)) { // Exclude explicitely linked comments #5363 if (commentText === linkedComment) { continue; @@ -51,22 +51,22 @@ function init(): void { // Comments that contain useful images or links shouldn't be removed // Images are wrapped in <a> tags on GitHub hence included in the selector // TODO: use :has() - if (select.exists('a', commentText)) { + if (elementExists('a', commentText)) { continue; } // Ensure that they're not by VIPs (owner, collaborators, etc) // TODO: use :has() const comment = commentText.closest('.js-timeline-item')!; - if (select.exists('.Label', comment)) { + if (elementExists('.Label', comment)) { continue; } // If the person is having a conversation, then don't hide it - const author = select('.author', comment)!.getAttribute('href')!; + const author = $('.author', comment)!.getAttribute('href')!; // If the first comment left by the author isn't a low quality comment // (previously hidden or about to be hidden), then leave this one as well - const previousComment = select(`.js-timeline-item:not([hidden]) .unminimized-comment .author[href="${author}"]`); + const previousComment = $(`.js-timeline-item:not([hidden]) .unminimized-comment .author[href="${author}"]`); if (previousComment?.closest('.js-timeline-item') !== comment) { continue; } @@ -74,9 +74,9 @@ function init(): void { hideComment(comment); } - const lowQualityCount = select.all('.rgh-hidden-comment').length; + const lowQualityCount = $$('.rgh-hidden-comment').length; if (lowQualityCount > 0) { - select('.discussion-timeline-actions')!.prepend( + $('.discussion-timeline-actions')!.prepend( <p className="rgh-low-quality-comments-note"> {`${lowQualityCount} unhelpful comment${lowQualityCount > 1 ? 's were' : ' was'} automatically hidden. `} <button className="btn-link text-emphasized rgh-unhide-low-quality-comments" type="button">Show</button> diff --git a/source/features/highest-rated-comment.tsx b/source/features/highest-rated-comment.tsx index af91975f..3dd29971 100644 --- a/source/features/highest-rated-comment.tsx +++ b/source/features/highest-rated-comment.tsx @@ -1,7 +1,7 @@ import './highest-rated-comment.css'; import mem from 'mem'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {ArrowDownIcon, CheckCircleFillIcon} from '@primer/octicons-react'; @@ -38,7 +38,7 @@ const getPositiveReactions = mem((comment: HTMLElement): number | void => { function getBestComment(): HTMLElement | undefined { let highest; - for (const reaction of select.all(positiveReactionsSelector)) { + for (const reaction of $$(positiveReactionsSelector)) { const comment = reaction.closest(commentSelector)!; const positiveReactions = getPositiveReactions(comment); if (positiveReactions && (!highest || positiveReactions > highest.count)) { @@ -50,8 +50,8 @@ function getBestComment(): HTMLElement | undefined { } function highlightBestComment(bestComment: Element): void { - select('.unminimized-comment', bestComment)!.classList.add('rgh-highest-rated-comment'); - select('.unminimized-comment .timeline-comment-header > h3', bestComment)!.before( + $('.unminimized-comment', bestComment)!.classList.add('rgh-highest-rated-comment'); + $('.unminimized-comment .timeline-comment-header > h3', bestComment)!.before( <span className="color-fg-success tooltipped tooltipped-s" aria-label="This comment has the most positive reactions on this issue." @@ -63,16 +63,16 @@ function highlightBestComment(bestComment: Element): void { function linkBestComment(bestComment: HTMLElement): void { // Find position of comment in thread - const position = select.all(commentSelector).indexOf(bestComment); + const position = $$(commentSelector).indexOf(bestComment); // Only link to it if it doesn't already appear at the top of the conversation if (position < 3) { return; } - const text = select('.comment-body', bestComment)!.textContent.slice(0, 100); - const {hash} = select('a.js-timestamp', bestComment)!; - const avatar = select('img.avatar', bestComment)!.cloneNode(); + const text = $('.comment-body', bestComment)!.textContent.slice(0, 100); + const {hash} = $('a.js-timestamp', bestComment)!; + const avatar = $('img.avatar', bestComment)!.cloneNode(); bestComment.parentElement!.firstElementChild!.after( <a href={hash} className="no-underline rounded-1 rgh-highest-rated-comment timeline-comment color-bg-subtle px-2 d-flex flex-items-center"> @@ -90,7 +90,7 @@ function linkBestComment(bestComment: HTMLElement): void { } function selectSum(selector: string, container: HTMLElement): number { - return select.all(selector, container).reduce((sum, element) => sum + looseParseInt(element), 0); + return $$(selector, container).reduce((sum, element) => sum + looseParseInt(element), 0); } function init(): false | void { @@ -99,7 +99,7 @@ function init(): false | void { return false; } - const commentText = select(singleParagraphCommentSelector, bestComment)?.textContent; + const commentText = $(singleParagraphCommentSelector, bestComment)?.textContent; if (commentText && isLowQualityComment(commentText)) { // #5567 return false; } diff --git a/source/features/highlight-collaborators-and-own-conversations.tsx b/source/features/highlight-collaborators-and-own-conversations.tsx index eba3b7ae..38790c3a 100644 --- a/source/features/highlight-collaborators-and-own-conversations.tsx +++ b/source/features/highlight-collaborators-and-own-conversations.tsx @@ -1,6 +1,6 @@ import './highlight-collaborators-and-own-conversations.css'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import domLoaded from 'dom-loaded'; import * as pageDetect from 'github-url-detection'; @@ -11,8 +11,7 @@ import {buildRepoURL, cacheByRepo, getUsername} from '../github-helpers/index.js 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) + return $$('.SelectMenu-item img[alt]', dom) .map(avatar => avatar.alt.slice(1)); }, maxAge: {days: 1}, @@ -23,7 +22,7 @@ const collaborators = new CachedFunction('repo-collaborators', { async function highlightCollaborators(): Promise<void> { const list = await collaborators.get(); await domLoaded; - for (const author of select.all('.js-issue-row [data-hovercard-type="user"]')) { + for (const author of $$('.js-issue-row [data-hovercard-type="user"]')) { if (list.includes(author.textContent.trim())) { author.classList.add('rgh-collaborator'); } @@ -32,7 +31,7 @@ async function highlightCollaborators(): Promise<void> { function highlightSelf(): void { // "Opened by {user}" and "Created by {user}" - for (const author of select.all(`.opened-by a[title$="ed by ${CSS.escape(getUsername()!)}"]`)) { + for (const author of $$(`.opened-by a[title$="ed by ${CSS.escape(getUsername()!)}"]`)) { author.classList.add('rgh-collaborator'); author.style.fontStyle = 'italic'; } diff --git a/source/features/highlight-non-default-base-branch.tsx b/source/features/highlight-non-default-base-branch.tsx index 6fc4b798..f119abb3 100644 --- a/source/features/highlight-non-default-base-branch.tsx +++ b/source/features/highlight-non-default-base-branch.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {GitPullRequestIcon} from '@primer/octicons-react'; @@ -31,7 +31,7 @@ function buildQuery(issueIds: string[]): string { } async function init(): Promise<false | void> { - const prLinks = select.all('.js-issue-row .js-navigation-open[data-hovercard-type="pull_request"]'); + const prLinks = $$('.js-issue-row .js-navigation-open[data-hovercard-type="pull_request"]'); if (prLinks.length === 0) { return false; } diff --git a/source/features/improve-shortcut-help.tsx b/source/features/improve-shortcut-help.tsx index 4a9ee78c..14ccdee6 100644 --- a/source/features/improve-shortcut-help.tsx +++ b/source/features/improve-shortcut-help.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import onetime from 'onetime'; import features from '../feature-manager.js'; @@ -10,7 +10,7 @@ function splitKeys(keys: string): DocumentFragment[] { } function improveShortcutHelp(dialog: Element): void { - select('.Box-body .col-5 .Box:first-child', dialog)!.after( + $('.Box-body .col-5 .Box:first-child', dialog)!.after( <div className="Box Box--condensed m-4"> <div className="Box-header"> <h2 className="Box-title">Refined GitHub</h2> @@ -33,7 +33,7 @@ function improveShortcutHelp(dialog: Element): void { } const observer = new MutationObserver(([{target}]) => { - if (target instanceof Element && !select.exists('.js-details-dialog-spinner', target)) { + if (target instanceof Element && !elementExists('.js-details-dialog-spinner', target)) { improveShortcutHelp(target); observer.disconnect(); } @@ -44,7 +44,7 @@ function observeShortcutModal({key, target}: KeyboardEvent): void { return; } - const modal = select('body > details:not(.js-command-palette-dialog) > details-dialog'); + const modal = $('body > details:not(.js-command-palette-dialog) > details-dialog'); if (modal) { observer.observe(modal, {childList: true}); } diff --git a/source/features/infinite-scroll.tsx b/source/features/infinite-scroll.tsx index f5df3ea9..be4f4022 100644 --- a/source/features/infinite-scroll.tsx +++ b/source/features/infinite-scroll.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import debounce from 'debounce-fn'; import * as pageDetect from 'github-url-detection'; @@ -34,7 +34,7 @@ function copyFooter(originalFooter: HTMLElement): void { child.classList.remove('pl-lg-4', 'col-xl-3'); } - select('[aria-label^="Explore"]')!.append( + $('[aria-label^="Explore"]')!.append( <div className="footer mt-4 py-4 border-top"> {footer} </div>, diff --git a/source/features/jump-to-change-requested-comment.tsx b/source/features/jump-to-change-requested-comment.tsx index 3a6b72c4..e2809d94 100644 --- a/source/features/jump-to-change-requested-comment.tsx +++ b/source/features/jump-to-change-requested-comment.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; @@ -7,9 +7,9 @@ import features from '../feature-manager.js'; import observe from '../helpers/selector-observer.js'; function linkify(textLine: HTMLElement): void { - const url = select('a.dropdown-item[href^="#pullrequestreview-"]', textLine.parentElement!); + const url = $('a.dropdown-item[href^="#pullrequestreview-"]', textLine.parentElement!)!; // `lastChild` is a textNode - wrap(textLine.lastChild!, <a href={url!.hash}/>); + wrap(textLine.lastChild!, <a href={url.hash}/>); } function init(signal: AbortSignal): void { diff --git a/source/features/jump-to-conversation-close-event.tsx b/source/features/jump-to-conversation-close-event.tsx index 6ca8b97b..609cd649 100644 --- a/source/features/jump-to-conversation-close-event.tsx +++ b/source/features/jump-to-conversation-close-event.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {css} from 'code-tag'; -import select from 'select-dom'; +import {lastElement} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; @@ -15,7 +15,7 @@ export const closedOrMergedMarkerSelector = css` `; export function getLastCloseEvent(): HTMLElement | undefined { - return select.last(` + return lastElement(` .TimelineItem-badge :is( .octicon-issue-closed, .octicon-git-merge, diff --git a/source/features/keyboard-navigation.tsx b/source/features/keyboard-navigation.tsx index ed781bd3..63a70c15 100644 --- a/source/features/keyboard-navigation.tsx +++ b/source/features/keyboard-navigation.tsx @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; import {isEditable} from '../helpers/dom-utils.js'; const isCommentGroupMinimized = (comment: HTMLElement): boolean => - select.exists('.minimized-comment:not(.d-none)', comment) + elementExists('.minimized-comment:not(.d-none)', comment) || Boolean(comment.closest([ '.js-resolvable-thread-contents.d-none', // Regular comments 'details.js-resolvable-timeline-thread-container:not([open])', // Review comments @@ -18,17 +18,17 @@ function runShortcuts(event: KeyboardEvent): void { event.preventDefault(); - const focusedComment = select(':target')!; - const items = select - .all([ + const focusedComment = $(':target')!; + const items + = $$([ '.js-targetable-element[id^="diff-"]', // Files in diffs '.js-minimizable-comment-group', // Comments (to be `.filter()`ed) ]) - .filter(element => - element.classList.contains('js-minimizable-comment-group') - ? !isCommentGroupMinimized(element) - : true, - ); + .filter(element => + element.classList.contains('js-minimizable-comment-group') + ? !isCommentGroupMinimized(element) + : true, + ); // `j` goes to the next comment, `k` goes back a comment const direction = event.key === 'j' ? 1 : -1; diff --git a/source/features/last-notification-page-button.tsx b/source/features/last-notification-page-button.tsx index 5adc326d..1daaf319 100644 --- a/source/features/last-notification-page-button.tsx +++ b/source/features/last-notification-page-button.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -10,7 +10,7 @@ import observe from '../helpers/selector-observer.js'; const itemsPerNotificationsPage = 25; function linkify(nextButton: HTMLAnchorElement): void { - const totalNotificationsNode = select('.js-notifications-list-paginator-counts')!.lastChild!; + const totalNotificationsNode = $('.js-notifications-list-paginator-counts')!.lastChild!; assertNodeContent(totalNotificationsNode, /^of \d+$/); const totalNotificationsNumber = looseParseInt(totalNotificationsNode); const lastCursor = Math.floor((totalNotificationsNumber - 1) / itemsPerNotificationsPage) * itemsPerNotificationsPage; diff --git a/source/features/link-to-changelog-file.tsx b/source/features/link-to-changelog-file.tsx index b357ef0f..08661d12 100644 --- a/source/features/link-to-changelog-file.tsx +++ b/source/features/link-to-changelog-file.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import {BookIcon} from '@primer/octicons-react'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -22,7 +22,7 @@ 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); + const files = $$('[aria-labelledby="files"] .js-navigation-open[href*="/blob/"').map(file => file.title); void changelogName.applyOverride( [findChangelogName(files) as string] /* TODO: Type mistake */, getRepo()!.nameWithOwner, diff --git a/source/features/link-to-compare-diff.tsx b/source/features/link-to-compare-diff.tsx index e1fecce8..da5df859 100644 --- a/source/features/link-to-compare-diff.tsx +++ b/source/features/link-to-compare-diff.tsx @@ -1,6 +1,6 @@ import './link-to-compare-diff.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -20,7 +20,7 @@ void features.add(import.meta.url, { pageDetect.isCompare, ], exclude: [ - () => select.exists('.tabnav'), // The commit list and compare diff are in two separate tabs + () => elementExists('.tabnav'), // The commit list and compare diff are in two separate tabs ], deduplicate: 'has-rgh-inner', awaitDomReady: true, // DOM-based filter diff --git a/source/features/linkify-code.tsx b/source/features/linkify-code.tsx index e7d444b5..d272e906 100644 --- a/source/features/linkify-code.tsx +++ b/source/features/linkify-code.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import observe from '../helpers/selector-observer.js'; @@ -13,7 +13,7 @@ function initTitle(signal: AbortSignal): void { observe('.js-issue-title', title => { // TODO: Replace with :has - if (!select.exists('a', title)) { + if (!elementExists('a', title)) { linkifyIssues(currentRepo, title); } }, {signal}); @@ -30,7 +30,7 @@ function linkifyContent(wrapper: Element): void { // https://github.com/refined-github/refined-github/pull/3844#issuecomment-751427568 if (!pageDetect.isGist()) { const currentRepo = getRepo() ?? {}; - for (const element of select.all('.pl-c', wrapper)) { + for (const element of $$('.pl-c', wrapper)) { linkifyIssues(currentRepo, element); } } diff --git a/source/features/linkify-commit-sha.tsx b/source/features/linkify-commit-sha.tsx index d33ffdca..bd43f7f0 100644 --- a/source/features/linkify-commit-sha.tsx +++ b/source/features/linkify-commit-sha.tsx @@ -1,12 +1,12 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; import features from '../feature-manager.js'; function init(): void { - const element = select('.sha.user-select-contain:not(a *)'); + const element = $('.sha.user-select-contain:not(a *)'); if (element) { wrap(element, <a href={location.pathname.replace(/pull\/\d+\/commits/, 'commit')}/>); } diff --git a/source/features/linkify-labels-on-dashboard.tsx b/source/features/linkify-labels-on-dashboard.tsx index 1916abf8..78ca2eb9 100644 --- a/source/features/linkify-labels-on-dashboard.tsx +++ b/source/features/linkify-labels-on-dashboard.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; @@ -8,8 +8,8 @@ import observe from '../helpers/selector-observer.js'; function linkifyLabel(label: Element): void { const activity = label.closest('div:not([class])')!; - const isPR = select.exists('.octicon-git-pull-request', activity); - const repository = select('a[data-hovercard-type="repository"]', activity)!; + const isPR = elementExists('.octicon-git-pull-request', activity); + const repository = $('a[data-hovercard-type="repository"]', activity)!; const url = new URL(`${repository.href}/${isPR ? 'pulls' : 'issues'}`); const labelName = label.textContent.trim(); diff --git a/source/features/linkify-symbolic-links.tsx b/source/features/linkify-symbolic-links.tsx index bc4f4884..01b2c627 100644 --- a/source/features/linkify-symbolic-links.tsx +++ b/source/features/linkify-symbolic-links.tsx @@ -1,13 +1,13 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {wrap} from '../helpers/dom-utils.js'; import features from '../feature-manager.js'; function init(): void { - if (select('.file-mode')?.textContent === 'symbolic link') { - const line = select('.js-file-line')!; + if ($('.file-mode')?.textContent === 'symbolic link') { + const line = $('.js-file-line')!; wrap(line.firstChild!, <a href={line.textContent} data-turbo-frame="repo-content-turbo-frame"/>); } } diff --git a/source/features/mark-merge-commits-in-list.tsx b/source/features/mark-merge-commits-in-list.tsx index 6e9bb93d..4767038d 100644 --- a/source/features/mark-merge-commits-in-list.tsx +++ b/source/features/mark-merge-commits-in-list.tsx @@ -1,6 +1,6 @@ import './mark-merge-commits-in-list.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import {GitMergeIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import {objectEntries} from 'ts-extras'; @@ -35,11 +35,11 @@ const filterMergeCommits = async (commits: string[]): Promise<string[]> => { }; export function getCommitHash(commit: HTMLElement): string { - return select('a.markdown-title', commit)!.pathname.split('/').pop()!; + return $('a.markdown-title', commit)!.pathname.split('/').pop()!; } async function init(): Promise<void> { - const pageCommits = select.all([ + const pageCommits = $$([ '.js-commits-list-item', // `isCommitList` '.js-timeline-item .TimelineItem:has(.octicon-git-commit)', // `isPRConversation`, "js-timeline-item" to exclude "isCommitList" ]); @@ -52,7 +52,7 @@ async function init(): Promise<void> { for (const commit of pageCommits) { if (mergeCommits.includes(getCommitHash(commit))) { commit.classList.add('rgh-merge-commit'); - select('a.markdown-title', commit)!.before(<GitMergeIcon className="mr-1"/>); + $('a.markdown-title', commit)!.before(<GitMergeIcon className="mr-1"/>); } } } diff --git a/source/features/mark-private-orgs.tsx b/source/features/mark-private-orgs.tsx index 60c0ccea..855c24fa 100644 --- a/source/features/mark-private-orgs.tsx +++ b/source/features/mark-private-orgs.tsx @@ -1,7 +1,7 @@ import './mark-private-orgs.css'; import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import {EyeClosedIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -21,7 +21,7 @@ const publicOrganizationsNames = new CachedFunction('public-organizations', { }); async function init(): Promise<false | void> { - const orgs = select.all('a.avatar-group-item[data-hovercard-type="organization"][itemprop="follows"]'); // `itemprop` excludes sponsorships #3770 + const orgs = $$('a.avatar-group-item[data-hovercard-type="organization"][itemprop="follows"]'); // `itemprop` excludes sponsorships #3770 if (orgs.length === 0) { return false; } diff --git a/source/features/more-conversation-filters.tsx b/source/features/more-conversation-filters.tsx index 37387a04..cb80ccfc 100644 --- a/source/features/more-conversation-filters.tsx +++ b/source/features/more-conversation-filters.tsx @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; import SearchQuery from '../github-helpers/search-query.js'; function init(): void { - const sourceItem = select('#filters-select-menu a:nth-last-child(2)')!; + const sourceItem = $('#filters-select-menu a:nth-last-child(2)')!; // "Involved" filter const commentsLink = sourceItem.cloneNode(true); @@ -17,14 +17,14 @@ function init(): void { sourceItem.after(commentsLink); // "Subscribed" external link - const searchSyntaxLink = select('#filters-select-menu a:last-child')!; + const searchSyntaxLink = $('#filters-select-menu a:last-child')!; const subscriptionsLink = searchSyntaxLink.cloneNode(true); subscriptionsLink.lastElementChild!.textContent = 'Everything you subscribed to'; const subscriptionsUrl = new URL('https://github.com/notifications/subscriptions'); const repositoryId - = select('meta[name="octolytics-dimension-repository_id"]')?.content - ?? select('input[name="repository_id"]')!.value; + = $('meta[name="octolytics-dimension-repository_id"]')?.content + ?? $('input[name="repository_id"]')!.value; subscriptionsUrl.searchParams.set('repository', btoa(`010:Repository${repositoryId}`)); subscriptionsLink.href = subscriptionsUrl.href; diff --git a/source/features/more-dropdown-links.tsx b/source/features/more-dropdown-links.tsx index cd2451fd..2c10626d 100644 --- a/source/features/more-dropdown-links.tsx +++ b/source/features/more-dropdown-links.tsx @@ -1,6 +1,6 @@ import './more-dropdown-links.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -15,7 +15,7 @@ export async function unhideOverflowDropdown(): Promise<boolean> { const repoNavigationBar = await elementReady('.UnderlineNav-body'); // No dropdown on mobile #5781 - if (!select.exists('.js-responsive-underlinenav')) { + if (!elementExists('.js-responsive-underlinenav')) { return false; } @@ -50,7 +50,7 @@ void features.add(import.meta.url, { pageDetect.isEmptyRepo, // No dropdown on mobile #5781 - () => !select.exists('.js-responsive-underlinenav'), + () => !elementExists('.js-responsive-underlinenav'), ], deduplicate: 'has-rgh', awaitDomReady: true, // DOM-based filter diff --git a/source/features/more-file-links.tsx b/source/features/more-file-links.tsx index 8d0b85f2..d6866836 100644 --- a/source/features/more-file-links.tsx +++ b/source/features/more-file-links.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -9,7 +9,7 @@ import GitHubFileURL from '../github-helpers/github-file-url.js'; function handleMenuOpening({delegateTarget: dropdown}: DelegateEvent): void { dropdown.classList.add('rgh-more-file-links'); // Mark this as processed - const viewFile = select('a[data-ga-click^="View file"]', dropdown)!; + const viewFile = $('a[data-ga-click^="View file"]', dropdown)!; const getDropdownLink = (name: string, route: string): JSX.Element => { const {href} = new GitHubFileURL(viewFile.href).assign({route}); return ( diff --git a/source/features/netiquette.tsx b/source/features/netiquette.tsx index 0117f76a..57c4d52c 100644 --- a/source/features/netiquette.tsx +++ b/source/features/netiquette.tsx @@ -1,7 +1,7 @@ import React from 'dom-chef'; import * as pageDetect from 'github-url-detection'; import toMilliseconds from '@sindresorhus/to-milliseconds'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import twas from 'twas'; import {InfoIcon} from '@primer/octicons-react'; @@ -11,7 +11,7 @@ import observe from '../helpers/selector-observer.js'; import {buildRepoURL, isAnyRefinedGitHubRepo} from '../github-helpers/index.js'; import {closedOrMergedMarkerSelector, getLastCloseEvent} from './jump-to-conversation-close-event.js'; -const isClosedOrMerged = (): boolean => select.exists(closedOrMergedMarkerSelector); +const isClosedOrMerged = (): boolean => elementExists(closedOrMergedMarkerSelector); /** Returns milliseconds passed since `date` */ function timeAgo(date: Date): number { @@ -19,7 +19,7 @@ function timeAgo(date: Date): number { } function getCloseDate(): Date { - const datetime = select('relative-time', getLastCloseEvent())!.getAttribute('datetime')!; + const datetime = $('relative-time', getLastCloseEvent())!.getAttribute('datetime')!; console.assert(datetime, 'Datetime attribute missing from relative-time'); return new Date(datetime); } diff --git a/source/features/new-or-deleted-file.tsx b/source/features/new-or-deleted-file.tsx index bcc60bd8..2256e319 100644 --- a/source/features/new-or-deleted-file.tsx +++ b/source/features/new-or-deleted-file.tsx @@ -1,25 +1,25 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; import observe from '../helpers/selector-observer.js'; function add(filename: HTMLAnchorElement): void { - const list = select('ul[aria-label="File Tree"]'); + const list = $('ul[aria-label="File Tree"]'); if (!list && pageDetect.isCommit()) { // Silence error, single-file commits don't have the file list return; } - const fileInList = select(`[href="${filename.hash}"]`, list); + const fileInList = $(`[href="${filename.hash}"]`, list); if (!fileInList) { features.log.error(import.meta.url, 'Could not find file in sidebar, is the sidebar loaded?'); features.unload(import.meta.url); return; } - const icon = select('.octicon-diff-removed, .octicon-diff-added', fileInList) + const icon = $('.octicon-diff-removed, .octicon-diff-added', fileInList) ?.cloneNode(true); if (icon) { // `span` needed for native vertical alignment diff --git a/source/features/new-repo-disable-projects-and-wikis.tsx b/source/features/new-repo-disable-projects-and-wikis.tsx index 5ee56662..a2bf25dd 100644 --- a/source/features/new-repo-disable-projects-and-wikis.tsx +++ b/source/features/new-repo-disable-projects-and-wikis.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, lastElement} from 'select-dom'; import onetime from 'onetime'; import delegate from 'delegate-it'; import domLoaded from 'dom-loaded'; @@ -23,14 +23,14 @@ async function disableWikiAndProjects(): Promise<void> { }, }); await domLoaded; - select('[data-menu-item$="wiki-tab"]')?.remove(); - select('[data-menu-item$="projects-tab"]')?.remove(); + $('[data-menu-item$="wiki-tab"]')?.remove(); + $('[data-menu-item$="projects-tab"]')?.remove(); selectHas('li:has([data-content="Wiki"]')?.remove(); selectHas('li:has([data-content="Projects"])')?.remove(); } function setStorage(): void { - if (select('input#rgh-disable-project')!.checked) { + if ($('input#rgh-disable-project')!.checked) { sessionStorage.rghNewRepo = true; } } @@ -38,7 +38,7 @@ function setStorage(): void { async function init(signal: AbortSignal): Promise<void> { await api.expectToken(); - const anchor = select.last([ + const anchor = lastElement([ '.js-repo-init-setting-container', // IsNewRepo '.form-checkbox', // IsNewRepoTemplate ]); diff --git a/source/features/no-duplicate-list-update-time.tsx b/source/features/no-duplicate-list-update-time.tsx index 1effedeb..d4fd3805 100644 --- a/source/features/no-duplicate-list-update-time.tsx +++ b/source/features/no-duplicate-list-update-time.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -9,7 +9,7 @@ function parseTime(element: HTMLElement): number { } function remove(issue: HTMLElement): void { - const [stateChangeTime, updateTime] = select.all('relative-time', issue); + const [stateChangeTime, updateTime] = $$('relative-time', issue); if (parseTime(updateTime) - parseTime(stateChangeTime) < 10_000) { // Hide if within 10 seconds updateTime.parentElement!.remove(); } diff --git a/source/features/one-click-diff-options.tsx b/source/features/one-click-diff-options.tsx index 6f1eb6f0..091f5408 100644 --- a/source/features/one-click-diff-options.tsx +++ b/source/features/one-click-diff-options.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {BookIcon, CheckIcon, DiffIcon, DiffModifiedIcon} from '@primer/octicons-react'; @@ -9,7 +9,7 @@ import {removeTextNodeContaining} from '../helpers/dom-utils.js'; function isHidingWhitespace(): boolean { // The selector is the native button - return new URL(location.href).searchParams.get('w') === '1' || select.exists('button[name="w"][value="0"]:not([hidden])'); + return new URL(location.href).searchParams.get('w') === '1' || elementExists('button[name="w"][value="0"]:not([hidden])'); } function createWhitespaceButton(): HTMLElement { @@ -36,11 +36,11 @@ function createWhitespaceButton(): HTMLElement { function attachPRButtons(dropdownIcon: SVGElement): void { // TODO: Replace with :has selector const dropdown = dropdownIcon.closest('details.diffbar-item')!; - const diffSettingsForm = select('form[action$="/diffview"]', dropdown)!; + const diffSettingsForm = $('form[action$="/diffview"]', dropdown)!; // Preserve data before emption the form const isUnified = new FormData(diffSettingsForm).get('diff') === 'unified'; - const token = select('[name="authenticity_token"]', diffSettingsForm)!; + const token = $('[name="authenticity_token"]', diffSettingsForm)!; // Empty form except the token field diffSettingsForm.replaceChildren(token); @@ -77,20 +77,20 @@ function attachPRButtons(dropdownIcon: SVGElement): void { dropdown.replaceWith(diffSettingsForm); // Trim title - const prTitle = select('.pr-toolbar .js-issue-title'); - if (prTitle && select.exists('.pr-toolbar progress-bar')) { // Only review view has progress-bar + const prTitle = $('.pr-toolbar .js-issue-title'); + if (prTitle && elementExists('.pr-toolbar progress-bar')) { // Only review view has progress-bar prTitle.style.maxWidth = '24em'; prTitle.title = prTitle.textContent; } // Make space for the new button #655 removeTextNodeContaining( - select('[data-hotkey="c"] strong')!.previousSibling!, + $('[data-hotkey="c"] strong')!.previousSibling!, 'Changes from', ); // Remove extraneous padding around "Clear filters" button - select('.subset-files-tab')?.classList.replace('px-sm-3', 'ml-sm-2'); + $('.subset-files-tab')?.classList.replace('px-sm-3', 'ml-sm-2'); } function initPR(signal: AbortSignal): void { diff --git a/source/features/one-click-pr-or-gist.tsx b/source/features/one-click-pr-or-gist.tsx index 4b964bab..edafda1c 100644 --- a/source/features/one-click-pr-or-gist.tsx +++ b/source/features/one-click-pr-or-gist.tsx @@ -1,6 +1,6 @@ import './one-click-pr-or-gist.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -14,10 +14,10 @@ function init(): void | false { return false; } - for (const dropdownItem of select.all('.select-menu-item', initialGroupedButtons)) { - let title = select('.select-menu-item-heading', dropdownItem)!.textContent.trim(); - const description = select('.description', dropdownItem)!.textContent.trim(); - const radioButton = select('input[type=radio]', dropdownItem)!; + for (const dropdownItem of $$('.select-menu-item', initialGroupedButtons)) { + let title = $('.select-menu-item-heading', dropdownItem)!.textContent.trim(); + const description = $('.description', dropdownItem)!.textContent.trim(); + const radioButton = $('input[type=radio]', dropdownItem)!; const classList = ['btn', 'ml-2', 'tooltipped', 'tooltipped-s']; if (/\bdraft\b/i.test(title)) { @@ -49,7 +49,7 @@ void features.add(import.meta.url, { pageDetect.isGist, ], exclude: [ - () => select.exists('[data-show-dialog-id="drafts-upgrade-dialog"]'), + () => elementExists('[data-show-dialog-id="drafts-upgrade-dialog"]'), ], deduplicate: 'has-rgh', awaitDomReady: true, diff --git a/source/features/one-click-review-submission.tsx b/source/features/one-click-review-submission.tsx index 942ba758..0d7cd60f 100644 --- a/source/features/one-click-review-submission.tsx +++ b/source/features/one-click-review-submission.tsx @@ -12,7 +12,7 @@ function replaceCheckboxes(originalSubmitButton: HTMLButtonElement): void { const actionsRow = originalSubmitButton.closest('.form-actions')!; const formAttribute = originalSubmitButton.getAttribute('form')!; - // Do not use `select.all` because elements can be outside `form` + // Do not use `$$` because elements can be outside `form` // `RadioNodeList` is dynamic, so we need to make a copy const radios = [...form.elements.namedItem('pull_request_review[event]') as RadioNodeList] as HTMLInputElement[]; if (radios.length === 0) { diff --git a/source/features/open-all-conversations.tsx b/source/features/open-all-conversations.tsx index d6d22257..f6ba04b8 100644 --- a/source/features/open-all-conversations.tsx +++ b/source/features/open-all-conversations.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import delegate, {DelegateEvent} from 'delegate-it'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -21,9 +21,9 @@ const issueListSelector = pageDetect.isGlobalIssueOrPRList() function onButtonClick(event: DelegateEvent<MouseEvent, HTMLButtonElement>): void { const onlySelected = event.delegateTarget.closest('.table-list-triage'); - const issues = select.all(`${issueListSelector} .js-issue-row`) + const issues = $$(`${issueListSelector} .js-issue-row`) // TODO: Use conditional :has(:checked) instead - .filter(issue => onlySelected ? select.exists(':checked', issue) : true); + .filter(issue => onlySelected ? elementExists(':checked', issue) : true); void openTabs(issues.map(issue => getUrlFromItem(issue))); } diff --git a/source/features/open-all-notifications.tsx b/source/features/open-all-notifications.tsx index 1e2b3639..7af28417 100644 --- a/source/features/open-all-notifications.tsx +++ b/source/features/open-all-notifications.tsx @@ -1,6 +1,6 @@ import './open-all-notifications.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {LinkExternalIcon} from '@primer/octicons-react'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -20,7 +20,7 @@ const openUnread = features.getIdentifiers('open-notifications-button'); const openSelected = features.getIdentifiers('open-selected-button'); function getUnreadNotifications(container: ParentNode = document): HTMLElement[] { - return select.all('.notification-unread', container); + return $$('.notification-unread', container); } async function openNotifications(notifications: Element[], markAsDone = false): Promise<void> { @@ -52,17 +52,17 @@ async function openUnreadNotifications({delegateTarget, altKey}: DelegateEvent<M } async function openSelectedNotifications(): Promise<void> { - const selectedNotifications = select.all('.notifications-list-item :checked') + const selectedNotifications = $$('.notifications-list-item :checked') .map(checkbox => checkbox.closest('.notifications-list-item')!); await openNotifications(selectedNotifications); - if (!select.exists('.notification-unread')) { + if (!elementExists('.notification-unread')) { removeOpenUnreadButtons(); } } function removeOpenUnreadButtons(container: ParentNode = document): void { - for (const button of select.all(openUnread.selector, container)) { + for (const button of $$(openUnread.selector, container)) { button.remove(); } } diff --git a/source/features/open-issue-to-latest-comment.tsx b/source/features/open-issue-to-latest-comment.tsx index 070f960c..fdc04b22 100644 --- a/source/features/open-issue-to-latest-comment.tsx +++ b/source/features/open-issue-to-latest-comment.tsx @@ -1,15 +1,15 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; const selector = ` :is(.js-issue-row, .js-pinned-issue-list-item) - .Link--muted:is([aria-label$="comment"], [aria-label$="comments"]) + .Link--muted:is(a[aria-label$="comment"], a[aria-label$="comments"]) `; function init(): void { - for (const link of select.all<HTMLAnchorElement>(selector)) { + for (const link of $$(selector)) { link.hash = '#partial-timeline'; } } diff --git a/source/features/pinned-issues-update-time.tsx b/source/features/pinned-issues-update-time.tsx index c840b16d..cfce8777 100644 --- a/source/features/pinned-issues-update-time.tsx +++ b/source/features/pinned-issues-update-time.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$} from 'select-dom'; import batchedFunction from 'batched-function'; import * as pageDetect from 'github-url-detection'; @@ -33,7 +33,7 @@ const getLastUpdated = new CachedFunction('last-updated', { }); function getPinnedIssueNumber(pinnedIssue: HTMLElement): number { - return looseParseInt(select('.opened-by', pinnedIssue)!.firstChild!); + return looseParseInt($('.opened-by', pinnedIssue)!.firstChild!); } async function update(pinnedIssues: HTMLElement[]): Promise<void> { @@ -41,7 +41,7 @@ async function update(pinnedIssues: HTMLElement[]): Promise<void> { for (const pinnedIssue of pinnedIssues) { const issueNumber = getPinnedIssueNumber(pinnedIssue); const {updatedAt} = lastUpdated[api.escapeKey(issueNumber)]; - const originalLine = select('.opened-by', pinnedIssue)!; + const originalLine = $('.opened-by', pinnedIssue)!; originalLine.after( // .rgh class enables tweakers to hide the number <span className="text-small color-fg-muted"> diff --git a/source/features/pr-base-commit.tsx b/source/features/pr-base-commit.tsx index c03a4423..c9e0688c 100644 --- a/source/features/pr-base-commit.tsx +++ b/source/features/pr-base-commit.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; @@ -60,7 +60,7 @@ void features.add(import.meta.url, { ], exclude: [ pageDetect.isClosedPR, - () => select('.head-ref')!.title === 'This repository has been deleted', + () => $('.head-ref')!.title === 'This repository has been deleted', ], awaitDomReady: true, // DOM-based exclusions init, diff --git a/source/features/pr-branch-auto-delete.tsx b/source/features/pr-branch-auto-delete.tsx index ca798774..e2d35d01 100644 --- a/source/features/pr-branch-auto-delete.tsx +++ b/source/features/pr-branch-auto-delete.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {InfoIcon} from '@primer/octicons-react'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -14,7 +14,7 @@ import {canEditEveryComment} from './quick-comment-edit.js'; const canCreateRelease = canEditEveryComment; async function init(): Promise<void> { - const deleteButton = select('[action$="/cleanup"] [type="submit"]'); + const deleteButton = $('[action$="/cleanup"] [type="submit"]'); if (!deleteButton) { return; } diff --git a/source/features/pr-filters.tsx b/source/features/pr-filters.tsx index c1487037..e06f3802 100644 --- a/source/features/pr-filters.tsx +++ b/source/features/pr-filters.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {CheckIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -72,10 +72,10 @@ async function addChecksFilter(reviewsFilter: HTMLElement): Promise<void> { const checksFilter = reviewsFilter.cloneNode(true); checksFilter.id = ''; - select('summary', checksFilter)!.firstChild!.textContent = 'Checks\u00A0'; // Only replace text node, keep caret - select('.SelectMenu-title', checksFilter)!.textContent = 'Filter by checks status'; + $('summary', checksFilter)!.firstChild!.textContent = 'Checks\u00A0'; // Only replace text node, keep caret + $('.SelectMenu-title', checksFilter)!.textContent = 'Filter by checks status'; - const dropdown = select('.SelectMenu-list', checksFilter)!; + const dropdown = $('.SelectMenu-list', checksFilter)!; dropdown.textContent = ''; // Drop previous filters for (const status of ['Success', 'Failure', 'Pending']) { diff --git a/source/features/pr-jump-to-first-non-viewed-file.tsx b/source/features/pr-jump-to-first-non-viewed-file.tsx index 119a4c13..eb1f2d2c 100644 --- a/source/features/pr-jump-to-first-non-viewed-file.tsx +++ b/source/features/pr-jump-to-first-non-viewed-file.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import elementReady from 'element-ready'; import delegate from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -6,7 +6,7 @@ import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; function jumpToFirstNonViewed(): void { - const firstNonViewedFile = select('.file:not([data-file-user-viewed])')!; + const firstNonViewedFile = $('.file:not([data-file-user-viewed])')!; if (firstNonViewedFile) { // Scroll to file without pushing to history location.replace('#' + firstNonViewedFile.id); diff --git a/source/features/prevent-link-loss.tsx b/source/features/prevent-link-loss.tsx index 19adde33..a300403a 100644 --- a/source/features/prevent-link-loss.tsx +++ b/source/features/prevent-link-loss.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {AlertIcon} from '@primer/octicons-react'; import debounceFn from 'debounce-fn'; import * as pageDetect from 'github-url-detection'; @@ -29,7 +29,7 @@ function handleButtonClick({currentTarget: fixButton}: React.MouseEvent<HTMLButt } function getUI(field: HTMLTextAreaElement, ...classes: string[]): HTMLElement { - return select('.rgh-prevent-link-loss-container', field.form!) ?? (createBanner({ + return $('.rgh-prevent-link-loss-container', field.form!) ?? (createBanner({ icon: <AlertIcon className="m-0"/>, text: ( <> @@ -57,11 +57,11 @@ const updateUI = debounceFn(({delegateTarget: field}: DelegateEvent<Event, HTMLT if (!isVulnerableToLinkLoss(field.value)) { getUI(field).remove(); } else if (pageDetect.isNewIssue() || pageDetect.isNewRelease() || pageDetect.isCompare()) { - select('file-attachment', field.form!)!.append( + $('file-attachment', field.form!)!.append( getUI(field, 'mt-2', 'mx-0', 'mx-md-2'), ); } else { - select('.form-actions', field.form!)!.before( + $('.form-actions', field.form!)!.before( getUI(field, 'mx-md-2', 'mb-2'), ); } diff --git a/source/features/prevent-pr-merge-panel-opening.tsx b/source/features/prevent-pr-merge-panel-opening.tsx index 13bd7db7..f7d6473f 100644 --- a/source/features/prevent-pr-merge-panel-opening.tsx +++ b/source/features/prevent-pr-merge-panel-opening.tsx @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; async function sessionResumeHandler(): Promise<void> { await Promise.resolve(); // The `session:resume` event fires a bit too early - const cancelMergeButton = select('.merge-branch-form .js-details-target'); + const cancelMergeButton = $('.merge-branch-form .js-details-target'); if (cancelMergeButton) { cancelMergeButton.click(); document.removeEventListener('session:resume', sessionResumeHandler); diff --git a/source/features/preview-hidden-comments.tsx b/source/features/preview-hidden-comments.tsx index dd2c6cc7..ae89e98e 100644 --- a/source/features/preview-hidden-comments.tsx +++ b/source/features/preview-hidden-comments.tsx @@ -1,6 +1,6 @@ import './preview-hidden-comments.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -11,7 +11,7 @@ function preview(hiddenCommentHeader: HTMLElement): void { const details = hiddenCommentHeader.closest('details')!; details.classList.add('rgh-preview-hidden-comments'); // Used in CSS - const comment = select('.comment-body', details)!; + const comment = $('.comment-body', details)!; const commentText = comment.textContent.trim(); if (commentText.length === 0) { return; diff --git a/source/features/previous-next-commit-buttons.tsx b/source/features/previous-next-commit-buttons.tsx index 71ffd615..a983c59c 100644 --- a/source/features/previous-next-commit-buttons.tsx +++ b/source/features/previous-next-commit-buttons.tsx @@ -1,17 +1,17 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; function init(): false | void { - const originalPreviousNext = select('.commit .BtnGroup.float-right'); + const originalPreviousNext = $('.commit .BtnGroup.float-right'); if (!originalPreviousNext) { return false; } // Wrap the button in a <div> to avoid #4503 - select('#files')!.after( + $('#files')!.after( <div className="d-flex flex-justify-end mb-3"> {originalPreviousNext.cloneNode(true)} </div>, diff --git a/source/features/profile-gists-link.tsx b/source/features/profile-gists-link.tsx index 737cdcf7..eaa18f76 100644 --- a/source/features/profile-gists-link.tsx +++ b/source/features/profile-gists-link.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {CodeSquareIcon} from '@primer/octicons-react'; @@ -49,8 +49,8 @@ async function appendTab(navigationBar: Element): Promise<void> { navigationBar.replaceWith(navigationBar); // There are two UnderlineNav items (responsive…) that point to the same dropdown - const overflowNav = select('.js-responsive-underlinenav .dropdown-menu ul')!; - if (!select.exists('[data-rgh-label="Gists"]', overflowNav)) { + const overflowNav = $('.js-responsive-underlinenav .dropdown-menu ul')!; + if (!elementExists('[data-rgh-label="Gists"]', overflowNav)) { overflowNav.append( createDropdownItem('Gists', user.url), ); diff --git a/source/features/pull-request-hotkeys.tsx b/source/features/pull-request-hotkeys.tsx index 64929964..ce4b3df8 100644 --- a/source/features/pull-request-hotkeys.tsx +++ b/source/features/pull-request-hotkeys.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -7,7 +7,7 @@ import {addHotkey} from '../github-helpers/hotkey.js'; async function init(): Promise<void> { const tabnav = await elementReady('#partial-discussion-header + .tabnav'); - const tabs = select.all('a.tabnav-tab', tabnav); + const tabs = $$('a.tabnav-tab', tabnav); const lastTab = tabs.length - 1; const selectedIndex = tabs.findIndex(tab => tab.classList.contains('selected')); diff --git a/source/features/quick-comment-edit.tsx b/source/features/quick-comment-edit.tsx index a9d343a6..9b60b369 100644 --- a/source/features/quick-comment-edit.tsx +++ b/source/features/quick-comment-edit.tsx @@ -1,6 +1,6 @@ import './quick-comment-edit.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import {PencilIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -11,7 +11,7 @@ import {isArchivedRepoAsync} from '../github-helpers/index.js'; function addQuickEditButton(commentForm: Element): void { const commentBody = commentForm.closest('.js-comment')!; // We can't rely on a class for deduplication because the whole comment might be replaced by GitHub #5572 - if (select.exists('.rgh-quick-comment-edit-button', commentBody)) { + if (elementExists('.rgh-quick-comment-edit-button', commentBody)) { return; } @@ -30,7 +30,7 @@ function addQuickEditButton(commentForm: Element): void { } export function canEditEveryComment(): boolean { - return select.exists([ + return elementExists([ // If you can lock conversations, you have write access '.lock-toggle-link > .octicon-lock', diff --git a/source/features/quick-comment-hiding.tsx b/source/features/quick-comment-hiding.tsx index 15075f2c..c37c4c4f 100644 --- a/source/features/quick-comment-hiding.tsx +++ b/source/features/quick-comment-hiding.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import delegate, {DelegateEvent} from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -20,7 +20,7 @@ function generateSubmenu(hideButton: Element): void { detailsElement.classList.add('rgh-quick-comment-hiding-details'); const comment = hideButton.closest('.unminimized-comment')!; - const hideCommentForm: HTMLFormElement = select(formSelector, comment)!; + const hideCommentForm: HTMLFormElement = $(formSelector, comment)!; // Generate dropdown const newForm = hideCommentForm.cloneNode(); @@ -31,7 +31,7 @@ function generateSubmenu(hideButton: Element): void { // Imitate existing menu, reset classes newForm.className = ['dropdown-menu', 'dropdown-menu-sw', 'color-fg-default', 'show-more-popover', 'anim-scale-in'].join(' '); - for (const reason of select.all('option:not([value=""])', hideCommentForm.elements.classifier)) { + for (const reason of $$('option:not([value=""])', hideCommentForm.elements.classifier)) { newForm.append( <button type="submit" @@ -60,10 +60,10 @@ function toggleSubMenu(hideButton: Element, show: boolean): void { const dropdown = hideButton.closest('details')!; // Native dropdown - select('details-menu', dropdown)!.classList.toggle('v-hidden', show); + $('details-menu', dropdown)!.classList.toggle('v-hidden', show); // "Hide comment" dropdown - select(formSelector, dropdown)!.classList.toggle('v-hidden', !show); + $(formSelector, dropdown)!.classList.toggle('v-hidden', !show); } function resetDropdowns(event: DelegateEvent): void { diff --git a/source/features/quick-label-removal.tsx b/source/features/quick-label-removal.tsx index ef0efeab..99b7e35f 100644 --- a/source/features/quick-label-removal.tsx +++ b/source/features/quick-label-removal.tsx @@ -1,6 +1,6 @@ import './quick-label-removal.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import onetime from 'onetime'; import {XIcon} from '@primer/octicons-react'; import {assertError} from 'ts-extras'; @@ -13,7 +13,7 @@ import showToast from '../github-helpers/toast.js'; import {getConversationNumber} from '../github-helpers/index.js'; import observe from '../helpers/selector-observer.js'; -const canNotEditLabels = onetime((): boolean => !select.exists('.label-select-menu .octicon-gear')); +const canNotEditLabels = onetime((): boolean => !elementExists('.label-select-menu .octicon-gear')); async function removeLabelButtonClickHandler(event: DelegateEvent<MouseEvent, HTMLButtonElement>): Promise<void> { event.preventDefault(); diff --git a/source/features/quick-mention.tsx b/source/features/quick-mention.tsx index 20377d5b..6d0a10e2 100644 --- a/source/features/quick-mention.tsx +++ b/source/features/quick-mention.tsx @@ -1,6 +1,6 @@ import './quick-mention.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import {ReplyIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import * as textFieldEdit from 'text-field-edit'; @@ -18,7 +18,7 @@ function prefixUserMention(userMention: string): string { function mentionUser({delegateTarget: button}: DelegateEvent): void { const userMention = button.parentElement!.querySelector('img')!.alt; - const newComment = select('textarea#new_comment_field')!; + const newComment = $('textarea#new_comment_field')!; newComment.focus(); // If the new comment field has selected text, don’t replace it @@ -53,7 +53,7 @@ function add(avatar: HTMLElement): void { if ( // TODO: Rewrite with :has() // Exclude events that aren't tall enough, like hidden comments or reviews without comments - !select.exists('.unminimized-comment, .js-comment-container', timelineItem) + !elementExists('.unminimized-comment, .js-comment-container', timelineItem) ) { return; } @@ -68,7 +68,7 @@ function add(avatar: HTMLElement): void { wrap(avatar, <div className="avatar-parent-child TimelineItem-avatar d-none d-md-block"/>); } - const userMention = select('img', avatar)!.alt; + const userMention = $('img', avatar)!.alt; avatar.classList.add('rgh-quick-mention'); avatar.after( <button diff --git a/source/features/quick-repo-deletion.tsx b/source/features/quick-repo-deletion.tsx index 08a01939..72a59206 100644 --- a/source/features/quick-repo-deletion.tsx +++ b/source/features/quick-repo-deletion.tsx @@ -1,7 +1,7 @@ import './quick-repo-deletion.css'; import delay from 'delay'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import {TrashIcon} from '@primer/octicons-react'; import elementReady from 'element-ready'; import {assertError} from 'ts-extras'; @@ -18,7 +18,7 @@ import parseBackticks from '../github-helpers/parse-backticks.js'; import observe from '../helpers/selector-observer.js'; function handleToggle(event: DelegateEvent<Event, HTMLDetailsElement>): void { - const hasContent = select.exists([ + const hasContent = elementExists([ '[data-hotkey="g i"] .Counter:not([hidden])', // Open issues '[data-hotkey="g p"] .Counter:not([hidden])', // Open PRs '.rgh-open-prs-of-forks', // PRs opened in the source repo @@ -71,7 +71,7 @@ async function buttonTimeout(buttonContainer: HTMLDetailsElement): Promise<boole void verifyScopesWhileWaiting(abortController); let secondsLeft = 5; - const button = select('.btn', buttonContainer)!; + const button = $('.btn', buttonContainer)!; try { do { button.style.transform = `scale(${1.2 - ((secondsLeft - 5) / 3)})`; // Dividend is zoom speed @@ -91,7 +91,7 @@ async function start(buttonContainer: HTMLDetailsElement): Promise<void> { return; } - select('.btn', buttonContainer)!.textContent = 'Deleting repo…'; + $('.btn', buttonContainer)!.textContent = 'Deleting repo…'; const {nameWithOwner, owner} = getRepo()!; try { await api.v3('/repos/' + nameWithOwner, { @@ -120,7 +120,7 @@ async function start(buttonContainer: HTMLDetailsElement): Promise<void> { <><TrashIcon/> <span>Repository <strong>{nameWithOwner}</strong> deleted. <a href={restoreURL}>Restore it</a>, <a href={forkSource}>visit the source repo</a>, or see <a href={otherForksURL}>your other forks.</a></span></>, {action: false}, ); - select('.application-main')!.remove(); + $('.application-main')!.remove(); if (document.hidden) { // Try closing the tab if in the background. Could fail, so we still update the UI above void browser.runtime.sendMessage({closeTab: true}); diff --git a/source/features/quick-review-comment-deletion.tsx b/source/features/quick-review-comment-deletion.tsx index 447acfb5..f5e6f154 100644 --- a/source/features/quick-review-comment-deletion.tsx +++ b/source/features/quick-review-comment-deletion.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {TrashIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -23,7 +23,7 @@ function onButtonClick({delegateTarget: button}: DelegateEvent): void { async function preloadDropdown({delegateTarget: button}: DelegateEvent): Promise<void> { const comment = button.closest('.js-comment')!; - await loadDetailsMenu(select('details-menu.show-more-popover', comment)!); + await loadDetailsMenu($('details-menu.show-more-popover', comment)!); } function addDeleteButton(cancelButton: Element): void { diff --git a/source/features/quick-review.tsx b/source/features/quick-review.tsx index ac159dd4..14c5f38a 100644 --- a/source/features/quick-review.tsx +++ b/source/features/quick-review.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import delay from 'delay'; -import select from 'select-dom'; +import {$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -28,7 +28,7 @@ function initSidebarReviewButton(signal: AbortSignal): void { function focusReviewTextarea({delegateTarget}: DelegateEvent<Event, HTMLDetailsElement>): void { if (delegateTarget.open) { - select('textarea', delegateTarget)!.focus(); + $('textarea', delegateTarget)!.focus(); } } diff --git a/source/features/reactions-avatars.tsx b/source/features/reactions-avatars.tsx index 0e1d590c..29d868f8 100644 --- a/source/features/reactions-avatars.tsx +++ b/source/features/reactions-avatars.tsx @@ -1,6 +1,6 @@ import './reactions-avatars.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import {flatZip} from 'flat-zip'; import * as pageDetect from 'github-url-detection'; @@ -58,12 +58,12 @@ const viewportObserver = new IntersectionObserver(changes => { }); function showAvatarsOn(commentReactions: Element): void { - const reactionTypes = select.all('.social-reaction-summary-item', commentReactions).length; + const reactionTypes = $$('.social-reaction-summary-item', commentReactions).length; const avatarLimit = arbitraryAvatarLimit - (reactionTypes * approximateHeaderLength); - const participantByReaction = select - .all(':scope > button.social-reaction-summary-item', commentReactions) - .map(button => getParticipants(button)); + const participantByReaction + = $$(':scope > button.social-reaction-summary-item', commentReactions) + .map(button => getParticipants(button)); const flatParticipants = flatZip(participantByReaction, avatarLimit); for (const {button, username, imageUrl} of flatParticipants) { diff --git a/source/features/release-download-count.tsx b/source/features/release-download-count.tsx index 97e30e01..9cbe8acb 100644 --- a/source/features/release-download-count.tsx +++ b/source/features/release-download-count.tsx @@ -1,6 +1,6 @@ import './release-download-count.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import {DownloadIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; import {abbreviateNumber} from 'js-abbreviation-number'; @@ -24,7 +24,7 @@ async function getAssetsForTag(tag: string): Promise<Record<string, number>> { async function addCounts(assetsList: HTMLElement): Promise<void> { // TODO: Use :has selector instead - if (!select.exists('.octicon-package', assetsList)) { + if (!elementExists('.octicon-package', assetsList)) { return; } @@ -40,7 +40,7 @@ async function addCounts(assetsList: HTMLElement): Promise<void> { const assets = await getAssetsForTag(releaseName); const calculateHeatIndex = createHeatIndexFunction(Object.values(assets)); - for (const assetLink of select.all('.octicon-package ~ a', assetsList)) { + for (const assetLink of $$('.octicon-package ~ a', assetsList)) { // Match the asset in the DOM to the asset in the API response const downloadCount = assets[assetLink.pathname.split('/').pop()!] ?? 0; diff --git a/source/features/repo-header-info.tsx b/source/features/repo-header-info.tsx index 56963078..1942f22a 100644 --- a/source/features/repo-header-info.tsx +++ b/source/features/repo-header-info.tsx @@ -1,7 +1,7 @@ import * as pageDetect from 'github-url-detection'; import {LockIcon, RepoForkedIcon, StarIcon} from '@primer/octicons-react'; import React from 'dom-chef'; -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import {CachedFunction} from 'webext-storage-cache'; import observe from '../helpers/selector-observer.js'; @@ -31,14 +31,14 @@ async function add(repoLink: HTMLAnchorElement): Promise<void> { const {isFork, isPrivate, stargazerCount} = await repositoryInfo.get(); // GitHub may already show this icon natively, so we match its position - if (isPrivate && !select.exists('.octicon-lock', repoLink)) { + if (isPrivate && !elementExists('.octicon-lock', repoLink)) { repoLink.append( <LockIcon className="ml-1" width={12} height={12}/>, ); } // GitHub may already show this icon natively, so we match its position - if (isFork && !select.exists('.octicon-repo-forked', repoLink)) { + if (isFork && !elementExists('.octicon-repo-forked', repoLink)) { repoLink.append( <RepoForkedIcon className="ml-1" width={12} height={12}/>, ); diff --git a/source/features/repo-wide-file-finder.tsx b/source/features/repo-wide-file-finder.tsx index 26599177..407be229 100644 --- a/source/features/repo-wide-file-finder.tsx +++ b/source/features/repo-wide-file-finder.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -18,7 +18,7 @@ void features.add(import.meta.url, { pageDetect.isRepo, ], exclude: [ - () => select.exists('[data-hotkey="t"]'), + () => elementExists('[data-hotkey="t"]'), pageDetect.isEmptyRepo, pageDetect.isPRFiles, pageDetect.isFileFinder, diff --git a/source/features/rgh-improve-new-issue-form.tsx b/source/features/rgh-improve-new-issue-form.tsx index 33ed0890..b574412f 100644 --- a/source/features/rgh-improve-new-issue-form.tsx +++ b/source/features/rgh-improve-new-issue-form.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -9,7 +9,7 @@ import {expectToken, expectTokenScope} from '../github-helpers/api.js'; import {isRefinedGitHubRepo} from '../github-helpers/index.js'; function addNotice(adjective: JSX.Element | string): void { - select('#issue_body_template_name')!.before( + $('#issue_body_template_name')!.before( <div className="flash flash-warn m-2"> Your Personal Access Token is {adjective}. Some Refined GitHub features will not work without it. You can update it <button className="btn-link" type="button" onClick={openOptions as unknown as React.MouseEventHandler}>in the options</button>. @@ -34,11 +34,11 @@ async function checkToken(): Promise<void> { async function setVersion(): Promise<void> { const {version} = browser.runtime.getManifest(); - select('input#issue_form_version')!.value = version; + $('input#issue_form_version')!.value = version; } async function linkifyCacheRefresh(): Promise<void> { - select('[href="#clear-cache"]')!.replaceWith( + $('[href="#clear-cache"]')!.replaceWith( <button className="btn" type="button" diff --git a/source/features/rgh-welcome-issue.tsx b/source/features/rgh-welcome-issue.tsx index 86a2de2f..62b07af2 100644 --- a/source/features/rgh-welcome-issue.tsx +++ b/source/features/rgh-welcome-issue.tsx @@ -1,5 +1,5 @@ import './rgh-welcome-issue.css'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import delegate from 'delegate-it'; import features from '../feature-manager.js'; @@ -23,11 +23,11 @@ const placeholdersSelector = 'a[href="#rgh-linkify-welcome-issue"]'; function init(signal: AbortSignal): void { delegate(placeholdersSelector, 'click', openOptions, {signal}); - if (select.exists('.rgh-linkify-welcome-issue')) { + if (elementExists('.rgh-linkify-welcome-issue')) { return; } - const [opening, closing] = select.all<HTMLAnchorElement>(placeholdersSelector); + const [opening, closing] = $$(placeholdersSelector); closing.remove(); // Move the wrapped text into the existing link diff --git a/source/features/same-branch-author-commits.tsx b/source/features/same-branch-author-commits.tsx index 84afda1c..b0230752 100644 --- a/source/features/same-branch-author-commits.tsx +++ b/source/features/same-branch-author-commits.tsx @@ -1,10 +1,10 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; function init(): void { - for (const author of select.all('.js-navigation-container a.commit-author')) { + for (const author of $$('.js-navigation-container a.commit-author')) { author.pathname = location.pathname; } } diff --git a/source/features/select-all-notifications-shortcut.tsx b/source/features/select-all-notifications-shortcut.tsx index 396df608..9fc46687 100644 --- a/source/features/select-all-notifications-shortcut.tsx +++ b/source/features/select-all-notifications-shortcut.tsx @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; import {registerHotkey} from '../github-helpers/hotkey.js'; function selectAllNotifications(): void { - select('.js-notifications-mark-all-prompt')!.click(); + $('.js-notifications-mark-all-prompt')!.click(); } function init(signal: AbortSignal): void { diff --git a/source/features/select-notifications.tsx b/source/features/select-notifications.tsx index 992ca994..904f86bf 100644 --- a/source/features/select-notifications.tsx +++ b/source/features/select-notifications.tsx @@ -1,6 +1,6 @@ import './select-notifications.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import onetime from 'onetime'; import delegate from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -34,8 +34,8 @@ type Filter = keyof typeof filters; type Category = 'Type' | 'Status' | 'Read'; function resetFilters({target}: React.SyntheticEvent): void { - select('form#rgh-select-notifications-form')!.reset(); - for (const label of select.all('label', target as Element)) { + $('form#rgh-select-notifications-form')!.reset(); + for (const label of $$('label', target as Element)) { label.setAttribute('aria-checked', 'false'); } } @@ -45,37 +45,37 @@ function getFiltersSelector(formData: FormData, category: Category): string { } function handleSelection({target}: Event): void { - const selectAllCheckbox = select('input[type="checkbox"].js-notifications-mark-all-prompt')!; + const selectAllCheckbox = $('input[type="checkbox"].js-notifications-mark-all-prompt')!; // Reset the "Select all" checkbox if (selectAllCheckbox.checked) { selectAllCheckbox.click(); } - if (select.exists(':checked', target as Element)) { - const formData = new FormData(select('form#rgh-select-notifications-form')); + if (elementExists(':checked', target as Element)) { + const formData = new FormData($('form#rgh-select-notifications-form')); const types = getFiltersSelector(formData, 'Type'); const statuses = getFiltersSelector(formData, 'Status'); const readStatus = getFiltersSelector(formData, 'Read'); - for (const notification of select.all('.notifications-list-item')) { + for (const notification of $$('.notifications-list-item')) { if ( - (types && !select.exists(types, notification)) - || (statuses && !select.exists(statuses, notification)) + (types && !elementExists(types, notification)) + || (statuses && !elementExists(statuses, notification)) || (readStatus && !notification.matches(readStatus)) ) { // Make excluded notifications unselectable - select('.js-notification-bulk-action-check-item', notification)!.removeAttribute('data-check-all-item'); + $('.js-notification-bulk-action-check-item', notification)!.removeAttribute('data-check-all-item'); } } // If at least one notification is selectable, trigger the "Select all" checkbox - if (select.exists('.js-notification-bulk-action-check-item[data-check-all-item]')) { + if (elementExists('.js-notification-bulk-action-check-item[data-check-all-item]')) { selectAllCheckbox.click(); } } // Make all notifications selectable again - for (const disabledNotificationCheckbox of select.all('.js-notification-bulk-action-check-item:not([data-check-all-item])')) { + for (const disabledNotificationCheckbox of $$('.js-notification-bulk-action-check-item:not([data-check-all-item])')) { disabledNotificationCheckbox.setAttribute('data-check-all-item', ''); } } @@ -152,7 +152,7 @@ const createDropdown = onetime(() => ( )); function closeDropdown(): void { - select('.rgh-select-notifications')?.removeAttribute('open'); + $('.rgh-select-notifications')?.removeAttribute('open'); } function addDropdown(markAllPrompt: Element): void { diff --git a/source/features/selection-in-new-tab.tsx b/source/features/selection-in-new-tab.tsx index ae4285d2..a33b4d05 100644 --- a/source/features/selection-in-new-tab.tsx +++ b/source/features/selection-in-new-tab.tsx @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import onetime from 'onetime'; import features from '../feature-manager.js'; import {registerHotkey} from '../github-helpers/hotkey.js'; function openInNewTab(): void { - const selected = select('.navigation-focus a.js-navigation-open[href]'); + const selected = $('.navigation-focus a.js-navigation-open[href]'); if (!selected) { return; } diff --git a/source/features/show-open-prs-of-forks.tsx b/source/features/show-open-prs-of-forks.tsx index 6f7091b0..82fd909d 100644 --- a/source/features/show-open-prs-of-forks.tsx +++ b/source/features/show-open-prs-of-forks.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -62,7 +62,7 @@ async function initHeadHint(): Promise<void | false> { return false; } - select(`[data-hovercard-type="repository"][href="/${getForkedRepo()!}"]`)!.after( + $(`[data-hovercard-type="repository"][href="/${getForkedRepo()!}"]`)!.after( // The class is used by `quick-fork-deletion` <> with <a href={url} className="rgh-open-prs-of-forks">{getLinkCopy(count)}</a></>, ); @@ -74,7 +74,7 @@ async function initDeleteHint(): Promise<void | false> { return false; } - select('details-dialog[aria-label*="Delete"] .Box-body p:first-child')!.after( + $('details-dialog[aria-label*="Delete"] .Box-body p:first-child')!.after( <p className="flash flash-warn"> It will also abandon <a href={url}>your {getLinkCopy(count)}</a> in <strong>{getForkedRepo()!}</strong> and you’ll no longer be able to edit {count === 1 ? 'it' : 'them'}. </p>, diff --git a/source/features/sort-conversations-by-update-time.tsx b/source/features/sort-conversations-by-update-time.tsx index a63292f6..fb2f6862 100644 --- a/source/features/sort-conversations-by-update-time.tsx +++ b/source/features/sort-conversations-by-update-time.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import elementReady from 'element-ready'; @@ -16,9 +16,9 @@ export function saveOriginalHref(link: HTMLAnchorElement): void { async function selectCurrentConversationFilter(): Promise<void> { const currentSearchURL = location.href.replace('/pulls?', '/issues?'); // Replacement needed to make up for the redirection of "Your pull requests" link const menu = await elementReady('#filters-select-menu'); - const currentFilter = select(`a.SelectMenu-item[href="${currentSearchURL}"]`, menu); + const currentFilter = $(`a.SelectMenu-item[href="${currentSearchURL}"]`, menu); if (currentFilter) { - select('[aria-checked="true"]', menu)?.setAttribute('aria-checked', 'false'); + $('[aria-checked="true"]', menu)?.setAttribute('aria-checked', 'false'); currentFilter.setAttribute('aria-checked', 'true'); } } diff --git a/source/features/submission-via-ctrl-enter-everywhere.tsx b/source/features/submission-via-ctrl-enter-everywhere.tsx index 1472b9ea..773e0d89 100644 --- a/source/features/submission-via-ctrl-enter-everywhere.tsx +++ b/source/features/submission-via-ctrl-enter-everywhere.tsx @@ -1,10 +1,10 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; function addQuickSubmit(): void { - select([ + $([ 'input#commit-summary-input', 'textarea[aria-label="Describe this release"]', ])!.classList.add('js-quick-submit'); diff --git a/source/features/swap-branches-on-compare.tsx b/source/features/swap-branches-on-compare.tsx index 7d52bf32..559f3653 100644 --- a/source/features/swap-branches-on-compare.tsx +++ b/source/features/swap-branches-on-compare.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -16,10 +16,10 @@ function init(): void { // Compares against the "base" branch if the URL only has one reference if (references.length === 1) { - references.unshift(select('.branch span')!.textContent); + references.unshift($('.branch span')!.textContent); } - const referencePicker = select('.range-editor .d-inline-block + .range-cross-repo-pair')!; + const referencePicker = $('.range-editor .d-inline-block + .range-cross-repo-pair')!; referencePicker.after( <a className="btn btn-sm mx-2" href={buildRepoURL('compare/' + references.join('...'))}> Swap diff --git a/source/features/sync-pr-commit-title.tsx b/source/features/sync-pr-commit-title.tsx index 114ec2ad..22362ee7 100644 --- a/source/features/sync-pr-commit-title.tsx +++ b/source/features/sync-pr-commit-title.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import delegate from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -14,7 +14,7 @@ const prTitleFieldSelector = 'input#issue_title'; const commitTitleFieldSelector = '.is-squashing form:not([hidden]) input#merge_title_field'; function getCurrentCommitTitleField(): HTMLInputElement | undefined { - return select(commitTitleFieldSelector); + return $(commitTitleFieldSelector); } function getCurrentCommitTitle(): string | undefined { @@ -22,7 +22,7 @@ function getCurrentCommitTitle(): string | undefined { } function createCommitTitle(): string { - const prTitle = select(prTitleFieldSelector)!.value.trim(); + const prTitle = $(prTitleFieldSelector)!.value.trim(); return `${prTitle} (#${getConversationNumber()!})`; } @@ -33,7 +33,7 @@ function needsSubmission(): boolean { function getUI(): HTMLElement { const cancelButton = <button type="button" className="btn-link Link--muted text-underline rgh-sync-pr-commit-title">Cancel</button>; - return select('.rgh-sync-pr-commit-title-note') ?? ( + return $('.rgh-sync-pr-commit-title-note') ?? ( <p className="note rgh-sync-pr-commit-title-note"> The title of this PR will be updated to match this title. {cancelButton} </p> diff --git a/source/features/tag-changes-link.tsx b/source/features/tag-changes-link.tsx index de60aedf..70b7f58a 100644 --- a/source/features/tag-changes-link.tsx +++ b/source/features/tag-changes-link.tsx @@ -1,6 +1,6 @@ import './tag-changes-link.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import domLoaded from 'dom-loaded'; import {DiffIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -19,7 +19,7 @@ type TagDetails = { }; async function getNextPage(): Promise<DocumentFragment> { - const nextPageLink = select('.pagination a:last-child'); + const nextPageLink = $('.pagination a:last-child'); if (nextPageLink) { return fetchDom(nextPageLink.href); } @@ -34,13 +34,13 @@ async function getNextPage(): Promise<DocumentFragment> { function parseTags(element: HTMLElement): TagDetails { // Safari doesn't correctly parse links if they're loaded via AJAX #3899 - const {pathname: tagUrl} = new URL(select(['a[href*="/tree/"]', 'a[href*="/tag/"]'], element)!.href); + const {pathname: tagUrl} = new URL($(['a[href*="/tree/"]', 'a[href*="/tag/"]'], element)!.href); const tag = /\/(?:releases\/tag|tree)\/(.*)/.exec(tagUrl)![1]; return { element, tag, - commit: select('[href*="/commit/"]', element)!.textContent.trim(), + commit: $('[href*="/commit/"]', element)!.textContent.trim(), ...parseTag(decodeURIComponent(tag)), // `version`, `namespace` }; } @@ -89,7 +89,7 @@ async function init(): Promise<void> { // Look for tags in the current page and the next page const pages = [document, await getNextPage()]; await domLoaded; - const allTags = select.all(tagsSelector, pages).map(tag => parseTags(tag)); + const allTags = $$(tagsSelector, pages).map(tag => parseTags(tag)); for (const [index, container] of allTags.entries()) { const previousTag = getPreviousTag(index, allTags); @@ -97,7 +97,7 @@ async function init(): Promise<void> { continue; } - const lastLinks = select.all([ + const lastLinks = $$([ '.Link--muted[data-hovercard-type="commit"]', // Link to commit in release sidebar '.list-style-none > .d-inline-block:last-child', // Link to source tarball under release tag ], container.element); @@ -114,7 +114,7 @@ async function init(): Promise<void> { ); // The page of a tag without a release still uses the old layout #5037 - if (pageDetect.isEnterprise() || pageDetect.isTags() || (pageDetect.isSingleReleaseOrTag() && select.exists('.release'))) { + if (pageDetect.isEnterprise() || pageDetect.isTags() || (pageDetect.isSingleReleaseOrTag() && elementExists('.release'))) { lastLink.after( <li className={lastLink.className + ' rgh-changelog-link'}> {compareLink} diff --git a/source/features/tags-on-commits-list.tsx b/source/features/tags-on-commits-list.tsx index 1d21e56f..36f127bc 100644 --- a/source/features/tags-on-commits-list.tsx +++ b/source/features/tags-on-commits-list.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import cache from 'webext-storage-cache/legacy.js'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import {TagIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -88,7 +88,7 @@ async function getTags(lastCommit: string, after?: string): Promise<CommitTags> async function init(): Promise<void | false> { const cacheKey = `tags:${getRepo()!.nameWithOwner}`; - const commitsOnPage = select.all('.js-commits-list-item'); + const commitsOnPage = $$('.js-commits-list-item'); const lastCommitOnPage = getCommitHash(commitsOnPage.at(-1)!); let cached = await cache.get<Record<string, string[]>>(cacheKey) ?? {}; const commitsWithNoTags = []; @@ -105,7 +105,7 @@ async function init(): Promise<void | false> { // There was no tags for this commit, save that info to the cache commitsWithNoTags.push(targetCommit); } else if (targetTags.length > 0) { - const commitMeta = select('.flex-auto .d-flex.mt-1', commit)!; + const commitMeta = $('.flex-auto .d-flex.mt-1', commit)!; commitMeta.classList.add('flex-wrap'); commitMeta.append( <span> diff --git a/source/features/toggle-files-button.tsx b/source/features/toggle-files-button.tsx index 40c8ee39..7f6ffd9c 100644 --- a/source/features/toggle-files-button.tsx +++ b/source/features/toggle-files-button.tsx @@ -1,5 +1,5 @@ import './toggle-files-button.css'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {CachedValue} from 'webext-storage-cache'; import React from 'dom-chef'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -14,7 +14,7 @@ const wereFilesHidden = new CachedValue<boolean>('files-hidden'); const toggleButtonClass = 'rgh-toggle-files'; function addButton(filesBox: HTMLElement): void { - select('ul:has(.octicon-history)', filesBox)?.append( + $('ul:has(.octicon-history)', filesBox)?.append( <button type="button" className={`btn-octicon ${toggleButtonClass}`} @@ -31,7 +31,7 @@ type Targets = { }; function getTargets(): Targets { - const fileList = select('[aria-labelledby="files"]')!; + const fileList = $('[aria-labelledby="files"]')!; const buttonWrapper = fileList.nextElementSibling!; return {fileList, buttonWrapper}; } diff --git a/source/features/unfinished-comments.tsx b/source/features/unfinished-comments.tsx index 2e824ec1..b0b14177 100644 --- a/source/features/unfinished-comments.tsx +++ b/source/features/unfinished-comments.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$$} from 'select-dom'; import delegate from 'delegate-it'; import * as pageDetect from 'github-url-detection'; @@ -10,7 +10,7 @@ let submitting: number | undefined; function hasDraftComments(): boolean { // `[disabled]` excludes the PR description field that `wait-for-checks` disables while it waits // `[id^="convert-to-issue-body"]` excludes the hidden pre-filled textareas created when opening the dropdown menu of review comments - return select.all('textarea:not([disabled], [id^="convert-to-issue-body"])').some(textarea => + return $$('textarea:not([disabled], [id^="convert-to-issue-body"])').some(textarea => textarea.value !== textarea.textContent, // Exclude comments being edited but not yet changed (and empty comment fields) ); } diff --git a/source/features/unwrap-unnecessary-dropdowns.tsx b/source/features/unwrap-unnecessary-dropdowns.tsx index 690f5c4b..26d445a8 100644 --- a/source/features/unwrap-unnecessary-dropdowns.tsx +++ b/source/features/unwrap-unnecessary-dropdowns.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -13,7 +13,7 @@ function replaceDropdownInPlace(dropdown: Element, form: Element): void { async function unwrapNotifications(): Promise<void | false> { await elementReady('.js-check-all-container > :first-child'); // Ensure the entire dropdown has loaded - const forms = select.all('[action="/notifications/beta/update_view_preference"]'); + const forms = $$('[action="/notifications/beta/update_view_preference"]'); if (forms.length === 0) { return false; } @@ -23,14 +23,14 @@ async function unwrapNotifications(): Promise<void | false> { } const dropdown = forms[0].closest('details')!; - const currentView = select('summary i', dropdown)!.nextSibling!.textContent.trim(); + const currentView = $('summary i', dropdown)!.nextSibling!.textContent.trim(); const desiredForm = currentView === 'Date' ? forms[0] : forms[1]; // Replace dropdown replaceDropdownInPlace(dropdown, desiredForm); // Fix button’s style - const button = select('[type="submit"]', desiredForm)!; + const button = $('[type="submit"]', desiredForm)!; button.className = 'btn'; button.textContent = `Group by ${button.textContent.toLowerCase()}`; } diff --git a/source/features/update-pr-from-base-branch.tsx b/source/features/update-pr-from-base-branch.tsx index e6f1eca5..388861c7 100644 --- a/source/features/update-pr-from-base-branch.tsx +++ b/source/features/update-pr-from-base-branch.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -56,7 +56,7 @@ function createButton(): JSX.Element { } async function addButton(mergeBar: Element): Promise<void> { - if (!select.exists(canMerge) || select.exists(canNativelyUpdate)) { + if (!elementExists(canMerge) || elementExists(canNativelyUpdate)) { return; } @@ -104,7 +104,7 @@ void features.add(import.meta.url, { ], exclude: [ pageDetect.isClosedPR, - () => select('.head-ref')!.title === 'This repository has been deleted', + () => $('.head-ref')!.title === 'This repository has been deleted', ], awaitDomReady: true, // DOM-based exclusions init, diff --git a/source/features/use-first-commit-message-for-new-prs.tsx b/source/features/use-first-commit-message-for-new-prs.tsx index 7f263979..6dff0a3f 100644 --- a/source/features/use-first-commit-message-for-new-prs.tsx +++ b/source/features/use-first-commit-message-for-new-prs.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; import * as textFieldEdit from 'text-field-edit'; @@ -24,8 +24,8 @@ function interpretNode(node: ChildNode): string | void { } function getFirstCommit(): {title: string; body: string | undefined} { - const titleParts = select('.js-commits-list-item:first-child p')!.childNodes; - const body = select('.js-commits-list-item:first-child .Details-content--hidden pre') + const titleParts = $('.js-commits-list-item:first-child p')!.childNodes; + const body = $('.js-commits-list-item:first-child .Details-content--hidden pre') ?.textContent.trim() ?? undefined; const title = [...titleParts] @@ -40,21 +40,21 @@ async function init(): Promise<void | false> { const requestedContent = new URL(location.href).searchParams; const commitCountIcon = await elementReady('div.Box.mb-3 .octicon-git-commit'); const commitCount = commitCountIcon?.nextElementSibling; - if (!commitCount || looseParseInt(commitCount) < 2 || !select.exists('#new_pull_request')) { + if (!commitCount || looseParseInt(commitCount) < 2 || !elementExists('#new_pull_request')) { return false; } const firstCommit = getFirstCommit(); if (!requestedContent.has('pull_request[title]')) { textFieldEdit.set( - select('.discussion-topic-header input')!, + $('.discussion-topic-header input')!, firstCommit.title, ); } if (firstCommit.body && !requestedContent.has('pull_request[body]')) { textFieldEdit.insert( - select('#new_pull_request textarea[aria-label="Comment body"]')!, + $('#new_pull_request textarea[aria-label="Comment body"]')!, firstCommit.body, ); } diff --git a/source/features/useful-not-found-page.tsx b/source/features/useful-not-found-page.tsx index 122450cd..4cfa35fd 100644 --- a/source/features/useful-not-found-page.tsx +++ b/source/features/useful-not-found-page.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import onetime from 'onetime'; import elementReady from 'element-ready'; import * as pageDetect from 'github-url-detection'; @@ -114,7 +114,7 @@ async function showMissingPart(): Promise<void> { .reverse() // Restore order .flatMap((link, i) => [i > 0 && ' / ', link]); // Add separators - select('main > :first-child, #parallax_illustration')!.after( + $('main > :first-child, #parallax_illustration')!.after( <h2 className="container mt-4 text-center">{breadcrumbs}</h2>, ); } @@ -125,7 +125,7 @@ async function showDefaultBranchLink(): Promise<void> { return; } - select('main > .container-lg')!.before( + $('main > .container-lg')!.before( <p className="container mt-4 text-center"> <a href={urlToFileOnDefaultBranch}>This {getType()}</a> exists on the default branch. </p>, @@ -172,7 +172,7 @@ async function getGitObjectHistoryLink(): Promise<HTMLElement | undefined> { async function showGitObjectHistory(): Promise<void> { const link = await getGitObjectHistoryLink(); if (link) { - select('main > .container-lg')!.before(link); + $('main > .container-lg')!.before(link); } } diff --git a/source/features/user-local-time.tsx b/source/features/user-local-time.tsx index 070e1cc6..3b64e0b3 100644 --- a/source/features/user-local-time.tsx +++ b/source/features/user-local-time.tsx @@ -4,7 +4,7 @@ import './user-local-time.css'; import React from 'dom-chef'; import {CachedFunction} from 'webext-storage-cache'; import delay from 'delay'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import {ClockIcon} from '@primer/octicons-react'; import features from '../feature-manager.js'; @@ -108,17 +108,17 @@ async function display({ async function insertUserLocalTime(hovercardContainer: Element): Promise<void> { const hovercard = hovercardContainer.closest('div.Popover-message')!; - if (!select.exists('[data-hydro-view*="user-hovercard-hover"]', hovercard)) { + if (!elementExists('[data-hydro-view*="user-hovercard-hover"]', hovercard)) { // It's not the hovercard type we expect return; } - if (select.exists('profile-timezone', hovercard)) { + if (elementExists('profile-timezone', hovercard)) { // Native time already present return; } - const login = select('a.Link--primary', hovercard)?.pathname.slice(1); + const login = $('a.Link--primary', hovercard)?.pathname.slice(1); if (!login || login === getUsername()) { return; } diff --git a/source/features/vertical-front-matter.tsx b/source/features/vertical-front-matter.tsx index f26ab3a0..e2427ac3 100644 --- a/source/features/vertical-front-matter.tsx +++ b/source/features/vertical-front-matter.tsx @@ -1,6 +1,6 @@ import './vertical-front-matter.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import elementReady from 'element-ready'; @@ -15,12 +15,12 @@ async function init(): Promise<false | void> { return false; } - const headers = select.all(':scope > thead th', table); + const headers = $$(':scope > thead th', table); if (headers.length <= 4) { return false; } - const rows = select.all(':scope > tbody > tr', table); + const rows = $$(':scope > tbody > tr', table); if (rows.length !== 1 || headers.length !== rows[0].childElementCount) { return false; } diff --git a/source/features/view-last-pr-deployment.tsx b/source/features/view-last-pr-deployment.tsx index c70b3991..17f3ff91 100644 --- a/source/features/view-last-pr-deployment.tsx +++ b/source/features/view-last-pr-deployment.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {lastElement} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import {RocketIcon} from '@primer/octicons-react'; @@ -7,7 +7,7 @@ import features from '../feature-manager.js'; import observe from '../helpers/selector-observer.js'; function addLink(header: HTMLElement): void { - const lastDeployment = select.last('.js-timeline-item a[title="Deployment has completed"]'); + const lastDeployment = lastElement('.js-timeline-item a[title="Deployment has completed"]'); if (!lastDeployment) { return; } diff --git a/source/features/wait-for-attachments.tsx b/source/features/wait-for-attachments.tsx index 49d1e403..5a78c98a 100644 --- a/source/features/wait-for-attachments.tsx +++ b/source/features/wait-for-attachments.tsx @@ -2,7 +2,7 @@ // `textarea[data-required-trimmed]` conflicts with this behavior by overriding the validity states in the meantime. // `button[data-disable-invalid]` are automatically disabled while the form is invalid, but some buttons don't have it. -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -13,13 +13,13 @@ const attributeBackup = 'data-rgh-required-trimmed'; function toggleSubmitButtons({target, type}: Event): void { const fileAttachment = target as HTMLElement; - for (const button of select.all('.btn-primary[type="submit"]:not([data-disable-invalid])', fileAttachment.closest('form')!)) { + for (const button of $$('.btn-primary[type="submit"]:not([data-disable-invalid])', fileAttachment.closest('form')!)) { button.dataset.disableInvalid = ''; } // Temporarily disable `data-required-trimmed` so that it doesn't conflict with the desired behavior. // The complex selector ensures that we don't add the attribute to fields that never had it in the first place. - const textarea = select(`[${attribute}], [${attributeBackup}]`, fileAttachment)!; + const textarea = $(`[${attribute}], [${attributeBackup}]`, fileAttachment)!; if (textarea) { if (type === 'upload:setup') { textarea.setAttribute(attributeBackup, textarea.getAttribute(attribute)!); diff --git a/source/features/wait-for-checks.tsx b/source/features/wait-for-checks.tsx index 0e067063..1a28b54d 100644 --- a/source/features/wait-for-checks.tsx +++ b/source/features/wait-for-checks.tsx @@ -1,6 +1,6 @@ import './wait-for-checks.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import onetime from 'onetime'; import {InfoIcon} from '@primer/octicons-react'; import * as pageDetect from 'github-url-detection'; @@ -36,7 +36,7 @@ const generateCheckbox = onetime(() => ( )); function getCheckbox(): HTMLInputElement | undefined { - return select('input[name="rgh-pr-check-waiter"]'); + return $('input[name="rgh-pr-check-waiter"]'); } // Only show the checkbox if the last commit doesn't have a green or red CI icon @@ -46,10 +46,10 @@ function showCheckboxIfNecessary(): void { const isNecessary = lastCommitStatus === prCiStatus.PENDING // If the latest commit is missing an icon, add the checkbox as long as there's at least one CI icon on the page (including `ci-link`) - || (lastCommitStatus === false && select.exists(prCommitStatusIcon)); + || (lastCommitStatus === false && elementExists(prCommitStatusIcon)); if (!checkbox && isNecessary) { - select('.js-merge-form .select-menu')?.append(generateCheckbox()); + $('.js-merge-form .select-menu')?.append(generateCheckbox()); } else if (checkbox && !isNecessary) { checkbox.parentElement!.remove(); } @@ -58,7 +58,7 @@ function showCheckboxIfNecessary(): void { let waiting: symbol | undefined; function disableForm(disabled = true): void { - for (const field of select.all(` + for (const field of $$(` textarea[name="commit_message"], input[name="commit_title"], input[name="rgh-pr-check-waiter"], @@ -196,7 +196,7 @@ void features.add(import.meta.url, { userCanLikelyMergePR, pageDetect.isOpenPR, // The repo has enabled Actions - () => select.exists(actionsTab), + () => elementExists(actionsTab), ], include: [ pageDetect.isPRConversation, diff --git a/source/features/warn-pr-from-master.tsx b/source/features/warn-pr-from-master.tsx index c47108c4..0b15080d 100644 --- a/source/features/warn-pr-from-master.tsx +++ b/source/features/warn-pr-from-master.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import * as pageDetect from 'github-url-detection'; import features from '../feature-manager.js'; @@ -8,8 +8,8 @@ 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)!; + if (elementExists('.is-cross-repo')) { + const forkedRepository = getRepo($('[title^="head: "]')!.textContent)!; defaultBranch = await defaultBranchOfRepo.get(forkedRepository); } else { defaultBranch = await getDefaultBranch(); @@ -20,7 +20,7 @@ async function init(): Promise<false | void> { return false; } - select('.js-compare-pr')!.before( + $('.js-compare-pr')!.before( <div className="flash flash-error my-3"> <strong>Note:</strong> Creating a PR from the default branch is an <a href="https://blog.jasonmeridth.com/posts/do-not-issue-pull-requests-from-your-master-branch/" target="_blank" rel="noopener noreferrer">anti-pattern</a>. </div>, diff --git a/source/features/warning-for-disallow-edits.tsx b/source/features/warning-for-disallow-edits.tsx index 6aba8e1b..5d598af5 100644 --- a/source/features/warning-for-disallow-edits.tsx +++ b/source/features/warning-for-disallow-edits.tsx @@ -1,6 +1,6 @@ import './warning-for-disallow-edits.css'; import React from 'dom-chef'; -import select from 'select-dom'; +import {$} from 'select-dom'; import onetime from 'onetime'; import * as pageDetect from 'github-url-detection'; import delegate, {DelegateEvent} from 'delegate-it'; @@ -33,7 +33,7 @@ function toggleHandler(event: DelegateEvent<Event, HTMLInputElement>): void { } function init(signal: AbortSignal): void | false { - const checkbox = select('input[name="collab_privs"]'); + const checkbox = $('input[name="collab_privs"]'); if (!checkbox) { return false; } diff --git a/source/github-events/on-field-keydown.tsx b/source/github-events/on-field-keydown.tsx index ae21b164..fc8e491e 100644 --- a/source/github-events/on-field-keydown.tsx +++ b/source/github-events/on-field-keydown.tsx @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {elementExists} from 'select-dom'; import delegate, {DelegateEventHandler} from 'delegate-it'; type DelegateFieldEvent = DelegateEventHandler<KeyboardEvent, HTMLTextAreaElement>; @@ -8,7 +8,7 @@ function onFieldKeydown(selector: string, callback: DelegateFieldEvent, signal: const field = event.delegateTarget; // The suggester is GitHub’s autocomplete dropdown - if (select.exists('.suggester', field.form!) || event.isComposing) { + if (elementExists('.suggester', field.form!) || event.isComposing) { return; } diff --git a/source/github-helpers/dom-formatters.tsx b/source/github-helpers/dom-formatters.tsx index d764498b..ca19d5f7 100644 --- a/source/github-helpers/dom-formatters.tsx +++ b/source/github-helpers/dom-formatters.tsx @@ -1,5 +1,5 @@ import React from 'dom-chef'; -import select from 'select-dom'; +import {$$, elementExists} from 'select-dom'; import zipTextNodes from 'zip-text-nodes'; import {applyToLink} from 'shorten-repo-url'; import linkifyURLsCore from 'linkify-urls'; @@ -68,8 +68,8 @@ export function linkifyURLs(element: Element): Element[] | void { return; } - if (select.exists(linkifiedURLSelector, element)) { - return select.all(linkifiedURLSelector, element); + if (elementExists(linkifiedURLSelector, element)) { + return $$(linkifiedURLSelector, element); } const linkified = linkifyURLsCore(element.textContent, { diff --git a/source/github-helpers/get-current-git-ref.ts b/source/github-helpers/get-current-git-ref.ts index b2d1d1c9..65b9feff 100644 --- a/source/github-helpers/get-current-git-ref.ts +++ b/source/github-helpers/get-current-git-ref.ts @@ -1,5 +1,5 @@ import {isRepoCommitList} from 'github-url-detection'; -import select from 'select-dom'; +import {$} from 'select-dom'; import {extractCurrentBranchFromBranchPicker} from './index.js'; import {branchSelector} from './selectors.js'; @@ -11,7 +11,7 @@ const titleWithGitRef = / at (?<branch>[.\w-/]+)( · [\w-]+\/[\w-]+)?$/i; export default function getCurrentGitRef(): string | undefined { // Note: This is not in the <head> so it's only available on AJAXed loads. // It appears on every Code page except `commits` on folders/files - const picker = select(branchSelector); + const picker = $(branchSelector); const refViaPicker = picker && extractCurrentBranchFromBranchPicker(picker); if (refViaPicker) { return refViaPicker; @@ -49,7 +49,7 @@ export function getGitRef(pathname: string, title: string): string | undefined { // In <head>, but not reliable https://github.com/refined-github/refined-github/assets/1402241/50357d94-505f-48dc-bd54-74e86b19d642 function getCurrentBranchFromFeed(): string | undefined { - const feedLink = isRepoCommitList() && select('link[type="application/atom+xml"]'); + const feedLink = isRepoCommitList() && $('link[type="application/atom+xml"]'); if (!feedLink) { // Do not throw errors, the element may be missing after AJAX navigation even if on the right page return; diff --git a/source/github-helpers/get-tab-count.ts b/source/github-helpers/get-tab-count.ts index c7658395..af7a18a4 100644 --- a/source/github-helpers/get-tab-count.ts +++ b/source/github-helpers/get-tab-count.ts @@ -1,8 +1,8 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import oneMutation from 'one-mutation'; export default async function getTabCount(tab: Element): Promise<number> { - const counter = select('.Counter, .num', tab); + const counter = $('.Counter, .num', tab); if (!counter) { // GitHub might have already dropped the counter, which means it's 0 return 0; diff --git a/source/github-helpers/get-user-avatar.ts b/source/github-helpers/get-user-avatar.ts index 510066e2..e61b2e81 100644 --- a/source/github-helpers/get-user-avatar.ts +++ b/source/github-helpers/get-user-avatar.ts @@ -1,11 +1,11 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import * as pageDetect from 'github-url-detection'; export default function getUserAvatar(username: string, size: number): string | void { const cleanName = username.replace('[bot]', ''); // Find image on page. Saves a request and a redirect + add support for bots - const existingAvatar = select(`img[alt="@${cleanName}"]`); + const existingAvatar = $(`img[alt="@${cleanName}"]`); if (existingAvatar) { return existingAvatar.src; } diff --git a/source/github-helpers/index.ts b/source/github-helpers/index.ts index b9e4e673..051d4b02 100644 --- a/source/github-helpers/index.ts +++ b/source/github-helpers/index.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, elementExists} from 'select-dom'; import onetime from 'onetime'; import elementReady from 'element-ready'; import compareVersions from 'tiny-version-compare'; @@ -37,7 +37,7 @@ export function buildRepoURL<S extends string>(...pathParts: RequireAtLeastOne<A } export function getForkedRepo(): string | undefined { - return select('meta[name="octolytics-dimension-repository_parent_nwo"]')?.content; + return $('meta[name="octolytics-dimension-repository_parent_nwo"]')?.content; } export function parseTag(tag: string): {version: string; namespace: string} { @@ -92,7 +92,7 @@ export const isPermalink = mem(async () => { } // Awaiting only the branch selector means it resolves early even if the icon tag doesn't exist, whereas awaiting the icon tag would wait for the DOM ready event before resolving. - return select.exists( + return elementExists( '.octicon-tag', // Tags have an icon await elementReady(branchSelector), ); @@ -118,7 +118,7 @@ export async function isArchivedRepoAsync(): Promise<boolean> { return pageDetect.isArchivedRepo(); } -export const userCanLikelyMergePR = (): boolean => select.exists('.discussion-sidebar-item .octicon-lock'); +export const userCanLikelyMergePR = (): boolean => elementExists('.discussion-sidebar-item .octicon-lock'); export const cacheByRepo = (): string => getRepo()!.nameWithOwner; diff --git a/source/github-helpers/load-details-menu.ts b/source/github-helpers/load-details-menu.ts index f3db3d42..13158676 100644 --- a/source/github-helpers/load-details-menu.ts +++ b/source/github-helpers/load-details-menu.ts @@ -1,8 +1,8 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import oneEvent from 'one-event'; export default async function loadDetailsMenu(detailsMenu: HTMLElement): Promise<void> { - const fragment = select('.js-comment-header-actions-deferred-include-fragment', detailsMenu); + const fragment = $('.js-comment-header-actions-deferred-include-fragment', detailsMenu); if (!fragment) { return; } diff --git a/source/github-helpers/pr-branches.ts b/source/github-helpers/pr-branches.ts index a2105bc8..38747679 100644 --- a/source/github-helpers/pr-branches.ts +++ b/source/github-helpers/pr-branches.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; export type PrReference = { /** @example fregante/mem:main */ @@ -59,10 +59,10 @@ function parseReference(referenceElement: HTMLElement): PrReference { export function getBranches(): {base: PrReference; head: PrReference} { return { get base() { - return parseReference(select('.base-ref')!); + return parseReference($('.base-ref')!); }, get head() { - return parseReference(select('.head-ref')!); + return parseReference($('.head-ref')!); }, }; } diff --git a/source/github-helpers/pr-ci-status.ts b/source/github-helpers/pr-ci-status.ts index d1c4a970..31e074fc 100644 --- a/source/github-helpers/pr-ci-status.ts +++ b/source/github-helpers/pr-ci-status.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, lastElement} from 'select-dom'; import api from './api.js'; import {prCommit, prCommitStatusIcon} from './selectors.js'; @@ -9,13 +9,13 @@ export const PENDING = Symbol('Pending'); export type CommitStatus = false | typeof SUCCESS | typeof FAILURE | typeof PENDING; export function getLastCommitReference(): string { - return select.last(`${prCommit} code`)!.textContent; + return lastElement(`${prCommit} code`)!.textContent; } export function getLastCommitStatus(): CommitStatus { // Select the last commit first, THEN pick the icon, otherwise it might pick non-last commit while the CI is starting up - const lastCommit = select.last(prCommit)!; - const lastCommitStatusIcon = select(prCommitStatusIcon, lastCommit); + const lastCommit = lastElement(prCommit)!; + const lastCommitStatusIcon = $(prCommitStatusIcon, lastCommit); // Some commits don't have a CI status icon at all if (lastCommitStatusIcon) { diff --git a/source/helpers/attach-element.ts b/source/helpers/attach-element.ts index 031925cf..0703febc 100644 --- a/source/helpers/attach-element.ts +++ b/source/helpers/attach-element.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$, $$, elementExists} from 'select-dom'; import {RequireAtLeastOne} from 'type-fest'; import {isDefined} from 'ts-extras'; @@ -29,7 +29,7 @@ export default function attachElement<NewElement extends Element>( forEach, allowMissingAnchor = false, }: Attachment<NewElement>): NewElement[] { - const anchorElement = typeof anchor === 'string' ? select(anchor) : anchor; + const anchorElement = typeof anchor === 'string' ? $(anchor) : anchor; if (!anchorElement) { if (allowMissingAnchor) { return []; @@ -38,7 +38,7 @@ export default function attachElement<NewElement extends Element>( throw new Error('Element not found'); } - if (select.exists('.' + className, anchorElement.parentElement ?? anchorElement)) { + if (elementExists('.' + className, anchorElement.parentElement ?? anchorElement)) { return []; } @@ -68,6 +68,6 @@ export function attachElements<NewElement extends Element>(anchors: string | str className = 'rgh-' + getCallerID(), ...options }: Attachment<NewElement>): NewElement[] { - return select.all(`:is(${String(anchors)}):not(.${className})`) + return $$(`:is(${String(anchors)}):not(.${className})`) .flatMap(anchor => attachElement(anchor, {...options, className})); } diff --git a/source/helpers/bisect.tsx b/source/helpers/bisect.tsx index e9bc47aa..902c4a2b 100644 --- a/source/helpers/bisect.tsx +++ b/source/helpers/bisect.tsx @@ -1,6 +1,6 @@ import React from 'dom-chef'; import {CachedValue} from 'webext-storage-cache'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import elementReady from 'element-ready'; import pluralize from './pluralize.js'; @@ -58,7 +58,7 @@ async function onEndButtonClick(): Promise<void> { } function createMessageBox(message: Element | string, extraButtons?: Element): void { - select('#rgh-bisect-dialog')?.remove(); + $('#rgh-bisect-dialog')?.remove(); document.body.append( <div id="rgh-bisect-dialog" className="Box p-3"> <p>{message}</p> @@ -97,7 +97,7 @@ export default async function bisectFeatures(): Promise<Record<string, boolean> // Enable "Yes"/"No" buttons once the page is done loading window.addEventListener('load', () => { - for (const button of select.all('#rgh-bisect-dialog [aria-disabled]')) { + for (const button of $$('#rgh-bisect-dialog [aria-disabled]')) { button.removeAttribute('aria-disabled'); } }); diff --git a/source/helpers/click-all.ts b/source/helpers/click-all.ts index 9d38adff..a9fb4f64 100644 --- a/source/helpers/click-all.ts +++ b/source/helpers/click-all.ts @@ -1,5 +1,5 @@ import mem from 'mem'; -import select from 'select-dom'; +import {$$} from 'select-dom'; import {DelegateEvent} from 'delegate-it'; import preserveScroll from './preserve-scroll.js'; @@ -17,7 +17,7 @@ export default mem((selector: string | ((clickedItem: HTMLElement) => string)): }); function clickAllExcept(elementsToClick: string, except: HTMLElement): void { - for (const item of select.all(elementsToClick)) { + for (const item of $$(elementsToClick)) { if (item !== except) { item.click(); } diff --git a/source/helpers/dom-utils.ts b/source/helpers/dom-utils.ts index ce301d5b..415436ac 100644 --- a/source/helpers/dom-utils.ts +++ b/source/helpers/dom-utils.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import {setFetch} from 'push-form'; // Nodes may be exactly `null` import {type Nullable} from 'vitest'; @@ -35,11 +35,11 @@ if (window.content?.fetch) { */ export const appendBefore = (parent: string | Element, before: string, child: Element): void => { if (typeof parent === 'string') { - parent = select(parent)!; + parent = $(parent)!; } // Select direct children only - const beforeElement = select(`:scope > :is(${before})`, parent); + const beforeElement = $(`:scope > :is(${before})`, parent); if (beforeElement) { beforeElement.before(child); } else { diff --git a/source/helpers/on-element-replacement.ts b/source/helpers/on-element-replacement.ts index 27c555e4..f5110205 100644 --- a/source/helpers/on-element-replacement.ts +++ b/source/helpers/on-element-replacement.ts @@ -1,4 +1,4 @@ -import select from 'select-dom'; +import {$} from 'select-dom'; import onElementRemoval from './on-element-removal.js'; @@ -18,7 +18,7 @@ export default async function onElementReplacement( return; } - let trackedElement = select(selector); + let trackedElement = $(selector); if (!trackedElement) { throw new Error('The element can’t be found'); } @@ -34,7 +34,7 @@ export default async function onElementReplacement( return; } - trackedElement = select(selector); + trackedElement = $(selector); if (trackedElement) { callback(trackedElement); } diff --git a/source/options.tsx b/source/options.tsx index 3c5e842f..5c47aba4 100644 --- a/source/options.tsx +++ b/source/options.tsx @@ -2,7 +2,7 @@ import 'webext-base-css/webext-base.css'; import './options.css'; import React from 'dom-chef'; import domify from 'doma'; -import select from 'select-dom'; +import {$, $$} from 'select-dom'; import fitTextarea from 'fit-textarea'; import prettyBytes from 'pretty-bytes'; import {assertError} from 'ts-extras'; @@ -34,7 +34,7 @@ type Status = { const {version} = browser.runtime.getManifest(); function reportStatus({tokenType, error, text, scopes}: Status): void { - const tokenStatus = select('#validation')!; + const tokenStatus = $('#validation')!; tokenStatus.textContent = text ?? ''; if (error) { tokenStatus.dataset.validation = 'invalid'; @@ -43,11 +43,11 @@ function reportStatus({tokenType, error, text, scopes}: Status): void { } // Toggle the ulists by token type (default to classic) - for (const ulist of select.all('[data-token-type]')) { + for (const ulist of $$('[data-token-type]')) { ulist.style.display = ulist.dataset.tokenType === tokenType ? '' : 'none'; } - for (const scope of select.all('[data-scope]')) { + for (const scope of $$('[data-scope]')) { if (scopes) { scope.dataset.validation = scopes.includes(scope.dataset.scope!) ? 'valid' : 'invalid'; } else { @@ -57,7 +57,7 @@ function reportStatus({tokenType, error, text, scopes}: Status): void { } async function getTokenScopes(personalToken: string): Promise<string[]> { - const tokenLink = select('a#personal-token-link')!; + const tokenLink = $('a#personal-token-link')!; const url = tokenLink.host === 'github.com' ? 'https://api.github.com/' : `${tokenLink.origin}/api/v3/`; @@ -91,14 +91,14 @@ async function getTokenScopes(personalToken: string): Promise<string[]> { } function expandTokenSection(): void { - select('details#token')!.open = true; + $('details#token')!.open = true; } async function updateStorageUsage(area: 'sync' | 'local'): Promise<void> { const storage = browser.storage[area]; const used = await getStorageBytesInUse(area); const available = storage.QUOTA_BYTES - used; - for (const output of select.all(`.storage-${area}`)) { + for (const output of $$(`.storage-${area}`)) { output.textContent = available < 1000 ? 'FULL!' : (available < 100_000 @@ -108,7 +108,7 @@ async function updateStorageUsage(area: 'sync' | 'local'): Promise<void> { } async function validateToken(): Promise<void> { - const tokenField = select('input[name="personalToken"]')!; + const tokenField = $('input[name="personalToken"]')!; const tokenType = tokenField.value.startsWith('github_pat_') ? 'fine_grained' : 'classic'; reportStatus({tokenType}); @@ -135,9 +135,9 @@ async function validateToken(): Promise<void> { } function moveDisabledFeaturesToTop(): void { - const container = select('.js-features')!; + const container = $('.js-features')!; - for (const unchecked of select.all('.feature-checkbox:not(:checked)', container).reverse()) { + for (const unchecked of $$('.feature-checkbox:not(:checked)', container).reverse()) { // .reverse() needed to preserve alphabetical order while prepending container.prepend(unchecked.closest('.feature')!); } @@ -180,7 +180,7 @@ async function findFeatureHandler(event: Event): Promise<void> { button.disabled = false; }, 10_000); - select('#find-feature-message')!.hidden = false; + $('#find-feature-message')!.hidden = false; } function summaryHandler(event: DelegateEvent<MouseEvent>): void { @@ -190,7 +190,7 @@ function summaryHandler(event: DelegateEvent<MouseEvent>): void { event.preventDefault(); if (event.altKey) { - for (const screenshotLink of select.all('.screenshot-link')) { + for (const screenshotLink of $$('.screenshot-link')) { toggleScreenshot(screenshotLink.parentElement!); } } else { @@ -213,7 +213,7 @@ function featuresFilterHandler(event: Event): void { .replaceAll(/\W/g, ' ') .split(/\s+/) .filter(Boolean); // Ignore empty strings - for (const feature of select.all('.feature')) { + for (const feature of $$('.feature')) { feature.hidden = !keywords.every(word => feature.dataset.text!.includes(word)); } } @@ -222,7 +222,7 @@ function focusFirstField({delegateTarget: section}: DelegateEvent<Event, HTMLDet // @ts-expect-error No Firefox support https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded (section.scrollIntoViewIfNeeded ?? section.scrollIntoView).call(section); if (section.open) { - const field = select('input, textarea', section); + const field = $('input, textarea', section); if (field) { field.focus(); if (field instanceof HTMLTextAreaElement) { @@ -236,10 +236,10 @@ function focusFirstField({delegateTarget: section}: DelegateEvent<Event, HTMLDet async function markLocalHotfixes(): Promise<void> { for (const [feature, relatedIssue] of await getLocalHotfixes()) { if (importedFeatures.includes(feature)) { - const input = select<HTMLInputElement>('#' + feature)!; + const input = $<HTMLInputElement>('#' + feature)!; input.disabled = true; input.removeAttribute('name'); - select(`.feature-name[for="${feature}"]`)!.after( + $(`.feature-name[for="${feature}"]`)!.after( <span className="hotfix-notice"> (Disabled due to {createRghIssueLink(relatedIssue)})</span>, ); } @@ -251,12 +251,12 @@ function updateRateLink(): void { return; } - select('a#rate-link')!.href = isFirefox() ? 'https://addons.mozilla.org/en-US/firefox/addon/refined-github-' : 'https://apps.apple.com/app/id1519867270?action=write-review'; + $('a#rate-link')!.href = isFirefox() ? 'https://addons.mozilla.org/en-US/firefox/addon/refined-github-' : 'https://apps.apple.com/app/id1519867270?action=write-review'; } async function showStoredCssHotfixes(): Promise<void> { const cachedCSS = await styleHotfixes.getCached(version); - select('#hotfixes-field')!.textContent + $('#hotfixes-field')!.textContent = isDevelopmentVersion() ? 'Hotfixes are not applied in the development version.' : isEnterprise() @@ -266,30 +266,30 @@ async function showStoredCssHotfixes(): Promise<void> { function enableToggleAll({currentTarget: button}: Event): void { (button as HTMLButtonElement).parentElement!.remove(); - for (const ui of select.all('.toggle-all-features')) { + for (const ui of $$('.toggle-all-features')) { ui.hidden = false; } } function disableAllFeatures(): void { - for (const enabledFeature of select.all('.feature-checkbox:checked')) { + for (const enabledFeature of $$('.feature-checkbox:checked')) { enabledFeature.click(); } - select('details#features')!.open = true; + $('details#features')!.open = true; } function enableAllFeatures(): void { - for (const disabledFeature of select.all('.feature-checkbox:not(:checked)')) { + for (const disabledFeature of $$('.feature-checkbox:not(:checked)')) { disabledFeature.click(); } - select('details#features')!.open = true; + $('details#features')!.open = true; } async function generateDom(): Promise<void> { // Generate list - select('.js-features')!.append(...featuresMeta + $('.js-features')!.append(...featuresMeta .filter(feature => importedFeatures.includes(feature.id)) .map(feature => buildFeatureCheckbox(feature)), ); @@ -301,7 +301,7 @@ async function generateDom(): Promise<void> { await perDomainOptions.syncForm('form'); // Only now the form is ready, we can show it - select('#js-failed')!.remove(); + $('#js-failed')!.remove(); // Decorate list moveDisabledFeaturesToTop(); @@ -310,7 +310,7 @@ async function generateDom(): Promise<void> { void validateToken(); // Add feature count. CSS-only features are added approximately - select('.features-header')!.append(` (${featuresMeta.length + 25})`); + $('.features-header')!.append(` (${featuresMeta.length + 25})`); // Update rate link if necessary updateRateLink(); @@ -321,7 +321,7 @@ async function generateDom(): Promise<void> { // Hide non-applicable "Button link" section if (doesBrowserActionOpenOptions) { - select('#action')!.hidden = true; + $('#action')!.hidden = true; } // Show stored CSS hotfixes @@ -330,9 +330,9 @@ async function generateDom(): Promise<void> { function addEventListeners(): void { // Update domain-dependent page content when the domain is changed - select('.OptionsSyncPerDomain-picker select')?.addEventListener('change', ({currentTarget: dropdown}) => { + $('.OptionsSyncPerDomain-picker select')?.addEventListener('change', ({currentTarget: dropdown}) => { const host = (dropdown as HTMLSelectElement).value; - select('a#personal-token-link')!.host = host === 'default' ? 'github.com' : host; + $('a#personal-token-link')!.host = host === 'default' ? 'github.com' : host; // Delay validating to let options load first setTimeout(validateToken, 100); }); @@ -361,21 +361,21 @@ function addEventListeners(): void { delegate('details', 'toggle', focusFirstField, {capture: true}); // Filter feature list - select('#filter-features')!.addEventListener('input', featuresFilterHandler); + $('#filter-features')!.addEventListener('input', featuresFilterHandler); // Add cache clearer - select('#clear-cache')!.addEventListener('click', clearCacheHandler); + $('#clear-cache')!.addEventListener('click', clearCacheHandler); // Add bisect tool - select('#find-feature')!.addEventListener('click', findFeatureHandler); + $('#find-feature')!.addEventListener('click', findFeatureHandler); // Handle "Toggle all" buttons - select('#toggle-all-features')!.addEventListener('click', enableToggleAll); - select('#disable-all-features')!.addEventListener('click', disableAllFeatures); - select('#enable-all-features')!.addEventListener('click', enableAllFeatures); + $('#toggle-all-features')!.addEventListener('click', enableToggleAll); + $('#disable-all-features')!.addEventListener('click', disableAllFeatures); + $('#enable-all-features')!.addEventListener('click', enableAllFeatures); // Add token validation - select('[name="personalToken"]')!.addEventListener('input', validateToken); + $('[name="personalToken"]')!.addEventListener('input', validateToken); } async function init(): Promise<void> { |