@talex-touch/utils 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-box/tuff/tuff-dsl.ts +69 -55
- package/package.json +1 -1
- package/plugin/index.ts +5 -0
- package/plugin/sdk/README.md +54 -6
- package/plugin/sdk/clipboard.ts +198 -27
- package/plugin/sdk/enum/bridge-event.ts +1 -0
- package/plugin/sdk/feature-sdk.ts +85 -6
- package/plugin/sdk/flow.ts +246 -0
- package/plugin/sdk/hooks/bridge.ts +18 -1
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/performance.ts +201 -0
- package/plugin/widget.ts +5 -0
- package/renderer/storage/base-storage.ts +98 -15
- package/renderer/storage/storage-subscription.ts +17 -9
- package/search/fuzzy-match.ts +254 -0
- package/types/flow.ts +283 -0
- package/types/index.ts +1 -0
|
@@ -1005,15 +1005,31 @@ export interface TuffMeta {
|
|
|
1005
1005
|
permissions?: string[]
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
|
-
/**
|
|
1009
|
-
* TuffIntelligence
|
|
1010
|
-
*/
|
|
1008
|
+
/** TuffIntelligence */
|
|
1011
1009
|
intelligence?: any
|
|
1012
1010
|
|
|
1011
|
+
/** Keep CoreBox open after action execution */
|
|
1012
|
+
keepCoreBoxOpen?: boolean
|
|
1013
|
+
|
|
1014
|
+
/** 固定配置 */
|
|
1015
|
+
pinned?: {
|
|
1016
|
+
isPinned: boolean
|
|
1017
|
+
pinnedAt?: number
|
|
1018
|
+
order?: number
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/** 推荐来源标记 */
|
|
1022
|
+
recommendation?: {
|
|
1023
|
+
source: 'frequent' | 'recent' | 'time-based' | 'trending' | 'pinned' | 'context'
|
|
1024
|
+
score?: number
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1013
1027
|
/**
|
|
1014
|
-
*
|
|
1028
|
+
* 额外关键词
|
|
1029
|
+
* @description 用于搜索索引的额外关键词,会参与关键字索引匹配
|
|
1030
|
+
* @warning 不建议添加太多关键词(建议 <= 10),过多会影响搜索性能
|
|
1015
1031
|
*/
|
|
1016
|
-
|
|
1032
|
+
keywords?: string[]
|
|
1017
1033
|
}
|
|
1018
1034
|
|
|
1019
1035
|
// ==================== 前端展示结构 ====================
|
|
@@ -1267,71 +1283,69 @@ export interface SortStat {
|
|
|
1267
1283
|
duration: number
|
|
1268
1284
|
}
|
|
1269
1285
|
|
|
1270
|
-
/**
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
*/
|
|
1286
|
+
/** 容器布局配置 */
|
|
1287
|
+
export interface TuffContainerLayout {
|
|
1288
|
+
/** 布局模式 */
|
|
1289
|
+
mode: 'list' | 'grid'
|
|
1290
|
+
/** 宫格配置 */
|
|
1291
|
+
grid?: {
|
|
1292
|
+
/** 列数 */
|
|
1293
|
+
columns: number
|
|
1294
|
+
/** 间距(px) */
|
|
1295
|
+
gap?: number
|
|
1296
|
+
/** 单项尺寸 */
|
|
1297
|
+
itemSize?: 'small' | 'medium' | 'large'
|
|
1298
|
+
}
|
|
1299
|
+
/** 分组配置 */
|
|
1300
|
+
sections?: TuffSection[]
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/** 分组元数据 */
|
|
1304
|
+
export interface TuffSectionMeta {
|
|
1305
|
+
/** 是否为智能推荐分组 */
|
|
1306
|
+
intelligence?: boolean
|
|
1307
|
+
/** 扩展字段 */
|
|
1308
|
+
[key: string]: unknown
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/** 分组定义 */
|
|
1312
|
+
export interface TuffSection {
|
|
1313
|
+
id: string
|
|
1314
|
+
title?: string
|
|
1315
|
+
layout: 'list' | 'grid'
|
|
1316
|
+
/** 该分组的 item ids */
|
|
1317
|
+
itemIds: string[]
|
|
1318
|
+
collapsed?: boolean
|
|
1319
|
+
/** 分组元数据 */
|
|
1320
|
+
meta?: TuffSectionMeta
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/** 搜索结果结构 */
|
|
1277
1324
|
export interface TuffSearchResult {
|
|
1278
|
-
/**
|
|
1279
|
-
* A unique identifier for this specific search operation.
|
|
1280
|
-
* This is crucial for the streaming model to associate updates with the correct search instance.
|
|
1281
|
-
*/
|
|
1325
|
+
/** 搜索会话 ID */
|
|
1282
1326
|
sessionId?: string
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* 结果项目
|
|
1286
|
-
* @description 匹配的TuffItem列表
|
|
1287
|
-
* @required
|
|
1288
|
-
*/
|
|
1327
|
+
/** 结果项目 */
|
|
1289
1328
|
items: TuffItem[]
|
|
1290
1329
|
|
|
1291
|
-
/**
|
|
1292
|
-
* 查询信息
|
|
1293
|
-
* @description 原始查询参数
|
|
1294
|
-
* @required
|
|
1295
|
-
*/
|
|
1330
|
+
/** 原始查询参数 */
|
|
1296
1331
|
query: TuffQuery
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* 搜索耗时
|
|
1300
|
-
* @description 搜索执行的毫秒数
|
|
1301
|
-
* @required
|
|
1302
|
-
*/
|
|
1332
|
+
/** 搜索耗时(ms) */
|
|
1303
1333
|
duration: number
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* 来源统计
|
|
1307
|
-
* @description 各数据来源的结果统计
|
|
1308
|
-
* @required
|
|
1309
|
-
*/
|
|
1334
|
+
/** 来源统计 */
|
|
1310
1335
|
sources: Array<{
|
|
1311
|
-
/** Provider's unique ID. */
|
|
1312
1336
|
providerId: string
|
|
1313
|
-
/** Provider's display name. */
|
|
1314
1337
|
providerName: string
|
|
1315
|
-
/** Search duration in milliseconds. */
|
|
1316
1338
|
duration: number
|
|
1317
|
-
/** Number of results returned. */
|
|
1318
1339
|
resultCount: number
|
|
1319
|
-
/** Status of the search operation. */
|
|
1320
1340
|
status: 'success' | 'timeout' | 'error'
|
|
1321
1341
|
}>
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
* @description AI生成的搜索建议
|
|
1326
|
-
*/
|
|
1342
|
+
/** 容器布局配置 */
|
|
1343
|
+
containerLayout?: TuffContainerLayout
|
|
1344
|
+
/** AI 推荐建议 */
|
|
1327
1345
|
suggestions?: string[]
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* The provider(s) to activate after this search result.
|
|
1331
|
-
*/
|
|
1346
|
+
/** 激活的 provider */
|
|
1332
1347
|
activate?: IProviderActivate[]
|
|
1333
|
-
|
|
1334
|
-
/** Optional statistics about the sorting process. */
|
|
1348
|
+
/** 排序统计 */
|
|
1335
1349
|
sort_stats?: SortStat[]
|
|
1336
1350
|
}
|
|
1337
1351
|
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -142,10 +142,15 @@ export interface IPluginFeature {
|
|
|
142
142
|
name: string
|
|
143
143
|
desc: string
|
|
144
144
|
icon: ITuffIcon
|
|
145
|
+
keywords?: string[]
|
|
145
146
|
push: boolean
|
|
146
147
|
platform: IPlatform
|
|
147
148
|
commands: IFeatureCommand[]
|
|
148
149
|
interaction?: IFeatureInteraction
|
|
150
|
+
/**
|
|
151
|
+
* Internal search tokens generated at runtime for better matching
|
|
152
|
+
*/
|
|
153
|
+
searchTokens?: string[]
|
|
149
154
|
/**
|
|
150
155
|
* Priority of the feature for sorting in search results
|
|
151
156
|
* Higher numbers have higher priority (displayed first)
|
package/plugin/sdk/README.md
CHANGED
|
@@ -66,6 +66,53 @@ const unsubscribe = plugin.feature.onInputChange((input) => {
|
|
|
66
66
|
|
|
67
67
|
// 取消监听
|
|
68
68
|
unsubscribe()
|
|
69
|
+
|
|
70
|
+
// 监听键盘事件(UI View 模式下)
|
|
71
|
+
const unsubscribeKey = plugin.feature.onKeyEvent((event) => {
|
|
72
|
+
if (event.key === 'Enter') {
|
|
73
|
+
// 处理回车键
|
|
74
|
+
submitSelection()
|
|
75
|
+
} else if (event.key === 'ArrowDown') {
|
|
76
|
+
// 向下导航
|
|
77
|
+
selectNext()
|
|
78
|
+
} else if (event.key === 'ArrowUp') {
|
|
79
|
+
// 向上导航
|
|
80
|
+
selectPrev()
|
|
81
|
+
} else if (event.metaKey && event.key === 'k') {
|
|
82
|
+
// 处理 Cmd+K
|
|
83
|
+
openSearch()
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. 键盘事件自动处理
|
|
89
|
+
|
|
90
|
+
当插件的 UI View 附加到 CoreBox 时,系统会自动处理以下行为:
|
|
91
|
+
|
|
92
|
+
#### ESC 键自动退出
|
|
93
|
+
- 在 UI View 中按下 ESC 键会**自动退出 UI 模式**(deactivate providers)
|
|
94
|
+
- 插件无需手动处理 ESC 键的退出逻辑
|
|
95
|
+
- 这与 CoreBox 主界面的 ESC 行为保持一致
|
|
96
|
+
|
|
97
|
+
#### 键盘事件转发
|
|
98
|
+
以下按键会从 CoreBox 主输入框转发到插件 UI View:
|
|
99
|
+
- **Enter** - 确认/提交
|
|
100
|
+
- **ArrowUp / ArrowDown** - 上下导航
|
|
101
|
+
- **Meta/Ctrl + 任意键** - 快捷键组合(Cmd+V 除外,用于粘贴)
|
|
102
|
+
|
|
103
|
+
> **注意**:`ArrowLeft` 和 `ArrowRight` 不会被转发,因为它们用于输入框中的文本编辑。如果需要左右导航,请使用 `Meta/Ctrl + ArrowLeft/ArrowRight`。
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// 键盘事件数据结构
|
|
107
|
+
interface ForwardedKeyEvent {
|
|
108
|
+
key: string // 按键名称,如 'Enter', 'ArrowDown'
|
|
109
|
+
code: string // 按键代码,如 'Enter', 'ArrowDown'
|
|
110
|
+
metaKey: boolean // Cmd/Win 键是否按下
|
|
111
|
+
ctrlKey: boolean // Ctrl 键是否按下
|
|
112
|
+
altKey: boolean // Alt 键是否按下
|
|
113
|
+
shiftKey: boolean // Shift 键是否按下
|
|
114
|
+
repeat: boolean // 是否为重复按键
|
|
115
|
+
}
|
|
69
116
|
```
|
|
70
117
|
|
|
71
118
|
## 废弃的 API
|
|
@@ -151,7 +198,7 @@ onInit(context) {
|
|
|
151
198
|
export default {
|
|
152
199
|
onInit(context) {
|
|
153
200
|
const { feature, box } = context.utils
|
|
154
|
-
|
|
201
|
+
|
|
155
202
|
// 监听输入变化
|
|
156
203
|
feature.onInputChange((input) => {
|
|
157
204
|
if (input.length > 2) {
|
|
@@ -163,10 +210,10 @@ export default {
|
|
|
163
210
|
}
|
|
164
211
|
})
|
|
165
212
|
},
|
|
166
|
-
|
|
213
|
+
|
|
167
214
|
onFeatureTriggered(featureId, query, feature) {
|
|
168
215
|
const { feature: featureSDK, box } = this.context.utils
|
|
169
|
-
|
|
216
|
+
|
|
170
217
|
// 推送结果
|
|
171
218
|
featureSDK.pushItems([
|
|
172
219
|
{
|
|
@@ -176,10 +223,10 @@ export default {
|
|
|
176
223
|
source: { id: this.pluginName, name: this.pluginName }
|
|
177
224
|
}
|
|
178
225
|
])
|
|
179
|
-
|
|
226
|
+
|
|
180
227
|
// 扩展窗口显示结果
|
|
181
228
|
box.expand({ length: 5 })
|
|
182
|
-
|
|
229
|
+
|
|
183
230
|
// 3秒后隐藏
|
|
184
231
|
setTimeout(() => {
|
|
185
232
|
box.hide()
|
|
@@ -207,7 +254,8 @@ export default {
|
|
|
207
254
|
- `core-box:get-input` - 获取当前输入值
|
|
208
255
|
- `core-box:set-input` - 设置输入框内容
|
|
209
256
|
- `core-box:clear-input` - 清空输入框
|
|
210
|
-
- `core-box:input-
|
|
257
|
+
- `core-box:input-change` - 输入变化广播(主进程 → 插件)
|
|
258
|
+
- `core-box:key-event` - 键盘事件转发(主进程 → 插件 UI View)
|
|
211
259
|
- `core-box:set-input-visibility` - 设置输入框可见性(主进程 → 渲染进程)
|
|
212
260
|
- `core-box:request-input-value` - 请求输入值(主进程 → 渲染进程)
|
|
213
261
|
|
package/plugin/sdk/clipboard.ts
CHANGED
|
@@ -23,7 +23,7 @@ function normalizeItem(item: PluginClipboardItem | null): PluginClipboardItem |
|
|
|
23
23
|
return item
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export type ClipboardHistoryOptions =
|
|
26
|
+
export type ClipboardHistoryOptions = PluginClipboardSearchOptions
|
|
27
27
|
|
|
28
28
|
export interface ClipboardFavoriteOptions {
|
|
29
29
|
id: number
|
|
@@ -44,69 +44,141 @@ export interface ClipboardApplyOptions {
|
|
|
44
44
|
type?: PluginClipboardItem['type']
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export interface ClipboardWriteOptions {
|
|
48
|
+
text?: string
|
|
49
|
+
html?: string
|
|
50
|
+
image?: string
|
|
51
|
+
files?: string[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ClipboardReadResult {
|
|
55
|
+
text: string
|
|
56
|
+
html: string
|
|
57
|
+
hasImage: boolean
|
|
58
|
+
hasFiles: boolean
|
|
59
|
+
formats: string[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ClipboardImageResult {
|
|
63
|
+
dataUrl: string
|
|
64
|
+
width: number
|
|
65
|
+
height: number
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ClipboardCopyAndPasteOptions {
|
|
69
|
+
text?: string
|
|
70
|
+
html?: string
|
|
71
|
+
image?: string
|
|
72
|
+
files?: string[]
|
|
73
|
+
delayMs?: number
|
|
74
|
+
hideCoreBox?: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
47
77
|
export type ClipboardSearchOptions = PluginClipboardSearchOptions
|
|
48
78
|
export type ClipboardSearchResponse = PluginClipboardSearchResponse
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated Use `useClipboard()` instead. This function will be removed in a future version.
|
|
82
|
+
*/
|
|
51
83
|
export function useClipboardHistory() {
|
|
84
|
+
return useClipboard().history
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Unified Clipboard SDK for plugin renderer context.
|
|
89
|
+
*
|
|
90
|
+
* Provides:
|
|
91
|
+
* - Basic clipboard operations (read/write) via IPC to main process
|
|
92
|
+
* - Clipboard history management
|
|
93
|
+
* - Copy and paste to active application
|
|
94
|
+
*
|
|
95
|
+
* All operations go through IPC to avoid WebContents focus issues.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const clipboard = useClipboard()
|
|
100
|
+
*
|
|
101
|
+
* // === Basic Operations ===
|
|
102
|
+
* await clipboard.writeText('Hello World')
|
|
103
|
+
* const content = await clipboard.read()
|
|
104
|
+
*
|
|
105
|
+
* // === Copy and Paste ===
|
|
106
|
+
* await clipboard.copyAndPaste({ text: 'Pasted content' })
|
|
107
|
+
*
|
|
108
|
+
* // === History Operations ===
|
|
109
|
+
* const latest = await clipboard.history.getLatest()
|
|
110
|
+
* const { history } = await clipboard.history.getHistory({ page: 1 })
|
|
111
|
+
*
|
|
112
|
+
* // === Listen to Changes ===
|
|
113
|
+
* // Note: Plugin must call box.allowClipboard(types) first
|
|
114
|
+
* const unsubscribe = clipboard.history.onDidChange((item) => {
|
|
115
|
+
* console.log('Clipboard changed:', item)
|
|
116
|
+
* })
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export function useClipboard() {
|
|
52
120
|
const channel = ensurePluginChannel()
|
|
53
121
|
|
|
54
|
-
|
|
122
|
+
const history = {
|
|
123
|
+
/**
|
|
124
|
+
* Gets the most recent clipboard item
|
|
125
|
+
*/
|
|
55
126
|
async getLatest(): Promise<PluginClipboardItem | null> {
|
|
56
127
|
const result = await channel.send('clipboard:get-latest')
|
|
57
128
|
return normalizeItem(result)
|
|
58
129
|
},
|
|
59
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Gets clipboard history with pagination
|
|
133
|
+
*/
|
|
60
134
|
async getHistory(options: ClipboardHistoryOptions = {}): Promise<PluginClipboardHistoryResponse> {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const history = Array.isArray(response?.history)
|
|
135
|
+
const response = await channel.send('clipboard:get-history', options)
|
|
136
|
+
const items = Array.isArray(response?.history)
|
|
64
137
|
? response.history.map((item: PluginClipboardItem) => normalizeItem(item) ?? item)
|
|
65
138
|
: []
|
|
66
139
|
return {
|
|
67
140
|
...response,
|
|
68
|
-
history,
|
|
141
|
+
history: items,
|
|
69
142
|
}
|
|
70
143
|
},
|
|
71
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Sets favorite status for a clipboard item
|
|
147
|
+
*/
|
|
72
148
|
async setFavorite(options: ClipboardFavoriteOptions): Promise<void> {
|
|
73
149
|
await channel.send('clipboard:set-favorite', options)
|
|
74
150
|
},
|
|
75
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Deletes a clipboard item from history
|
|
154
|
+
*/
|
|
76
155
|
async deleteItem(options: ClipboardDeleteOptions): Promise<void> {
|
|
77
156
|
await channel.send('clipboard:delete-item', options)
|
|
78
157
|
},
|
|
79
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Clears all clipboard history
|
|
161
|
+
*/
|
|
80
162
|
async clearHistory(): Promise<void> {
|
|
81
163
|
await channel.send('clipboard:clear-history')
|
|
82
164
|
},
|
|
83
165
|
|
|
84
166
|
/**
|
|
85
|
-
* Search clipboard history with advanced filtering
|
|
86
|
-
* Supports keyword search, time-based filtering, and combined filters.
|
|
87
|
-
*
|
|
88
|
-
* @param options - Search options
|
|
89
|
-
* @returns Search results with pagination metadata
|
|
167
|
+
* Search clipboard history with advanced filtering
|
|
90
168
|
*
|
|
91
169
|
* @example
|
|
92
170
|
* ```typescript
|
|
93
171
|
* // Search by keyword
|
|
94
|
-
* const result = await searchHistory({ keyword: 'hello' })
|
|
172
|
+
* const result = await clipboard.history.searchHistory({ keyword: 'hello' })
|
|
95
173
|
*
|
|
96
|
-
* //
|
|
97
|
-
* const
|
|
98
|
-
* const recent = await searchHistory({ startTime: oneDayAgo })
|
|
99
|
-
*
|
|
100
|
-
* // Combined search: text type, favorite items from a specific app
|
|
101
|
-
* const filtered = await searchHistory({
|
|
174
|
+
* // Filter by type and time
|
|
175
|
+
* const recent = await clipboard.history.searchHistory({
|
|
102
176
|
* type: 'text',
|
|
103
|
-
*
|
|
104
|
-
* sourceApp: 'com.apple.Safari'
|
|
177
|
+
* startTime: Date.now() - 24 * 60 * 60 * 1000
|
|
105
178
|
* })
|
|
106
179
|
* ```
|
|
107
180
|
*/
|
|
108
181
|
async searchHistory(options: ClipboardSearchOptions = {}): Promise<ClipboardSearchResponse> {
|
|
109
|
-
// Use the extended clipboard:get-history interface with search parameters
|
|
110
182
|
const response = await channel.send('clipboard:get-history', options)
|
|
111
183
|
const items = Array.isArray(response?.history)
|
|
112
184
|
? response.history.map((item: PluginClipboardItem) => normalizeItem(item) ?? item)
|
|
@@ -119,21 +191,120 @@ export function useClipboardHistory() {
|
|
|
119
191
|
}
|
|
120
192
|
},
|
|
121
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Listen to clipboard changes.
|
|
196
|
+
*
|
|
197
|
+
* **Important**: Plugin must call `box.allowClipboard(types)` first to enable monitoring.
|
|
198
|
+
* Only changes matching the allowed types will be received.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* import { ClipboardType } from '@talex-touch/utils/plugin/sdk'
|
|
203
|
+
*
|
|
204
|
+
* // Enable monitoring for text and images
|
|
205
|
+
* await box.allowClipboard(ClipboardType.TEXT | ClipboardType.IMAGE)
|
|
206
|
+
*
|
|
207
|
+
* // Listen to changes
|
|
208
|
+
* const unsubscribe = clipboard.history.onDidChange((item) => {
|
|
209
|
+
* console.log('New clipboard item:', item.type, item.content)
|
|
210
|
+
* })
|
|
211
|
+
*
|
|
212
|
+
* // Later: stop listening
|
|
213
|
+
* unsubscribe()
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
122
216
|
onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
|
|
123
|
-
return channel.regChannel('core-box:clipboard-change', ({ data }) => {
|
|
124
|
-
const item = (data && 'item' in data ? data.item : data) as PluginClipboardItem
|
|
217
|
+
return channel.regChannel('core-box:clipboard-change', ({ data }: { data: unknown }) => {
|
|
218
|
+
const item = (data && typeof data === 'object' && 'item' in data ? (data as { item: PluginClipboardItem }).item : data) as PluginClipboardItem
|
|
125
219
|
callback(normalizeItem(item) ?? item)
|
|
126
220
|
})
|
|
127
221
|
},
|
|
128
222
|
|
|
129
223
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
224
|
+
* Apply a clipboard item to the active application (write + paste)
|
|
225
|
+
* @deprecated Use `clipboard.copyAndPaste()` instead
|
|
132
226
|
*/
|
|
133
227
|
async applyToActiveApp(options: ClipboardApplyOptions = {}): Promise<boolean> {
|
|
134
228
|
const response = await channel.send('clipboard:apply-to-active-app', options)
|
|
135
229
|
if (typeof response === 'object' && response) {
|
|
136
|
-
return Boolean((response as
|
|
230
|
+
return Boolean((response as { success?: boolean }).success)
|
|
231
|
+
}
|
|
232
|
+
return true
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
/**
|
|
238
|
+
* Clipboard history operations
|
|
239
|
+
*/
|
|
240
|
+
history,
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Writes text to the system clipboard
|
|
244
|
+
*/
|
|
245
|
+
async writeText(text: string): Promise<void> {
|
|
246
|
+
await channel.send('clipboard:write-text', { text })
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Writes content to the system clipboard.
|
|
251
|
+
* Supports text, HTML, image (data URL), and files.
|
|
252
|
+
*/
|
|
253
|
+
async write(options: ClipboardWriteOptions): Promise<void> {
|
|
254
|
+
await channel.send('clipboard:write', options)
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Reads current clipboard content
|
|
259
|
+
*/
|
|
260
|
+
async read(): Promise<ClipboardReadResult> {
|
|
261
|
+
return await channel.send('clipboard:read')
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Reads image from clipboard as data URL
|
|
266
|
+
*/
|
|
267
|
+
async readImage(): Promise<ClipboardImageResult | null> {
|
|
268
|
+
return await channel.send('clipboard:read-image')
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Reads file paths from clipboard
|
|
273
|
+
*/
|
|
274
|
+
async readFiles(): Promise<string[]> {
|
|
275
|
+
return await channel.send('clipboard:read-files')
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Clears the system clipboard
|
|
280
|
+
*/
|
|
281
|
+
async clear(): Promise<void> {
|
|
282
|
+
await channel.send('clipboard:clear')
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Writes content to clipboard and simulates paste command to the active application.
|
|
287
|
+
* This is the recommended way to "paste" content from a plugin.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* // Paste text
|
|
292
|
+
* await clipboard.copyAndPaste({ text: 'Hello' })
|
|
293
|
+
*
|
|
294
|
+
* // Paste with HTML formatting
|
|
295
|
+
* await clipboard.copyAndPaste({ text: 'Hello', html: '<b>Hello</b>' })
|
|
296
|
+
*
|
|
297
|
+
* // Paste image
|
|
298
|
+
* await clipboard.copyAndPaste({ image: 'data:image/png;base64,...' })
|
|
299
|
+
*
|
|
300
|
+
* // Paste files
|
|
301
|
+
* await clipboard.copyAndPaste({ files: ['/path/to/file.txt'] })
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
async copyAndPaste(options: ClipboardCopyAndPasteOptions): Promise<boolean> {
|
|
305
|
+
const response = await channel.send('clipboard:copy-and-paste', options)
|
|
306
|
+
if (typeof response === 'object' && response) {
|
|
307
|
+
return Boolean((response as { success?: boolean }).success)
|
|
137
308
|
}
|
|
138
309
|
return true
|
|
139
310
|
},
|