Skip to content

Conversation

gadenbuie
Copy link
Collaborator

@gadenbuie gadenbuie commented Sep 5, 2025

Fixes #11

pkgload::load_all("pkg-r")

output_markdown_stream(
  "foo",
  content = "Here's a link to [wikipedia](https://wikipedia.org)."
)

output_markdown_stream(
  "foo",
  content = "Here's a link to [wikipedia](https://wikipedia.org).",
  content_type = "semi-markdown"
)

@gadenbuie gadenbuie force-pushed the fix/11-links-in-chat branch from cca8f4a to a227602 Compare September 5, 2025 13:24
@gadenbuie gadenbuie force-pushed the fix/11-links-in-chat branch from a227602 to 0b3e7b0 Compare September 5, 2025 13:28
@gadenbuie gadenbuie requested a review from cpsievert September 5, 2025 13:28
@cpsievert
Copy link
Collaborator

Have you tested that this works? I can confirm the rel="noopener noreferrer" is getting added, but it appears the target="_blank" gets stripped out, probably by DOMpurify cure53/DOMPurify#317

@gadenbuie
Copy link
Collaborator Author

Have you tested that this works? I can confirm the rel="noopener noreferrer" is getting added, but it appears the target="_blank" gets stripped out, probably by DOMpurify cure53/DOMPurify#317

Oh shoot, yeah I saw the rel attribute was added and thought it was both. I'll fix the sanitization too

Now a two step process:
* Markdown renderer adds `data-external-link` to external links
* DOMPurify adds `target="_blank" rel="noopener noreferrer"` to links with that attribute

Otherwise sanitization strips out the target attribute.
@gadenbuie
Copy link
Collaborator Author

@cpsievert okay, now it works! 😃

Comment on lines +624 to +629
window.open(linkEl.href, "_blank", "noopener,noreferrer")
}
})
.catch(() => {
// If dialog fails for any reason, fall back to opening the link directly
window.open(linkEl.href, "_blank", "noopener,noreferrer")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're opening links this way, seems we could drop the afterSanitizeAttributes hook?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do we want to keep it for non-chat situations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember deleting them, but maybe they crept back in?

})
}

private createDialog(): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this broken down into 2 methods: createDialog(): HTMLElement and attachDialogListeners(x: HTMLElement): void. Then do all state mutation at the top-level in connectedCallback()

Comment on lines +169 to +170
// Check if the browser supports HTMLDialogElement
if (typeof window.HTMLDialogElement !== "undefined") {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm not sure if this is needed? It'd be a surprise to me if Bootstrap depended on <dialog>, and also the browser support is pretty good https://caniuse.com/?search=HTMLDialog

}
}

export class ChatExternalLinkDialog extends LightElement {
Copy link
Collaborator

@cpsievert cpsievert Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be used in other non-chat scenarios, right? I don't want to make this a requirement, but perhaps worth noting that we could abstract this and re-use if/when we decide to do tool approval?

Suggested change
export class ChatExternalLinkDialog extends LightElement {
export class ExternalLinkDialog extends LightElement {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good idea! But we own all the code here so we can just rename this when we get to that

@@ -380,6 +382,7 @@ class ChatContainer extends LightElement {
)

this.inputSentinelObserver.observe(sentinel)
this._boundOnExternalLinkClick = this.#onExternalLinkClick.bind(this)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised the .bind() is necessary?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we need it because we and the event listener to the window and not the chat container element

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Small suggestion - render links so that they open in a new tab
2 participants