From a3482b91c11a21452869d6c8b8969ac706bda229 Mon Sep 17 00:00:00 2001
From: "Federico \"Edo\" Granata" <3602209+Edo78@users.noreply.github.com>
Date: Mon, 31 Jan 2022 15:59:31 +0100
Subject: [PATCH 1/5] feat(books): add to the book note the actual percentage
read
Close #41
---
src/koreader-metadata.ts | 2 ++
src/main.ts | 16 +++++++++-------
src/types.d.ts | 1 +
3 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/koreader-metadata.ts b/src/koreader-metadata.ts
index 6352e70..f233267 100644
--- a/src/koreader-metadata.ts
+++ b/src/koreader-metadata.ts
@@ -28,6 +28,7 @@ export class KOReaderMetadata {
bookmarks,
doc_props: { title },
doc_props: { authors },
+ percent_finished,
} = jsonMetadata;
if (Object.keys(highlight).length && Object.keys(bookmarks).length) {
metadatas[`${title} - ${authors}`] = {
@@ -35,6 +36,7 @@ export class KOReaderMetadata {
authors,
// highlight,
bookmarks,
+ percent_finished,
};
}
}
diff --git a/src/main.ts b/src/main.ts
index af038f8..e884651 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -161,7 +161,7 @@ export default class KOReader extends Plugin {
checkCallback: (checking: boolean) => {
if (this.settings.enbleResetImportedNotes) {
if (!checking) {
- this.resetSyncList();
+ this.settings.importedNotes = {};
this.settings.enbleResetImportedNotes = false;
this.saveSettings();
}
@@ -184,11 +184,6 @@ export default class KOReader extends Plugin {
await this.saveData(this.settings);
}
- private async resetSyncList() {
- this.settings.importedNotes = {};
- await this.saveSettings();
- }
-
private getObjectProperty(object: { [x: string]: any }, path: string) {
if (path === undefined || path === null) {
return object;
@@ -314,18 +309,24 @@ Page: <%= it.page %>
path: string;
managedBookTitle: string;
title: string;
+ percent_finished: number;
}) {
- const { path, title, managedBookTitle } = dataview;
+ const { path, title, managedBookTitle, percent_finished } = dataview;
const frontMatter = {
cssclass: 'koreader-sync-dataview',
[KOREADERKEY]: {
type: 'koreader-sync-dataview',
managed_title: managedBookTitle,
+ data: {
+ title,
+ percent_finished,
+ },
},
};
const defaultTemplate = `# Title: <%= it.title %>
+
\`\`\`dataviewjs
const title = dv.current()['koreader-sync'].managed_title
dv.pages().where(n => {
@@ -390,6 +391,7 @@ return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' &&
path,
managedBookTitle,
title: data[book].title,
+ percent_finished: data[book].percent_finished * 100,
});
}
diff --git a/src/types.d.ts b/src/types.d.ts
index 1e88c4d..842e6d4 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -18,6 +18,7 @@ export interface Book {
authors: string;
bookmarks: Bookmarks;
highlight: any;
+ percent_finished: number;
}
export interface Books {
From d766ff12d83d7f1d04f95e509f5d34e2ad9dd4a7 Mon Sep 17 00:00:00 2001
From: "Federico \"Edo\" Granata" <3602209+Edo78@users.noreply.github.com>
Date: Mon, 31 Jan 2022 16:57:50 +0100
Subject: [PATCH 2/5] feat(settings): add settings for book template
---
src/main.ts | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/src/main.ts b/src/main.ts
index e884651..bf0d549 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -25,7 +25,9 @@ interface KOReaderSettings {
keepInSync: boolean;
aFolderForEachBook: boolean;
customTemplate: boolean;
+ customDataviewTemplate: boolean;
templatePath?: string;
+ dataviewTemplatePath?: string;
createDataviewQuery: boolean;
importedNotes: { [key: string]: boolean };
enbleResetImportedNotes: boolean;
@@ -37,6 +39,7 @@ const DEFAULT_SETTINGS: KOReaderSettings = {
keepInSync: false,
aFolderForEachBook: false,
customTemplate: false,
+ customDataviewTemplate: false,
createDataviewQuery: false,
koreaderBasePath: '/media/user/KOBOeReader',
obsidianNoteFolder: '/',
@@ -566,6 +569,31 @@ class KoreaderSettingTab extends PluginSettingTab {
})
);
+ new Setting(containerEl)
+ .setName('Custom book template')
+ .setDesc('Use a custom template for the dataview')
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.settings.customDataviewTemplate)
+ .onChange(async (value) => {
+ this.plugin.settings.customDataviewTemplate = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
+ new Setting(containerEl)
+ .setName('Book template file')
+ .setDesc('The template file to use. Remember to add the ".md" extension')
+ .addText((text) =>
+ text
+ .setPlaceholder('templates/template-book.md')
+ .setValue(this.plugin.settings.dataviewTemplatePath)
+ .onChange(async (value) => {
+ this.plugin.settings.dataviewTemplatePath = value;
+ await this.plugin.saveSettings();
+ })
+ );
+
new Setting(containerEl)
.setName('Create a dataview query')
.setDesc(
From b633aa25b441d710f2ac446aa7f65464a0a2b4c5 Mon Sep 17 00:00:00 2001
From: "Federico \"Edo\" Granata" <3602209+Edo78@users.noreply.github.com>
Date: Mon, 31 Jan 2022 17:08:13 +0100
Subject: [PATCH 3/5] feat(templates): add support for custom template for book
notes
Close #33
---
src/main.ts | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/main.ts b/src/main.ts
index bf0d549..d4e02f1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -337,7 +337,13 @@ return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' &&
}).sort(p => p['koreader-sync'].data.page).forEach(p => dv.paragraph(dv.fileLink(p.file.name, true), {style: 'test-css'}))
\`\`\`
`;
- const template = defaultTemplate;
+
+ const templateFile = this.settings.customDataviewTemplate
+ ? this.app.vault.getAbstractFileByPath(this.settings.dataviewTemplatePath)
+ : null;
+ const template = templateFile
+ ? await this.app.vault.read(templateFile as TFile)
+ : defaultTemplate;
const content = (await eta.render(template, {
title,
})) as string;
From fd868ca47b5734dba817d5e34024dea59da418b0 Mon Sep 17 00:00:00 2001
From: "Federico \"Edo\" Granata" <3602209+Edo78@users.noreply.github.com>
Date: Mon, 31 Jan 2022 18:39:01 +0100
Subject: [PATCH 4/5] fix: show commands only when they are needed
It makes no sense to have a command available to change a frontmatter
value that doesn't exists or that it already has such value
Close #43
---
README.md | 2 ++
src/main.ts | 91 ++++++++++++++++++++++++++++++++++-------------------
2 files changed, 61 insertions(+), 32 deletions(-)
diff --git a/README.md b/README.md
index 55cade4..aed102f 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,8 @@ The query itself will embed the single notes and a CSS will hide every `h2` and
Once the plugin is configured properly you can plug the device with KOReader and click on the icon with two documents and the tooltip `Sync your KOReader highlights`. The plugin should propmplty create a single file for each note.
### Commands
+**NOTE:** if a command is suppose to set a frontmatter property equal to a certain value then it will be shown only if the open note has such property with a different value.
+
There are five commands:
- `Sync` it's the same as clicking on the plugin's icon, it's trigger the sync of the notes
- `Reset Sync List` empty the list of imported notes (see [Danger Zone](#danger-zone)). Always try to retrieve the deleted notes from trash before using this command because all the rightfully discarded notes will be imported again. This command will also disable itself so you have to enable in the settings again if you wish to use it again.
diff --git a/src/main.ts b/src/main.ts
index d4e02f1..1fef0f4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -113,48 +113,76 @@ export default class KOReader extends Plugin {
this.addCommand({
id: 'obsidian-koreader-plugin-set-edit',
name: 'Mark this note as Edited',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- this.setFrontmatterProperty(
- `${[KOREADERKEY]}.metadata.yet_to_be_edited`,
- false,
- view
- );
+ editorCheckCallback: (
+ checking: boolean,
+ editor: Editor,
+ view: MarkdownView
+ ) => {
+ const propertyPath = `${[KOREADERKEY]}.metadata.yet_to_be_edited`;
+ if (checking) {
+ if (this.getFrontmatterProperty(propertyPath, view) === true) {
+ return true;
+ }
+ return false;
+ }
+ this.setFrontmatterProperty(propertyPath, false, view);
},
});
this.addCommand({
id: 'obsidian-koreader-plugin-clear-edit',
name: 'Mark this note as NOT Edited',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- this.setFrontmatterProperty(
- `${[KOREADERKEY]}.metadata.yet_to_be_edited`,
- true,
- view
- );
+ editorCheckCallback: (
+ checking: boolean,
+ editor: Editor,
+ view: MarkdownView
+ ) => {
+ const propertyPath = `${[KOREADERKEY]}.metadata.yet_to_be_edited`;
+ if (checking) {
+ if (this.getFrontmatterProperty(propertyPath, view) === false) {
+ return true;
+ }
+ return false;
+ }
+ this.setFrontmatterProperty(propertyPath, true, view);
},
});
this.addCommand({
id: 'obsidian-koreader-plugin-set-sync',
name: 'Enable Sync for this note',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- this.setFrontmatterProperty(
- `${[KOREADERKEY]}.metadata.keep_in_sync`,
- true,
- view
- );
+ editorCheckCallback: (
+ checking: boolean,
+ editor: Editor,
+ view: MarkdownView
+ ) => {
+ const propertyPath = `${[KOREADERKEY]}.metadata.keep_in_sync`;
+ if (checking) {
+ if (this.getFrontmatterProperty(propertyPath, view) === false) {
+ return true;
+ }
+ return false;
+ }
+ this.setFrontmatterProperty(propertyPath, true, view);
},
});
this.addCommand({
id: 'obsidian-koreader-plugin-clear-sync',
name: 'Disable Sync for this note',
- editorCallback: (editor: Editor, view: MarkdownView) => {
- this.setFrontmatterProperty(
- `${[KOREADERKEY]}.metadata.keep_in_sync`,
- false,
- view
- );
+ editorCheckCallback: (
+ checking: boolean,
+ editor: Editor,
+ view: MarkdownView
+ ) => {
+ const propertyPath = `${[KOREADERKEY]}.metadata.keep_in_sync`;
+ if (checking) {
+ if (this.getFrontmatterProperty(propertyPath, view) === true) {
+ return true;
+ }
+ return false;
+ }
+ this.setFrontmatterProperty(propertyPath, false, view);
},
});
@@ -217,19 +245,19 @@ export default class KOReader extends Plugin {
object[key] = value;
}
- async setFrontmatterProperty(
- property: string,
- value: any,
- view: MarkdownView
- ) {
- const { data, content } = matter(view.data);
+ setFrontmatterProperty(property: string, value: any, view: MarkdownView) {
+ const { data, content } = matter(view.data, {});
this.setObjectProperty(data, property, value);
- const val = this.getObjectProperty(data, property);
const note = matter.stringify(content, data);
view.setViewData(note, false);
view.requestSave();
}
+ getFrontmatterProperty(property: string, view: MarkdownView): any {
+ const { data, content } = matter(view.data, {});
+ return this.getObjectProperty(data, property);
+ }
+
private async createNote(note: {
path: string;
uniqueId: string;
@@ -482,7 +510,6 @@ class KoreaderSettingTab extends PluginSettingTab {
.setPlaceholder('Enter the path wher KOReader is mounted')
.setValue(this.plugin.settings.koreaderBasePath)
.onChange(async (value) => {
- console.log(`Path: ${value}`);
this.plugin.settings.koreaderBasePath = value;
await this.plugin.saveSettings();
})
From a2add349c6e4bae6935572fc33f2ffdde715395e Mon Sep 17 00:00:00 2001
From: "Federico \"Edo\" Granata" <3602209+Edo78@users.noreply.github.com>
Date: Mon, 31 Jan 2022 20:00:38 +0100
Subject: [PATCH 5/5] feat(frontmatter): add frontmatter properties to book
notes
Close #42
---
README.md | 17 ++++++++
src/koreader-metadata.ts | 4 +-
src/main.ts | 89 ++++++++++++++++++++++++++--------------
3 files changed, 78 insertions(+), 32 deletions(-)
diff --git a/README.md b/README.md
index aed102f..ae55fcb 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,23 @@ The template receive the following arguments:
- `datetime`: 2022-01-22 09:57:29
- `page`: 19
+### Book view configuration
+The default template is minimal but complex
+~~~markdown
+# Title: <%= it.data.title %>
+
+
+```dataviewjs
+const title = dv.current()['koreader-sync'].metadata.managed_title
+dv.pages().where(n => {
+return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' && n['koreader-sync'].metadata.managed_book_title == title
+}).sort(p => p['koreader-sync'].data.page).forEach(p => dv.paragraph(dv.fileLink(p.file.name, true), {style: 'test-css'}))
+```
+~~~
+The core of this template is a js [dataview embedded](#dataview-embedded) query. Don't mess with it if you don't know what you are doing (I don't because I barely know Dataview).
+
+The template receive exactly the same data you can see in the frontmatter. If it's not there you can't use it but you can create an issue asking for it.
+
#### Dataview embedded
Besides a native support for [Dataview](https://github.com/blacksmithgu/obsidian-dataview) (look at the [example](#dataview-examples)) the plugin let the user chose to automatically create a note for each book with a dataview query inside.
The note is created in the same folder of the notes of the book but can be moved and renamed and Obsidian will take care of updating the links.
diff --git a/src/koreader-metadata.ts b/src/koreader-metadata.ts
index f233267..e38ad56 100644
--- a/src/koreader-metadata.ts
+++ b/src/koreader-metadata.ts
@@ -1,9 +1,9 @@
import * as fs from 'fs';
import * as path from 'path';
-import { Books } from './types';
import finder from 'node-find-files';
import { parse } from 'lua-json';
+import { Books } from './types';
export class KOReaderMetadata {
koreaderBasePath: string;
@@ -36,7 +36,7 @@ export class KOReaderMetadata {
authors,
// highlight,
bookmarks,
- percent_finished,
+ percent_finished: percent_finished * 100,
};
}
}
diff --git a/src/main.ts b/src/main.ts
index 1fef0f4..fe2be76 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -336,30 +336,53 @@ Page: <%= it.page %>
return { content, frontmatterData, notePath };
}
- async createDataviewQueryPerBook(dataview: {
- path: string;
- managedBookTitle: string;
- title: string;
- percent_finished: number;
- }) {
- const { path, title, managedBookTitle, percent_finished } = dataview;
+ async createDataviewQueryPerBook(
+ dataview: {
+ path: string;
+ managedBookTitle: string;
+ book: Book;
+ },
+ updateNote?: TFile
+ ) {
+ const { path, book, managedBookTitle } = dataview;
+ let { keepInSync } = this.settings;
+ if (updateNote) {
+ const { data, content } = matter(
+ await this.app.vault.read(updateNote),
+ {}
+ );
+ keepInSync = data[KOREADERKEY].metadata.keep_in_sync;
+ const yetToBeEdited = data[KOREADERKEY].metadata.yet_to_be_edited;
+ if (!keepInSync || !yetToBeEdited) {
+ return;
+ }
+ }
const frontMatter = {
cssclass: 'koreader-sync-dataview',
[KOREADERKEY]: {
+ uniqueId: crypto
+ .createHash('md5')
+ .update(`${book.title} - ${book.authors}}`)
+ .digest('hex'),
type: 'koreader-sync-dataview',
- managed_title: managedBookTitle,
data: {
- title,
- percent_finished,
+ title: book.title,
+ authors: book.authors,
+ },
+ metadata: {
+ percent_finished: book.percent_finished,
+ managed_title: managedBookTitle,
+ keep_in_sync: keepInSync,
+ yet_to_be_edited: true,
},
},
};
- const defaultTemplate = `# Title: <%= it.title %>
+ const defaultTemplate = `# Title: <%= it.data.title %>
-
+
\`\`\`dataviewjs
-const title = dv.current()['koreader-sync'].managed_title
+const title = dv.current()['koreader-sync'].metadata.managed_title
dv.pages().where(n => {
return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' && n['koreader-sync'].metadata.managed_book_title == title
}).sort(p => p['koreader-sync'].data.page).forEach(p => dv.paragraph(dv.fileLink(p.file.name, true), {style: 'test-css'}))
@@ -372,13 +395,18 @@ return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' &&
const template = templateFile
? await this.app.vault.read(templateFile as TFile)
: defaultTemplate;
- const content = (await eta.render(template, {
- title,
- })) as string;
- this.app.vault.create(
- `${path}/${managedBookTitle}.md`,
- matter.stringify(content, frontMatter)
- );
+ const content = (await eta.render(
+ template,
+ frontMatter[KOREADERKEY]
+ )) as string;
+ if (updateNote) {
+ this.app.vault.modify(updateNote, matter.stringify(content, frontMatter));
+ } else {
+ this.app.vault.create(
+ `${path}/${managedBookTitle}.md`,
+ matter.stringify(content, frontMatter)
+ );
+ }
}
async importNotes() {
@@ -420,16 +448,17 @@ return n['koreader-sync'] && n['koreader-sync'].type == 'koreader-sync-note' &&
}
}
// if createDataviewQuery is set, create a dataview query, for each book, with the book's managed title (if it doesn't exist)
- if (
- this.settings.createDataviewQuery &&
- !this.app.vault.getAbstractFileByPath(`${path}/${managedBookTitle}.md`)
- ) {
- this.createDataviewQueryPerBook({
- path,
- managedBookTitle,
- title: data[book].title,
- percent_finished: data[book].percent_finished * 100,
- });
+ if (this.settings.createDataviewQuery) {
+ this.createDataviewQueryPerBook(
+ {
+ path,
+ managedBookTitle,
+ book: data[book],
+ },
+ this.app.vault.getAbstractFileByPath(
+ `${path}/${managedBookTitle}.md`
+ ) as TFile
+ );
}
for (const bookmark in data[book].bookmarks) {