Skip to content

Commit

Permalink
v0.0.17
Browse files Browse the repository at this point in the history
  • Loading branch information
kaiiiz committed Mar 21, 2023
2 parents dd1187f + dedad6d commit 52b54d3
Show file tree
Hide file tree
Showing 13 changed files with 238 additions and 174 deletions.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "obsidian-raindrop-highlights",
"name": "Raindrop Highlights",
"version": "0.0.16",
"version": "0.0.17",
"minAppVersion": "0.14.0",
"description": "Sync your Raindrop.io highlights.",
"author": "kaiiiz",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-raindrop-highlights",
"version": "0.0.16",
"version": "0.0.17",
"description": "Sync your Raindrop.io highlights.",
"main": "main.js",
"scripts": {
Expand All @@ -15,6 +15,7 @@
"@tsconfig/svelte": "3.0.0",
"@types/node": "16.11.6",
"@types/nunjucks": "3.2.1",
"@types/truncate-utf8-bytes": "^1.0.0",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
Expand Down
16 changes: 8 additions & 8 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export class RaindropAPI {
async getCollections(): Promise<RaindropCollection[]> {
let res = await this.get(`${BASEURL}/collections`, {});

let collections: RaindropCollection[] = [
const collections: RaindropCollection[] = [
{ id: -1, title: 'Unsorted' },
{ id: -99, title: 'Trash' },
];
Expand Down Expand Up @@ -93,18 +93,18 @@ export class RaindropAPI {

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

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

// get real highlights (raindrop returns only 3 highlights in /raindrops/${collectionId} endpoint)
for (let [idx, bookmark] of bookmarks.entries()) {
for (const [idx, bookmark] of bookmarks.entries()) {
notice.setMessage(`Sync Raindrop bookmarks: ${idx + 1}/${bookmarks.length}`)
if (bookmark.highlights.length == 3) {
let res = await this.get(`${BASEURL}/raindrop/${bookmark.id}`, {});
const res = await this.get(`${BASEURL}/raindrop/${bookmark.id}`, {});
bookmark['highlights'] = this.parseHighlights(res.item.highlights);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/assets/defaultTemplate.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% if is_new_article %}
# Metadata
{% if link %}Source URL:: {{link}}{% endif %}
{% if tags|length %}Topics:: {{ tags | map("#{}") | join(", ") }}{% endif %}
{% if tags|length %}Topics:: #{{ tags | join(", #") }}{% endif %}

---
# {{title}}
Expand Down
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DEFAULT_TEMPLATE from './assets/defaultTemplate.njk';
import type { RaindropPluginSettings } from "./types";

export const VERSION = '0.0.16';
export const VERSION = '0.0.17';

export const DEFAULT_SETTINGS: RaindropPluginSettings = {
version: VERSION,
Expand All @@ -15,6 +15,7 @@ export const DEFAULT_SETTINGS: RaindropPluginSettings = {
syncCollections: {},
template: DEFAULT_TEMPLATE,
metadataTemplate: "",
filenameTemplate: "{{title}}",
dateTimeFormat: 'YYYY/MM/DD HH:mm:ss',
autoSyncInterval: 0,
};
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default class RaindropPlugin extends Plugin {
id: 'raindrop-show-last-sync-time',
name: 'Show last sync time',
callback: async () => {
let message = Object.values(this.settings.syncCollections)
const message = Object.values(this.settings.syncCollections)
.filter((collection: SyncCollection) => collection.sync)
.map((collection: SyncCollection) => {
return `${collection.title}: ${collection.lastSyncDate?.toLocaleString()}`;
Expand Down Expand Up @@ -81,7 +81,7 @@ export default class RaindropPlugin extends Plugin {

async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
for (let id in this.settings.syncCollections) {
for (const id in this.settings.syncCollections) {
const collection = this.settings.syncCollections[id];
if (collection.lastSyncDate) {
collection.lastSyncDate = new Date(collection.lastSyncDate);
Expand Down
20 changes: 17 additions & 3 deletions src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import nunjucks from "nunjucks";
import Moment from "moment";
import type RaindropPlugin from "./main";
import type { BookmarkFileFrontMatter, RaindropBookmark } from "./types";
import { Notice, parseYaml, stringifyYaml } from "obsidian";
import sanitize from "sanitize-filename";
import type { RaindropBookmark } from "./types";
import { parseYaml } from "obsidian";

type RenderHighlight = {
id: string;
Expand Down Expand Up @@ -96,7 +97,11 @@ export default class Renderer {

renderFrontmatter(bookmark: RaindropBookmark, newArticle: boolean) {
const newMdFrontmatter = this.renderTemplate(this.plugin.settings.metadataTemplate, bookmark, newArticle);
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n${newMdFrontmatter}\n`
if (newMdFrontmatter.length > 0) {
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n${newMdFrontmatter}\n`
} else {
return `raindrop_id: ${bookmark.id}\nraindrop_last_update: ${(new Date()).toISOString()}\n`
}
}

renderFullArticle(bookmark: RaindropBookmark) {
Expand All @@ -106,6 +111,15 @@ export default class Renderer {
return mdContent;
}

renderFileName(bookmark: RaindropBookmark, newArticle: boolean) {
const filename = this.renderTemplate(this.plugin.settings.filenameTemplate, bookmark, newArticle);
return this.sanitizeFilename(filename);
}

private sanitizeFilename(filename: string): string {
return sanitize(filename.replace(/[':#|]/g, "").trim());
}

private renderTemplate(template:string, bookmark: RaindropBookmark, newArticle: boolean) {
const dateTimeFormat = this.plugin.settings.dateTimeFormat;

Expand Down
87 changes: 59 additions & 28 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {App, Notice, PluginSettingTab, Setting} from 'obsidian';
import DEFAULT_METADATA_TEMPLATE from './assets/defaultMetadataTemplate.njk';
import templateInstructions from './templates/templateInstructions.html';
import metadataTemplateInstructions from './templates/metadataTemplateInstructions.html';
import filenameTemplateInstructions from './templates/filenameTemplateInstructions.html';
import datetimeInstructions from './templates/datetimeInstructions.html';
import appendModeInstructions from './templates/appendModeInstructions.html';
import type { RaindropAPI } from './api';
Expand Down Expand Up @@ -41,6 +42,7 @@ export class RaindropSettingTab extends PluginSettingTab {
this.dateFormat();
this.template();
this.metadataTemplate();
this.filenameTemplate();
this.resetSyncHistory();
}

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

private appendMode(): void {
const descFragment = document
.createRange()
.createContextualFragment(appendModeInstructions);
.createRange()
.createContextualFragment(appendModeInstructions);

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

private highlightsFolder(): void {
new Setting(this.containerEl)
.setName('Highlights folder location')
.setDesc('Vault folder to use for writing Raindrop.io highlights')
.addDropdown((dropdown) => {
const files = (this.app.vault.adapter as any).files;
Object.keys(files).forEach((key) => {
if (files[key].type == 'folder') {
const folder = files[key].realpath;
dropdown.addOption(folder, folder);
}
})
.setName('Highlights folder location')
.setDesc('Vault folder to use for storing Raindrop.io highlights')
.addDropdown((dropdown) => {
const files = (this.app.vault.adapter as any).files;
Object.keys(files).forEach((key) => {
if (files[key].type == 'folder') {
const folder = files[key].realpath;
dropdown.addOption(folder, folder);
}
})

return dropdown
.setValue(this.plugin.settings.highlightsFolder)
.onChange(async (value) => {
this.plugin.settings.highlightsFolder = value;
await this.plugin.saveSettings();
});
});
}
return dropdown
.setValue(this.plugin.settings.highlightsFolder)
.onChange(async (value) => {
this.plugin.settings.highlightsFolder = value;
await this.plugin.saveSettings();
});
});
}

private async collections(): Promise<void> {
new Setting(this.containerEl)
Expand All @@ -205,11 +207,13 @@ export class RaindropSettingTab extends PluginSettingTab {
.setButtonText('Manage')
.setCta()
.onClick(async () => {
button.setButtonText('Loading collections...');

// update for new collections
const allCollections = await this.api.getCollections();
this.plugin.updateCollectionSettings(allCollections);

const collectionsModal = new CollectionsModal(this.app, this.plugin);
new CollectionsModal(this.app, this.plugin);
this.display(); // rerender
});
});
Expand Down Expand Up @@ -241,6 +245,7 @@ export class RaindropSettingTab extends PluginSettingTab {
return text;
});
}

private async metadataTemplate(): Promise<void> {
const templateDescFragment = document
.createRange()
Expand Down Expand Up @@ -268,19 +273,45 @@ export class RaindropSettingTab extends PluginSettingTab {
return text;
});
}


private async filenameTemplate(): Promise<void> {
const templateDescFragment = document
.createRange()
.createContextualFragment(filenameTemplateInstructions);

new Setting(this.containerEl)
.setName('Filename template')
.setDesc(templateDescFragment)
.addTextArea((text) => {
text.inputEl.style.width = '100%';
text.inputEl.style.height = '250px';
text.inputEl.style.fontSize = '0.8em';
text.setValue(this.plugin.settings.filenameTemplate)
.onChange(async (value) => {
const isValid = this.renderer.validate(value, false);

if (isValid) {
this.plugin.settings.filenameTemplate = value;
await this.plugin.saveSettings();
}

text.inputEl.style.border = isValid ? '' : '1px solid red';
});
return text;
});
}

private resetSyncHistory(): void {
new Setting(this.containerEl)
.setName('Reset sync')
.setDesc('Reset last sync time to resync')
.addButton((button) => {
.setName('Reset sync')
.setDesc('Reset last sync time to resync')
.addButton((button) => {
return button
.setButtonText('Reset')
.setDisabled(!this.plugin.settings.isConnected)
.setWarning()
.onClick(async () => {
for (let id in this.plugin.settings.syncCollections) {
for (const id in this.plugin.settings.syncCollections) {
const collection = this.plugin.settings.syncCollections[id];
collection.lastSyncDate = undefined;
}
Expand All @@ -292,8 +323,8 @@ export class RaindropSettingTab extends PluginSettingTab {

private dateFormat(): void {
const descFragment = document
.createRange()
.createContextualFragment(datetimeInstructions);
.createRange()
.createContextualFragment(datetimeInstructions);

new Setting(this.containerEl)
.setName('Date & time format')
Expand Down
20 changes: 8 additions & 12 deletions src/sync.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { App, Notice, parseYaml, stringifyYaml, TFile } from "obsidian";
import sanitize from "sanitize-filename";
import type { RaindropAPI } from "./api";
import type RaindropPlugin from "./main";
import Renderer from "./renderer";
import truncate from "truncate-utf8-bytes";
import type { BookmarkFile, BookmarkFileFrontMatter, RaindropBookmark, RaindropCollection, SyncCollection } from "./types";

export default class RaindropSync {
Expand Down Expand Up @@ -55,7 +55,7 @@ export default class RaindropSync {
if (bookmarks.length == 0) return;

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

for (let bookmark of bookmarks) {
for (const bookmark of bookmarks) {
if (this.plugin.settings.onlyBookmarksWithHl && bookmark.highlights.length == 0) {
continue;
}

if (bookmark.id in bookmarkFilesMap) {
await this.updateFile(bookmarkFilesMap[bookmark.id], bookmark);
} else {
let fileName = `${this.sanitizeTitle(bookmark.title)}.md`;
const renderedFilename = this.renderer.renderFileName(bookmark, true);
let fileName = truncate(`${renderedFilename}`, 252) + ".md";
let filePath = `${folderPath}/${fileName}`;
let suffix = 1;
while (await this.app.vault.adapter.exists(filePath)) {
console.debug(`${filePath} alreay exists`);
fileName = `${this.sanitizeTitle(bookmark.title)} (${suffix++}).md`;
const fileSuffix = ` (${suffix++}).md`;
fileName = truncate(`${renderedFilename}`, 255 - fileSuffix.length) + fileSuffix;
filePath = `${folderPath}/${fileName}`;
}
bookmarkFilesMap[bookmark.id] = await this.createFile(filePath, bookmark);
Expand Down Expand Up @@ -130,8 +132,7 @@ export default class RaindropSync {
if (metadata?.frontmatter) {
// separate content and front matter
const fileContent = await this.app.vault.cachedRead(file);
const {position: {start, end}} = metadata.frontmatter;
const article = this.splitFrontmatterAndContent(fileContent, end.line);
const article = this.splitFrontmatterAndContent(fileContent, metadata.frontmatter.position.end.line);

const frontmatterObj: BookmarkFileFrontMatter = parseYaml(article.frontmatter);
frontmatterObj.raindrop_last_update = (new Date()).toISOString();
Expand Down Expand Up @@ -168,11 +169,6 @@ export default class RaindropSync {
});
}

sanitizeTitle(title: string): string {
const santizedTitle = title.replace(/[':#|]/g, "").trim();
return sanitize(santizedTitle).substring(0, 192);
}

private splitFrontmatterAndContent(content: string, fmEndLine: number): {
content: string,
frontmatter: string,
Expand Down
14 changes: 14 additions & 0 deletions src/templates/filenameTemplateInstructions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Filename template (<a href="https://mozilla.github.io/nunjucks/">Nunjucks</a>) for
creating synced Raindrop.io highlights & annotations.

<p>
Some notes:
</p>
<ul>
<li>The rendered result is used as the filename for the bookmark.</li>
<li>This template is only used when creating the new file.</li>
<li>The rendered result is sanitized and truncated to 255 bytes.</li>
<li>The plugin will reject the invalid template.</li>
<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>
<li>Available variables to use are the same as the previous template.</li>
</ul>
Loading

0 comments on commit 52b54d3

Please sign in to comment.