Skip to content

Commit 52b54d3

Browse files
committed
v0.0.17
2 parents dd1187f + dedad6d commit 52b54d3

13 files changed

+238
-174
lines changed

manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "obsidian-raindrop-highlights",
33
"name": "Raindrop Highlights",
4-
"version": "0.0.16",
4+
"version": "0.0.17",
55
"minAppVersion": "0.14.0",
66
"description": "Sync your Raindrop.io highlights.",
77
"author": "kaiiiz",

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-raindrop-highlights",
3-
"version": "0.0.16",
3+
"version": "0.0.17",
44
"description": "Sync your Raindrop.io highlights.",
55
"main": "main.js",
66
"scripts": {
@@ -15,6 +15,7 @@
1515
"@tsconfig/svelte": "3.0.0",
1616
"@types/node": "16.11.6",
1717
"@types/nunjucks": "3.2.1",
18+
"@types/truncate-utf8-bytes": "^1.0.0",
1819
"@typescript-eslint/eslint-plugin": "5.29.0",
1920
"@typescript-eslint/parser": "5.29.0",
2021
"builtin-modules": "3.3.0",

src/api.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class RaindropAPI {
4848
async getCollections(): Promise<RaindropCollection[]> {
4949
let res = await this.get(`${BASEURL}/collections`, {});
5050

51-
let collections: RaindropCollection[] = [
51+
const collections: RaindropCollection[] = [
5252
{ id: -1, title: 'Unsorted' },
5353
{ id: -99, title: 'Trash' },
5454
];
@@ -93,18 +93,18 @@ export class RaindropAPI {
9393

9494
async getRaindropsAfter(collectionId: number, lastSync?: Date): Promise<RaindropBookmark[]> {
9595
const notice = new Notice("Fetch Raindrops highlights", 0);
96-
let res = await this.get(`${BASEURL}/raindrops/${collectionId}`, {
96+
const res = await this.get(`${BASEURL}/raindrops/${collectionId}`, {
9797
"page": 0,
9898
"sort": "-lastUpdate"
9999
});
100-
let raindropsCnt = res.count;
100+
const raindropsCnt = res.count;
101101
let bookmarks = this.parseRaindrops(res.items);
102102
let remainPages = Math.ceil(raindropsCnt / 25) - 1;
103-
let totalPages = Math.ceil(raindropsCnt / 25) - 1;
103+
const totalPages = Math.ceil(raindropsCnt / 25) - 1;
104104
let page = 1;
105105

106-
let addNewPages = async (page: number) => {
107-
let res = await this.get(`${BASEURL}/raindrops/${collectionId}`, {
106+
const addNewPages = async (page: number) => {
107+
const res = await this.get(`${BASEURL}/raindrops/${collectionId}`, {
108108
"page": page,
109109
"sort": "-lastUpdate"
110110
});
@@ -129,10 +129,10 @@ export class RaindropAPI {
129129
}
130130

131131
// get real highlights (raindrop returns only 3 highlights in /raindrops/${collectionId} endpoint)
132-
for (let [idx, bookmark] of bookmarks.entries()) {
132+
for (const [idx, bookmark] of bookmarks.entries()) {
133133
notice.setMessage(`Sync Raindrop bookmarks: ${idx + 1}/${bookmarks.length}`)
134134
if (bookmark.highlights.length == 3) {
135-
let res = await this.get(`${BASEURL}/raindrop/${bookmark.id}`, {});
135+
const res = await this.get(`${BASEURL}/raindrop/${bookmark.id}`, {});
136136
bookmark['highlights'] = this.parseHighlights(res.item.highlights);
137137
}
138138
}

src/assets/defaultTemplate.njk

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{% if is_new_article %}
22
# Metadata
33
{% if link %}Source URL:: {{link}}{% endif %}
4-
{% if tags|length %}Topics:: {{ tags | map("#{}") | join(", ") }}{% endif %}
4+
{% if tags|length %}Topics:: #{{ tags | join(", #") }}{% endif %}
55

66
---
77
# {{title}}

src/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import DEFAULT_TEMPLATE from './assets/defaultTemplate.njk';
22
import type { RaindropPluginSettings } from "./types";
33

4-
export const VERSION = '0.0.16';
4+
export const VERSION = '0.0.17';
55

66
export const DEFAULT_SETTINGS: RaindropPluginSettings = {
77
version: VERSION,
@@ -15,6 +15,7 @@ export const DEFAULT_SETTINGS: RaindropPluginSettings = {
1515
syncCollections: {},
1616
template: DEFAULT_TEMPLATE,
1717
metadataTemplate: "",
18+
filenameTemplate: "{{title}}",
1819
dateTimeFormat: 'YYYY/MM/DD HH:mm:ss',
1920
autoSyncInterval: 0,
2021
};

src/main.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default class RaindropPlugin extends Plugin {
3939
id: 'raindrop-show-last-sync-time',
4040
name: 'Show last sync time',
4141
callback: async () => {
42-
let message = Object.values(this.settings.syncCollections)
42+
const message = Object.values(this.settings.syncCollections)
4343
.filter((collection: SyncCollection) => collection.sync)
4444
.map((collection: SyncCollection) => {
4545
return `${collection.title}: ${collection.lastSyncDate?.toLocaleString()}`;
@@ -81,7 +81,7 @@ export default class RaindropPlugin extends Plugin {
8181

8282
async loadSettings() {
8383
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
84-
for (let id in this.settings.syncCollections) {
84+
for (const id in this.settings.syncCollections) {
8585
const collection = this.settings.syncCollections[id];
8686
if (collection.lastSyncDate) {
8787
collection.lastSyncDate = new Date(collection.lastSyncDate);

src/renderer.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import nunjucks from "nunjucks";
22
import Moment from "moment";
33
import type RaindropPlugin from "./main";
4-
import type { BookmarkFileFrontMatter, RaindropBookmark } from "./types";
5-
import { Notice, parseYaml, stringifyYaml } from "obsidian";
4+
import sanitize from "sanitize-filename";
5+
import type { RaindropBookmark } from "./types";
6+
import { parseYaml } from "obsidian";
67

78
type RenderHighlight = {
89
id: string;
@@ -96,7 +97,11 @@ export default class Renderer {
9697

9798
renderFrontmatter(bookmark: RaindropBookmark, newArticle: boolean) {
9899
const newMdFrontmatter = this.renderTemplate(this.plugin.settings.metadataTemplate, bookmark, newArticle);
99-
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n${newMdFrontmatter}\n`
100+
if (newMdFrontmatter.length > 0) {
101+
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n${newMdFrontmatter}\n`
102+
} else {
103+
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n`
104+
}
100105
}
101106

102107
renderFullArticle(bookmark: RaindropBookmark) {
@@ -106,6 +111,15 @@ export default class Renderer {
106111
return mdContent;
107112
}
108113

114+
renderFileName(bookmark: RaindropBookmark, newArticle: boolean) {
115+
const filename = this.renderTemplate(this.plugin.settings.filenameTemplate, bookmark, newArticle);
116+
return this.sanitizeFilename(filename);
117+
}
118+
119+
private sanitizeFilename(filename: string): string {
120+
return sanitize(filename.replace(/[':#|]/g, "").trim());
121+
}
122+
109123
private renderTemplate(template:string, bookmark: RaindropBookmark, newArticle: boolean) {
110124
const dateTimeFormat = this.plugin.settings.dateTimeFormat;
111125

src/settings.ts

+59-28
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {App, Notice, PluginSettingTab, Setting} from 'obsidian';
22
import DEFAULT_METADATA_TEMPLATE from './assets/defaultMetadataTemplate.njk';
33
import templateInstructions from './templates/templateInstructions.html';
44
import metadataTemplateInstructions from './templates/metadataTemplateInstructions.html';
5+
import filenameTemplateInstructions from './templates/filenameTemplateInstructions.html';
56
import datetimeInstructions from './templates/datetimeInstructions.html';
67
import appendModeInstructions from './templates/appendModeInstructions.html';
78
import type { RaindropAPI } from './api';
@@ -41,6 +42,7 @@ export class RaindropSettingTab extends PluginSettingTab {
4142
this.dateFormat();
4243
this.template();
4344
this.metadataTemplate();
45+
this.filenameTemplate();
4446
this.resetSyncHistory();
4547
}
4648

@@ -59,8 +61,8 @@ export class RaindropSettingTab extends PluginSettingTab {
5961

6062
private appendMode(): void {
6163
const descFragment = document
62-
.createRange()
63-
.createContextualFragment(appendModeInstructions);
64+
.createRange()
65+
.createContextualFragment(appendModeInstructions);
6466

6567
new Setting(this.containerEl)
6668
.setName('Append Mode')
@@ -175,25 +177,25 @@ export class RaindropSettingTab extends PluginSettingTab {
175177

176178
private highlightsFolder(): void {
177179
new Setting(this.containerEl)
178-
.setName('Highlights folder location')
179-
.setDesc('Vault folder to use for writing Raindrop.io highlights')
180-
.addDropdown((dropdown) => {
181-
const files = (this.app.vault.adapter as any).files;
182-
Object.keys(files).forEach((key) => {
183-
if (files[key].type == 'folder') {
184-
const folder = files[key].realpath;
185-
dropdown.addOption(folder, folder);
186-
}
187-
})
180+
.setName('Highlights folder location')
181+
.setDesc('Vault folder to use for storing Raindrop.io highlights')
182+
.addDropdown((dropdown) => {
183+
const files = (this.app.vault.adapter as any).files;
184+
Object.keys(files).forEach((key) => {
185+
if (files[key].type == 'folder') {
186+
const folder = files[key].realpath;
187+
dropdown.addOption(folder, folder);
188+
}
189+
})
188190

189-
return dropdown
190-
.setValue(this.plugin.settings.highlightsFolder)
191-
.onChange(async (value) => {
192-
this.plugin.settings.highlightsFolder = value;
193-
await this.plugin.saveSettings();
194-
});
195-
});
196-
}
191+
return dropdown
192+
.setValue(this.plugin.settings.highlightsFolder)
193+
.onChange(async (value) => {
194+
this.plugin.settings.highlightsFolder = value;
195+
await this.plugin.saveSettings();
196+
});
197+
});
198+
}
197199

198200
private async collections(): Promise<void> {
199201
new Setting(this.containerEl)
@@ -205,11 +207,13 @@ export class RaindropSettingTab extends PluginSettingTab {
205207
.setButtonText('Manage')
206208
.setCta()
207209
.onClick(async () => {
210+
button.setButtonText('Loading collections...');
211+
208212
// update for new collections
209213
const allCollections = await this.api.getCollections();
210214
this.plugin.updateCollectionSettings(allCollections);
211215

212-
const collectionsModal = new CollectionsModal(this.app, this.plugin);
216+
new CollectionsModal(this.app, this.plugin);
213217
this.display(); // rerender
214218
});
215219
});
@@ -241,6 +245,7 @@ export class RaindropSettingTab extends PluginSettingTab {
241245
return text;
242246
});
243247
}
248+
244249
private async metadataTemplate(): Promise<void> {
245250
const templateDescFragment = document
246251
.createRange()
@@ -268,19 +273,45 @@ export class RaindropSettingTab extends PluginSettingTab {
268273
return text;
269274
});
270275
}
271-
276+
277+
private async filenameTemplate(): Promise<void> {
278+
const templateDescFragment = document
279+
.createRange()
280+
.createContextualFragment(filenameTemplateInstructions);
281+
282+
new Setting(this.containerEl)
283+
.setName('Filename template')
284+
.setDesc(templateDescFragment)
285+
.addTextArea((text) => {
286+
text.inputEl.style.width = '100%';
287+
text.inputEl.style.height = '250px';
288+
text.inputEl.style.fontSize = '0.8em';
289+
text.setValue(this.plugin.settings.filenameTemplate)
290+
.onChange(async (value) => {
291+
const isValid = this.renderer.validate(value, false);
292+
293+
if (isValid) {
294+
this.plugin.settings.filenameTemplate = value;
295+
await this.plugin.saveSettings();
296+
}
297+
298+
text.inputEl.style.border = isValid ? '' : '1px solid red';
299+
});
300+
return text;
301+
});
302+
}
272303

273304
private resetSyncHistory(): void {
274305
new Setting(this.containerEl)
275-
.setName('Reset sync')
276-
.setDesc('Reset last sync time to resync')
277-
.addButton((button) => {
306+
.setName('Reset sync')
307+
.setDesc('Reset last sync time to resync')
308+
.addButton((button) => {
278309
return button
279310
.setButtonText('Reset')
280311
.setDisabled(!this.plugin.settings.isConnected)
281312
.setWarning()
282313
.onClick(async () => {
283-
for (let id in this.plugin.settings.syncCollections) {
314+
for (const id in this.plugin.settings.syncCollections) {
284315
const collection = this.plugin.settings.syncCollections[id];
285316
collection.lastSyncDate = undefined;
286317
}
@@ -292,8 +323,8 @@ export class RaindropSettingTab extends PluginSettingTab {
292323

293324
private dateFormat(): void {
294325
const descFragment = document
295-
.createRange()
296-
.createContextualFragment(datetimeInstructions);
326+
.createRange()
327+
.createContextualFragment(datetimeInstructions);
297328

298329
new Setting(this.containerEl)
299330
.setName('Date & time format')

src/sync.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { App, Notice, parseYaml, stringifyYaml, TFile } from "obsidian";
2-
import sanitize from "sanitize-filename";
32
import type { RaindropAPI } from "./api";
43
import type RaindropPlugin from "./main";
54
import Renderer from "./renderer";
5+
import truncate from "truncate-utf8-bytes";
66
import type { BookmarkFile, BookmarkFileFrontMatter, RaindropBookmark, RaindropCollection, SyncCollection } from "./types";
77

88
export default class RaindropSync {
@@ -55,7 +55,7 @@ export default class RaindropSync {
5555
if (bookmarks.length == 0) return;
5656

5757
if (this.plugin.settings.onlyBookmarksWithHl) {
58-
let requireUpdate = bookmarks.some((bookmark) => {
58+
const requireUpdate = bookmarks.some((bookmark) => {
5959
return bookmark.highlights.length != 0;
6060
});
6161
if (!requireUpdate) return;
@@ -72,20 +72,22 @@ export default class RaindropSync {
7272
...this.getBookmarkFiles().map((x) => ({ [x.raindropId]: x.file }))
7373
);
7474

75-
for (let bookmark of bookmarks) {
75+
for (const bookmark of bookmarks) {
7676
if (this.plugin.settings.onlyBookmarksWithHl && bookmark.highlights.length == 0) {
7777
continue;
7878
}
7979

8080
if (bookmark.id in bookmarkFilesMap) {
8181
await this.updateFile(bookmarkFilesMap[bookmark.id], bookmark);
8282
} else {
83-
let fileName = `${this.sanitizeTitle(bookmark.title)}.md`;
83+
const renderedFilename = this.renderer.renderFileName(bookmark, true);
84+
let fileName = truncate(`${renderedFilename}`, 252) + ".md";
8485
let filePath = `${folderPath}/${fileName}`;
8586
let suffix = 1;
8687
while (await this.app.vault.adapter.exists(filePath)) {
8788
console.debug(`${filePath} alreay exists`);
88-
fileName = `${this.sanitizeTitle(bookmark.title)} (${suffix++}).md`;
89+
const fileSuffix = ` (${suffix++}).md`;
90+
fileName = truncate(`${renderedFilename}`, 255 - fileSuffix.length) + fileSuffix;
8991
filePath = `${folderPath}/${fileName}`;
9092
}
9193
bookmarkFilesMap[bookmark.id] = await this.createFile(filePath, bookmark);
@@ -130,8 +132,7 @@ export default class RaindropSync {
130132
if (metadata?.frontmatter) {
131133
// separate content and front matter
132134
const fileContent = await this.app.vault.cachedRead(file);
133-
const {position: {start, end}} = metadata.frontmatter;
134-
const article = this.splitFrontmatterAndContent(fileContent, end.line);
135+
const article = this.splitFrontmatterAndContent(fileContent, metadata.frontmatter.position.end.line);
135136

136137
const frontmatterObj: BookmarkFileFrontMatter = parseYaml(article.frontmatter);
137138
frontmatterObj.raindrop_last_update = (new Date()).toISOString();
@@ -168,11 +169,6 @@ export default class RaindropSync {
168169
});
169170
}
170171

171-
sanitizeTitle(title: string): string {
172-
const santizedTitle = title.replace(/[':#|]/g, "").trim();
173-
return sanitize(santizedTitle).substring(0, 192);
174-
}
175-
176172
private splitFrontmatterAndContent(content: string, fmEndLine: number): {
177173
content: string,
178174
frontmatter: string,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Filename template (<a href="https://mozilla.github.io/nunjucks/">Nunjucks</a>) for
2+
creating synced Raindrop.io highlights & annotations.
3+
4+
<p>
5+
Some notes:
6+
</p>
7+
<ul>
8+
<li>The rendered result is used as the filename for the bookmark.</li>
9+
<li>This template is only used when creating the new file.</li>
10+
<li>The rendered result is sanitized and truncated to 255 bytes.</li>
11+
<li>The plugin will reject the invalid template.</li>
12+
<li>If the file already exists in the vault, the auto generated suffix will be appended to the rendered result and used as the filename.</li>
13+
<li>Available variables to use are the same as the previous template.</li>
14+
</ul>

0 commit comments

Comments
 (0)