接入原生壳剪贴板读取能力

新增 clipboard.readText HostBridge 契约和 H5 facade

移动端通过 expo-clipboard 读取纯文本剪贴板

桌面端通过 Tauri clipboard-manager 读取纯文本剪贴板

更新壳能力检查、测试、方案文档和共享决策记录
This commit is contained in:
2026-06-18 04:38:16 +08:00
parent bbfe4b7181
commit c7a24fba37
13 changed files with 158 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ import { describe, expect, test } from 'vitest';
import {
isHostBridgeCapability,
normalizeHostBridgeBadgeCount,
normalizeHostBridgeClipboardText,
normalizeHostBridgeColorScheme,
normalizeHostBridgeConnectionType,
normalizeHostBridgeExportFileName,
@@ -55,6 +56,7 @@ describe('HostBridge shared contract helpers', () => {
expect(isHostBridgeCapability('app.lifecycle')).toBe(true);
expect(isHostBridgeCapability('network.status')).toBe(true);
expect(isHostBridgeCapability('network.statusChanged')).toBe(true);
expect(isHostBridgeCapability('clipboard.readText')).toBe(true);
expect(isHostBridgeCapability('file.importImage')).toBe(true);
expect(isHostBridgeCapability('file.imageDropped')).toBe(true);
expect(isHostBridgeCapability('app.setBadgeCount')).toBe(true);
@@ -64,6 +66,16 @@ describe('HostBridge shared contract helpers', () => {
expect(isHostBridgeCapability(null)).toBe(false);
});
test('归一化宿主剪贴板读取文本', () => {
expect(normalizeHostBridgeClipboardText('作品号 PZ-1')).toEqual({
text: '作品号 PZ-1',
});
expect(normalizeHostBridgeClipboardText('a'.repeat(100010))).toEqual({
text: 'a'.repeat(100000),
});
expect(normalizeHostBridgeClipboardText(null)).toBeNull();
});
test('归一化宿主本地通知内容', () => {
expect(
normalizeHostBridgeLocalNotification({

View File

@@ -26,6 +26,7 @@ export const HOST_BRIDGE_METHODS = [
'app.setBadgeCount',
'network.status',
'clipboard.writeText',
'clipboard.readText',
'file.exportText',
'file.exportImage',
'file.importImage',
@@ -260,6 +261,24 @@ export type ClipboardWriteTextPayload = {
text: string;
};
export type ClipboardReadTextResult = {
text: string;
};
export const HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH = 100000;
export function normalizeHostBridgeClipboardText(
rawText: unknown,
): ClipboardReadTextResult | null {
if (typeof rawText !== 'string') {
return null;
}
return {
text: rawText.slice(0, HOST_BRIDGE_CLIPBOARD_TEXT_MAX_LENGTH),
};
}
export type FileExportTextPayload = {
fileName: string;
content: string;