🐛 fix: #144 Tolerate badly formatted URLs

URL constructor should not throw error anymore. Known special paths are ignored.

Closes: #144
This commit is contained in:
Nathan Smith 2024-08-30 17:17:34 -04:00
parent 8d495d990d
commit 2d42cb3edf
6 changed files with 77 additions and 9 deletions

View file

@ -1,3 +1,5 @@
import { logger } from "../plugin";
export interface ParsedUrl {
url: string;
host: string;
@ -18,7 +20,7 @@ export function repoAPIToBrowserUrl(urlString: string): string {
return urlString.replace(apiRegex, "https://github.com/");
}
export function parseUrl(urlString: string): ParsedUrl {
export function parseUrl(urlString: string): ParsedUrl | null {
// Some potential URLs:
// https://github.com/nathonius/alloy-theme
// https://github.com/nathonius/obsidian-trello/issues/45
@ -28,7 +30,14 @@ export function parseUrl(urlString: string): ParsedUrl {
// https://github.com/nathonius/obsidian-trello/blob/markdown/src/constants.ts#L44-L58
// https://github.com/nathonius/obsidian-trello/commit/7ea069dd0641441ec20fb194f50e746a21abbaf1
// https://github.com/nathonius/obsidian-trello/commit/7ea069dd0641441ec20fb194f50e746a21abbaf1#diff-8fa4b52909f895e8cda060d2035234e0a42ca2c7d3f8f8de1b35a056537bf199R35
const url = new URL(urlString);
let url: URL;
try {
url = new URL(urlString);
} catch (err) {
logger.error(`Attempted and failed to parse invalid URL: ${urlString}`);
return null;
}
const parsedUrl: ParsedUrl = { url: urlString, host: url.hostname };
const urlParts = url.pathname.split("/");

39
src/github/urls.ts Normal file
View file

@ -0,0 +1,39 @@
/**
* These are paths that begin with `https://github.com` but should not
* be processed by the plugin.
*/
const knownPaths = [
"about",
"account",
"codespaces",
"dashboard",
"discussions",
"explore",
"gist",
"github-copilot",
"issues",
"logout",
"marketplace",
"mine",
"new",
"organizations",
"projects",
"pulls",
"search",
"security",
"settings",
"sponsors",
];
export function isAllowedPath(link: string): boolean {
try {
const url = new URL(link);
const basePath = url.pathname.split("/")[1];
if (knownPaths.includes(basePath)) {
return false;
}
} catch (err) {
return false;
}
return true;
}

View file

@ -8,14 +8,18 @@ import { RequestError } from "../util";
import { parseUrl } from "../github/url-parse";
import type { ParsedUrl } from "../github/url-parse";
import { getIssue, getPullRequest } from "../github/github";
import { isAllowedPath } from "../github/urls";
interface TagConfig {
icon: HTMLSpanElement;
sections: HTMLElement[];
}
export function createTag(href: string): HTMLAnchorElement {
export function createTag(href: string): HTMLAnchorElement | null {
const parsedUrl = parseUrl(href);
if (!parsedUrl) {
return null;
}
const container = createEl("a", { cls: "github-link-inline", href, attr: { target: "_blank" } });
const config: TagConfig = {
icon: createSpan({ cls: ["github-link-status-icon", "github-link-inline-icon"] }),
@ -162,11 +166,17 @@ function createErrorSection(config: TagConfig, container: HTMLAnchorElement, err
}
export function InlineRenderer(el: HTMLElement) {
const githubLinks = el.querySelectorAll<HTMLAnchorElement>(`a.external-link[href^="https://github.com"]`);
let githubLinks = Array.from(el.querySelectorAll<HTMLAnchorElement>(`a.external-link[href^="https://github.com"]`));
// Filter out some special URLs from github
githubLinks = githubLinks.filter((l) => isAllowedPath(l.href));
for (const anchor of Array.from(githubLinks)) {
if (anchor.href === anchor.innerText) {
const container = createTag(anchor.href);
anchor.replaceWith(container);
if (container) {
anchor.replaceWith(container);
}
}
}
}

View file

@ -3,6 +3,7 @@ import type { DecorationSet, PluginValue, EditorView, ViewUpdate } from "@codemi
import { Decoration, MatchDecorator, ViewPlugin, WidgetType } from "@codemirror/view";
import type { GithubLinkPlugin } from "../plugin";
import { isAllowedPath } from "../github/urls";
import { createTag } from "./inline";
interface DecoSpec {
@ -28,7 +29,9 @@ class InlineTagWidget extends WidgetType {
toDOM(): HTMLElement {
const container = createSpan();
const tag = createTag(this.href);
container.appendChild(tag);
if (tag) {
container.appendChild(tag);
}
return container;
}
}
@ -83,6 +86,11 @@ export function createInlineViewPlugin(_plugin: GithubLinkPlugin) {
return false;
}
// Check if it's not an allowed URL
if (!isAllowedPath(match[0])) {
return false;
}
// Check if we're in a codeblock
// Note, codeblock check is more expensive than some others,
// But we do it first to ensure this.inCodeblock remains accurate
@ -116,7 +124,7 @@ export function createInlineViewPlugin(_plugin: GithubLinkPlugin) {
}
// Ignore matches inside a markdown link
// TODO: This could probably be a regex.
// TODO: This can probably be removed now that the regex is updated
const input = match.input ?? "";
const index = match.index ?? 0;
const matchValue = match[0];

View file

@ -57,7 +57,7 @@ export const CommonIssuePRColumns: ColumnsMap = {
el.classList.add("github-link-table-repo");
const url = repoAPIToBrowserUrl((row as IssueListResponse[number]).repository_url);
const parsed = parseUrl(url);
el.createEl("a", { text: parsed.repo, href: url, attr: { target: "_blank" } });
el.createEl("a", { text: parsed?.repo ?? "Repo", href: url, attr: { target: "_blank" } });
},
},
author: {

View file

@ -36,7 +36,9 @@ export const IssueColumns: ColumnsMap = {
return;
}
const tag = createTag(pullRequestUrl);
el.appendChild(tag);
if (tag) {
el.appendChild(tag);
}
},
},
};