diff --git a/src/core/Templater.ts b/src/core/Templater.ts index f1e3532e..cae4b356 100644 --- a/src/core/Templater.ts +++ b/src/core/Templater.ts @@ -1,5 +1,6 @@ import { App, + getFrontMatterInfo, MarkdownPostProcessorContext, MarkdownView, normalizePath, @@ -13,6 +14,7 @@ import { get_active_file, get_folder_path_from_file_path, resolve_tfile, + merge_front_matter, } from "utils/Utils"; import TemplaterPlugin from "main"; import { @@ -22,6 +24,7 @@ import { import { errorWrapper, errorWrapperSync, TemplaterError } from "utils/Error"; import { Parser } from "./parser/Parser"; import { log_error } from "utils/Log"; +import * as yaml from "js-yaml"; export enum RunMode { CreateNewFromTemplate, @@ -254,10 +257,20 @@ export class Templater { return; } + const front_matter_info = getFrontMatterInfo(output_content); + const frontmatter = yaml.load(front_matter_info.frontmatter); + await merge_front_matter( + this.plugin.app, + active_editor.file, + frontmatter + ); + const editor = active_editor.editor; const doc = editor.getDoc(); const oldSelections = doc.listSelections(); - doc.replaceSelection(output_content); + doc.replaceSelection( + output_content.slice(front_matter_info.contentStart) + ); if (active_view) { // Wait for view to finish rendering properties widget await delay(100); @@ -293,7 +306,7 @@ export class Templater { file, RunMode.OverwriteFile ); - const output_content = await errorWrapper( + let output_content = await errorWrapper( async () => this.read_and_parse_template(running_config), "Template parsing error, aborting." ); @@ -302,7 +315,30 @@ export class Templater { await this.end_templater_task(path); return; } - await this.plugin.app.vault.modify(file, output_content); + + let existing_front_matter = null; + await delay(100); // Sometimes the front matter is not yet available if the file was just created + await this.plugin.app.fileManager.processFrontMatter( + file, + (front_matter) => { + existing_front_matter = front_matter; + } + ); + + const front_matter_info = getFrontMatterInfo(output_content); + const frontmatter = yaml.load(front_matter_info.frontmatter); + + if (existing_front_matter) { + // Bases can create frontmatter, merge this into the template frontmatter + await merge_front_matter(this.plugin.app, file, frontmatter); + await this.plugin.app.vault.append( + file, + output_content.slice(front_matter_info.contentStart) + ); + output_content = await this.plugin.app.vault.read(file); + } else { + await this.plugin.app.vault.modify(file, output_content); + } // Set cursor to first line of editor (below properties) // https://github.com/SilentVoid13/Templater/issues/1231 if ( @@ -491,8 +527,13 @@ export class Templater { return; } + const file_content = await app.vault.read(file); + const frontmatter_info = getFrontMatterInfo(file_content); + const content_size = + file_content.length - frontmatter_info.contentStart; + if ( - file.stat.size == 0 && + content_size == 0 && templater.plugin.settings.enable_folder_templates ) { const folder_template_match = @@ -512,7 +553,7 @@ export class Templater { } await templater.write_template_to_file(template_file, file); } else if ( - file.stat.size == 0 && + content_size == 0 && templater.plugin.settings.enable_file_templates ) { const file_template_match = diff --git a/src/core/functions/internal_functions/file/InternalModuleFile.ts b/src/core/functions/internal_functions/file/InternalModuleFile.ts index 9e0b03db..3243b9dc 100644 --- a/src/core/functions/internal_functions/file/InternalModuleFile.ts +++ b/src/core/functions/internal_functions/file/InternalModuleFile.ts @@ -1,8 +1,10 @@ import { InternalModule } from "../InternalModule"; import { log_error } from "utils/Log"; +import * as yaml from "js-yaml"; import { FileSystemAdapter, getAllTags, + getFrontMatterInfo, moment, normalizePath, parseLinktext, @@ -13,6 +15,7 @@ import { } from "obsidian"; import { TemplaterError } from "utils/Error"; import { ModuleName } from "editor/TpDocumentation"; +import { merge_front_matter } from "utils/Utils"; export const DEPTH_LIMIT = 10; @@ -210,14 +213,20 @@ export class InternalModuleFile extends InternalModule { } } + const active_file = this.plugin.app.workspace.getActiveFile(); + try { const parsed_content = await this.plugin.templater.parser.parse_commands( inc_file_content, this.plugin.templater.current_functions_object ); + const front_matter_info = getFrontMatterInfo(parsed_content); + const frontmatter = yaml.load(front_matter_info.frontmatter); + await merge_front_matter(this.plugin.app, active_file, frontmatter); + this.include_depth -= 1; - return parsed_content; + return parsed_content.slice(front_matter_info.contentStart); } catch (e) { this.include_depth -= 1; throw e; diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 0517cdaf..d5dc13ba 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -1,4 +1,13 @@ -import { DocBlock, DocNode, DocParamBlock, DocParamCollection, DocPlainText, DocSection, ParserContext, TSDocParser } from "@microsoft/tsdoc"; +import { + DocBlock, + DocNode, + DocParamBlock, + DocParamCollection, + DocPlainText, + DocSection, + ParserContext, + TSDocParser, +} from "@microsoft/tsdoc"; import { TJDocFile, TJDocFileArgument } from "./TJDocFile"; @@ -80,23 +89,21 @@ export async function populate_docs_from_user_scripts( app: App, files: Array ): Promise { - const docFiles = await Promise.all(files.map(async file => { + const docFiles = await Promise.all( + files.map(async (file) => { // Get file contents - const content = await app.vault.cachedRead(file) - + const content = await app.vault.cachedRead(file); + const newDocFile = generate_jsdoc(file, content); - + return newDocFile; - } - )); + }) + ); return docFiles; } -function generate_jsdoc( - file: TFile, - content: string -): TJDocFile{ +function generate_jsdoc(file: TFile, content: string): TJDocFile { // Parse the content const tsdocParser = new TSDocParser(); const parsedDoc = tsdocParser.parseString(content); @@ -104,39 +111,43 @@ function generate_jsdoc( // Copy and extract information into the TJDocFile const newDocFile = new TJDocFile(file); - newDocFile.description = generate_jsdoc_description(parsedDoc.docComment.summarySection); - newDocFile.returns = generate_jsdoc_return(parsedDoc.docComment.returnsBlock); - newDocFile.arguments = generate_jsdoc_arguments(parsedDoc.docComment.params); - - return newDocFile + newDocFile.description = generate_jsdoc_description( + parsedDoc.docComment.summarySection + ); + newDocFile.returns = generate_jsdoc_return( + parsedDoc.docComment.returnsBlock + ); + newDocFile.arguments = generate_jsdoc_arguments( + parsedDoc.docComment.params + ); + + return newDocFile; } -function generate_jsdoc_description( - summarySection: DocSection -) : string { +function generate_jsdoc_description(summarySection: DocSection): string { try { - const description = summarySection.nodes.map((node: DocNode) => - node.getChildNodes() + const description = summarySection.nodes.map((node: DocNode) => + node + .getChildNodes() .filter((node: DocNode) => node instanceof DocPlainText) .map((x: DocPlainText) => x.text) .join("\n") ); - - return description.join("\n"); + + return description.join("\n"); } catch (error) { - console.error('Failed to parse sumamry section'); - throw error; + console.error("Failed to parse summary section"); } } -function generate_jsdoc_return( - returnSection : DocBlock | undefined -): string { +function generate_jsdoc_return(returnSection: DocBlock | undefined): string { if (!returnSection) return ""; try { - const returnValue = returnSection.content.nodes[0].getChildNodes()[0].text.trim(); - return returnValue; + const returnValue = returnSection.content.nodes[0] + .getChildNodes()[0] + .text.trim(); + return returnValue; } catch (error) { return ""; } @@ -144,18 +155,21 @@ function generate_jsdoc_return( function generate_jsdoc_arguments( paramSection: DocParamCollection -) : TJDocFileArgument[] { +): TJDocFileArgument[] { try { const blocks = paramSection.blocks; const args = blocks.map((block) => { - const name = block.parameterName; - const description = block.content.getChildNodes()[0].getChildNodes() - .filter(x => x instanceof DocPlainText) - .map(x => x.text).join(" ") - return new TJDocFileArgument(name, description); - }) - - return args; + const name = block.parameterName; + const description = block.content + .getChildNodes()[0] + .getChildNodes() + .filter((x) => x instanceof DocPlainText) + .map((x) => x.text) + .join(" "); + return new TJDocFileArgument(name, description); + }); + + return args; } catch (error) { return []; } @@ -212,17 +226,64 @@ export function get_fn_params(func: (...args: unknown[]) => unknown) { */ export function append_bolded_label_with_value_to_parent( parent: HTMLElement, - title: string, - value: string -): HTMLElement{ - const tag = parent instanceof HTMLOListElement ? "li" : "p"; + title: string, + value: string +): HTMLElement { + const tag = parent instanceof HTMLOListElement ? "li" : "p"; const para = parent.createEl(tag); - const bold = parent.createEl('b', {text: title}); + const bold = parent.createEl("b", { text: title }); para.appendChild(bold); - para.appendChild(document.createTextNode(`: ${value}`)) + para.appendChild(document.createTextNode(`: ${value}`)); // Returns a p or li element // Resulting in Title: value return para; } + +export async function merge_front_matter( + app: App, + file: TFile | null, + properties: Record +): Promise { + if (!file || !is_object(properties)) { + return; + } + try { + await app.fileManager.processFrontMatter(file, (frontmatter) => { + for (const prop in properties) { + const currentValue = frontmatter[prop]; + const newValue = properties[prop]; + + if (currentValue === undefined) { + // If the property doesn't exist, add it + frontmatter[prop] = newValue; + } else if ( + Array.isArray(currentValue) || + Array.isArray(newValue) + ) { + // If either is an array, merge them + frontmatter[prop] = Array.from( + new Set([ + ...(currentValue + ? Array.isArray(currentValue) + ? currentValue + : [currentValue] + : []), + ...(newValue + ? Array.isArray(newValue) + ? newValue + : [newValue] + : []), + ]) + ); + } else if (newValue !== currentValue && newValue != null) { + // If they are different, update the value + frontmatter[prop] = newValue; + } + } + }); + } catch (error) { + console.error("Error in processing frontmatter: ", error); + } +}