Skip to content
Open
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
27 changes: 17 additions & 10 deletions openbot/app/src/components/duet/page-text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,23 @@ export function PageText({ text }: { text: string }) {
let cursor = 0;
for (const match of text.matchAll(/https?:\/\/[^\s<>"']+/g)) {
const start = match.index;
let source = match[0].replace(/[.,;:!?]+$/, "");
for (const [open, close] of [
["(", ")"],
["[", "]"],
]) {
while (
source.endsWith(close) &&
source.split(close).length > source.split(open).length
) {
source = source.slice(0, -1);
// A period or comma can sit just inside the closing bracket. Trim, peel
// unmatched closers, and trim again until the suffix stops changing.
let source = match[0];
let previous = "";
while (source !== previous) {
previous = source;
source = source.replace(/[.,;:!?]+$/, "");
for (const [open, close] of [
["(", ")"],
["[", "]"],
]) {
while (
source.endsWith(close) &&
source.split(close).length > source.split(open).length
) {
source = source.slice(0, -1);
}
}
}
let url: URL;
Expand Down
15 changes: 15 additions & 0 deletions openbot/app/tests/pages.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,21 @@ test("source links keep balanced URL parentheses and support HTTP sources", () =
expect(view.container.textContent).toBe(text);
});

test("a bracketed citation drops sentence punctuation from the link", () => {
const text =
"(See https://example.com/pricing.) [See https://example.org/plans,] (https://example.org/plans). https://example.com/wiki/Project_(software)";
const view = render(<PageBlocks blocks={[{ type: "text", text }]} />);
expect(
view.getAllByRole("link").map((link) => link.getAttribute("href")),
).toEqual([
"https://example.com/pricing",
"https://example.org/plans",
"https://example.org/plans",
"https://example.com/wiki/Project_(software)",
]);
expect(view.container.textContent).toBe(text);
});

test("source text never turns markup, unsafe schemes or URL credentials into links", () => {
const text =
"<script>alert(1)</script> javascript:alert(1) data:text/html,unsafe https://user:secret@example.com/private https://";
Expand Down