Skip to content

Commit

Permalink
feat: include book details in export (#291)
Browse files Browse the repository at this point in the history
* feat: include book details in export

Closes: #109

* fix: broken test
  • Loading branch information
OGKevin authored Jan 27, 2024
1 parent 2b9a395 commit abe5f8e
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 39 deletions.
44 changes: 28 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ 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)
- [Highlight markers](#highlight-markers)
- [Helping Screenshots](#helping-screenshots)
Expand All @@ -29,31 +28,44 @@ Once installed, the steps to import your highlights directly into the vault are:
The default template is:

```markdown
# {{title}}
---
title: {{Title}}
author: {{Author}}
publisher: {{Publisher}}
dateLastRead: {{DateLastRead}}
readStatus: {{ReadStatus}}
percentRead: {{PercentRead}}
isbn: {{ISBN}}
series: {{Series}}
seriesNumber: {{SeriesNumber}}
timeSpentReading: {{TimeSpentReading}}
---

{{highlights}}
```
# {{Title}}

### Examples
## Description

```markdown
---
tags:
- books
bookTitle: {{title}}
---
{{Description}}

# {{title}}
## Highlights

{{highlights}}
```

### Variables

| Tag | Description | Example |
|------------|--------------------------------------------------|------------------|
| highlights | Will get replaced with the extracted highlights. | `{{highlights}}` |
| title | The title of the book | `{{title}}` |
| Tag | Description | Example |
| ---------------- | ------------------------------------------------ | ---------------------- |
| highlights | Will get replaced with the extracted highlights. | `{{highlights}}` |
| title | The title of the book. | `{{title}}` |
| author | The author of the book. | `{{author}}` |
| pulbisher | The publisher of the book | `{{publihser}}` |
| dateLastRead | The date the book was last read in ISO format. | `{{dateLastRead}}` |
| readStatus | Can be: Unopened, Reading, Read. | `{{readStatus}}` |
| isbn | The ISBN of the book. | `{{isbn}}` |
| series | The series of which the book is a part of. | `{{series}}` |
| seriesNumber | The position of the book in the series. | `{{seriesNumber}}` |
| timeSpentReading | The time spent reading the book. | `{{timeSpentReading}}` |

## Highlight markers
The plugin uses comments as highlight markers, to enable support for keeping existing highlights. All content between these markers will be transferred to the updated file.
Expand Down
16 changes: 15 additions & 1 deletion src/database/Highlight.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import moment from 'moment';
import { Bookmark, Content, Highlight } from "./interfaces";
import { BookDetails, Bookmark, Content, Highlight } from "./interfaces";
import { Repository } from "./repository";

type bookTitle = string
Expand All @@ -18,11 +18,25 @@ export class HighlightService {
repo: Repository

unkonwnBookTitle = 'Unknown Title'
unknownAuthor = 'Unknown Author'

constructor(repo: Repository) {
this.repo = repo
}

async getBookDetailsFromBookTitle(title: string): Promise<BookDetails> {
const details = await this.repo.getBookDetailsByBookTitle(title)

if (details == null) {
return {
title: this.unkonwnBookTitle,
author: this.unknownAuthor
}
}

return details;
}

extractExistingHighlight(bookmark: bookmark, existingContent: string): string {
// Define search terms
const startSearch = `%%START-${bookmark.bookmarkId}%%`
Expand Down
22 changes: 22 additions & 0 deletions src/database/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars */
export interface Bookmark {
bookmarkId: string,
text: string;
Expand All @@ -17,3 +18,24 @@ export interface Highlight {
bookmark: Bookmark;
content: Content;
}

export interface BookDetails {
title: string;
author: string;
description?: string;
publisher?: string;
dateLastRead?: Date;
readStatus?: ReadStatus;
percentRead?: number;
isbn?: string;
series?: string;
seriesNumber?: number;
timeSpentReading?: number;
}

export enum ReadStatus {
Unknown = -1,
Unopened = 0,
Reading = 1,
Read = 2
}
38 changes: 38 additions & 0 deletions src/database/repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,42 @@ describe('Repository', async function () {
});
chai.expect(await repo.getAllContentByBookTitle(titles.at(Math.floor(Math.random() * titles.length)) ?? "")).length.above(0)
});
it('getBookDetailsOnePunchMan', async function () {
const details = await repo.getBookDetailsByBookTitle("One-Punch Man, Vol. 2")

chai.expect(details).not.null
chai.expect(details?.title).is.eq("One-Punch Man, Vol. 2")
chai.expect(details?.author).is.eq("ONE")
chai.expect(details?.description).not.null
chai.expect(details?.publisher).is.eq("VIZ Media LLC")
chai.expect(details?.dateLastRead).not.null
chai.expect(details?.readStatus).is.eq(2)
chai.expect(details?.percentRead).is.eq(100)
chai.expect(details?.isbn).is.eq("9781421585659")
chai.expect(details?.seriesNumber).is.eq(2)
chai.expect(details?.series).is.eq("One-Punch man")
chai.expect(details?.timeSpentReading).is.eq(780)
});
it('getAllBookDetailsByBookTitle', async function () {
const bookmarks = await repo.getAllBookmark()
let titles: string[] = []

bookmarks.forEach(async b => {
let content = await this.repo.getContentByContentId(b.contentId)

if (content == null) {
content = await this.repo.getContentLikeContentId(b.contentId)
}

titles.push(content.title)
})

titles = titles.filter((v, i, a) => a.indexOf(v) === i)

titles.forEach(async t => {
const details = await repo.getBookDetailsByBookTitle(t)

chai.expect(details).not.null;
})
});
});
38 changes: 37 additions & 1 deletion src/database/repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Database, Statement } from "sql.js";
import { Bookmark, Content } from "./interfaces";
import { BookDetails, Bookmark, Content } from "./interfaces";

