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
6 changes: 4 additions & 2 deletions apps/mobile/modules/t3-markdown-text/src/markdownLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import type { MARKDOWN_FILE_ICON_SOURCES } from "./markdownFileIcons.generated";
const WINDOWS_DRIVE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
const WINDOWS_UNC_PATH_PATTERN = /^\\\\/;
const RELATIVE_PATH_PREFIX_PATTERN = /^(~\/|\.{1,2}\/)/;
const RELATIVE_FILE_PATH_PATTERN = /^[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+(?::\d+){0,2}$/;
const RELATIVE_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+\.[A-Za-z0-9_-]+(?::\d+){0,2}$/;
const RELATIVE_FILE_PATH_PATTERN =
/^(?:[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\/)+[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*(?::\d+){0,2}$/;
const RELATIVE_FILE_NAME_PATTERN =
/^[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\.[A-Za-z0-9_-]+(?::\d+){0,2}$/;
const POSITION_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
const POSIX_FILE_ROOT_PREFIXES = [
"/Users/",
Expand Down
18 changes: 18 additions & 0 deletions apps/mobile/src/lib/markdownLinks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ describe("resolveMarkdownLinkPresentation", () => {
});
});

it.each(["md", "html", "xml"])("recognizes a bare spaced .%s filename", (extension) => {
expect(
resolveMarkdownLinkPresentation(`Updated%20cutover%20checklist.${extension}`),
).toMatchObject({
kind: "file",
path: `Updated cutover checklist.${extension}`,
label: `Updated cutover checklist.${extension}`,
});
});

it("recognizes spaced relative paths", () => {
expect(resolveMarkdownLinkPresentation("docs/My%20Folder/checklist.xml")).toMatchObject({
kind: "file",
path: "docs/My Folder/checklist.xml",
label: "checklist.xml",
});
});

it("extracts line fragments from relative file links", () => {
expect(resolveMarkdownLinkPresentation("src/main.ts#L18C2")).toMatchObject({
kind: "file",
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
resolveInlineCodeFileLinkMeta,
resolveMarkdownFileLinkMeta,
rewriteMarkdownFileUriHref,
shouldOpenMarkdownFileLinkInBrowserByDefault,
shouldOpenMarkdownFileLinkInEditor,
type MarkdownFileLinkMeta,
} from "../markdown-links";
Expand Down Expand Up @@ -1394,7 +1395,7 @@ const MarkdownFileLink = memo(function MarkdownFileLink({
handleOpenInEditor();
return;
}
if (onOpenInBrowser) {
if (onOpenInBrowser && shouldOpenMarkdownFileLinkInBrowserByDefault(iconPath)) {
handleOpenInBrowser();
return;
}
Expand Down
24 changes: 24 additions & 0 deletions apps/web/src/markdown-links.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
resolveMarkdownFileLinkMeta,
resolveMarkdownFileLinkTarget,
rewriteMarkdownFileUriHref,
shouldOpenMarkdownFileLinkInBrowserByDefault,
shouldOpenMarkdownFileLinkInEditor,
} from "./markdown-links";

Expand Down Expand Up @@ -69,6 +70,15 @@ describe("shouldOpenMarkdownFileLinkInEditor", () => {
});
});

describe("shouldOpenMarkdownFileLinkInBrowserByDefault", () => {
it("keeps PDFs browser-first while source files open in the file viewer", () => {
expect(shouldOpenMarkdownFileLinkInBrowserByDefault("report.pdf")).toBe(true);
expect(shouldOpenMarkdownFileLinkInBrowserByDefault("report.PDF?download=1")).toBe(true);
expect(shouldOpenMarkdownFileLinkInBrowserByDefault("report.html")).toBe(false);
expect(shouldOpenMarkdownFileLinkInBrowserByDefault("report.xml")).toBe(false);
});
});

describe("rewriteMarkdownFileUriHref", () => {
it("rewrites file uri hrefs into direct path hrefs", () => {
expect(rewriteMarkdownFileUriHref("file:///Users/julius/project/src/main.ts#L42")).toBe(
Expand Down Expand Up @@ -198,6 +208,20 @@ describe("resolveMarkdownFileLinkTarget", () => {
});
});

it.each(["md", "html", "xml"])(
"resolves a bare spaced .%s filename from the markdown renderer",
(extension) => {
const href = renderMarkdownLinkHref(`[checklist](<Updated cutover checklist.${extension}>)`);

expect(href).toBe(`Updated%20cutover%20checklist.${extension}`);
expect(resolveMarkdownFileLinkMeta(href, "/repo/project")).toMatchObject({
targetPath: `/repo/project/Updated cutover checklist.${extension}`,
workspaceRelativePath: `Updated cutover checklist.${extension}`,
basename: `Updated cutover checklist.${extension}`,
});
},
);

it("formats tooltip display paths relative to the cwd for slash-prefixed windows paths", () => {
expect(
resolveMarkdownFileLinkMeta(
Expand Down
7 changes: 6 additions & 1 deletion apps/web/src/markdown-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const EXTERNAL_SCHEME_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):(.*)$/;
const RELATIVE_PATH_PREFIX_PATTERN = /^(~\/|\.{1,2}\/)/;
const RELATIVE_FILE_PATH_PATTERN =
/^(?:[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\/)+[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*(?::\d+){0,2}$/;
const RELATIVE_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+\.[A-Za-z0-9_-]+(?::\d+){0,2}$/;
const RELATIVE_FILE_NAME_PATTERN =
/^[A-Za-z0-9._-]+(?: +[A-Za-z0-9._-]+)*\.[A-Za-z0-9_-]+(?::\d+){0,2}$/;
const POSITION_SUFFIX_PATTERN = /:\d+(?::\d+)?$/;
const POSITION_ONLY_PATTERN = /^\d+(?::\d+)?$/;
// Standard OS and dev-container roots; deliberately excludes app-route-ish
Expand Down Expand Up @@ -71,6 +72,10 @@ export function shouldOpenMarkdownFileLinkInEditor(
return isTerminalLinkActivation(event, platform);
}

export function shouldOpenMarkdownFileLinkInBrowserByDefault(path: string): boolean {
return /\.pdf$/i.test(path.split(/[?#]/, 1)[0] ?? "");
}

function safeDecode(value: string): string {
try {
return decodeURIComponent(value);
Expand Down
Loading