Skip to content

Commit

Permalink
feat(#51): default templates for each notebook (#97)
Browse files Browse the repository at this point in the history
* add menu option for setting default template for notebook (change ux for global defaults)

* remaining work for this feature
  • Loading branch information
nishantwrp authored Sep 15, 2024
1 parent 4020af2 commit f021ffc
Show file tree
Hide file tree
Showing 6 changed files with 324 additions and 40 deletions.
125 changes: 99 additions & 26 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import joplin from "api";
import { MenuItemLocation, SettingItemType } from "api/types";
import { Parser } from "./parser";
import { DateAndTimeUtils } from "./utils/dateAndTime";
import { getTemplateFromId, getUserTemplateSelection, Note } from "./utils/templates";
import { setDefaultTemplatesView } from "./views/defaultTemplates";
import { getFolderFromId, getSelectedFolder, getUserFolderSelection, Folder } from "./utils/folders";
import { getTemplateFromId, getUserDefaultTemplateTypeSelection, getUserTemplateSelection, setDefaultTemplate, Note, DefaultTemplatesConfigSetting } from "./utils/templates";
import { setDefaultTemplatesView, DefaultTemplatesDisplayData, NotebookDefaultTemplatesDisplayData } from "./views/defaultTemplates";
import { TemplateAction, performAction } from "./actions";
import { loadLegacyTemplates } from "./legacyTemplates";
import * as open from "open";
Expand Down Expand Up @@ -34,6 +35,12 @@ joplin.plugins.register({
value: null,
label: "Default to-do template ID"
},
"defaultTemplatesConfig": {
public: false,
type: SettingItemType.Object,
value: null,
label: "Default templates config"
},
"applyTagsWhileInserting": {
public: true,
type: SettingItemType.Bool,
Expand Down Expand Up @@ -94,6 +101,31 @@ joplin.plugins.register({
await performActionWithParsedTemplate(action, template);
}

const getNotebookDefaultTemplatesDisplayData = async (settings: DefaultTemplatesConfigSetting): Promise<NotebookDefaultTemplatesDisplayData[]> => {
const getDisplayDataForNotebook = async (notebookId: string, defaultTemplateNoteId: string, defaultTemplateTodoId: string): Promise<NotebookDefaultTemplatesDisplayData | null> => {
const promiseGroup = new PromiseGroup();
promiseGroup.set("notebook", getFolderFromId(notebookId));
promiseGroup.set("noteTemplate", getTemplateFromId(defaultTemplateNoteId));
promiseGroup.set("todoTemplate", getTemplateFromId(defaultTemplateTodoId));
const { notebook, noteTemplate, todoTemplate } = await promiseGroup.groupAll();

// TODO: We can remove the deleted notebooks from settings storage.
if (notebook === null) return null;
return {
notebookTitle: notebook.title,
defaultNoteTemplateTitle: noteTemplate ? noteTemplate.title : null,
defaultTodoTemplateTitle: todoTemplate ? todoTemplate.title : null
};
}

const notebookDisplayDataPromiseGroup = new PromiseGroup();
for (const [notebookId, defaultTemplates] of Object.entries(settings)) {
notebookDisplayDataPromiseGroup.add(getDisplayDataForNotebook(
notebookId, defaultTemplates.defaultNoteTemplateId, defaultTemplates.defaultTodoTemplateId));
}
return (await notebookDisplayDataPromiseGroup.groupAll())[PromiseGroup.UNNAMED_KEY].filter(x => x !== null);
}


// Register all commands
const joplinCommands = new PromiseGroup();
Expand Down Expand Up @@ -129,45 +161,73 @@ joplin.plugins.register({
const noteTemplate = await getTemplateFromId(await joplin.settings.value("defaultNoteTemplateId"));
const todoTemplate = await getTemplateFromId(await joplin.settings.value("defaultTodoTemplateId"));

const noteTemplateTitle = noteTemplate ? noteTemplate.title : null;
const todoTemplateTitle = todoTemplate ? todoTemplate.title : null;
let defaultTemplatesConfig: DefaultTemplatesConfigSetting | null = await joplin.settings.value("defaultTemplatesConfig");
if (defaultTemplatesConfig === null) defaultTemplatesConfig = {};

await setDefaultTemplatesView(dialogViewHandle, noteTemplateTitle, todoTemplateTitle);
const globalDefaultTemplates: DefaultTemplatesDisplayData = {
defaultNoteTemplateTitle: noteTemplate ? noteTemplate.title : null,
defaultTodoTemplateTitle: todoTemplate ? todoTemplate.title : null
};
const notebookDisplayData = await getNotebookDefaultTemplatesDisplayData(defaultTemplatesConfig);

await setDefaultTemplatesView(dialogViewHandle, globalDefaultTemplates, notebookDisplayData);
await joplin.views.dialogs.open(dialogViewHandle);
}
}));

joplinCommands.add(joplin.commands.register({
name: "setDefaultNoteTemplate",
label: "Set default note template",
name: "setDefaultTemplate",
label: "Set default template",
execute: async () => {
const templateId = await getUserTemplateSelection("id");
if (templateId) {
await joplin.settings.setValue("defaultNoteTemplateId", templateId);
await joplin.views.dialogs.showMessageBox("Default note template set successfully!");
}
if (templateId === null) return;

const defaultType = await getUserDefaultTemplateTypeSelection();
if (defaultType === null) return;

await setDefaultTemplate(null, templateId, defaultType);
await joplin.views.dialogs.showMessageBox("Default template set successfully!");
}
}));

joplinCommands.add(joplin.commands.register({
name: "setDefaultTodoTemplate",
label: "Set default to-do template",
name: "setDefaultTemplateForNotebook",
label: "Set default template for notebook",
execute: async () => {
const templateId = await getUserTemplateSelection("id");
if (templateId) {
await joplin.settings.setValue("defaultTodoTemplateId", templateId);
await joplin.views.dialogs.showMessageBox("Default to-do template set successfully!");
}
const folder: Folder | null = JSON.parse(await getUserFolderSelection());
if (folder === null) return;

const templateId = await getUserTemplateSelection("id", `Default template for "${folder.title}":`);
if (templateId === null) return;

const defaultType = await getUserDefaultTemplateTypeSelection();
if (defaultType === null) return;

await setDefaultTemplate(folder.id, templateId, defaultType);
await joplin.views.dialogs.showMessageBox(`Default template set for "${folder.title}" successfully!`);
}
}));

joplinCommands.add(joplin.commands.register({
name: "createNoteFromDefaultTemplate",
label: "Create note from default template",
execute: async () => {
const template = await getTemplateFromId(await joplin.settings.value("defaultNoteTemplateId"));
if (template) {
return await performActionWithParsedTemplate(TemplateAction.NewNote, template);
let defaultTemplate: Note | null = null;

let defaultTemplatesConfig: DefaultTemplatesConfigSetting | null = await joplin.settings.value("defaultTemplatesConfig");
if (defaultTemplatesConfig === null) defaultTemplatesConfig = {};

const currentFolderId = await getSelectedFolder();
if (currentFolderId in defaultTemplatesConfig) {
defaultTemplate = await getTemplateFromId(defaultTemplatesConfig[currentFolderId].defaultNoteTemplateId);
}

if (defaultTemplate === null) {
defaultTemplate = await getTemplateFromId(await joplin.settings.value("defaultNoteTemplateId"));
}

if (defaultTemplate) {
return await performActionWithParsedTemplate(TemplateAction.NewNote, defaultTemplate);
}
await joplin.views.dialogs.showMessageBox("No default note template is set.");
}
Expand All @@ -177,9 +237,22 @@ joplin.plugins.register({
name: "createTodoFromDefaultTemplate",
label: "Create to-do from default template",
execute: async () => {
const template = await getTemplateFromId(await joplin.settings.value("defaultTodoTemplateId"));
if (template) {
return await performActionWithParsedTemplate(TemplateAction.NewTodo, template);
let defaultTemplate: Note | null = null;

let defaultTemplatesConfig: DefaultTemplatesConfigSetting | null = await joplin.settings.value("defaultTemplatesConfig");
if (defaultTemplatesConfig === null) defaultTemplatesConfig = {};

const currentFolderId = await getSelectedFolder();
if (currentFolderId in defaultTemplatesConfig) {
defaultTemplate = await getTemplateFromId(defaultTemplatesConfig[currentFolderId].defaultTodoTemplateId);
}

if (defaultTemplate === null) {
defaultTemplate = await getTemplateFromId(await joplin.settings.value("defaultTodoTemplateId"));
}

if (defaultTemplate) {
return await performActionWithParsedTemplate(TemplateAction.NewTodo, defaultTemplate);
}
await joplin.views.dialogs.showMessageBox("No default to-do template is set.");
}
Expand Down Expand Up @@ -237,10 +310,10 @@ joplin.plugins.register({
commandName: "showDefaultTemplates"
},
{
commandName: "setDefaultNoteTemplate"
commandName: "setDefaultTemplate"
},
{
commandName: "setDefaultTodoTemplate"
commandName: "setDefaultTemplateForNotebook"
}
]
},
Expand Down
65 changes: 65 additions & 0 deletions src/utils/folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,31 @@ import joplin from "api";
import { fetchAllItems } from "./dataApi";
import { Note } from "./templates";

export interface Folder {
id: string;
title: string;
}

type FolderProperty = "id" | "title";

export const getSelectedFolder = async (): Promise<string> => {
const folder = await joplin.workspace.selectedFolder();
return folder.id;
}

export const getFolderFromId = async (folderId: string | null): Promise<Folder | null> => {
if (!folderId) {
return null;
}

try {
return await joplin.data.get([ "folders", folderId ], { fields: ["id", "title"] });
} catch (error) {
console.error("There was an error loading a folder from id", error);
return null;
}
}

export const createFolder = async (title: string): Promise<string> => {
const folder = await joplin.data.post(["folders"], null, { title: title });
return folder.id;
Expand All @@ -24,3 +44,48 @@ export const doesFolderExist = async (folderId: string): Promise<boolean> => {
return false;
}
};

const getAllFolders = async (): Promise<Folder[]> => {
return (await fetchAllItems(["folders"], { fields: ["id", "title"] })).map(rawFolder => {
return {
id: rawFolder.id,
title: rawFolder.title,
}
});
}

export const getUserFolderSelection = async (property?: FolderProperty): Promise<string | null> => {
const folders = await getAllFolders();
if (!folders.length) {
await joplin.views.dialogs.showMessageBox("No notebooks found! Please create a notebook and try again.");
return null;
}

const notebookOptions = folders.map(folder => {
let optionValue;

if (!property) {
optionValue = JSON.stringify(folder);
} else {
optionValue = folder[property];
}

return {
label: folder.title,
value: optionValue
};
});

const { answer } = await joplin.commands.execute("showPrompt", {
label: "Notebook:",
inputType: "dropdown",
value: notebookOptions[0],
autocomplete: notebookOptions
});

if (answer) {
return answer.value;
}

return null;
}
6 changes: 5 additions & 1 deletion src/utils/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ export class PromiseGroup {
private promises: { [key: string]: Promise<any> } = {};
private unnamedPromises: Promise<any>[] = [];

// TODO: This has become too hacky. Refactor it.
public static UNNAMED_KEY = "__unnamed__";

public add(promise: Promise<any>): void {
this.unnamedPromises.push(promise);
}

public set(key: string, promise: Promise<any>): void {
if (key in this.promises) {
if (key in this.promises || key === PromiseGroup.UNNAMED_KEY) {
throw new Error(`key: ${key} already in use`);
}

Expand All @@ -26,6 +29,7 @@ export class PromiseGroup {
res[namedPromises[i][0]] = resolvedPromises[this.unnamedPromises.length + i];
}

res[PromiseGroup.UNNAMED_KEY] = resolvedPromises.slice(0, this.unnamedPromises.length);
return res;
}
}
Loading

0 comments on commit f021ffc

Please sign in to comment.