mirror of
https://github.com/devonthesofa/obsidian-note-status.git
synced 2026-07-22 12:30:24 +00:00
feat: implement selective settings synchronization
This commit is contained in:
parent
403c4113e4
commit
e5c456b0aa
4 changed files with 179 additions and 11 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { PluginSettings } from "@/types/pluginSettings";
|
||||
import { PluginSettings, SyncGroup } from "@/types/pluginSettings";
|
||||
import React from "react";
|
||||
import { SettingItem } from "./SettingItem";
|
||||
import settingsService from "@/core/settingsService";
|
||||
|
|
@ -22,18 +22,52 @@ export const SynchronizationSettings: React.FC<Props> = ({
|
|||
alert("Plugin settings imported successfully.");
|
||||
};
|
||||
|
||||
const toggleGroup = (group: SyncGroup) => {
|
||||
const currentGroups = [...settings.syncGroups];
|
||||
const index = currentGroups.indexOf(group);
|
||||
if (index === -1) {
|
||||
currentGroups.push(group);
|
||||
} else {
|
||||
currentGroups.splice(index, 1);
|
||||
}
|
||||
onChange("syncGroups", currentGroups);
|
||||
};
|
||||
|
||||
const syncGroups: { id: SyncGroup; label: string; description: string }[] =
|
||||
[
|
||||
{
|
||||
id: "statuses",
|
||||
label: "Statuses & Templates",
|
||||
description: "Custom statuses, templates, and workflow rules.",
|
||||
},
|
||||
{
|
||||
id: "appearance",
|
||||
label: "Appearance",
|
||||
description: "Colors, icons, and UI element styles.",
|
||||
},
|
||||
{
|
||||
id: "behavior",
|
||||
label: "Behavior & Storage",
|
||||
description: "Frontmatter keys, mappings, and general logic.",
|
||||
},
|
||||
{
|
||||
id: "features",
|
||||
label: "Commands & Features",
|
||||
description: "Quick commands and experimental feature toggles.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>Multi-device Synchronization</h3>
|
||||
<p>
|
||||
Keep all your plugin settings (statuses, templates, UI
|
||||
preferences, etc.) in sync across devices using a file in your
|
||||
vault.
|
||||
Keep your plugin settings in sync across devices using a file in
|
||||
your vault.
|
||||
</p>
|
||||
|
||||
<SettingItem
|
||||
name="Enable external synchronization"
|
||||
description="Automatically save and load all plugin settings from a file in your vault."
|
||||
description="Automatically save and load selected plugin settings from a file in your vault."
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
@ -61,12 +95,69 @@ export const SynchronizationSettings: React.FC<Props> = ({
|
|||
/>
|
||||
</SettingItem>
|
||||
|
||||
<div className="sync-groups-container" style={{ margin: "20px 0" }}>
|
||||
<h4>Selective Synchronization</h4>
|
||||
<p className="setting-item-description">
|
||||
Choose which groups of settings should be included in the
|
||||
synchronization.
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: "10px",
|
||||
marginTop: "10px",
|
||||
}}
|
||||
>
|
||||
{syncGroups.map((group) => (
|
||||
<div
|
||||
key={group.id}
|
||||
className={`selectable-group-item ${
|
||||
settings.syncGroups.includes(group.id)
|
||||
? "is-selected"
|
||||
: ""
|
||||
}`}
|
||||
style={{
|
||||
padding: "10px",
|
||||
border: "1px solid var(--background-modifier-border)",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
backgroundColor: settings.syncGroups.includes(
|
||||
group.id,
|
||||
)
|
||||
? "var(--background-modifier-hover)"
|
||||
: "transparent",
|
||||
}}
|
||||
onClick={() => toggleGroup(group.id)}
|
||||
>
|
||||
<div style={{ fontWeight: "bold" }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.syncGroups.includes(
|
||||
group.id,
|
||||
)}
|
||||
onChange={() => {}} // Handled by parent div onClick
|
||||
style={{ marginRight: "8px" }}
|
||||
/>
|
||||
{group.label}
|
||||
</div>
|
||||
<div
|
||||
className="setting-item-description"
|
||||
style={{ marginTop: "4px", fontSize: "0.85em" }}
|
||||
>
|
||||
{group.description}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="note-status-settings__actions"
|
||||
style={{ marginTop: "1em", display: "flex", gap: "10px" }}
|
||||
>
|
||||
<button onClick={handleExport}>📤 Export to file now</button>
|
||||
<button onClick={handleImport}>📥 Import from file now</button>
|
||||
<button onClick={handleExport}>📤 Export selected now</button>
|
||||
<button onClick={handleImport}>📥 Import selected now</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -57,4 +57,5 @@ export const DEFAULT_PLUGIN_SETTINGS: PluginSettings = {
|
|||
writeMappedTagsToDefault: false,
|
||||
enableExternalStatusSync: false,
|
||||
externalStatusSyncPath: "_note-status-sync.json",
|
||||
syncGroups: ["statuses", "appearance", "behavior", "features"],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,61 @@
|
|||
import { DEFAULT_PLUGIN_SETTINGS } from "@/constants/defaultSettings";
|
||||
import { PluginSettings } from "@/types/pluginSettings";
|
||||
import { PluginSettings, SyncGroup } from "@/types/pluginSettings";
|
||||
import { Plugin, TFile, normalizePath } from "obsidian";
|
||||
import eventBus from "./eventBus";
|
||||
|
||||
export const SETTINGS_GROUPS: Record<SyncGroup, (keyof PluginSettings)[]> = {
|
||||
statuses: [
|
||||
"templates",
|
||||
"customStatuses",
|
||||
"enabledTemplates",
|
||||
"useCustomStatusesOnly",
|
||||
"useMultipleStatuses",
|
||||
"singleStatusStorageMode",
|
||||
"strictStatuses",
|
||||
],
|
||||
appearance: [
|
||||
"fileExplorerIconPosition",
|
||||
"fileExplorerIconFrame",
|
||||
"fileExplorerIconColorMode",
|
||||
"statusColors",
|
||||
"showStatusBar",
|
||||
"autoHideStatusBar",
|
||||
"statusBarShowTemplateName",
|
||||
"statusBarBadgeStyle",
|
||||
"statusBarBadgeContentMode",
|
||||
"showStatusIconsInExplorer",
|
||||
"hideUnknownStatusInExplorer",
|
||||
"fileExplorerColorFileName",
|
||||
"fileExplorerColorBlock",
|
||||
"fileExplorerLeftBorder",
|
||||
"fileExplorerStatusDot",
|
||||
"fileExplorerUnderlineFileName",
|
||||
"unknownStatusIcon",
|
||||
"unknownStatusLucideIcon",
|
||||
"unknownStatusColor",
|
||||
"statusBarNoStatusText",
|
||||
"statusBarShowNoStatusIcon",
|
||||
"statusBarShowNoStatusText",
|
||||
"showEditorToolbarButton",
|
||||
"editorToolbarButtonPosition",
|
||||
"editorToolbarButtonDisplay",
|
||||
],
|
||||
behavior: [
|
||||
"tagPrefix",
|
||||
"statusFrontmatterMappings",
|
||||
"writeMappedTagsToDefault",
|
||||
"applyStatusRecursivelyToSubfolders",
|
||||
"vaultSizeLimit",
|
||||
"enableStatusOverviewPopup",
|
||||
],
|
||||
features: [
|
||||
"quickStatusCommands",
|
||||
"enableExperimentalFeatures",
|
||||
"enableStatusDashboard",
|
||||
"enableGroupedStatusView",
|
||||
],
|
||||
};
|
||||
|
||||
class SettingsService {
|
||||
private plugin: Plugin;
|
||||
public settings: PluginSettings;
|
||||
|
|
@ -84,8 +137,22 @@ class SettingsService {
|
|||
if (!this.settings.enableExternalStatusSync && !force) return;
|
||||
|
||||
const path = normalizePath(this.settings.externalStatusSyncPath);
|
||||
|
||||
// Filter settings based on selected groups
|
||||
const keysToSync = new Set<keyof PluginSettings>();
|
||||
this.settings.syncGroups.forEach((group) => {
|
||||
SETTINGS_GROUPS[group].forEach((key) => keysToSync.add(key));
|
||||
});
|
||||
|
||||
const filteredSettings: Partial<PluginSettings> = {};
|
||||
keysToSync.forEach((key) => {
|
||||
(filteredSettings as Record<string, unknown>)[key] =
|
||||
this.settings[key];
|
||||
});
|
||||
|
||||
const data = {
|
||||
...this.settings,
|
||||
...filteredSettings,
|
||||
syncGroups: this.settings.syncGroups, // Always include meta-settings
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
|
|
@ -108,13 +175,19 @@ class SettingsService {
|
|||
const content = await this.plugin.app.vault.adapter.read(path);
|
||||
const data = JSON.parse(content);
|
||||
|
||||
// Determine which keys we should actually apply based on CURRENT settings
|
||||
const allowedKeys = new Set<keyof PluginSettings>();
|
||||
this.settings.syncGroups.forEach((group) => {
|
||||
SETTINGS_GROUPS[group].forEach((key) => allowedKeys.add(key));
|
||||
});
|
||||
|
||||
let hasChanged = false;
|
||||
const keys = Object.keys(data).filter(
|
||||
(k) => k !== "updatedAt",
|
||||
(k) => k !== "updatedAt" && k !== "syncGroups",
|
||||
) as Array<keyof PluginSettings>;
|
||||
|
||||
for (const key of keys) {
|
||||
if (!(key in this.settings)) continue;
|
||||
if (!(key in this.settings) || !allowedKeys.has(key)) continue;
|
||||
|
||||
const newValue = data[key];
|
||||
const currentValue = this.settings[key];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export interface StatusTemplate {
|
|||
statuses: NoteStatus[];
|
||||
}
|
||||
|
||||
export type SyncGroup = "statuses" | "appearance" | "behavior" | "features";
|
||||
|
||||
export type PluginSettings = {
|
||||
fileExplorerIconPosition:
|
||||
| "absolute-right"
|
||||
|
|
@ -72,5 +74,6 @@ export type PluginSettings = {
|
|||
writeMappedTagsToDefault: boolean; // Whether mapped tags should also write to the default tag
|
||||
enableExternalStatusSync: boolean; // Whether to sync statuses to an external file
|
||||
externalStatusSyncPath: string; // Path to the external sync file
|
||||
syncGroups: SyncGroup[]; // Selected groups of settings to synchronize
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue