mirror of
https://github.com/firstsun-dev/git-files-sync.git
synced 2026-07-22 17:20:30 +00:00
feat(settings): add folder picker for root path and vault folder settings
Attach an AbstractInputSuggest to the "Root path" and "Vault folder" text inputs so typing shows a filtered dropdown of existing vault folders, sourced from app.vault.getAllFolders(). Selecting a suggestion fills the field and dispatches an input event so the existing onChange/save/initializeGitService flow still fires. Free typing is still accepted for paths that don't exist yet (e.g. a not-yet-created repo subfolder for "Root path"). Closes #48 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011BNWwPd5zudtW2JQr6ZAUm
This commit is contained in:
parent
ef238cea59
commit
c107979427
3 changed files with 71 additions and 15 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import {App, PluginSettingTab, Setting, Notice, TextComponent} from 'obsidian';
|
||||
import GitLabFilesPush from "./main";
|
||||
import {FolderSuggest} from "./ui/FolderSuggest";
|
||||
|
||||
// Minimal shape of Obsidian >= 1.13's SettingDefinitionItem. Declared locally so
|
||||
// the plugin still type-checks against older Obsidian typings (minAppVersion
|
||||
|
|
@ -162,25 +163,29 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
|||
new Setting(containerEl)
|
||||
.setName('Root path')
|
||||
.setDesc('Optional: subfolder in repository (e.g. "notes")')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter subfolder path')
|
||||
.setValue(this.plugin.settings.rootPath)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.rootPath = value.replace(/^\/|\/$/g, '');
|
||||
void this.plugin.saveSettings();
|
||||
this.plugin.initializeGitService();
|
||||
}));
|
||||
.addText(text => {
|
||||
text.setPlaceholder('Enter subfolder path')
|
||||
.setValue(this.plugin.settings.rootPath)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.rootPath = value.replace(/^\/|\/$/g, '');
|
||||
void this.plugin.saveSettings();
|
||||
this.plugin.initializeGitService();
|
||||
});
|
||||
FolderSuggest.attach(this.app, text.inputEl);
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Vault folder')
|
||||
.setDesc('Optional: only sync files in this vault folder (e.g. "sync" to only sync files in the sync folder)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Leave empty to sync all files')
|
||||
.setValue(this.plugin.settings.vaultFolder)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.vaultFolder = value.replace(/^\/|\/$/g, '');
|
||||
void this.plugin.saveSettings();
|
||||
}));
|
||||
.addText(text => {
|
||||
text.setPlaceholder('Leave empty to sync all files')
|
||||
.setValue(this.plugin.settings.vaultFolder)
|
||||
.onChange((value) => {
|
||||
this.plugin.settings.vaultFolder = value.replace(/^\/|\/$/g, '');
|
||||
void this.plugin.saveSettings();
|
||||
});
|
||||
FolderSuggest.attach(this.app, text.inputEl);
|
||||
});
|
||||
|
||||
// "Real symlink" needs the Git Data API, which only GitHub offers. For
|
||||
// other providers, offer follow/skip only so the option can't mislead.
|
||||
|
|
|
|||
38
src/ui/FolderSuggest.ts
Normal file
38
src/ui/FolderSuggest.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import {AbstractInputSuggest, App, TFolder} from 'obsidian';
|
||||
|
||||
/**
|
||||
* Type-ahead folder suggester for a settings text input. Suggests existing
|
||||
* vault folders but never forces a selection, since callers (e.g. the "Root
|
||||
* path" repo setting) may need to accept a path that doesn't exist locally.
|
||||
*/
|
||||
export class FolderSuggest extends AbstractInputSuggest<TFolder> {
|
||||
constructor(app: App, private readonly inputEl: HTMLInputElement) {
|
||||
super(app, inputEl);
|
||||
}
|
||||
|
||||
protected getSuggestions(query: string): TFolder[] {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return this.app.vault.getAllFolders(true)
|
||||
.filter(folder => folder.path.toLowerCase().contains(lowerQuery))
|
||||
.sort((a, b) => a.path.localeCompare(b.path));
|
||||
}
|
||||
|
||||
renderSuggestion(folder: TFolder, el: HTMLElement): void {
|
||||
el.setText(folder.path === '/' ? '/' : folder.path);
|
||||
}
|
||||
|
||||
selectSuggestion(folder: TFolder): void {
|
||||
const path = folder.path === '/' ? '' : folder.path;
|
||||
this.setValue(path);
|
||||
// TextComponent listens for the native "input" event to fire onChange,
|
||||
// so dispatch one to trigger the existing save/initializeGitService flow.
|
||||
this.inputEl.dispatchEvent(new Event('input'));
|
||||
this.close();
|
||||
}
|
||||
|
||||
/** Attaches a FolderSuggest to `inputEl`; the instance self-registers via the base class, so the caller has nothing to hold onto. */
|
||||
static attach(app: App, inputEl: HTMLInputElement): void {
|
||||
// eslint-disable-next-line sonarjs/constructor-for-side-effects -- AbstractInputSuggest wires itself to inputEl in its constructor; there's nothing to assign.
|
||||
new FolderSuggest(app, inputEl);
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,17 @@ export const App = class {
|
|||
};
|
||||
|
||||
export const TFile = class {};
|
||||
export const TFolder = class {};
|
||||
export const AbstractInputSuggest = class {
|
||||
app: unknown;
|
||||
constructor(app: unknown) {
|
||||
this.app = app;
|
||||
}
|
||||
setValue() {}
|
||||
getValue() { return ''; }
|
||||
close() {}
|
||||
open() {}
|
||||
};
|
||||
export const requestUrl = vi.fn();
|
||||
export const setTooltip = vi.fn();
|
||||
export const setIcon = vi.fn();
|
||||
|
|
@ -68,6 +79,8 @@ vi.mock('obsidian', () => ({
|
|||
Editor,
|
||||
App,
|
||||
TFile,
|
||||
TFolder,
|
||||
AbstractInputSuggest,
|
||||
requestUrl,
|
||||
setTooltip,
|
||||
setIcon,
|
||||
|
|
|
|||
Loading…
Reference in a new issue