Skip to content

Commit f66347a

Browse files
committed
start of #187 and #184
1 parent eb21509 commit f66347a

21 files changed

+345
-86
lines changed

exampleVault/Button Example.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,10 @@ actions:
210210

211211
`BUTTON[count-decrement, count-reset, count-increment]` `VIEW[{count}]`
212212

213+
## Button Templates
214+
215+
`BUTTON[test-id]`
216+
213217
## Invalid Buttons
214218

215219
```meta-bind-button
@@ -234,4 +238,4 @@ actions:
234238
- type: command
235239
command: obsidian-meta-bind-plugin:open-help
236240
237-
```
241+
```

src/EditorMenu.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ export function createEditorMenu(menu: Menu, editor: Editor, plugin: MetaBindPlu
4949
mbSubmenu.addItem(buttonItem => {
5050
buttonItem.setTitle('Button');
5151
buttonItem.onClick(() => {
52-
new ButtonBuilderModal(
53-
plugin,
54-
config => {
52+
new ButtonBuilderModal({
53+
plugin: plugin,
54+
onOkay: (config): void => {
5555
insetAtCursor(editor, `\`\`\`meta-bind-button\n${stringifyYaml(config)}\n\`\`\``);
5656
},
57-
'Insert',
58-
).open();
57+
submitText: 'Insert',
58+
}).open();
5959
});
6060
});
6161
});

src/api/API.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export class API implements IAPI {
351351
return this.createExcludedField(containerEl, filePath, component);
352352
}
353353

354-
const button = new ButtonMDRC(containerEl, fullDeclaration, this.plugin, filePath, getUUID());
354+
const button = new ButtonMDRC(containerEl, fullDeclaration, this.plugin, filePath, getUUID(), false);
355355
component.addChild(button);
356356

357357
return button;

src/fields/button/ButtonActionRunner.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type ButtonAction,
33
ButtonActionType,
44
type ButtonConfig,
5+
ButtonStyleType,
56
type CommandButtonAction,
67
type InputButtonAction,
78
type JSButtonAction,
@@ -22,6 +23,18 @@ export class ButtonActionRunner {
2223
this.plugin = plugin;
2324
}
2425

