Skip to content

Commit c68984c

Browse files
committed
feat: add GitLab support with multi-provider #44
1 parent 46e656d commit c68984c

31 files changed

Lines changed: 4183 additions & 1161 deletions

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"name": "Github Issues",
44
"version": "1.5.0",
55
"minAppVersion": "0.15.0",
6-
"description": "Track Github Issues and pull requests directly in your vault",
6+
"description": "Track GitHub Issues, Pull Requests, GitLab Issues and Merge Requests directly in your vault",
77
"author": "LonoxX",
88
"authorUrl": "https://github.com/LonoxX",
99
"isDesktopOnly": false,
1010
"fundingUrl": {
1111
"GitHub Sponsor": "https://github.com/sponsors/LonoxX",
1212
"Ko-fi": "https://ko-fi.com/LonoxX"
1313
}
14-
}
14+
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "github-issues",
33
"version": "1.5.0",
4-
"description": "Track GitHub issues and pull requests in Obsidian",
4+
"description": "Track GitHub and GitLab issues and pull/merge requests in Obsidian",
55
"main": "main.js",
66
"scripts": {
77
"dev": "node esbuild.config.mjs",
@@ -12,9 +12,11 @@
1212
"keywords": [
1313
"obsidian",
1414
"github",
15+
"gitlab",
1516
"tracker",
1617
"issues",
17-
"pull-requests"
18+
"pull-requests",
19+
"merge-requests"
1820
],
1921
"author": "LonoxX",
2022
"license": "MIT",

src/cleanup-manager.ts

Lines changed: 92 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { App, TFile, TFolder } from "obsidian";
2-
import { GitHubTrackerSettings, RepositoryTracking } from "./types";
2+
import { IssueTrackerSettings, RepositoryTracking } from "./types";
33
import { extractProperties } from "./util/properties";
44
import { NoticeManager } from "./notice-manager";
55
import { extractNumberFromFilename } from "./util/templateUtils";
@@ -10,7 +10,7 @@ export class CleanupManager {
1010

1111
constructor(
1212
private app: App,
13-
private settings: GitHubTrackerSettings,
13+
private settings: IssueTrackerSettings,
1414
private noticeManager: NoticeManager,
1515
) {
1616
this.folderPathManager = new FolderPathManager();
@@ -25,15 +25,21 @@ export class CleanupManager {
2525
repoCleaned: string,
2626
allIssuesIncludingRecentlyClosed: any[],
2727
): Promise<void> {
28-
const issueFolderPath = this.folderPathManager.getIssueFolderPath(repo, ownerCleaned, repoCleaned);
29-
const repoFolder = this.app.vault.getAbstractFileByPath(issueFolderPath);
28+
const issueFolderPath = this.folderPathManager.getIssueFolderPath(
29+
repo,
30+
ownerCleaned,
31+
repoCleaned,
32+
);
33+
const repoFolder =
34+
this.app.vault.getAbstractFileByPath(issueFolderPath);
3035

3136
if (repoFolder) {
3237
const files = this.app.vault
3338
.getFiles()
3439
.filter(
3540
(file) =>
36-
file.path.startsWith(`${issueFolderPath}/`) && file.extension === "md",
41+
file.path.startsWith(`${issueFolderPath}/`) &&
42+
file.extension === "md",
3743
);
3844

3945
for (const file of files) {
@@ -53,14 +59,14 @@ export class CleanupManager {
5359
// Fallback: try to extract from filename
5460
fileNumberString = extractNumberFromFilename(
5561
file.name,
56-
repo.issueNoteTemplate || "Issue - {number}"
62+
repo.issueNoteTemplate || "Issue - {number}",
5763
);
5864
}
5965

6066
if (!fileNumberString) {
6167
// If we can't determine the issue number, log a warning but skip
6268
this.noticeManager.debug(
63-
`Could not determine issue number for file: ${file.name}. Consider adding a 'number' property to the frontmatter.`
69+
`Could not determine issue number for file: ${file.name}. Consider adding a 'number' property to the frontmatter.`,
6470
);
6571
continue;
6672
}
@@ -75,15 +81,26 @@ export class CleanupManager {
7581
let deleteReason = "";
7682

7783
if (correspondingIssue) {
78-
if (correspondingIssue.state === "closed" && correspondingIssue.closed_at) {
84+
if (
85+
correspondingIssue.state === "closed" &&
86+
correspondingIssue.closed_at
87+
) {
7988
// Check if issue has been closed longer than the configured days
80-
const closedDate = new Date(correspondingIssue.closed_at);
89+
const closedDate = new Date(
90+
correspondingIssue.closed_at,
91+
);
8192
const cutoffDate = new Date();
82-
cutoffDate.setDate(cutoffDate.getDate() - this.settings.cleanupClosedIssuesDays);
93+
cutoffDate.setDate(
94+
cutoffDate.getDate() -
95+
this.settings.cleanupClosedIssuesDays,
96+
);
8397

8498
if (closedDate < cutoffDate) {
8599
shouldDelete = true;
86-
const daysClosed = Math.floor((Date.now() - closedDate.getTime()) / (1000 * 60 * 60 * 24));
100+
const daysClosed = Math.floor(
101+
(Date.now() - closedDate.getTime()) /
102+
(1000 * 60 * 60 * 24),
103+
);
87104
deleteReason = `Deleted issue ${fileNumberString} from ${repo.repository} (closed ${daysClosed} days ago, threshold: ${this.settings.cleanupClosedIssuesDays} days)`;
88105
}
89106
}
@@ -94,10 +111,10 @@ export class CleanupManager {
94111

95112
if (shouldDelete) {
96113
const allowDelete = properties.allowDelete
97-
? String(properties.allowDelete)
98-
.toLowerCase()
99-
.replace('"', "") === "true"
100-
: repo.allowDeleteIssue;
114+
? String(properties.allowDelete)
115+
.toLowerCase()
116+
.replace('"', "") === "true"
117+
: repo.allowDeleteIssue;
101118

102119
if (allowDelete) {
103120
await this.app.fileManager.trashFile(file);
@@ -117,15 +134,23 @@ export class CleanupManager {
117134
repoCleaned: string,
118135
allPullRequestsIncludingRecentlyClosed: any[],
119136
): Promise<void> {
120-
const pullRequestFolderPath = this.folderPathManager.getPullRequestFolderPath(repo, ownerCleaned, repoCleaned);
121-
const repoFolder = this.app.vault.getAbstractFileByPath(pullRequestFolderPath);
137+
const pullRequestFolderPath =
138+
this.folderPathManager.getPullRequestFolderPath(
139+
repo,
140+
ownerCleaned,
141+
repoCleaned,
142+
);
143+
const repoFolder = this.app.vault.getAbstractFileByPath(
144+
pullRequestFolderPath,
145+
);
122146

123147
if (repoFolder) {
124148
const files = this.app.vault
125149
.getFiles()
126150
.filter(
127151
(file) =>
128-
file.path.startsWith(`${pullRequestFolderPath}/`) && file.extension === "md",
152+
file.path.startsWith(`${pullRequestFolderPath}/`) &&
153+
file.extension === "md",
129154
);
130155

131156
for (const file of files) {
@@ -145,14 +170,15 @@ export class CleanupManager {
145170
// Fallback: try to extract from filename
146171
fileNumberString = extractNumberFromFilename(
147172
file.name,
148-
repo.pullRequestNoteTemplate || "Pull Request - {number}"
173+
repo.pullRequestNoteTemplate ||
174+
"Pull Request - {number}",
149175
);
150176
}
151177

152178
if (!fileNumberString) {
153179
// If we can't determine the PR number, log a warning but skip
154180
this.noticeManager.debug(
155-
`Could not determine PR number for file: ${file.name}. Consider adding a 'number' property to the frontmatter.`
181+
`Could not determine PR number for file: ${file.name}. Consider adding a 'number' property to the frontmatter.`,
156182
);
157183
continue;
158184
}
@@ -166,15 +192,24 @@ export class CleanupManager {
166192
let deleteReason = "";
167193

168194
if (correspondingPR) {
169-
if (correspondingPR.state === "closed" && correspondingPR.closed_at) {
195+
if (
196+
correspondingPR.state === "closed" &&
197+
correspondingPR.closed_at
198+
) {
170199
// Check if PR has been closed longer than the configured days
171200
const closedDate = new Date(correspondingPR.closed_at);
172201
const cutoffDate = new Date();
173-
cutoffDate.setDate(cutoffDate.getDate() - this.settings.cleanupClosedIssuesDays);
202+
cutoffDate.setDate(
203+
cutoffDate.getDate() -
204+
this.settings.cleanupClosedIssuesDays,
205+
);
174206

175207
if (closedDate < cutoffDate) {
176208
shouldDelete = true;
177-
const daysClosed = Math.floor((Date.now() - closedDate.getTime()) / (1000 * 60 * 60 * 24));
209+
const daysClosed = Math.floor(
210+
(Date.now() - closedDate.getTime()) /
211+
(1000 * 60 * 60 * 24),
212+
);
178213
deleteReason = `Deleted pull request ${fileNumberString} from ${repo.repository} (closed ${daysClosed} days ago, threshold: ${this.settings.cleanupClosedIssuesDays} days)`;
179214
}
180215
}
@@ -185,10 +220,10 @@ export class CleanupManager {
185220

186221
if (shouldDelete) {
187222
const allowDelete = properties.allowDelete
188-
? String(properties.allowDelete)
189-
.toLowerCase()
190-
.replace('"', "") === "true"
191-
: repo.allowDeletePullRequest;
223+
? String(properties.allowDelete)
224+
.toLowerCase()
225+
.replace('"', "") === "true"
226+
: repo.allowDeletePullRequest;
192227

193228
if (allowDelete) {
194229
await this.app.fileManager.trashFile(file);
@@ -219,10 +254,10 @@ export class CleanupManager {
219254
// Use Obsidian's MetadataCache to get frontmatter
220255
const properties = extractProperties(this.app, file);
221256
const allowDelete = properties.allowDelete
222-
? String(properties.allowDelete)
223-
.toLowerCase()
224-
.replace('"', "") === "true"
225-
: false;
257+
? String(properties.allowDelete)
258+
.toLowerCase()
259+
.replace('"', "") === "true"
260+
: false;
226261

227262
if (allowDelete) {
228263
await this.app.fileManager.trashFile(file);
@@ -236,14 +271,21 @@ export class CleanupManager {
236271
}
237272

238273
// Only cleanup nested folder structure if not using custom folder
239-
if (!repo.useCustomIssueFolder || !repo.customIssueFolder || !repo.customIssueFolder.trim()) {
274+
if (
275+
!repo.useCustomIssueFolder ||
276+
!repo.customIssueFolder ||
277+
!repo.customIssueFolder.trim()
278+
) {
240279
if (files.length === 0) {
241280
this.noticeManager.info(
242281
`Deleting empty folder: ${issueFolder}`,
243282
);
244283
const folder =
245284
this.app.vault.getAbstractFileByPath(issueFolder);
246-
if (folder instanceof TFolder && folder.children.length === 0) {
285+
if (
286+
folder instanceof TFolder &&
287+
folder.children.length === 0
288+
) {
247289
await this.app.fileManager.trashFile(folder);
248290
}
249291
}
@@ -285,10 +327,10 @@ export class CleanupManager {
285327
// Use Obsidian's MetadataCache to get frontmatter
286328
const properties = extractProperties(this.app, file);
287329
const allowDelete = properties.allowDelete
288-
? String(properties.allowDelete)
289-
.toLowerCase()
290-
.replace('"', "") === "true"
291-
: false;
330+
? String(properties.allowDelete)
331+
.toLowerCase()
332+
.replace('"', "") === "true"
333+
: false;
292334

293335
if (allowDelete) {
294336
await this.app.fileManager.trashFile(file);
@@ -302,21 +344,29 @@ export class CleanupManager {
302344
}
303345

304346
// Only cleanup nested folder structure if not using custom folder
305-
if (!repo.useCustomPullRequestFolder || !repo.customPullRequestFolder || !repo.customPullRequestFolder.trim()) {
347+
if (
348+
!repo.useCustomPullRequestFolder ||
349+
!repo.customPullRequestFolder ||
350+
!repo.customPullRequestFolder.trim()
351+
) {
306352
if (files.length === 0) {
307353
this.noticeManager.info(
308354
`Deleting empty folder: ${pullRequestFolder}`,
309355
);
310356
const folder =
311357
this.app.vault.getAbstractFileByPath(pullRequestFolder);
312-
if (folder instanceof TFolder && folder.children.length === 0) {
358+
if (
359+
folder instanceof TFolder &&
360+
folder.children.length === 0
361+
) {
313362
await this.app.fileManager.trashFile(folder);
314363
}
315364
}
316365

317-
const pullRequestOwnerFolder = this.app.vault.getAbstractFileByPath(
318-
`${repo.pullRequestFolder}/${ownerCleaned}`,
319-
);
366+
const pullRequestOwnerFolder =
367+
this.app.vault.getAbstractFileByPath(
368+
`${repo.pullRequestFolder}/${ownerCleaned}`,
369+
);
320370

321371
if (pullRequestOwnerFolder instanceof TFolder) {
322372
const files = pullRequestOwnerFolder.children;

0 commit comments

Comments
 (0)