Skip to content

Commit 6487bc6

Browse files
committed
Merge branch 'master' into release
2 parents 639c5c7 + be372a5 commit 6487bc6

16 files changed

+548
-272
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@ This plugin can create input fields inside your notes and bind them to metadata
44
### UNDER CONSTRUCTION
55
This plugin is not yet finished.
66

7-
Planed features are:
8-
- ranges for sliders
9-
- more input types
10-
117
### How to use
128
To create an input field you have to write an inline code block starting with `INPUT`. Then in square brackets the type of input field and what metadata field to bind to.
139

1410
Examples:
1511
- `INPUT[toggle]` will create an unbound toggle
1612
- `INPUT[slider:rating]` will create a slider bound to the metadata field `rating` of this note
17-
- `INPUT[text:taks#completedOn]` will create a text input bound to the metadata field `completedOn` of the note with the name `task`
13+
- `INPUT[text:task#completedOn]` will create a text input bound to the metadata field `completedOn` of the note with the name `task`
1814

1915
The plugin also allows further customization with arguments. So the complete syntax looks like this:
2016
```
@@ -25,10 +21,13 @@ INPUT[input_type(argument_name(argument_value), argument_name_2, ...):file_name_
2521
- `slider` a slider from 0 to 100 (custom ranges are on the road map)
2622
- `toggle` a toggle element
2723
- `text` a text field
24+
- `text_area` a bigger text field
2825

2926
#### Arguments
3027
- `class(class_name)` adds a css class to the input field
3128
- `addLabels` only for slider, adds labels for the min and max values
29+
- `minValue(value)` only for slider, sets the min value
30+
- `maxValue(value)` only for slider, sets the max value
3231

3332
### How to install
3433
This plugin is still in **beta**.

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "obsidian-meta-bind-plugin",
33
"name": "Meta Bind Plugin",
4-
"version": "0.1.4",
4+
"version": "0.1.6",
55
"minAppVersion": "0.14.0",
66
"description": "This plugin can create input fields inside your notes and bind them to metadata fields.",
77
"author": "Moritz Jung",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-meta-bind-plugin",
3-
"version": "0.1.2",
3+
"version": "0.1.6",
44
"description": "This plugin can create input fields inside your notes and bind them to metadata fields.",
55
"main": "main.js",
66
"scripts": {

src/InputFieldMarkdownRenderChild.ts

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
import {MarkdownRenderChild, TFile} from 'obsidian';
2+
import MetaBindPlugin from './main';
3+
import {Logger} from './utils/Logger';
4+
import {AbstractInputField} from './inputFields/AbstractInputField';
5+
import {InputFieldFactory, InputFieldType} from './inputFields/InputFieldFactory';
6+
7+
export class InputFieldMarkdownRenderChild extends MarkdownRenderChild {
8+
plugin: MetaBindPlugin;
9+
metaData: any;
10+
filePath: string;
11+
uid: number;
12+
inputField: AbstractInputField;
13+
error: string;
14+
15+
fullDeclaration: string;
16+
inputFieldType: InputFieldType;
17+
isBound: boolean;
18+
bindTargetMetadataField: string;
19+
file: TFile;
20+
21+
limitInterval: number;
22+
intervalCounter: number;
23+
valueQueue: any[];
24+
25+
arguments: { name: string, value: any }[];
26+
27+
constructor(containerEl: HTMLElement, fullDeclaration: string, plugin: MetaBindPlugin, filePath: string, uid: number) {
28+
super(containerEl);
29+
30+
//console.log(this, 2)
31+
32+
this.error = '';
33+
this.fullDeclaration = fullDeclaration;
34+
this.filePath = filePath;
35+
this.uid = uid;
36+
this.plugin = plugin;
37+
38+
this.valueQueue = [];
39+
this.intervalCounter = 0;
40+
this.limitInterval = window.setInterval(() => this.incrementInterval(), this.plugin.settings.syncInterval);
41+
42+
try {
43+
this.parseDeclaration();
44+
45+
this.inputField = InputFieldFactory.createInputField(this.inputFieldType, {
46+
inputFieldMarkdownRenderChild: this,
47+
onValueChanged: this.updateMetaData.bind(this),
48+
});
49+
} catch (e) {
50+
this.error = e.message;
51+
}
52+
53+
// console.log(this, 3)
54+
}
55+
56+
parseDeclaration() {
57+
const declarationRegExp: RegExp = new RegExp(/\[.*?\]/);
58+
let declaration: string = declarationRegExp.exec(this.fullDeclaration)[0];
59+
declaration = declaration.replace('[', '').replace(']', '');
60+
let declarationParts: string[] = declaration.split(':');
61+
62+
let boundTo: string = declarationParts[1] ?? '';
63+
this.isBound = !!boundTo;
64+
65+
let inputFieldTypeWithArguments: string = declarationParts[0];
66+
const inputFieldArgumentsRegExp: RegExp = new RegExp(/\(.*\)/);
67+
const inputFieldTypeString = inputFieldTypeWithArguments.replace(inputFieldArgumentsRegExp, '');
68+
this.inputFieldType = InputFieldFactory.getInputFieldType(inputFieldTypeString);
69+
if (this.inputFieldType === InputFieldType.INVALID) {
70+
throw Error(`Invalid input field type \'${inputFieldTypeString}\'`);
71+
}
72+
73+
this.arguments = [];
74+
let inputFieldArgumentsRegExpResult = inputFieldArgumentsRegExp.exec(inputFieldTypeWithArguments);
75+
let inputFieldArgumentsString = inputFieldArgumentsRegExpResult ? inputFieldArgumentsRegExpResult[0] : '';
76+
if (inputFieldArgumentsString) {
77+
this.parseArguments(inputFieldArgumentsString);
78+
}
79+
80+
if (this.isBound) {
81+
this.parseBindTarget(boundTo);
82+
}
83+
}
84+
85+
parseArguments(inputFieldArgumentsString: string) {
86+
const inputFieldArgumentsRegExp: RegExp = new RegExp(/\(.*\)/);
87+
88+
inputFieldArgumentsString = inputFieldArgumentsString.substring(1, inputFieldArgumentsString.length - 1);
89+
let inputFieldArguments: string[] = inputFieldArgumentsString.split(',');
90+
91+
inputFieldArguments = inputFieldArguments.map(x => x.trim());
92+
for (const inputFieldArgument of inputFieldArguments) {
93+
const inputFieldArgumentName: string = this.extractInputFieldArgumentName(inputFieldArgument);
94+
95+
if (inputFieldArgumentName === 'class') {
96+
const inputFieldArgumentValue: string = this.extractInputFieldArgumentValue(inputFieldArgument);
97+
98+
let inputFieldClassArgument: { name: string, value: string } = {name: inputFieldArgumentName, value: inputFieldArgumentValue};
99+
this.arguments.push(inputFieldClassArgument);
100+
}
101+
102+
if (inputFieldArgumentName === 'addLabels') {
103+
this.arguments.push({name: 'labels', value: true});
104+
}
105+
106+
if (inputFieldArgumentName === 'minValue') {
107+
const inputFieldArgumentValue: string = this.extractInputFieldArgumentValue(inputFieldArgument);
108+
const inputFieldArgumentValueAsNumber: number = Number.parseInt(inputFieldArgumentValue);
109+
110+
if (Number.isNaN(inputFieldArgumentValueAsNumber)) {
111+
throw new Error(`argument \'${inputFieldArgumentName}\' value must be of type number`);
112+
}
113+
114+
let inputFieldClassArgument: { name: string, value: number } = {name: inputFieldArgumentName, value: inputFieldArgumentValueAsNumber};
115+
this.arguments.push(inputFieldClassArgument);
116+
}
117+
118+
if (inputFieldArgumentName === 'maxValue') {
119+
const inputFieldArgumentValue: string = this.extractInputFieldArgumentValue(inputFieldArgument);
120+
const inputFieldArgumentValueAsNumber: number = Number.parseInt(inputFieldArgumentValue);
121+
122+
if (Number.isNaN(inputFieldArgumentValueAsNumber)) {
123+
throw new Error(`argument \'${inputFieldArgumentName}\' value must be of type number`);
124+
}
125+
126+
let inputFieldClassArgument: { name: string, value: number } = {name: inputFieldArgumentName, value: inputFieldArgumentValueAsNumber};
127+
this.arguments.push(inputFieldClassArgument);
128+
}
129+
}
130+
}
131+
132+
extractInputFieldArgumentName(argumentString: string): string {
133+
const argumentsRegExp: RegExp = new RegExp(/\(.*\)/);
134+
135+
return argumentString.replace(argumentsRegExp, '');
136+
}
137+
138+
extractInputFieldArgumentValue(argumentString: string): string {
139+
const argumentsRegExp: RegExp = new RegExp(/\(.*\)/);
140+
141+
let argumentName = this.extractInputFieldArgumentName(argumentString);
142+
143+
let argumentValueRegExpResult = argumentsRegExp.exec(argumentString);
144+
if (!argumentValueRegExpResult) {
145+
throw new Error(`argument \'${argumentName}\' requires a value`);
146+
}
147+
let argumentValue = argumentsRegExp.exec(argumentString)[0];
148+
if (!argumentValue && argumentValue.length >= 2) {
149+
throw new Error(`argument \'${argumentName}\' requires a value`);
150+
}
151+
argumentValue = argumentValue.substring(1, argumentValue.length - 1);
152+
if (!argumentValue) {
153+
throw new Error(`argument \'${argumentName}\' value can not be empty`);
154+
}
155+
156+
return argumentValue;
157+
}
158+
159+
parseBindTarget(bindTarget: string) {
160+
let bindTargetParts = bindTarget.split('#');
161+
if (bindTargetParts.length === 1) { // same file
162+
this.bindTargetMetadataField = bindTarget;
163+
const files = this.plugin.getFilesByName(this.filePath);
164+
if (files.length === 0) {
165+
throw new Error('file not found');
166+
} else if (files.length === 1) {
167+
this.file = files[0];
168+
} else {
169+
throw new Error('multiple files found. please specify the file path');
170+
}
171+
} else if (bindTargetParts.length === 2) {
172+
this.bindTargetMetadataField = bindTargetParts[1];
173+
const files = this.plugin.getFilesByName(bindTargetParts[0]);
174+
if (files.length === 0) {
175+
throw new Error('file not found');
176+
} else if (files.length === 1) {
177+
this.file = files[0];
178+
} else {
179+
throw new Error('multiple files found. please specify the file path');
180+
}
181+
} else {
182+
throw new Error('invalid binding');
183+
}
184+
this.metaData = this.plugin.getMetaDataForFile(this.file);
185+
}
186+
187+
// use this interval to reduce writing operations
188+
async incrementInterval() {
189+
if (this.valueQueue.length > 0) {
190+
// console.log(this.valueQueue.at(-1))
191+
await this.plugin.updateMetaData(this.bindTargetMetadataField, this.valueQueue.at(-1), this.file);
192+
this.valueQueue = [];
193+
}
194+
}
195+
196+
async updateMetaData(value: any) {
197+
if (this.isBound) {
198+
this.valueQueue.push(value);
199+
}
200+
}
201+
202+
updateValue(value: any) {
203+
if (value != null && this.inputField.getValue() !== value && this.valueQueue.length === 0) {
204+
Logger.logDebug(`updating input field ${this.uid} to '${value.toString()}'`);
205+
this.inputField.setValue(value);
206+
}
207+
}
208+
209+
getInitialValue() {
210+
// console.log(this);
211+
if (this.isBound) {
212+
return this.metaData[this.bindTargetMetadataField];
213+
}
214+
}
215+
216+
getArguments(name: string) {
217+
return this.arguments.filter(x => x.name === name);
218+
}
219+
220+
getArgument(name: string) {
221+
return this.getArguments(name).first();
222+
}
223+
224+
async onload() {
225+
Logger.logDebug(this);
226+
227+
this.metaData = await this.metaData;
228+
229+
const container = this.containerEl.createDiv();
230+
container.addClass('meta-bind-plugin-input-wrapper');
231+
232+
if (this.error) {
233+
container.innerText = ` -> Error: ${this.error}`;
234+
container.addClass('meta-bind-plugin-error');
235+
this.containerEl.appendChild(container);
236+
return;
237+
}
238+
239+
this.plugin.registerMarkdownInputField(this);
240+
241+
this.inputField.render(container);
242+
243+
const classArgument = this.getArguments('class');
244+
if (classArgument) {
245+
this.inputField.getHtmlElement().addClasses(classArgument.map(x => x.value));
246+
}
247+
248+
this.containerEl.empty();
249+
this.containerEl.appendChild(container);
250+
}
251+
252+
onunload() {
253+
this.plugin.unregisterMarkdownInputField(this);
254+
255+
super.onunload();
256+
257+
//console.log('unload', this);
258+
window.clearInterval(this.limitInterval);
259+
}
260+
}

0 commit comments

Comments
 (0)