Skip to content

Commit 81bc3ee

Browse files
committed
WIP: move all configurable settings into profiles #40
1 parent d817d65 commit 81bc3ee

15 files changed

Lines changed: 1521 additions & 1092 deletions

src/content-generator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ export class ContentGenerator {
2525
subIssues?: any[],
2626
parentIssue?: any,
2727
): Promise<string> {
28-
// Determine whether to escape hash tags (repo setting takes precedence if ignoreGlobalSettings is true)
29-
const shouldEscapeHashTags = repo.ignoreGlobalSettings ? repo.escapeHashTags : settings.escapeHashTags;
28+
// Determine whether to escape hash tags (repo setting takes precedence if using a custom profile)
29+
const shouldEscapeHashTags = repo.profileId !== "default" ? repo.escapeHashTags : settings.escapeHashTags;
3030

3131
// Check if custom template is enabled and load template content
3232
if (repo.useCustomIssueContentTemplate && repo.issueContentTemplate) {
@@ -152,8 +152,8 @@ ${subIssues.map((si: any) => {
152152
settings: GitHubTrackerSettings,
153153
projectData?: ProjectData[],
154154
): Promise<string> {
155-
// Determine whether to escape hash tags (repo setting takes precedence if ignoreGlobalSettings is true)
156-
const shouldEscapeHashTags = repo.ignoreGlobalSettings ? repo.escapeHashTags : settings.escapeHashTags;
155+
// Determine whether to escape hash tags (repo setting takes precedence if using a custom profile)
156+
const shouldEscapeHashTags = repo.profileId !== "default" ? repo.escapeHashTags : settings.escapeHashTags;
157157

158158
// Check if custom template is enabled and load template content
159159
if (repo.useCustomPullRequestContentTemplate && repo.pullRequestContentTemplate) {

src/file-manager.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
formatComments
1818
} from "./util/templateUtils";
1919
import { extractPersistBlocks, mergePersistBlocks } from "./util/persistUtils";
20+
import { getEffectiveProjectSettings } from "./util/settingsUtils";
2021

2122
export class FileManager {
2223
private issueFileManager: IssueFileManager;
@@ -104,8 +105,11 @@ export class FileManager {
104105
project: TrackedProject,
105106
items: any[],
106107
): Promise<void> {
107-
const issueFolderPath = this.folderPathManager.getProjectIssueFolderPath(project);
108-
const prFolderPath = this.folderPathManager.getProjectPullRequestFolderPath(project);
108+
// Apply profile settings to get effective project configuration
109+
const effectiveProject = getEffectiveProjectSettings(project, this.settings);
110+
111+
const issueFolderPath = this.folderPathManager.getProjectIssueFolderPath(effectiveProject);
112+
const prFolderPath = this.folderPathManager.getProjectPullRequestFolderPath(effectiveProject);
109113

110114
if (!issueFolderPath && !prFolderPath) {
111115
this.noticeManager.debug(`No folder configured for project ${project.title}`);
@@ -118,7 +122,7 @@ export class FileManager {
118122
let skippedHiddenStatus = 0;
119123

120124
const hiddenStatuses = new Set(project.hiddenStatuses || []);
121-
const skipHidden = project.skipHiddenStatusesOnSync && hiddenStatuses.size > 0;
125+
const skipHidden = effectiveProject.skipHiddenStatusesOnSync && hiddenStatuses.size > 0;
122126

123127
for (const item of items) {
124128
const content = item.content;
@@ -163,14 +167,14 @@ export class FileManager {
163167
let subIssues: any[] = [];
164168
let parentIssue: any = null;
165169

166-
if (isIssue && project.includeSubIssues) {
170+
if (isIssue && effectiveProject.includeSubIssues) {
167171
const [owner, repoName] = repository.split("/");
168172
if (owner && repoName) {
169173
subIssues = await this.gitHubClient.fetchSubIssues(owner, repoName, content.number);
170174
parentIssue = await this.gitHubClient.fetchParentIssue(owner, repoName, content.number);
171175

172176
// Enrich sub-issues with vault paths if they exist
173-
const noteTemplate = project.issueNoteTemplate || "Issue - {number} - {title}";
177+
const noteTemplate = effectiveProject.issueNoteTemplate || "Issue - {number} - {title}";
174178
subIssues = await this.fileHelpers.enrichSubIssuesWithVaultPaths(
175179
subIssues,
176180
folderPath,
@@ -205,8 +209,8 @@ export class FileManager {
205209
);
206210

207211
const filenameTemplate = isIssue
208-
? (project.issueNoteTemplate || "Issue - {number} - {title}")
209-
: (project.pullRequestNoteTemplate || "PR - {number} - {title}");
212+
? (effectiveProject.issueNoteTemplate || "Issue - {number} - {title}")
213+
: (effectiveProject.pullRequestNoteTemplate || "PR - {number} - {title}");
210214

211215
const baseFileName = processFilenameTemplate(filenameTemplate, templateData, this.settings.dateFormat);
212216
const fileName = `${baseFileName}.md`;
@@ -215,7 +219,7 @@ export class FileManager {
215219
const existingFile = this.app.vault.getAbstractFileByPath(filePath);
216220
let fileContent = await this.generateProjectItemContent(
217221
content,
218-
project,
222+
effectiveProject,
219223
status,
220224
isIssue,
221225
item.fieldValues?.nodes || [],

src/issue-file-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class IssueFileManager {
4343
_currentIssueNumbers: Set<string>,
4444
): Promise<void> {
4545
// Apply global defaults to repository settings
46-
const effectiveRepo = getEffectiveRepoSettings(repo, this.settings.globalDefaults);
46+
const effectiveRepo = getEffectiveRepoSettings(repo, this.settings);
4747

4848
const [owner, repoName] = effectiveRepo.repository.split("/");
4949
if (!owner || !repoName) return;
@@ -195,7 +195,7 @@ export class IssueFileManager {
195195
this.noticeManager.debug(`Updated issue ${issue.number}`);
196196
}
197197
} else if (updateMode === "append") {
198-
const shouldEscapeHashTags = repo.ignoreGlobalSettings ? repo.escapeHashTags : this.settings.escapeHashTags;
198+
const shouldEscapeHashTags = repo.profileId !== "default" ? repo.escapeHashTags : this.settings.escapeHashTags;
199199
content = `---\n### New status: "${
200200
issue.state
201201
}"\n\n# ${escapeBody(

src/kanban-view.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ItemView, WorkspaceLeaf, TFile, Notice, setIcon } from "obsidian";
22
import { GitHubTrackerSettings, ProjectData, TrackedProject } from "./types";
3+
import { getEffectiveProjectSettings } from "./util/settingsUtils";
34

45
export const KANBAN_VIEW_TYPE = "github-kanban-view";
56

@@ -279,11 +280,14 @@ export class GitHubKanbanView extends ItemView {
279280
return this.defaultStatusSort(statusesWithItems);
280281
}
281282

283+
// Get effective settings from profile
284+
const effectiveProject = getEffectiveProjectSettings(trackedProject, this.settings);
285+
282286
// Determine the order to use (includes ALL statuses, even empty ones)
283287
let statusOrder: string[] = [];
284288

285289
if (trackedProject.useCustomStatusOrder && trackedProject.customStatusOrder?.length) {
286-
// Use custom order
290+
// Use custom order (project-specific)
287291
statusOrder = trackedProject.customStatusOrder;
288292
} else if (trackedProject.statusOptions?.length) {
289293
// Use GitHub API order
@@ -295,8 +299,8 @@ export class GitHubKanbanView extends ItemView {
295299
return this.defaultStatusSort(statusesWithItems);
296300
}
297301

298-
// Check settings
299-
const showEmptyColumns = trackedProject.showEmptyColumns ?? true;
302+
// Check settings - showEmptyColumns from profile
303+
const showEmptyColumns = effectiveProject.showEmptyColumns ?? true;
300304
const hiddenStatuses = new Set(trackedProject.hiddenStatuses || []);
301305

302306
const orderedStatuses: string[] = [];
@@ -342,6 +346,11 @@ export class GitHubKanbanView extends ItemView {
342346

343347
const trackedProject = this.settings.trackedProjects?.find(p => p.id === project.id);
344348

349+
// Get effective settings from profile
350+
const effectiveProject = trackedProject
351+
? getEffectiveProjectSettings(trackedProject, this.settings)
352+
: null;
353+
345354
const processFolder = (folder: string | undefined): string | undefined => {
346355
if (!folder) return undefined;
347356
const sanitize = (str: string) => str.replace(/[<>:"|?*\\]/g, "-").replace(/\.\./g, ".").trim();
@@ -351,16 +360,8 @@ export class GitHubKanbanView extends ItemView {
351360
.replace(/\{project_number\}/g, (project.number || "").toString());
352361
};
353362

354-
const issueFolder = processFolder(
355-
trackedProject?.useCustomIssueFolder
356-
? trackedProject?.customIssueFolder
357-
: trackedProject?.issueFolder
358-
);
359-
const prFolder = processFolder(
360-
trackedProject?.useCustomPullRequestFolder
361-
? trackedProject?.customPullRequestFolder
362-
: trackedProject?.pullRequestFolder
363-
);
363+
const issueFolder = processFolder(effectiveProject?.issueFolder);
364+
const prFolder = processFolder(effectiveProject?.pullRequestFolder);
364365

365366
const files = this.app.vault.getMarkdownFiles();
366367
const matchedNumbers = new Set<number>();

src/main.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import {
33
GitHubTrackerSettings,
44
DEFAULT_SETTINGS,
55
DEFAULT_REPOSITORY_TRACKING,
6+
SettingsProfile,
7+
DEFAULT_REPOSITORY_PROFILE,
8+
DEFAULT_PROJECT_PROFILE,
69
} from "./types";
710
import { GitHubClient } from "./github-client";
811
import { FileManager } from "./file-manager";
@@ -505,19 +508,188 @@ export default class GitHubTrackerPlugin extends Plugin {
505508
this.settings.globalDefaults = Object.assign({}, DEFAULT_SETTINGS.globalDefaults);
506509
}
507510

511+
// === Profile Migration ===
512+
if (!this.settings.profiles || this.settings.profiles.length === 0) {
513+
this.settings.profiles = [];
514+
515+
// Create "Default Profile" from existing globalDefaults
516+
const defaultProfile: SettingsProfile = {
517+
id: "default",
518+
name: "Default Profile",
519+
type: "repository",
520+
issueUpdateMode: this.settings.globalDefaults.issueUpdateMode,
521+
allowDeleteIssue: this.settings.globalDefaults.allowDeleteIssue,
522+
issueFolder: this.settings.globalDefaults.issueFolder,
523+
issueNoteTemplate: this.settings.globalDefaults.issueNoteTemplate,
524+
issueContentTemplate: this.settings.globalDefaults.issueContentTemplate,
525+
useCustomIssueContentTemplate: this.settings.globalDefaults.useCustomIssueContentTemplate,
526+
includeIssueComments: this.settings.globalDefaults.includeIssueComments,
527+
pullRequestUpdateMode: this.settings.globalDefaults.pullRequestUpdateMode,
528+
allowDeletePullRequest: this.settings.globalDefaults.allowDeletePullRequest,
529+
pullRequestFolder: this.settings.globalDefaults.pullRequestFolder,
530+
pullRequestNoteTemplate: this.settings.globalDefaults.pullRequestNoteTemplate,
531+
pullRequestContentTemplate: this.settings.globalDefaults.pullRequestContentTemplate,
532+
useCustomPullRequestContentTemplate: this.settings.globalDefaults.useCustomPullRequestContentTemplate,
533+
includePullRequestComments: this.settings.globalDefaults.includePullRequestComments,
534+
includeClosedIssues: this.settings.globalDefaults.includeClosedIssues,
535+
includeClosedPullRequests: this.settings.globalDefaults.includeClosedPullRequests,
536+
};
537+
this.settings.profiles.push(defaultProfile);
538+
539+
// Create "Default Project Profile"
540+
this.settings.profiles.push({ ...DEFAULT_PROJECT_PROFILE });
541+
542+
// Migrate repositories
543+
this.settings.repositories = this.settings.repositories.map(repo => {
544+
const merged = Object.assign({}, DEFAULT_REPOSITORY_TRACKING, repo);
545+
546+
if ((repo as any).ignoreGlobalSettings) {
547+
// Repo had custom settings - create a dedicated profile
548+
const customProfileId = `migrated-${repo.repository.replace(/\//g, '-')}-${Date.now()}`;
549+
const customProfile: SettingsProfile = {
550+
id: customProfileId,
551+
name: `${repo.repository} (migrated)`,
552+
type: "repository",
553+
issueUpdateMode: merged.issueUpdateMode,
554+
allowDeleteIssue: merged.allowDeleteIssue,
555+
issueFolder: merged.issueFolder,
556+
issueNoteTemplate: merged.issueNoteTemplate,
557+
issueContentTemplate: merged.issueContentTemplate,
558+
useCustomIssueContentTemplate: merged.useCustomIssueContentTemplate,
559+
includeIssueComments: merged.includeIssueComments,
560+
pullRequestUpdateMode: merged.pullRequestUpdateMode,
561+
allowDeletePullRequest: merged.allowDeletePullRequest,
562+
pullRequestFolder: merged.pullRequestFolder,
563+
pullRequestNoteTemplate: merged.pullRequestNoteTemplate,
564+
pullRequestContentTemplate: merged.pullRequestContentTemplate,
565+
useCustomPullRequestContentTemplate: merged.useCustomPullRequestContentTemplate,
566+
includePullRequestComments: merged.includePullRequestComments,
567+
includeClosedIssues: merged.includeClosedIssues,
568+
includeClosedPullRequests: merged.includeClosedPullRequests,
569+
};
570+
this.settings.profiles.push(customProfile);
571+
merged.profileId = customProfileId;
572+
} else {
573+
merged.profileId = "default";
574+
}
575+
576+
// Clean up deprecated field
577+
delete (merged as any).ignoreGlobalSettings;
578+
579+
return merged;
580+
});
581+
582+
// Save migrated settings
583+
await this.saveData(this.settings);
584+
}
585+
586+
// Migrate repos that still have old per-repo settings into their own profiles
587+
const defaultProfile = this.settings.profiles.find(p => p.id === "default") ?? DEFAULT_REPOSITORY_PROFILE;
588+
let needsSave = false;
589+
this.settings.repositories = this.settings.repositories.map(repo => {
590+
// Skip repos that already have a non-default profile assigned
591+
if (repo.profileId && repo.profileId !== "default") {
592+
delete (repo as any).ignoreGlobalSettings;
593+
return repo;
594+
}
595+
596+
const merged = Object.assign({}, DEFAULT_REPOSITORY_TRACKING, repo);
597+
598+
// Check if repo has values that differ from the default profile
599+
const hasDiff = (
600+
merged.issueUpdateMode !== (defaultProfile.issueUpdateMode ?? "none") ||
601+
merged.allowDeleteIssue !== (defaultProfile.allowDeleteIssue ?? true) ||
602+
merged.issueFolder !== (defaultProfile.issueFolder ?? "GitHub") ||
603+
merged.issueNoteTemplate !== (defaultProfile.issueNoteTemplate ?? "Issue - {number}") ||
604+
(merged.issueContentTemplate || "") !== (defaultProfile.issueContentTemplate ?? "") ||
605+
merged.includeIssueComments !== (defaultProfile.includeIssueComments ?? true) ||
606+
merged.includeClosedIssues !== (defaultProfile.includeClosedIssues ?? false) ||
607+
merged.pullRequestUpdateMode !== (defaultProfile.pullRequestUpdateMode ?? "none") ||
608+
merged.allowDeletePullRequest !== (defaultProfile.allowDeletePullRequest ?? true) ||
609+
merged.pullRequestFolder !== (defaultProfile.pullRequestFolder ?? "GitHub Pull Requests") ||
610+
merged.pullRequestNoteTemplate !== (defaultProfile.pullRequestNoteTemplate ?? "PR - {number}") ||
611+
(merged.pullRequestContentTemplate || "") !== (defaultProfile.pullRequestContentTemplate ?? "") ||
612+
merged.includePullRequestComments !== (defaultProfile.includePullRequestComments ?? true) ||
613+
merged.includeClosedPullRequests !== (defaultProfile.includeClosedPullRequests ?? false) ||
614+
(merged.includeSubIssues ?? false) !== (defaultProfile.includeSubIssues ?? false)
615+
);
616+
617+
if (hasDiff) {
618+
// Repo has custom values - create a dedicated profile
619+
const customProfileId = `migrated-${repo.repository.replace(/\//g, '-')}-${Date.now()}`;
620+
const customProfile: SettingsProfile = {
621+
id: customProfileId,
622+
name: `${repo.repository}`,
623+
type: "repository",
624+
issueUpdateMode: merged.issueUpdateMode,
625+
allowDeleteIssue: merged.allowDeleteIssue,
626+
issueFolder: merged.issueFolder,
627+
issueNoteTemplate: merged.issueNoteTemplate,
628+
issueContentTemplate: merged.issueContentTemplate,
629+
useCustomIssueContentTemplate: merged.useCustomIssueContentTemplate,
630+
includeIssueComments: merged.includeIssueComments,
631+
pullRequestUpdateMode: merged.pullRequestUpdateMode,
632+
allowDeletePullRequest: merged.allowDeletePullRequest,
633+
pullRequestFolder: merged.pullRequestFolder,
634+
pullRequestNoteTemplate: merged.pullRequestNoteTemplate,
635+
pullRequestContentTemplate: merged.pullRequestContentTemplate,
636+
useCustomPullRequestContentTemplate: merged.useCustomPullRequestContentTemplate,
637+
includePullRequestComments: merged.includePullRequestComments,
638+
includeClosedIssues: merged.includeClosedIssues,
639+
includeClosedPullRequests: merged.includeClosedPullRequests,
640+
includeSubIssues: merged.includeSubIssues ?? false,
641+
};
642+
this.settings.profiles.push(customProfile);
643+
repo.profileId = customProfileId;
644+
needsSave = true;
645+
} else {
646+
repo.profileId = "default";
647+
}
648+
649+
delete (repo as any).ignoreGlobalSettings;
650+
return repo;
651+
});
652+
if (needsSave) {
653+
await this.saveData(this.settings);
654+
}
655+
508656
// Migrate existing repositories to include new custom folder properties
509657
// Defaults first, then override with saved values
510658
this.settings.repositories = this.settings.repositories.map(repo => {
511659
const merged = Object.assign({}, DEFAULT_REPOSITORY_TRACKING, repo);
512660
// Ensure critical fields are never undefined
513661
if (!merged.issueFolder) merged.issueFolder = DEFAULT_REPOSITORY_TRACKING.issueFolder;
514662
if (!merged.pullRequestFolder) merged.pullRequestFolder = DEFAULT_REPOSITORY_TRACKING.pullRequestFolder;
663+
if (!merged.profileId) merged.profileId = "default";
515664
return merged;
516665
});
517666

518667
}
519668

520669
async saveSettings() {
670+
// Sync "default" profile back to globalDefaults for backward compatibility
671+
const defaultProfile = this.settings.profiles.find(p => p.id === "default");
672+
if (defaultProfile) {
673+
this.settings.globalDefaults = {
674+
issueUpdateMode: defaultProfile.issueUpdateMode ?? "none",
675+
allowDeleteIssue: defaultProfile.allowDeleteIssue ?? true,
676+
issueFolder: defaultProfile.issueFolder ?? "GitHub",
677+
issueNoteTemplate: defaultProfile.issueNoteTemplate ?? "Issue - {number}",
678+
issueContentTemplate: defaultProfile.issueContentTemplate ?? "",
679+
useCustomIssueContentTemplate: defaultProfile.useCustomIssueContentTemplate ?? false,
680+
includeIssueComments: defaultProfile.includeIssueComments ?? true,
681+
pullRequestUpdateMode: defaultProfile.pullRequestUpdateMode ?? "none",
682+
allowDeletePullRequest: defaultProfile.allowDeletePullRequest ?? true,
683+
pullRequestFolder: defaultProfile.pullRequestFolder ?? "GitHub Pull Requests",
684+
pullRequestNoteTemplate: defaultProfile.pullRequestNoteTemplate ?? "PR - {number}",
685+
pullRequestContentTemplate: defaultProfile.pullRequestContentTemplate ?? "",
686+
useCustomPullRequestContentTemplate: defaultProfile.useCustomPullRequestContentTemplate ?? false,
687+
includePullRequestComments: defaultProfile.includePullRequestComments ?? true,
688+
includeClosedIssues: defaultProfile.includeClosedIssues ?? false,
689+
includeClosedPullRequests: defaultProfile.includeClosedPullRequests ?? false,
690+
};
691+
}
692+
521693
await this.saveData(this.settings);
522694
const token = this.getGitHubToken();
523695
if (token) {

src/pr-file-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class PullRequestFileManager {
4343
_currentPRNumbers: Set<string>,
4444
): Promise<void> {
4545
// Apply global defaults to repository settings
46-
const effectiveRepo = getEffectiveRepoSettings(repo, this.settings.globalDefaults);
46+
const effectiveRepo = getEffectiveRepoSettings(repo, this.settings);
4747

4848
const [owner, repoName] = effectiveRepo.repository.split("/");
4949
if (!owner || !repoName) return;
@@ -164,7 +164,7 @@ export class PullRequestFileManager {
164164
this.noticeManager.debug(`Updated PR ${pr.number}`);
165165
}
166166
} else if (updateMode === "append") {
167-
const shouldEscapeHashTags = repo.ignoreGlobalSettings ? repo.escapeHashTags : this.settings.escapeHashTags;
167+
const shouldEscapeHashTags = repo.profileId !== "default" ? repo.escapeHashTags : this.settings.escapeHashTags;
168168
content = `---\n### New status: "${
169169
pr.state
170170
}"\n\n# ${escapeBody(

0 commit comments

Comments
 (0)