@opentiny/next-sdk 0.4.2 → 0.4.3
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/dist/{SimulatorMask-BHVXyogh-CARX3Rff.js → SimulatorMask-BHVXyogh-CKmEDsYG.js} +1 -1
- package/dist/{index-R_HIbfUX.js → index-CHMa2zG4.js} +1065 -1042
- package/dist/index.js +1 -1
- package/dist/page-tools/a11y/config.d.ts +6 -3
- package/dist/page-tools/a11y/constants.d.ts +20 -0
- package/dist/page-tools/a11y/utils.d.ts +14 -0
- package/dist/page-tools/constants.d.ts +4 -2
- package/dist/page-tools/context.d.ts +2 -0
- package/dist/page-tools/handlers/hover.d.ts +9 -0
- package/dist/page-tools/schema.d.ts +3 -3
- package/dist/page-tools/utils/dom.d.ts +8 -2
- package/dist/runtime.js +19 -172
- package/package.json +6 -1
- package/WebMcpClient.ts +0 -505
- package/WebMcpServer.ts +0 -447
- package/agent/AgentModelProvider.ts +0 -932
- package/agent/type.ts +0 -91
- package/agent/utils/generateReActPrompt.ts +0 -71
- package/agent/utils/getAISDKTools.ts +0 -34
- package/agent/utils/getBuiltinMcpTools.ts +0 -86
- package/agent/utils/parseReActAction.ts +0 -34
- package/core.ts +0 -26
- package/index.ts +0 -91
- package/page-tools/a11y/build.ts +0 -74
- package/page-tools/a11y/config.ts +0 -465
- package/page-tools/a11y/constants.ts +0 -131
- package/page-tools/a11y/search.ts +0 -127
- package/page-tools/a11y/types.ts +0 -105
- package/page-tools/a11y/utils.ts +0 -239
- package/page-tools/a11y/vnode.ts +0 -439
- package/page-tools/a11y-tree.ts +0 -14
- package/page-tools/bridge.ts +0 -722
- package/page-tools/configs/console-cloud.ts +0 -172
- package/page-tools/constants.ts +0 -12
- package/page-tools/context.ts +0 -50
- package/page-tools/effects.ts +0 -345
- package/page-tools/handlers/browserState.ts +0 -12
- package/page-tools/handlers/click.ts +0 -30
- package/page-tools/handlers/executeJavascript.ts +0 -22
- package/page-tools/handlers/fill.ts +0 -65
- package/page-tools/handlers/scroll.ts +0 -66
- package/page-tools/handlers/searchTree.ts +0 -27
- package/page-tools/handlers/select.ts +0 -39
- package/page-tools/initialize-builtin-WebMCP.ts +0 -20
- package/page-tools/page-agent-highlight/index.ts +0 -245
- package/page-tools/page-agent-mask/SimulatorMask.module.css +0 -14
- package/page-tools/page-agent-mask/SimulatorMask.ts +0 -299
- package/page-tools/page-agent-mask/checkDarkMode.ts +0 -181
- package/page-tools/page-agent-mask/cursor-border.svg +0 -3
- package/page-tools/page-agent-mask/cursor-fill.svg +0 -5
- package/page-tools/page-agent-mask/cursor.module.css +0 -70
- package/page-tools/page-agent-mask/hauwei.svg +0 -25
- package/page-tools/page-agent-prompt.md +0 -139
- package/page-tools/page-agent-tool-event.ts +0 -113
- package/page-tools/page-agent-tool.ts +0 -207
- package/page-tools/page-state-cache.ts +0 -78
- package/page-tools/schema.ts +0 -52
- package/page-tools/tool-config.ts +0 -100
- package/page-tools/utils/dom.ts +0 -158
- package/page-tools/utils/scroll.ts +0 -58
- package/remoter/QrCode.ts +0 -54
- package/remoter/createRemoter.ts +0 -957
- package/remoter/svgs/chat.svg +0 -43
- package/remoter/svgs/icon-copy.svg +0 -25
- package/remoter/svgs/link.svg +0 -28
- package/remoter/svgs/logo.svg +0 -45
- package/remoter/svgs/qrcode.svg +0 -35
- package/remoter/svgs/scan.svg +0 -21
- package/remoter/tooltips.ts +0 -260
- package/runtime.html +0 -39
- package/runtime.ts +0 -44
- package/script/utils.ts +0 -26
- package/skills/index.ts +0 -287
- package/test/page-tools/a11y/build.test.ts +0 -638
- package/test/page-tools/a11y/config.test.ts +0 -370
- package/test/page-tools/configs/console-cloud.test.ts +0 -168
- package/test/page-tools/page-agent-highlight.test.ts +0 -110
- package/test/page-tools/page-agent-tool-dispatch.test.ts +0 -208
- package/test/page-tools/page-agent-tool.test.ts +0 -102
- package/test/page-tools/tool-config.test.ts +0 -112
- package/test/page-tools/utils/dom.test.ts +0 -122
- package/tsconfig.json +0 -16
- package/utils/builtinProxy.ts +0 -144
- package/utils/env.ts +0 -13
- package/utils/uuid.ts +0 -10
- package/vite.config.runtime.ts +0 -22
- package/vite.config.ts +0 -63
- package/vitest.config.ts +0 -10
package/remoter/createRemoter.ts
DELETED
|
@@ -1,957 +0,0 @@
|
|
|
1
|
-
import { QrCode } from './QrCode'
|
|
2
|
-
import { Tooltip } from './tooltips'
|
|
3
|
-
import chat from './svgs/chat.svg?url'
|
|
4
|
-
import scan from './svgs/scan.svg?url'
|
|
5
|
-
import link from './svgs/link.svg?url'
|
|
6
|
-
import qrCode from './svgs/qrcode.svg?url'
|
|
7
|
-
import iconCopy from './svgs/icon-copy.svg?url'
|
|
8
|
-
import logo from './svgs/logo.svg?url'
|
|
9
|
-
|
|
10
|
-
const DEFAULT_REMOTE_URL = 'https://chat.opentiny.design'
|
|
11
|
-
const DEFAULT_QR_CODE_URL = 'https://ai.opentiny.design/next-remoter'
|
|
12
|
-
const DEFAULT_LOGO_URL = logo
|
|
13
|
-
|
|
14
|
-
/** 菜单项配置接口 */
|
|
15
|
-
export interface MenuItemConfig {
|
|
16
|
-
/** 菜单项标识 */
|
|
17
|
-
action: 'qr-code' | 'ai-chat' | 'remote-control' | 'remote-url'
|
|
18
|
-
/** 是否显示该菜单项 */
|
|
19
|
-
show?: boolean
|
|
20
|
-
/** 菜单项文本 */
|
|
21
|
-
text?: string
|
|
22
|
-
/** 菜单文字颜色 */
|
|
23
|
-
active?: boolean
|
|
24
|
-
/** 识别码 */
|
|
25
|
-
know?: boolean
|
|
26
|
-
/** 菜单项描述 */
|
|
27
|
-
desc?: string
|
|
28
|
-
/** 菜单项提示 */
|
|
29
|
-
tip?: string
|
|
30
|
-
/** 菜单项图标SVG */
|
|
31
|
-
icon?: string
|
|
32
|
-
/** 是否显示复制图标 */
|
|
33
|
-
showCopyIcon?: boolean
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** 配置选项接口 */
|
|
37
|
-
export interface FloatingBlockOptions {
|
|
38
|
-
/** 弹出 AI 对话框的回调函数 */
|
|
39
|
-
onShowAIChat?: () => void
|
|
40
|
-
|
|
41
|
-
/** 遥控端页面地址,默认为: https://ai.opentiny.design/next-remoter */
|
|
42
|
-
qrCodeUrl?: string
|
|
43
|
-
/** 被遥控页面的 sessionId;无 sessionId 时仅显示「打开对话框」菜单,不显示二维码、识别码、遥控器链接 */
|
|
44
|
-
sessionId?: string
|
|
45
|
-
/** 菜单项配置 */
|
|
46
|
-
menuItems?: MenuItemConfig[]
|
|
47
|
-
/** 遥控端页面地址,默认为: https://chat.opentiny.design */
|
|
48
|
-
remoteUrl?: string
|
|
49
|
-
/** 悬浮Logo的url地址,默认为: https://ai.opentiny.design/next-remoter/svgs/logo-next-no-bg-left.svg */
|
|
50
|
-
logoUrl?: string
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 动作类型
|
|
54
|
-
export type ActionType = 'qr-code' | 'ai-chat' | 'remote-control' | 'remote-url'
|
|
55
|
-
|
|
56
|
-
/** 有 sessionId 时的完整菜单;无 sessionId 时仅返回 ai-chat(不依赖会话的菜单项) */
|
|
57
|
-
const getDefaultMenuItems = (options: FloatingBlockOptions): MenuItemConfig[] => {
|
|
58
|
-
const hasSession = !!options.sessionId
|
|
59
|
-
const baseItems: MenuItemConfig[] = [
|
|
60
|
-
{
|
|
61
|
-
action: 'qr-code',
|
|
62
|
-
show: hasSession,
|
|
63
|
-
text: '扫码登录',
|
|
64
|
-
desc: '使用手机遥控页面',
|
|
65
|
-
icon: qrCode
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
action: 'ai-chat',
|
|
69
|
-
show: true,
|
|
70
|
-
text: '打开对话框',
|
|
71
|
-
desc: '支持在网页端操作AI',
|
|
72
|
-
icon: chat
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
action: 'remote-url',
|
|
76
|
-
show: hasSession,
|
|
77
|
-
text: `遥控器链接`,
|
|
78
|
-
desc: `${options.remoteUrl}`,
|
|
79
|
-
active: true,
|
|
80
|
-
tip: options.remoteUrl,
|
|
81
|
-
showCopyIcon: true,
|
|
82
|
-
icon: link
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
action: 'remote-control',
|
|
86
|
-
show: hasSession,
|
|
87
|
-
text: `识别码`,
|
|
88
|
-
desc: hasSession ? `${options.sessionId!.slice(-6)}` : '',
|
|
89
|
-
know: true,
|
|
90
|
-
showCopyIcon: true,
|
|
91
|
-
icon: scan
|
|
92
|
-
}
|
|
93
|
-
]
|
|
94
|
-
return baseItems
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
class FloatingBlock {
|
|
98
|
-
private options: FloatingBlockOptions
|
|
99
|
-
private isExpanded = false
|
|
100
|
-
private floatingBlock!: HTMLDivElement
|
|
101
|
-
private dropdownMenu!: HTMLDivElement
|
|
102
|
-
private menuItems: MenuItemConfig[]
|
|
103
|
-
/** 即将关闭dropdown的定时器 */
|
|
104
|
-
private closingTimer = 0
|
|
105
|
-
|
|
106
|
-
// 计算 sessionPrefix 属性
|
|
107
|
-
private get sessionPrefix(): string {
|
|
108
|
-
return this.options.qrCodeUrl?.includes('?') ? '&sessionId=' : '?sessionId='
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
constructor(options: FloatingBlockOptions) {
|
|
112
|
-
this.options = {
|
|
113
|
-
...options,
|
|
114
|
-
qrCodeUrl: options.qrCodeUrl || DEFAULT_QR_CODE_URL,
|
|
115
|
-
remoteUrl: options.remoteUrl || DEFAULT_REMOTE_URL,
|
|
116
|
-
logoUrl: options.logoUrl || DEFAULT_LOGO_URL
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 合并默认菜单项配置和用户配置。 用户不传入任何menu时, 则不自动合并默认菜单了,即不渲染任何菜单。
|
|
120
|
-
if (options.menuItems && options.menuItems.length === 0) {
|
|
121
|
-
this.menuItems = []
|
|
122
|
-
} else {
|
|
123
|
-
this.menuItems = this.mergeMenuItems(options.menuItems)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
this.init()
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private getImageUrl = (asset: string | undefined): HTMLImageElement | undefined => {
|
|
130
|
-
if (!asset) return
|
|
131
|
-
const img = new Image()
|
|
132
|
-
img.src = asset
|
|
133
|
-
return img
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private renderItem = (): void => {
|
|
137
|
-
this.menuItems
|
|
138
|
-
.filter((item) => item.show !== false) // 过滤掉show为false的菜单项
|
|
139
|
-
.map((item) => {
|
|
140
|
-
const wrapper = document.getElementById(`tiny-remoter-icon-item-${item.action}`) as HTMLDivElement
|
|
141
|
-
if (!wrapper) return
|
|
142
|
-
wrapper.innerHTML = ''
|
|
143
|
-
const img = this.getImageUrl(item.icon)
|
|
144
|
-
if (img) wrapper.appendChild(img)
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 合并菜单项配置。
|
|
150
|
-
* - 用户明确传入 menuItems:直接使用用户配置,不受 sessionId 限制;未传 icon 时自动补充默认图标
|
|
151
|
-
* - 有 sessionId 且未传 menuItems:使用默认菜单
|
|
152
|
-
* - 无 sessionId 且未传 menuItems:不渲染任何下拉菜单,仅保留点击浮标打开对话框的能力
|
|
153
|
-
*/
|
|
154
|
-
private mergeMenuItems(userMenuItems?: MenuItemConfig[]): MenuItemConfig[] {
|
|
155
|
-
// 各 action 对应的默认图标映射
|
|
156
|
-
const defaultIcons: Partial<Record<ActionType, string>> = {
|
|
157
|
-
'qr-code': qrCode,
|
|
158
|
-
'ai-chat': chat,
|
|
159
|
-
'remote-url': link,
|
|
160
|
-
'remote-control': scan
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 用户明确传入了 menuItems,直接使用,并补充缺失的图标
|
|
164
|
-
if (userMenuItems) {
|
|
165
|
-
return userMenuItems.map((item) => ({
|
|
166
|
-
...item,
|
|
167
|
-
icon: item.icon ?? defaultIcons[item.action]
|
|
168
|
-
}))
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 无 sessionId 且无用户菜单:完全关闭下拉菜单,只保留点击浮标触发 onShowAIChat
|
|
172
|
-
if (!this.options.sessionId) {
|
|
173
|
-
return []
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return getDefaultMenuItems(this.options)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private init(): void {
|
|
180
|
-
this.createFloatingBlock()
|
|
181
|
-
this.createDropdownMenu()
|
|
182
|
-
this.bindEvents()
|
|
183
|
-
this.addStyles()
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 创建主浮动块
|
|
187
|
-
private createFloatingBlock(): void {
|
|
188
|
-
this.floatingBlock = document.createElement('div')
|
|
189
|
-
this.floatingBlock.className = 'tiny-remoter-floating-block'
|
|
190
|
-
this.floatingBlock.innerHTML = `
|
|
191
|
-
<div class="tiny-remoter-floating-block__icon">
|
|
192
|
-
<img style="display: block; width: 56px;" src="${this.options.logoUrl}" alt="icon" />
|
|
193
|
-
</div>
|
|
194
|
-
`
|
|
195
|
-
|
|
196
|
-
document.body.appendChild(this.floatingBlock)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private readyTips = (el: string): void => {
|
|
200
|
-
const element = document.getElementById(el)
|
|
201
|
-
if (!element) {
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
new Tooltip(element, {
|
|
205
|
-
content: '复制',
|
|
206
|
-
placement: 'top',
|
|
207
|
-
trigger: 'hover'
|
|
208
|
-
})
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// 创建下拉菜单
|
|
212
|
-
private createDropdownMenu(): void {
|
|
213
|
-
this.dropdownMenu = document.createElement('div')
|
|
214
|
-
this.dropdownMenu.className = 'tiny-remoter-floating-dropdown'
|
|
215
|
-
|
|
216
|
-
// 根据配置动态生成菜单项
|
|
217
|
-
const menuItemsHTML = this.menuItems
|
|
218
|
-
.filter((item) => item.show !== false) // 过滤掉show为false的菜单项
|
|
219
|
-
.map(
|
|
220
|
-
(item) => `
|
|
221
|
-
<div class="tiny-remoter-dropdown-item" data-action="${item.action}">
|
|
222
|
-
<div id="tiny-remoter-icon-item-${item.action}" class="tiny-remoter-dropdown-item__icon">
|
|
223
|
-
</div>
|
|
224
|
-
<div class="tiny-remoter-dropdown-item__content">
|
|
225
|
-
<div title="${item.tip}">${item.text}</div>
|
|
226
|
-
<div class="tiny-remoter-dropdown-item__desc-wrapper">
|
|
227
|
-
<div class="tiny-remoter-dropdown-item__desc ${item.active ? 'tiny-remoter-dropdown-item__desc--active' : ''} ${item.know ? 'tiny-remoter-dropdown-item__desc--know' : ''}">${item.desc ?? ''}</div>
|
|
228
|
-
<div>
|
|
229
|
-
${
|
|
230
|
-
item.showCopyIcon
|
|
231
|
-
? `
|
|
232
|
-
<div class="tiny-remoter-copy-icon" id="${item.action}" data-action="${item.action}">
|
|
233
|
-
<img src="${iconCopy}"/>
|
|
234
|
-
</div>
|
|
235
|
-
`
|
|
236
|
-
: ''
|
|
237
|
-
}
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
`
|
|
243
|
-
)
|
|
244
|
-
.join('')
|
|
245
|
-
|
|
246
|
-
this.dropdownMenu.innerHTML = menuItemsHTML
|
|
247
|
-
document.body.appendChild(this.dropdownMenu)
|
|
248
|
-
this.renderItem()
|
|
249
|
-
this.readyTips(`remote-control`)
|
|
250
|
-
this.readyTips(`remote-url`)
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private bindEvents(): void {
|
|
254
|
-
// 绑定浮动块点击事件
|
|
255
|
-
this.floatingBlock.addEventListener('click', () => {
|
|
256
|
-
this.showAIChat()
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// 浮动块悬浮处理
|
|
260
|
-
this.floatingBlock.addEventListener('mouseenter', () => {
|
|
261
|
-
this.openDropdown()
|
|
262
|
-
|
|
263
|
-
if (this.closingTimer) {
|
|
264
|
-
window.clearTimeout(this.closingTimer)
|
|
265
|
-
this.closingTimer = 0
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
this.floatingBlock.addEventListener('mouseleave', () => {
|
|
269
|
-
this.shouldCloseDropdown()
|
|
270
|
-
})
|
|
271
|
-
// 悬浮菜单进入,则阻止关闭
|
|
272
|
-
this.dropdownMenu.addEventListener('mouseenter', (e: Event) => {
|
|
273
|
-
if (this.closingTimer) {
|
|
274
|
-
window.clearTimeout(this.closingTimer)
|
|
275
|
-
this.closingTimer = 0
|
|
276
|
-
}
|
|
277
|
-
})
|
|
278
|
-
this.dropdownMenu.addEventListener('mouseleave', (e: Event) => {
|
|
279
|
-
this.shouldCloseDropdown()
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// 绑定菜单项点击事件
|
|
283
|
-
this.dropdownMenu.addEventListener('click', (e: Event) => {
|
|
284
|
-
const target = e.target as HTMLElement
|
|
285
|
-
|
|
286
|
-
// 检查是否点击了复制图标
|
|
287
|
-
const copyIcon = target.closest('.tiny-remoter-copy-icon') as HTMLElement
|
|
288
|
-
if (copyIcon) {
|
|
289
|
-
e.stopPropagation() // 阻止事件冒泡,避免触发菜单项点击
|
|
290
|
-
const action = copyIcon.dataset.action as ActionType
|
|
291
|
-
if (action) {
|
|
292
|
-
this.handleAction(action)
|
|
293
|
-
}
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// 处理普通菜单项点击
|
|
298
|
-
const actionItem = target.closest('.tiny-remoter-dropdown-item') as HTMLElement
|
|
299
|
-
const action = actionItem?.dataset.action as ActionType
|
|
300
|
-
if (action) {
|
|
301
|
-
this.handleAction(action)
|
|
302
|
-
}
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
// 点击外部关闭菜单
|
|
306
|
-
document.addEventListener('click', (e: Event) => {
|
|
307
|
-
const target = e.target as HTMLElement
|
|
308
|
-
if (!this.floatingBlock.contains(target) && !this.dropdownMenu.contains(target)) {
|
|
309
|
-
this.closeDropdown()
|
|
310
|
-
}
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
// ESC键关闭菜单
|
|
314
|
-
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
315
|
-
if (e.key === 'Escape') {
|
|
316
|
-
this.closeDropdown()
|
|
317
|
-
}
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private openDropdown(): void {
|
|
322
|
-
// 没有menuItems,则返回
|
|
323
|
-
if (!this.menuItems || (this.menuItems && this.menuItems.length === 0)) {
|
|
324
|
-
return
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
this.isExpanded = true
|
|
328
|
-
this.floatingBlock.classList.add('expanded')
|
|
329
|
-
this.dropdownMenu.classList.add('show')
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
private shouldCloseDropdown() {
|
|
333
|
-
this.closingTimer = window.setTimeout(() => {
|
|
334
|
-
this.closeDropdown()
|
|
335
|
-
}, 300)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/** 真正的立即关闭 */
|
|
339
|
-
private closeDropdown(): void {
|
|
340
|
-
this.isExpanded = false
|
|
341
|
-
this.floatingBlock.classList.remove('expanded')
|
|
342
|
-
this.dropdownMenu.classList.remove('show')
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private handleAction(action: ActionType): void {
|
|
346
|
-
switch (action) {
|
|
347
|
-
case 'qr-code':
|
|
348
|
-
this.showQRCode()
|
|
349
|
-
break
|
|
350
|
-
case 'ai-chat':
|
|
351
|
-
this.showAIChat()
|
|
352
|
-
break
|
|
353
|
-
case 'remote-control':
|
|
354
|
-
this.copyRemoteControl()
|
|
355
|
-
break
|
|
356
|
-
case 'remote-url':
|
|
357
|
-
this.copyRemoteURL()
|
|
358
|
-
break
|
|
359
|
-
}
|
|
360
|
-
this.closeDropdown()
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
private copyRemoteControl(): void {
|
|
364
|
-
// 优先使用用户菜单项中的 desc/text(支持自定义识别码),回退到 sessionId 末 6 位
|
|
365
|
-
const menuItem = this.menuItems.find((item) => item.action === 'remote-control')
|
|
366
|
-
const codeToCopy =
|
|
367
|
-
menuItem?.desc || menuItem?.text || (this.options.sessionId ? this.options.sessionId.slice(-6) : '')
|
|
368
|
-
if (codeToCopy) {
|
|
369
|
-
this.copyToClipboard(codeToCopy)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private copyRemoteURL(): void {
|
|
374
|
-
const menuItem = this.menuItems.find((item) => item.action === 'remote-url')
|
|
375
|
-
|
|
376
|
-
// 构造带 sessionId 的完整遥控链接(默认行为)
|
|
377
|
-
const sessionUrl = this.options.sessionId
|
|
378
|
-
? this.options.remoteUrl + this.sessionPrefix + this.options.sessionId
|
|
379
|
-
: ''
|
|
380
|
-
|
|
381
|
-
// 仅当 desc 是用户真正自定义的值(不同于裸 remoteUrl)时才优先使用,
|
|
382
|
-
// 否则回退到带 sessionId 的完整链接,避免默认菜单场景复制裸域名
|
|
383
|
-
const customDesc = menuItem?.desc && menuItem.desc !== this.options.remoteUrl ? menuItem.desc : undefined
|
|
384
|
-
const urlToCopy = customDesc || sessionUrl || menuItem?.text || ''
|
|
385
|
-
|
|
386
|
-
if (urlToCopy) {
|
|
387
|
-
this.copyToClipboard(urlToCopy)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// 实现复制到剪贴板功能
|
|
392
|
-
private async copyToClipboard(text: string): Promise<void> {
|
|
393
|
-
try {
|
|
394
|
-
// 优先使用现代浏览器的 Clipboard API
|
|
395
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
396
|
-
await navigator.clipboard.writeText(text)
|
|
397
|
-
this.showCopyFeedback(true)
|
|
398
|
-
} else {
|
|
399
|
-
// 降级方案:使用传统的 document.execCommand
|
|
400
|
-
const textArea = document.createElement('textarea')
|
|
401
|
-
textArea.value = text
|
|
402
|
-
textArea.style.position = 'fixed'
|
|
403
|
-
textArea.style.left = '-999999px'
|
|
404
|
-
textArea.style.top = '-999999px'
|
|
405
|
-
document.body.appendChild(textArea)
|
|
406
|
-
textArea.focus()
|
|
407
|
-
textArea.select()
|
|
408
|
-
|
|
409
|
-
const successful = document.execCommand('copy')
|
|
410
|
-
document.body.removeChild(textArea)
|
|
411
|
-
|
|
412
|
-
if (successful) {
|
|
413
|
-
this.showCopyFeedback(true)
|
|
414
|
-
} else {
|
|
415
|
-
this.showCopyFeedback(false)
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.error('复制失败:', error)
|
|
420
|
-
this.showCopyFeedback(false)
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// 显示复制反馈提示
|
|
425
|
-
private showCopyFeedback(success: boolean): void {
|
|
426
|
-
const message = success ? '复制成功!' : '复制失败,请手动复制'
|
|
427
|
-
const feedback = document.createElement('div')
|
|
428
|
-
feedback.className = `tiny-remoter-copy-feedback ${success ? 'success' : 'error'}`
|
|
429
|
-
feedback.textContent = message
|
|
430
|
-
|
|
431
|
-
document.body.appendChild(feedback)
|
|
432
|
-
|
|
433
|
-
// 添加显示动画
|
|
434
|
-
setTimeout(() => feedback.classList.add('show'), 10)
|
|
435
|
-
|
|
436
|
-
// 3秒后自动移除
|
|
437
|
-
setTimeout(() => {
|
|
438
|
-
feedback.classList.remove('show')
|
|
439
|
-
setTimeout(() => {
|
|
440
|
-
if (feedback.parentNode) {
|
|
441
|
-
feedback.parentNode.removeChild(feedback)
|
|
442
|
-
}
|
|
443
|
-
}, 300)
|
|
444
|
-
}, 1500)
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// 创建二维码弹窗(无 sessionId 时不展示)
|
|
448
|
-
private async showQRCode(): Promise<void> {
|
|
449
|
-
if (!this.options.sessionId) return
|
|
450
|
-
const qrCode = new QrCode((this.options.qrCodeUrl || '') + this.sessionPrefix + this.options.sessionId, {})
|
|
451
|
-
const base64 = await qrCode.toDataURL()
|
|
452
|
-
const modal = this.createModal(
|
|
453
|
-
'扫码前往智能遥控器',
|
|
454
|
-
`
|
|
455
|
-
<div style="text-align: center; padding: 32px;">
|
|
456
|
-
<!-- 二维码容器 - 添加渐变背景和阴影效果 -->
|
|
457
|
-
<div style="
|
|
458
|
-
width: 240px;
|
|
459
|
-
height: 240px;
|
|
460
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
461
|
-
margin: 0 auto 24px;
|
|
462
|
-
display: flex;
|
|
463
|
-
align-items: center;
|
|
464
|
-
justify-content: center;
|
|
465
|
-
border-radius: 20px;
|
|
466
|
-
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
|
|
467
|
-
position: relative;
|
|
468
|
-
overflow: hidden;
|
|
469
|
-
">
|
|
470
|
-
<!-- 装饰性背景元素 -->
|
|
471
|
-
<div style="
|
|
472
|
-
position: absolute;
|
|
473
|
-
top: -50%;
|
|
474
|
-
left: -50%;
|
|
475
|
-
width: 200%;
|
|
476
|
-
height: 200%;
|
|
477
|
-
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
|
478
|
-
animation: rotate 20s linear infinite;
|
|
479
|
-
"></div>
|
|
480
|
-
|
|
481
|
-
<!-- 二维码图片容器 -->
|
|
482
|
-
<div style="
|
|
483
|
-
width: 200px;
|
|
484
|
-
height: 200px;
|
|
485
|
-
background: white;
|
|
486
|
-
border-radius: 16px;
|
|
487
|
-
padding: 16px;
|
|
488
|
-
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
489
|
-
position: relative;
|
|
490
|
-
z-index: 1;
|
|
491
|
-
">
|
|
492
|
-
<img src="${base64}" alt="二维码" style="
|
|
493
|
-
width: 100%;
|
|
494
|
-
height: 100%;
|
|
495
|
-
object-fit: contain;
|
|
496
|
-
border-radius: 8px;
|
|
497
|
-
">
|
|
498
|
-
</div>
|
|
499
|
-
</div>
|
|
500
|
-
|
|
501
|
-
<!-- 标题文字 -->
|
|
502
|
-
<h3 style="
|
|
503
|
-
color: #333;
|
|
504
|
-
margin: 0 0 12px 0;
|
|
505
|
-
font-size: 20px;
|
|
506
|
-
font-weight: 600;
|
|
507
|
-
letter-spacing: 0.5px;
|
|
508
|
-
">扫描二维码</h3>
|
|
509
|
-
|
|
510
|
-
<!-- 描述文字 -->
|
|
511
|
-
<p style="
|
|
512
|
-
color: #666;
|
|
513
|
-
margin: 0 auto;
|
|
514
|
-
margin-bottom: 20px;
|
|
515
|
-
font-size: 14px;
|
|
516
|
-
line-height: 1.6;
|
|
517
|
-
max-width: 280px;
|
|
518
|
-
">请使用手机微信或者浏览器扫描二维码跳转到智能遥控器</p>
|
|
519
|
-
|
|
520
|
-
<!-- 提示图标和文字 -->
|
|
521
|
-
<div style="
|
|
522
|
-
display: flex;
|
|
523
|
-
align-items: center;
|
|
524
|
-
justify-content: center;
|
|
525
|
-
gap: 8px;
|
|
526
|
-
color: #999;
|
|
527
|
-
font-size: 12px;
|
|
528
|
-
">
|
|
529
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="opacity: 0.7;">
|
|
530
|
-
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" fill="currentColor"/>
|
|
531
|
-
</svg>
|
|
532
|
-
<span>支持微信、浏览器等多种方式</span>
|
|
533
|
-
</div>
|
|
534
|
-
</div>
|
|
535
|
-
`
|
|
536
|
-
)
|
|
537
|
-
this.showModal(modal)
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// 创建AI对话弹窗--- 暂时调 “用户函数”
|
|
541
|
-
private showAIChat(): void {
|
|
542
|
-
this.options.onShowAIChat?.()
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
private createModal(title: string, content: string): HTMLDivElement {
|
|
546
|
-
const modal = document.createElement('div')
|
|
547
|
-
modal.className = 'tiny-remoter-floating-modal'
|
|
548
|
-
modal.innerHTML = `
|
|
549
|
-
<div class="tiny-remoter-modal-overlay"></div>
|
|
550
|
-
<div class="tiny-remoter-modal-content">
|
|
551
|
-
<div class="tiny-remoter-modal-header">
|
|
552
|
-
<h3>${title}</h3>
|
|
553
|
-
<button class="tiny-remoter-modal-close">×</button>
|
|
554
|
-
</div>
|
|
555
|
-
<div class="tiny-remoter-modal-body">
|
|
556
|
-
${content}
|
|
557
|
-
</div>
|
|
558
|
-
</div>
|
|
559
|
-
`
|
|
560
|
-
|
|
561
|
-
// 绑定关闭事件
|
|
562
|
-
const closeBtn = modal.querySelector('.tiny-remoter-modal-close') as HTMLButtonElement
|
|
563
|
-
const overlay = modal.querySelector('.tiny-remoter-modal-overlay') as HTMLDivElement
|
|
564
|
-
|
|
565
|
-
closeBtn.addEventListener('click', () => this.hideModal(modal))
|
|
566
|
-
overlay.addEventListener('click', () => this.hideModal(modal))
|
|
567
|
-
|
|
568
|
-
return modal
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
private showModal(modal: HTMLDivElement): void {
|
|
572
|
-
document.body.appendChild(modal)
|
|
573
|
-
// 添加显示动画
|
|
574
|
-
setTimeout(() => modal.classList.add('show'), 10)
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
private hideModal(modal: HTMLDivElement): void {
|
|
578
|
-
modal.classList.remove('show')
|
|
579
|
-
setTimeout(() => {
|
|
580
|
-
if (modal.parentNode) {
|
|
581
|
-
modal.parentNode.removeChild(modal)
|
|
582
|
-
}
|
|
583
|
-
}, 100)
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// 创建样式表
|
|
587
|
-
private addStyles(): void {
|
|
588
|
-
const style = document.createElement('style')
|
|
589
|
-
style.textContent = `
|
|
590
|
-
/* 浮动块样式 */
|
|
591
|
-
.tiny-remoter-floating-block {
|
|
592
|
-
position: fixed;
|
|
593
|
-
bottom: 30px;
|
|
594
|
-
right: 30px;
|
|
595
|
-
width: 60px;
|
|
596
|
-
height: 60px;
|
|
597
|
-
cursor: pointer;
|
|
598
|
-
display: flex;
|
|
599
|
-
align-items: center;
|
|
600
|
-
justify-content: center;
|
|
601
|
-
color: white;
|
|
602
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
603
|
-
z-index: 99;
|
|
604
|
-
overflow: hidden;
|
|
605
|
-
border-radius: 50%;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
.tiny-remoter-floating-block__icon {
|
|
609
|
-
transform: scale(0.8);
|
|
610
|
-
transition: transform 0.3s ease;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
.tiny-remoter-floating-block__icon:hover {
|
|
614
|
-
transform: scale(1.1);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
.tiny-remoter-floating-block.expanded .tiny-remoter-floating-block__icon {
|
|
618
|
-
transform: scale(1.1);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/* 下拉菜单样式 */
|
|
622
|
-
.tiny-remoter-floating-dropdown {
|
|
623
|
-
position: fixed;
|
|
624
|
-
bottom: 100px;
|
|
625
|
-
right: 30px;
|
|
626
|
-
background: white;
|
|
627
|
-
border-radius: 18px;
|
|
628
|
-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
629
|
-
padding: 24px 24px 0px 24px;
|
|
630
|
-
opacity: 0;
|
|
631
|
-
visibility: hidden;
|
|
632
|
-
transform: translateY(20px) scale(0.95);
|
|
633
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
634
|
-
z-index: 999;
|
|
635
|
-
min-width: 200px;
|
|
636
|
-
width: 223px;
|
|
637
|
-
height: auto;
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.tiny-remoter-floating-dropdown.show {
|
|
641
|
-
opacity: 1;
|
|
642
|
-
visibility: visible;
|
|
643
|
-
transform: translateY(0) scale(1);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
.tiny-remoter-dropdown-item {
|
|
647
|
-
display: flex;
|
|
648
|
-
align-items: center;
|
|
649
|
-
border-radius: 12px;
|
|
650
|
-
cursor: pointer;
|
|
651
|
-
transition: all 0.2s ease;
|
|
652
|
-
color: #333;
|
|
653
|
-
margin-bottom: 24px;
|
|
654
|
-
height: 40px;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
.tiny-remoter-dropdown-item:last-child {
|
|
658
|
-
margin-bottom: 32px;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
.tiny-remoter-dropdown-item > span {
|
|
662
|
-
flex: 1;
|
|
663
|
-
overflow: hidden;
|
|
664
|
-
max-width: 120px;
|
|
665
|
-
text-overflow: ellipsis;
|
|
666
|
-
white-space: nowrap;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
.tiny-remoter-dropdown-item__icon {
|
|
670
|
-
display: flex;
|
|
671
|
-
align-items: center;
|
|
672
|
-
justify-content: center;
|
|
673
|
-
width: 40px;
|
|
674
|
-
height: 40px;
|
|
675
|
-
background: #f8f9fa;
|
|
676
|
-
border-radius: 8px;
|
|
677
|
-
color: #667eea;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
.tiny-remoter-dropdown-item__content {
|
|
681
|
-
flex: 1;
|
|
682
|
-
display: flex;
|
|
683
|
-
flex-direction: column;
|
|
684
|
-
gap: 4px;
|
|
685
|
-
overflow: hidden;
|
|
686
|
-
font-size: 12px;
|
|
687
|
-
margin-left: 16px;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
.tiny-remoter-dropdown-item__desc-wrapper {
|
|
691
|
-
display: flex;
|
|
692
|
-
align-items: center;
|
|
693
|
-
gap: 4px;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
.tiny-remoter-dropdown-item__desc {
|
|
697
|
-
color: #808080;
|
|
698
|
-
overflow: hidden;
|
|
699
|
-
white-space: nowrap;
|
|
700
|
-
text-overflow: ellipsis;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.tiny-remoter-dropdown-item__desc--active {
|
|
704
|
-
color: #1476ff;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.tiny-remoter-dropdown-item__desc--know {
|
|
708
|
-
color: #191919;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/* 复制图标样式 */
|
|
712
|
-
.tiny-remoter-copy-icon {
|
|
713
|
-
display: flex;
|
|
714
|
-
align-items: center;
|
|
715
|
-
justify-content: center;
|
|
716
|
-
width: 24px;
|
|
717
|
-
height: 24px;
|
|
718
|
-
background: #f0f0f0;
|
|
719
|
-
border-radius: 6px;
|
|
720
|
-
color: #666;
|
|
721
|
-
cursor: pointer;
|
|
722
|
-
transition: all 0.2s ease;
|
|
723
|
-
opacity: 0.7;
|
|
724
|
-
margin-left: auto;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
.tiny-remoter-copy-icon:hover {
|
|
728
|
-
background: #e0e0e0;
|
|
729
|
-
color: #333;
|
|
730
|
-
opacity: 1;
|
|
731
|
-
transform: scale(1.1);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
.tiny-remoter-copy-icon:active {
|
|
735
|
-
transform: scale(0.95);
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
/* 弹窗样式 */
|
|
739
|
-
.tiny-remoter-floating-modal {
|
|
740
|
-
position: fixed;
|
|
741
|
-
top: 0;
|
|
742
|
-
left: 0;
|
|
743
|
-
width: 100%;
|
|
744
|
-
height: 100%;
|
|
745
|
-
z-index: 2000;
|
|
746
|
-
display: flex;
|
|
747
|
-
align-items: center;
|
|
748
|
-
justify-content: center;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
.tiny-remoter-modal-overlay {
|
|
752
|
-
position: absolute;
|
|
753
|
-
top: 0;
|
|
754
|
-
left: 0;
|
|
755
|
-
width: 100%;
|
|
756
|
-
height: 100%;
|
|
757
|
-
background: rgba(0, 0, 0, 0.5);
|
|
758
|
-
backdrop-filter: blur(4px);
|
|
759
|
-
opacity: 0;
|
|
760
|
-
transition: opacity 0.3s ease;
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
.tiny-remoter-modal-content {
|
|
764
|
-
background: white;
|
|
765
|
-
border-radius: 16px;
|
|
766
|
-
box-shadow: 0 25px 80px rgba(0, 0, 0, 0.2);
|
|
767
|
-
max-width: 500px;
|
|
768
|
-
width: 90%;
|
|
769
|
-
max-height: 80vh;
|
|
770
|
-
overflow: hidden;
|
|
771
|
-
transform: scale(0.9) translateY(20px);
|
|
772
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
.tiny-remoter-floating-modal.show .tiny-remoter-modal-overlay {
|
|
776
|
-
opacity: 1;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
.tiny-remoter-floating-modal.show .tiny-remoter-modal-content {
|
|
780
|
-
transform: scale(1) translateY(0);
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
.tiny-remoter-modal-header {
|
|
784
|
-
display: flex;
|
|
785
|
-
align-items: center;
|
|
786
|
-
justify-content: space-between;
|
|
787
|
-
padding: 20px 24px;
|
|
788
|
-
border-bottom: 1px solid #f0f0f0;
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
.tiny-remoter-modal-header h3 {
|
|
792
|
-
margin: 0;
|
|
793
|
-
font-size: 18px;
|
|
794
|
-
font-weight: 600;
|
|
795
|
-
color: #333;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
.tiny-remoter-modal-close {
|
|
799
|
-
background: none;
|
|
800
|
-
border: none;
|
|
801
|
-
font-size: 24px;
|
|
802
|
-
cursor: pointer;
|
|
803
|
-
color: #999;
|
|
804
|
-
padding: 0;
|
|
805
|
-
width: 32px;
|
|
806
|
-
height: 32px;
|
|
807
|
-
border-radius: 50%;
|
|
808
|
-
display: flex;
|
|
809
|
-
align-items: center;
|
|
810
|
-
justify-content: center;
|
|
811
|
-
transition: all 0.2s ease;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
.tiny-remoter-modal-close:hover {
|
|
815
|
-
background: #f5f5f5;
|
|
816
|
-
color: #666;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
.tiny-remoter-modal-body {
|
|
820
|
-
padding: 24px;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/* 二维码弹窗动画 */
|
|
824
|
-
@keyframes rotate {
|
|
825
|
-
from {
|
|
826
|
-
transform: rotate(0deg);
|
|
827
|
-
}
|
|
828
|
-
to {
|
|
829
|
-
transform: rotate(360deg);
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
/* 响应式设计 */
|
|
834
|
-
@media (max-width: 768px) {
|
|
835
|
-
.tiny-remoter-floating-block {
|
|
836
|
-
bottom: 20px;
|
|
837
|
-
right: 20px;
|
|
838
|
-
width: 56px;
|
|
839
|
-
height: 56px;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
.tiny-remoter-floating-dropdown {
|
|
843
|
-
bottom: 90px;
|
|
844
|
-
right: 20px;
|
|
845
|
-
min-width: 180px;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
.tiny-remoter-modal-content {
|
|
849
|
-
width: 95%;
|
|
850
|
-
margin: 20px;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
/* 复制反馈提示样式 */
|
|
855
|
-
.tiny-remoter-copy-feedback {
|
|
856
|
-
position: fixed;
|
|
857
|
-
top: 50%;
|
|
858
|
-
left: 50%;
|
|
859
|
-
transform: translate(-50%, -50%);
|
|
860
|
-
background: rgba(0, 0, 0, 0.8);
|
|
861
|
-
color: white;
|
|
862
|
-
padding: 12px 24px;
|
|
863
|
-
border-radius: 8px;
|
|
864
|
-
font-size: 14px;
|
|
865
|
-
font-weight: 500;
|
|
866
|
-
z-index: 10000;
|
|
867
|
-
opacity: 0;
|
|
868
|
-
visibility: hidden;
|
|
869
|
-
transition: all 0.3s ease;
|
|
870
|
-
backdrop-filter: blur(4px);
|
|
871
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
.tiny-remoter-copy-feedback.show {
|
|
875
|
-
opacity: 1;
|
|
876
|
-
visibility: visible;
|
|
877
|
-
transform: translate(-50%, -50%) scale(1);
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
.tiny-remoter-copy-feedback.success {
|
|
881
|
-
background: rgba(34, 197, 94, 0.9);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
.tiny-remoter-copy-feedback.error {
|
|
885
|
-
background: rgba(239, 68, 68, 0.9);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
/* 深色主题支持 */
|
|
889
|
-
@media (prefers-color-scheme: dark) {
|
|
890
|
-
.tiny-remoter-floating-dropdown {
|
|
891
|
-
background: #1a1a1a;
|
|
892
|
-
color: white;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
.tiny-remoter-dropdown-item {
|
|
896
|
-
color: white;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
.tiny-remoter-copy-icon {
|
|
901
|
-
background: #2a2a2a;
|
|
902
|
-
color: #ccc;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
.tiny-remoter-copy-icon:hover {
|
|
906
|
-
background: #3a3a3a;
|
|
907
|
-
color: white;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
.tiny-remoter-modal-content {
|
|
911
|
-
background: #1a1a1a;
|
|
912
|
-
color: white;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
.tiny-remoter-modal-header {
|
|
916
|
-
border-bottom-color: #333;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
.tiny-remoter-modal-header h3 {
|
|
920
|
-
color: white;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
`
|
|
924
|
-
|
|
925
|
-
document.head.appendChild(style)
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// 销毁组件
|
|
929
|
-
public destroy(): void {
|
|
930
|
-
if (this.floatingBlock.parentNode) {
|
|
931
|
-
this.floatingBlock.parentNode.removeChild(this.floatingBlock)
|
|
932
|
-
}
|
|
933
|
-
if (this.dropdownMenu.parentNode) {
|
|
934
|
-
this.dropdownMenu.parentNode.removeChild(this.dropdownMenu)
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// 隐藏组件
|
|
939
|
-
public hide(): void {
|
|
940
|
-
if (this.floatingBlock) {
|
|
941
|
-
this.floatingBlock.style.display = 'none'
|
|
942
|
-
}
|
|
943
|
-
this.closeDropdown()
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// 显示组件
|
|
947
|
-
public show(): void {
|
|
948
|
-
if (this.floatingBlock) {
|
|
949
|
-
this.floatingBlock.style.display = 'flex'
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// 导出组件
|
|
955
|
-
export const createRemoter = (options = {} as FloatingBlockOptions) => {
|
|
956
|
-
return new FloatingBlock(options)
|
|
957
|
-
}
|