Skip to content

Commit 4f0ce2e

Browse files
authored
Merge pull request #188 from DominikPieper/feat/inbox-dir-template
feat: inbox directories template variables
2 parents 661ba0c + 4ec944f commit 4f0ce2e

18 files changed

+723
-328
lines changed

README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ Save the web with ReadItLater plugin for Obsidian. Archive web pages for reading
2020

2121
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.
2222

23-
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.
23+
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.
24+
25+
### What makes ReadItLater plugin great?
26+
27+
- Simple, but powerful template engine
28+
- Carefully selected predefined template variables to straightforward archiving process
29+
- Compatibility with Obsidian iOS and Android apps
30+
- Downloading images from articles to your Vault
31+
- Batch processing of URLs list
2432

2533
## Template engine
2634

@@ -88,6 +96,16 @@ outputs: HELLO WORLD
8896
```
8997
</details>
9098

99+
## Inbox and Assets directories
100+
101+
You can use template variables in `Inbox dir` and `Assets dir` settings to better distribute content in your Vault.
102+
103+
| Directory template variable | Description |
104+
| --------------------------- | -------------------------------------------------- |
105+
| date | Current date in format from plguin settins |
106+
| fileName | Filename of new note |
107+
| contentType | Slug of detected content type from plugin settings |
108+
91109
## Content Types
92110

93111
Structure of note content is determined by URL. Currenty plugin supports saving content of websites and embedding content from multiple services. Each content type has title and note template with replacable variables, which can be edited in plugin settings.

manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "ReadItLater",
44
"version": "0.7.0",
55
"minAppVersion": "1.6.2",
6-
"description": "Saves the clipboard to a new note.",
6+
"description": "Archive web pages for reading later, referencing in your second brain or for other flexible use case Obsidian provides.",
77
"author": "Dominik Pieper",
88
"authorUrl": "https://github.com/DominikPieper",
99
"isDesktopOnly": false

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "obsidian-ReadItLater",
33
"version": "0.7.0",
4-
"description": "Plugin for Obsidian to collect interesting information from your clipboard into your vault",
4+
"description": "Archive web pages for reading later, referencing in your second brain or for other flexible use case Obsidian provides.",
55
"main": "main.js",
66
"scripts": {
77
"dev": "node esbuild.config.mjs development",

src/main.ts

+17-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Menu, MenuItem, Notice, Plugin, addIcon, normalizePath } from 'obsidian';
2-
import { checkAndCreateFolder, isValidUrl, normalizeFilename } from './helpers';
2+
import { checkAndCreateFolder, formatDate, isValidUrl } from './helpers';
33
import { DEFAULT_SETTINGS, ReadItLaterSettings } from './settings';
44
import { ReadItLaterSettingsTab } from './views/settings-tab';
55
import YoutubeParser from './parsers/YoutubeParser';
@@ -15,8 +15,9 @@ import ParserCreator from './parsers/ParserCreator';
1515
import { HTTPS_PROTOCOL, HTTP_PROTOCOL } from './constants/urlProtocols';
1616
import GithubParser from './parsers/GithubParser';
1717
import WikipediaParser from './parsers/WikipediaParser';
18-
import { Delimiter, getDelimiterValue } from './enums/delimiter';
18+
import { getDelimiterValue } from './enums/delimiter';
1919
import TemplateEngine from './template/TemplateEngine';
20+
import { Note } from './parsers/Note';
2021

2122
export default class ReadItLaterPlugin extends Plugin {
2223
settings: ReadItLaterSettings;
@@ -105,7 +106,7 @@ export default class ReadItLaterPlugin extends Plugin {
105106
async _processUrlSingle(clipboardContent: string): Promise<void> {
106107
const parser = await this.parserCreator.createParser(clipboardContent);
107108
const note = await parser.prepareNote(clipboardContent);
108-
await this.writeFile(note.fileName, note.content);
109+
await this.writeFile(note);
109110
}
110111

111112
async _processUrlsBatch(clipboardContent: string): Promise<void> {
@@ -128,28 +129,31 @@ export default class ReadItLaterPlugin extends Plugin {
128129
const parser = await this.parserCreator.createParser(content);
129130

130131
const note = await parser.prepareNote(content);
131-
await this.writeFile(note.fileName, note.content);
132+
await this.writeFile(note);
132133
}
133134

134-
async writeFile(fileName: string, content: string): Promise<void> {
135+
async writeFile(note: Note): Promise<void> {
135136
let filePath;
136-
fileName = normalizeFilename(fileName);
137-
await checkAndCreateFolder(this.app.vault, this.settings.inboxDir);
138-
139137
if (this.settings.inboxDir) {
140-
filePath = normalizePath(`${this.settings.inboxDir}/${fileName}`);
138+
const inboxDir = this.templateEngine.render(this.settings.inboxDir, {
139+
date: formatDate(note.createdAt, this.settings.dateTitleFmt),
140+
fileName: note.fileName,
141+
contentType: note.contentType,
142+
});
143+
await checkAndCreateFolder(this.app.vault, inboxDir);
144+
filePath = normalizePath(`${inboxDir}/${note.getFullFilename()}`);
141145
} else {
142-
filePath = normalizePath(`/${fileName}`);
146+
filePath = normalizePath(`/${note.getFullFilename()}`);
143147
}
144148

145149
if (await this.app.vault.adapter.exists(filePath)) {
146-
new Notice(`${fileName} already exists!`);
150+
new Notice(`${note.getFullFilename()} already exists!`);
147151
} else {
148-
const newFile = await this.app.vault.create(filePath, content);
152+
const newFile = await this.app.vault.create(filePath, note.content);
149153
if (this.settings.openNewNote || this.settings.openNewNoteInNewTab) {
150154
this.app.workspace.getLeaf(this.settings.openNewNoteInNewTab ? 'tab' : false).openFile(newFile);
151155
}
152-
new Notice(`${fileName} created successful`);
156+
new Notice(`${note.getFullFilename()} created successful`);
153157
}
154158
}
155159
}

src/parsers/BilibiliParser.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,20 @@ class BilibiliParser extends Parser {
2424
}
2525

2626
async prepareNote(url: string): Promise<Note> {
27-
const data = await this.getNoteData(url);
27+
const createdAt = new Date();
28+
const data = await this.getNoteData(url, createdAt);
2829

2930
const content = this.templateEngine.render(this.settings.bilibiliNote, data);
3031

3132
const fileNameTemplate = this.templateEngine.render(this.settings.bilibiliNoteTitle, {
3233
title: data.videoTitle,
33-
date: this.getFormattedDateForFilename(),
34+
date: this.getFormattedDateForFilename(createdAt),
3435
});
3536

36-
return new Note(`${fileNameTemplate}.md`, content);
37+
return new Note(fileNameTemplate, 'md', content, this.settings.bilibiliContentTypeSlug, createdAt);
3738
}
3839

39-
private async getNoteData(url: string): Promise<BilibiliNoteData> {
40+
private async getNoteData(url: string, createdAt: Date): Promise<BilibiliNoteData> {
4041
const response = await request({
4142
method: 'GET',
4243
url,
@@ -49,7 +50,7 @@ class BilibiliParser extends Parser {
4950
const videoId = this.PATTERN.exec(url)[3] ?? '';
5051

5152
return {
52-
date: this.getFormattedDateForContent(),
53+
date: this.getFormattedDateForContent(createdAt),
5354
videoId: videoId,
5455
videoTitle: videoHTML.querySelector("[property~='og:title']").getAttribute('content') ?? '',
5556
videoURL: url,

src/parsers/MastodonParser.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class MastodonParser extends Parser {
5757
}
5858

5959
async prepareNote(url: string): Promise<Note> {
60+
const createdAt = new Date();
6061
const mastodonUrl = new URL(url);
6162
const statusId = mastodonUrl.pathname.split('/')[2];
6263

@@ -68,21 +69,38 @@ class MastodonParser extends Parser {
6869

6970
const fileNameTemplate = this.templateEngine.render(this.settings.mastodonNoteTitle, {
7071
tootAuthorName: status.account.display_name,
71-
date: this.getFormattedDateForFilename(),
72+
date: this.getFormattedDateForFilename(createdAt),
7273
});
7374

74-
const assetsDir = this.settings.downloadMastodonMediaAttachmentsInDir
75-
? `${this.settings.assetsDir}/${normalizeFilename(fileNameTemplate)}/`
76-
: this.settings.assetsDir;
75+
let assetsDir;
76+
if (this.settings.downloadMastodonMediaAttachmentsInDir) {
77+
assetsDir = this.templateEngine.render(this.settings.assetsDir, {
78+
date: '',
79+
fileName: '',
80+
contentType: '',
81+
});
82+
assetsDir = `${assetsDir}/${normalizeFilename(fileNameTemplate)}`;
83+
} else {
84+
assetsDir = this.templateEngine.render(this.settings.assetsDir, {
85+
date: this.getFormattedDateForFilename(createdAt),
86+
fileName: normalizeFilename(fileNameTemplate),
87+
contentType: this.settings.mastodonContentTypeSlug,
88+
});
89+
}
7790

78-
const data = await this.getNoteData(status, replies, assetsDir);
91+
const data = await this.getNoteData(status, replies, assetsDir, createdAt);
7992

8093
const content = this.templateEngine.render(this.settings.mastodonNote, data);
8194

82-
return new Note(`${fileNameTemplate}.md`, content);
95+
return new Note(fileNameTemplate, 'md', content, this.settings.mastodonContentTypeSlug, createdAt);
8396
}
8497

85-
private async getNoteData(status: Status, replies: Status[], assetsDir: string): Promise<MastodonStatusNoteData> {
98+
private async getNoteData(
99+
status: Status,
100+
replies: Status[],
101+
assetsDir: string,
102+
createdAt: Date,
103+
): Promise<MastodonStatusNoteData> {
86104
let parsedStatusContent = await this.parseStatus(status, assetsDir);
87105

88106
if (replies.length > 0) {
@@ -98,7 +116,7 @@ class MastodonParser extends Parser {
98116
}
99117

100118
return {
101-
date: this.getFormattedDateForContent(),
119+
date: this.getFormattedDateForContent(createdAt),
102120
tootAuthorName: status.account.display_name,
103121
tootURL: status.url,
104122
tootContent: parsedStatusContent,

src/parsers/Note.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import { normalizeFilename } from 'src/helpers';
2+
13
export class Note {
2-
public readonly fileName: string;
3-
public readonly content: string;
4+
constructor(
5+
public readonly fileName: string,
6+
public readonly fileExtension: string,
7+
public readonly content: string,
8+
public readonly contentType: string,
9+
public readonly createdAt: Date,
10+
) {
11+
this.fileName = normalizeFilename(this.fileName);
12+
}
413

5-
constructor(fileName: string, content: string) {
6-
this.fileName = fileName;
7-
this.content = content;
14+
public getFullFilename(): string {
15+
return `${this.fileName}.${this.fileExtension}`;
816
}
917
}

src/parsers/Parser.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { App } from 'obsidian';
22
import TemplateEngine from 'src/template/TemplateEngine';
3-
import { formatCurrentDate, isValidUrl } from '../helpers';
3+
import { formatDate, isValidUrl } from '../helpers';
44
import { ReadItLaterSettings } from '../settings';
55
import { Note } from './Note';
66

@@ -23,11 +23,11 @@ export abstract class Parser {
2323
return isValidUrl(url);
2424
}
2525

26-
protected getFormattedDateForFilename(): string {
27-
return formatCurrentDate(this.settings.dateTitleFmt);
26+
protected getFormattedDateForFilename(date: Date | string): string {
27+
return formatDate(date, this.settings.dateTitleFmt);
2828
}
2929

30-
protected getFormattedDateForContent(): string {
31-
return formatCurrentDate(this.settings.dateContentFmt);
30+
protected getFormattedDateForContent(date: Date | string): string {
31+
return formatDate(date, this.settings.dateContentFmt);
3232
}
3333
}

src/parsers/StackExchangeParser.ts

+32-15
Original file line numberDiff line numberDiff line change
@@ -53,34 +53,51 @@ class StackExchangeParser extends Parser {
5353
}
5454

5555
async prepareNote(clipboardContent: string): Promise<Note> {
56+
const createdAt = new Date();
5657
const response = await request({ method: 'GET', url: clipboardContent });
5758
const document = new DOMParser().parseFromString(response, 'text/html');
5859
const question = await this.parseDocument(document);
5960

60-
const fileNameTemplate = this.settings.stackExchangeNoteTitle
61-
.replace(/%title%/g, () => question.title)
62-
.replace(/%date%/g, this.getFormattedDateForFilename());
63-
64-
const assetsDir = this.settings.downloadStackExchangeAssetsInDir
65-
? `${this.settings.assetsDir}/${normalizeFilename(fileNameTemplate)}/`
66-
: this.settings.assetsDir;
61+
const fileNameTemplate = this.templateEngine.render(this.settings.stackExchangeNoteTitle, {
62+
title: question.title,
63+
date: this.getFormattedDateForFilename(createdAt),
64+
});
65+
66+
let assetsDir;
67+
if (this.settings.downloadStackExchangeAssetsInDir) {
68+
assetsDir = this.templateEngine.render(this.settings.assetsDir, {
69+
date: '',
70+
fileName: '',
71+
contentType: '',
72+
});
73+
assetsDir = `${assetsDir}/${normalizeFilename(fileNameTemplate)}`;
74+
} else {
75+
assetsDir = this.templateEngine.render(this.settings.assetsDir, {
76+
date: this.getFormattedDateForFilename(createdAt),
77+
fileName: normalizeFilename(fileNameTemplate),
78+
contentType: this.settings.stackExchangeContentType,
79+
});
80+
}
6781

68-
let content = this.templateEngine.render(this.settings.stackExchangeNote, this.getNoteData(question));
82+
let content = this.templateEngine.render(
83+
this.settings.stackExchangeNote,
84+
this.getNoteData(question, createdAt),
85+
);
6986

7087
if (this.settings.downloadStackExchangeAssets && Platform.isDesktop) {
7188
content = await replaceImages(this.app, content, assetsDir);
7289
}
7390

74-
return new Note(`${fileNameTemplate}.md`, content);
91+
return new Note(fileNameTemplate, 'md', content, this.settings.stackExchangeContentType, createdAt);
7592
}
7693

77-
private getNoteData(question: StackExchangeQuestion): StackExchangeNoteData {
94+
private getNoteData(question: StackExchangeQuestion, createdAt: Date): StackExchangeNoteData {
7895
const topAnswer = question.topAnswer
7996
? this.templateEngine.render(this.settings.stackExchangeAnswer, {
80-
date: this.getFormattedDateForContent(),
97+
date: this.getFormattedDateForContent(createdAt),
8198
answerContent: question.topAnswer.content,
82-
authorName: question.author.name,
83-
authorProfileURL: question.author.profile,
99+
authorName: question.topAnswer.author.name,
100+
authorProfileURL: question.topAnswer.author.profile,
84101
})
85102
: '';
86103

@@ -89,7 +106,7 @@ class StackExchangeParser extends Parser {
89106
answers = answers.concat(
90107
'\n\n***\n\n',
91108
this.templateEngine.render(this.settings.stackExchangeAnswer, {
92-
date: this.getFormattedDateForContent(),
109+
date: this.getFormattedDateForContent(createdAt),
93110
answerContent: question.answers[i].content,
94111
authorName: question.answers[i].author.name,
95112
authorProfileURL: question.answers[i].author.profile,
@@ -98,7 +115,7 @@ class StackExchangeParser extends Parser {
98115
}
99116

100117
return {
101-
date: this.getFormattedDateForContent(),
118+
date: this.getFormattedDateForContent(createdAt),
102119
questionTitle: question.title,
103120
questionURL: question.url,
104121
questionContent: question.content,

src/parsers/TextSnippetParser.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ class TextSnippetParser extends Parser {
1414
}
1515

1616
async prepareNote(text: string): Promise<Note> {
17-
const fileNameTemplate = this.settings.textSnippetNoteTitle.replace(
18-
/%date%/g,
19-
this.getFormattedDateForFilename(),
20-
);
21-
const fileName = `${fileNameTemplate}.md`;
17+
const createdAt = new Date();
2218

23-
const content = this.settings.textSnippetNote
24-
.replace(/%content%/g, () => text)
25-
.replace(/%date%/g, this.getFormattedDateForContent());
26-
return new Note(fileName, content);
19+
const fileNameTemplate = this.templateEngine.render(this.settings.textSnippetNoteTitle, {
20+
date: this.getFormattedDateForFilename(createdAt),
21+
});
22+
23+
const content = this.templateEngine.render(this.settings.textSnippetNote, {
24+
content: text,
25+
date: this.getFormattedDateForContent(createdAt),
26+
});
27+
return new Note(fileNameTemplate, 'md', content, this.settings.textSnippetContentType, createdAt);
2728
}
2829
}
2930

0 commit comments

Comments
 (0)