Skip to content

Commit 44d48a3

Browse files
committed
feat: Pin issue of current active tab to the top
1 parent bf77bd7 commit 44d48a3

File tree

9 files changed

+101
-13
lines changed

9 files changed

+101
-13
lines changed

src/components/issues/IssuesList.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import clsx from "clsx";
44
import { Fragment, useState } from "react";
55
import { FormattedMessage } from "react-intl";
6+
import useActiveRedmineTab from "../../hooks/useActiveRedmineTab";
67
import useIssuePriorities from "../../hooks/useIssuePriorities";
78
import useMyProjectRoles from "../../hooks/useMyProjectRoles";
89
import useMyProjects from "../../hooks/useMyProjects";
@@ -35,19 +36,27 @@ type PropTypes = {
3536
const IssuesList = ({ issues: rawIssues, issuePriorities, projectVersions, issuesData: { data: issuesData, setData: setIssuesData }, onSearchInProject }: PropTypes) => {
3637
const { settings } = useSettings();
3738

39+
const activeTab = useActiveRedmineTab();
40+
3841
const myUser = useMyUser();
3942
const projects = useMyProjects();
4043
const projectRoles = useMyProjectRoles([...new Set(rawIssues.map((i) => i.project.id))]);
41-
const groupedIssues = getGroupedIssues(getSortedIssues(rawIssues, settings.style.sortIssuesByPriority ? issuePriorities.data : [], issuesData), projectVersions?.data ?? {}, issuesData, settings);
44+
const groupedIssues = getGroupedIssues({
45+
issues: getSortedIssues(rawIssues, settings.style.sortIssuesByPriority ? issuePriorities.data : [], issuesData),
46+
projectVersions: projectVersions?.data ?? {},
47+
issuesData,
48+
settings,
49+
activeTabIssueId: activeTab?.data?.type === "issue" ? activeTab?.data?.id : undefined,
50+
});
4251

4352
const [createIssue, setCreateIssue] = useState<number | undefined>(undefined);
4453

4554
return (
4655
<>
47-
{groupedIssues.map(({ type, project: projectRef, versions, groups }) => {
56+
{groupedIssues.map(({ id, project: projectRef, versions, groups }) => {
4857
const project: TProject | TReference | undefined = projects.data?.find((p) => p.id === projectRef?.id) ?? projectRef;
4958
return (
50-
<Fragment key={type}>
59+
<Fragment key={id}>
5160
{project && (
5261
<div
5362
className={clsx("flex justify-between gap-x-2", {

src/hooks/useActiveRedmineTab.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { useEffect, useState } from "react";
2+
import useSettings from "./useSettings";
3+
4+
function useActiveRedmineTab() {
5+
const { settings } = useSettings();
6+
const [currentUrl, setCurrentUrl] = useState<
7+
| {
8+
url: string;
9+
data?: {
10+
type: "issue";
11+
id: number;
12+
};
13+
}
14+
| undefined
15+
>();
16+
17+
useEffect(() => {
18+
const onActivated = () => {
19+
(async () => {
20+
const tabs = await chrome.tabs.query({ active: true, lastFocusedWindow: true, url: `${settings.redmineURL}/*` });
21+
const currentTab = tabs[0];
22+
if (currentTab?.url) {
23+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
24+
const [_, issueId] = currentTab.url.match(new RegExp(`^${settings.redmineURL}/issues/(\\d+)$`)) || [];
25+
if (issueId) {
26+
setCurrentUrl({ url: currentTab.url, data: { type: "issue", id: Number(issueId) } });
27+
} else {
28+
setCurrentUrl({ url: currentTab.url });
29+
}
30+
} else {
31+
setCurrentUrl(undefined);
32+
}
33+
})();
34+
};
35+
36+
// Initial load
37+
onActivated();
38+
39+
// Listen to tab change
40+
chrome.tabs.onActivated.addListener(onActivated);
41+
return () => chrome.tabs.onActivated.removeListener(onActivated);
42+
}, [settings.redmineURL]);
43+
44+
return currentUrl;
45+
}
46+
47+
export default useActiveRedmineTab;

src/lang/de.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"settings.style.show-issues-priority.title": "Tickets Priorität anzeigen",
137137
"settings.style.sort-issues-by-priority.title": "Tickets nach Priorität sortieren",
138138
"settings.style.pin-tracked-issues.title": "Aktive Tickets oben anheften",
139+
"settings.style.pin-active-tab-issue.title": "Ticket des aktiven Tabs oben anheften",
139140
"settings.style.show-tooltips.title": "Tooltips anzeigen",
140141
"settings.style.time-format.title": "Format für Zeitspannen",
141142
"settings.save-settings": "Einstellungen speichern",

src/lang/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"settings.style.show-issues-priority.title": "Show issues priority",
137137
"settings.style.sort-issues-by-priority.title": "Sort issues by priority",
138138
"settings.style.pin-tracked-issues.title": "Pin tracked issues to the top",
139+
"settings.style.pin-active-tab-issue.title": "Pin issue of active tab to the top",
139140
"settings.style.show-tooltips.title": "Show tooltips",
140141
"settings.style.time-format.title": "Time span format",
141142
"settings.save-settings": "Save settings",

src/lang/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"settings.style.show-issues-priority.title": "Показывать приоритет задач",
137137
"settings.style.sort-issues-by-priority.title": "Сортировать задачи по приоритету",
138138
"settings.style.pin-tracked-issues.title": "Pin tracked issues to the top",
139+
"settings.style.pin-active-tab-issue.title": "Pin issue of active tab to the top",
139140
"settings.style.show-tooltips.title": "Показывать всплывающие подсказки",
140141
"settings.style.time-format.title": "Формат времени",
141142
"settings.save-settings": "Сохранить настройки",

src/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
"open_in_tab": false
2121
},
2222
"permissions": [
23-
"storage"
23+
"storage",
24+
"tabs",
25+
"activeTab"
2426
],
2527
"host_permissions": [
2628
"http://*/*",

src/pages/SettingsPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ const SettingsPage = () => {
240240
<Field type="checkbox" name="style.showIssuesPriority" title={formatMessage({ id: "settings.style.show-issues-priority.title" })} as={CheckBox} />
241241
<Field type="checkbox" name="style.sortIssuesByPriority" title={formatMessage({ id: "settings.style.sort-issues-by-priority.title" })} as={CheckBox} />
242242
<Field type="checkbox" name="style.pinTrackedIssues" title={formatMessage({ id: "settings.style.pin-tracked-issues.title" })} as={CheckBox} />
243+
<Field type="checkbox" name="style.pinActiveTabIssue" title={formatMessage({ id: "settings.style.pin-active-tab-issue.title" })} as={CheckBox} />
243244
<Field type="checkbox" name="style.showTooltips" title={formatMessage({ id: "settings.style.show-tooltips.title" })} as={CheckBox} />
244245
<Field
245246
name="style.timeFormat"

src/provider/SettingsProvider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type Settings = {
2020
showIssuesPriority: boolean;
2121
sortIssuesByPriority: boolean;
2222
pinTrackedIssues: boolean;
23+
pinActiveTabIssue: boolean;
2324
showTooltips: boolean;
2425
timeFormat: "decimal" | "minutes";
2526
};
@@ -43,6 +44,7 @@ const defaultSettings: Settings = {
4344
showIssuesPriority: true,
4445
sortIssuesByPriority: true,
4546
pinTrackedIssues: false,
47+
pinActiveTabIssue: true,
4648
showTooltips: true,
4749
timeFormat: "decimal",
4850
},

src/utils/issue.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { IssuesData } from "../components/issues/IssuesList";
22
import { Settings } from "../provider/SettingsProvider";
33
import { TIssue, TIssuePriority, TReference, TVersion } from "../types/redmine";
44

5+
type GroupId = "active-tab" | "active" | "paused" | `${number}`;
56
type GroupType = "pinned" | "version" | "no-version";
67

78
type IssueGroup = {
@@ -11,7 +12,7 @@ type IssueGroup = {
1112
};
1213

1314
type GroupedIssues = {
14-
type: string;
15+
id: GroupId;
1516
project?: TReference;
1617
versions: TVersion[];
1718
groups: {
@@ -24,7 +25,7 @@ type GroupedIssues = {
2425
type GroupedIssuesHelper = Record<
2526
number | string,
2627
{
27-
type: string;
28+
id: GroupId;
2829
// The project reference
2930
project?: TReference;
3031
// Pinned issues
@@ -72,13 +73,36 @@ export const getSortedIssues = (issues: TIssue[], issuePriorities: TIssuePriorit
7273
* - group pinned issues
7374
* - group issues by version
7475
*/
75-
export const getGroupedIssues = (issues: TIssue[], projectVersions: Record<number, TVersion[]>, issuesData: IssuesData, settings: Settings): GroupedIssues => {
76+
export const getGroupedIssues = ({
77+
issues,
78+
projectVersions,
79+
issuesData,
80+
settings,
81+
activeTabIssueId,
82+
}: {
83+
issues: TIssue[];
84+
projectVersions: Record<number, TVersion[]>;
85+
issuesData: IssuesData;
86+
settings: Settings;
87+
activeTabIssueId?: number;
88+
}): GroupedIssues => {
7689
const grouped = issues.reduce((result: GroupedIssuesHelper, issue) => {
90+
if (settings.style.pinActiveTabIssue && activeTabIssueId && issue.id === activeTabIssueId) {
91+
result["active-tab"] = {
92+
id: "active-tab",
93+
pinnedIssues: [],
94+
versions: {},
95+
issues: [issue],
96+
sort: -3,
97+
};
98+
return result;
99+
}
100+
77101
if (settings.style.pinTrackedIssues) {
78102
if (issuesData[issue.id]?.active) {
79103
if (!("active" in result)) {
80104
result["active"] = {
81-
type: "active",
105+
id: "active",
82106
pinnedIssues: [],
83107
versions: {},
84108
issues: [],
@@ -93,7 +117,7 @@ export const getGroupedIssues = (issues: TIssue[], projectVersions: Record<numbe
93117
if (issuesData[issue.id]?.time) {
94118
if (!("paused" in result)) {
95119
result["paused"] = {
96-
type: "paused",
120+
id: "paused",
97121
pinnedIssues: [],
98122
versions: {},
99123
issues: [],
@@ -108,7 +132,7 @@ export const getGroupedIssues = (issues: TIssue[], projectVersions: Record<numbe
108132

109133
if (!(issue.project.id in result)) {
110134
result[issue.project.id] = {
111-
type: issue.project.id.toString(),
135+
id: issue.project.id.toString() as GroupId,
112136
project: issue.project,
113137
pinnedIssues: [],
114138
versions: {},
@@ -149,9 +173,9 @@ export const getGroupedIssues = (issues: TIssue[], projectVersions: Record<numbe
149173

150174
return Object.values(grouped)
151175
.sort((a, b) => a.sort - b.sort)
152-
.map(({ type, project, pinnedIssues, versions, issues }) => ({
153-
type,
154-
project: project,
176+
.map(({ id, project, pinnedIssues, versions, issues }) => ({
177+
id,
178+
project,
155179
versions: (project ? projectVersions[project.id] : undefined) ?? [],
156180
groups: [
157181
...(pinnedIssues.length > 0

0 commit comments

Comments
 (0)