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 {App, PluginSettingTab, Setting, Notice, TextComponent} from 'obsidian';
|
||||||
import GitLabFilesPush from "./main";
|
import GitLabFilesPush from "./main";
|
||||||
|
import {FolderSuggest} from "./ui/FolderSuggest";
|
||||||
|
|
||||||
// Minimal shape of Obsidian >= 1.13's SettingDefinitionItem. Declared locally so
|
// Minimal shape of Obsidian >= 1.13's SettingDefinitionItem. Declared locally so
|
||||||
// the plugin still type-checks against older Obsidian typings (minAppVersion
|
// the plugin still type-checks against older Obsidian typings (minAppVersion
|
||||||
|
|
@ -162,25 +163,29 @@ export class GitLabSyncSettingTab extends PluginSettingTab {
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Root path')
|
.setName('Root path')
|
||||||
.setDesc('Optional: subfolder in repository (e.g. "notes")')
|
.setDesc('Optional: subfolder in repository (e.g. "notes")')
|
||||||
.addText(text => text
|
.addText(text => {
|
||||||
.setPlaceholder('Enter subfolder path')
|
text.setPlaceholder('Enter subfolder path')
|
||||||
.setValue(this.plugin.settings.rootPath)
|
.setValue(this.plugin.settings.rootPath)
|
||||||
.onChange((value) => {
|
.onChange((value) => {
|
||||||
this.plugin.settings.rootPath = value.replace(/^\/|\/$/g, '');
|
this.plugin.settings.rootPath = value.replace(/^\/|\/$/g, '');
|
||||||
void this.plugin.saveSettings();
|
void this.plugin.saveSettings();
|
||||||
this.plugin.initializeGitService();
|
this.plugin.initializeGitService();
|
||||||
}));
|
});
|
||||||
|
FolderSuggest.attach(this.app, text.inputEl);
|
||||||
|
});
|
||||||
|
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName('Vault folder')
|
.setName('Vault folder')
|
||||||
.setDesc('Optional: only sync files in this vault folder (e.g. "sync" to only sync files in the sync folder)')
|
.setDesc('Optional: only sync files in this vault folder (e.g. "sync" to only sync files in the sync folder)')
|
||||||
.addText(text => text
|
.addText(text => {
|
||||||
.setPlaceholder('Leave empty to sync all files')
|
text.setPlaceholder('Leave empty to sync all files')
|
||||||
.setValue(this.plugin.settings.vaultFolder)
|
.setValue(this.plugin.settings.vaultFolder)
|
||||||
.onChange((value) => {
|
.onChange((value) => {
|
||||||
this.plugin.settings.vaultFolder = value.replace(/^\/|\/$/g, '');
|
this.plugin.settings.vaultFolder = value.replace(/^\/|\/$/g, '');
|
||||||
void this.plugin.saveSettings();
|
void this.plugin.saveSettings();
|
||||||
}));
|
});
|
||||||
|
FolderSuggest.attach(this.app, text.inputEl);
|
||||||
|
});
|
||||||
|
|
||||||
// "Real symlink" needs the Git Data API, which only GitHub offers. For
|
// "Real symlink" needs the Git Data API, which only GitHub offers. For
|
||||||
// other providers, offer follow/skip only so the option can't mislead.
|
// 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 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 requestUrl = vi.fn();
|
||||||
export const setTooltip = vi.fn();
|
export const setTooltip = vi.fn();
|
||||||
export const setIcon = vi.fn();
|
export const setIcon = vi.fn();
|
||||||
|
|
@ -68,6 +79,8 @@ vi.mock('obsidian', () => ({
|
||||||
Editor,
|
Editor,
|
||||||
App,
|
App,
|
||||||
TFile,
|
TFile,
|
||||||
|
TFolder,
|
||||||
|
AbstractInputSuggest,
|
||||||
requestUrl,
|
requestUrl,
|
||||||
setTooltip,
|
setTooltip,
|
||||||
setIcon,
|
setIcon,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue