obsidian-plugin-config 1.3.8 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/obsidian-inject.js +1 -1
- package/package.json +1 -1
- package/scripts/help.ts +24 -25
- package/scripts/inject-core.ts +37 -13
- package/src/main.ts +105 -108
- package/src/modals/GenericConfirmModal.ts +48 -47
- package/src/tools/index.ts +3 -3
- package/src/utils/NoticeHelper.ts +70 -87
- package/src/utils/SettingsHelper.ts +158 -168
- package/templates/scripts/esbuild.config.ts +1 -1
- package/versions.json +3 -1
package/bin/obsidian-inject.js
CHANGED
package/package.json
CHANGED
package/scripts/help.ts
CHANGED
|
@@ -29,8 +29,10 @@ INJECTION (Development phase):
|
|
|
29
29
|
yarn inject <path> --sass # Injection with SASS support
|
|
30
30
|
yarn check-plugin <path> # Verification only (dry-run)
|
|
31
31
|
|
|
32
|
-
NPM PUBLISHING:
|
|
33
|
-
yarn npm-publish #
|
|
32
|
+
NPM PUBLISHING (all-in-one - no acp needed before):
|
|
33
|
+
yarn npm-publish # Full workflow:
|
|
34
|
+
# version → exports → bin
|
|
35
|
+
# → commit+push → publish
|
|
34
36
|
yarn build-npm # Alias for npm-publish
|
|
35
37
|
|
|
36
38
|
HELP:
|
|
@@ -82,17 +84,15 @@ Plugin Config Development:
|
|
|
82
84
|
6. yarn npm-publish # Publish to NPM
|
|
83
85
|
|
|
84
86
|
Injection Usage:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
yarn inject-
|
|
93
|
-
yarn
|
|
94
|
-
yarn inject ../my-plugin --sass # With SASS support
|
|
95
|
-
yarn check-plugin ../my-plugin # Verification only
|
|
87
|
+
Usage (from plugin directory or by path):
|
|
88
|
+
obsidian-inject # Inject in current dir
|
|
89
|
+
obsidian-inject ../my-plugin # Inject by path
|
|
90
|
+
obsidian-inject ../my-plugin --sass # With SASS
|
|
91
|
+
|
|
92
|
+
Or with local yarn commands:
|
|
93
|
+
yarn inject-prompt # Interactive
|
|
94
|
+
yarn inject-path ../my-plugin # Direct injection
|
|
95
|
+
yarn check-plugin ../my-plugin # Dry-run only
|
|
96
96
|
|
|
97
97
|
SASS Support (--sass flag):
|
|
98
98
|
What gets added to the target plugin:
|
|
@@ -109,23 +109,22 @@ SASS Support (--sass flag):
|
|
|
109
109
|
|
|
110
110
|
STANDARD DEVELOPMENT WORKFLOW:
|
|
111
111
|
1. yarn i # Install dependencies
|
|
112
|
-
2. Make changes to
|
|
113
|
-
3. yarn
|
|
114
|
-
4. yarn
|
|
115
|
-
|
|
116
|
-
6. yarn npm-publish # Complete NPM workflow
|
|
117
|
-
|
|
118
|
-
AUTOMATED WORKFLOW (One command):
|
|
119
|
-
yarn npm-publish # Does EVERYTHING automatically:
|
|
120
|
-
# → Update version
|
|
112
|
+
2. Make changes to src/ or templates/
|
|
113
|
+
3. yarn lint:fix # Fix any linting issues
|
|
114
|
+
4. yarn npm-publish # Does EVERYTHING:
|
|
115
|
+
# → Ask for version bump type
|
|
121
116
|
# → Update exports
|
|
122
|
-
# → Generate bin
|
|
117
|
+
# → Generate bin file
|
|
123
118
|
# → Verify package
|
|
119
|
+
# → Commit + push to GitHub
|
|
124
120
|
# → Publish to NPM
|
|
125
121
|
|
|
126
|
-
|
|
122
|
+
Note: yarn acp is only needed for intermediate commits
|
|
123
|
+
(not required before npm-publish).
|
|
124
|
+
|
|
125
|
+
AFTER NPM PUBLISH - Update global CLI:
|
|
127
126
|
npm install -g obsidian-plugin-config@latest
|
|
128
|
-
obsidian-inject
|
|
127
|
+
obsidian-inject # Test in current plugin dir
|
|
129
128
|
obsidian-inject ../test-plugin --sass
|
|
130
129
|
|
|
131
130
|
TESTING AS PLUGIN (Optional):
|
package/scripts/inject-core.ts
CHANGED
|
@@ -293,9 +293,9 @@ export async function injectScripts(targetPath: string, useSass: boolean = false
|
|
|
293
293
|
"templates/scripts/help.ts"
|
|
294
294
|
];
|
|
295
295
|
|
|
296
|
-
// Files that
|
|
297
|
-
//
|
|
298
|
-
const
|
|
296
|
+
// Files that need value-preserving merge instead
|
|
297
|
+
// of full overwrite (user fills in their paths)
|
|
298
|
+
const mergeEnvFile = new Set([".env"]);
|
|
299
299
|
|
|
300
300
|
// Files with .template suffix (NPM excludes dotfiles)
|
|
301
301
|
// Map: { source: targetName }
|
|
@@ -331,7 +331,9 @@ export async function injectScripts(targetPath: string, useSass: boolean = false
|
|
|
331
331
|
fs.writeFileSync(targetFile, content, "utf8");
|
|
332
332
|
console.log(` ✅ ${fileName}`);
|
|
333
333
|
} catch (error) {
|
|
334
|
-
console.error(
|
|
334
|
+
console.error(
|
|
335
|
+
` ❌ Failed to inject ${scriptFile}: ${error}`
|
|
336
|
+
);
|
|
335
337
|
}
|
|
336
338
|
}
|
|
337
339
|
|
|
@@ -342,27 +344,49 @@ export async function injectScripts(targetPath: string, useSass: boolean = false
|
|
|
342
344
|
configFileMap
|
|
343
345
|
)) {
|
|
344
346
|
try {
|
|
345
|
-
const targetFile = path.join(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
//
|
|
347
|
+
const targetFile = path.join(targetPath, destName);
|
|
348
|
+
const templateContent = copyFromLocal(src);
|
|
349
|
+
|
|
350
|
+
// For .env: merge existing values into the template
|
|
349
351
|
if (
|
|
350
|
-
|
|
352
|
+
mergeEnvFile.has(destName) &&
|
|
351
353
|
fs.existsSync(targetFile)
|
|
352
354
|
) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
+
const existing = fs.readFileSync(
|
|
356
|
+
targetFile, "utf8"
|
|
355
357
|
);
|
|
358
|
+
// Parse existing key=value pairs
|
|
359
|
+
const existingVals: Record<string, string> = {};
|
|
360
|
+
for (const line of existing.split(/\r?\n/)) {
|
|
361
|
+
const m = line.match(/^([^#=]+)=(.*)$/);
|
|
362
|
+
if (m) existingVals[m[1].trim()] = m[2].trim();
|
|
363
|
+
}
|
|
364
|
+
// Re-write template, substituting existing values
|
|
365
|
+
const merged = templateContent
|
|
366
|
+
.split(/\r?\n/)
|
|
367
|
+
.map((line) => {
|
|
368
|
+
const m = line.match(/^([^#=]+)=(.*)$/);
|
|
369
|
+
if (m) {
|
|
370
|
+
const key = m[1].trim();
|
|
371
|
+
const val = existingVals[key] ?? m[2].trim();
|
|
372
|
+
return `${key}=${val}`;
|
|
373
|
+
}
|
|
374
|
+
return line;
|
|
375
|
+
})
|
|
376
|
+
.join("\n");
|
|
377
|
+
fs.writeFileSync(targetFile, merged, "utf8");
|
|
378
|
+
console.log(` ✅ ${destName} (values preserved)`);
|
|
356
379
|
continue;
|
|
357
380
|
}
|
|
358
|
-
|
|
359
|
-
fs.writeFileSync(targetFile,
|
|
381
|
+
|
|
382
|
+
fs.writeFileSync(targetFile, templateContent, "utf8");
|
|
360
383
|
console.log(` ✅ ${destName}`);
|
|
361
384
|
} catch (error) {
|
|
362
385
|
console.error(
|
|
363
386
|
` ❌ Failed to inject ${destName}: ${error}`
|
|
364
387
|
);
|
|
365
388
|
}
|
|
389
|
+
|
|
366
390
|
}
|
|
367
391
|
|
|
368
392
|
// Copy .vscode config files
|
package/src/main.ts
CHANGED
|
@@ -1,121 +1,118 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
Plugin,
|
|
4
|
-
PluginSettingTab,
|
|
5
|
-
Setting,
|
|
6
|
-
Notice
|
|
7
|
-
} from "obsidian";
|
|
8
|
-
import { showConfirmModal } from "./modals/GenericConfirmModal.ts";
|
|
1
|
+
import { App, Plugin, PluginSettingTab, Setting, Notice } from 'obsidian';
|
|
2
|
+
import { showConfirmModal } from './modals/GenericConfirmModal.ts';
|
|
9
3
|
// import { showTestMessage, getRandomEmoji } from "obsidian-plugin-config/tools";
|
|
10
4
|
|
|
11
5
|
interface MyPluginSettings {
|
|
12
|
-
|
|
6
|
+
mySetting: string;
|
|
13
7
|
}
|
|
14
8
|
|
|
15
9
|
const DEFAULT_SETTINGS: MyPluginSettings = {
|
|
16
|
-
|
|
10
|
+
mySetting: 'default'
|
|
17
11
|
};
|
|
18
12
|
|
|
19
13
|
export default class ObsidianPluginConfigPlugin extends Plugin {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
14
|
+
settings: MyPluginSettings;
|
|
15
|
+
|
|
16
|
+
async onload(): Promise<void> {
|
|
17
|
+
console.log('Loading obsidian-plugin-config plugin for testing NPM exports');
|
|
18
|
+
await this.loadSettings();
|
|
19
|
+
|
|
20
|
+
this.addCommand({
|
|
21
|
+
id: 'show-confirmation-modal',
|
|
22
|
+
name: 'Test Confirmation Modal (Local)',
|
|
23
|
+
callback: () => this.showConfirmationModal()
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
this.addCommand({
|
|
27
|
+
id: 'show-centralized-modal',
|
|
28
|
+
name: 'Test Confirmation Modal (Centralized)',
|
|
29
|
+
callback: () => this.showCentralizedModal()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
this.addCommand({
|
|
33
|
+
id: 'test-tools',
|
|
34
|
+
name: 'Test Centralized Tools',
|
|
35
|
+
callback: () => {
|
|
36
|
+
// const message = showTestMessage();
|
|
37
|
+
// const emoji = getRandomEmoji();
|
|
38
|
+
new Notice(`🎯 Test centralized tools (commented for autonomous mode)`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
this.addSettingTab(new PluginConfigSettingTab(this.app, this));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private showConfirmationModal(): void {
|
|
46
|
+
showConfirmModal(this.app, {
|
|
47
|
+
title: 'Confirmation requise',
|
|
48
|
+
message:
|
|
49
|
+
'Êtes-vous sûr de vouloir effectuer cette action ? Cette action ne peut pas être annulée.',
|
|
50
|
+
confirmText: 'Confirmer',
|
|
51
|
+
cancelText: 'Annuler',
|
|
52
|
+
onConfirm: () => {
|
|
53
|
+
new Notice('Action confirmée !');
|
|
54
|
+
console.log("Action confirmée par l'utilisateur");
|
|
55
|
+
},
|
|
56
|
+
onCancel: () => {
|
|
57
|
+
new Notice('Action annulée.');
|
|
58
|
+
console.log("Action annulée par l'utilisateur");
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private showCentralizedModal(): void {
|
|
64
|
+
showConfirmModal(this.app, {
|
|
65
|
+
title: 'Centralized Modal Test',
|
|
66
|
+
message:
|
|
67
|
+
'This modal comes from the centralized configuration! Pretty cool, right?',
|
|
68
|
+
confirmText: 'Awesome!',
|
|
69
|
+
cancelText: 'Not bad',
|
|
70
|
+
onConfirm: () => {
|
|
71
|
+
new Notice('Centralized modal confirmed! 🎉');
|
|
72
|
+
},
|
|
73
|
+
onCancel: () => {
|
|
74
|
+
new Notice('Centralized modal cancelled 😢');
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async loadSettings(): Promise<void> {
|
|
80
|
+
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async saveSettings(): Promise<void> {
|
|
84
|
+
await this.saveData(this.settings);
|
|
85
|
+
}
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
class PluginConfigSettingTab extends PluginSettingTab {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
89
|
+
plugin: ObsidianPluginConfigPlugin;
|
|
90
|
+
|
|
91
|
+
constructor(app: App, plugin: ObsidianPluginConfigPlugin) {
|
|
92
|
+
super(app, plugin);
|
|
93
|
+
this.plugin = plugin;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
display(): void {
|
|
97
|
+
const { containerEl } = this;
|
|
98
|
+
containerEl.empty();
|
|
99
|
+
|
|
100
|
+
containerEl.createEl('h2', { text: 'Obsidian Plugin Config Settings' });
|
|
101
|
+
containerEl.createEl('p', {
|
|
102
|
+
text: 'This plugin is used for testing NPM exports and development of the obsidian-plugin-config system.'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
new Setting(containerEl)
|
|
106
|
+
.setName('Test Setting')
|
|
107
|
+
.setDesc('A test setting for development purposes')
|
|
108
|
+
.addText((text) =>
|
|
109
|
+
text
|
|
110
|
+
.setPlaceholder('Enter test value')
|
|
111
|
+
.setValue(this.plugin.settings.mySetting)
|
|
112
|
+
.onChange(async (value) => {
|
|
113
|
+
this.plugin.settings.mySetting = value;
|
|
114
|
+
await this.plugin.saveSettings();
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
}
|
|
120
118
|
}
|
|
121
|
-
|
|
@@ -1,66 +1,67 @@
|
|
|
1
|
-
import { App, Modal } from
|
|
1
|
+
import { App, Modal } from 'obsidian';
|
|
2
2
|
|
|
3
3
|
export interface ConfirmModalOptions {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
title: string;
|
|
5
|
+
message: string;
|
|
6
|
+
confirmText?: string;
|
|
7
|
+
cancelText?: string;
|
|
8
|
+
onConfirm: () => void;
|
|
9
|
+
onCancel?: () => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export class GenericConfirmModal extends Modal {
|
|
13
|
-
|
|
13
|
+
private options: ConfirmModalOptions;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
constructor(app: App, options: ConfirmModalOptions) {
|
|
16
|
+
super(app);
|
|
17
|
+
this.options = options;
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
onOpen(): void {
|
|
21
|
+
const { contentEl } = this;
|
|
22
|
+
contentEl.empty();
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const title =
|
|
25
|
+
this.options.title.charAt(0).toUpperCase() + this.options.title.slice(1);
|
|
26
|
+
contentEl.createEl('h2', { text: title });
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// Message
|
|
29
|
+
contentEl.createEl('p', { text: this.options.message });
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// Buttons container
|
|
32
|
+
const buttonContainer = contentEl.createDiv('modal-button-container');
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
// Cancel button
|
|
35
|
+
const cancelBtn = buttonContainer.createEl('button', {
|
|
36
|
+
text: this.options.cancelText || 'Cancel',
|
|
37
|
+
cls: 'mod-cta'
|
|
38
|
+
});
|
|
39
|
+
cancelBtn.addEventListener('click', () => {
|
|
40
|
+
this.options.onCancel?.();
|
|
41
|
+
this.close();
|
|
42
|
+
});
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
// Confirm button
|
|
45
|
+
const confirmBtn = buttonContainer.createEl('button', {
|
|
46
|
+
text: this.options.confirmText || 'Confirm',
|
|
47
|
+
cls: 'mod-cta mod-warning'
|
|
48
|
+
});
|
|
49
|
+
confirmBtn.addEventListener('click', () => {
|
|
50
|
+
this.options.onConfirm();
|
|
51
|
+
this.close();
|
|
52
|
+
});
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// Focus on confirm button
|
|
55
|
+
confirmBtn.focus();
|
|
56
|
+
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
onClose(): void {
|
|
59
|
+
const { contentEl } = this;
|
|
60
|
+
contentEl.empty();
|
|
61
|
+
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// Utility function for quick usage
|
|
64
65
|
export function showConfirmModal(app: App, options: ConfirmModalOptions): void {
|
|
65
|
-
|
|
66
|
+
new GenericConfirmModal(app, options).open();
|
|
66
67
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Simple tools for testing
|
|
2
2
|
export function showTestMessage(): string {
|
|
3
|
-
|
|
3
|
+
return '✅ CENTRALIZED TOOLS WORK! This comes from obsidian-plugin-config!';
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
export function getRandomEmoji(): string {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const emojis = ['🚀', '🎉', '✨', '🔥', '💯', '⚡', '🎯', '🌟'];
|
|
8
|
+
return emojis[Math.floor(Math.random() * emojis.length)];
|
|
9
9
|
}
|
|
@@ -1,102 +1,85 @@
|
|
|
1
|
-
import { Notice } from
|
|
1
|
+
import { Notice } from 'obsidian';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Enhanced Notice helper with different types and durations
|
|
5
5
|
*/
|
|
6
6
|
export class NoticeHelper {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
private static readonly DEFAULT_DURATION = 5000;
|
|
8
|
+
private static readonly SUCCESS_DURATION = 3000;
|
|
9
|
+
private static readonly ERROR_DURATION = 8000;
|
|
10
|
+
private static readonly WARNING_DURATION = 6000;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
return notice;
|
|
21
|
-
}
|
|
12
|
+
/**
|
|
13
|
+
* Show a success notice with green styling
|
|
14
|
+
*/
|
|
15
|
+
static success(message: string, duration?: number): Notice {
|
|
16
|
+
const notice = new Notice(`✅ ${message}`, duration ?? this.SUCCESS_DURATION);
|
|
17
|
+
return notice;
|
|
18
|
+
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
);
|
|
31
|
-
return notice;
|
|
32
|
-
}
|
|
20
|
+
/**
|
|
21
|
+
* Show an error notice with red styling
|
|
22
|
+
*/
|
|
23
|
+
static error(message: string, duration?: number): Notice {
|
|
24
|
+
const notice = new Notice(`❌ ${message}`, duration ?? this.ERROR_DURATION);
|
|
25
|
+
return notice;
|
|
26
|
+
}
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
42
|
-
return notice;
|
|
43
|
-
}
|
|
28
|
+
/**
|
|
29
|
+
* Show a warning notice with yellow styling
|
|
30
|
+
*/
|
|
31
|
+
static warning(message: string, duration?: number): Notice {
|
|
32
|
+
const notice = new Notice(`⚠️ ${message}`, duration ?? this.WARNING_DURATION);
|
|
33
|
+
return notice;
|
|
34
|
+
}
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
);
|
|
53
|
-
return notice;
|
|
54
|
-
}
|
|
36
|
+
/**
|
|
37
|
+
* Show an info notice with blue styling
|
|
38
|
+
*/
|
|
39
|
+
static info(message: string, duration?: number): Notice {
|
|
40
|
+
const notice = new Notice(`ℹ️ ${message}`, duration ?? this.DEFAULT_DURATION);
|
|
41
|
+
return notice;
|
|
42
|
+
}
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Show a loading notice that can be updated
|
|
46
|
+
*/
|
|
47
|
+
static loading(message: string): Notice {
|
|
48
|
+
return new Notice(`⏳ ${message}`, 0); // 0 = permanent until manually hidden
|
|
49
|
+
}
|
|
62
50
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Update a loading notice to success and auto-hide
|
|
53
|
+
*/
|
|
54
|
+
static updateToSuccess(notice: Notice, message: string): void {
|
|
55
|
+
notice.setMessage(`✅ ${message}`);
|
|
56
|
+
setTimeout(() => notice.hide(), this.SUCCESS_DURATION);
|
|
57
|
+
}
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Update a loading notice to error and auto-hide
|
|
61
|
+
*/
|
|
62
|
+
static updateToError(notice: Notice, message: string): void {
|
|
63
|
+
notice.setMessage(`❌ ${message}`);
|
|
64
|
+
setTimeout(() => notice.hide(), this.ERROR_DURATION);
|
|
65
|
+
}
|
|
78
66
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
duration ?? this.DEFAULT_DURATION
|
|
86
|
-
);
|
|
87
|
-
}
|
|
67
|
+
/**
|
|
68
|
+
* Show a notice with custom emoji and duration
|
|
69
|
+
*/
|
|
70
|
+
static custom(emoji: string, message: string, duration?: number): Notice {
|
|
71
|
+
return new Notice(`${emoji} ${message}`, duration ?? this.DEFAULT_DURATION);
|
|
72
|
+
}
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
}
|
|
74
|
+
/**
|
|
75
|
+
* Show a progress notice for long operations
|
|
76
|
+
*/
|
|
77
|
+
static progress(message: string, current: number, total: number): Notice {
|
|
78
|
+
const percentage = Math.round((current / total) * 100);
|
|
79
|
+
const progressBar =
|
|
80
|
+
'█'.repeat(Math.floor(percentage / 5)) +
|
|
81
|
+
'░'.repeat(20 - Math.floor(percentage / 5));
|
|
82
|
+
|
|
83
|
+
return new Notice(`🔄 ${message}\n[${progressBar}] ${percentage}%`, 0);
|
|
84
|
+
}
|
|
102
85
|
}
|
|
@@ -1,180 +1,170 @@
|
|
|
1
|
-
import { Setting } from
|
|
1
|
+
import { Setting } from 'obsidian';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Helper for creating common setting types with consistent styling
|
|
5
5
|
*/
|
|
6
6
|
export class SettingsHelper {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Create a text input setting
|
|
9
|
+
*/
|
|
10
|
+
static createTextSetting(
|
|
11
|
+
containerEl: HTMLElement,
|
|
12
|
+
name: string,
|
|
13
|
+
desc: string,
|
|
14
|
+
value: string,
|
|
15
|
+
onChange: (value: string) => void,
|
|
16
|
+
placeholder?: string
|
|
17
|
+
): Setting {
|
|
18
|
+
return new Setting(containerEl)
|
|
19
|
+
.setName(name)
|
|
20
|
+
.setDesc(desc)
|
|
21
|
+
.addText((text) =>
|
|
22
|
+
text
|
|
23
|
+
.setPlaceholder(placeholder || '')
|
|
24
|
+
.setValue(value)
|
|
25
|
+
.onChange(onChange)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.onChange(onChange)
|
|
45
|
-
);
|
|
46
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a toggle setting
|
|
31
|
+
*/
|
|
32
|
+
static createToggleSetting(
|
|
33
|
+
containerEl: HTMLElement,
|
|
34
|
+
name: string,
|
|
35
|
+
desc: string,
|
|
36
|
+
value: boolean,
|
|
37
|
+
onChange: (value: boolean) => void
|
|
38
|
+
): Setting {
|
|
39
|
+
return new Setting(containerEl)
|
|
40
|
+
.setName(name)
|
|
41
|
+
.setDesc(desc)
|
|
42
|
+
.addToggle((toggle) => toggle.setValue(value).onChange(onChange));
|
|
43
|
+
}
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
});
|
|
70
|
-
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a dropdown setting
|
|
47
|
+
*/
|
|
48
|
+
static createDropdownSetting(
|
|
49
|
+
containerEl: HTMLElement,
|
|
50
|
+
name: string,
|
|
51
|
+
desc: string,
|
|
52
|
+
options: Record<string, string>,
|
|
53
|
+
value: string,
|
|
54
|
+
onChange: (value: string) => void
|
|
55
|
+
): Setting {
|
|
56
|
+
return new Setting(containerEl)
|
|
57
|
+
.setName(name)
|
|
58
|
+
.setDesc(desc)
|
|
59
|
+
.addDropdown((dropdown) => {
|
|
60
|
+
Object.entries(options).forEach(([key, label]) => {
|
|
61
|
+
dropdown.addOption(key, label);
|
|
62
|
+
});
|
|
63
|
+
dropdown.setValue(value).onChange(onChange);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
71
66
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
text
|
|
95
|
-
.setValue(value.toString())
|
|
96
|
-
.onChange(val => {
|
|
97
|
-
const num = parseFloat(val);
|
|
98
|
-
if (!isNaN(num)) onChange(num);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
}
|
|
67
|
+
/**
|
|
68
|
+
* Create a number input setting
|
|
69
|
+
*/
|
|
70
|
+
static createNumberSetting(
|
|
71
|
+
containerEl: HTMLElement,
|
|
72
|
+
name: string,
|
|
73
|
+
desc: string,
|
|
74
|
+
value: number,
|
|
75
|
+
onChange: (value: number) => void,
|
|
76
|
+
min?: number,
|
|
77
|
+
max?: number,
|
|
78
|
+
step?: number
|
|
79
|
+
): Setting {
|
|
80
|
+
return new Setting(containerEl)
|
|
81
|
+
.setName(name)
|
|
82
|
+
.setDesc(desc)
|
|
83
|
+
.addText((text) => {
|
|
84
|
+
text.inputEl.type = 'number';
|
|
85
|
+
if (min !== undefined) text.inputEl.min = min.toString();
|
|
86
|
+
if (max !== undefined) text.inputEl.max = max.toString();
|
|
87
|
+
if (step !== undefined) text.inputEl.step = step.toString();
|
|
102
88
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
desc: string,
|
|
110
|
-
buttonText: string,
|
|
111
|
-
onClick: () => void
|
|
112
|
-
): Setting {
|
|
113
|
-
return new Setting(containerEl)
|
|
114
|
-
.setName(name)
|
|
115
|
-
.setDesc(desc)
|
|
116
|
-
.addButton(button => button
|
|
117
|
-
.setButtonText(buttonText)
|
|
118
|
-
.onClick(onClick)
|
|
119
|
-
);
|
|
120
|
-
}
|
|
89
|
+
text.setValue(value.toString()).onChange((val) => {
|
|
90
|
+
const num = parseFloat(val);
|
|
91
|
+
if (!isNaN(num)) onChange(num);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
121
95
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const descEl = containerEl.createEl("p", { text: description });
|
|
138
|
-
descEl.style.marginTop = "0";
|
|
139
|
-
descEl.style.marginBottom = "15px";
|
|
140
|
-
descEl.style.color = "var(--text-muted)";
|
|
141
|
-
descEl.style.fontSize = "0.9em";
|
|
142
|
-
}
|
|
143
|
-
}
|
|
96
|
+
/**
|
|
97
|
+
* Create a button setting
|
|
98
|
+
*/
|
|
99
|
+
static createButtonSetting(
|
|
100
|
+
containerEl: HTMLElement,
|
|
101
|
+
name: string,
|
|
102
|
+
desc: string,
|
|
103
|
+
buttonText: string,
|
|
104
|
+
onClick: () => void
|
|
105
|
+
): Setting {
|
|
106
|
+
return new Setting(containerEl)
|
|
107
|
+
.setName(name)
|
|
108
|
+
.setDesc(desc)
|
|
109
|
+
.addButton((button) => button.setButtonText(buttonText).onClick(onClick));
|
|
110
|
+
}
|
|
144
111
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Create a section header
|
|
114
|
+
*/
|
|
115
|
+
static createHeader(
|
|
116
|
+
containerEl: HTMLElement,
|
|
117
|
+
title: string,
|
|
118
|
+
description?: string
|
|
119
|
+
): void {
|
|
120
|
+
const headerEl = containerEl.createEl('h3', { text: title });
|
|
121
|
+
headerEl.style.marginTop = '20px';
|
|
122
|
+
headerEl.style.marginBottom = '10px';
|
|
123
|
+
headerEl.style.borderBottom = '1px solid var(--background-modifier-border)';
|
|
124
|
+
headerEl.style.paddingBottom = '5px';
|
|
125
|
+
|
|
126
|
+
if (description) {
|
|
127
|
+
const descEl = containerEl.createEl('p', { text: description });
|
|
128
|
+
descEl.style.marginTop = '0';
|
|
129
|
+
descEl.style.marginBottom = '15px';
|
|
130
|
+
descEl.style.color = 'var(--text-muted)';
|
|
131
|
+
descEl.style.fontSize = '0.9em';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Create a collapsible section
|
|
137
|
+
*/
|
|
138
|
+
static createCollapsibleSection(
|
|
139
|
+
containerEl: HTMLElement,
|
|
140
|
+
title: string,
|
|
141
|
+
isOpen: boolean = false
|
|
142
|
+
): { container: HTMLElement; toggle: () => void } {
|
|
143
|
+
const sectionEl = containerEl.createDiv('setting-item');
|
|
144
|
+
const headerEl = sectionEl.createDiv('setting-item-info');
|
|
145
|
+
const nameEl = headerEl.createDiv('setting-item-name');
|
|
146
|
+
nameEl.setText(title);
|
|
147
|
+
nameEl.style.cursor = 'pointer';
|
|
148
|
+
nameEl.style.userSelect = 'none';
|
|
149
|
+
|
|
150
|
+
const contentEl = containerEl.createDiv('collapsible-content');
|
|
151
|
+
contentEl.style.display = isOpen ? 'block' : 'none';
|
|
152
|
+
contentEl.style.marginLeft = '20px';
|
|
153
|
+
contentEl.style.marginTop = '10px';
|
|
154
|
+
|
|
155
|
+
const arrow = nameEl.createSpan('collapse-icon');
|
|
156
|
+
arrow.setText(isOpen ? '▼' : '▶');
|
|
157
|
+
arrow.style.marginRight = '8px';
|
|
158
|
+
arrow.style.fontSize = '0.8em';
|
|
159
|
+
|
|
160
|
+
const toggle = (): void => {
|
|
161
|
+
const isCurrentlyOpen = contentEl.style.display !== 'none';
|
|
162
|
+
contentEl.style.display = isCurrentlyOpen ? 'none' : 'block';
|
|
163
|
+
arrow.setText(isCurrentlyOpen ? '▶' : '▼');
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
nameEl.addEventListener('click', toggle);
|
|
167
|
+
|
|
168
|
+
return { container: contentEl, toggle };
|
|
169
|
+
}
|
|
180
170
|
}
|
|
@@ -173,7 +173,7 @@ async function createBuildContext(buildPath: string, isProd: boolean, entryPoint
|
|
|
173
173
|
...(hasSass ? [
|
|
174
174
|
await (async () => {
|
|
175
175
|
try {
|
|
176
|
-
// @ts-
|
|
176
|
+
// @ts-expect-error - esbuild-sass-plugin is installed during injection
|
|
177
177
|
const { sassPlugin } = await import('esbuild-sass-plugin');
|
|
178
178
|
return sassPlugin({
|
|
179
179
|
syntax: 'scss',
|