diff --git a/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx b/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx index 7d1c51852cad3..74eb4063e62f2 100644 --- a/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx +++ b/develop-docs/sdk/getting-started/templates/saved-replies/index.mdx @@ -10,11 +10,11 @@ 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 - + 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. @@ -22,9 +22,7 @@ Please also have a look at our CONTRIBUTING.md for more PR guidelines. - + 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. @@ -32,16 +30,9 @@ Please also have a look at our CONTRIBUTING.md for more PR guidelines. - + Thanks for the contribution! This PR needs some updates before we can review: @@ -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. + + +## Issues + +### Closing Issues + + + +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! + + + + + +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! + + + + + +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! + + + + + +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! + + + + + +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! + + + + + +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! + + + + + +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! + + + +### Requesting Information + + + +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! + + + + + +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! + + + +### Redirecting to Support + + + +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. + + diff --git a/src/components/copyableCard.tsx b/src/components/copyableCard.tsx index 45112bd6c27e2..9c77bc6728334 100644 --- a/src/components/copyableCard.tsx +++ b/src/components/copyableCard.tsx @@ -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(null); const dropdownRef = useRef(null); + const contentRef = useRef(null); useEffect(() => { setIsMounted(true); @@ -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); @@ -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'; }; @@ -89,7 +152,7 @@ export function CopyableCard({title, description, children}: CopyableCardProps)
, @@ -142,7 +205,9 @@ export function CopyableCard({title, description, children}: CopyableCardProps) )}
-
{children}
+
+ {children} +
);