From 782e9f14a8d103f44e87f0af0595650675324573 Mon Sep 17 00:00:00 2001 From: Aleix Soler Date: Wed, 18 Mar 2026 19:02:33 +0100 Subject: [PATCH] fix: preserve original template IDs during migration and installation for legacy compatibility --- components/SettingsUI/TemplateSettings.tsx | 25 +- constants/defaultSettings.ts | 38 +- constants/predefinedTemplates.ts | 398 ++++++++++----------- core/settingsService.ts | 101 +++++- 4 files changed, 318 insertions(+), 244 deletions(-) diff --git a/components/SettingsUI/TemplateSettings.tsx b/components/SettingsUI/TemplateSettings.tsx index 033c79a..32c4a82 100644 --- a/components/SettingsUI/TemplateSettings.tsx +++ b/components/SettingsUI/TemplateSettings.tsx @@ -69,7 +69,15 @@ export const TemplateSettings: React.FC = ({ ); return; } - const newId = generateTemplateId(template.name, settings.templates); + + // Try to use the original ID if it's unique, otherwise generate a new one + const isIdUnique = !settings.templates.some( + (t) => t.id === template.id, + ); + const newId = isIdUnique + ? template.id + : generateTemplateId(template.name, settings.templates); + const installedTemplate: StatusTemplate = { ...template, id: newId, @@ -105,11 +113,16 @@ export const TemplateSettings: React.FC = ({ ); onChange("templates", updatedTemplates); } else { - // Add new template - generate unique ID - const uniqueId = generateTemplateId( - template.name, - settings.templates, - ); + // Add new template + // Try to use the original ID if it's unique, otherwise generate a new one + const isIdUnique = + template.id && + !settings.templates.some((t) => t.id === template.id); + + const uniqueId = isIdUnique + ? template.id + : generateTemplateId(template.name, settings.templates); + finalTemplate = { ...template, id: uniqueId }; // Update statuses with the final template ID diff --git a/constants/defaultSettings.ts b/constants/defaultSettings.ts index a28f31f..4264818 100644 --- a/constants/defaultSettings.ts +++ b/constants/defaultSettings.ts @@ -14,41 +14,7 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = { showStatusBar: true, autoHideStatusBar: false, enableStatusOverviewPopup: true, - templates: [ - { - id: "default-starter", - name: "Starter Template", - description: - "A simplified set of essential workflow statuses to get you started.", - authorGithub: "soler1212", - statuses: [ - { - name: "todo", - icon: "πŸ“Œ", - color: "#F44336", - templateId: "default-starter", - }, - { - name: "inProgress", - icon: "βš™οΈ", - color: "#2196F3", - templateId: "default-starter", - }, - { - name: "review", - icon: "πŸ‘€", - color: "#9C27B0", - templateId: "default-starter", - }, - { - name: "done", - icon: "βœ“", - color: "#4CAF50", - templateId: "default-starter", - }, - ], - }, - ], + templates: [], customStatuses: [], showStatusIconsInExplorer: true, hideUnknownStatusInExplorer: true, // Default to hide unknown status @@ -60,7 +26,7 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = { enableExperimentalFeatures: true, enableStatusDashboard: true, enableGroupedStatusView: true, - enabledTemplates: ["default-starter"], + enabledTemplates: [], useCustomStatusesOnly: false, useMultipleStatuses: true, singleStatusStorageMode: "list", diff --git a/constants/predefinedTemplates.ts b/constants/predefinedTemplates.ts index a5753fb..e6a914e 100644 --- a/constants/predefinedTemplates.ts +++ b/constants/predefinedTemplates.ts @@ -6,275 +6,273 @@ import { StatusTemplate } from "types/pluginSettings"; export const PREDEFINED_TEMPLATES: StatusTemplate[] = [ { - id: "academic", - name: "Academic research", - description: "Status workflow for academic research and writing", - authorGithub: "soler1212", - statuses: [ + "id": "academic", + "name": "Academic research", + "description": "Status workflow for academic research and writing", + "authorGithub": "soler1212", + "statuses": [ { - name: "research", - icon: "πŸ”", - color: "#2196F3", - templateId: "academic", + "name": "research", + "icon": "πŸ”", + "color": "#2196F3", + "templateId": "academic" }, { - name: "outline", - icon: "πŸ“‘", - color: "#9E9E9E", - templateId: "academic", + "name": "outline", + "icon": "πŸ“‘", + "color": "#9E9E9E", + "templateId": "academic" }, { - name: "draft", - icon: "✏️", - color: "#FFC107", - templateId: "academic", + "name": "draft", + "icon": "✏️", + "color": "#FFC107", + "templateId": "academic" }, { - name: "review", - icon: "πŸ”¬", - color: "#9C27B0", - templateId: "academic", + "name": "review", + "icon": "πŸ”¬", + "color": "#9C27B0", + "templateId": "academic" }, { - name: "revision", - icon: "πŸ“", - color: "#FF5722", - templateId: "academic", + "name": "revision", + "icon": "πŸ“", + "color": "#FF5722", + "templateId": "academic" }, { - name: "final", - icon: "πŸ“š", - color: "#4CAF50", - templateId: "academic", + "name": "final", + "icon": "πŸ“š", + "color": "#4CAF50", + "templateId": "academic" }, { - name: "published", - icon: "πŸŽ“", - color: "#795548", - templateId: "academic", - }, - ], + "name": "published", + "icon": "πŸŽ“", + "color": "#795548", + "templateId": "academic" + } + ] }, { - id: "colorful", - name: "Colorful workflow", - description: - "A colorful set of workflow statuses with descriptive icons", - authorGithub: "soler1212", - statuses: [ + "id": "colorful", + "name": "Colorful workflow", + "description": "A colorful set of workflow statuses with descriptive icons", + "authorGithub": "soler1212", + "statuses": [ { - name: "idea", - icon: "πŸ’‘", - color: "#FFEB3B", - templateId: "colorful", + "name": "idea", + "icon": "πŸ’‘", + "color": "#FFEB3B", + "templateId": "colorful" }, { - name: "draft", - icon: "πŸ“", - color: "#E0E0E0", - templateId: "colorful", + "name": "draft", + "icon": "πŸ“", + "color": "#E0E0E0", + "templateId": "colorful" }, { - name: "inProgress", - icon: "πŸ”§", - color: "#FFC107", - templateId: "colorful", + "name": "inProgress", + "icon": "πŸ”§", + "color": "#FFC107", + "templateId": "colorful" }, { - name: "editing", - icon: "πŸ–ŠοΈ", - color: "#2196F3", - templateId: "colorful", + "name": "editing", + "icon": "πŸ–ŠοΈ", + "color": "#2196F3", + "templateId": "colorful" }, { - name: "pending", - icon: "⏳", - color: "#9C27B0", - templateId: "colorful", + "name": "pending", + "icon": "⏳", + "color": "#9C27B0", + "templateId": "colorful" }, { - name: "onHold", - icon: "⏸", - color: "#9E9E9E", - templateId: "colorful", + "name": "onHold", + "icon": "⏸", + "color": "#9E9E9E", + "templateId": "colorful" }, { - name: "needsUpdate", - icon: "πŸ”„", - color: "#FF5722", - templateId: "colorful", + "name": "needsUpdate", + "icon": "πŸ”„", + "color": "#FF5722", + "templateId": "colorful" }, { - name: "completed", - icon: "βœ…", - color: "#4CAF50", - templateId: "colorful", + "name": "completed", + "icon": "βœ…", + "color": "#4CAF50", + "templateId": "colorful" }, { - name: "archived", - icon: "πŸ“¦", - color: "#795548", - templateId: "colorful", - }, - ], + "name": "archived", + "icon": "πŸ“¦", + "color": "#795548", + "templateId": "colorful" + } + ] }, { - id: "creative-writing", - name: "Creative Writing", - description: "Workflow for novelists and creative writers", - authorGithub: "soler1212", - statuses: [ + "id": "creative-writing", + "name": "Creative Writing", + "description": "Workflow for novelists and creative writers", + "authorGithub": "soler1212", + "statuses": [ { - name: "idea", - icon: "🧠", - color: "#FFD700", - templateId: "creative-writing", + "name": "idea", + "icon": "🧠", + "color": "#FFD700", + "templateId": "creative-writing" }, { - name: "outline", - icon: "πŸ—ΊοΈ", - color: "#87CEEB", - templateId: "creative-writing", + "name": "outline", + "icon": "πŸ—ΊοΈ", + "color": "#87CEEB", + "templateId": "creative-writing" }, { - name: "first-draft", - icon: "✍️", - color: "#FFA07A", - templateId: "creative-writing", + "name": "first-draft", + "icon": "✍️", + "color": "#FFA07A", + "templateId": "creative-writing" }, { - name: "revision", - icon: "πŸ”", - color: "#DA70D6", - templateId: "creative-writing", + "name": "revision", + "icon": "πŸ”", + "color": "#DA70D6", + "templateId": "creative-writing" }, { - name: "final-polish", - icon: "✨", - color: "#32CD32", - templateId: "creative-writing", - }, - ], + "name": "final-polish", + "icon": "✨", + "color": "#32CD32", + "templateId": "creative-writing" + } + ] }, { - id: "minimal", - name: "Minimal workflow", - description: "A simplified set of essential workflow statuses", - authorGithub: "soler1212", - statuses: [ + "id": "default-starter", + "name": "Starter Template", + "description": "A simplified set of essential workflow statuses to get you started.", + "authorGithub": "soler1212", + "statuses": [ { - name: "todo", - icon: "πŸ“Œ", - color: "#F44336", - templateId: "minimal", + "name": "todo", + "icon": "πŸ“Œ", + "color": "#F44336", + "templateId": "default-starter" }, { - name: "inProgress", - icon: "βš™οΈ", - color: "#2196F3", - templateId: "minimal", + "name": "inProgress", + "icon": "βš™οΈ", + "color": "#2196F3", + "templateId": "default-starter" }, { - name: "review", - icon: "πŸ‘€", - color: "#9C27B0", - templateId: "minimal", + "name": "review", + "icon": "πŸ‘€", + "color": "#9C27B0", + "templateId": "default-starter" }, { - name: "done", - icon: "βœ“", - color: "#4CAF50", - templateId: "minimal", - }, - ], + "name": "done", + "icon": "βœ“", + "color": "#4CAF50", + "templateId": "default-starter" + } + ] }, { - id: "project", - name: "Project management", - description: "Status workflow for project management and tracking", - authorGithub: "soler1212", - statuses: [ + "id": "minimal", + "name": "Minimal workflow", + "description": "A simplified set of essential workflow statuses", + "authorGithub": "soler1212", + "statuses": [ { - name: "planning", - icon: "πŸ—“οΈ", - color: "#9E9E9E", - templateId: "project", + "name": "todo", + "icon": "πŸ“Œ", + "color": "#F44336", + "templateId": "minimal" }, { - name: "backlog", - icon: "πŸ“‹", - color: "#E0E0E0", - templateId: "project", + "name": "inProgress", + "icon": "βš™οΈ", + "color": "#2196F3", + "templateId": "minimal" }, { - name: "ready", - icon: "🚦", - color: "#8BC34A", - templateId: "project", + "name": "review", + "icon": "πŸ‘€", + "color": "#9C27B0", + "templateId": "minimal" }, { - name: "inDevelopment", - icon: "πŸ‘¨β€πŸ’»", - color: "#2196F3", - templateId: "project", - }, - { - name: "testing", - icon: "πŸ§ͺ", - color: "#9C27B0", - templateId: "project", - }, - { - name: "review", - icon: "πŸ‘οΈ", - color: "#FFC107", - templateId: "project", - }, - { - name: "approved", - icon: "πŸ‘", - color: "#4CAF50", - templateId: "project", - }, - { - name: "live", - icon: "πŸš€", - color: "#3F51B5", - templateId: "project", - }, - ], + "name": "done", + "icon": "βœ“", + "color": "#4CAF50", + "templateId": "minimal" + } + ] }, { - id: "default-starter", - name: "Starter Template", - description: - "A simplified set of essential workflow statuses to get you started.", - authorGithub: "soler1212", - statuses: [ + "id": "project", + "name": "Project management", + "description": "Status workflow for project management and tracking", + "authorGithub": "soler1212", + "statuses": [ { - name: "todo", - icon: "πŸ“Œ", - color: "#F44336", - templateId: "default-starter", + "name": "planning", + "icon": "πŸ—“οΈ", + "color": "#9E9E9E", + "templateId": "project" }, { - name: "inProgress", - icon: "βš™οΈ", - color: "#2196F3", - templateId: "default-starter", + "name": "backlog", + "icon": "πŸ“‹", + "color": "#E0E0E0", + "templateId": "project" }, { - name: "review", - icon: "πŸ‘€", - color: "#9C27B0", - templateId: "default-starter", + "name": "ready", + "icon": "🚦", + "color": "#8BC34A", + "templateId": "project" }, { - name: "done", - icon: "βœ“", - color: "#4CAF50", - templateId: "default-starter", + "name": "inDevelopment", + "icon": "πŸ‘¨β€πŸ’»", + "color": "#2196F3", + "templateId": "project" }, - ], - }, + { + "name": "testing", + "icon": "πŸ§ͺ", + "color": "#9C27B0", + "templateId": "project" + }, + { + "name": "review", + "icon": "πŸ‘οΈ", + "color": "#FFC107", + "templateId": "project" + }, + { + "name": "approved", + "icon": "πŸ‘", + "color": "#4CAF50", + "templateId": "project" + }, + { + "name": "live", + "icon": "πŸš€", + "color": "#3F51B5", + "templateId": "project" + } + ] + } ]; diff --git a/core/settingsService.ts b/core/settingsService.ts index 6d440ec..1fcba68 100644 --- a/core/settingsService.ts +++ b/core/settingsService.ts @@ -1,5 +1,6 @@ import { DEFAULT_PLUGIN_SETTINGS } from "@/constants/defaultSettings"; import { PluginSettings, SyncGroup } from "@/types/pluginSettings"; +import { PREDEFINED_TEMPLATES } from "@/constants/predefinedTemplates"; import { Plugin, TFile, normalizePath } from "obsidian"; import eventBus from "./eventBus"; @@ -64,7 +65,8 @@ class SettingsService { async initialize(plugin: Plugin): Promise { this.plugin = plugin; - await this.loadSettings(); + const loadedData = await this.loadSettings(); + this.migrateLegacySettings(loadedData); if (this.settings.enableExternalStatusSync) { await this.loadFromExternalFile(); @@ -104,7 +106,102 @@ class SettingsService { const loadedData = await this.plugin.loadData(); this.settings = this.mergeSettings(DEFAULT_PLUGIN_SETTINGS, loadedData); this.deduplicateTemplates(); - return this.settings; + return loadedData; + } + + /** + * Migrates legacy settings and ensures new users have a starter template. + */ + private migrateLegacySettings(loadedData: Partial | null) { + const hasTemplates = + this.settings.templates && this.settings.templates.length > 0; + + // 1. If it's a truly new user (no data at all), give them the starter template + if (!loadedData) { + this.injectStarterTemplate(); + return; + } + + // 2. If it's an existing user but has no templates, they were using the old defaults + if (!hasTemplates) { + const legacyIds = [ + "colorful", + "minimal", + "academic", + "project", + "creative-writing", + ]; + const enabledLegacy = (this.settings.enabledTemplates || []).filter( + (id) => legacyIds.includes(id), + ); + + if (enabledLegacy.length > 0) { + // Restore the ones they had enabled from the marketplace list + const toRestore = PREDEFINED_TEMPLATES.filter((t) => + enabledLegacy.includes(t.id), + ).map((t) => ({ + ...t, + id: t.id, // Keep original ID for legacy compatibility + statuses: t.statuses.map((s) => ({ + ...s, + templateId: t.id, + })), + })); + + this.settings.templates = toRestore; + // Update enabled list + this.settings.enabledTemplates = toRestore.map((t) => t.id); + this.saveSettings().catch(console.error); + } else { + // If nothing was enabled, just give them the starter template + this.injectStarterTemplate(); + } + } + } + + /** + * Injects the default starter template. + */ + private injectStarterTemplate() { + const starterTemplate = { + id: "starter", + name: "Starter Template", + description: + "A simplified set of essential workflow statuses to get you started.", + authorGithub: "soler1212", + statuses: [ + { + name: "todo", + icon: "πŸ“Œ", + color: "#F44336", + templateId: "starter", + }, + { + name: "inProgress", + icon: "βš™οΈ", + color: "#2196F3", + templateId: "starter", + }, + { + name: "review", + icon: "πŸ‘€", + color: "#9C27B0", + templateId: "starter", + }, + { + name: "done", + icon: "βœ“", + color: "#4CAF50", + templateId: "starter", + }, + ], + }; + + this.settings.templates = [starterTemplate]; + if (!this.settings.enabledTemplates.includes(starterTemplate.id)) { + this.settings.enabledTemplates.push(starterTemplate.id); + } + this.saveSettings().catch(console.error); } /**