From d93b7f4018cfe02750d5650a5f99e1c9d064e4b6 Mon Sep 17 00:00:00 2001 From: Johannes Theiner Date: Wed, 5 Jan 2022 17:30:58 +0100 Subject: [PATCH] + Highlights in article view, with extraction(closes #60) + a new style for the article list(references #54) ~ ignoring of folders, feeds and tags in filters(closes #58) ~ fixing an issue where filters would not work correctly(closes #63) --- README.md | 3 +- manifest.json | 2 +- package.json | 2 +- src/functions.ts | 53 +++++++-- src/l10n/locales/de.ts | 13 ++- src/l10n/locales/en.ts | 7 +- src/main.ts | 143 +++++++++++++++-------- src/modals/FilteredFolderModal.ts | 167 ++++++++++++++++++++++++++- src/modals/ItemModal.ts | 185 +++++++++++++++++------------- src/parser/rssParser.ts | 5 +- src/settings/FilterSettings.ts | 8 +- src/settings/SettingsTab.ts | 6 +- src/settings/settings.ts | 10 +- src/view/FolderView.svelte | 3 + src/view/IconComponent.svelte | 1 + src/view/ItemView.svelte | 8 +- src/view/TopRowButtons.svelte | 21 ++++ styles.css | 14 ++- versions.json | 3 +- 19 files changed, 503 insertions(+), 151 deletions(-) create mode 100644 src/view/TopRowButtons.svelte diff --git a/README.md b/README.md index b1a19c1..47b42e2 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,8 @@ Plugin for [Obsidian](https://obsidian.md) - `{{tags}}` - tags, seperated by comma, you can also specify a seperator like this: `{{tags:;}}` - `{{#tags}}` - tags with #, seperated by comma, with #, you can also specify a seperator like this: `{{#tags:;}}` - `{{media}}` link to media -- `{{highlights}}` - list of highlights, specify a style with the following syntax `{{hightlights:-$\n}}` +- `{{highlights}}` - list of highlights, you can also specify a custom style, this example creates a [admonition](https://github.com/valentine195/obsidian-admonition) for each highlight: + ![](https://i.joethei.space/obsidian-rss-highlight-syntax.png) ## Styling If you want to style the plugin differently you can use the following css classes diff --git a/manifest.json b/manifest.json index d2645bf..4a7d052 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "rss-reader", "name": "RSS Reader", - "version": "0.9.3", + "version": "1.0.0", "minAppVersion": "0.12.17", "description": "Read RSS Feeds from within obsidian", "author": "Johannes Theiner", diff --git a/package.json b/package.json index 3d8b60f..ae8afee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rss-reader", - "version": "0.9.3", + "version": "1.0.0", "description": "Read RSS Feeds from inside obsidian", "main": "main.js", "scripts": { diff --git a/src/functions.ts b/src/functions.ts index 5869daa..bbf7627 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -129,23 +129,38 @@ function applyTemplate(item: RssFeedItem, template: string, settings: RssReaderS return item.tags.map(i => '#' + i).join(separator); }); - result = result.replace(/{{tags}}/, item.tags.join(", ")); - result = result.replace(/{{#tags}}/, item.tags.map(i => '#' + i).join(", ")); + result = result.replace(/{{tags}}/g, item.tags.join(", ")); + result = result.replace(/{{#tags}}/g, item.tags.map(i => '#' + i).join(", ")); - result = result.replace(/{{highlights}}/, item.highlights.map(value => "- " + htmlToMarkdown(value)).join("\n")); - result = result.replace(/({{highlights:).*(}})/g, function (k) { - const value = k.split(":")[1]; - const separator = value.substring(0, value.indexOf("}")); - return item.highlights.map(i => '' + i).join(separator); - }); + result = result.replace(/{{highlights}}/g, item.highlights.map(value => { + //remove all - from the start of a highlight + return "- " + htmlToMarkdown(removeFormatting(value).replace(/^(-+)/, "")) + }).join("\n")); + + result = result.replace(/({{highlights:)[\s\S][^}]*(}})/g, function (k) { + const value = k.split(/(:[\s\S]?)/); + const tmp = value.slice(1).join(""); + const template = tmp.substring(1, tmp.indexOf("}")); + return item.highlights.map(i => { + return template.replace(/%%highlight%%/g, htmlToMarkdown(removeFormatting(i)).replace(/^(-+)/, "")); + }).join(""); + }); + if(filename) { result = result.replace(/{{filename}}/g, filename); } let content = htmlToMarkdown(item.content); + + item.highlights.forEach(highlight => { + const mdHighlight = htmlToMarkdown(highlight); + content = content.replace(mdHighlight, "==" + mdHighlight + "=="); + }); + + /* fixes #48 replacing $ with $$$, because that is a special regex character: @@ -154,11 +169,33 @@ function applyTemplate(item: RssFeedItem, template: string, settings: RssReaderS */ content = content.replace(/\$/g, "$$$"); + result = result.replace(/{{content}}/g, content); return result; } +function removeFormatting(html: string) : string { + const doc = new DOMParser().parseFromString(html, 'text/html'); + const elements = doc.querySelectorAll("html body a"); + for (let i = 0; i < elements.length; i++) { + const el = elements.item(i) as HTMLAnchorElement; + if(el.dataset) { + Object.keys(el.dataset).forEach(key => { + delete el.dataset[key]; + }); + } + } + + const objects = doc.querySelectorAll("object"); + for (let i = 0; i { - const input = new TextInputPrompt(this.app, "URL", "URL", "", "", t("open")); - await input.openAndGetValue(async (text) => { - const items = await getFeedItems({name: "", folder: "", url: text.getValue()}); - if(!items || items.items.length === 0) { - input.setValidationError(text, t("invalid_feed")); - return; - } - - input.close(); - new ArticleSuggestModal(this, items.items).open(); - }); + const input = new TextInputPrompt(this.app, "URL", "URL", "", "", t("open")); + await input.openAndGetValue(async (text) => { + const items = await getFeedItems({name: "", folder: "", url: text.getValue()}); + if (!items || items.items.length === 0) { + input.setValidationError(text, t("invalid_feed")); + return; + } + + input.close(); + new ArticleSuggestModal(this, items.items).open(); + }); } }); @@ -141,7 +141,6 @@ export default class RssReaderPlugin extends Plugin { await this.updateFeeds(); - feedsStore.subscribe((feeds: RssFeedContent[]) => { //keep sorted store sorted when the items change. const sorted = groupBy(feeds, "folder"); @@ -157,13 +156,13 @@ export default class RssReaderPlugin extends Plugin { //collect all tags for auto completion const tags: string[] = []; for (const item of items) { - if(item !== undefined) + if (item !== undefined) tags.push(...item.tags); } //@ts-ignore const fileTags = this.app.metadataCache.getTags(); - for(const tag of Object.keys(fileTags)) { + for (const tag of Object.keys(fileTags)) { tags.push(tag.replace('#', '')); } tagsStore.update(() => new Set(tags.filter(tag => tag.length > 0))); @@ -171,7 +170,7 @@ export default class RssReaderPlugin extends Plugin { //collect all folders for auto-completion const folders: string[] = []; for (const item of items) { - if(item !== undefined) + if (item !== undefined) folders.push(item.folder); } folderStore.update(() => new Set(folders.filter(folder => folder.length > 0))); @@ -187,28 +186,62 @@ export default class RssReaderPlugin extends Plugin { // @ts-ignore const sortOrder = SortOrder[filter.sortOrder]; let filteredItems: RssFeedItem[]; - filteredItems = items.filter((item) => { - return item.read === filter.read || item.read !== filter.unread; - }); - if(filter.favorites) { - filteredItems = filteredItems.filter((item) => { + + if (filter.read && filter.unread) { + filteredItems = items.filter((item) => { + return item.read === filter.read || item.read !== filter.unread; + }); + } else if (filter.read) { + filteredItems = items.filter((item) => { + return item.read; + }); + } else if (filter.unread) { + filteredItems = items.filter((item) => { + return !item.read; + }); + } + + + if (filter.favorites) { + filteredItems = filteredItems.filter((item) => { return item.favorite === filter.favorites; }); } - if(filter.filterFolders.length > 0) { + + if (filter.filterFolders.length > 0) { + filteredItems = filteredItems.filter((item) => { + return filter.filterFolders.includes(item.folder); + }); + } + if (filter.ignoreFolders.length > 0) { filteredItems = filteredItems.filter((item) => { - filter.filterFolders.includes(item.folder); + return !filter.ignoreFolders.includes(item.folder); }); } - if(filter.filterFeeds.length > 0) { + + if (filter.filterFeeds.length > 0) { filteredItems = filteredItems.filter((item) => { - filter.filterFeeds.includes(item.feed); + return filter.filterFeeds.includes(item.feed); }); } - if(filter.filterTags.length > 0) { + if (filter.ignoreFeeds.length > 0) { + filteredItems = filteredItems.filter((item) => { + return !filter.ignoreFeeds.includes(item.feed); + }); + } + + if (filter.filterTags.length > 0) { filteredItems = filteredItems.filter((item) => { for (const tag of filter.filterTags) { - if(!item.tags.contains(tag)) return false; + if (!item.tags.contains(tag)) return false; + } + return true; + }); + } + if (filter.ignoreTags.length > 0) { + filteredItems = filteredItems.filter((item) => { + for (const tag of filter.ignoreTags) { + if (item.tags.contains(tag)) return false; } return true; }); @@ -221,7 +254,7 @@ export default class RssReaderPlugin extends Plugin { } sortItems(items: RssFeedItem[], sortOrder: SortOrder): RssFeedItem[] { - if(!items) return items; + if (!items) return items; if (sortOrder === SortOrder.ALPHABET_NORMAL) { return items.sort((a, b) => a.title.localeCompare(b.title)); } @@ -275,27 +308,27 @@ export default class RssReaderPlugin extends Plugin { let result: RssFeedContent[] = []; for (const feed of this.settings.feeds) { const items = await getFeedItems(feed); - if(items) + if (items) result.push(items); } const items = this.settings.items; for (const feed of items) { - if(feed.hash === undefined || feed.hash === "") { + if (feed.hash === undefined || feed.hash === "") { feed.hash = new Md5().appendStr(feed.name).appendStr(feed.folder).end(); } for (const item of feed.items) { - if(item.folder !== feed.folder || item.feed !== feed.name) { + if (item.folder !== feed.folder || item.feed !== feed.name) { feed.items.remove(item); } - if(item.hash === undefined) { + if (item.hash === undefined) { item.hash = new Md5().appendStr(item.title).appendStr(item.folder).appendStr(item.link).end(); } } } result = mergeWith(result, items, customizer); - new Notice(t("refreshed_feeds")); + new Notice(t("refreshed_feeds")); await this.writeFeedContent(() => result); } @@ -319,8 +352,25 @@ export default class RssReaderPlugin extends Plugin { const configPath = this.app.vault.configDir + "/plugins/rss-reader/data.json"; const config = JSON.parse(await this.app.vault.adapter.read(configPath)); - if(config.filtered.length === 0) return; - if(config.filtered[0].filterType === undefined) return; + if (config.filtered.length === 0) return; + + if (config.filtered[0].ignoreFolders === undefined) { + new Notice("RSS Reader: migrating data"); + console.log("RSS Reader: adding ignored fields to filters"); + + for (const filter of config.filtered) { + filter.ignoreTags = []; + filter.ignoreFolders = []; + filter.ignoreFeeds = []; + + } + await this.app.vault.adapter.write(configPath, JSON.stringify(config)); + await this.loadSettings(); + new Notice("RSS Reader: data has been migrated"); + } + + if (config.filtered[0].filterType === undefined) return; + new Notice("RSS Reader: migrating data"); @@ -333,18 +383,21 @@ export default class RssReaderPlugin extends Plugin { read: false, unread: false, sortOrder: filter.sortOrder, - name: filter.name + name: filter.name, + ignoreFolders: [], + ignoreFeeds: [], + ignoreTags: [], }; - if(filter.filterType === "FAVORITES") newFilter.favorites = true; - if(filter.filterType === "READ") newFilter.read = true; - if(filter.filterType === "UNREAD") newFilter.unread = true; - if(filter.filterType === "TAGS") { - if(filter.filterContent !== "") { + if (filter.filterType === "FAVORITES") newFilter.favorites = true; + if (filter.filterType === "READ") newFilter.read = true; + if (filter.filterType === "UNREAD") newFilter.unread = true; + if (filter.filterType === "TAGS") { + if (filter.filterContent !== "") { newFilter.filterTags = filter.filterContent.split(","); } - }else { - if(filter.filterContent !== "") { + } else { + if (filter.filterContent !== "") { newFilter.filterFolders = filter.filterContent.split(","); } } @@ -402,7 +455,7 @@ export default class RssReaderPlugin extends Plugin { console.error(e); } - if(file !== undefined) { + if (file !== undefined) { try { JSON.parse(file); } catch (e) { @@ -415,7 +468,7 @@ export default class RssReaderPlugin extends Plugin { const data = await this.loadData(); this.settings = Object.assign({}, DEFAULT_SETTINGS, data); - if(data !== undefined && data !== null) { + if (data !== undefined && data !== null) { this.settings.hotkeys = Object.assign({}, DEFAULT_SETTINGS.hotkeys, data.hotkeys); } settingsStore.set(this.settings); diff --git a/src/modals/FilteredFolderModal.ts b/src/modals/FilteredFolderModal.ts index adf32b5..bc73a1f 100644 --- a/src/modals/FilteredFolderModal.ts +++ b/src/modals/FilteredFolderModal.ts @@ -23,8 +23,11 @@ export enum SortOrder { export interface FilteredFolder{ name: string; filterTags: string[]; + ignoreTags: string[]; filterFolders: string[]; + ignoreFolders: string[]; filterFeeds: string[]; + ignoreFeeds: string[]; sortOrder: string; read: boolean; unread: boolean; @@ -35,9 +38,12 @@ export class FilteredFolderModal extends BaseModal { plugin: RssReaderPlugin; name: string; - filterFolders: string[] = []; filterTags: string[] = []; + ignoreTags: string[] = []; + filterFolders: string[] = []; + ignoreFolders: string[] = []; filterFeeds: string[] = []; + ignoreFeeds: string[] = []; sortOrder: string; read: boolean; unread: boolean; @@ -53,8 +59,11 @@ export class FilteredFolderModal extends BaseModal { this.name = folder.name; this.sortOrder = folder.sortOrder; this.filterTags = folder.filterTags; + this.ignoreTags = folder.ignoreTags; this.filterFolders = folder.filterFolders; + this.ignoreFolders = folder.ignoreFolders; this.filterFeeds = folder.filterFeeds; + this.ignoreFeeds = folder.ignoreFeeds; this.read = folder.read; this.unread = folder.unread; this.favorites = folder.favorites; @@ -176,6 +185,52 @@ export class FilteredFolderModal extends BaseModal { }); newFolder.controlEl.addClass("rss-setting-input"); + foldersDiv.createEl("p", {text: t("filter_folder_ignore_help")}); + + //ignore folders + for (const folder in this.ignoreFolders) { + new Setting(foldersDiv) + .addSearch(async (search: SearchComponent) => { + new ArraySuggest(this.app, search.inputEl, get(folderStore)); + search + .setValue(this.ignoreFolders[folder]) + .onChange(async (value: string) => { + this.removeValidationError(search); + this.ignoreFolders = this.ignoreFolders.filter(e => e !== this.ignoreFolders[folder]); + this.ignoreFolders.push(value); + }); + }) + .addExtraButton((button) => { + button + .setTooltip(t("delete")) + .setIcon("feather-trash") + .onClick(() => { + this.ignoreFolders = this.ignoreFolders.filter(e => e !== this.ignoreFolders[folder]); + this.display(); + }); + + }); + } + + let folderIgnoreValue = ""; + const newIgnoreFolder = new Setting(foldersDiv) + .addSearch(async (search: SearchComponent) => { + new ArraySuggest(this.app, search.inputEl, get(folderStore)); + search + .onChange(async (value: string) => { + folderIgnoreValue = value; + }); + }).addExtraButton(button => { + button + .setTooltip(t("add")) + .setIcon("feather-plus") + .onClick(() => { + this.ignoreFolders.push(folderIgnoreValue); + this.display(); + }); + }); + newIgnoreFolder.controlEl.addClass("rss-setting-input"); + //feeds const feedsDiv = contentEl.createDiv("feeds"); @@ -231,6 +286,52 @@ export class FilteredFolderModal extends BaseModal { }); newFeed.controlEl.addClass("rss-setting-input"); + feedsDiv.createEl("p", {text: t("filter_feed_ignore_help")}); + + //ignore feeds + for (const folder in this.ignoreFeeds) { + new Setting(feedsDiv) + .addSearch(async (search: SearchComponent) => { + new ArraySuggest(this.app, search.inputEl, new Set(feeds)); + search + .setValue(this.ignoreFeeds[folder]) + .onChange(async (value: string) => { + this.removeValidationError(search); + this.ignoreFeeds = this.ignoreFeeds.filter(e => e !== this.ignoreFeeds[folder]); + this.ignoreFeeds.push(value); + }); + }) + .addExtraButton((button) => { + button + .setTooltip(t("delete")) + .setIcon("feather-trash") + .onClick(() => { + this.ignoreFeeds = this.ignoreFeeds.filter(e => e !== this.ignoreFeeds[folder]); + this.display(); + }); + + }); + } + + let feedIgnoreValue = ""; + const newIgnoreFeed = new Setting(feedsDiv) + .addSearch(async (search: SearchComponent) => { + new ArraySuggest(this.app, search.inputEl, new Set(feeds)); + search + .onChange(async (value: string) => { + feedIgnoreValue = value; + }); + }).addExtraButton(button => { + button + .setTooltip(t("add")) + .setIcon("feather-plus") + .onClick(() => { + this.ignoreFeeds.push(feedIgnoreValue); + this.display(); + }); + }); + newIgnoreFeed.controlEl.addClass("rss-setting-input"); + //tags const tagDiv = contentEl.createDiv("tags"); tagDiv.createEl("h2", {text: t("tags")}); @@ -293,6 +394,68 @@ export class FilteredFolderModal extends BaseModal { }); newTag.controlEl.addClass("rss-setting-input"); + tagDiv.createEl("p", {text: t("filter_tags_ignore_help")}); + + for (const tag in this.ignoreTags) { + new Setting(tagDiv) + .addSearch(async (search: SearchComponent) => { + new ArraySuggest(this.app, search.inputEl, get(tagsStore)); + search + .setValue(this.ignoreTags[tag]) + .onChange(async (value: string) => { + this.removeValidationError(search); + if (!value.match(TAG_REGEX) || value.match(NUMBER_REGEX) || value.contains(" ") || value.contains('#')) { + this.setValidationError(search, t("invalid_tag")); + return; + } + this.ignoreTags = this.ignoreTags.filter(e => e !== this.ignoreTags[tag]); + this.ignoreTags.push(value); + }); + }) + .addExtraButton((button) => { + button + .setTooltip(t("delete")) + .setIcon("feather-trash") + .onClick(() => { + this.ignoreTags = this.ignoreTags.filter(e => e !== this.ignoreTags[tag]); + this.display(); + }); + + }); + } + + let ignoreTagValue = ""; + let ignoreTagComponent: SearchComponent; + const newTagIgnore = new Setting(tagDiv) + .addSearch(async (search: SearchComponent) => { + ignoreTagComponent = search; + new ArraySuggest(this.app, search.inputEl, get(tagsStore)); + search + .onChange(async (value: string) => { + if (!value.match(TAG_REGEX) || value.match(NUMBER_REGEX) || value.contains(" ") || value.contains('#')) { + this.setValidationError(search, t("invalid_tag")); + return; + } + ignoreTagValue = value; + }); + }).addExtraButton(button => { + button + .setTooltip(t("add")) + .setIcon("feather-plus") + .onClick(() => { + if (!ignoreTagValue.match(TAG_REGEX) || ignoreTagValue.match(NUMBER_REGEX) || ignoreTagValue.contains(" ") || ignoreTagValue.contains('#')) { + this.setValidationError(ignoreTagComponent, t("invalid_tag")); + return; + } + this.ignoreTags.push(ignoreTagValue); + this.display(); + }); + }); + newTagIgnore.controlEl.addClass("rss-setting-input"); + + + //save & cancel + const footerEl = contentEl.createDiv(); const footerButtons = new Setting(footerEl); footerButtons.addButton((b) => { @@ -325,6 +488,8 @@ export class FilteredFolderModal extends BaseModal { }); } + + async onOpen() : Promise { await this.display(); } diff --git a/src/modals/ItemModal.ts b/src/modals/ItemModal.ts index e7fa6b5..a8d7b06 100644 --- a/src/modals/ItemModal.ts +++ b/src/modals/ItemModal.ts @@ -22,7 +22,7 @@ export class ItemModal extends Modal { private readButton: ButtonComponent; private favoriteButton: ButtonComponent; - constructor(plugin: RssReaderPlugin, item: RssFeedItem, items: RssFeedItem[], save= true) { + constructor(plugin: RssReaderPlugin, item: RssFeedItem, items: RssFeedItem[], save = true) { super(plugin.app); this.plugin = plugin; this.items = items; @@ -30,7 +30,7 @@ export class ItemModal extends Modal { this.save = save; - if(this.save) { + if (this.save) { this.item.read = true; const feedContents = this.plugin.settings.items; @@ -38,7 +38,7 @@ export class ItemModal extends Modal { return feedContents; }); - if(!this.plugin.settings) { + if (!this.plugin.settings) { return; } @@ -85,12 +85,12 @@ export class ItemModal extends Modal { }); } - if(this.plugin.settings.hotkeys.next) { + if (this.plugin.settings.hotkeys.next) { this.scope.register([], this.plugin.settings.hotkeys.next, () => { this.next(); }); } - if(this.plugin.settings.hotkeys.previous) { + if (this.plugin.settings.hotkeys.previous) { this.scope.register([], this.plugin.settings.hotkeys.previous, () => { this.previous(); }); @@ -116,25 +116,25 @@ export class ItemModal extends Modal { } } - previous() : void { + previous(): void { let index = this.items.findIndex((item) => { return item === this.item; }); index++; const item = this.items[index]; - if(item !== undefined) { + if (item !== undefined) { this.close(); new ItemModal(this.plugin, item, this.items, this.save).open(); } } - next() : void { + next(): void { let index = this.items.findIndex((item) => { return item === this.item; }); index--; const item = this.items[index]; - if(item !== undefined) { + if (item !== undefined) { this.close(); new ItemModal(this.plugin, item, this.items, this.save).open(); } @@ -165,7 +165,7 @@ export class ItemModal extends Modal { let actions = Array.of(Action.CREATE_NOTE, Action.PASTE, Action.COPY, Action.OPEN); - if(this.save) { + if (this.save) { this.readButton = new ButtonComponent(topButtons) .setIcon(this.item.read ? 'feather-eye-off' : 'feather-eye') .setTooltip(this.item.read ? t("mark_as_unread") : t("mark_as_read")) @@ -215,7 +215,7 @@ export class ItemModal extends Modal { .setIcon("left-arrow-with-tail") .setTooltip(t("previous")) .onClick(() => { - this.previous(); + this.previous(); }); prevButton.buttonEl.addClass("rss-button"); @@ -276,100 +276,113 @@ export class ItemModal extends Modal { if (this.item.content) { await MarkdownRenderer.renderMarkdown(htmlToMarkdown(this.item.content), content, "", this.plugin); - /*this.item.highlights.forEach(highlight => { + this.item.highlights.forEach(highlight => { if (content.innerHTML.includes(highlight)) { const newNode = contentEl.createEl("mark"); newNode.innerHTML = highlight; content.innerHTML = content.innerHTML.replace(highlight, newNode.outerHTML); newNode.remove(); + } else { + console.log("Highlight not included"); + console.log(highlight); } }); content.addEventListener('contextmenu', (event) => { event.preventDefault(); + + const selection = document.getSelection(); + const range = selection.getRangeAt(0); + + const div = contentEl.createDiv(); + const htmlContent = range.cloneContents(); + const html = htmlContent.cloneNode(true); + div.appendChild(html); + const selected = div.innerHTML; + div.remove(); + const menu = new Menu(this.app); - menu - .addItem(item => { - item - .setIcon("documents") - .setTitle(t("copy_to_clipboard")) - .onClick(async () => { - await copy(document.getSelection().toString()); - }); - }).addItem(item => { + + let previousHighlight: HTMLElement; + if (this.item.highlights.includes(range.startContainer.parentElement.innerHTML)) { + previousHighlight = range.startContainer.parentElement; + } + if (this.item.highlights.includes(range.startContainer.parentElement.parentElement.innerHTML)) { + previousHighlight = range.startContainer.parentElement.parentElement; + } + + if(previousHighlight) { + menu.addItem(item => { item .setIcon("highlight-glyph") - .setTitle("Highlight") + .setTitle(t("highlight_remove")) .onClick(async () => { - const selection = document.getSelection(); - const range = selection.getRangeAt(0); - - if(selection.getRangeAt) { - const div = contentEl.createDiv(); - const htmlContent = range.cloneContents(); - const html = htmlContent.cloneNode(true); - html.childNodes.forEach(item => { - if(item.hasChildNodes()) { - item.childNodes.forEach(child => { - if(child.nodeType !== 3) {//if not text node - - //get text content - const tmpEl = contentEl.createDiv(); - tmpEl.style.display = "hidden"; - const childCopy = child.cloneNode(true); - tmpEl.appendChild(childCopy); - const tmp = tmpEl.innerHTML; - if(tmp.startsWith(" { + return feedContents; + }); }); }); - - //@ts-ignore - if (this.app.plugins.plugins["obsidian-tts"]) { + }else if(!this.item.highlights.includes(selected) && selected.length > 0) { menu.addItem(item => { - item - .setIcon("feather-headphones") - .setTitle(t("read_article_tts")) - .onClick(() => { - //@ts-ignore - const tts = this.app.plugins.plugins["obsidian-tts"].ttsService; - tts.say("", document.getSelection().toString()); - }); + item + .setIcon("highlight-glyph") + .setTitle(t("highlight")) + .onClick(async () => { + const newNode = contentEl.createEl("mark"); + newNode.innerHTML = selected; + range.deleteContents(); + range.insertNode(newNode); + this.item.highlights.push(selected); + + const feedContents = this.plugin.settings.items; + await this.plugin.writeFeedContent(() => { + return feedContents; + }); + + //cleaning up twice to remove nested elements + this.removeDanglingElements(contentEl); + this.removeDanglingElements(contentEl); + }); }); } + if(selected.length > 0) { + menu + .addItem(item => { + item + .setIcon("documents") + .setTitle(t("copy_to_clipboard")) + .onClick(async () => { + await copy(selection.toString()); + }); + }); + //@ts-ignore + if (this.app.plugins.plugins["obsidian-tts"]) { + menu.addItem(item => { + item + .setIcon("feather-headphones") + .setTitle(t("read_article_tts")) + .onClick(() => { + //@ts-ignore + const tts = this.app.plugins.plugins["obsidian-tts"].ttsService; + tts.say("", selection.toString()); + }); + }); + } + } menu.showAtMouseEvent(event); - });*/ + }); } } - async onClose() : Promise { + async onClose(): Promise { const {contentEl} = this; contentEl.empty(); @@ -382,4 +395,16 @@ export class ItemModal extends Modal { async onOpen(): Promise { await this.display(); } + + removeDanglingElements(el: HTMLElement) : void { + //remove all dangling elements + const lists = el.querySelectorAll('li, a, div, p, span'); + for (let i = 0; i < lists.length; i++) { + const listEL = lists.item(i); + if(listEL.innerHTML === '') { + listEL.remove(); + } + } + } + } diff --git a/src/parser/rssParser.ts b/src/parser/rssParser.ts index a5fa3e9..8571285 100644 --- a/src/parser/rssParser.ts +++ b/src/parser/rssParser.ts @@ -103,7 +103,6 @@ function getContent(element: Element | Document, names: string[]): string { if (data.nodeName === elementName) { //@ts-ignore const tmp = data.getAttr(attr); - console.log(tmp); if (tmp.length > 0) { value = tmp; } @@ -201,6 +200,10 @@ export async function getFeedItems(feed: RssFeed): Promise { item.language = language; item.hash = new Md5().appendStr(item.title).appendStr(item.folder).appendStr(item.link).end(); + if(!item.image && feed.url.contains("youtube.com/feeds")) { + item.image = "https://i3.ytimg.com/vi/" + item.id.split(":")[2] + "/hqdefault.jpg"; + } + items.push(item); } }) diff --git a/src/settings/FilterSettings.ts b/src/settings/FilterSettings.ts index 6f79e02..e75f2db 100644 --- a/src/settings/FilterSettings.ts +++ b/src/settings/FilterSettings.ts @@ -3,7 +3,7 @@ import {ButtonComponent, Notice, Setting} from "obsidian"; import {FilteredFolderModal} from "../modals/FilteredFolderModal"; import RssReaderPlugin from "../main"; -export function displayFilterSettings(plugin: RssReaderPlugin, containerEl: HTMLElement) { +export function displayFilterSettings(plugin: RssReaderPlugin, containerEl: HTMLElement) : void { containerEl.empty(); @@ -34,6 +34,9 @@ export function displayFilterSettings(plugin: RssReaderPlugin, containerEl: HTML filterFolders: modal.filterFolders, filterTags: modal.filterTags, favorites: modal.favorites, + ignoreFolders: modal.ignoreFolders, + ignoreFeeds: modal.ignoreFeeds, + ignoreTags: modal.ignoreTags, read: modal.read, unread: modal.unread, } @@ -102,6 +105,9 @@ export function displayFilterSettings(plugin: RssReaderPlugin, containerEl: HTML filterFeeds: modal.filterFeeds, filterFolders: modal.filterFolders, filterTags: modal.filterTags, + ignoreFolders: modal.ignoreFolders, + ignoreFeeds: modal.ignoreFeeds, + ignoreTags: modal.ignoreTags, favorites: modal.favorites, read: modal.read, unread: modal.unread, diff --git a/src/settings/SettingsTab.ts b/src/settings/SettingsTab.ts index 75d286e..c520ef5 100644 --- a/src/settings/SettingsTab.ts +++ b/src/settings/SettingsTab.ts @@ -1,6 +1,5 @@ import { App, - ButtonComponent, DropdownComponent, MomentFormatComponent, Notice, @@ -14,7 +13,6 @@ import { import RssReaderPlugin from "../main"; import t from "../l10n/locale"; import {FolderSuggest} from "./FolderSuggestor"; -import {FilteredFolderModal} from "../modals/FilteredFolderModal"; import {displayFeedSettings} from "./FeedSettings"; import {DEFAULT_SETTINGS} from "./settings"; import {displayHotkeys} from "./HotkeySettings"; @@ -251,7 +249,7 @@ export class RSSReaderSettingsTab extends PluginSettingTab { }); }); - /*new Setting(containerEl) + new Setting(containerEl) .setName(t("display_style")) .addDropdown(dropdown => { return dropdown @@ -263,7 +261,7 @@ export class RSSReaderSettingsTab extends PluginSettingTab { displayStyle: value })); }); - });*/ + }); containerEl.createEl("h2", {text: t("content")}); diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 49bee47..0115fe9 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -51,11 +51,14 @@ export const DEFAULT_SETTINGS: RssReaderSettings = Object.freeze({ filterTags: [], filterFolders: [], filterFeeds: [], + ignoreTags: [], + ignoreFeeds: [], + ignoreFolders: [], favorites: true, sortOrder: "ALPHABET_NORMAL" }], saveLocation: 'default', - displayStyle: 'list', + displayStyle: 'cards', saveLocationFolder: '', items: [], dateFormat: "YYYY-MM-DDTHH:mm:SS", @@ -65,7 +68,10 @@ export const DEFAULT_SETTINGS: RssReaderSettings = Object.freeze({ "published: {{published}}\n" + "tags: [{{tags:,}}]\n" + "---\n" + - "{{title}}\n" + + "# Highlights\n" + + "{{highlights}}\n\n" + + "---\n" + + "# {{title}}\n" + "{{content}}", pasteTemplate: "## {{title}}\n" + "{{content}}", diff --git a/src/view/FolderView.svelte b/src/view/FolderView.svelte index 081d2d8..a9d5eb9 100644 --- a/src/view/FolderView.svelte +++ b/src/view/FolderView.svelte @@ -8,6 +8,7 @@ import Action from "../actions/Action"; import {RssFeedItem} from "../parser/rssParser"; import t from "../l10n/locale"; + import TopRowButtons from "./TopRowButtons.svelte"; export let plugin: RssReaderPlugin; @@ -91,6 +92,8 @@ + + {#if !folded}

Loading

{:else} diff --git a/src/view/IconComponent.svelte b/src/view/IconComponent.svelte index b99f79e..67fabee 100644 --- a/src/view/IconComponent.svelte +++ b/src/view/IconComponent.svelte @@ -5,6 +5,7 @@ export let iconName: String = ""; const icon = (node: HTMLElement, icon: string) => { + setIcon(node, icon); } diff --git a/src/view/ItemView.svelte b/src/view/ItemView.svelte index 323afe5..c72b055 100644 --- a/src/view/ItemView.svelte +++ b/src/view/ItemView.svelte @@ -4,7 +4,6 @@ import HtmlTooltip from "./HtmlTooltip.svelte"; import ItemTitle from "./ItemTitle.svelte"; import {settingsStore} from "../stores"; - import {htmlToMarkdown, MarkdownRenderer} from "obsidian"; import MarkdownContent from "./MarkdownContent.svelte"; export let plugin: RssReaderPlugin = null; @@ -47,6 +46,13 @@ on:mouseover={toggleHover} on:mouseleave={toggleHover} on:focus={toggleHover}/> + {#if item.tags.length > 0} + + {#each item.tags as tag} +  {tag} + {/each} + + {/if} diff --git a/src/view/TopRowButtons.svelte b/src/view/TopRowButtons.svelte new file mode 100644 index 0000000..8176668 --- /dev/null +++ b/src/view/TopRowButtons.svelte @@ -0,0 +1,21 @@ + + + diff --git a/styles.css b/styles.css index f8300e5..ed59cd0 100644 --- a/styles.css +++ b/styles.css @@ -112,6 +112,16 @@ input.is-invalid { background: var(--background-secondary); } -mark li { - background: var(--text-highlight-bg); +/*making sure both highlight styles look consistent*/ +.rss-modal li mark { + background-color: var(--text-highlight-bg); +} + + +.rss-modal mark li { + background-color: var(--text-highlight-bg); +} + +.rss-modal mark { + background-color: var(--text-highlight-bg); } diff --git a/versions.json b/versions.json index 1474fc8..7772cc3 100644 --- a/versions.json +++ b/versions.json @@ -18,5 +18,6 @@ "0.9.0": "0.12.19", "0.9.1": "0.12.19", "0.9.2": "0.13.14", - "0.9.3": "0.12.17" + "0.9.3": "0.12.17", + "1.0.0": "0.12.17" }