From 12b6303db102430fc1d535e266fc853e1a32c2ad Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 29 Jul 2022 20:47:29 +0200 Subject: [PATCH 01/13] WIP: Create Note ^& Timestamp --- src/ui/settings/CaptureSettings.svelte | 107 +++++++++++++++++++++++++ src/ui/settings/PodNotesSettingsTab.ts | 45 ++++++++++- 2 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/ui/settings/CaptureSettings.svelte diff --git a/src/ui/settings/CaptureSettings.svelte b/src/ui/settings/CaptureSettings.svelte new file mode 100644 index 00000000..7997ce91 --- /dev/null +++ b/src/ui/settings/CaptureSettings.svelte @@ -0,0 +1,107 @@ + + +
+
+
+
+
+ + diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index 9540d1fc..9ead10b2 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -1,13 +1,19 @@ -import { App, PluginSettingTab, Setting } from 'obsidian'; +import { App, debounce, MarkdownRenderer, PluginSettingTab, Setting } from 'obsidian'; import PodNotes from '../../main'; import PodcastQueryGrid from './PodcastQueryGrid.svelte'; import PlaylistManager from './PlaylistManager.svelte'; +import CaptureSettings from './CaptureSettings.svelte'; +import { get } from 'svelte/store'; +import { episodeCache } from 'src/store'; +import { Episode } from 'src/types/Episode'; +import { IconType } from 'src/types/IconType'; export class PodNotesSettingsTab extends PluginSettingTab { plugin: PodNotes; - + private podcastQueryGrid: PodcastQueryGrid; private playlistManager: PlaylistManager; + private captureSettings: CaptureSettings; constructor(app: App, plugin: PodNotes) { super(app, plugin); @@ -29,7 +35,7 @@ export class PodNotesSettingsTab extends PluginSettingTab { .setName('Search Podcasts') .setHeading() .setDesc('Search for podcasts by name or custom feed URL.'); - + const queryGridContainer = settingsContainer.createDiv(); this.podcastQueryGrid = new PodcastQueryGrid({ target: queryGridContainer @@ -44,14 +50,16 @@ export class PodNotesSettingsTab extends PluginSettingTab { this.playlistManager = new PlaylistManager({ target: playlistManagerContainer }); - + this.addDefaultPlaybackRateSetting(settingsContainer); this.addSkipLengthSettings(settingsContainer); + this.addNoteSettings(settingsContainer); } hide(): void { this.podcastQueryGrid?.$destroy(); this.playlistManager?.$destroy(); + this.captureSettings?.$destroy(); } private addDefaultPlaybackRateSetting(container: HTMLElement): void { @@ -95,4 +103,33 @@ export class PodNotesSettingsTab extends PluginSettingTab { .setPlaceholder('seconds'); }); } + + private addNoteSettings(settingsContainer: HTMLDivElement) { + const container = settingsContainer.createDiv(); + + container.createEl('h4', { text: 'Note settings' }); + const captureSettingsContainer = container.createDiv(); + + this.captureSettings = new CaptureSettings({ + target: captureSettingsContainer, + }); + + new Setting(container) + .setName('Capture timestamp format') + .addMomentFormat((momentFormatComponent) => { + momentFormatComponent + .setValue("") // this.plugin.settings.captureTimestampFormat + .onChange(value => { + //this.plugin.settings.captureTimestampFormat = value; + this.plugin.saveSettings(); + }); + }) + .addExtraButton((extraButtonComponent) => { + extraButtonComponent + .setIcon("link-2" as IconType) + . + .onClick(() => { + }); + }); + } } From 1fed90468ef8ffcc818de46d50cfe0308c89fa42 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 5 Aug 2022 17:42:03 +0200 Subject: [PATCH 02/13] build: dependency update --- package-lock.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73feefc8..7fd5fca1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8078,7 +8078,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/through": { "version": "2.3.8", @@ -14421,7 +14422,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "peer": true }, "through": { "version": "2.3.8", From 7df023325a2daf83b0b90b331a2db323a2d31481 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 5 Aug 2022 17:42:28 +0200 Subject: [PATCH 03/13] create template engine --- src/ui/TemplateEngine.ts | 64 ++++++++++++++++++++++++++ src/ui/settings/CaptureSettings.svelte | 28 +++-------- src/ui/settings/PodNotesSettingsTab.ts | 49 +++++++++----------- 3 files changed, 92 insertions(+), 49 deletions(-) create mode 100644 src/ui/TemplateEngine.ts diff --git a/src/ui/TemplateEngine.ts b/src/ui/TemplateEngine.ts new file mode 100644 index 00000000..95aa1f5f --- /dev/null +++ b/src/ui/TemplateEngine.ts @@ -0,0 +1,64 @@ +import { htmlToMarkdown, Notice } from "obsidian"; +import { Episode } from "src/types/Episode"; +import Fuse from "fuse.js"; +import { plugin } from "src/store"; +import { get } from "svelte/store"; + +interface Tags { + [tag: string]: string | ((...args: unknown[]) => string); +} + +function TemplateEngine(template: string, tags: Tags) { + return template.replace(/\{\{(.*?)(:\s*?.+?)?\}\}/g, (match: string, tagId: string, params: string) => { + const tagValue = tags[tagId.toLowerCase()]; + if (!tagValue) { + const fuse = new Fuse(Object.keys(tags), { + shouldSort: true, + findAllMatches: false, + threshold: 0.4, + isCaseSensitive: false, + }); + + const similarTag = fuse.search(tagId); + + new Notice(`Tag ${tagId} is invalid.${similarTag.length > 0 ? ` Did you mean ${similarTag[0].item}?` : ""}`); + return match; + } + + if (typeof tagValue === 'function') { + if (params) { + // Remove initial colon with splice. + const splitParams = params.slice(1).split(','); + const args = Array.isArray(splitParams) ? splitParams : [params]; + + return tagValue(...args); + } + + return tagValue(); + } + + return tagValue; + }); +} + +export function NoteTemplateEngine(template: string, episode: Episode) { + return TemplateEngine(template, { + "title": episode.title, + "description": (prependLine?: string) => prependLine ? + htmlToMarkdown(episode.description) + : htmlToMarkdown(episode.description).split('\n').map(line => `${prependLine}${line}`).join('\n'), + "url": episode.url, + "date": (format?: string) => episode.episodeDate ? + window.moment(episode.episodeDate).format(format ?? "YYYY-MM-DD") + : "", + "podcast": episode.podcastName, + "artwork": episode.artworkUrl ?? "", + }); +} + +export function TimestampTemplateEngine(template: string) { + return TemplateEngine(template, { + "time": (format?: string) => get(plugin).api.getPodcastTimeFormatted(format ?? "HH:mm:ss"), + "linktime": (format?: string) => get(plugin).api.getPodcastTimeFormatted(format ?? "HH:mm:ss", true), + }); +} diff --git a/src/ui/settings/CaptureSettings.svelte b/src/ui/settings/CaptureSettings.svelte index 7997ce91..5b6b1274 100644 --- a/src/ui/settings/CaptureSettings.svelte +++ b/src/ui/settings/CaptureSettings.svelte @@ -1,7 +1,5 @@
diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index 9ead10b2..c5803c31 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -1,19 +1,15 @@ -import { App, debounce, MarkdownRenderer, PluginSettingTab, Setting } from 'obsidian'; +import { App, MarkdownRenderer, PluginSettingTab, Setting } from 'obsidian'; import PodNotes from '../../main'; import PodcastQueryGrid from './PodcastQueryGrid.svelte'; import PlaylistManager from './PlaylistManager.svelte'; -import CaptureSettings from './CaptureSettings.svelte'; -import { get } from 'svelte/store'; -import { episodeCache } from 'src/store'; -import { Episode } from 'src/types/Episode'; -import { IconType } from 'src/types/IconType'; +import { TimestampTemplateEngine } from '../TemplateEngine'; + export class PodNotesSettingsTab extends PluginSettingTab { plugin: PodNotes; private podcastQueryGrid: PodcastQueryGrid; private playlistManager: PlaylistManager; - private captureSettings: CaptureSettings; constructor(app: App, plugin: PodNotes) { super(app, plugin); @@ -59,7 +55,6 @@ export class PodNotesSettingsTab extends PluginSettingTab { hide(): void { this.podcastQueryGrid?.$destroy(); this.playlistManager?.$destroy(); - this.captureSettings?.$destroy(); } private addDefaultPlaybackRateSetting(container: HTMLElement): void { @@ -108,28 +103,26 @@ export class PodNotesSettingsTab extends PluginSettingTab { const container = settingsContainer.createDiv(); container.createEl('h4', { text: 'Note settings' }); - const captureSettingsContainer = container.createDiv(); - - this.captureSettings = new CaptureSettings({ - target: captureSettingsContainer, - }); - new Setting(container) + const timestampSetting = new Setting(container) .setName('Capture timestamp format') - .addMomentFormat((momentFormatComponent) => { - momentFormatComponent - .setValue("") // this.plugin.settings.captureTimestampFormat - .onChange(value => { - //this.plugin.settings.captureTimestampFormat = value; - this.plugin.saveSettings(); - }); + .addTextArea(textArea => { + textArea.setValue("") // format is not saved yet + textArea.onChange(value => { + //this.plugin.settings.timestampFormat = value; + this.plugin.saveSettings(); + const demoVal = TimestampTemplateEngine(value); + timestampFormatDemoEl.empty(); + MarkdownRenderer.renderMarkdown(demoVal, timestampFormatDemoEl, "", textArea); + + }); + textArea.inputEl.style.width = "100%"; }) - .addExtraButton((extraButtonComponent) => { - extraButtonComponent - .setIcon("link-2" as IconType) - . - .onClick(() => { - }); - }); + + timestampSetting.settingEl.style.flexDirection = 'column'; + timestampSetting.settingEl.style.alignItems = ''; + timestampSetting.settingEl.style.gap = '10px'; + + const timestampFormatDemoEl = container.createDiv(); } } From 3ea3f3ce8035030fd8fafa2632370e440c9b2050 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 5 Aug 2022 17:42:47 +0200 Subject: [PATCH 04/13] refactor: comment out unused lines --- src/ui/PodcastView/Loading.svelte | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ui/PodcastView/Loading.svelte b/src/ui/PodcastView/Loading.svelte index 6702c85e..fb378976 100644 --- a/src/ui/PodcastView/Loading.svelte +++ b/src/ui/PodcastView/Loading.svelte @@ -24,9 +24,9 @@ font-size: 0; color: #fff; } -.la-line-scale.la-dark { +/* .la-line-scale.la-dark { color: #333; -} +} */ .la-line-scale > div { display: inline-block; float: none; @@ -79,7 +79,7 @@ -o-animation-delay: -.8s; animation-delay: -.8s; } -.la-line-scale.la-sm { +/* .la-line-scale.la-sm { width: 20px; height: 16px; } @@ -100,7 +100,7 @@ margin: 4px; margin-top: 0; margin-bottom: 0; -} +} .la-line-scale.la-3x { width: 120px; height: 96px; @@ -111,7 +111,7 @@ margin: 6px; margin-top: 0; margin-bottom: 0; -} +}*/ /* * Animation */ From 664de2e6dc0cf5d3cf21f723cdce601bb68d9688 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Fri, 5 Aug 2022 22:34:15 +0200 Subject: [PATCH 05/13] feat: capture timestamp and create podcast notes #7 --- src/{ui => }/TemplateEngine.ts | 31 +++++++- src/constants.ts | 9 +++ src/main.ts | 49 +++++++++++++ src/types/IPodNotesSettings.ts | 9 +++ src/ui/settings/CaptureSettings.svelte | 2 +- src/ui/settings/PodNotesSettingsTab.ts | 98 ++++++++++++++++++++++++-- 6 files changed, 189 insertions(+), 9 deletions(-) rename src/{ui => }/TemplateEngine.ts (64%) diff --git a/src/ui/TemplateEngine.ts b/src/TemplateEngine.ts similarity index 64% rename from src/ui/TemplateEngine.ts rename to src/TemplateEngine.ts index 95aa1f5f..5a3ee9fd 100644 --- a/src/ui/TemplateEngine.ts +++ b/src/TemplateEngine.ts @@ -44,9 +44,16 @@ function TemplateEngine(template: string, tags: Tags) { export function NoteTemplateEngine(template: string, episode: Episode) { return TemplateEngine(template, { "title": episode.title, - "description": (prependLine?: string) => prependLine ? - htmlToMarkdown(episode.description) - : htmlToMarkdown(episode.description).split('\n').map(line => `${prependLine}${line}`).join('\n'), + "description": (prependToLines?: string) => { + if (prependToLines) { + return htmlToMarkdown(episode.description) + .split("\n") + .map(prepend(prependToLines)) + .join("\n") + } + + return htmlToMarkdown(episode.description) + }, "url": episode.url, "date": (format?: string) => episode.episodeDate ? window.moment(episode.episodeDate).format(format ?? "YYYY-MM-DD") @@ -56,9 +63,27 @@ export function NoteTemplateEngine(template: string, episode: Episode) { }); } +function prepend(prepend: string) { + return (str: string) => `${prepend}${str}`; +} + export function TimestampTemplateEngine(template: string) { return TemplateEngine(template, { "time": (format?: string) => get(plugin).api.getPodcastTimeFormatted(format ?? "HH:mm:ss"), "linktime": (format?: string) => get(plugin).api.getPodcastTimeFormatted(format ?? "HH:mm:ss", true), }); } + +export function FilePathTemplateEngine(template: string, episode: Episode) { + return TemplateEngine(template, { + "title": replaceIllegalFileNameCharactersInString(episode.title), + "podcast": replaceIllegalFileNameCharactersInString(episode.podcastName), + }); +} + +function replaceIllegalFileNameCharactersInString(string: string) { + return string + .replace(/[\\,#%&{}/*<>$'":@\u2023|?]*/g, '') // Replace illegal file name characters with empty string + .replace(/\n/, ' ') // replace newlines with spaces + .replace(' ', ' '); // replace multiple spaces with single space to make sure we don't have double spaces in the file name +} diff --git a/src/constants.ts b/src/constants.ts index fe3c88fb..0eafc9a1 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,5 +36,14 @@ export const DEFAULT_SETTINGS: IPodNotesSettings = { skipBackwardLength: 15, skipForwardLength: 15, currentEpisode: undefined, + + timestamp: { + template: "- {{time}} ", + }, + + note: { + path: "", + template: "", + } } diff --git a/src/main.ts b/src/main.ts index 0dd5f1ae..cfc5f2a3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { FavoritesController } from './store_controllers/FavoritesController'; import { Episode } from './types/Episode'; import CurrentEpisodeController from './store_controllers/CurrentEpisodeController'; import { ViewState } from './types/ViewState'; +import { FilePathTemplateEngine, NoteTemplateEngine, TimestampTemplateEngine } from './TemplateEngine'; export default class PodNotes extends Plugin implements IPodNotes { public api: IAPI; @@ -113,6 +114,54 @@ export default class PodNotes extends Plugin implements IPodNotes { //@ts-ignore this.app.plugins.disablePlugin(id).then(() => this.app.plugins.enablePlugin(id)) } + }); + + this.addCommand({ + id: 'capture-timestamp', + name: 'Capture Timestamp', + editorCheckCallback: (checking, editor, view) => { + if (checking) { + return !!this.api.podcast && + !!this.settings.timestamp.template; + } + + const cursorPos = editor.getCursor(); + const capture = TimestampTemplateEngine( + this.settings.timestamp.template, + ); + + editor.replaceRange(capture, cursorPos); + editor.setCursor(cursorPos.line, cursorPos.ch + capture.length); + } + }); + + this.addCommand({ + id: 'create-podcast-note', + name: 'Create Podcast Note', + checkCallback: (checking) => { + if (checking) { + return !!this.api.podcast && + !!this.settings.note.path && + !!this.settings.note.template; + } + + const filePath = FilePathTemplateEngine( + this.settings.note.path, + this.api.podcast + ); + + const content = NoteTemplateEngine( + this.settings.note.template, + this.api.podcast + ); + + this.app.vault.create( + filePath.endsWith('.md') ? filePath : `${filePath}.md`, + content + ).then((createdFile) => + this.app.workspace.getLeaf().openFile(createdFile) + ) + }, }) this.addSettingTab(new PodNotesSettingsTab(this.app, this)); diff --git a/src/types/IPodNotesSettings.ts b/src/types/IPodNotesSettings.ts index 1eda91a4..7b531490 100644 --- a/src/types/IPodNotesSettings.ts +++ b/src/types/IPodNotesSettings.ts @@ -15,4 +15,13 @@ export interface IPodNotesSettings { queue: Playlist, favorites: Playlist, currentEpisode?: Episode, + + timestamp: { + template: string; + }, + + note: { + path: string; + template: string; + } } diff --git a/src/ui/settings/CaptureSettings.svelte b/src/ui/settings/CaptureSettings.svelte index 5b6b1274..ff484824 100644 --- a/src/ui/settings/CaptureSettings.svelte +++ b/src/ui/settings/CaptureSettings.svelte @@ -8,7 +8,7 @@ import { Episode } from "src/types/Episode"; import { onMount } from "svelte"; import { get } from "svelte/store"; - import { NoteTemplateEngine } from "../TemplateEngine"; + import { NoteTemplateEngine } from "../../TemplateEngine"; let demoEpisode: Episode; let markdownDemoEl: HTMLDivElement; diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index c5803c31..4f48ac48 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -2,7 +2,11 @@ import { App, MarkdownRenderer, PluginSettingTab, Setting } from 'obsidian'; import PodNotes from '../../main'; import PodcastQueryGrid from './PodcastQueryGrid.svelte'; import PlaylistManager from './PlaylistManager.svelte'; -import { TimestampTemplateEngine } from '../TemplateEngine'; +import { TimestampTemplateEngine } from '../../TemplateEngine'; +import { FilePathTemplateEngine } from '../../TemplateEngine'; +import { episodeCache } from 'src/store'; +import { Episode } from 'src/types/Episode'; +import { get } from 'svelte/store'; export class PodNotesSettingsTab extends PluginSettingTab { @@ -11,9 +15,12 @@ export class PodNotesSettingsTab extends PluginSettingTab { private podcastQueryGrid: PodcastQueryGrid; private playlistManager: PlaylistManager; + private settingsTab: PodNotesSettingsTab; + constructor(app: App, plugin: PodNotes) { super(app, plugin); this.plugin = plugin; + this.settingsTab = this; } display(): void { @@ -106,23 +113,104 @@ export class PodNotesSettingsTab extends PluginSettingTab { const timestampSetting = new Setting(container) .setName('Capture timestamp format') + .setHeading() .addTextArea(textArea => { - textArea.setValue("") // format is not saved yet + textArea.setValue(this.plugin.settings.timestamp.template) + textArea.setPlaceholder('- {{linktime}} ') textArea.onChange(value => { - //this.plugin.settings.timestampFormat = value; + this.plugin.settings.timestamp.template = value; this.plugin.saveSettings(); + const demoVal = TimestampTemplateEngine(value); timestampFormatDemoEl.empty(); - MarkdownRenderer.renderMarkdown(demoVal, timestampFormatDemoEl, "", textArea); + // @ts-ignore - documentation says component is optional, yet not providing one causes an error + MarkdownRenderer.renderMarkdown(demoVal, timestampFormatDemoEl, "", null); }); textArea.inputEl.style.width = "100%"; }) timestampSetting.settingEl.style.flexDirection = 'column'; - timestampSetting.settingEl.style.alignItems = ''; + timestampSetting.settingEl.style.alignItems = 'unset'; timestampSetting.settingEl.style.gap = '10px'; const timestampFormatDemoEl = container.createDiv(); + const randomEpisode = getRandomEpisode(); + + const noteCreationFilePathSetting = new Setting(container) + .setName('Note creation file path') + .setHeading() + .addText((textComponent) => { + textComponent.setValue(this.plugin.settings.note.path) + textComponent.setPlaceholder('inputs/podcasts/{{podcast}} - {{title}}.md') + textComponent.onChange(value => { + this.plugin.settings.note.path = value; + this.plugin.saveSettings(); + + const demoVal = FilePathTemplateEngine(value, randomEpisode); + noteCreationFilePathDemoEl.empty(); + // @ts-ignore - documentation says component is optional, yet not providing one causes an error + MarkdownRenderer.renderMarkdown(demoVal, noteCreationFilePathDemoEl, "", null); + }); + textComponent.inputEl.style.width = "100%"; + }); + + noteCreationFilePathSetting.settingEl.style.flexDirection = 'column'; + noteCreationFilePathSetting.settingEl.style.alignItems = 'unset'; + noteCreationFilePathSetting.settingEl.style.gap = '10px'; + + const noteCreationFilePathDemoEl = container.createDiv(); + + const noteCreationSetting = new Setting(container) + .setName('Note creation template') + .setHeading() + .addTextArea(textArea => { + textArea.setValue(this.plugin.settings.note.template) + textArea.onChange(value => { + this.plugin.settings.note.template = value; + this.plugin.saveSettings(); + }); + + textArea.inputEl.style.width = "100%"; + textArea.inputEl.style.height = "25vh"; + textArea.setPlaceholder(`## {{title}} +![]({{artwork}}) +### Metadata +Podcast:: {{podcast}} +Episode:: {{title}} +PublishDate:: {{date:YYYY-MM-DD}} + +### Description +> {{description}}`) + }); + + noteCreationSetting.settingEl.style.flexDirection = 'column'; + noteCreationSetting.settingEl.style.alignItems = 'unset'; + noteCreationSetting.settingEl.style.gap = '10px'; } } + +function getRandomEpisode(): Episode { + const fallbackDemoObj = { + description: "demo", + podcastName: "demo", + title: "demo", + url: "demo", + artworkUrl: "demo", + streamUrl: "demo", + episodeDate: new Date(), + feedUrl: "demo", + }; + + const feedEpisodes = Object.values(get(episodeCache)); + if (!feedEpisodes.length) return fallbackDemoObj; + + const randomFeed = + feedEpisodes[Math.floor(Math.random() * feedEpisodes.length)]; + if (!randomFeed.length) return fallbackDemoObj; + + const randomEpisode = + randomFeed[Math.floor(Math.random() * randomFeed.length)]; + + return randomEpisode; +} From de61e2ed20e06b43fcf165cd0903dabdd4fe18e9 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Sat, 6 Aug 2022 00:02:47 +0200 Subject: [PATCH 06/13] refactor: update function name --- src/main.ts | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main.ts b/src/main.ts index cfc5f2a3..d1179793 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,7 +49,7 @@ export default class PodNotes extends Plugin implements IPodNotes { favorites.set(this.settings.favorites); if (this.settings.currentEpisode) { currentEpisode.set(this.settings.currentEpisode); - } + } this.playedEpisodeController = new EpisodeStatusController(playedEpisodes, this).on(); this.savedFeedsController = new SavedFeedsController(savedFeeds, this).on(); @@ -140,27 +140,36 @@ export default class PodNotes extends Plugin implements IPodNotes { name: 'Create Podcast Note', checkCallback: (checking) => { if (checking) { - return !!this.api.podcast && + return !!this.api.podcast && !!this.settings.note.path && !!this.settings.note.template; } - const filePath = FilePathTemplateEngine( - this.settings.note.path, - this.api.podcast - ); - - const content = NoteTemplateEngine( - this.settings.note.template, - this.api.podcast - ); - - this.app.vault.create( - filePath.endsWith('.md') ? filePath : `${filePath}.md`, - content - ).then((createdFile) => - this.app.workspace.getLeaf().openFile(createdFile) - ) + (async function createPodcastNote() { + const filePath = FilePathTemplateEngine( + this.settings.note.path, + this.api.podcast + ); + + const content = NoteTemplateEngine( + this.settings.note.template, + this.api.podcast + ); + + try { + const createdFile = await this.app.vault.create( + filePath.endsWith('.md') ? filePath : `${filePath}.md`, + content + ) + + this.app.workspace + .getLeaf() + .openFile(createdFile) + } catch (error) { + console.error(error); + new Notice(error); + } + }).bind(this)(); }, }) From 80288fa2430fcd43abaa992822a53d4cb8683184 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 14:34:13 +0200 Subject: [PATCH 07/13] refactor: string indentation --- src/ui/settings/PodNotesSettingsTab.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index 4f48ac48..4b0a3b2b 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -173,15 +173,16 @@ export class PodNotesSettingsTab extends PluginSettingTab { textArea.inputEl.style.width = "100%"; textArea.inputEl.style.height = "25vh"; - textArea.setPlaceholder(`## {{title}} -![]({{artwork}}) -### Metadata -Podcast:: {{podcast}} -Episode:: {{title}} -PublishDate:: {{date:YYYY-MM-DD}} - -### Description -> {{description}}`) + textArea.setPlaceholder( + "## {{title}}" + + "\n![]({{artwork}})" + + "\n### Metadata" + + "\nPodcast:: {{podcast}}" + + "\nEpisode:: {{title}}" + + "\nPublishDate:: {{date:YYYY-MM-DD}}" + + "\n### Description" + + "\n> {{description}}" + ); }); noteCreationSetting.settingEl.style.flexDirection = 'column'; From a6c9bcf43c58381736d7058479312ac4dd6db0c8 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 15:49:58 +0200 Subject: [PATCH 08/13] refactor: error message & line changer for readability --- src/main.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index d1179793..135d3a02 100644 --- a/src/main.ts +++ b/src/main.ts @@ -151,6 +151,8 @@ export default class PodNotes extends Plugin implements IPodNotes { this.api.podcast ); + const filePathDotMd = filePath.endsWith('.md') ? filePath : `${filePath}.md`; + const content = NoteTemplateEngine( this.settings.note.template, this.api.podcast @@ -158,16 +160,16 @@ export default class PodNotes extends Plugin implements IPodNotes { try { const createdFile = await this.app.vault.create( - filePath.endsWith('.md') ? filePath : `${filePath}.md`, + filePathDotMd, content - ) + ); this.app.workspace .getLeaf() .openFile(createdFile) } catch (error) { console.error(error); - new Notice(error); + new Notice(`Failed to create note: "${filePathDotMd}"`); } }).bind(this)(); }, From e1e862e3b3d8e3cebcdfe14335a7094eba8f7490 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 16:14:38 +0200 Subject: [PATCH 09/13] docs: add link to plugin store install --- docs/docs/index.md | 27 ++++++++++++++++----------- docs/mkdocs.yml | 8 +++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index 40987232..209eed32 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -5,23 +5,21 @@ The one goal for PodNotes is to make it easier to write notes on podcasts. Here are the features that will help you do that 👇. ## Features + - Mobile friendly - Podcast player built into Obsidian -- Add any publicly available podcast through search +- Add any publicly available podcast through search, or custom feeds by URL - Track played episodes & playback progress -- API that can be used by plugins like [QuickAdd](https://github.com/chhoumann/QuickAdd) or [Templater](https://github.com/silentvoid13/Templater). With it, you can get - - Episode title - - Episode description - - Episode URL - - Episode podcast name - - Episode publish date - - Current playback time, both raw and formatted (with customn formatting). This can link to the episode at a given time, which PodNotes can pick up and play -- API allows controlling the player +- Create podcast notes from templates with metadata about episodes +- Capture timestamps & link directly to the time in the episode +- API that can be used by plugins like [QuickAdd](https://github.com/chhoumann/QuickAdd) or [Templater](https://github.com/silentvoid13/Templater) for custom workflows ## Installation -This plugin is currently not in the Obsidian plugin repository. Therefore, you'll need to install it manually. + +This plugin is in the Obsidian community plugin store. You can find it by searching in the store, or by clicking [here](obsidian://show-plugin?id=podnotes). ### Installation with BRAT + BRAT is an Obsidian plugin that helps you test beta plugins and themes. Click [here](obsidian://show-plugin?id=obsidian42-brat) to install it in Obsidian. Add `chhoumann/PodNotes` to BRAT with the `Add a beta plugin for testing` command. @@ -29,6 +27,7 @@ Add `chhoumann/PodNotes` to BRAT with the `Add a beta plugin for testing` comman Now follow the appropriate instructions, which most likely will have you go and enable the plugin once it has finished installing. ### Manual installation + Go to the [releases](https://github.com/chhoumann/podnotes/releases/latest) page. Download `main.js` and `manifest.json`. Create a new directory in your Obsidian vaults `.obsidian/plugins/` folder called `podnotes` and place the downloaded files there. @@ -36,21 +35,27 @@ Create a new directory in your Obsidian vaults `.obsidian/plugins/` folder calle Now refresh the plugins in Obsidian and enable PodNotes. ## Screenshots + ### Demo + ![Demo](resources/demo.gif) ### Podcast Grid + ![Podcast Grid](resources/podcast_grid.png) ### Episode List + ![Episode List](resources/episode_list.png) ### Player + ![Player](resources/player.png) ### Podcast Note Editing + ![Podcast Note Editing](resources/podcast_note.png) ### Podcast search -![Podcast Search](resources/podcast_search.png) +![Podcast Search](resources/podcast_search.png) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fafcb549..8e1abf2a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,7 +1,9 @@ site_name: PodNotes repo_url: https://github.com/chhoumann/podnotes edit_uri: edit/master/docs/docs +nav: + - Home: index.md theme: - name: material - palette: - primary: blue + name: material + palette: + primary: blue From 2bdb2d4f0d9097abf0ae2e10891963a57cce2eab Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 16:15:11 +0200 Subject: [PATCH 10/13] docs: document podcast-related features --- docs/docs/podcasts.md | 49 +++++++++++++++++++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + 2 files changed, 50 insertions(+) create mode 100644 docs/docs/podcasts.md diff --git a/docs/docs/podcasts.md b/docs/docs/podcasts.md new file mode 100644 index 00000000..07597b45 --- /dev/null +++ b/docs/docs/podcasts.md @@ -0,0 +1,49 @@ +## Adding podcasts +You can add podcasts by searching for them in the podcast search box in settings. +This searches the iTunes podcast repository. + +It is also possible to add custom podcast feeds by pasting the feed URL in the search box. +PodNotes will try to parse the feed, and if successful, will prompt you to add it to your saved feeds. + + +## Playlists +PodNotes can create playlists, which are lists of episodes. +For each playlist, you can specify a name and an icon. + +The playlists are shown in the episode grid, represented by their icons. + +By default, you will have a queue playlist and a favorites playlist. + +You can delete playlists by pressing the trash bin icon next to the playlist name. +The icon will change to a checkmark, which should be pressed within a short duration to confirm the deletion. + +## Episode list +The episode list is a dynamic view, which will change depending on your selection. + +If you have no podcast selected, the episode list will show the latest episodes of all your saved feeds. + +If you have a podcast selected, the episode list will show all episodes of that podcast. + +And lastly, if you select a playlist, the episode list shows all episodes in that playlist. + +## Context menu +You can right-click (desktop) or long-press (mobile) on an episode in the episode list to open the context menu. + +The context menu will let you + +- Play the episode +- Mark the episode as played +- Add / remove the episode to favorites +- Add / remove the episode to queue +- Add / remove the episode to a playlist + +## Player +The player will automatically load and play the current episode. + +Clicking on the epsiode image will toggle episode playback. + +The player also features a progress bar, which you can use to seek to a specific time in the episode. +You can also press the buttons on the player to skip backwards or forwards in the episode. + +Episode playback rate can be controlled by using the playback rate slider. +This is, by default, what you have set in the settings. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8e1abf2a..2eb97f8e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -3,6 +3,7 @@ repo_url: https://github.com/chhoumann/podnotes edit_uri: edit/master/docs/docs nav: - Home: index.md + - Podcasts: podcasts.md theme: name: material palette: From a5fca66737d0b7386c32677e4d1c01b60b1db012 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 16:15:33 +0200 Subject: [PATCH 11/13] docs: document note creation and timestamp features --- docs/docs/templates.md | 25 +++++++++++++++++++++++++ docs/docs/timestamps.md | 14 ++++++++++++++ docs/mkdocs.yml | 6 ++++++ 3 files changed, 45 insertions(+) create mode 100644 docs/docs/templates.md create mode 100644 docs/docs/timestamps.md diff --git a/docs/docs/templates.md b/docs/docs/templates.md new file mode 100644 index 00000000..14432f91 --- /dev/null +++ b/docs/docs/templates.md @@ -0,0 +1,25 @@ +PodNotes can create notes from templates. These templates can contain certain syntax, which will be expanded to metadata about the podcast episode you are listening to. + +To use templates, you can use the `Create podcast note` Obsidian command. +This requires you to have defined a template for both the file path and note text. + +## File path +This template will be used to create the file path for the note. You can use the following syntax: + +- `{{title}}`: The title of the podcast episode. +- `{{podcast}}`: The name of the podcast. + +Both syntax items will be formatted such that it is safe to use in a file path. +This means the following characters will be removed: `\ , # % & / { } * < > $ ' " : @ ‣ | ?`. + +## Note template +This template will be used to create the note text. You can use the following syntax: + +- `{{title}}`: The title of the podcast episode. +- `{{description}}`: The description of the podcast episode. + - You can use `{{description:> }}` to prepend each new line with a `>` (to put the entire description in a blockquote). +- `{{podcast}}`: The name of the podcast. +- `{{url}}`: The URL of the podcast episode. +- `{{date}}`: The publish date of the podcast episode. + - You can use `{{date:format}}` to specify a custom [Moment.js](https://momentjs.com) format. E.g. `{{date:YYYY-MM-DD}}`. +- `{{artwork}}`: The URL of the podcast artwork. If no artwork is found, an empty string will be used. diff --git a/docs/docs/timestamps.md b/docs/docs/timestamps.md new file mode 100644 index 00000000..e4f29aaa --- /dev/null +++ b/docs/docs/timestamps.md @@ -0,0 +1,14 @@ +Timestamps can be created with the `Capture Timestamp` Obsidian command. + +This will make PodNotes capture the current playback time to the active note, in the format given in the plugin settings. + +## Settings +For timestamps, you can use the following format strings: + +- `{{time}}`: The current playback time. Default format is `HH:mm:ss`. +- `{{linktime}}`: The current playback time, formatted as a link to the current episode. Default format is `HH:mm:ss`. + +Both of these allow for custom formatting. +By using `{{time:format}}` or `{{linktime:format}}`, you can specify a custom [Moment.js](https://momentjs.com) format. + +For example, you might use `{{time:H\h mm\m ss\s}}` to get the time in the format `0h 20m 37s`. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2eb97f8e..64104635 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -4,6 +4,12 @@ edit_uri: edit/master/docs/docs nav: - Home: index.md - Podcasts: podcasts.md + - 'Notes': + - Timestamps: timestamps.md + - Templates: templates.md + - Advanced: + - API: api.md + - Usage with QuickAdd: QuickAdd.md theme: name: material palette: From 503596f11c4c9ae8c408eb1eee3fd0af921754e5 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 16:29:00 +0200 Subject: [PATCH 12/13] fix: detect if podcast note exists and open instead of trying to create again --- src/main.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main.ts b/src/main.ts index 135d3a02..de81a9c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -158,15 +158,22 @@ export default class PodNotes extends Plugin implements IPodNotes { this.api.podcast ); + const createOrGetFile = async (path: string, content: string) => { + const file = app.vault.getAbstractFileByPath(path); + if (file) { + new Notice(`Note for "${this.api.podcast.title}" already exists`); + return file; + } + + return await this.app.vault.create(path, content); + } + try { - const createdFile = await this.app.vault.create( - filePathDotMd, - content - ); + const file = await createOrGetFile(filePathDotMd, content); this.app.workspace .getLeaf() - .openFile(createdFile) + .openFile(file) } catch (error) { console.error(error); new Notice(`Failed to create note: "${filePathDotMd}"`); From 887ac0868fb068796d8b4fd3deca84c05358ed41 Mon Sep 17 00:00:00 2001 From: Christian Bager Bach Houmann Date: Tue, 16 Aug 2022 16:34:18 +0200 Subject: [PATCH 13/13] fix: show timestamp demo on settings open --- src/ui/settings/PodNotesSettingsTab.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/ui/settings/PodNotesSettingsTab.ts b/src/ui/settings/PodNotesSettingsTab.ts index 4b0a3b2b..22437bb9 100644 --- a/src/ui/settings/PodNotesSettingsTab.ts +++ b/src/ui/settings/PodNotesSettingsTab.ts @@ -120,12 +120,7 @@ export class PodNotesSettingsTab extends PluginSettingTab { textArea.onChange(value => { this.plugin.settings.timestamp.template = value; this.plugin.saveSettings(); - - const demoVal = TimestampTemplateEngine(value); - timestampFormatDemoEl.empty(); - // @ts-ignore - documentation says component is optional, yet not providing one causes an error - MarkdownRenderer.renderMarkdown(demoVal, timestampFormatDemoEl, "", null); - + updateTimestampDemo(value); }); textArea.inputEl.style.width = "100%"; }) @@ -135,6 +130,16 @@ export class PodNotesSettingsTab extends PluginSettingTab { timestampSetting.settingEl.style.gap = '10px'; const timestampFormatDemoEl = container.createDiv(); + + const updateTimestampDemo = (value: string) => { + const demoVal = TimestampTemplateEngine(value); + timestampFormatDemoEl.empty(); + // @ts-ignore - documentation says component is optional, yet not providing one causes an error + MarkdownRenderer.renderMarkdown(demoVal, timestampFormatDemoEl, "", null); + } + + updateTimestampDemo(this.plugin.settings.timestamp.template); + const randomEpisode = getRandomEpisode(); const noteCreationFilePathSetting = new Setting(container)