mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-27 10:04:58 +00:00
fix(web): preserve selected text when copying over HTTP (#2120)
Co-authored-by: chenyicun <chenyicun@msh.team>
This commit is contained in:
parent
3615b5da9f
commit
0d00a07c02
4 changed files with 46 additions and 14 deletions
5
.changeset/fix-web-clipboard-paste.md
Normal file
5
.changeset/fix-web-clipboard-paste.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
web: Fix copying selected chat text over plain HTTP from replacing the clipboard with an event placeholder.
|
||||
|
|
@ -16,7 +16,7 @@ import { useIsDark } from '../../composables/useIsDark';
|
|||
import type { FilePreviewRequest } from '../../types';
|
||||
import { collectFilePathAliases, findFilePathLinks } from '../../lib/filePathLinks';
|
||||
import { markdownRenderPlan } from '../../lib/markdownPerformance';
|
||||
import { copyTextToClipboard } from '../../lib/clipboard';
|
||||
import { copyCodeBlockFallback, copyTextToClipboard } from '../../lib/clipboard';
|
||||
import * as katexWorkerModule from 'markstream-vue/workers/katexRenderer.worker?worker&type=module';
|
||||
import * as mermaidWorkerModule from 'markstream-vue/workers/mermaidParser.worker?worker&type=module';
|
||||
import Tooltip from '../ui/Tooltip.vue';
|
||||
|
|
@ -338,15 +338,6 @@ const codeBlockProps = {
|
|||
loading: false,
|
||||
};
|
||||
|
||||
function copyCodeBlockFallback(code: string): void {
|
||||
// markstream emits `copy` even when it skipped the write because the
|
||||
// Clipboard API is unavailable. Reuse our plain-HTTP fallback in that case,
|
||||
// while avoiding a duplicate write after markstream succeeds on HTTPS.
|
||||
const clipboard = typeof navigator !== 'undefined' ? navigator.clipboard : undefined;
|
||||
if (clipboard && typeof clipboard.writeText === 'function') return;
|
||||
void copyTextToClipboard(code);
|
||||
}
|
||||
|
||||
// Root cause for the "large session turns into code skeletons" failure:
|
||||
// markstream mounts every code block in the loaded transcript, then shiki has
|
||||
// to tokenize all of them. `loading: false` removes the visible skeleton gate,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,18 @@ export async function copyTextToClipboard(text: string): Promise<boolean> {
|
|||
return legacyCopy(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete markstream's code-block copy on plain HTTP without intercepting
|
||||
* native selection-copy events that bubble through MarkdownRender.
|
||||
*/
|
||||
export function copyCodeBlockFallback(payload: unknown): void {
|
||||
if (typeof payload !== 'string') return;
|
||||
|
||||
const clipboard = typeof navigator !== 'undefined' ? navigator.clipboard : undefined;
|
||||
if (clipboard && typeof clipboard.writeText === 'function') return;
|
||||
void copyTextToClipboard(payload);
|
||||
}
|
||||
|
||||
function legacyCopy(text: string): boolean {
|
||||
if (typeof document === 'undefined' || typeof document.execCommand !== 'function') {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// Scenario: clipboard writes in secure and plain-HTTP web contexts.
|
||||
// Responsibilities: preserve native selection copies and provide the legacy
|
||||
// code-block fallback. The test stubs only navigator/document browser APIs.
|
||||
// Run: pnpm --filter @moonshot-ai/kimi-web test -- clipboard.test.ts
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { copyTextToClipboard } from '../src/lib/clipboard';
|
||||
|
||||
// The web test suite runs in the default node environment (no jsdom), so we
|
||||
// mock the tiny `navigator` / `document` surface that the helper touches.
|
||||
import { copyCodeBlockFallback, copyTextToClipboard } from '../src/lib/clipboard';
|
||||
|
||||
interface FakeDocument {
|
||||
execCommand: ReturnType<typeof vi.fn>;
|
||||
|
|
@ -78,3 +79,26 @@ describe('copyTextToClipboard', () => {
|
|||
await expect(copyTextToClipboard('nope')).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('code-block copy fallback', () => {
|
||||
it('does not overwrite selected text when a native copy event bubbles on plain HTTP', () => {
|
||||
installNavigator(undefined);
|
||||
const doc = installDocument(true);
|
||||
const copyEvent = { toString: () => '[object ClipboardEvent]' };
|
||||
|
||||
copyCodeBlockFallback(copyEvent);
|
||||
|
||||
expect(doc.execCommand).not.toHaveBeenCalled();
|
||||
expect(doc.textarea.value).toBe('');
|
||||
});
|
||||
|
||||
it('copies emitted code text when the Clipboard API is unavailable', () => {
|
||||
installNavigator(undefined);
|
||||
const doc = installDocument(true);
|
||||
|
||||
copyCodeBlockFallback('const host = "example.test";');
|
||||
|
||||
expect(doc.execCommand).toHaveBeenCalledWith('copy');
|
||||
expect(doc.textarea.value).toBe('const host = "example.test";');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue