Skip to content

Commit

Permalink
fix: too many pages created due to author names with commas (#30)
Browse files Browse the repository at this point in the history
* fix: variable collision

* feat: import authors with links

* docs: update readme

* docs: add badges

* ci: add review workflow
  • Loading branch information
theBenForce committed Aug 20, 2022
1 parent 7029bcf commit 874f44d
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 39 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/main.yml → .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@ jobs:
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/setup-node@v2

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: "16"
- run: yarn install
node-version: 16
cache: "yarn"

- name: Install Dependencies
run: yarn install
env:
CI: TRUE

- name: Test
run: yarn test

- name: Install zip
uses: montudor/action-zip@v1

- name: Release
run: npx semantic-release
env:
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Review

on: [pull_request]

jobs:
node_test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
cache: "yarn"

- name: Install Dependencies
run: yarn install
env:
CI: TRUE

- name: Test
run: yarn test --json --outputFile=jest-results.json
continue-on-error: true

- name: Process jest results with default
if: always()
uses: im-open/process-jest-test-results@v2.0.6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
results-file: "jest-results.json"

- name: Linting
run: yarn lint --output-file eslint_report.json --format json src
continue-on-error: true

- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
report-json: "eslint_report.json"

- name: Upload ESLint report
uses: actions/upload-artifact@v2
with:
name: eslint_report.json
path: eslint_report.json
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ node_modules
dist
dist-ssr
*.local
.env
.env
coverage/
Binary file added docs/settings.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: ['src/**/*.tsx?'],
collectCoverage: true,
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"conventional-changelog-conventionalcommits": "5.0.0",
"eslint": "8.22.0",
"eslint-plugin-react": "7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.3",
"semantic-release": "19.0.3",
Expand Down
39 changes: 37 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Highlight Importer
# My Highlights

[![GitHub release (latest by date)](https://img.shields.io/github/v/release/theBenForce/logseq-plugin-my-highlights)](https://github.com/theBenForce/logseq-plugin-my-highlights/releases)
[![Releases](https://github.com/theBenForce/logseq-plugin-my-highlights/actions/workflows/main.yml/badge.svg)](https://github.com/theBenForce/logseq-plugin-my-highlights/actions/workflows/main.yml)

## Features

- easliy import highlights and notes directly from your kindle
- creates author links

## Importing Highlights

Expand All @@ -15,7 +18,39 @@ After selecting the clippings file you will be presented with a list of books th
import highlights from. Select the checkbox next to each book that you would like to import and click
the "Import" button.

### Adding More Details
### Customizing Import Settings

If you open Settings -> Plugin Settings and select "My Highlights", you'll see a few options to
customize how "My Highlights" imports data.

![Plugin Settings](docs/settings.jpg)

#### Highlight Path

The setting that changes how your highlights are imported the most is the "Highlight Path" setting. This
changes the title of the page where your imports will be stored. You can set this to anything that you
want, but each book will need a unique title.

When editing the highlight path you can use the following variables:

| Variable | Description |
| --- | --- |
| {title} | Title of the book that's being imported |
| {author} | Name of the first author listed on the book |
| {type} | Type of highlights being imoprted, right now this will only be 'Book' |
| {zettel} | A date time string to guarantee uniqueness. Only us this if you don't want to import more highlights later |

#### Fallback Author

If for some reason the clippings file doesn't have an author listed, this value will be used instead.

#### Author First Name First

By default, author names are listed as "Last, First". Check this box if you would rather have authors listed as "First Last".

### Adding More Details (beta)

> This feature is in beta testing and may not work yet
If you would like to add the book's cover image, ASIN, and a link to the kindle web reader to the
book page during import click the "Next" button instead of "Import". This will take you to a second
Expand Down
3 changes: 1 addition & 2 deletions src/actions/syncBookHighlights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ export const syncBookHighlights = async ({book, logseq, transaction}: SyncBookHi
const content = annotation.content ?? '';
const type = annotation.type;
const start = annotation.location?.start;
const page = annotation.page;

updates.push(addContentBlock(content, type, start, page));
updates.push(addContentBlock(content, type, start, annotation.page));

return updates;
}, [] as Array<IBatchBlock>)
Expand Down
2 changes: 1 addition & 1 deletion src/component/pages/BookSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const BookSelector: React.FC<BookSelectorParams> = ({books, selectedBooks
<div className="flex flex-col grow truncate" style={{flexGrow: 1}}>
<div className="truncate text-lg">{book.title}</div>
<div className="flex grow flex-row gap-1 justify-between">
{book.author && <div className="truncate text-sm flex-1 grow">{book.author}</div>}
{book.authors && <div className="truncate text-sm flex-1 grow">{book.authors.join('; ')}</div>}
<div className='text-sm'>Last Highlight {book.lastAnnotation.toLocaleDateString()}</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/component/pages/SelectBookDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const BookDetailsSelector: React.FC<BookDetailsSelectorProps> = ({ books,

const onSelectBook = (book: KindleBook) => () => {
logEvent(analytics!, 'search', {
search_term: [book.title, book.author].filter(Boolean).join(' ')
search_term: [book.title, ...(book.authors ?? [])].filter(Boolean).join(' ')
});

setSelectedBook(book);
Expand All @@ -57,7 +57,7 @@ export const BookDetailsSelector: React.FC<BookDetailsSelectorProps> = ({ books,
<div className="flex flex-col grow truncate">
<div className="truncate text-lg">{book.title}</div>
<div className="flex grow flex-row gap-1 justify-between">
{book.author && <div className="truncate text-sm flex-1 grow">{book.author}</div>}
{book.authors && <div className="truncate text-sm flex-1 grow">{book.authors.join('; ')}</div>}
<div className='text-sm'>Last Highlight {book.lastAnnotation.toLocaleDateString()}</div>
</div>
</div>
Expand Down
17 changes: 17 additions & 0 deletions src/hooks/useImportBooks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { nameToLink } from './useImportBooks';

describe('useImportBooks', () => {
describe('nameToLink', () => {
it('should create a link', () => {
const result = nameToLink()('Last, First');

expect(result).toEqual('[[Last, First]]');
});

it('should reverse name order when set', () => {
const result = nameToLink({ reverseNameOrder: true })('Last, First');

expect(result).toEqual('[[First Last]]');
})
});
});
41 changes: 23 additions & 18 deletions src/hooks/useImportBooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,45 @@ import { renderTemplate } from "../utils/renderTemplate";
import { createZettelId } from "../utils/zettelId";
import { useLogseq } from "./useLogseq";

export const createBookPageProperties = (title: string, book: KindleBook) => ({
export const nameToLink = ({ reverseNameOrder }: { reverseNameOrder?: boolean; } = {}) => (name: string): string => {
let result = name;

if (reverseNameOrder) {
result = result.split(',').reverse().join(' ').trim();
}

return `[[${result}]]`;
};

export const createBookPageProperties = (title: string, book: KindleBook, reverseNameOrder: boolean) => ({
title,
alias: `${book.title.replaceAll('/', '_').split(':')[0]} - Highlights`,
author: book.author,
author: book.authors?.map(nameToLink({ reverseNameOrder }))?.join(' '),
last_sync: new Date().toISOString(),
type: 'Book'
});

interface GetBookPageParams { logseq: ILSPluginUser; book: KindleBook; createPage?: boolean; }

const getPageByAsin = async (db: IDBProxy, asin: string): Promise<PageEntity | null> => {
console.info(`Getting page by asin`);
const result = await db.q<BlockEntity>(`(page-property asin "${asin}")`);
const getPageByQuery = async (db: IDBProxy, query: string): Promise<PageEntity | null> => {
const result = await db.q<BlockEntity>(query);

if (!result?.length) {
return null;
}

const block = result[0];
const page = await window.logseq.Editor.getPage(block.name, { includeChildren: true });
return page;
return window.logseq.Editor.getPage(block.name, { includeChildren: true });
}

const getPageByAsin = async (db: IDBProxy, asin: string): Promise<PageEntity | null> => {
console.info(`Getting page by asin`);
return getPageByQuery(db, `(page-property asin "${asin}")`);
}

const getPageByBookId = async (db: IDBProxy, bookId: string): Promise<PageEntity | null> => {
console.info(`Getting page by book id`);
const result = await db.q<BlockEntity>(`(page-property ${BookPageProperties.SourceBookIds} "${bookId}")`);

if (!result?.length) {
return null;
}

const block = result[0];
const page = await window.logseq.Editor.getPage(block.name, { includeChildren: true });
return page;
return getPageByQuery(db, `(page-property ${BookPageProperties.SourceBookIds} "${bookId}")`);
}

export async function getBookPage({ logseq, book, createPage = true }: GetBookPageParams) {
Expand All @@ -49,7 +54,7 @@ export async function getBookPage({ logseq, book, createPage = true }: GetBookPa
path = renderTemplate(path, {
type: 'book',
title: book.title,
author: book.author ?? logseq.settings?.default_author ?? 'UnknownAuthor',
author: book.authors?.[0] ?? logseq.settings?.default_author ?? 'UnknownAuthor',
zettel,
});

Expand All @@ -74,7 +79,7 @@ export async function getBookPage({ logseq, book, createPage = true }: GetBookPa
// TODO: Try to find page with asin, then book id
if (!createPage) throw new Error(`Page not found`);
console.info(`Creating new page`);
page = await logseq.Editor.createPage(path, createBookPageProperties(path, book), { createFirstBlock: true });
page = await logseq.Editor.createPage(path, createBookPageProperties(path, book, logseq.settings?.author_first_name_first), { createFirstBlock: true });
}

const pageBlocksTree = await logseq.Editor.getPageBlocksTree(page!.name);
Expand Down
9 changes: 8 additions & 1 deletion src/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Variables include:
{title} - title provided by article
{author} - author provided by article
{zettel} - a datetime string (20220101000000)`,
default: "highlights/{type}/{title}",
default: "{title}/highlights",
type: "string"
},
{
Expand All @@ -19,5 +19,12 @@ Variables include:
description: 'Author value to use when actual author is unknown',
default: 'Unknown Author',
type: "string"
},
{
title: 'Author First Name First',
key: 'author_first_name_first',
description: 'When adding author links, put their first name first',
default: false,
type: 'boolean'
}
];
5 changes: 3 additions & 2 deletions src/utils/parseKindleHighlights.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ describe('parseKindleHighlights', () => {

it('should parse author', () => {
const result = parseTitleLine('Effective Notetaking (Study Skills Book 1) (McPherson, Fiona)');
expect(result).toHaveProperty('author', 'McPherson, Fiona');

expect(result).toHaveProperty('authors', expect.arrayContaining(['McPherson, Fiona']));
});
});

Expand All @@ -82,7 +83,7 @@ describe('parseKindleHighlights', () => {

it('should parse author name', () => {
const result = parseClipping(CLIPPING_1);
expect(result).toHaveProperty('author', 'Ahrens, Sönke');
expect(result).toHaveProperty('authors', expect.arrayContaining(['Ahrens, Sönke']));
});

it('should parse timestamp', () => {
Expand Down
14 changes: 7 additions & 7 deletions src/utils/parseKindleHighlights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,20 @@ export interface KindleBook extends BookMetadata {

interface BookMetadata extends Partial<AmazonSearchResult> {
title: string;
author?: string;
authors?: Array<string>;
}

export const parseTitleLine = (titleLine: string) => {
export const parseTitleLine = (titleLine: string): BookMetadata => {
const title = titleLine.replace(/\([^)]+\)$/g, '').trim();
const authorMatches = /\((?<author>[^)]+)\)$/g.exec(titleLine);

let author = authorMatches?.groups?.["author"]?.trim();
let authors = authorMatches?.groups?.["author"]?.trim()?.split(';');

if (author === 'Unknown') {
author = undefined;
if (!authors?.length || authors[0] === 'Unknown') {
authors = undefined;
}

return { title, author };
return { title, authors };
}

export const parseMetaLine = (metaLine: string): KindleAnnotation => {
Expand Down Expand Up @@ -122,7 +122,7 @@ export const parseKindleHighlights = (content: string): Array<KindleBook> => {
if (!book) {
book = {
title: clipping.title,
author: clipping.author,
authors: clipping.authors,
annotations: [],
lastAnnotation: clipping.timestamp,
bookId: ""
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3688,6 +3688,11 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"

eslint-plugin-react-hooks@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==

eslint-plugin-react@7.30.1:
version "7.30.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz#2be4ab23ce09b5949c6631413ba64b2810fd3e22"
Expand Down

0 comments on commit 874f44d

Please sign in to comment.