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
127 changes: 112 additions & 15 deletions develop-docs/sdk/getting-started/templates/saved-replies/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,29 @@ Using these replies is encouraged - they give everyone permission to politely up

To set up saved replies go to [GitHub > Settings > Saved replies](https://github.com/settings/replies), and add all of the replies below.

---
## Pull Requests

<CopyableCard title="Open an issue first (behavior/refactor -> close)" description={`Thanks for the contribution! We ask that behavioral changes and refactors have a linked issue so we can discuss the approach before a PR is opened. Could you open an issue describing the problem you're solving and your proposed approach? Closing this for now - happy to revisit once there's an issue to reference.
### Closing Pull Requests

Please also have a look at our CONTRIBUTING.md for more PR guidelines.`}>
<CopyableCard title="Open an issue first (behavior/refactor -> close)">

Thanks for the contribution! We ask that behavioral changes and refactors have a linked issue so we can discuss the approach before a PR is opened. Could you open an issue describing the problem you're solving and your proposed approach? Closing this for now - happy to revisit once there's an issue to reference.

Please also have a look at our CONTRIBUTING.md for more PR guidelines.

</CopyableCard>

<CopyableCard title="Let's discuss the approach first (idea -> close)" description={`This is an interesting idea! We'd like to align on the approach before reviewing code - could you open an issue describing the problem and your proposed solution? That way we can agree on direction first. Closing this PR for now.

Please also have a look at our CONTRIBUTING.md for more PR guidelines.`}>
<CopyableCard title="Let's discuss the approach first (idea -> close)">

This is an interesting idea! We'd like to align on the approach before reviewing code - could you open an issue describing the problem and your proposed solution? That way we can agree on direction first. Closing this PR for now.

Please also have a look at our CONTRIBUTING.md for more PR guidelines.

</CopyableCard>

<CopyableCard title="Not ready for review (request changes / mark as draft)" description={`Thanks for the contribution! This PR needs some updates before we can review:

- [ ] CI checks are passing
- [ ] PR description explains what and why
- [ ] Linked issue exists
- [ ] Tests are included

We marked it as draft for now. Please update and we'll take another look.
### Requesting Changes

Please also have a look at our CONTRIBUTING.md for more PR guidelines.`}>
<CopyableCard title="Not ready for review (request changes / mark as draft)">

Thanks for the contribution! This PR needs some updates before we can review:

Expand All @@ -55,3 +46,109 @@ We marked it as draft for now. Please update and we'll take another look.
Please also have a look at our CONTRIBUTING.md for more PR guidelines.

</CopyableCard>


## Issues

### Closing Issues

<CopyableCard title="Issue/Close: Inactivity">

Closing this issue due to inactivity. If this is still relevant for you, please feel free to reopen or create a new issue with up-to-date information and we'll be happy to take another look.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: No response">

We haven't heard back on this, so we're closing it for now. If you're able to provide the requested information, please feel free to comment and we'll reopen and take another look.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: Can't reproduce">

We weren't able to reproduce this on our end. If you're still experiencing this issue and can share a minimal reproduction or additional details on how to trigger it, please feel free to comment and we'll reopen.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: Outdated / Superseded">

Closing this as it has been superseded by newer SDK or platform versions, or by other issues that cover this topic. If you believe this is still relevant with a current SDK version, please feel free to comment with updated information and we'll reopen.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: Duplicate">

This appears to be a duplicate of #ISSUE_NUMBER, so we're closing this to keep the discussion in one place. Please follow that issue for updates and feel free to add any additional context there.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: Working as intended">

After looking into this, we believe the current behavior is working as intended. EXPLAIN_REASON_HERE

If you have a use case that isn't covered or think we've misunderstood the issue, please feel free to comment and we can revisit.

Thank you for taking the time to report this!

</CopyableCard>

<CopyableCard title="Issue/Close: Wrong repo">

It looks like this issue belongs in a different repository. Could you please open it in REPO_LINK instead? That team will be better equipped to help.

Closing this for now. Thank you for taking the time to report this!

</CopyableCard>

### Requesting Information

<CopyableCard title="Issue/Request: More information needed">

Thanks for reporting this! To help us investigate, could you provide a few more details?

- **SDK version**: (e.g. 1.2.3)
- **Platform/OS version**: (e.g. iOS 17, Android 14, Node 20)
- **Steps to reproduce**: What were you doing when this occurred?
- **Expected behavior**: What did you expect to happen?
- **Actual behavior**: What happened instead?
- **Logs or stack traces**: If available

This will help us narrow things down. Thank you!

</CopyableCard>

<CopyableCard title="Issue/Request: Minimal reproduction needed">

Thanks for reporting this! We'd love to look into it further, but we need a minimal reproduction to investigate effectively. Could you provide a small, self-contained project or code snippet that demonstrates the issue?

A good minimal reproduction:
- Uses the latest SDK version
- Contains only the code necessary to trigger the issue
- Includes steps to run and observe the behavior

This helps us rule out project-specific factors and get to the root cause faster. Thank you!

</CopyableCard>

### Redirecting to Support

<CopyableCard title="Issue/Redirect: Support channel">

Thanks for reaching out! This looks like a usage or configuration question rather than a bug report or feature request. For the fastest help, we'd recommend reaching out through one of our support channels:

- [Sentry Discord](https://discord.gg/sentry)
- [Sentry Support](https://help.sentry.io/)

Our support team and community are well-equipped to help with setup and usage questions. If it turns out there is a bug, please feel free to open a new issue with reproduction steps and we'll investigate.

</CopyableCard>
85 changes: 75 additions & 10 deletions src/components/copyableCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,74 @@ import {Clipboard} from 'react-feather';

import Chevron from 'sentry-docs/icons/Chevron';

/**
* Converts a DOM tree back to markdown so that copied saved replies
* preserve formatting (checkboxes, bold, links, lists) when pasted
* into GitHub's markdown editor.
*/
function domToMarkdown(node: Node): string {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent ?? '';
}

if (node.nodeType !== Node.ELEMENT_NODE) {
return '';
}

const el = node as HTMLElement;
const tag = el.tagName.toLowerCase();

// Self-closing / special elements
if (tag === 'br') {
return '\n';
}
if (tag === 'input' && el.getAttribute('type') === 'checkbox') {
return (el as HTMLInputElement).checked ? '[x] ' : '[ ] ';
}

const childText = Array.from(el.childNodes).map(domToMarkdown).join('');

switch (tag) {
case 'strong':
case 'b':
return `**${childText}**`;
case 'em':
case 'i':
return `*${childText}*`;
case 'a':
return `[${childText}](${el.getAttribute('href') ?? ''})`;
case 'p':
return `${childText}\n\n`;
case 'li': {
const parent = el.parentElement;
if (parent?.tagName.toLowerCase() === 'ol') {
const index = Array.from(parent.children).indexOf(el as HTMLLIElement) + 1;
return `${index}. ${childText}\n`;
}
return `- ${childText}\n`;
}
case 'ul':
case 'ol':
return `${childText}\n`;
case 'code':
return `\`${childText}\``;
default:
return childText;
}
}

interface CopyableCardProps {
children: React.ReactNode;
description: string;
title: string;
}

export function CopyableCard({title, description, children}: CopyableCardProps) {
const [copiedItem, setCopiedItem] = useState<'title' | 'description' | null>(null);
export function CopyableCard({title, children}: CopyableCardProps) {
const [copiedItem, setCopiedItem] = useState<'title' | 'body' | null>(null);
const [isOpen, setIsOpen] = useState(false);
const [isMounted, setIsMounted] = useState(false);
const buttonRef = useRef<HTMLDivElement>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);

useEffect(() => {
setIsMounted(true);
Expand All @@ -39,7 +95,7 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
};
}, []);

async function copyText(text: string, item: 'title' | 'description') {
async function copyText(text: string, item: 'title' | 'body') {
try {
await navigator.clipboard.writeText(text.trim());
setCopiedItem(item);
Expand Down Expand Up @@ -67,12 +123,19 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
const dropdownItemClass =
'w-full p-2 px-3 text-left text-sm bg-transparent border-none rounded-md transition-colors hover:bg-gray-100 dark:hover:bg-[var(--gray-a4)] font-sans text-gray-900 dark:text-[var(--foreground)] cursor-pointer';

function getBodyText(): string {
if (!contentRef.current) {
return '';
}
return domToMarkdown(contentRef.current).trim();
}

const getButtonLabel = () => {
if (copiedItem === 'title') {
return 'Reply title copied!';
}
if (copiedItem === 'description') {
return 'Reply description copied!';
if (copiedItem === 'body') {
return 'Reply body copied!';
}
return 'Copy';
};
Expand All @@ -89,7 +152,7 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
<div className="relative inline-block" ref={buttonRef}>
<div className="inline-flex items-center h-8 border border-gray-200 dark:border-[var(--gray-6)] rounded-full overflow-hidden bg-white dark:bg-[var(--gray-2)]">
<button
onClick={() => copyText(description, 'description')}
onClick={() => copyText(getBodyText(), 'body')}
className={`${buttonClass} gap-1.5 px-3 text-sm font-medium`}
style={{borderRadius: '9999px 0 0 9999px'}}
>
Expand Down Expand Up @@ -129,10 +192,10 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
Reply title
</button>
<button
onClick={() => copyText(description, 'description')}
onClick={() => copyText(getBodyText(), 'body')}
className={dropdownItemClass}
>
Reply description
Reply body
</button>
</div>
</div>,
Expand All @@ -142,7 +205,9 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
)}
</div>
<div className="p-4 bg-white dark:bg-[var(--gray-1)]">
<div className="prose dark:prose-invert max-w-none">{children}</div>
<div ref={contentRef} className="prose dark:prose-invert max-w-none">
{children}
</div>
</div>
</div>
);
Expand Down
Loading