Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "podnotes",
"name": "PodNotes",
"version": "2.20.2",
"minAppVersion": "1.11.5",
"minAppVersion": "1.13.0",
"description": "Helps you write notes on podcasts.",
"author": "Christian B. B. Houmann",
"authorUrl": "https://bagerbach.com",
Expand Down
8 changes: 4 additions & 4 deletions src/TemplateEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ function escapeMarkdownText(text: string): string {
* must maintain that context boundary.
*/
function escapeMarkdownBodyText(text: string): string {
// eslint-disable-next-line no-control-regex
// eslint-disable-next-line no-control-regex -- Control characters must be collapsed before feed text enters Markdown.
const singleLine = text.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
return escapeMarkdownText(singleLine);
}
Expand Down Expand Up @@ -243,7 +243,7 @@ function feedHtmlToMarkdown(html: string): string {
for (const element of document.querySelectorAll(
"img, audio, video, source, iframe, object, embed, link",
)) {
const alt = element instanceof HTMLImageElement ? element.alt.trim() : "";
const alt = element.instanceOf(HTMLImageElement) ? element.alt.trim() : "";
element.replaceWith(document.createTextNode(alt));
}
return neutralizeMarkdownEmbeds(
Expand Down Expand Up @@ -561,7 +561,7 @@ export function getFeedNoteWikilink(feedTitle: string): string {
* URLs are unchanged.
*/
function sanitizeUrlForTemplate(url: string): string {
// eslint-disable-next-line no-control-regex
// eslint-disable-next-line no-control-regex -- URL control characters must be encoded before interpolation.
return url.replace(/[\u0000-\u0020"'`()[\]<>\\]/g, (char) => {
return `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`;
});
Expand Down Expand Up @@ -598,7 +598,7 @@ export function replaceIllegalFileNameCharactersInString(string: string) {
.replace(/[\\,#%&{}/*<>$'":@\u2023|?[\]]/g, "")
// Replace any control characters (newlines, tabs, carriage returns)
// with spaces so they can never end up in a file name.
// eslint-disable-next-line no-control-regex
// eslint-disable-next-line no-control-regex -- File names cannot contain control characters.
.replace(/[\u0000-\u001f]/g, " ")
// Collapse every run of whitespace into a single space.
.replace(/\s+/g, " ")
Expand Down
35 changes: 17 additions & 18 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { FeedSuggestModal, orderFeedsByCurrent } from "src/ui/FeedSuggestModal";
import downloadEpisodeWithNotice from "src/downloadEpisode";
import getUniversalPodcastLink from "src/getUniversalPodcastLink";
import { getEpisodeMediaType } from "src/utility/mediaType";
import type { IconType } from "src/types/IconType";
import type PodNotes from "src/main";

/**
Expand Down Expand Up @@ -59,7 +58,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "podnotes-show-leaf",
name: "Show player",
icon: "podcast" as IconType,
icon: "podcast",
// Always available, and always reveals the view. The previous
// checkCallback hid this command whenever a leaf already existed, so
// once the view was open-but-hidden (collapsed sidebar, sidebar
Expand All @@ -73,7 +72,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "start-playing",
name: "Play Podcast",
icon: "play-circle" as IconType,
icon: "play-circle",
checkCallback: (checking) => {
if (checking) {
return !plugin.api.isPlaying && !!plugin.api.podcast;
Expand All @@ -86,7 +85,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "stop-playing",
name: "Stop Podcast",
icon: "stop-circle" as IconType,
icon: "stop-circle",
checkCallback: (checking) => {
if (checking) {
return plugin.api.isPlaying && !!plugin.api.podcast;
Expand All @@ -99,7 +98,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "skip-backward",
name: "Skip Backward",
icon: "skip-back" as IconType,
icon: "skip-back",
checkCallback: (checking) => {
// Skipping only seeks the position, so it is available whenever an
// episode is loaded — paused or playing — matching the always-active
Expand All @@ -116,7 +115,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "skip-forward",
name: "Skip Forward",
icon: "skip-forward" as IconType,
icon: "skip-forward",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -129,7 +128,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "download-playing-episode",
name: "Download Playing Episode",
icon: "download" as IconType,
icon: "download",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -148,7 +147,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "reorder-queue",
name: "Reorder Queue",
icon: "list-ordered" as IconType,
icon: "list-ordered",
checkCallback: (checking) => {
if (checking) {
return get(queue).episodes.length > 1;
Expand All @@ -161,7 +160,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "capture-timestamp",
name: "Capture Timestamp",
icon: "clock" as IconType,
icon: "clock",
// Keep this an editorCallback (not editorCheckCallback): an unconditional
// editor command stays addable to the mobile editor toolbar / command
// picker even before an episode is loaded, whereas a checkCallback that
Expand All @@ -181,7 +180,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "capture-segment-10s",
name: "Capture Last 10 Seconds",
icon: "scissors" as IconType,
icon: "scissors",
editorCheckCallback: (checking, editor) => {
if (checking) {
return canCaptureTimestamp();
Expand All @@ -194,7 +193,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "capture-segment-20s",
name: "Capture Last 20 Seconds",
icon: "scissors" as IconType,
icon: "scissors",
editorCheckCallback: (checking, editor) => {
if (checking) {
return canCaptureTimestamp();
Expand All @@ -211,7 +210,7 @@ export function registerCommands(plugin: PodNotes): void {
// "Create podcast feed note" command below (issue #163). The id is kept
// for backward compatibility (hotkeys/API).
name: "Create episode note",
icon: "file-plus" as IconType,
icon: "file-plus",
checkCallback: (checking) => {
if (checking) {
return (
Expand All @@ -228,7 +227,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "create-podcast-feed-note",
name: "Create podcast feed note",
icon: "file-plus" as IconType,
icon: "file-plus",
checkCallback: (checking) => {
const feeds = Object.values(get(savedFeeds));
const canCreate =
Expand All @@ -255,7 +254,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "get-share-link-episode",
name: "Copy universal episode link to clipboard",
icon: "share" as IconType,
icon: "share",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -268,7 +267,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "podnotes-toggle-playback",
name: "Toggle playback",
icon: "play" as IconType,
icon: "play",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -281,7 +280,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "increase-playback-rate",
name: "Increase playback rate",
icon: "gauge" as IconType,
icon: "gauge",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -294,7 +293,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "decrease-playback-rate",
name: "Decrease playback rate",
icon: "gauge" as IconType,
icon: "gauge",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand All @@ -307,7 +306,7 @@ export function registerCommands(plugin: PodNotes): void {
plugin.addCommand({
id: "reset-playback-rate",
name: "Reset playback rate",
icon: "rotate-ccw" as IconType,
icon: "rotate-ccw",
checkCallback: (checking) => {
if (checking) {
return !!plugin.api.podcast;
Expand Down
49 changes: 33 additions & 16 deletions src/createPodcastNote.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { TFile } from "obsidian";
import { TFile, TFolder } from "obsidian";
import createPodcastNote, { getPodcastNote } from "./createPodcastNote";
import { plugin } from "./store";
import type { Episode } from "./types/Episode";
Expand Down Expand Up @@ -156,7 +156,17 @@ describe("getPodcastNote title fallbacks (#315)", () => {
});

function fileAt(path: string): TFile {
return Object.assign(Object.create(TFile.prototype), { path }) as TFile;
return Object.assign(Object.create(TFile.prototype), {
path,
extension: path.split(".").pop() ?? "",
}) as TFile;
}

function folderWith(files: TFile[]): TFolder {
return Object.assign(Object.create(TFolder.prototype), {
path: "podcasts",
children: files,
}) as TFolder;
}

it("opens a note written with the 2.16 filename sanitizer", () => {
Expand All @@ -168,8 +178,11 @@ describe("getPodcastNote title fallbacks (#315)", () => {
plugin.set({
app: {
vault: {
getAbstractFileByPath: vi.fn((path: string) => files.get(path) ?? null),
getMarkdownFiles: vi.fn(() => [...files.values()]),
getAbstractFileByPath: vi.fn((path: string) =>
path === "podcasts"
? folderWith([...files.values()])
: (files.get(path) ?? null),
),
},
},
settings: {
Expand All @@ -190,8 +203,11 @@ describe("getPodcastNote title fallbacks (#315)", () => {
plugin.set({
app: {
vault: {
getAbstractFileByPath: vi.fn((path: string) => files.get(path) ?? null),
getMarkdownFiles: vi.fn(() => [...files.values()]),
getAbstractFileByPath: vi.fn((path: string) =>
path === "podcasts"
? folderWith([...files.values()])
: (files.get(path) ?? null),
),
createFolder: vi.fn(async () => {}),
create: vi.fn(async (path: string) => {
createdFiles.push({ path });
Expand Down Expand Up @@ -229,10 +245,10 @@ describe("getPodcastNote title fallbacks (#315)", () => {
plugin.set({
app: {
vault: {
getAbstractFileByPath: vi.fn((path: string) =>
path === part2Note.path ? part2Note : null,
),
getMarkdownFiles: vi.fn(() => [part2Note]),
getAbstractFileByPath: vi.fn((path: string) => {
if (path === "podcasts") return folderWith([part2Note]);
return path === part2Note.path ? part2Note : null;
}),
createFolder: vi.fn(async () => {}),
create: vi.fn(async (path: string) => {
createdFiles.push({ path });
Expand Down Expand Up @@ -271,10 +287,10 @@ describe("getPodcastNote title fallbacks (#315)", () => {
plugin.set({
app: {
vault: {
getAbstractFileByPath: vi.fn((path: string) =>
path === androidNote.path ? androidNote : null,
),
getMarkdownFiles: vi.fn(() => [androidNote]),
getAbstractFileByPath: vi.fn((path: string) => {
if (path === "podcasts") return folderWith([androidNote]);
return path === androidNote.path ? androidNote : null;
}),
},
},
settings: {
Expand All @@ -291,8 +307,9 @@ describe("getPodcastNote title fallbacks (#315)", () => {
plugin.set({
app: {
vault: {
getAbstractFileByPath: vi.fn(() => null),
getMarkdownFiles: vi.fn(() => [other]),
getAbstractFileByPath: vi.fn((path: string) =>
path === "podcasts" ? folderWith([other]) : null,
),
},
},
settings: {
Expand Down
17 changes: 8 additions & 9 deletions src/createPodcastNote.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Notice, TFile } from "obsidian";
import { Notice, TFile, TFolder } from "obsidian";
import {
FilePathTemplateEngine,
legacyReplaceIllegalFileNameCharactersInString,
Expand Down Expand Up @@ -124,15 +124,14 @@ export function getPodcastNote(episode: Episode): TFile | null {

function findPodcastNoteByTitleOverlap(episode: Episode): TFile | null {
const { vault } = get(plugin).app;
if (typeof vault.getMarkdownFiles !== "function") {
return null;
}

const expectedPath = getPodcastNotePath(episode);
const folder = parentFolder(expectedPath);
const siblings = vault
.getMarkdownFiles()
.filter((file) => parentFolder(file.path) === folder && file instanceof TFile);
const folderPath = parentFolder(expectedPath);
const folder = folderPath ? vault.getAbstractFileByPath(folderPath) : vault.getRoot();
if (!(folder instanceof TFolder)) return null;

const siblings = folder.children.filter(
(file): file is TFile => file instanceof TFile && file.extension === "md",
);

return (
findUniqueTitleMatch(getPodcastNoteTitleCandidates(episode), siblings, (file) =>
Expand Down
Loading