From ec1f6272761b52b35eac18389fd88f8492b91c38 Mon Sep 17 00:00:00 2001 From: Kevin Hellemun <17928966+OGKevin@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:32:14 +0200 Subject: [PATCH] Add support for templating (#3) --- README.md | 32 ++++++++++++++++ src/database/Highlight.ts | 4 +- src/main.ts | 1 - src/modal/ExtractHighlightsModal.ts | 9 ++++- src/settings/Settings.ts | 47 +++++++++++++++++------- src/settings/suggestors/FileSuggestor.ts | 34 +++++++++++++++++ src/template/template.test.ts | 43 ++++++++++++++++++++++ src/template/template.ts | 10 +++++ src/template/templateContents.ts | 28 ++++++++++++++ 9 files changed, 190 insertions(+), 18 deletions(-) create mode 100644 src/settings/suggestors/FileSuggestor.ts create mode 100644 src/template/template.test.ts create mode 100644 src/template/template.ts create mode 100644 src/template/templateContents.ts diff --git a/README.md b/README.md index 660282e..3a24ebb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ This plugin aims to make highlight import from Kobo devices easier. +- [Obsidian Kobo Highlight Importer](#obsidian-kobo-highlight-importer) + - [How to use](#how-to-use) + - [Templating](#templating) + - [Examples](#examples) + - [Variables](#variables) + - [Helping Screenshots](#helping-screenshots) + - [Contributing](#contributing) + ## How to use Once installed, the steps to import your highlights directly into the vault are: @@ -12,6 +20,30 @@ Once installed, the steps to import your highlights directly into the vault are: 4. Locate _KoboReader.sqlite_ in the _.kobo_ folder ( this folder is hidden, so if you don't see it you should enable hidden files view from system configs ) 5. Extract +## Templating + +The default template is: + +```markdown +{{highlights}} +``` + +### Examples + +```markdown +--- +tags: +- books +--- + +{{highlights}} +``` +### Variables + +| Tag | Description | Example | +|------------|--------------------------------------------------|------------------| +| highlights | Will get replaced with the extracted highlights. | `{{highlights}}` | + ## Helping Screenshots ![](./README_assets/step1.png) ![](./README_assets/step2.png) diff --git a/src/database/Highlight.ts b/src/database/Highlight.ts index 710cea0..de26804 100644 --- a/src/database/Highlight.ts +++ b/src/database/Highlight.ts @@ -16,8 +16,8 @@ export class HighlightService { fromMapToMarkdown(bookTitle: string, chapters: Map): string { let markdown = `# ${bookTitle}\n\n`; for (const [chapter, highlights] of chapters) { - markdown += `## ${chapter}\n\n` - markdown += highlights.join('\n\n') + markdown += `## ${chapter.trim()}\n\n` + markdown += highlights.join('\n\n').trim() markdown += `\n\n` } diff --git a/src/main.ts b/src/main.ts index 8859fbb..216f51a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -41,4 +41,3 @@ export default class KoboHighlightsImporter extends Plugin { await this.saveData(this.settings); } } - diff --git a/src/modal/ExtractHighlightsModal.ts b/src/modal/ExtractHighlightsModal.ts index d09a5bb..4804aa8 100644 --- a/src/modal/ExtractHighlightsModal.ts +++ b/src/modal/ExtractHighlightsModal.ts @@ -6,6 +6,8 @@ import { binary } from "src/binaries/sql-wasm"; import { HighlightService } from "src/database/Highlight"; import { Repository } from "src/database/repository"; import { KoboHighlightsImporterSettings } from "src/settings/Settings"; +import { applyTemplateTransformations } from 'src/template/template'; +import { getTemplateContents } from 'src/template/templateContents'; export class ExtractHighlightsModal extends Modal { goButtonEl!: HTMLButtonElement; @@ -44,11 +46,16 @@ export class ExtractHighlightsModal extends Modal { this.settings.dateFormat, ) + const template = await getTemplateContents(this.app, this.settings.templatePath) + for (const [bookTitle, chapters] of content) { const markdown = service.fromMapToMarkdown(bookTitle, chapters) const saniizedBookName = sanitize(bookTitle) const fileName = normalizePath(`${this.settings.storageFolder}/${saniizedBookName}.md`) - this.app.vault.adapter.write(fileName, markdown) + this.app.vault.adapter.write( + fileName, + applyTemplateTransformations(template, markdown) + ) } } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b35117b..f37b71b 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -1,43 +1,62 @@ import { App, PluginSettingTab, Setting } from "obsidian"; import KoboHighlightsImporter from "src/main"; +import { FileSuggestor } from "./suggestors/FileSuggestor"; import { FolderSuggestor } from "./suggestors/FolderSuggestor"; export const DEFAULT_SETTINGS: KoboHighlightsImporterSettings = { - storageFolder: '', + storageFolder: '', includeCreatedDate: false, - dateFormat: "YYYY-MM-DD" + dateFormat: "YYYY-MM-DD", + templatePath: "", } export interface KoboHighlightsImporterSettings { - storageFolder: string; + storageFolder: string; includeCreatedDate: boolean; dateFormat: string; + templatePath: string; } export class KoboHighlightsImporterSettingsTab extends PluginSettingTab { - constructor(public app: App, private plugin: KoboHighlightsImporter) { - super(app, plugin); - } + constructor(public app: App, private plugin: KoboHighlightsImporter) { + super(app, plugin); + } - display(): void { - this.containerEl.empty(); + display(): void { + this.containerEl.empty(); this.containerEl.createEl('h2', { text: this.plugin.manifest.name }); this.add_destination_folder(); this.add_enable_creation_date(); this.add_date_fromat(); - } + this.add_temaplte_path(); + } add_destination_folder(): void { new Setting(this.containerEl) - .setName('Destination folder') - .setDesc('Where to save your imported highlights') - .addSearch((cb) => { + .setName('Destination folder') + .setDesc('Where to save your imported highlights') + .addSearch((cb) => { new FolderSuggestor(this.app, cb.inputEl); cb.setPlaceholder("Example: folder1/folder2") .setValue(this.plugin.settings.storageFolder) - .onChange((new_folder) => { - this.plugin.settings.storageFolder = new_folder; + .onChange((newFolder) => { + this.plugin.settings.storageFolder = newFolder; + this.plugin.saveSettings(); + }); + }); + } + + add_temaplte_path(): void { + new Setting(this.containerEl) + .setName('Tempalte Path') + .setDesc('Which tempalte to use for extracted highlights') + .addSearch((cb) => { + new FileSuggestor(this.app, cb.inputEl); + cb.setPlaceholder("Example: folder1/template") + .setValue(this.plugin.settings.templatePath) + .onChange((newTemplatePath) => { + this.plugin.settings.templatePath = newTemplatePath; this.plugin.saveSettings(); }); }); diff --git a/src/settings/suggestors/FileSuggestor.ts b/src/settings/suggestors/FileSuggestor.ts new file mode 100644 index 0000000..3695c99 --- /dev/null +++ b/src/settings/suggestors/FileSuggestor.ts @@ -0,0 +1,34 @@ +// source: https://github.com/liamcain/obsidian-periodic-notes/blob/04965a1e03932d804f6dd42c2e5dba0ede010d79/src/ui/file-suggest.ts + +import { TAbstractFile, TFile } from "obsidian"; +import { TextInputSuggest } from "./Suggest"; + +export class FileSuggestor extends TextInputSuggest { + getSuggestions(inputStr: string): TFile[] { + const abstractFiles = this.app.vault.getAllLoadedFiles(); + const files: TFile[] = []; + const lowerCaseInputStr = inputStr.toLowerCase(); + + abstractFiles.forEach((file: TAbstractFile) => { + if ( + file instanceof TFile && + file.extension === "md" && + file.path.toLowerCase().contains(lowerCaseInputStr) + ) { + files.push(file); + } + }); + + return files; + } + + renderSuggestion(file: TFile, el: HTMLElement): void { + el.setText(file.path); + } + + selectSuggestion(file: TFile): void { + this.inputEl.value = file.path; + this.inputEl.trigger("input"); + this.close(); + } +} diff --git a/src/template/template.test.ts b/src/template/template.test.ts new file mode 100644 index 0000000..3f6d174 --- /dev/null +++ b/src/template/template.test.ts @@ -0,0 +1,43 @@ +import * as chai from 'chai'; +import { applyTemplateTransformations, defaultTemplate } from './template'; + + +describe('template', async function () { + it('applyTemplateTransformations default', async function () { + const content = applyTemplateTransformations(defaultTemplate, "test") + + chai.expect(content).deep.eq("test") + }); + + const templates = new Map([ + [ + "default", + [defaultTemplate, "test"] + ], + [ + "with front matter", + [ + ` +--- +tag: [tags] +--- +{{highlights}} +`, + `--- +tag: [tags] +--- +test` + ] + ], + ]) + + for (const [title, t] of templates) { + it(`applyTemplateTransformations ${title}`, async function () { + const content = applyTemplateTransformations(t[0], "test") + + chai.expect(content).deep.eq(t[1]) + }); + } + + +}); diff --git a/src/template/template.ts b/src/template/template.ts new file mode 100644 index 0000000..8f00e62 --- /dev/null +++ b/src/template/template.ts @@ -0,0 +1,10 @@ +export const defaultTemplate = ` +{{highlights}} +` + +export function applyTemplateTransformations(rawTemaple: string, highlights: string): string { + return rawTemaple.replace( + /{{\s*highlights\s*}}/gi, + highlights, + ).trim() +} diff --git a/src/template/templateContents.ts b/src/template/templateContents.ts new file mode 100644 index 0000000..e0cfaaa --- /dev/null +++ b/src/template/templateContents.ts @@ -0,0 +1,28 @@ +// Inspired by https://github.com/liamcain/obsidian-periodic-notes/blob/04965a1e03932d804f6dd42c2e5dba0ede010d79/src/utils.ts + +import { App, normalizePath, Notice } from "obsidian"; +import { defaultTemplate } from "./template"; + +export async function getTemplateContents( + app: App, + templatePath: string | undefined +): Promise { + const { metadataCache, vault } = app; + const normalizedTemplatePath = normalizePath(templatePath ?? ""); + if (normalizedTemplatePath === "/") { + return defaultTemplate; + } + + try { + const templateFile = metadataCache.getFirstLinkpathDest(normalizedTemplatePath, ""); + return templateFile ? vault.cachedRead(templateFile) : defaultTemplate; + } catch (err) { + console.error( + `Failed to read the kobo highlight exporter template '${normalizedTemplatePath}'`, + err + ); + new Notice("Failed to read the kobo highlight exporter template"); + + return ""; + } +}