mirror of
https://github.com/rifts-obsidian-laboratory/anki-integration.git
synced 2026-07-22 05:42:50 +00:00
Refactoring - Several refactoring
This commit is contained in:
parent
e694935aa2
commit
e852f991bb
6 changed files with 363 additions and 761 deletions
|
|
@ -1,32 +1,26 @@
|
|||
import {App, ButtonComponent, DropdownComponent, Modal, TFile} from "obsidian";
|
||||
import {
|
||||
App,
|
||||
DropdownComponent,
|
||||
Modal,
|
||||
TFile
|
||||
} from "obsidian";
|
||||
import AnkiIntegration from "../main";
|
||||
import {
|
||||
AddButton,
|
||||
AddContainer,
|
||||
AddDropdown,
|
||||
AddFieldGroups,
|
||||
AddInput,
|
||||
AddOptionsToDropdownFromDataset,
|
||||
AddParagraph,
|
||||
AddSubtitle, AddTagInputGroup,
|
||||
AddTitle,
|
||||
AddSubtitle,
|
||||
AddTitle
|
||||
} from "../utils";
|
||||
import {
|
||||
GenerateFieldGroups,
|
||||
AddDeckSelector,
|
||||
AddModelSelector,
|
||||
AddSubmitButton,
|
||||
AddTagsSection,
|
||||
AutoAssignDeck,
|
||||
AutoAssignModel,
|
||||
AutoGenerateFields,
|
||||
BuildTagsArray,
|
||||
CreateFieldsGroupData,
|
||||
FetchModelByName,
|
||||
ReadFileContent
|
||||
} from "../utils";
|
||||
import {ProcessAddNote} from "../AnkiConnect";
|
||||
import {
|
||||
GenerateFieldGroups,
|
||||
GenerateDeckSelector,
|
||||
GenerateModelSelector,
|
||||
GenerateSubmitButton,
|
||||
GenerateTagsSection
|
||||
} from "./modalsUtils";
|
||||
import {Drop} from "esbuild";
|
||||
AddTagInputGroup
|
||||
} from "./addNoteModalsUtils";
|
||||
|
||||
/**
|
||||
* A modal dialog for creating a new Anki note by using a code block content as pre-filled values.
|
||||
|
|
@ -46,13 +40,6 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
*/
|
||||
plugin: AnkiIntegration;
|
||||
|
||||
/**
|
||||
* Creates a new AddNoteFromCodeBlockModal instance.
|
||||
* Initializes the modal with provided app and plugin.
|
||||
* @param {App} app - The Obsidian app instance.
|
||||
* @param {AnkiIntegration} plugin - The AnkiIntegration plugin instance.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(app: App, plugin: AnkiIntegration) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
|
|
@ -65,55 +52,30 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
*/
|
||||
const ankiData: Object = this.plugin.settings.ankiData;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
this.contentEl.focus();
|
||||
|
||||
AddTitle(contentEl, "Add a new note using code block");
|
||||
AddSubtitle(contentEl, "Deck & Model");
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} dropdownContainer
|
||||
* @description Container for the deck and model dropdown selectors.
|
||||
*/
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__dropdownContainer--flex"
|
||||
])
|
||||
|
||||
const deckSelector: DropdownComponent = GenerateDeckSelector(dropdownContainer, ankiData);
|
||||
|
||||
const modelSelector: DropdownComponent = GenerateModelSelector(dropdownContainer, ankiData);
|
||||
|
||||
const tagsContainer: HTMLDivElement = GenerateTagsSection(contentEl);
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, ["ankiIntegrationModal__dropdownContainer--flex"])
|
||||
const deckSelector: DropdownComponent = AddDeckSelector(dropdownContainer, ankiData);
|
||||
const modelSelector: DropdownComponent = AddModelSelector(dropdownContainer, ankiData);
|
||||
const tagsContainer: HTMLDivElement = AddTagsSection(contentEl);
|
||||
const tagsBody: HTMLDivElement = tagsContainer.querySelector('#tagsBody');
|
||||
const tagsBodyParagraph: HTMLElement = tagsContainer.querySelector('#tagsBodyTip');
|
||||
|
||||
AddSubtitle(contentEl, "Fields");
|
||||
const inputFieldsContainer: HTMLDivElement = AddContainer(contentEl, ["ankiIntegrationModal__inputContainer--flex"]);
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} inputContainer
|
||||
* @description Container for dynamically generated input fields based on the selected model.
|
||||
*/
|
||||
const inputContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__inputContainer--flex"
|
||||
]);
|
||||
|
||||
/**
|
||||
* Event listener triggered when the model selector value changes.
|
||||
* Updates the inputContainer with the fields corresponding to the selected model.
|
||||
* @param {string} value - The selected model name.
|
||||
*/
|
||||
modelSelector.onChange(async (value) => {
|
||||
const codeBlockParameters = await this.GetCodeBlockParameters();
|
||||
GenerateFieldGroups(this.plugin, inputContainer, value, codeBlockParameters);
|
||||
GenerateFieldGroups(this.plugin, inputFieldsContainer, value, codeBlockParameters);
|
||||
});
|
||||
|
||||
this.onOpenAsync(deckSelector, modelSelector, tagsBody, tagsBodyParagraph, inputContainer);
|
||||
this.onOpenAsync(deckSelector, modelSelector, tagsBody, tagsBodyParagraph, inputFieldsContainer);
|
||||
|
||||
GenerateSubmitButton(contentEl, deckSelector, modelSelector, inputContainer, this);
|
||||
AddSubmitButton(contentEl, deckSelector, modelSelector, inputFieldsContainer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,13 +83,7 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
* Removes all elements within the modal's content area.
|
||||
*/
|
||||
onClose() {
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
|
||||
// Clear the content of the modal.
|
||||
contentEl.empty();
|
||||
}
|
||||
|
||||
|
|
@ -139,18 +95,10 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
* @param {HTMLDivElement} inputContainer - Speaking for itself.
|
||||
*/
|
||||
async onOpenAsync(deckSelector: DropdownComponent, modelSelector: DropdownComponent, tagsBody: HTMLDivElement, tagsBodyParagraph: HTMLElement, inputContainer: HTMLDivElement): Promise<void> {
|
||||
/**
|
||||
* @type {Object} codeBlockParameters
|
||||
* @description Stores the values parsed by GetCodeBlockParameters().
|
||||
*/
|
||||
const codeBlockParameters: Object = await this.GetCodeBlockParameters();
|
||||
// console.log(codeBlockParameters);
|
||||
if (!codeBlockParameters) {
|
||||
GenerateFieldGroups(this.plugin, inputContainer, modelSelector.getValue(), null);
|
||||
} else {
|
||||
/**
|
||||
* @description Functions called to pre-select and pre-fill both dropdowns and input fields.
|
||||
*/
|
||||
AutoAssignDeck(deckSelector, codeBlockParameters);
|
||||
AutoAssignModel(modelSelector, codeBlockParameters);
|
||||
if (codeBlockParameters["tags"] != null) {
|
||||
|
|
@ -173,35 +121,12 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
* @return {Object} codeBlockParameters
|
||||
*/
|
||||
async GetCodeBlockParameters(): Promise<Object> {
|
||||
/**
|
||||
* @type {TFile} activeFileData
|
||||
* @description The open and active file in the current instance of Obsidian.
|
||||
* @remarks Since we can't retrieve a code block if no file is opened, if activeFileData is null, we shut down the function.
|
||||
*/
|
||||
const activeFileData: TFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFileData) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @type {string} activeFileContent
|
||||
* @description The whole content of the note.
|
||||
*/
|
||||
const activeFileContent: string = await ReadFileContent(this, activeFileData);
|
||||
/**
|
||||
* @type {string} codeBlock
|
||||
* @description The first code block using "AnkiIntegration" as its language in activeFileContent.
|
||||
*/
|
||||
if (!activeFileData) return;
|
||||
const activeFileContent: string = await this.app.vault.read(activeFileData);
|
||||
const codeBlock: string = activeFileContent.match(/(```AnkiIntegration[\s\S]*?```)/)[1];
|
||||
/**
|
||||
* @type {RegExp} regex
|
||||
* @description A regular expression that is used to retrieve each line of the code block using a "Key: Value;" or "Key: "Value";" format.
|
||||
*/
|
||||
const regex: RegExp = /^\s*(\w+):\s*(?:"([^"]+)"|([^;]+));/gm;
|
||||
/**
|
||||
* @type {RegExp} tagsRegex
|
||||
* @description A regular expression used to retrieve tags from the extracted corresponding line.
|
||||
*/
|
||||
const tagsRegex: RegExp = /"([^"]+)"/g;
|
||||
const retrieveLinesRegex: RegExp = /^\s*(\w+):\s*(?:"([^"]+)"|([^;]+));/gm;
|
||||
const retrieveTagsRegex: RegExp = /"([^"]+)"/g;
|
||||
/**
|
||||
* @type {Object} codeBlockParameters
|
||||
* @description The object that stores all the fields of the note that has to be created, along with their values.
|
||||
|
|
@ -211,43 +136,20 @@ export class AddNoteFromCodeBlockModal extends Modal {
|
|||
"fields": {},
|
||||
"tags": {}
|
||||
};
|
||||
/**
|
||||
* @type {Array} match
|
||||
* @description Stores all the result of regex.exec(codeBlock).
|
||||
*/
|
||||
let match: Array<string> = []
|
||||
/**
|
||||
* @description As long as there are string that match the regex,
|
||||
* we add them as field of codeBlockParameters or as field of codeBlockParameters["fields"].
|
||||
*/
|
||||
while ((match = regex.exec(codeBlock)) !== null) {
|
||||
/**
|
||||
* @type {Array<string>} codeBlockFields
|
||||
* @description All the fields that has to be added as direct child fields of codeBlockParameters.
|
||||
*/
|
||||
const codeBlockChildFields: Array<string> = ["deck", "model", "tags"];
|
||||
/**
|
||||
* @type {string} key
|
||||
* @description The key of the item that will be added to codeBlockParameters.
|
||||
*/
|
||||
while ((match = retrieveLinesRegex.exec(codeBlock)) !== null) {
|
||||
const noteChildAttributes: Array<string> = ["deck", "model", "tags"];
|
||||
const key: string = match[1];
|
||||
/**
|
||||
* @type {string} value
|
||||
* @description The value of the item that will be added to codeBlockParameters.
|
||||
*/
|
||||
const value: string = match[2] || match[3];
|
||||
/**
|
||||
* @description If/else statements allowing to add a value as a direct child of codeBlockParameters or as a direct child of codeBlockParameters["fields"].
|
||||
*/
|
||||
if (codeBlockChildFields.includes(key)) {
|
||||
if (key == "tags") {
|
||||
codeBlockParameters[key] = [];
|
||||
while ((match = tagsRegex.exec(value)) !== null) {
|
||||
const isKeyATag: boolean = key === "tags";
|
||||
const isKeyANoteChildAttribute: boolean = noteChildAttributes.includes(key);
|
||||
if (isKeyANoteChildAttribute) {
|
||||
codeBlockParameters[key] = isKeyATag ? [] : value;
|
||||
if (isKeyATag) {
|
||||
while ((match = retrieveTagsRegex.exec(value)) !== null) {
|
||||
const tag = match[1];
|
||||
codeBlockParameters[key].push(tag);
|
||||
}
|
||||
} else {
|
||||
codeBlockParameters[key] = value;
|
||||
}
|
||||
} else {
|
||||
codeBlockParameters["fields"][key.toLowerCase()] = value;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,28 @@
|
|||
import {
|
||||
App, ButtonComponent, DropdownComponent, FrontMatterCache,
|
||||
App,
|
||||
DropdownComponent,
|
||||
FrontMatterCache,
|
||||
Modal,
|
||||
TFile
|
||||
} from "obsidian";
|
||||
import AnkiIntegration from "../main";
|
||||
import {
|
||||
ProcessAddNote
|
||||
} from "../AnkiConnect";
|
||||
import {
|
||||
AddButton,
|
||||
AddContainer,
|
||||
AddDropdown, AddFieldGroups, AddInput,
|
||||
AddOptionsToDropdownFromDataset,
|
||||
AddParagraph,
|
||||
AddSubtitle, AddTagInputGroup,
|
||||
AddTitle, AutoAssignDeck, AutoAssignModel, AutoGenerateFields, BuildTagsArray, CreateFieldsGroupData,
|
||||
FetchModelByName
|
||||
AddSubtitle,
|
||||
AddTitle
|
||||
} from "../utils";
|
||||
import {
|
||||
GenerateFieldGroups,
|
||||
GenerateDeckSelector,
|
||||
GenerateModelSelector,
|
||||
GenerateSubmitButton,
|
||||
GenerateTagsSection
|
||||
} from "./modalsUtils";
|
||||
AddDeckSelector,
|
||||
AddModelSelector,
|
||||
AddSubmitButton,
|
||||
AddTagsSection,
|
||||
AutoAssignDeck,
|
||||
AutoAssignModel,
|
||||
AutoGenerateFields,
|
||||
AddTagInputGroup
|
||||
} from "./addNoteModalsUtils";
|
||||
|
||||
/**
|
||||
* A modal dialog for creating a new Anki note by using metadata as pre-filled values.
|
||||
|
|
@ -43,13 +42,6 @@ export class AddNoteFromMetadataModal extends Modal {
|
|||
*/
|
||||
plugin: AnkiIntegration;
|
||||
|
||||
/**
|
||||
* Creates a new AddNoteFromMetadataModal instance.
|
||||
* Initializes the modal with the provided app and plugin.
|
||||
* @param {App} app - The Obsidian app instance.
|
||||
* @param {AnkiIntegration} plugin - The AnkiIntegration plugin instance.
|
||||
* @constructor
|
||||
*/
|
||||
constructor(app: App, plugin: AnkiIntegration) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
|
|
@ -62,10 +54,6 @@ export class AddNoteFromMetadataModal extends Modal {
|
|||
*/
|
||||
const ankiData: Object = this.plugin.settings.ankiData;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
this.contentEl.focus();
|
||||
|
||||
|
|
@ -81,23 +69,13 @@ export class AddNoteFromMetadataModal extends Modal {
|
|||
yaml = this.app.metadataCache.getFileCache(activeFileData).frontmatter;
|
||||
}
|
||||
|
||||
// Add the title and subtitle to the modal.
|
||||
AddTitle(contentEl, "Add a new note using metadata");
|
||||
AddSubtitle(contentEl, "Deck & Model");
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} dropdownContainer
|
||||
* @description Container for the deck and model dropdown selectors.
|
||||
*/
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__dropdownContainer--flex"
|
||||
]);
|
||||
|
||||
const deckSelector = GenerateDeckSelector(dropdownContainer, ankiData);
|
||||
|
||||
const modelSelector: DropdownComponent = GenerateModelSelector(dropdownContainer, ankiData);
|
||||
|
||||
const tagsContainer: HTMLDivElement = GenerateTagsSection(contentEl);
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, ["ankiIntegrationModal__dropdownContainer--flex"]);
|
||||
const deckSelector: DropdownComponent = AddDeckSelector(dropdownContainer, ankiData);
|
||||
const modelSelector: DropdownComponent = AddModelSelector(dropdownContainer, ankiData);
|
||||
const tagsContainer: HTMLDivElement = AddTagsSection(contentEl);
|
||||
const tagsBody: HTMLDivElement = tagsContainer.querySelector('#tagsBody');
|
||||
const tagsBodyParagraph: HTMLElement = tagsContainer.querySelector('#tagsBodyTip');
|
||||
|
||||
|
|
@ -108,38 +86,22 @@ export class AddNoteFromMetadataModal extends Modal {
|
|||
}
|
||||
}
|
||||
|
||||
// Add the "Fields" section subtitle.
|
||||
AddSubtitle(contentEl, "Fields");
|
||||
const inputFieldsContainer: HTMLDivElement = AddContainer(contentEl, ["ankiIntegrationModal__inputContainer--flex"]);
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} inputContainer
|
||||
* @description Container for dynamically generated input fields based on the selected model.
|
||||
*/
|
||||
const inputContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__inputContainer--flex"
|
||||
]);
|
||||
|
||||
/**
|
||||
* Event listener triggered when the model selector value changes.
|
||||
* Updates the inputContainer with the fields corresponding to the selected model.
|
||||
* @param {string} value - The selected model name.
|
||||
*/
|
||||
modelSelector.onChange(async (value) => {
|
||||
GenerateFieldGroups(this.plugin, inputContainer, value, yaml);
|
||||
GenerateFieldGroups(this.plugin, inputFieldsContainer, value, yaml);
|
||||
});
|
||||
|
||||
/**
|
||||
* @description If yaml isn't null, trigger the autofill functions. Else, display the "Select a model [...]" message.
|
||||
*/
|
||||
if (yaml != null) {
|
||||
AutoAssignDeck(deckSelector, yaml);
|
||||
AutoAssignModel(modelSelector, yaml);
|
||||
AutoGenerateFields(this, modelSelector, inputContainer, yaml);
|
||||
AutoGenerateFields(this, modelSelector, inputFieldsContainer, yaml);
|
||||
} else {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
AddParagraph(inputFieldsContainer, "Select a model to see its fields.");
|
||||
}
|
||||
|
||||
GenerateSubmitButton(contentEl, deckSelector, modelSelector, inputContainer, this);
|
||||
AddSubmitButton(contentEl, deckSelector, modelSelector, inputFieldsContainer, this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,13 +109,7 @@ export class AddNoteFromMetadataModal extends Modal {
|
|||
* Removes all elements within the modal's content area.
|
||||
*/
|
||||
onClose() {
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
|
||||
// Clear the content of the modal.
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
import {
|
||||
App, ButtonComponent,
|
||||
Modal,
|
||||
App,
|
||||
DropdownComponent,
|
||||
Modal
|
||||
} from "obsidian";
|
||||
import AnkiIntegration from "../main";
|
||||
import {
|
||||
ProcessAddNote
|
||||
} from "../AnkiConnect";
|
||||
import {
|
||||
AddButton,
|
||||
AddContainer,
|
||||
AddDropdown, AddFieldGroups, AddInput,
|
||||
AddOptionsToDropdownFromDataset,
|
||||
AddParagraph,
|
||||
AddSubtitle, AddTagInputGroup,
|
||||
AddTitle, BuildTagsArray, CreateFieldsGroupData,
|
||||
FetchModelByName
|
||||
AddSubtitle,
|
||||
AddTitle
|
||||
} from "../utils";
|
||||
import {
|
||||
AddDeckSelector,
|
||||
AddModelSelector,
|
||||
AddSubmitButton,
|
||||
AddTagsSection,
|
||||
GenerateFieldGroups
|
||||
} from "./addNoteModalsUtils";
|
||||
|
||||
/**
|
||||
* A modal dialog for creating a new Anki note.
|
||||
|
|
@ -33,12 +34,6 @@ export class AddNoteModal extends Modal {
|
|||
*/
|
||||
plugin: AnkiIntegration;
|
||||
|
||||
/**
|
||||
* Creates a new AddNoteModal instance.
|
||||
* Initializes the modal with the provided app and plugin.
|
||||
* @param {App} app - The Obsidian app instance.
|
||||
* @param {AnkiIntegration} plugin - The AnkiIntegration plugin instance.
|
||||
*/
|
||||
constructor(app: App, plugin: AnkiIntegration) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
|
|
@ -55,170 +50,30 @@ export class AddNoteModal extends Modal {
|
|||
*/
|
||||
const ankiData: Object = this.plugin.settings.ankiData;
|
||||
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
this.contentEl.focus();
|
||||
|
||||
// Add the title and subtitle to the modal.
|
||||
AddTitle(contentEl, "Add a new note");
|
||||
AddSubtitle(contentEl, "Deck & Model");
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} dropdownContainer
|
||||
* @description Container for the deck and model dropdown selectors.
|
||||
*/
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__dropdownContainer--flex"
|
||||
]);
|
||||
const dropdownContainer: HTMLDivElement = AddContainer(contentEl, ["ankiIntegrationModal__dropdownContainer--flex"]);
|
||||
const deckSelector: DropdownComponent = AddDeckSelector(dropdownContainer, ankiData);
|
||||
const modelSelector: DropdownComponent = AddModelSelector(dropdownContainer, ankiData);
|
||||
AddTagsSection(contentEl);
|
||||
|
||||
// Create and configure the deck selector dropdown.
|
||||
const deckSelector = AddDropdown(dropdownContainer, "Choose a deck");
|
||||
|
||||
/**
|
||||
* @type {string[]} deckKeys
|
||||
* @description An array containing the keys of all available decks.
|
||||
*/
|
||||
const deckKeys: string[] = Object.keys(ankiData["decksData"]);
|
||||
AddOptionsToDropdownFromDataset(deckSelector, deckKeys, "name", "name", ankiData["decksData"]);
|
||||
|
||||
// Create and configure the model selector dropdown.
|
||||
const modelSelector = AddDropdown(dropdownContainer, "Choose a model");
|
||||
|
||||
/**
|
||||
* @type {string[]} modelKeys
|
||||
* @description An array containing the keys of all available models.
|
||||
*/
|
||||
const modelKeys: string[] = Object.keys(ankiData["modelsData"]);
|
||||
AddOptionsToDropdownFromDataset(modelSelector, modelKeys, "name", "name", ankiData["modelsData"]);
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} tagsHeader
|
||||
* @description A container serving as the head part of the tags section.
|
||||
*/
|
||||
const tagsHeader: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-align-center",
|
||||
"ankiIntegrationModal__container--flex-justify-space-between",
|
||||
]);
|
||||
AddSubtitle(tagsHeader, "Tags");
|
||||
/**
|
||||
* @type {ButtonComponent} addTagFieldButton
|
||||
* @description Button used by the user to add a tag field in the pop-up.
|
||||
*/
|
||||
let addTagFieldButton: ButtonComponent = AddButton(tagsHeader, "", "circle-plus");
|
||||
addTagFieldButton.buttonEl.removeClasses([
|
||||
"ankiIntegrationModal__button--default-width",
|
||||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
]);
|
||||
/**
|
||||
* @type {HTMLDivElement} tagsBody
|
||||
* @description A container serving as the body of the tags section.
|
||||
*/
|
||||
const tagsBody: HTMLDivElement = AddContainer(contentEl);
|
||||
tagsBody.addClasses([
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-wrap",
|
||||
"ankiIntegrationModal__container--gap-16px"
|
||||
])
|
||||
|
||||
const tagsBodyParagraph: HTMLElement = AddParagraph(tagsBody, "No tags will be added to this note, click the \"+\" button to add a new one.");
|
||||
|
||||
/**
|
||||
* @description addTagFieldButton's onClick() event listener used to add a tag input group in tagsBody.
|
||||
*/
|
||||
addTagFieldButton.onClick(async () => {
|
||||
if (tagsBody.firstChild == tagsBodyParagraph) {
|
||||
tagsBody.removeChild(tagsBodyParagraph);
|
||||
}
|
||||
AddTagInputGroup(tagsBody, tagsBodyParagraph);
|
||||
});
|
||||
|
||||
// Add the "Fields" section subtitle.
|
||||
AddSubtitle(contentEl, "Fields");
|
||||
|
||||
/**
|
||||
* @type {HTMLDivElement} inputContainer
|
||||
* @description Container for dynamically generated input fields based on the selected model.
|
||||
*/
|
||||
const inputContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
const inputFieldsContainer: HTMLDivElement = AddContainer(contentEl, [
|
||||
"ankiIntegrationModal__inputContainer--flex"
|
||||
]);
|
||||
|
||||
// Display default message before a model is selected.
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
AddParagraph(inputFieldsContainer, "Select a model to see its fields.");
|
||||
|
||||
/**
|
||||
* Event listener triggered when the model selector value changes.
|
||||
* Updates the inputContainer with the fields corresponding to the selected model.
|
||||
* @param {string} value - The selected model name.
|
||||
*/
|
||||
modelSelector.onChange(async (value) => {
|
||||
/**
|
||||
* @type {Object} selectedModel
|
||||
* @description The model object corresponding to the selected model name.
|
||||
*/
|
||||
const selectedModel: Object = FetchModelByName(this.plugin, value);
|
||||
|
||||
/**
|
||||
* @type {Array} fieldsGroupData
|
||||
* An array of input data storing as separate object (1 objet = 1 input) the keys used to create each label-input pair and the values of each input.
|
||||
*/
|
||||
const fieldsGroupData: Array<Object> = [];
|
||||
|
||||
inputContainer.empty();
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Checks the currently selected option of the dropdown.
|
||||
* If its value is default, it displays a message requesting the user to select a model.
|
||||
* Else, it means that a model is selected, therefore, it creates the fields groups data and displays them.
|
||||
*/
|
||||
if (value === "default") {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
return;
|
||||
} else {
|
||||
/**
|
||||
* @description
|
||||
* Create the different fields group objects and push them into fieldsGroupData.
|
||||
*/
|
||||
CreateFieldsGroupData(fieldsGroupData, selectedModel["fields"], {});
|
||||
AddFieldGroups(inputContainer, fieldsGroupData);
|
||||
}
|
||||
GenerateFieldGroups(this.plugin, inputFieldsContainer, value, null);
|
||||
});
|
||||
|
||||
/**
|
||||
* @type {ButtonComponent} submitButtonEl
|
||||
* @description Submit button for the user to add the note.
|
||||
*/
|
||||
const submitButtonEl: ButtonComponent = AddButton(contentEl, "Create note");
|
||||
|
||||
/**
|
||||
* @description
|
||||
* "Click" event handler to send the form and trigger ProcessAddNote().
|
||||
* @async
|
||||
* @param {MouseEvent} event - The click event triggered by the submit button.
|
||||
*/
|
||||
submitButtonEl.onClick(async () => {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, this);
|
||||
})
|
||||
|
||||
/**
|
||||
* @description
|
||||
* "SHIFT + ENTER" event shortcut handler to send the form and trigger ProcessAddNote().
|
||||
* @async
|
||||
* @param {KeyboardEvent} event - The registered keys that are pressed when contentEl is open.
|
||||
*/
|
||||
this.contentEl.addEventListener("keydown", async (event) => {
|
||||
if (event.shiftKey && event.key === "Enter") {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, this);
|
||||
}
|
||||
})
|
||||
AddSubmitButton(contentEl, deckSelector, modelSelector, inputFieldsContainer, this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -226,13 +81,7 @@ export class AddNoteModal extends Modal {
|
|||
* Removes all elements within the modal's content area.
|
||||
*/
|
||||
onClose() {
|
||||
/**
|
||||
* @type {HTMLElement} contentEl
|
||||
* @description The main content container of the modal.
|
||||
*/
|
||||
const { contentEl } = this;
|
||||
|
||||
// Clear the content of the modal.
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
279
src/modals/addNoteModalsUtils.ts
Normal file
279
src/modals/addNoteModalsUtils.ts
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
import {
|
||||
ButtonComponent,
|
||||
DropdownComponent,
|
||||
FrontMatterCache,
|
||||
Modal
|
||||
} from "obsidian";
|
||||
import {
|
||||
AddButton,
|
||||
AddContainer,
|
||||
AddDropdown,
|
||||
AddFieldGroups, AddInput,
|
||||
AddOptionsToDropdownFromDataset,
|
||||
AddParagraph,
|
||||
AddSubtitle,
|
||||
FetchModelByName
|
||||
} from "../utils";
|
||||
import {ProcessAddNote} from "../AnkiConnect";
|
||||
import AnkiIntegration from "../main";
|
||||
import {AddNoteFromMetadataModal} from "./AddNoteFromMetadataModal";
|
||||
import {AddNoteFromCodeBlockModal} from "./AddNoteFromCodeBlockModal";
|
||||
|
||||
export function AddDeckSelector(parent: HTMLDivElement, ankiData: Object) {
|
||||
const deckSelector: DropdownComponent = AddDropdown(parent, "Choose a deck");
|
||||
const deckKeys: string[] = Object.keys(ankiData["decksData"]);
|
||||
AddOptionsToDropdownFromDataset(deckSelector, deckKeys, "name", "name", ankiData["decksData"]);
|
||||
|
||||
return deckSelector;
|
||||
}
|
||||
|
||||
export function AddModelSelector(parent: HTMLDivElement, ankiData: Object) {
|
||||
const modelSelector: DropdownComponent = AddDropdown(parent, "Choose a model");
|
||||
const modelKeys: string[] = Object.keys(ankiData["modelsData"]);
|
||||
AddOptionsToDropdownFromDataset(modelSelector, modelKeys, "name", "name", ankiData["modelsData"]);
|
||||
|
||||
return modelSelector;
|
||||
}
|
||||
|
||||
export function AddTagsSection(parent: HTMLElement) {
|
||||
const tagsContainer: HTMLDivElement = AddContainer(parent, [
|
||||
"ankiIntegrationModal__container--flex-column"
|
||||
]);
|
||||
|
||||
const tagsHeader: HTMLDivElement = AddContainer(tagsContainer, [
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-align-center",
|
||||
"ankiIntegrationModal__container--flex-justify-space-between",
|
||||
]);
|
||||
|
||||
AddSubtitle(tagsHeader, "Tags");
|
||||
|
||||
let addTagFieldButton: ButtonComponent = AddButton(tagsHeader, "", "circle-plus");
|
||||
addTagFieldButton.buttonEl.removeClasses([
|
||||
"ankiIntegrationModal__button--default-width",
|
||||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
]);
|
||||
|
||||
const tagsBody: HTMLDivElement = AddContainer(
|
||||
tagsContainer,
|
||||
[
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-wrap",
|
||||
"ankiIntegrationModal__container--gap-16px"
|
||||
],
|
||||
"tagsBody")
|
||||
;
|
||||
|
||||
const tagsBodyParagraph: HTMLElement = AddParagraph(tagsBody, "No tags will be added to this note, click the \"+\" button to add a new one.", [], "tagsBodyTip");
|
||||
|
||||
addTagFieldButton.onClick(async () => {
|
||||
if (tagsBody.firstChild == tagsBodyParagraph) {
|
||||
tagsBody.removeChild(tagsBodyParagraph);
|
||||
}
|
||||
AddTagInputGroup(tagsBody, tagsBodyParagraph);
|
||||
});
|
||||
|
||||
return tagsContainer;
|
||||
}
|
||||
|
||||
export function AddSubmitButton(parent: HTMLElement, deckSelector: DropdownComponent, modelSelector: DropdownComponent, inputContainer: HTMLDivElement, modal: Modal) {
|
||||
const submitButtonEl: ButtonComponent = AddButton(parent, "Create Note");
|
||||
|
||||
submitButtonEl.onClick(async () => {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, modal);
|
||||
});
|
||||
|
||||
parent.addEventListener("keydown", async (event) => {
|
||||
if (event.shiftKey && event.key === "Enter") {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, modal);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function GenerateFieldGroups(plugin: AnkiIntegration, inputContainer: HTMLDivElement, selectedValue: any, inputValues: FrontMatterCache | Object | null) {
|
||||
inputContainer.empty();
|
||||
|
||||
const selectedModel: Object = FetchModelByName(plugin, selectedValue);
|
||||
const inputFieldGroupData: Array<Object> = [];
|
||||
|
||||
if (selectedValue === "default") {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
return;
|
||||
} else {
|
||||
if (inputValues) {
|
||||
CreateFieldsGroupData(inputFieldGroupData, selectedModel["fields"], inputValues);
|
||||
AddFieldGroups(inputContainer, inputFieldGroupData);
|
||||
} else {
|
||||
CreateFieldsGroupData(inputFieldGroupData, selectedModel["fields"]);
|
||||
AddFieldGroups(inputContainer, inputFieldGroupData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a div as a child of a given HTMLElement. The div contains an input and a button.
|
||||
* @param {HTMLElement} parent - The parent container to which the button will be added.
|
||||
* @param tagsBodyParagraph
|
||||
* @param {string} tagValue - Speaking for itself.
|
||||
* @return {HTMLDivElement} tagInputGroup
|
||||
*/
|
||||
export function AddTagInputGroup(parent: HTMLElement, tagsBodyParagraph: HTMLElement, tagValue: string = null): HTMLDivElement {
|
||||
/**
|
||||
* @type {HTMLDivElement} inputGroup
|
||||
* @description A container storing the input field and the delete input field button.
|
||||
*/
|
||||
const tagInputGroup: HTMLDivElement = AddContainer(parent);
|
||||
tagInputGroup.addClasses([
|
||||
"ankiIntegrationModal__container--width-fit-content",
|
||||
"ankiIntegrationModal__container--flex-row"
|
||||
]);
|
||||
/**
|
||||
* @type {HTMLInputElement} tagInput
|
||||
* @description A tag input field.
|
||||
* @remarks Default width class has to be removed and replaced by a field-sizing width for the great-parent's wrap to work.
|
||||
*/
|
||||
const tagInput: HTMLInputElement = AddInput(tagInputGroup, "text", "My tag::Super", tagValue, [
|
||||
"ankiIntegrationModal__input--field-sizing-content",
|
||||
"ankiIntegrationModal__tagInput--border",
|
||||
"ankiIntegrationModal__tagInput--focus"
|
||||
]);
|
||||
tagInput.removeClasses([
|
||||
"ankiIntegrationModal__input--default-width"
|
||||
]);
|
||||
tagInput.id = 'tagInput';
|
||||
/**
|
||||
* @type {ButtonComponent} deleteTagInputButton
|
||||
* @description A button allowing the user to delete the field group the button belongs to.
|
||||
* @remarks For the button to look great along with the input, the CTA is disabled, a class is added and all the default classes are removed.
|
||||
*/
|
||||
const deleteTagInputButton: ButtonComponent = AddButton(tagInputGroup, "", "x", [
|
||||
"ankiIntegrationModal__deleteInputButton--border",
|
||||
"ankiIntegrationModal__icon--color-red"
|
||||
]);
|
||||
deleteTagInputButton.removeCta();
|
||||
deleteTagInputButton.buttonEl.removeClasses([
|
||||
"ankiIntegrationModal__button--default-width",
|
||||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
]);
|
||||
/**
|
||||
* @description deleteTagInputButton's onClick() event listener used to delete an input group in tagsBody.
|
||||
*/
|
||||
deleteTagInputButton.onClick(async () => {
|
||||
tagInputGroup.remove();
|
||||
if (parent.children.length == 0) {
|
||||
parent.appendChild(tagsBodyParagraph);
|
||||
}
|
||||
})
|
||||
|
||||
tagInput.focus();
|
||||
|
||||
return tagInputGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of noteParameters["deck"] exists as an option of deckSelector and pre-select it if it exists.
|
||||
*/
|
||||
export function AutoAssignDeck(deckSelector: DropdownComponent, noteParameters: Object) {
|
||||
let deckSelectorHasNoteParametersDeck: boolean = Array.from(deckSelector.selectEl.options).some(option => option.value === noteParameters["deck"]);
|
||||
if (deckSelectorHasNoteParametersDeck) {
|
||||
deckSelector.setValue(noteParameters["deck"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of noteParameters["model"] exists as an option of modelSelector and pre-select it if it exists.
|
||||
*/
|
||||
export function AutoAssignModel(modelSelector: DropdownComponent, noteParameters: Object) {
|
||||
let modelSelectorHasNoteParametersModel: boolean = Array.from(modelSelector.selectEl.options).some(option => option.value === noteParameters["model"]);
|
||||
if (modelSelectorHasNoteParametersModel) {
|
||||
modelSelector.setValue(noteParameters["model"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no model metadata existing as model option, it displays the "Select a model..." message,
|
||||
* else, since it means that a model has been preselected, it generates the fields groups and pre-fill them.
|
||||
*/
|
||||
export function AutoGenerateFields(modal: AddNoteFromMetadataModal | AddNoteFromCodeBlockModal, modelSelector: DropdownComponent, inputContainer: HTMLDivElement, noteParameters: Object) {
|
||||
let modelSelectorHasNoteParametersModel: boolean = Array.from(modelSelector.selectEl.options).some(option => option.value === noteParameters["model"]);
|
||||
if (!modelSelectorHasNoteParametersModel) {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
} else {
|
||||
GenerateFieldGroups(modal.plugin, inputContainer, modelSelector.getValue(), noteParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that create and push fields group data in a given array.
|
||||
*
|
||||
* @description
|
||||
* The following for statement inject in fieldsGroupData a new object corresponding to an input and its label that has to be generated.
|
||||
* Each object has the following property :
|
||||
* - fieldName, a string used as the label text and the placeholder text of the input.
|
||||
* - fieldValue, a string used as the value text of the input.
|
||||
* The fieldName is mandatorily filled, while the fieldValue is optional by being set to null in the first place and being overwritten if the set of value contains a key that is the same as the currently used key.
|
||||
* @param fieldsGroupData - The array in which fieldsGroupData Object will be push.
|
||||
* @param {Array} keys - The set of keys.
|
||||
* @param {Array} values - The set of values.
|
||||
*/
|
||||
export function CreateFieldsGroupData(fieldsGroupData: Array<Object>, keys: Array<string>, values: Object = {}) {
|
||||
for (let i = 0 ; i < keys.length; i++) {
|
||||
const fieldName = keys[i];
|
||||
let fieldValue = null;
|
||||
if (values["fields"]) {
|
||||
fieldValue = ExtractValueFromCodeBlock(fieldValue, values, fieldName);
|
||||
} else {
|
||||
fieldValue = ExtractValueFromMetadata(fieldValue, values, fieldName);
|
||||
}
|
||||
fieldsGroupData[i] = {
|
||||
fieldName: fieldName,
|
||||
fieldValue: fieldValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract value from metadata.
|
||||
* @param {string} fieldValue - The value to change.
|
||||
* @param {Object} values - An object storing the gathered values to draw in.
|
||||
* @param {string} fieldName - The name of the field the value has to be taken for.
|
||||
* @return {string} fieldValue
|
||||
*/
|
||||
function ExtractValueFromMetadata(fieldValue: string, values: Object, fieldName: string): string {
|
||||
if (values.hasOwnProperty(fieldName.toLowerCase())) {
|
||||
fieldValue = values[fieldName.toLowerCase()];
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract value from code block.
|
||||
* @param {string} fieldValue - The value to change.
|
||||
* @param {Object} values - An object storing the gathered values to draw in.
|
||||
* @param {string} fieldName - The name of the field the value has to be taken for.
|
||||
* @return {string} fieldValue
|
||||
*/
|
||||
function ExtractValueFromCodeBlock(fieldValue: string, values: Object, fieldName: string): string {
|
||||
if (values["fields"].hasOwnProperty(fieldName.toLowerCase())) {
|
||||
fieldValue = values["fields"][fieldName.toLowerCase()];
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array that stores all tags entered by the user in the form used to add a note.
|
||||
* @return {Array<string>} tags
|
||||
*/
|
||||
export function BuildTagsArray(): Array<string> {
|
||||
const tagInputs = document.querySelectorAll('#tagInput');
|
||||
let tags: Array<string> = [];
|
||||
tagInputs.forEach((tagInput) => {
|
||||
// @ts-ignore
|
||||
tags.push(tagInput.value);
|
||||
})
|
||||
return tags;
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
import {ButtonComponent, DropdownComponent, FrontMatterCache, Modal} from "obsidian";
|
||||
import {
|
||||
AddButton,
|
||||
AddContainer,
|
||||
AddDropdown, AddFieldGroups,
|
||||
AddOptionsToDropdownFromDataset,
|
||||
AddParagraph,
|
||||
AddSubtitle,
|
||||
AddTagInputGroup, BuildTagsArray, CreateFieldsGroupData, FetchModelByName
|
||||
} from "../utils";
|
||||
import {ProcessAddNote} from "../AnkiConnect";
|
||||
import AnkiIntegration from "../main";
|
||||
|
||||
export function GenerateDeckSelector(parent: HTMLDivElement, ankiData: Object) {
|
||||
/**
|
||||
* @type {DropdownComponent} deckSelector
|
||||
* @description Dropdown allowing the user to select a deck among those that are synchronized.
|
||||
*/
|
||||
const deckSelector: DropdownComponent = AddDropdown(parent, "Choose a deck");
|
||||
|
||||
/**
|
||||
* @type {string[]} deckKeys
|
||||
* @description An array containing the keys of all available decks.
|
||||
*/
|
||||
const deckKeys: string[] = Object.keys(ankiData["decksData"]);
|
||||
AddOptionsToDropdownFromDataset(deckSelector, deckKeys, "name", "name", ankiData["decksData"]);
|
||||
|
||||
return deckSelector;
|
||||
}
|
||||
|
||||
export function GenerateModelSelector(parent: HTMLDivElement, ankiData: Object) {
|
||||
/**
|
||||
* @type {DropdownComponent} modelSelector
|
||||
* @description Dropdown allowing the user to select a model among those that are synchronized.
|
||||
*/
|
||||
const modelSelector: DropdownComponent = AddDropdown(parent, "Choose a model");
|
||||
|
||||
/**
|
||||
* @type {string[]} modelKeys
|
||||
* @description An array containing the keys of all available models.
|
||||
*/
|
||||
const modelKeys: string[] = Object.keys(ankiData["modelsData"]);
|
||||
AddOptionsToDropdownFromDataset(modelSelector, modelKeys, "name", "name", ankiData["modelsData"]);
|
||||
|
||||
return modelSelector;
|
||||
}
|
||||
|
||||
export function GenerateTagsSection(parent: HTMLElement) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const tagsContainer: HTMLDivElement = AddContainer(parent, [
|
||||
"ankiIntegrationModal__container--flex-column"
|
||||
])
|
||||
/**
|
||||
* @type {HTMLDivElement} tagsHeader
|
||||
* @description A container serving as the head part of the tags section.
|
||||
*/
|
||||
const tagsHeader: HTMLDivElement = AddContainer(tagsContainer, [
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-align-center",
|
||||
"ankiIntegrationModal__container--flex-justify-space-between",
|
||||
]);
|
||||
AddSubtitle(tagsHeader, "Tags");
|
||||
/**
|
||||
* @type {ButtonComponent} addTagFieldButton
|
||||
* @description Button used by the user to add a tag field in the pop-up.
|
||||
*/
|
||||
let addTagFieldButton: ButtonComponent = AddButton(tagsHeader, "", "circle-plus");
|
||||
addTagFieldButton.buttonEl.removeClasses([
|
||||
"ankiIntegrationModal__button--default-width",
|
||||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
]);
|
||||
/**
|
||||
* @type {HTMLDivElement} tagsBody
|
||||
* @description A container serving as the body of the tags section.
|
||||
*/
|
||||
const tagsBody: HTMLDivElement = AddContainer(
|
||||
tagsContainer,
|
||||
[
|
||||
"ankiIntegrationModal__container--flex-row",
|
||||
"ankiIntegrationModal__container--flex-wrap",
|
||||
"ankiIntegrationModal__container--gap-16px"
|
||||
],
|
||||
"tagsBody")
|
||||
;
|
||||
|
||||
const tagsBodyParagraph: HTMLElement = AddParagraph(tagsBody, "No tags will be added to this note, click the \"+\" button to add a new one.", [], "tagsBodyTip");
|
||||
|
||||
/**
|
||||
* @description addTagFieldButton's onClick() event listener used to add a tag input group in tagsBody.
|
||||
*/
|
||||
addTagFieldButton.onClick(async () => {
|
||||
if (tagsBody.firstChild == tagsBodyParagraph) {
|
||||
tagsBody.removeChild(tagsBodyParagraph);
|
||||
}
|
||||
/**
|
||||
* @type {HTMLDivElement} inputGroup
|
||||
* @description A container storing the input field and the delete input field button.
|
||||
*/
|
||||
const tagInputGroup: HTMLDivElement = AddTagInputGroup(tagsBody, tagsBodyParagraph);
|
||||
});
|
||||
|
||||
return tagsContainer;
|
||||
}
|
||||
|
||||
export function GenerateSubmitButton(parent: HTMLElement, deckSelector: DropdownComponent, modelSelector: DropdownComponent, inputContainer: HTMLDivElement, modal: Modal) {
|
||||
/**
|
||||
* @type {ButtonComponent} submitButtonEl
|
||||
* @description Submit button for the user to add the note.
|
||||
*/
|
||||
const submitButtonEl: ButtonComponent = AddButton(parent, "Create Note");
|
||||
|
||||
/**
|
||||
* @description
|
||||
* "Click" event handler to send the form and trigger ProcessAddNote().
|
||||
* @async
|
||||
* @param {MouseEvent} event - The click event triggered by the submit button.
|
||||
*/
|
||||
submitButtonEl.onClick(async () => {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, modal);
|
||||
});
|
||||
/**
|
||||
* @description
|
||||
* "SHIFT + ENTER" event shortcut handler to send the form and trigger ProcessAddNote().
|
||||
* @async
|
||||
* @param {KeyboardEvent} event - The registered keys that are pressed when contentEl is open.
|
||||
*/
|
||||
parent.addEventListener("keydown", async (event) => {
|
||||
if (event.shiftKey && event.key === "Enter") {
|
||||
const tags: Array<string> = BuildTagsArray();
|
||||
await ProcessAddNote(deckSelector, modelSelector, inputContainer, tags, modal);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function GenerateFieldGroups(plugin: AnkiIntegration, inputContainer: HTMLDivElement, selectedValue: any, inputValues:FrontMatterCache | Object | null) {
|
||||
inputContainer.empty();
|
||||
|
||||
/**
|
||||
* @type {Object} selectedModel
|
||||
* @description The model object corresponding to the selected model name.
|
||||
*/
|
||||
const selectedModel: Object = FetchModelByName(plugin, selectedValue);
|
||||
/**
|
||||
* @type {Array} fieldsGroupData
|
||||
* @description An array of input data storing as separate object (1 object = 1 input) the keys used to create each label-input pair and the values of each input.
|
||||
*/
|
||||
const fieldsGroupData: Array<Object> = [];
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Checks the currently selected option of the dropdown.
|
||||
* If its value is default, it displays a message requesting the user to select a model.
|
||||
* Else, it means that a model is selected, therefore, it creates the fields groups data and displays them.
|
||||
*/
|
||||
if (selectedValue === "default") {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
return;
|
||||
} else {
|
||||
if (inputValues) {
|
||||
CreateFieldsGroupData(fieldsGroupData, selectedModel["fields"], inputValues);
|
||||
AddFieldGroups(inputContainer, fieldsGroupData);
|
||||
} else {
|
||||
CreateFieldsGroupData(fieldsGroupData, selectedModel["fields"]);
|
||||
AddFieldGroups(inputContainer, fieldsGroupData);
|
||||
}
|
||||
}
|
||||
}
|
||||
221
src/utils.ts
221
src/utils.ts
|
|
@ -1,8 +1,8 @@
|
|||
import AnkiIntegration from "./main";
|
||||
import {ButtonComponent, DropdownComponent, Modal, TFile} from "obsidian";
|
||||
import {AddNoteFromMetadataModal} from "./modals/AddNoteFromMetadataModal";
|
||||
import {AddNoteFromCodeBlockModal} from "./modals/AddNoteFromCodeBlockModal";
|
||||
import {GenerateFieldGroups} from "./modals/modalsUtils";
|
||||
import {
|
||||
ButtonComponent,
|
||||
DropdownComponent,
|
||||
} from "obsidian";
|
||||
|
||||
/**
|
||||
* Fetches a model that has been saved in data.json by SynchronizeData().
|
||||
|
|
@ -11,10 +11,6 @@ import {GenerateFieldGroups} from "./modals/modalsUtils";
|
|||
* @return {Object} model - The model data, if found.
|
||||
*/
|
||||
export function FetchModelByName(plugin: AnkiIntegration, name: string): Object {
|
||||
/**
|
||||
* @type {Object<string, string|number>} modelsData
|
||||
* A reference to the JSON containing every model-related data.
|
||||
*/
|
||||
const modelsData: Object = plugin.settings.ankiData["modelsData"];
|
||||
for (const [key, model] of Object.entries(modelsData)) {
|
||||
if (model.name === name) {
|
||||
|
|
@ -35,10 +31,6 @@ export function AddContainer(parent: HTMLElement, classes: string[] = [], id: st
|
|||
* Pushes into classes all CSS classes that are mandatory for a container.
|
||||
*/
|
||||
classes.push();
|
||||
/**
|
||||
* @type {HTMLDivElement} createdEl
|
||||
* The created container.
|
||||
*/
|
||||
let createdEl: HTMLDivElement;
|
||||
createdEl = parent.createEl("div", {
|
||||
cls: [
|
||||
|
|
@ -67,10 +59,6 @@ export function AddTitle(parent: HTMLElement, title: string, classes: string[] =
|
|||
"ankiIntegrationModal__h1--margin",
|
||||
"ankiIntegrationModal__h1--text-align"
|
||||
);
|
||||
/**
|
||||
* @type {HTMLElement} createdEl
|
||||
* The created title.
|
||||
*/
|
||||
let createdEl: HTMLElement;
|
||||
createdEl = parent.createEl("h1", {
|
||||
text: title,
|
||||
|
|
@ -96,10 +84,6 @@ export function AddSubtitle(parent: HTMLElement, subtitle: string, classes: stri
|
|||
classes.push(
|
||||
"ankiIntegrationModal__h2--fit-content"
|
||||
);
|
||||
/**
|
||||
* @type {HTMLElement} createdEl
|
||||
* The created subtitle.
|
||||
*/
|
||||
let createdEl: HTMLElement;
|
||||
createdEl = parent.createEl("h2", {
|
||||
text: subtitle,
|
||||
|
|
@ -127,10 +111,6 @@ export function AddParagraph(parent: HTMLElement, text: string, classes: string[
|
|||
"ankiIntegrationModal__paragraph--text-align",
|
||||
"ankiIntegrationModal__paragraph--default-width"
|
||||
);
|
||||
/**
|
||||
* @type {HTMLElement} createdEl
|
||||
* The created paragraph.
|
||||
*/
|
||||
let createdEl: HTMLElement;
|
||||
createdEl = parent.createEl("p", {
|
||||
text: text,
|
||||
|
|
@ -159,10 +139,6 @@ export function AddDropdown(parent: HTMLElement, defaultString: string, classes:
|
|||
classes.push(
|
||||
"ankiIntegrationModal__dropdown--default-width"
|
||||
)
|
||||
/**
|
||||
* @type {DropdownComponent} createdEl
|
||||
* The created dropdown component.
|
||||
*/
|
||||
let createdEl: DropdownComponent;
|
||||
createdEl = new DropdownComponent(parent);
|
||||
/**
|
||||
|
|
@ -203,12 +179,6 @@ export function AddLabel(parent: HTMLElement, text: string, classes: string[] =
|
|||
* Pushes into classes all CSS classes that are mandatory for a container.
|
||||
*/
|
||||
classes.push();
|
||||
/**
|
||||
* @type {HTMLLabelElement} createdEl
|
||||
* The created label.
|
||||
* @remarks
|
||||
* Defining the created label as an `HTMLLabelElement` instead of a generic `HTMLElement` ensures access to label-specific properties.
|
||||
*/
|
||||
let createdEl: HTMLLabelElement;
|
||||
createdEl = parent.createEl("label", {
|
||||
text: text,
|
||||
|
|
@ -236,12 +206,6 @@ export function AddInput(parent: HTMLElement, type: string, placeholder: string
|
|||
classes.push(
|
||||
"ankiIntegrationModal__input--default-width"
|
||||
);
|
||||
/**
|
||||
* @type {HTMLInputElement} createdEl
|
||||
* The created input.
|
||||
* @remarks
|
||||
* Defining the created input as an `HTMLInputElement` instead of a generic `HTMLElement` ensures access to input-specific properties such as `value` or `checked`.
|
||||
*/
|
||||
let createdEl: HTMLInputElement;
|
||||
createdEl = parent.createEl("input", {
|
||||
type: type,
|
||||
|
|
@ -284,10 +248,6 @@ export function AddButton(parent: HTMLElement, text: string = null, icon: string
|
|||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
);
|
||||
/**
|
||||
* @type {ButtonComponent} createdEl
|
||||
* The created button.
|
||||
*/
|
||||
let createdEl: ButtonComponent = new ButtonComponent(parent);
|
||||
createdEl.setCta();
|
||||
if (text) {
|
||||
|
|
@ -300,177 +260,4 @@ export function AddButton(parent: HTMLElement, text: string = null, icon: string
|
|||
createdEl.setClass(cssClass);
|
||||
})
|
||||
return createdEl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that create and push fields group data in a given array.
|
||||
*
|
||||
* @description
|
||||
* The following for statement inject in fieldsGroupData a new object corresponding to an input and its label that has to be generated.
|
||||
* Each object has the following property :
|
||||
* - fieldName, a string used as the label text and the placeholder text of the input.
|
||||
* - fieldValue, a string used as the value text of the input.
|
||||
* The fieldName is mandatorily filled, while the fieldValue is optional by being set to null in the first place and being overwritten if the set of value contains a key that is the same as the currently used key.
|
||||
* @param fieldsGroupData - The array in which fieldsGroupData Object will be push.
|
||||
* @param {Array} keys - The set of keys.
|
||||
* @param {Array} values - The set of values.
|
||||
*/
|
||||
export function CreateFieldsGroupData(fieldsGroupData: Array<Object>, keys: Array<string>, values: Object = {}) {
|
||||
for (let i = 0 ; i < keys.length; i++) {
|
||||
const fieldName = keys[i];
|
||||
let fieldValue = null;
|
||||
if (values["fields"]) {
|
||||
fieldValue = ExtractValueFromCodeBlock(fieldValue, values, fieldName);
|
||||
} else {
|
||||
fieldValue = ExtractValueFromMetadata(fieldValue, values, fieldName);
|
||||
}
|
||||
fieldsGroupData[i] = {
|
||||
fieldName: fieldName,
|
||||
fieldValue: fieldValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract value from metadata.
|
||||
* @param {string} fieldValue - The value to change.
|
||||
* @param {Object} values - An object storing the gathered values to draw in.
|
||||
* @param {string} fieldName - The name of the field the value has to be taken for.
|
||||
* @return {string} fieldValue
|
||||
*/
|
||||
function ExtractValueFromMetadata(fieldValue: string, values: Object, fieldName: string): string {
|
||||
if (values.hasOwnProperty(fieldName.toLowerCase())) {
|
||||
fieldValue = values[fieldName.toLowerCase()];
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract value from code block.
|
||||
* @param {string} fieldValue - The value to change.
|
||||
* @param {Object} values - An object storing the gathered values to draw in.
|
||||
* @param {string} fieldName - The name of the field the value has to be taken for.
|
||||
* @return {string} fieldValue
|
||||
*/
|
||||
function ExtractValueFromCodeBlock(fieldValue: string, values: Object, fieldName: string): string {
|
||||
if (values["fields"].hasOwnProperty(fieldName.toLowerCase())) {
|
||||
fieldValue = values["fields"][fieldName.toLowerCase()];
|
||||
return fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that returns the content of a given TFile.
|
||||
* @param {Modal} modal - The instance of the modal that needs to read a file content.
|
||||
* @param {TFile} fileData - The file that has to be read.
|
||||
*/
|
||||
export async function ReadFileContent(modal: Modal, fileData: TFile): Promise<string> {
|
||||
return await modal.app.vault.read(fileData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of noteParameters["deck"] exists as an option of deckSelector and pre-select it if it exists.
|
||||
*/
|
||||
export function AutoAssignDeck(deckSelector: DropdownComponent, noteParameters: Object) {
|
||||
let deckSelectorHasNoteParametersDeck: boolean = Array.from(deckSelector.selectEl.options).some(option => option.value === noteParameters["deck"]);
|
||||
if (deckSelectorHasNoteParametersDeck) {
|
||||
deckSelector.setValue(noteParameters["deck"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value of noteParameters["model"] exists as an option of modelSelector and pre-select it if it exists.
|
||||
*/
|
||||
export function AutoAssignModel(modelSelector: DropdownComponent, noteParameters: Object) {
|
||||
let modelSelectorHasNoteParametersModel: boolean = Array.from(modelSelector.selectEl.options).some(option => option.value === noteParameters["model"]);
|
||||
if (modelSelectorHasNoteParametersModel) {
|
||||
modelSelector.setValue(noteParameters["model"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is no model metadata existing as model option, it displays the "Select a model..." message,
|
||||
* else, since it means that a model has been preselected, it generates the fields groups and pre-fill them.
|
||||
*/
|
||||
export function AutoGenerateFields(modal: AddNoteFromMetadataModal | AddNoteFromCodeBlockModal, modelSelector: DropdownComponent, inputContainer: HTMLDivElement, noteParameters: Object) {
|
||||
let modelSelectorHasNoteParametersModel: boolean = Array.from(modelSelector.selectEl.options).some(option => option.value === noteParameters["model"]);
|
||||
if (!modelSelectorHasNoteParametersModel) {
|
||||
AddParagraph(inputContainer, "Select a model to see its fields.");
|
||||
} else {
|
||||
GenerateFieldGroups(modal.plugin, inputContainer, modelSelector.getValue(), noteParameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array that stores all tags entered by the user in the form used to add a note.
|
||||
* @return {Array<string>} tags
|
||||
*/
|
||||
export function BuildTagsArray(): Array<string> {
|
||||
const tagInputs = document.querySelectorAll('#tagInput');
|
||||
let tags: Array<string> = [];
|
||||
tagInputs.forEach((tagInput) => {
|
||||
// @ts-ignore
|
||||
tags.push(tagInput.value);
|
||||
})
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a div as a child of a given HTMLElement. The div contains an input and a button.
|
||||
* @param {HTMLElement} parent - The parent container to which the button will be added.
|
||||
* @param {string} tagValue - Speaking for itself.
|
||||
* @return {HTMLDivElement} tagInputGroup
|
||||
*/
|
||||
export function AddTagInputGroup(parent: HTMLElement, tagsBodyParagraph: HTMLElement, tagValue: string = null): HTMLDivElement {
|
||||
/**
|
||||
* @type {HTMLDivElement} inputGroup
|
||||
* @description A container storing the input field and the delete input field button.
|
||||
*/
|
||||
const tagInputGroup: HTMLDivElement = AddContainer(parent);
|
||||
tagInputGroup.addClasses([
|
||||
"ankiIntegrationModal__container--width-fit-content",
|
||||
"ankiIntegrationModal__container--flex-row"
|
||||
]);
|
||||
/**
|
||||
* @type {HTMLInputElement} tagInput
|
||||
* @description A tag input field.
|
||||
* @remarks Default width class has to be removed and replaced by a field-sizing width for the great-parent's wrap to work.
|
||||
*/
|
||||
const tagInput: HTMLInputElement = AddInput(tagInputGroup, "text", "My tag::Super", tagValue, [
|
||||
"ankiIntegrationModal__input--field-sizing-content",
|
||||
"ankiIntegrationModal__tagInput--border",
|
||||
"ankiIntegrationModal__tagInput--focus"
|
||||
]);
|
||||
tagInput.removeClasses([
|
||||
"ankiIntegrationModal__input--default-width"
|
||||
]);
|
||||
tagInput.id = 'tagInput';
|
||||
/**
|
||||
* @type {ButtonComponent} deleteTagInputButton
|
||||
* @description A button allowing the user to delete the field group the button belongs to.
|
||||
* @remarks For the button to look great along with the input, the CTA is disabled, a class is added and all the default classes are removed.
|
||||
*/
|
||||
const deleteTagInputButton: ButtonComponent = AddButton(tagInputGroup, "", "x", [
|
||||
"ankiIntegrationModal__deleteInputButton--border",
|
||||
"ankiIntegrationModal__icon--color-red"
|
||||
]);
|
||||
deleteTagInputButton.removeCta();
|
||||
deleteTagInputButton.buttonEl.removeClasses([
|
||||
"ankiIntegrationModal__button--default-width",
|
||||
"ankiIntegrationModal__button--default-margin",
|
||||
"ankiIntegrationModal__button--default-padding"
|
||||
]);
|
||||
/**
|
||||
* @description deleteTagInputButton's onClick() event listener used to delete an input group in tagsBody.
|
||||
*/
|
||||
deleteTagInputButton.onClick(async () => {
|
||||
tagInputGroup.remove();
|
||||
if (parent.children.length == 0) {
|
||||
parent.appendChild(tagsBodyParagraph);
|
||||
}
|
||||
})
|
||||
|
||||
tagInput.focus();
|
||||
|
||||
return tagInputGroup;
|
||||
}
|
||||
Loading…
Reference in a new issue