Skip to content

Commit

Permalink
Merge pull request #203 from DominikPieper/dev
Browse files Browse the repository at this point in the history
Release 0.10.0
  • Loading branch information
adamluckdev authored Dec 20, 2024
2 parents de127c0 + b4444da commit 35264d5
Show file tree
Hide file tree
Showing 35 changed files with 962 additions and 224 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ module.exports = {
ignoreDeclarationSort: true,
},
],
'no-useless-escape': 0
},
};
76 changes: 60 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [Introduction](#introduction)
- [Content Types](#content-types)
- [Youtube](#youtube)
- [Youtube Channel](#youtube-channel)
- [Vimeo](#vimeo)
- [Bilibili](#bilibili)
- [Twitter](#twitter)
Expand All @@ -13,15 +14,14 @@
- [TikTok](#tiktok)
- [Website URL](#website-url)
- [Text Snippet](#text-snippet)
- [API](#api)

## Introduction

Save the web with ReadItLater plugin for Obsidian. Archive web pages for reading later, referencing in your second brain or for other flexible use case Obsidian provides.

ReadItLater can do a lot more than converting web pages to markdown. For every content type there is specific template with carefully selected variables to ease up your archiving process.

To add something to your Vault just click the `ReadItLater: Save clipboard` ribbon or run the `ReadItLater: Save clipboard` command. New note will be created in folder configured in plugin settings.

### What makes ReadItLater plugin great?

- Simple, but powerful template engine
Expand All @@ -30,6 +30,12 @@ To add something to your Vault just click the `ReadItLater: Save clipboard` ribb
- Downloading images from articles to your Vault
- Batch processing of URLs list

### How to use ReadItLater plugin?

To create single note you can either click on the plugin ribbon icon, select `ReadItLater: Create from clipboard` from command palette or click on `ReadItLater` shortcut in context or share menu. You can also create multiple notes from batch of URLs, delimited by selected delimiter in plugin settings using `ReadItLater: Create from batch in clipboard` command.

If you want just add content to existing note, you can use `ReadItLater: Insert at the cursor position` command to insert content after the current cursor position.

## Template engine

ReadItLater provides for every content type dedicated template that can be edited in plugin settings.
Expand Down Expand Up @@ -60,6 +66,18 @@ outputs: Hello world
```
</details>

<details>
<summary>numberLexify</summary>

Converts number to lexified format.

```
{{ 12682|numberLexify}}
outputs: 12.6K
```
</details>

<details>
<summary>lower</summary>

Expand Down Expand Up @@ -119,28 +137,50 @@ Available content types are ordered by URL detection priority.
| title | Video title |
| date | Current date in format from plugin settings |

| Content template variable | Description |
| ------------------------- | ------------------------------------------- |
| videoTitle | Video title |
| date | Current date in format from plugin settings |
| videoURL | Video URL on Youtube.com |
| videoId | Video ID |
| videoPlayer | Embeded player generated by plugin |
| channelId | Channel ID |
| channelName | Channel name |
| channelURL | Channel URL on Youtube.com |
| videoThumbnail | Video thumbnail image URL |
| Content template variable | Description |
| ------------------------- | -------------------------------------------------------------------- |
| videoTitle | Video title |
| date | Current date in format from plugin settings |
| videoDescription | Video description |
| videoURL | Video URL on Youtube.com |
| videoId | Video ID |
| videoPlayer | Embeded player generated by plugin |
| channelId | Channel ID |
| channelName | Channel name |
| channelURL | Channel URL on Youtube.com |
| videoThumbnail | Video thumbnail image URL |
| videoChapters | List of video chapters with linked timestamps |
| videoPublishDate | Video plublish date formatted in content format from plugin settings |
| videoViewsCount | Video views count |

Parsing of HTML DOM has its limitations thus additional data can be fetched only from [Google API](https://developers.google.com/youtube/v3/getting-started). Retrieved API key can be set in plugin settings and then plugin will use the Google API for fetching data.

| Content template variable | Description |
| ------------------------- | -------------------------------------------------------------------- |
| videoDescription | Video description |
| videoDuration | Video duration in seconds |
| videoDurationFormatted | Formatted video duration (1h 25m 23s) |
| videoPublishDate | Video plublish date formatted in content format from plugin settings |
| videoTags | Formatted list of tags delimited by space |
| videoViewsCount | Video views count |

### Youtube Channel

| Title template variable | Description |
| ----------------------- | -------------------------------------------- |
| title | Channel title. |
| date | Current date in format from plugin settings. |

| Content template variable | Description |
| ------------------------- | ---------------------------------------------------- |
| date | Current date in format from plugin settings. |
| channelId | Channel ID. |
| channelTitle | Channel title. |
| channelDescription | Channel description. |
| channelURL | Channel URL on Youtube.com. |
| channelAvater | URL of channel's avatar (thumbnail) image. |
| channelBanner | URL of channel's banner image. |
| channelSubscribersCount | The number of subscribers that the channel has. |
| channelVideosCount | The number of public videos uploaded to the channel. |
| channelVideosURL | URL to channel's videos on Youtube.com |
| channelShortsURL | URL to channel's shorts on Youtube.com |

### Vimeo

Expand Down Expand Up @@ -294,3 +334,7 @@ If your clipboard content is not recognized by any of above parsers plugin will
| ------------------------- | ------------------------------------------- |
| content | Clipboard content |
| date | Current date in format from plugin settings |

## API

To invoke functionality from other plugins we provide an API. You can access it via `this.app.plugins.plugins['obsidian-read-it-later'].api` which is an instance of `ReadItLaterAPI` class defined in [src/ReadItLaterApi.ts](https://github.com/DominikPieper/obsidian-ReadItLater/blob/master/src/ReadItLaterApi.ts).
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"id": "obsidian-read-it-later",
"name": "ReadItLater",
"version": "0.9.1",
"version": "0.10.0",
"minAppVersion": "1.7.2",
"description": "Archive web pages for reading later, referencing in your second brain or for other flexible use case Obsidian provides.",
"description": "Save online content to your Vault, utilize embedded template engine and organize your reading list to your needs. Preserve the web with ReadItLater.",
"author": "Dominik Pieper",
"authorUrl": "https://github.com/DominikPieper",
"isDesktopOnly": false
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "obsidian-ReadItLater",
"version": "0.9.1",
"description": "Archive web pages for reading later, referencing in your second brain or for other flexible use case Obsidian provides.",
"version": "0.10.0",
"description": "Save online content to your Vault, utilize embedded template engine and organize your reading list to your needs. Preserve the web with ReadItLater.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs development",
Expand Down
129 changes: 129 additions & 0 deletions src/NoteService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Editor, Notice } from 'obsidian';
import ParserCreator from './parsers/ParserCreator';
import { VaultRepository } from './repository/VaultRepository';
import { Note } from './parsers/Note';
import FileExistsError from './error/FileExists';
import FileExistsAsk from './modal/FileExistsAsk';
import { FileExistsStrategy } from './enums/fileExistsStrategy';
import ReadItLaterPlugin from './main';
import { getAndCheckUrls } from './helpers/stringUtils';

export class NoteService {
constructor(
private parserCreator: ParserCreator,
private plugin: ReadItLaterPlugin,
private repository: VaultRepository,
) {}

public async createNote(content: string): Promise<void> {
const note = await this.makeNote(content);
try {
await this.repository.saveNote(note);
} catch (error) {
if (error instanceof FileExistsError) {
this.handleFileExistsError([note]);
}
}
}

public async createNotesFromBatch(contentBatch: string): Promise<void> {
const urlCheckResult = getAndCheckUrls(contentBatch, this.plugin.settings.batchProcessDelimiter);
const existingNotes: Note[] = [];

for (const contentSegment of urlCheckResult.everyLineIsURL ? urlCheckResult.urls : [contentBatch]) {
const note = await this.makeNote(contentSegment);
try {
await this.repository.saveNote(note);
} catch (error) {
if (error instanceof FileExistsError) {
existingNotes.push(note);
}
}
}

if (existingNotes.length > 0) {
this.handleFileExistsError(existingNotes);
}
}

public async insertContentAtEditorCursorPosition(content: string, editor: Editor): Promise<void> {
const note = await this.makeNote(content);
editor.replaceRange(note.content, editor.getCursor());
}

private async makeNote(content: string): Promise<Note> {
const parser = await this.parserCreator.createParser(content);
return await parser.prepareNote(content);
}

private openNote(note: Note): void {
if (this.plugin.settings.openNewNote || this.plugin.settings.openNewNoteInNewTab) {
try {
const file = this.repository.getFileByPath(note.filePath);
this.plugin.app.workspace
.getLeaf(this.plugin.settings.openNewNoteInNewTab ? 'tab' : false)
.openFile(file);
} catch (error) {
console.error(error);
new Notice(`Unable to open ${note.getFullFilename()}`);
}
}
}

private async handleFileExistsError(notes: Note[]): Promise<void> {
switch (this.plugin.settings.fileExistsStrategy) {
case FileExistsStrategy.Ask:
new FileExistsAsk(this.plugin.app, notes, (strategy, doNotAskAgain) => {
this.handleFileAskModalResponse(strategy, doNotAskAgain, notes);
}).open();
break;
case FileExistsStrategy.Nothing:
this.handleFileExistsStrategyNothing(notes);
break;
case FileExistsStrategy.AppendToExisting:
this.handleFileExistsStrategyAppend(notes);
break;
}
}

private async handleFileAskModalResponse(
strategy: FileExistsStrategy,
doNotAskAgain: boolean,
notes: Note[],
): Promise<void> {
switch (strategy) {
case FileExistsStrategy.Nothing:
this.handleFileExistsStrategyNothing(notes);
break;
case FileExistsStrategy.AppendToExisting:
this.handleFileExistsStrategyAppend(notes);
break;
}

if (doNotAskAgain) {
this.plugin.saveSetting('fileExistsStrategy', strategy as FileExistsStrategy);
}

if (notes.length === 1) {
this.openNote(notes.shift());
}
}

private async handleFileExistsStrategyAppend(notes: Note[]): Promise<void> {
for (const note of notes) {
try {
await this.repository.appendToExistingNote(note);
new Notice(`${note.getFullFilename()} was updated.`);
} catch (error) {
console.error(error);
new Notice(`${note.getFullFilename()} was not updated!`, 0);
}
}
}

private handleFileExistsStrategyNothing(notes: Note[]): void {
for (const note of notes) {
new Notice(`${note.getFullFilename()} already exists.`);
}
}
}
27 changes: 27 additions & 0 deletions src/ReadtItLaterApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Editor } from 'obsidian';
import { NoteService } from './NoteService';

export class ReadItLaterApi {
constructor(private noteService: NoteService) {}

/**
* Create single note from provided input.
*/
public async processContent(content: string): Promise<void> {
this.noteService.createNote(content);
}

/**
* Create multiple notes from provided input delimited by delimiter defined in plugin settings.
*/
public async processContentBatch(contentBatch: string): Promise<void> {
this.noteService.createNotesFromBatch(contentBatch);
}

/**
* Insert processed content from input at current position in editor.
*/
public async insertContentAtEditorCursorPosition(content: string, editor: Editor): Promise<void> {
this.noteService.insertContentAtEditorCursorPosition(content, editor);
}
}
9 changes: 3 additions & 6 deletions src/enums/delimiter.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { DropdownEnumOption } from './enum';

export enum Delimiter {
NewLine = 'newLine',
Comma = 'comma',
Period = 'period',
Semicolon = 'semicolon',
}

export interface DelimiterOption {
label: string;
option: string;
}

export function getDelimiterOptions(): DelimiterOption[] {
export function getDelimiterOptions(): DropdownEnumOption[] {
return [
{ label: 'New Line', option: Delimiter.NewLine },
{ label: 'Comma', option: Delimiter.Comma },
Expand Down
4 changes: 4 additions & 0 deletions src/enums/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface DropdownEnumOption {
label: string;
option: string;
}
15 changes: 15 additions & 0 deletions src/enums/fileExistsStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DropdownEnumOption } from './enum';

export enum FileExistsStrategy {
AppendToExisting = 'appendToExisting',
Ask = 'ask',
Nothing = 'nothing',
}

export function getFileExistStrategyOptions(): DropdownEnumOption[] {
return [
{ label: 'Ask', option: FileExistsStrategy.Ask },
{ label: 'Nothing', option: FileExistsStrategy.Nothing },
{ label: 'Append to the existing note', option: FileExistsStrategy.AppendToExisting },
];
}
6 changes: 6 additions & 0 deletions src/error/FileExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default class FileExistsError extends Error {
constructor(message: string) {
super(message);
this.name = 'FileExistsError';
}
}
6 changes: 6 additions & 0 deletions src/error/FileNotFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default class FileNotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = 'FileNotFoundError';
}
}
Loading

0 comments on commit 35264d5

Please sign in to comment.