mirror of
https://github.com/rordaz/ColorTab.git
synced 2026-07-22 07:48:53 +00:00
feat: add Prevent Tab Duplication setting with settings UI fallback; release v1.1.0
This commit is contained in:
parent
fff95809f5
commit
186a5f285f
4 changed files with 184 additions and 10 deletions
|
|
@ -54,8 +54,9 @@ Examples of how people use it:
|
|||
|
||||
1. Go to **Settings → Color Tab**.
|
||||
2. Each of the 5 color slots has a **name field** and a **color picker**.
|
||||
3. Changes take effect immediately — open tabs update live.
|
||||
4. Click **Reset** to restore the default pastel palette.
|
||||
3. Toggle **Prevent Tab Duplication** to keep only one tab per file.
|
||||
4. Changes take effect immediately — open tabs update live.
|
||||
5. Click **Reset** to restore the default pastel palette.
|
||||
|
||||
## Default colors
|
||||
|
||||
|
|
|
|||
185
main.ts
185
main.ts
|
|
@ -17,6 +17,7 @@ interface ColorTabSettings {
|
|||
/** Maps file path → hex color */
|
||||
fileColors: Record<string, string>;
|
||||
autoPinColoredTabs: boolean;
|
||||
preventTabDuplication: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_COLORS: ColorEntry[] = [
|
||||
|
|
@ -31,6 +32,7 @@ const DEFAULT_SETTINGS: ColorTabSettings = {
|
|||
colors: DEFAULT_COLORS,
|
||||
fileColors: {},
|
||||
autoPinColoredTabs: true,
|
||||
preventTabDuplication: true,
|
||||
};
|
||||
|
||||
export default class ColorTabPlugin extends Plugin {
|
||||
|
|
@ -53,20 +55,29 @@ export default class ColorTabPlugin extends Plugin {
|
|||
|
||||
// Re-apply stored colors whenever the layout changes
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("layout-change", () =>
|
||||
this.applyAllColors()
|
||||
)
|
||||
this.app.workspace.on("layout-change", () => {
|
||||
this.applyAllColors();
|
||||
if (this.settings.preventTabDuplication) {
|
||||
this.handleDuplicateTabs();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Clear/apply color when a new file is loaded into any leaf
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("active-leaf-change", () =>
|
||||
this.applyAllColors()
|
||||
)
|
||||
this.app.workspace.on("active-leaf-change", () => {
|
||||
this.applyAllColors();
|
||||
if (this.settings.preventTabDuplication) {
|
||||
this.handleDuplicateTabs();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
this.applyAllColors();
|
||||
if (this.settings.preventTabDuplication) {
|
||||
this.handleDuplicateTabs();
|
||||
}
|
||||
this.registerColorCommands();
|
||||
});
|
||||
}
|
||||
|
|
@ -222,6 +233,45 @@ export default class ColorTabPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
// ── Tab duplication prevention ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Detects and handles duplicate tabs (same file open in multiple tabs).
|
||||
* Closes duplicate tabs and focuses on the first instance.
|
||||
* Works with both regular and colored (pinned) tabs.
|
||||
*/
|
||||
private handleDuplicateTabs() {
|
||||
const filePathMap = new Map<string, WorkspaceLeaf[]>();
|
||||
|
||||
// Build a map of file paths to their corresponding leaves
|
||||
this.app.workspace.iterateAllLeaves((leaf) => {
|
||||
const path = this.getFilePath(leaf);
|
||||
if (path) {
|
||||
if (!filePathMap.has(path)) {
|
||||
filePathMap.set(path, []);
|
||||
}
|
||||
filePathMap.get(path)!.push(leaf);
|
||||
}
|
||||
});
|
||||
|
||||
// For each file with duplicates, keep the first and close the rest
|
||||
filePathMap.forEach((leaves) => {
|
||||
if (leaves.length > 1) {
|
||||
// Keep the first leaf and focus on it
|
||||
const firstLeaf = leaves[0];
|
||||
this.app.workspace.setActiveLeaf(firstLeaf);
|
||||
|
||||
// Close the duplicate leaves
|
||||
for (let i = 1; i < leaves.length; i++) {
|
||||
const duplicateLeaf = leaves[i];
|
||||
// Unpin the duplicate tab before closing it (important for colored tabs)
|
||||
duplicateLeaf.setPinned(false);
|
||||
duplicateLeaf.detach();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private getFilePath(leaf: WorkspaceLeaf): string | null {
|
||||
|
|
@ -252,6 +302,7 @@ export default class ColorTabPlugin extends Plugin {
|
|||
colors: saved?.colors ?? DEFAULT_COLORS.map((c) => ({ ...c })),
|
||||
fileColors: saved?.fileColors ?? {},
|
||||
autoPinColoredTabs: saved?.autoPinColoredTabs ?? DEFAULT_SETTINGS.autoPinColoredTabs,
|
||||
preventTabDuplication: saved?.preventTabDuplication ?? DEFAULT_SETTINGS.preventTabDuplication,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -353,6 +404,11 @@ class ColorTabSettingTab extends PluginSettingTab {
|
|||
desc: "When enabled, applying a tab color pins the tab and removing color unpins it.",
|
||||
control: { type: "toggle" as const, key: "autoPinColoredTabs" },
|
||||
},
|
||||
{
|
||||
name: "Prevent Tab Duplication",
|
||||
desc: "When enabled, opening an already-open file will focus on the existing tab instead of creating a duplicate.",
|
||||
control: { type: "toggle" as const, key: "preventTabDuplication" },
|
||||
},
|
||||
{
|
||||
name: "Reset to defaults",
|
||||
desc: "Restore the original pastel color palette.",
|
||||
|
|
@ -366,6 +422,8 @@ class ColorTabSettingTab extends PluginSettingTab {
|
|||
);
|
||||
this.plugin.settings.autoPinColoredTabs =
|
||||
DEFAULT_SETTINGS.autoPinColoredTabs;
|
||||
this.plugin.settings.preventTabDuplication =
|
||||
DEFAULT_SETTINGS.preventTabDuplication;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.applyAllColors();
|
||||
this.update();
|
||||
|
|
@ -376,5 +434,120 @@ class ColorTabSettingTab extends PluginSettingTab {
|
|||
];
|
||||
}
|
||||
|
||||
// Fallback for Obsidian versions that do not consume getSettingDefinitions.
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
this.plugin.settings.colors.forEach((entry, index) => {
|
||||
let hexInputEl: HTMLInputElement;
|
||||
let colorPickerEl: HTMLInputElement;
|
||||
let swatchEl: HTMLSpanElement;
|
||||
|
||||
const setting = new Setting(containerEl)
|
||||
.setName(`Color ${index + 1}`)
|
||||
.addText((text) => {
|
||||
text.setPlaceholder("Meaning")
|
||||
.setValue(entry.name)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.colors[index].name = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
text.inputEl.setCssStyles({ width: "120px" });
|
||||
text.inputEl.setAttribute("aria-label", "Color meaning / name");
|
||||
})
|
||||
.addText((hex) => {
|
||||
hex.setPlaceholder("#rrggbb")
|
||||
.setValue(entry.color)
|
||||
.onChange(async (value) => {
|
||||
const normalized = value.trim();
|
||||
if (!/^#[0-9a-fA-F]{6}$/.test(normalized)) return;
|
||||
this.plugin.settings.colors[index].color = normalized;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.applyAllColors();
|
||||
if (colorPickerEl) colorPickerEl.value = normalized;
|
||||
if (swatchEl) swatchEl.style.backgroundColor = normalized;
|
||||
});
|
||||
hex.inputEl.setCssStyles({ width: "88px", fontFamily: "monospace" });
|
||||
hex.inputEl.setAttribute("aria-label", "Hex color code");
|
||||
hexInputEl = hex.inputEl;
|
||||
})
|
||||
.addColorPicker((picker) => {
|
||||
picker
|
||||
.setValue(entry.color)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.colors[index].color = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.applyAllColors();
|
||||
if (hexInputEl) hexInputEl.value = value;
|
||||
if (swatchEl) swatchEl.style.backgroundColor = value;
|
||||
});
|
||||
});
|
||||
|
||||
swatchEl = setting.controlEl.createEl("span", {
|
||||
cls: "color-tab-settings-swatch",
|
||||
});
|
||||
swatchEl.style.backgroundColor = entry.color;
|
||||
|
||||
colorPickerEl = setting.controlEl.querySelector(
|
||||
"input[type=color]"
|
||||
) as HTMLInputElement;
|
||||
|
||||
colorPickerEl?.addEventListener("input", () => {
|
||||
if (hexInputEl) hexInputEl.value = colorPickerEl.value;
|
||||
if (swatchEl) swatchEl.style.backgroundColor = colorPickerEl.value;
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Auto-pin colored tabs")
|
||||
.setDesc(
|
||||
"When enabled, applying a tab color pins the tab and removing color unpins it."
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.autoPinColoredTabs)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.autoPinColoredTabs = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.applyAllColors();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Prevent Tab Duplication")
|
||||
.setDesc(
|
||||
"When enabled, opening an already-open file will focus on the existing tab instead of creating a duplicate."
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle
|
||||
.setValue(this.plugin.settings.preventTabDuplication)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.preventTabDuplication = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Reset to defaults")
|
||||
.setDesc("Restore the original pastel color palette.")
|
||||
.addButton((btn) => {
|
||||
btn.setButtonText("Reset")
|
||||
.setDestructive()
|
||||
.onClick(async () => {
|
||||
this.plugin.settings.colors = DEFAULT_COLORS.map(
|
||||
(c) => ({ ...c })
|
||||
);
|
||||
this.plugin.settings.autoPinColoredTabs =
|
||||
DEFAULT_SETTINGS.autoPinColoredTabs;
|
||||
this.plugin.settings.preventTabDuplication =
|
||||
DEFAULT_SETTINGS.preventTabDuplication;
|
||||
await this.plugin.saveSettings();
|
||||
this.plugin.applyAllColors();
|
||||
this.display();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "color-tab",
|
||||
"name": "Color Tab",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "1.13.0",
|
||||
"description": "Colorize open tabs to visually distinguish notes at a glance, using a right-click context menu.",
|
||||
"author": "Rafael Ordaz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "color-tab",
|
||||
"version": "1.0.4",
|
||||
"version": "1.1.0",
|
||||
"description": "Obsidian plugin to colorize open tabs",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue