Skip to content

Commit c36dea0

Browse files
committed
feat: allow specifying title and tags in templates
1 parent 728fcf9 commit c36dea0

2 files changed

Lines changed: 93 additions & 14 deletions

File tree

src/actions.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
11
import joplin from "api";
2+
import { NewNote } from "./parser";
23
import { getSelectedFolder } from "./utils/folders";
4+
import { applyTagToNote, getAnyTagWithTitle } from "./utils/tags";
35

46
export enum TemplateAction {
57
NewNote = "newNote",
68
NewTodo = "newTodo",
79
InsertText = "insertText"
810
}
911

10-
const performInsertTextAction = async (template: string) => {
11-
await joplin.commands.execute("insertText", template);
12+
const performInsertTextAction = async (template: NewNote) => {
13+
await joplin.commands.execute("insertText", template.body);
1214
}
1315

14-
const performNewNoteAction = async (template: string) => {
16+
const performNewNoteAction = async (template: NewNote) => {
1517
const currentFolder = await getSelectedFolder();
16-
const note = await joplin.data.post(["notes"], null, { body: template, parent_id: currentFolder });
18+
const note = await joplin.data.post(["notes"], null, { body: template.body, parent_id: currentFolder, title: template.title });
19+
for (const tag of template.tags) {
20+
const tagId = (await getAnyTagWithTitle(tag)).id;
21+
await applyTagToNote(tagId, note.id);
22+
}
1723
await joplin.commands.execute("openNote", note.id);
1824
}
1925

20-
const performNewTodoAction = async (template: string) => {
26+
const performNewTodoAction = async (template: NewNote) => {
2127
const currentFolder = await getSelectedFolder();
22-
const note = await joplin.data.post(["notes"], null, { body: template, parent_id: currentFolder, is_todo: 1 });
28+
const note = await joplin.data.post(["notes"], null, { body: template.body, parent_id: currentFolder, is_todo: 1, title: template.title });
29+
for (const tag of template.tags) {
30+
const tagId = (await getAnyTagWithTitle(tag)).id;
31+
await applyTagToNote(tagId, note.id);
32+
}
2333
await joplin.commands.execute("openNote", note.id);
2434
}
2535

26-
export const performAction = async (action: TemplateAction, template: string): Promise<void> => {
36+
export const performAction = async (action: TemplateAction, template: NewNote): Promise<void> => {
2737
switch (action) {
2838
case TemplateAction.InsertText:
2939
await performInsertTextAction(template);

src/parser.ts

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,20 @@ import { setTemplateVariablesView } from "./views/templateVariables";
1111
// eslint-disable-next-line @typescript-eslint/no-var-requires
1212
const frontmatter = require("front-matter");
1313

14+
export interface NewNote {
15+
title: string;
16+
tags: string[];
17+
body: string;
18+
}
19+
20+
const NOTE_TITLE_VARIABLE_NAME = "template_title";
21+
const NOTE_TAGS_VARIABLE_NAME = "template_tags";
22+
1423
export class Parser {
1524
private utils: DateAndTimeUtils;
1625
private dialog: string;
1726
private logger: Logger;
27+
private specialVariableNames = [NOTE_TITLE_VARIABLE_NAME, NOTE_TAGS_VARIABLE_NAME];
1828

1929
constructor(dateAndTimeUtils: DateAndTimeUtils, dialogViewHandle: string, logger: Logger) {
2030
this.utils = dateAndTimeUtils;
@@ -105,33 +115,92 @@ export class Parser {
105115
return this.mapUserResponseToVariables(variables, userResponse);
106116
}
107117

108-
public async parseTemplate(template: Note | null): Promise<string> {
118+
private parseSpecialVariables(specialVariables: Record<string, unknown>, customVariableInputs: Record<string, string>) {
119+
const res: Record<string, string> = {};
120+
const context = {
121+
...this.getDefaultContext(),
122+
...customVariableInputs
123+
};
124+
125+
for (const variable of Object.keys(specialVariables)) {
126+
if (typeof specialVariables[variable] !== "string") {
127+
throw new Error(`${variable} should be a string, found ${typeof specialVariables[variable]}.`);
128+
}
129+
130+
const compiledText = Handlebars.compile(specialVariables[variable]);
131+
res[variable] = compiledText(context);
132+
}
133+
134+
return res;
135+
}
136+
137+
private getNoteMetadata(parsedSpecialVariables: Record<string, string>) {
138+
const meta = {
139+
title: parsedSpecialVariables.fallback_note_title,
140+
tags: []
141+
};
142+
143+
if (NOTE_TITLE_VARIABLE_NAME in parsedSpecialVariables) {
144+
meta.title = parsedSpecialVariables[NOTE_TITLE_VARIABLE_NAME];
145+
}
146+
147+
if (NOTE_TAGS_VARIABLE_NAME in parsedSpecialVariables) {
148+
meta.tags = parsedSpecialVariables[NOTE_TAGS_VARIABLE_NAME].split(",").map(t => t.trim());
149+
}
150+
151+
return meta;
152+
}
153+
154+
public async parseTemplate(template: Note | null): Promise<NewNote> {
109155
if (!template) {
110-
return "";
156+
return null;
111157
}
112158

113159
try {
114160
const processedTemplate = frontmatter(template.body);
115161
const templateVariables = processedTemplate.attributes;
116162

117-
const variableInputs = await this.getVariableInputs(template.title, templateVariables);
163+
const customVariables = {};
164+
const specialVariables = {
165+
fallback_note_title: template.title
166+
};
167+
168+
for (const variable of Object.keys(templateVariables)) {
169+
if (this.specialVariableNames.includes(variable)) {
170+
specialVariables[variable] = templateVariables[variable];
171+
} else {
172+
customVariables[variable] = templateVariables[variable];
173+
}
174+
}
175+
176+
const variableInputs = await this.getVariableInputs(template.title, customVariables);
118177
if (variableInputs === null) {
119-
return "";
178+
return null;
120179
}
121180

181+
const parsedSpecialVariables = this.parseSpecialVariables(specialVariables, variableInputs);
182+
const newNoteMeta = this.getNoteMetadata(parsedSpecialVariables);
183+
184+
// Remove the fallback property because it's not actually a variable defined by the user.
185+
delete parsedSpecialVariables.fallback_note_title;
186+
122187
const context = {
123188
...this.getDefaultContext(),
124-
...variableInputs
189+
...variableInputs,
190+
...parsedSpecialVariables
125191
};
126192

127193
const templateBody = processedTemplate.body;
128194
const compiledTemplate = Handlebars.compile(templateBody);
129195

130-
return compiledTemplate(context);
196+
return {
197+
...newNoteMeta,
198+
body: compiledTemplate(context)
199+
};
131200
} catch (err) {
132201
console.error("Error in parsing template.", err);
133202
await joplin.views.dialogs.showMessageBox(`There was an error parsing this template, please review it and try again.\n\n${err}`);
134-
return "";
203+
return null;
135204
}
136205
}
137206
}

0 commit comments

Comments
 (0)