Repository: refined-github/refined-github (ts)

Issue:
`sort-conversations-by-update-time` - Prepend sort instead of appending it
### Description

Instead of 

```
https://github.com/refined-github/refined-github/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc
```

it should produce

```
https://github.com/refined-github/refined-github/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen
```

because the cursor ends up at the end when editing the filter, so the sort filter is more of an annoyance

### Example URLs

https://github.com/refined-github/refined-github/issues

Pull request:
`sort-conversations-by-update-time` - Various improvements
- Closes #7415 

This PR:

- makes it easier to alter the query by prepending the sort instead of appending it
- excludes `&page=` links outside the pagination widget as well
- moves some exclusion filters into the selector


## Test URLs


Live link:

- https://github.com/refined-github/refined-github/pulls

Should become:

```diff
- https://github.com/refined-github/refined-github/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc
+ https://github.com/refined-github/refined-github/pulls?q=sort%3Aupdated-desc+is%3Apr+is%3Aopen
```

## Before

<img width="447" alt="Screenshot 2" src="https://github.com/refined-github/refined-github/assets/1402241/1bf7da59-dd64-43b8-a5c8-0c6b9606651c">


## After

<img width="447" alt="Screenshot 1" src="https://github.com/refined-github/refined-github/assets/1402241/51f4d046-2ee2-421b-8c18-1535ac5ec7e3">




