mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix/line-number-wrongly-copied (#11857)
* fix(code-viewer): copy selected code without line numbers * fix(context-menu): strip line numbers from code selection * style(codeviewer): fix format * fix: preserve indentation and format when copying mixed content (text + code blocks) - Replace regex-based extraction with DOM structure-based approach - Remove line number elements while preserving all other content - Use TreeWalker to handle mixed content (text paragraphs + code blocks) - Preserve indentation and newlines in code blocks - Simplify CodeViewer.tsx by removing duplicate context menu logic Fixes #11790 * style: remove unused comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * refactor: optimize TreeWalker performance --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a1f0addafb
commit
4d3d5ae4ce
@ -6,6 +6,61 @@ interface ContextMenuProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract text content from selection, filtering out line numbers in code viewers.
|
||||
* Preserves all content including plain text and code blocks, only removing line numbers.
|
||||
* This ensures right-click copy in code blocks doesn't include line numbers while preserving indentation.
|
||||
*/
|
||||
function extractSelectedText(selection: Selection): string {
|
||||
// Validate selection
|
||||
if (selection.rangeCount === 0 || selection.isCollapsed) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0)
|
||||
const fragment = range.cloneContents()
|
||||
|
||||
// Check if the selection contains code viewer elements
|
||||
const hasLineNumbers = fragment.querySelectorAll('.line-number').length > 0
|
||||
|
||||
// If no line numbers, return the original text (preserves formatting)
|
||||
if (!hasLineNumbers) {
|
||||
return selection.toString()
|
||||
}
|
||||
|
||||
// Remove all line number elements
|
||||
fragment.querySelectorAll('.line-number').forEach((el) => el.remove())
|
||||
|
||||
// Handle all content using optimized TreeWalker with precise node filtering
|
||||
// This approach handles mixed content correctly while improving performance
|
||||
const walker = document.createTreeWalker(fragment, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null)
|
||||
|
||||
let result = ''
|
||||
let node = walker.nextNode()
|
||||
|
||||
while (node) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
// Preserve text content including whitespace
|
||||
result += node.textContent
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const element = node as Element
|
||||
|
||||
// Add newline after block elements and code lines to preserve structure
|
||||
if (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(element.tagName)) {
|
||||
result += '\n'
|
||||
} else if (element.classList.contains('line')) {
|
||||
// Add newline after code lines to preserve code structure
|
||||
result += '\n'
|
||||
}
|
||||
}
|
||||
|
||||
node = walker.nextNode()
|
||||
}
|
||||
|
||||
// Clean up excessive newlines but preserve code structure
|
||||
return result.trim()
|
||||
}
|
||||
|
||||
// FIXME: Why does this component name look like a generic component but is not customizable at all?
|
||||
const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
|
||||
const { t } = useTranslation()
|
||||
@ -45,8 +100,12 @@ const ContextMenu: React.FC<ContextMenuProps> = ({ children }) => {
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
if (open) {
|
||||
const selectedText = window.getSelection()?.toString()
|
||||
setSelectedText(selectedText)
|
||||
const selection = window.getSelection()
|
||||
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
||||
setSelectedText(undefined)
|
||||
return
|
||||
}
|
||||
setSelectedText(extractSelectedText(selection) || undefined)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user