26+
createDefaultButtonConfig(): ButtonConfig {
27+
return {
28+
label: 'This is a button',
29+
hidden: false,
30+
class: '',
31+
tooltip: '',
32+
id: '',
33+
style: ButtonStyleType.DEFAULT,
34+
actions: [],
35+
};
36+
}
37+
2538
async runButtonAction(buttonConfig: ButtonConfig, filePath: string): Promise<void> {
2639
try {
2740
if (buttonConfig.action) {

src/fields/button/ButtonBuilderModal.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@ import type MetaBindPlugin from '../../main';
33
import ButtonBuilderModalComponent from './ButtonBuilderModalComponent.svelte';
44
import { type ButtonConfig } from '../../config/ButtonConfig';
55

6+
export interface ButtonBuilderModalOptions {
7+
plugin: MetaBindPlugin;
8+
onOkay: (config: ButtonConfig) => void;
9+
submitText: string;
10+
config?: ButtonConfig;
11+
}
12+
613
export class ButtonBuilderModal extends Modal {
714
plugin: MetaBindPlugin;
815
component?: ButtonBuilderModalComponent;
916
onOkay: (config: ButtonConfig) => void;
1017
submitText: string;
18+
config?: ButtonConfig;
1119

12-
constructor(plugin: MetaBindPlugin, onOkay: (config: ButtonConfig) => void, submitText: string) {
13-
super(plugin.app);
14-
this.plugin = plugin;
15-
this.onOkay = onOkay;
16-
this.submitText = submitText;
20+
constructor(options: ButtonBuilderModalOptions) {
21+
super(options.plugin.app);
22+
this.plugin = options.plugin;
23+
this.onOkay = options.onOkay;
24+
this.submitText = options.submitText;
25+
this.config = options.config;
1726
}
1827

1928
public onOpen(): void {
@@ -26,6 +35,7 @@ export class ButtonBuilderModal extends Modal {
2635
target: this.contentEl,
2736
props: {
2837
modal: this,
38+
buttonConfig: this.config ?? this.plugin.api.buttonActionRunner.createDefaultButtonConfig(),
2939
},
3040
});
3141
}

src/fields/button/ButtonBuilderModalComponent.svelte

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,8 @@
2121
import Toggle from '../../utils/components/Toggle.svelte';
2222
2323
export let modal: ButtonBuilderModal;
24-
let buttonConfig: ButtonConfig = {
25-
label: 'This is a button',
26-
hidden: false,
27-
class: '',
28-
tooltip: '',
29-
id: '',
30-
style: ButtonStyleType.DEFAULT,
31-
actions: [],
32-
};
24+
export let buttonConfig: ButtonConfig;
25+
3326
let buttonEl: HTMLElement;
3427
let buttonMDRC: ButtonMDRC;
3528
let addActionType: ButtonActionType;
@@ -44,7 +37,7 @@
4437
buttonMDRC?.unload();
4538
if (el) {
4639
el.empty();
47-
buttonMDRC = new ButtonMDRC(el, stringifyYaml(config), modal.plugin, '', getUUID());
40+
buttonMDRC = new ButtonMDRC(el, stringifyYaml(config), modal.plugin, '', getUUID(), true);
4841
buttonMDRC.load();
4942
}
5043
}

src/fields/button/ButtonField.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ export class ButtonField {
1414
inline: boolean;
1515
buttonComponent?: ButtonComponent;
1616
config: ButtonConfig | undefined;
17+
isPreview: boolean;
1718

18-
constructor(plugin: IPlugin, config: unknown, filePath: string, inline: boolean) {
19+
constructor(plugin: IPlugin, config: unknown, filePath: string, inline: boolean, isPreview: boolean) {
1920
this.plugin = plugin;
2021
this.unvalidatedConfig = config;
2122
this.filePath = filePath;
2223
this.inline = inline;
2324
this.config = undefined;
25+
this.isPreview = isPreview;
2426
}
2527

2628
private renderButton(container: HTMLElement): void {
@@ -66,7 +68,7 @@ export class ButtonField {
6668

6769
this.config = validationResult.data;
6870

69-
if (!this.inline) {
71+
if (!this.inline && !this.isPreview) {
7072
if (this.config.id) {
7173
this.plugin.api.buttonManager.addButton(this.filePath, this.config);
7274
}

src/fields/button/ButtonManager.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,54 @@
11
import { type ButtonConfig } from '../../config/ButtonConfig';
22
import { getUUID } from '../../utils/Utils';
3+
import { ErrorCollection } from '../../utils/errors/ErrorCollection';
4+
import { ErrorLevel, MetaBindButtonError } from '../../utils/errors/MetaBindErrors';
35

46
export class ButtonManager {
57
buttons: Map<string, Map<string, ButtonConfig>>;
68
buttonLoadListeners: Map<string, Map<string, Map<string, (config: ButtonConfig) => void>>>;
9+
buttonTemplates: Map<string, ButtonConfig>;
710

811
constructor() {
912
this.buttons = new Map();
1013
this.buttonLoadListeners = new Map();
14+
this.buttonTemplates = new Map();
15+
}
16+
17+
public setButtonTemplates(buttonTemplates: ButtonConfig[]): ErrorCollection {
18+
const idSet = new Set<string>();
19+
20+
const errorCollection = new ErrorCollection('ButtonManager');
21+
22+
this.buttonTemplates.clear();
23+
24+
for (const buttonTemplate of buttonTemplates) {
25+
if (buttonTemplate.id === undefined || buttonTemplate.id === '') {
26+
errorCollection.add(
27+
new MetaBindButtonError({
28+
errorLevel: ErrorLevel.ERROR,
29+
cause: `Button with label "${buttonTemplate.label}" has no id, but button templates must have an id.`,
30+
effect: 'Button templates could not be saved.',
31+
}),
32+
);
33+
} else if (idSet.has(buttonTemplate.id)) {
34+
errorCollection.add(
35+
new MetaBindButtonError({
36+
errorLevel: ErrorLevel.ERROR,
37+
cause: `Button id "${buttonTemplate.id}" is not unique. The same id is used by multiple buttons.`,
38+
effect: 'Button templates could not be saved.',
39+
}),
40+
);
41+
} else {
42+
idSet.add(buttonTemplate.id);
43+
this.buttonTemplates.set(buttonTemplate.id, buttonTemplate);
44+
}
45+
}
46+
47+
if (errorCollection.hasErrors()) {
48+
this.buttonTemplates.clear();
49+
}
50+
51+
return errorCollection;
1152
}
1253

1354
public registerButtonLoadListener(
@@ -81,16 +122,24 @@ export class ButtonManager {
81122
}
82123

83124
public addButton(filePath: string, button: ButtonConfig): void {
84-
if (button.id === undefined) {
125+
if (button.id === undefined || button.id === '') {
85126
throw new Error('ButtonManager | button id is undefined');
86127
}
87128

129+
if (this.buttonTemplates.has(button.id)) {
130+
throw new Error(`ButtonManager | button with id "${button.id}" already exists in the button templates`);
131+
}
132+
88133
if (!this.buttons.has(filePath)) {
89134
this.buttons.set(filePath, new Map());
90135
}
91136

92137
const fileButtons = this.buttons.get(filePath)!;
93138

139+
if (fileButtons.has(button.id)) {
140+
throw new Error(`ButtonManager | button with id "${button.id}" already exists`);
141+
}
142+
94143
fileButtons.set(button.id, button);
95144
this.notifyButtonLoadListeners(filePath, button.id);
96145
}
@@ -100,6 +149,10 @@ export class ButtonManager {
100149
}
101150

102151
public getButton(filePath: string, buttonId: string): ButtonConfig | undefined {
152+
if (this.buttonTemplates.has(buttonId)) {
153+
return this.buttonTemplates.get(buttonId);
154+
}
155+
103156
const fileButtons = this.buttons.get(filePath);
104157
if (fileButtons) {
105158
return fileButtons.get(buttonId);

src/fields/button/InlineButtonField.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class InlineButtonField {
4848
buttonId,
4949
(buttonConfig: ButtonConfig) => {
5050
initialButton?.$destroy();
51-
button = new ButtonField(this.plugin, buttonConfig, this.filePath, true);
51+
button = new ButtonField(this.plugin, buttonConfig, this.filePath, true, false);
5252
button.mount(element);
5353
},
5454
);

src/main.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { EMBED_MAX_DEPTH, EmbedMDRC } from './renderChildren/EmbedMDRC';
1414
import { getUUID } from './utils/Utils';
1515
import { ObsidianAPIAdapter } from './api/internalApi/ObsidianAPIAdapter';
1616
import { RenderChildType } from './config/FieldConfigs';
17-
import { ButtonMDRC } from './renderChildren/ButtonMDRC';
1817
import { ButtonBuilderModal } from './fields/button/ButtonBuilderModal';
1918
import { InlineMDRCType, InlineMDRCUtils } from './utils/InlineMDRCUtils';
2019
import { registerCm5HLModes } from './cm6/Cm5_Modes';
@@ -57,7 +56,6 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
5756

5857
// settings
5958
await this.loadSettings();
60-
await this.saveSettings();
6159
this.addSettingTab(new MetaBindSettingTab(this.app, this));
6260

6361
// check dependencies
@@ -184,8 +182,7 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
184182
}
185183

186184
this.registerMarkdownCodeBlockProcessor('meta-bind-button', (source, el, ctx) => {
187-
const button = new ButtonMDRC(el, source, this, ctx.sourcePath, getUUID());
188-
ctx.addChild(button);
185+
this.api.createButtonFromString(source, ctx.sourcePath, el, ctx);
189186
});
190187
}
191188

@@ -218,15 +215,15 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
218215
id: 'open-button-builder',
219216
name: 'Open Button Builder',
220217
callback: () => {
221-
new ButtonBuilderModal(
222-
this,
223-
config => {
218+
new ButtonBuilderModal({
219+
plugin: this,
220+
onOkay: (config): void => {
224221
void window.navigator.clipboard.writeText(
225222
`\`\`\`meta-bind-button\n${stringifyYaml(config)}\n\`\`\``,
226223
);
227224
},
228-
'Copy to Clipboard',
229-
).open();
225+
submitText: 'Copy to Clipboard',
226+
}).open();
230227
},
231228
});
232229
}
@@ -248,11 +245,18 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
248245
}
249246

250247
loadTemplates(): void {
251-
const templateParseErrorCollection = this.api.inputFieldParser.parseTemplates(
248+
const inputFieldTemplateParseErrorCollection = this.api.inputFieldParser.parseTemplates(
252249
this.settings.inputFieldTemplates,
253250
);
254-
if (templateParseErrorCollection.hasErrors()) {
255-
console.warn('meta-bind | failed to parse templates', templateParseErrorCollection);
251+
if (inputFieldTemplateParseErrorCollection.hasErrors()) {
252+
console.warn('meta-bind | failed to parse input field templates', inputFieldTemplateParseErrorCollection);
253+
}
254+
255+
const buttonTemplateParseErrorCollection = this.api.buttonManager.setButtonTemplates(
256+
this.settings.buttonTemplates,
257+
);
258+
if (buttonTemplateParseErrorCollection.hasErrors()) {
259+
console.warn('meta-bind | failed to parse button templates', buttonTemplateParseErrorCollection);
256260
}
257261
}
258262

@@ -270,11 +274,9 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
270274
async loadSettings(): Promise<void> {
271275
console.log(`meta-bind | Main >> settings load`);
272276

273-
let loadedSettings = (await this.loadData()) as MetaBindPluginSettings;
277+
const loadedSettings = (await this.loadData()) as MetaBindPluginSettings;
274278

275-
loadedSettings = this.applyTemplatesMigration(Object.assign({}, DEFAULT_SETTINGS, loadedSettings));
276-
277-
this.settings = loadedSettings;
279+
this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedSettings);
278280

279281
await this.saveSettings();
280282
}
@@ -289,11 +291,6 @@ export default class MetaBindPlugin extends Plugin implements IPlugin {
289291
await this.saveData(this.settings);
290292
}
291293

292-
// TODO: move to internal API
293-
applyTemplatesMigration(oldSettings: MetaBindPluginSettings): MetaBindPluginSettings {
294-
return oldSettings;
295-
}
296-
297294
// TODO: move to internal API
298295
async activateView(viewType: string): Promise<void> {
299296
const { workspace } = this.app;

0 commit comments

Comments
 (0)