fix: preserve cjk code block copy text

This commit is contained in:
lucaronin
2026-05-14 07:48:04 +02:00
parent 2fcea0624d
commit 95dab219b8
3 changed files with 41 additions and 2 deletions

View File

@@ -796,6 +796,31 @@ describe('SingleEditorView', () => {
expect(clipboardData.setData).toHaveBeenCalledWith('text/plain', json)
})
it('copies CJK fenced code text from the selected DOM fragment when selection text is mojibake', async () => {
const text = 'const label = "中文測試"'
const { container } = renderEditorHarness()
const { codeBlock, code } = createCodeBlockFixture(text)
await act(async () => {
container.appendChild(codeBlock)
await Promise.resolve()
})
const range = document.createRange()
range.selectNodeContents(code)
const getSelection = vi.spyOn(window, 'getSelection').mockReturnValue({
rangeCount: 1,
isCollapsed: false,
getRangeAt: () => range,
toString: () => 'const label = "中文測試"',
} as Selection)
const clipboardData = { setData: vi.fn() }
fireEvent.copy(code, { clipboardData })
getSelection.mockRestore()
expect(clipboardData.setData).toHaveBeenCalledWith('text/plain', text)
})
it('copies fenced code from the code-block action button', async () => {
const json = '{\n "id": "Demo"\n}'
const writeText = vi.fn().mockResolvedValue(undefined)

View File

@@ -421,7 +421,7 @@ function selectedCodeBlockText(options: {
const range = selectedCodeBlockRange(options)
if (!range) return null
return options.selection?.toString() || range.cloneContents().textContent || ''
return range.cloneContents().textContent || options.selection?.toString() || ''
}
function selectedEditorRange(selection: Selection | null, container: HTMLElement): Range | null {

View File

@@ -12,6 +12,7 @@ const CODE_COPY_NOTE_RELATIVE_PATH = path.join('note', 'fenced-code-copy.md')
const CODE_COPY_NOTE_TITLE = 'Fenced Code Copy'
const JSON_SNIPPET = '{\n "id": "Demo",\n "enabled": true\n}'
const TYPESCRIPT_SNIPPET = 'const answer = 42\nconsole.log(answer)'
const CJK_SNIPPET = 'const label = "中文測試"\nconsole.log(label)'
const RICH_CODE_SELECTOR = '.bn-block-content[data-content-type="codeBlock"] pre code'
function writeCodeCopyFixtureNote(tempVaultDir: string) {
@@ -35,6 +36,12 @@ Copy this TypeScript exactly:
\`\`\`ts
${TYPESCRIPT_SNIPPET}
\`\`\`
Copy this CJK text exactly:
\`\`\`ts
${CJK_SNIPPET}
\`\`\`
`)
}
@@ -101,6 +108,8 @@ async function selectRawEditorText(page: Page, text: string) {
test.describe('Fenced code copy', () => {
let tempVaultDir: string
test.setTimeout(30_000)
test.beforeEach(async ({ page }) => {
await page.context().grantPermissions(['clipboard-read', 'clipboard-write'])
tempVaultDir = createFixtureVaultCopy()
@@ -112,14 +121,16 @@ test.describe('Fenced code copy', () => {
removeFixtureVaultCopy(tempVaultDir)
})
test('copies rich code blocks from the button and selections without escape backslashes', async ({ page }) => {
test('copies rich code blocks from the button and selections without corruption', async ({ page }) => {
const noteList = page.locator('[data-testid="note-list-container"]')
const noteItem = noteList.getByText(CODE_COPY_NOTE_TITLE, { exact: true })
await expect(noteItem).toBeVisible({ timeout: 10_000 })
await noteItem.click()
await expect(page.locator(RICH_CODE_SELECTOR)).toHaveCount(3, { timeout: 10_000 })
await expect.poll(() => copyRichCodeBlockFromButton(page, 0)).toBe(JSON_SNIPPET)
await expect.poll(() => copyRichCodeBlockFromButton(page, 1)).toBe(TYPESCRIPT_SNIPPET)
await expect.poll(() => copyRichCodeBlockFromButton(page, 2)).toBe(CJK_SNIPPET)
await selectRichCodeBlock(page, 0)
await expect.poll(() => copySelectedText(page)).toBe(JSON_SNIPPET)
@@ -127,6 +138,9 @@ test.describe('Fenced code copy', () => {
await selectRichCodeBlock(page, 1)
await expect.poll(() => copySelectedText(page)).toBe(TYPESCRIPT_SNIPPET)
await selectRichCodeBlock(page, 2)
await expect.poll(() => copySelectedText(page)).toBe(CJK_SNIPPET)
await openCommandPalette(page)
await executeCommand(page, 'Toggle Raw')
await selectRawEditorText(page, JSON_SNIPPET)