export class Repository {
db: Database
Expand Down Expand Up @@ -165,6 +165,42 @@ export class Repository {
return contents
}

async getBookDetailsByBookTitle(bookTitle: string): Promise<BookDetails | null> {
const statement = this.db.prepare(
`select Attribution, Description, Publisher, DateLastRead, ReadStatus, ___PercentRead, ISBN, Series, SeriesNumber, TimeSpentReading from content where Title = $title limit 1;`,
{
$title: bookTitle
}
)

if (!statement.step()) {
return null
}

const row = statement.get()

if (row.length == 0 || row[0] == null) {
console.debug("Used query: select Attribution, Description, Publisher, DateLastRead, ReadStatus, ___PercentRead, ISBN, Series, SeriesNumber, TimeSpentReading from content where Title = $title limit 2;", { $title: bookTitle, result: row })
console.warn("Could not find book details in database")

return null
}

return {
title: bookTitle,
author: row[0].toString(),
description: row[1]?.toString(),
publisher: row[2]?.toString(),
dateLastRead: row[3] ? new Date(row[3].toString()) : undefined,
readStatus: row[4] ? +row[4].toString() : 0,
percentRead: row[5] ? +row[5].toString() : 0,
isbn: row[6]?.toString(),
series: row[7]?.toString(),
seriesNumber: row[8] ? +row[8].toString() : undefined,
timeSpentReading: row[9] ? +row[9].toString() : 0,
}
}

private parseContentStatement(statement: Statement): Content[] {
const contents: Content[] = []

Expand Down
4 changes: 3 additions & 1 deletion src/modal/ExtractHighlightsModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ export class ExtractHighlightsModal extends Modal {
} catch (error) {
console.warn("Attempted to read file, but it does not already exist.")
}

const markdown = service.fromMapToMarkdown(chapters, existingFile)
const details = await service.getBookDetailsFromBookTitle(bookTitle)

// Write file

await this.app.vault.adapter.write(
fileName,
applyTemplateTransformations(template, markdown, bookTitle)
applyTemplateTransformations(template, markdown, details)
)
}
}
Expand Down
71 changes: 56 additions & 15 deletions src/template/template.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,70 @@
import * as chai from 'chai';
import {applyTemplateTransformations, defaultTemplate} from './template';

import * as chai from 'chai'
import { applyTemplateTransformations, defaultTemplate } from './template'

describe('template', async function () {
it('applyTemplateTransformations default', async function () {
const content = applyTemplateTransformations(defaultTemplate, "test", "test")
const content = applyTemplateTransformations(defaultTemplate, 'test', {
title: 'test title',
author: 'test'
})

chai.expect(content).deep.eq(
`# test
`---
title: test title
author: test
publisher:
dateLastRead:
readStatus: Unknown
percentRead:
isbn:
series:
seriesNumber:
timeSpentReading:
---
# test title
## Description
## Highlights
test`
)
});
})

const templates = new Map<string, string[]>([
[
"default",
'default',
[
defaultTemplate,
`# test title
`---
title: test title
author: test
publisher:
dateLastRead:
readStatus: Unknown
percentRead:
isbn:
series:
seriesNumber:
timeSpentReading:
---
# test title
## Description
## Highlights
test`
]
],
[
"with front matter",
'with front matter',
[
`
---
Expand All @@ -42,16 +82,17 @@ title: test title
test`
]
],
]
])

for (const [title, t] of templates) {
it(`applyTemplateTransformations ${title}`, async function () {
const content = applyTemplateTransformations(t[0], "test", "test title")
const content = applyTemplateTransformations(t[0], 'test', {
title: 'test title',
author: 'test'
})

chai.expect(content).deep.eq(t[1])
});
})
}


});
})
Loading

0 comments on commit abe5f8e

Please sign in to comment.