Patch:
diff --git a/source/features/bugs-tab.tsx b/source/features/bugs-tab.tsx
index 515cee77..82486909 100644
--- a/source/features/bugs-tab.tsx
+++ b/source/features/bugs-tab.tsx
@@ -98,7 +98,7 @@ async function addBugsTab(): Promise<void | false> {
 	bugsCounter.title = '';
 
 	// Update Bugs’ link
-	bugsTab.href = SearchQuery.from(bugsTab).add(await getSearchQueryBugLabel()).href;
+	bugsTab.href = SearchQuery.from(bugsTab).append(await getSearchQueryBugLabel()).href;
 
 	// In case GitHub changes its layout again #4166
 	if (issuesTab.parentElement instanceof HTMLLIElement) {
diff --git a/source/features/sort-conversations-by-update-time.tsx b/source/features/sort-conversations-by-update-time.tsx
index 896e7865..5b2fbc13 100644
--- a/source/features/sort-conversations-by-update-time.tsx
+++ b/source/features/sort-conversations-by-update-time.tsx
@@ -5,6 +5,7 @@ import elementReady from 'element-ready';
 import features from '../feature-manager.js';
 import SearchQuery from '../github-helpers/search-query.js';
 import observe from '../helpers/selector-observer.js';
+import {linksToConversationLists} from '../github-helpers/selectors.js';
 
 /** Keep the original URL on the element so that `shorten-links` can use it reliably #5890 */
 export function saveOriginalHref(link: HTMLAnchorElement): void {
@@ -22,7 +23,7 @@ async function selectCurrentConversationFilter(): Promise<void> {
 }
 
 function updateLink(link: HTMLAnchorElement): void {
-	if (link.host !== location.host || link.closest('.pagination, .table-list-header-toggle')) {
+	if (link.host !== location.host) {
 		return;
 	}
 
@@ -32,7 +33,7 @@ function updateLink(link: HTMLAnchorElement): void {
 	if (pageDetect.isIssueOrPRList(link)) {
 		saveOriginalHref(link);
 
-		const newUrl = SearchQuery.from(link).add('sort:updated-desc').href;
+		const newUrl = SearchQuery.from(link).prepend('sort:updated-desc').href;
 
 		// Preserve relative attributes as such #5435
 		const isRelativeAttribute = link.getAttribute('href')!.startsWith('/');
@@ -46,25 +47,15 @@ function updateLink(link: HTMLAnchorElement): void {
 		// Projects use a different parameter name so don't use SearchQuery
 		const search = new URLSearchParams(link.search);
 		const query = search.get('query') ?? 'is:open'; // Default value query is missing
-		search.set('query', `${query} sort:updated-desc`);
+		search.set('query', `sort:updated-desc ${query}`);
 		link.search = search.toString();
 	}
 }
 
 function init(signal: AbortSignal): void {
-	// Get issues links that don't already have a specific sorting applied
+	// Get links that don't already have a specific sorting or pagination applied
 	observe(
-		`
-			a:is(
-				[href*="/issues"],
-				[href*="/pulls"],
-				[href*="/projects"],
-				[href*="/labels/"]
-			):not(
-				[href*="sort%3A"],
-				.issues-reset-query
-			)
-		`,
+		linksToConversationLists,
 		updateLink,
 		{signal},
 	);
@@ -76,6 +67,16 @@ void features.add(import.meta.url, {
 	include: [
 		pageDetect.isRepoIssueOrPRList,
 	],
-	deduplicate: 'has-rgh-inner',
 	init: selectCurrentConversationFilter,
 });
+
+/*
+
+Test URLs
+
+Live links, these should be altered to include the `sort:updated-desc` query parameter:
+
+- https://github.com/refined-github/refined-github/pulls
+- https://github.com/refined-github/refined-github/labels/bug
+
+*/
diff --git a/source/github-helpers/search-query.ts b/source/github-helpers/search-query.ts
index 3652d6a2..c710d23c 100644
--- a/source/github-helpers/search-query.ts
+++ b/source/github-helpers/search-query.ts
@@ -128,11 +128,16 @@ export default class SearchQuery {
 		return this;
 	}
 
-	add(...queryPartsToAdd: string[]): this {
+	append(...queryPartsToAdd: string[]): this {
 		this.queryParts.push(...queryPartsToAdd);
 		return this;
 	}
 
+	prepend(...queryPartsToAdd: string[]): this {
+		this.queryParts.unshift(...queryPartsToAdd);
+		return this;
+	}
+
 	includes(...searchStrings: string[]): boolean {
 		return this.getQueryParts().some(queryPart => searchStrings.includes(queryPart));
 	}
diff --git a/source/github-helpers/selectors.ts b/source/github-helpers/selectors.ts
index f6bd506a..25b8bc1c 100644
--- a/source/github-helpers/selectors.ts
+++ b/source/github-helpers/selectors.ts
@@ -108,3 +108,22 @@ export const codeSearchHeader_ = [
 	// Search not available when logged out :(
 	[0, 'https://github.com/search?q=repo%3Arefined-github%2Frefined-github&type=code'],
 ] satisfies UrlMatch[];
+
+export const linksToConversationLists = `
+	a:is(
+		[href*="/issues"],
+		[href*="/pulls"],
+		[href*="/projects"],
+		[href*="/labels/"]
+	):not(
+		[href*="sort%3A"],
+		[href*="page="],
+		.issues-reset-query,
+		.pagination *,
+		.table-list-header-toggle *
+	)
+`;
+export const linksToConversationLists_ = [
+	[6, 'https://github.com/fregante/iphone-inline-video/issues?q=cool+is%3Aissue+is%3Aopen+'],
+	[26, 'https://github.com/fregante/iphone-inline-video/issues?q=cool+is%3Aissue+is%3Aclosed'],
+] satisfies UrlMatch[];


Test patch:
diff --git a/source/github-helpers/search-query.test.ts b/source/github-helpers/search-query.test.ts
index 9c5707f9..8bd83411 100644
--- a/source/github-helpers/search-query.test.ts
+++ b/source/github-helpers/search-query.test.ts
@@ -44,12 +44,18 @@ test('.remove', () => {
 	assert.equal(query.get(), 'dog');
 });
 
-test('.add', () => {
+test('.append', () => {
 	const query = SearchQuery.from({q: 'is:pr birds everywhere'});
-	query.add('and', 'aliens');
+	query.append('and', 'aliens');
 	assert.equal(query.get(), 'is:pr birds everywhere and aliens');
 });
 
+test('.prepend', () => {
+	const query = SearchQuery.from({q: 'is:pr birds everywhere'});
+	query.prepend('sort:cool', 'exclude:chicken');
+	assert.equal(query.get(), 'sort:cool exclude:chicken is:pr birds everywhere');
+});
+
 test('.includes', () => {
 	const query = SearchQuery.from({q: 'label:nonsense'});
 	assert.isFalse(query.includes('nonsense'));
@@ -68,7 +74,7 @@ test('defaults', () => {
 
 test('deduplicate is:pr/issue', () => {
 	const query = SearchQuery.from({q: 'refined github is:pr'});
-	query.add('is:issue');
+	query.append('is:issue');
 
 	assert.isFalse(query.includes('is:pr'));
 	assert.isTrue(query.includes('is:issue'));