Skip to content

Support disabling default front matter and add support for Templater #119

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 9 commits into from
Jan 24, 2024
100 changes: 94 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { MarkdownView, Notice, parseYaml, Plugin, stringifyYaml, TFile, TFolder
import { getDefaultSettings, MediaDbPluginSettings, MediaDbSettingTab } from './settings/Settings';
import { APIManager } from './api/APIManager';
import { MediaTypeModel } from './models/MediaTypeModel';
import { CreateNoteOptions, dateTimeToString, markdownTable, replaceIllegalFileNameCharactersInString, unCamelCase } from './utils/Utils';
import {
CreateNoteOptions,
dateTimeToString,
markdownTable,
replaceIllegalFileNameCharactersInString,
unCamelCase,
hasTemplaterPlugin,
useTemplaterPluginInFile,
} from './utils/Utils';
import { OMDbAPI } from './api/apis/OMDbAPI';
import { MALAPI } from './api/apis/MALAPI';
import { MALAPIManga } from './api/apis/MALAPIManga';
Expand Down Expand Up @@ -63,7 +71,8 @@ export default class MediaDbPlugin extends Plugin {
this.app.workspace.on('file-menu', (menu, file) => {
if (file instanceof TFolder) {
menu.addItem(item => {
item.setTitle('Import folder as Media DB entries')
item
.setTitle('Import folder as Media DB entries')
.setIcon('database')
.onClick(() => this.createEntriesFromFolder(file));
});
Expand Down Expand Up @@ -286,7 +295,11 @@ export default class MediaDbPlugin extends Plugin {
options.folder = await this.mediaTypeManager.getFolder(mediaTypeModel, this.app);
}

await this.createNote(this.mediaTypeManager.getFileName(mediaTypeModel), fileContent, options);
const targetFile = await this.createNote(this.mediaTypeManager.getFileName(mediaTypeModel), fileContent, options);

if (this.settings.enableTemplaterIntegration) {
await useTemplaterPluginInFile(this.app, targetFile);
}
} catch (e) {
console.warn(e);
new Notice(e.toString());
Expand All @@ -299,14 +312,87 @@ export default class MediaDbPlugin extends Plugin {
}

async generateMediaDbNoteContents(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions): Promise<string> {
let template = await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app);

if (this.settings.useDefaultFrontMatter || !template) {
return this.generateContentWithDefaultFrontMatter(mediaTypeModel, options, template);
} else {
return this.generateContentWithCustomFrontMatter(mediaTypeModel, options, template);
}
}

async generateContentWithDefaultFrontMatter(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions, template?: string): Promise<string> {
let fileMetadata = this.modelPropertyMapper.convertObject(mediaTypeModel.toMetaDataObject());
let fileContent = '';
const template = options.attachTemplate ? await this.mediaTypeManager.getTemplate(mediaTypeModel, this.app) : '';
template = options.attachTemplate ? template : '';

({ fileMetadata, fileContent } = await this.attachFile(fileMetadata, fileContent, options.attachFile));
({ fileMetadata, fileContent } = await this.attachTemplate(fileMetadata, fileContent, template));

fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---\n` + fileContent;
if (this.settings.enableTemplaterIntegration && hasTemplaterPlugin(this.app)) {
// Only support stringifyYaml for templater plugin
// Include the media variable in all templater commands by using a top level JavaScript execution command.
fileContent = `---\n<%* const media = ${JSON.stringify(mediaTypeModel)} %>\n${stringifyYaml(fileMetadata)}---\n${fileContent}`;
} else {
fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(fileMetadata) : stringifyYaml(fileMetadata)}---\n` + fileContent;
}

return fileContent;
}

async generateContentWithCustomFrontMatter(mediaTypeModel: MediaTypeModel, options: CreateNoteOptions, template: string): Promise<string> {
const frontMatterRegex = /^---*\n([\s\S]*?)\n---\h*/;

const match = template.match(frontMatterRegex);

if (!match || match.length !== 2) {
throw new Error('Cannot find YAML front matter for template.');
}

let frontMatter = parseYaml(match[1]);
let fileContent: string = template.replace(frontMatterRegex, '');

// Updating a previous file
if (options.attachFile) {
const previousMetadata = this.app.metadataCache.getFileCache(options.attachFile).frontmatter;

// Use contents (below front matter) from previous file
fileContent = await this.app.vault.read(options.attachFile);
const regExp = new RegExp(this.frontMatterRexExpPattern);
fileContent = fileContent.replace(regExp, '');
fileContent = fileContent.startsWith('\n') ? fileContent.substring(1) : fileContent;

// Update updated front matter with entries from the old front matter, if it isn't defined in the new front matter
Object.keys(previousMetadata).forEach(key => {
const value = previousMetadata[key];

if (!frontMatter[key] && value) {
frontMatter[key] = value;
}
});
}

// Ensure that id, type, and dataSource are defined
if (!frontMatter.id) {
frontMatter.id = mediaTypeModel.id;
}

if (!frontMatter.type) {
frontMatter.type = mediaTypeModel.type;
}

if (!frontMatter.dataSource) {
frontMatter.dataSource = mediaTypeModel.dataSource;
}

if (this.settings.enableTemplaterIntegration && hasTemplaterPlugin(this.app)) {
// Only support stringifyYaml for templater plugin
// Include the media variable in all templater commands by using a top level JavaScript execution command.
fileContent = `---\n<%* const media = ${JSON.stringify(mediaTypeModel)} %>\n${stringifyYaml(frontMatter)}---\n${fileContent}`;
} else {
fileContent = `---\n${this.settings.useCustomYamlStringifier ? YAMLConverter.toYaml(frontMatter) : stringifyYaml(frontMatter)}---\n` + fileContent;
}

return fileContent;
}

Expand Down Expand Up @@ -386,7 +472,7 @@ export default class MediaDbPlugin extends Plugin {
* @param fileContent
* @param options
*/
async createNote(fileName: string, fileContent: string, options: CreateNoteOptions): Promise<void> {
async createNote(fileName: string, fileContent: string, options: CreateNoteOptions): Promise<TFile> {
// find and possibly create the folder set in settings or passed in folder
const folder = options.folder ?? this.app.vault.getAbstractFileByPath('/');

Expand All @@ -412,6 +498,8 @@ export default class MediaDbPlugin extends Plugin {
}
await activeLeaf.openFile(targetFile, { state: { mode: 'source' } });
}

return targetFile;
}

/**
Expand Down
73 changes: 51 additions & 22 deletions src/settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface MediaDbPluginSettings {
templates: boolean;
customDateFormat: string;
openNoteInNewTab: boolean;
useDefaultFrontMatter: boolean;
enableTemplaterIntegration: boolean;

movieTemplate: string;
seriesTemplate: string;
Expand Down Expand Up @@ -63,6 +65,8 @@ const DEFAULT_SETTINGS: MediaDbPluginSettings = {
templates: true,
customDateFormat: 'L',
openNoteInNewTab: true,
useDefaultFrontMatter: true,
enableTemplaterIntegration: false,

movieTemplate: '',
seriesTemplate: '',
Expand Down Expand Up @@ -218,6 +222,30 @@ export class MediaDbSettingTab extends PluginSettingTab {
});
});

new Setting(containerEl)
.setName('Use default front matter')
.setDesc('Wheter to use the default front matter. If disabled, the front matter from the template will be used. Same as mapping everything to remove.')
.addToggle(cb => {
cb.setValue(this.plugin.settings.useDefaultFrontMatter).onChange(data => {
this.plugin.settings.useDefaultFrontMatter = data;
this.plugin.saveSettings();
// Redraw settings to display/remove the property mappings
this.display();
});
});

new Setting(containerEl)
.setName('Enable Templater integration')
.setDesc(
'Enable integration with the templater plugin, this also needs templater to be installed. Warning: Templater allows you to execute arbitrary JavaScript code and system commands.',
)
.addToggle(cb => {
cb.setValue(this.plugin.settings.enableTemplaterIntegration).onChange(data => {
this.plugin.settings.enableTemplaterIntegration = data;
this.plugin.saveSettings();
});
});

containerEl.createEl('h3', { text: 'New File Location' });
// region new file location
new Setting(containerEl)
Expand Down Expand Up @@ -531,11 +559,11 @@ export class MediaDbSettingTab extends PluginSettingTab {
// endregion

// region Property Mappings
if (this.plugin.settings.useDefaultFrontMatter) {
containerEl.createEl('h3', { text: 'Property Mappings' });

containerEl.createEl('h3', { text: 'Property Mappings' });

const propertyMappingExplanation = containerEl.createEl('div');
propertyMappingExplanation.innerHTML = `
const propertyMappingExplanation = containerEl.createEl('div');
propertyMappingExplanation.innerHTML = `
<p>Allow you to remap the metadata fields of newly created media db entries.</p>
<p>
The different options are:
Expand All @@ -549,27 +577,28 @@ export class MediaDbSettingTab extends PluginSettingTab {
Don't forget to save your changes using the save button for each individual category.
</p>`;

new PropertyMappingModelsComponent({
target: this.containerEl,
props: {
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
save: (model: PropertyMappingModel): void => {
const propertyMappingModels: PropertyMappingModel[] = [];

for (const model2 of this.plugin.settings.propertyMappingModels) {
if (model2.type === model.type) {
propertyMappingModels.push(model);
} else {
propertyMappingModels.push(model2);
new PropertyMappingModelsComponent({
target: this.containerEl,
props: {
models: this.plugin.settings.propertyMappingModels.map(x => x.copy()),
save: (model: PropertyMappingModel): void => {
const propertyMappingModels: PropertyMappingModel[] = [];

for (const model2 of this.plugin.settings.propertyMappingModels) {
if (model2.type === model.type) {
propertyMappingModels.push(model);
} else {
propertyMappingModels.push(model2);
}
}
}

this.plugin.settings.propertyMappingModels = propertyMappingModels;
new Notice(`MDB: Property Mappings for ${model.type} saved successfully.`);
this.plugin.saveSettings();
this.plugin.settings.propertyMappingModels = propertyMappingModels;
new Notice(`MDB: Property Mappings for ${model.type} saved successfully.`);
this.plugin.saveSettings();
},
},
},
});
});
}

// endregion
}
Expand Down
4 changes: 2 additions & 2 deletions src/settings/suggesters/FileSuggest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export class FileSuggest extends TextInputSuggest<TFile> {
}

renderSuggestion(file: TFile, el: HTMLElement): void {
el.setText(file.name);
el.setText(file.path);
}

selectSuggestion(file: TFile): void {
this.inputEl.value = file.name;
this.inputEl.value = file.path;
this.inputEl.trigger('input');
this.close();
}
Expand Down
24 changes: 15 additions & 9 deletions src/utils/MediaTypeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,28 @@ export class MediaTypeManager {
}

async getTemplate(mediaTypeModel: MediaTypeModel, app: App): Promise<string> {
const templateFileName = this.mediaTemplateMap.get(mediaTypeModel.getMediaType());
const templateFilePath = this.mediaTemplateMap.get(mediaTypeModel.getMediaType());

if (!templateFileName) {
if (!templateFilePath) {
return '';
}

const templateFile: TFile = app.vault
.getFiles()
.filter((f: TFile) => f.name === templateFileName)
.first();
let templateFile = app.vault.getAbstractFileByPath(templateFilePath);

if (!templateFile) {
return '';
// WARNING: This was previously selected by filename, but that could lead to collisions and unwanted effects.
// This now falls back to the previous method if no file is found
if (!templateFile || templateFile instanceof TFolder) {
templateFile = app.vault
.getFiles()
.filter((f: TFile) => f.name === templateFilePath)
.first();

if (!templateFile) {
return '';
}
}

const template = await app.vault.cachedRead(templateFile);
const template = await app.vault.cachedRead(templateFile as TFile);
// console.log(template);
return replaceTags(template, mediaTypeModel);
}
Expand Down
18 changes: 17 additions & 1 deletion src/utils/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MediaTypeModel } from '../models/MediaTypeModel';
import { TFile, TFolder } from 'obsidian';
import { TFile, TFolder, App } from 'obsidian';

export const pluginName: string = 'obsidian-media-db-plugin';
export const contactEmail: string = 'm.projects.code@gmail.com';
Expand Down Expand Up @@ -202,3 +202,19 @@ export function unCamelCase(str: string): string {
})
);
}

export function hasTemplaterPlugin(app: App) {
const templater = (app as any).plugins.plugins['templater-obsidian'];

return !!templater;
}

// Copied from https://github.com/anpigon/obsidian-book-search-plugin
// Licensed under the MIT license. Copyright (c) 2020 Jake Runzer
export async function useTemplaterPluginInFile(app: App, file: TFile) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const templater = (app as any).plugins.plugins['templater-obsidian'];
if (templater && !templater?.settings['trigger_on_file_creation']) {
await templater.templater.overwrite_file_commands(file);
}
}