collab-canvas 0.3.0 → 0.4.0
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/README.md +16 -5
- package/dist/host.js +957 -161
- package/lib/ccv-logic.js +281 -0
- package/lib/client.js +1058 -1633
- package/lib/doc-model.js +291 -0
- package/lib/selref.js +107 -20
- package/lib/viewer-md.js +4075 -0
- package/package.json +4 -3
- package/lib/docdrop.js +0 -362
- package/lib/doclink.js +0 -436
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "collab-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "DSH 协作画布插件(话布):人和 AI 共写的 Markdown 文档面。AI 通过 canvas_* 工具读写,带版本冲突检测、可撤销历史与文件持久化。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,9 @@
|
|
|
26
26
|
"test": "node tools/test-all.cjs",
|
|
27
27
|
"test:verbose": "node tools/test-all.cjs --verbose",
|
|
28
28
|
"build": "node tools/build.cjs",
|
|
29
|
-
"deploy": "node tools/deploy.cjs",
|
|
30
|
-
"deploy:
|
|
29
|
+
"deploy": "node tools/deploy-all.cjs",
|
|
30
|
+
"deploy:one": "node tools/deploy.cjs",
|
|
31
|
+
"deploy:check": "node tools/deploy-all.cjs --check",
|
|
31
32
|
"verify": "node tools/verify-all.cjs"
|
|
32
33
|
},
|
|
33
34
|
"main": "dist/host.js",
|
package/lib/docdrop.js
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
// ═══ DSH Collab-Canvas · 文档拖拽上传(独立功能模块,单独一个文件维护)═══
|
|
2
|
-
// 交互:把非图片文件(md/txt/任意类型)拖进 DSH 窗口 → 上传到会话工作区 uploads/
|
|
3
|
-
// 文件夹 → @ 引用自动追加进 DSH 输入框 → 发送后 AI 用文件工具读全文
|
|
4
|
-
// 加载方式:DSH 每个插件只打包一个 client 入口(lib/client.js),本文件由
|
|
5
|
-
// host 端点 /api/canvas/docdrop.js 提供源码,client 入口动态执行——
|
|
6
|
-
// 因此改本文件只需刷新页面,不用重启 dsh-web
|
|
7
|
-
//
|
|
8
|
-
// 健壮性约定:
|
|
9
|
-
// 1) 不依赖 DSH 模块加载器(自执行 IIFE,加载器对新模块可能延迟/忽略)
|
|
10
|
-
// 2) 监听挂在 window 捕获层(比 document 捕获更早,抢在 DSH 自带 drop 处理前)
|
|
11
|
-
// 3) 防重复安装:window 标记位
|
|
12
|
-
// 4) 图片拖拽、话布面板内部的拖拽一律不接管(不干扰 DSH 原生流程)
|
|
13
|
-
// 5) 每个回调独立捕获异常,任何一步失败只写诊断日志 + 提示条,绝不外溢
|
|
14
|
-
// 6) 多文件拖入时逐个上传,单个失败不影响其余
|
|
15
|
-
;(function () {
|
|
16
|
-
try {
|
|
17
|
-
if (window.__ccvDocdropInstalled) return
|
|
18
|
-
|
|
19
|
-
var VERSION = 9 // v8: 拖入 canvas-docs/ 下的文件时,自动注册为话布(按标题找
|
|
20
|
-
// 已有,没有就新建)并 @ 原生相对路径 canvas-docs/xxx.md——
|
|
21
|
-
// 未注册的遗留文档从此能被 AI、点击、画布清单三方识别
|
|
22
|
-
var ENDPOINT = "/api/canvas/import-doc"
|
|
23
|
-
var MAX_BYTES = 100 * 1024 * 1024
|
|
24
|
-
var PANEL_SELECTOR = "#collab-canvas-panel"
|
|
25
|
-
|
|
26
|
-
// ─── 诊断日志(沿用插件现有落盘通道,失败静默)───
|
|
27
|
-
// 优先用 client.js 挂出来的共用实现(见 client.js 里的 window.__ccvKit):
|
|
28
|
-
// 日志这份代码原本在 client/docdrop/doclink 三处各存了一份。
|
|
29
|
-
// 取不到(加载顺序不同、或 client.js 是旧版)就退回下面这份等价副本。
|
|
30
|
-
function log(msg) {
|
|
31
|
-
var kit = window.__ccvKit
|
|
32
|
-
if (kit && kit.log) { kit.log(msg, "docdrop"); return }
|
|
33
|
-
try {
|
|
34
|
-
fetch("/api/canvas/log", {
|
|
35
|
-
method: "POST",
|
|
36
|
-
headers: { "Content-Type": "application/json" },
|
|
37
|
-
body: JSON.stringify({ line: "[docdrop] " + msg })
|
|
38
|
-
}).catch(function () {})
|
|
39
|
-
} catch (_) {}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── 轻提示条(深浅主题自动反色,2.5 秒自动消失)───
|
|
43
|
-
function toast(msg) {
|
|
44
|
-
var kit = window.__ccvKit
|
|
45
|
-
if (kit && kit.toast) { kit.toast(msg, "ccv-docdrop-toast"); return }
|
|
46
|
-
try {
|
|
47
|
-
var old = document.getElementById("ccv-docdrop-toast")
|
|
48
|
-
if (old) old.remove()
|
|
49
|
-
var t = document.createElement("div")
|
|
50
|
-
t.id = "ccv-docdrop-toast"
|
|
51
|
-
t.textContent = msg
|
|
52
|
-
t.style.cssText = "position:fixed;left:50%;bottom:96px;transform:translateX(-50%);" +
|
|
53
|
-
"z-index:2147483000;background:var(--dsw-alias-label-primary,#1a1a1a);" +
|
|
54
|
-
"color:var(--dsw-alias-bg-overlay,#fff);font-size:13px;padding:8px 14px;" +
|
|
55
|
-
"border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,.25);pointer-events:none;" +
|
|
56
|
-
"opacity:1;transition:opacity .3s"
|
|
57
|
-
document.body.appendChild(t)
|
|
58
|
-
setTimeout(function () { t.style.opacity = "0" }, 2200)
|
|
59
|
-
setTimeout(function () { if (t.parentNode) t.remove() }, 2600)
|
|
60
|
-
} catch (_) {}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ─── 找 DSH 输入框(与划词栏「问AI」同一套探测链,逐条判空)───
|
|
64
|
-
function findDshInput() {
|
|
65
|
-
var selectors = [
|
|
66
|
-
'div[data-composer-input="true"]',
|
|
67
|
-
'[data-slot="conversation.input"] textarea',
|
|
68
|
-
'[data-slot="conversation.composer"] textarea',
|
|
69
|
-
'[data-slot="conversation.composer.bar"] textarea',
|
|
70
|
-
'[data-pane="conversation"] textarea',
|
|
71
|
-
'textarea[placeholder*="发消息"]',
|
|
72
|
-
'textarea[placeholder*="说话"]',
|
|
73
|
-
'textarea[placeholder*="消息"]',
|
|
74
|
-
'textarea[placeholder*="输入"]',
|
|
75
|
-
'[contenteditable="true"][role="textbox"]'
|
|
76
|
-
]
|
|
77
|
-
for (var i = 0; i < selectors.length; i++) {
|
|
78
|
-
try {
|
|
79
|
-
var el = document.querySelector(selectors[i])
|
|
80
|
-
if (el) return el
|
|
81
|
-
} catch (_) {}
|
|
82
|
-
}
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ─── 追加文本到输入框(不清空已有内容;三种输入框形态分别处理)───
|
|
87
|
-
function appendDraft(text) {
|
|
88
|
-
var input = findDshInput()
|
|
89
|
-
if (!input) { log("输入框未找到"); return false }
|
|
90
|
-
try {
|
|
91
|
-
if (input.tagName === "TEXTAREA") {
|
|
92
|
-
var desc = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value")
|
|
93
|
-
var cur = (input.value || "").replace(/\s*$/, "")
|
|
94
|
-
if (desc && typeof desc.set === "function") {
|
|
95
|
-
desc.set.call(input, cur ? cur + "\n" + text : text)
|
|
96
|
-
} else {
|
|
97
|
-
input.value = cur ? cur + "\n" + text : text
|
|
98
|
-
}
|
|
99
|
-
input.dispatchEvent(new Event("input", { bubbles: true }))
|
|
100
|
-
} else if (window.__ccvInputActions && typeof window.__ccvInputActions.setDraft === "function") {
|
|
101
|
-
var st = window.__ccvInputState
|
|
102
|
-
var cur2 = st && st.draft ? String(st.draft).replace(/\s*$/, "") : ""
|
|
103
|
-
window.__ccvInputActions.setDraft(cur2 ? cur2 + "\n" + text : text)
|
|
104
|
-
} else {
|
|
105
|
-
var esc = String(text).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">")
|
|
106
|
-
input.innerHTML += "<div>" + esc + "</div>"
|
|
107
|
-
input.dispatchEvent(new InputEvent("input", { bubbles: true, inputType: "insertText", data: text.slice(0, 20) }))
|
|
108
|
-
}
|
|
109
|
-
input.focus()
|
|
110
|
-
return true
|
|
111
|
-
} catch (e) {
|
|
112
|
-
log("追加输入框失败: " + (e && e.message))
|
|
113
|
-
return false
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ─── 上传单个文档到会话工作区 ───
|
|
118
|
-
function uploadDoc(file, onOk, onFail) {
|
|
119
|
-
try {
|
|
120
|
-
if (!file || typeof file.size !== "number") { onFail(file, "非法文件对象"); return }
|
|
121
|
-
if (file.size > MAX_BYTES) { onFail(file, "超过 100MB 上限"); return }
|
|
122
|
-
fetch(ENDPOINT + "?name=" + encodeURIComponent(file.name || "doc.md"), {
|
|
123
|
-
method: "POST",
|
|
124
|
-
headers: { "Content-Type": file.type || "application/octet-stream" },
|
|
125
|
-
body: file
|
|
126
|
-
}).then(function (r) { return r.json() }).then(function (d) {
|
|
127
|
-
if (d && d.ok && d.path) onOk(d.path, file.name)
|
|
128
|
-
else onFail(file, (d && d.error) || "接口返回异常")
|
|
129
|
-
}).catch(function (e) { onFail(file, (e && e.message) || "网络错误") })
|
|
130
|
-
} catch (e) { onFail(file, (e && e.message) || "上传异常") }
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ─── 拖拽判定 ───
|
|
134
|
-
function inCanvasPanel(node) {
|
|
135
|
-
try { return !!(node && node.closest && node.closest(PANEL_SELECTOR)) } catch (_) { return false }
|
|
136
|
-
}
|
|
137
|
-
function hasFileDrag(e) {
|
|
138
|
-
try {
|
|
139
|
-
return !!(e.dataTransfer && e.dataTransfer.types &&
|
|
140
|
-
Array.prototype.indexOf.call(e.dataTransfer.types, "Files") >= 0)
|
|
141
|
-
} catch (_) { return false }
|
|
142
|
-
}
|
|
143
|
-
// 只收非图片文件:图片拖拽完全留给 DSH 原生流程
|
|
144
|
-
function collectNonImageFiles(e) {
|
|
145
|
-
var out = []
|
|
146
|
-
try {
|
|
147
|
-
var items = e.dataTransfer && e.dataTransfer.items
|
|
148
|
-
if (!items) return out
|
|
149
|
-
for (var i = 0; i < items.length; i++) {
|
|
150
|
-
var it = items[i]
|
|
151
|
-
if (!it || it.kind !== "file") continue
|
|
152
|
-
var f = it.getAsFile ? it.getAsFile() : null
|
|
153
|
-
if (f && !(it.type && String(it.type).indexOf("image/") === 0)) out.push(f)
|
|
154
|
-
}
|
|
155
|
-
} catch (_) {}
|
|
156
|
-
return out
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// 会话键:读 client.js 每 2 秒同步的全局 sid(与画布面板同键)
|
|
160
|
-
function sidQ() {
|
|
161
|
-
var kit = window.__ccvKit
|
|
162
|
-
if (kit && kit.sidQ) return kit.sidQ()
|
|
163
|
-
try { const sid = window.__ccvSid; return sid ? "sid=" + encodeURIComponent(sid) : "sid=default"; } catch (_) { return "sid=default"; }
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ─── DSH 图片拖放层善后:我们拦截了 drop,DSH 收不到“拖放结束”信号,
|
|
167
|
-
// 它的全屏“图片拖动到此处”层会一直挂着。补发 dragleave + 把残留层藏掉 ───
|
|
168
|
-
function overlayDirectText(el) {
|
|
169
|
-
// 只看元素自身的直接文本(避免命中把整页内容都算进 textContent 的外层容器)
|
|
170
|
-
var s = ""
|
|
171
|
-
var kids = el.childNodes || []
|
|
172
|
-
for (var i = 0; i < kids.length; i++) {
|
|
173
|
-
if (kids[i].nodeType === 3) s += kids[i].nodeValue
|
|
174
|
-
}
|
|
175
|
-
return s
|
|
176
|
-
}
|
|
177
|
-
function dismissDshImageOverlay() {
|
|
178
|
-
try { window.dispatchEvent(new DragEvent("dragleave")) } catch (_) {}
|
|
179
|
-
try { document.dispatchEvent(new DragEvent("dragleave", { bubbles: true })) } catch (_) {}
|
|
180
|
-
setTimeout(function () {
|
|
181
|
-
try {
|
|
182
|
-
var nodes = document.querySelectorAll("body *")
|
|
183
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
184
|
-
var el = nodes[i]
|
|
185
|
-
if ((el.textContent || "").indexOf("图片拖动到此处即可添加") === -1) continue
|
|
186
|
-
if (overlayDirectText(el).indexOf("图片拖动") === -1) continue
|
|
187
|
-
// 命中文案元素 → 先看自己是不是 fixed/absolute 覆盖层(portal 直挂
|
|
188
|
-
// body 的常见形态),再把祖先链上的覆盖层全部收起
|
|
189
|
-
try { el.dispatchEvent(new DragEvent("dragleave", { bubbles: true })) } catch (_) {}
|
|
190
|
-
var hidden = 0
|
|
191
|
-
try {
|
|
192
|
-
var csSelf = getComputedStyle(el)
|
|
193
|
-
if (csSelf.position === "fixed" || csSelf.position === "absolute") {
|
|
194
|
-
el.style.setProperty("display", "none", "important")
|
|
195
|
-
hidden++
|
|
196
|
-
}
|
|
197
|
-
} catch (_) {}
|
|
198
|
-
var node = el
|
|
199
|
-
while (node.parentElement && node.parentElement !== document.body) {
|
|
200
|
-
node = node.parentElement
|
|
201
|
-
var cs = getComputedStyle(node)
|
|
202
|
-
if (cs.position === "fixed" || cs.position === "absolute") {
|
|
203
|
-
node.style.setProperty("display", "none", "important")
|
|
204
|
-
hidden++
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
if (hidden) {
|
|
208
|
-
log("已收起 DSH 图片拖放残留层(" + hidden + " 层)")
|
|
209
|
-
}
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
} catch (_) {}
|
|
213
|
-
}, 120)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// dragenter/dragover 阶段拿不到 File 对象,但 item.type 可读:据此判断
|
|
217
|
-
// 是否“含非图片文件”的拖拽
|
|
218
|
-
function nonImageFileDrag(e) {
|
|
219
|
-
try {
|
|
220
|
-
var items = e.dataTransfer && e.dataTransfer.items
|
|
221
|
-
if (!items) return false
|
|
222
|
-
var any = false
|
|
223
|
-
for (var i = 0; i < items.length; i++) {
|
|
224
|
-
var it = items[i]
|
|
225
|
-
if (!it || it.kind !== "file") continue
|
|
226
|
-
any = true
|
|
227
|
-
if (!(it.type && String(it.type).indexOf("image/") === 0)) return true
|
|
228
|
-
}
|
|
229
|
-
return false
|
|
230
|
-
} catch (_) { return false }
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// ─── 安装(window 捕获层:window → document → … → 目标,比 DSH 的
|
|
234
|
-
// document 级监听更早触发)───
|
|
235
|
-
function onDragEnter(e) {
|
|
236
|
-
try {
|
|
237
|
-
if (inCanvasPanel(e.target)) return
|
|
238
|
-
// 非图片文件拖入:从 dragenter 就拦掉,DSH 的图片拖放层没有登场机会
|
|
239
|
-
if (hasFileDrag(e) && nonImageFileDrag(e)) {
|
|
240
|
-
e.preventDefault()
|
|
241
|
-
e.stopImmediatePropagation()
|
|
242
|
-
}
|
|
243
|
-
} catch (_) {}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function onDragOver(e) {
|
|
247
|
-
try {
|
|
248
|
-
if (inCanvasPanel(e.target)) return
|
|
249
|
-
// 只拦非图片文件拖拽;纯图片拖拽一个字节都不碰,DSH 原生逻辑全权处理
|
|
250
|
-
if (hasFileDrag(e) && nonImageFileDrag(e)) {
|
|
251
|
-
e.preventDefault()
|
|
252
|
-
e.stopImmediatePropagation()
|
|
253
|
-
}
|
|
254
|
-
} catch (_) {}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function onDrop(e) {
|
|
258
|
-
try {
|
|
259
|
-
if (inCanvasPanel(e.target)) {
|
|
260
|
-
// 话布面板内部:不接管,但挡掉浏览器默认行为(防止整页跳转到被拖文件)
|
|
261
|
-
if (hasFileDrag(e)) e.preventDefault()
|
|
262
|
-
return
|
|
263
|
-
}
|
|
264
|
-
var files = collectNonImageFiles(e)
|
|
265
|
-
if (!files.length) return // 纯图片或非文件拖拽 → 交给 DSH 原生
|
|
266
|
-
|
|
267
|
-
e.preventDefault()
|
|
268
|
-
e.stopImmediatePropagation()
|
|
269
|
-
dismissDshImageOverlay()
|
|
270
|
-
|
|
271
|
-
var pending = files.length
|
|
272
|
-
var refs = []
|
|
273
|
-
var usedFallback = false // 登记表未命中、走了 uploads 副本
|
|
274
|
-
function settle() {
|
|
275
|
-
pending--
|
|
276
|
-
if (pending > 0) return
|
|
277
|
-
if (refs.length) {
|
|
278
|
-
if (appendDraft(refs.join(" ") + " ")) {
|
|
279
|
-
if (usedFallback && lastRegFolders === 0) {
|
|
280
|
-
toast("登记表为空:本次已复制副本到 uploads/ 并引用。点话布工具栏「登记」登记常用文件夹后,拖拽将按真实路径引用")
|
|
281
|
-
} else {
|
|
282
|
-
toast("已处理 " + refs.length + " 个文档,@ 引用已插入输入框")
|
|
283
|
-
}
|
|
284
|
-
} else {
|
|
285
|
-
toast("@ 引用生成失败——输入框未找到")
|
|
286
|
-
}
|
|
287
|
-
} else {
|
|
288
|
-
toast("未生成引用(可能已取消或上传失败),详见诊断日志")
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
// 查文件登记表:按文件名找真实路径(host 端索引);同时记录登记表是否为空
|
|
292
|
-
var lastRegFolders = -1
|
|
293
|
-
function locateReal(name, cb) {
|
|
294
|
-
try {
|
|
295
|
-
fetch("/api/canvas/locate?name=" + encodeURIComponent(name)).then(function (r) { return r.json() }).then(function (d) {
|
|
296
|
-
if (d && d.ok) lastRegFolders = (typeof d.folders === "number") ? d.folders : lastRegFolders
|
|
297
|
-
if (d && d.ok && Array.isArray(d.matches) && d.matches.length) cb(d.matches)
|
|
298
|
-
else cb(null)
|
|
299
|
-
}).catch(function () { cb(null) })
|
|
300
|
-
} catch (_) { cb(null) }
|
|
301
|
-
}
|
|
302
|
-
// 同名多文件:让用户选(prompt 同步,取消则放弃该文件)
|
|
303
|
-
function pickMatch(matches, cb) {
|
|
304
|
-
if (matches.length === 1) return cb(matches[0])
|
|
305
|
-
var list = matches.map(function (m, i) { return (i + 1) + ". " + (m.rel || m.path) }).join("\n")
|
|
306
|
-
var ans = prompt("同名文件有 " + matches.length + " 处,输入序号选择要引用的:\n" + list, "1")
|
|
307
|
-
if (ans === null) return cb(null)
|
|
308
|
-
var idx = parseInt(ans, 10)
|
|
309
|
-
cb(idx >= 1 && idx <= matches.length ? matches[idx - 1] : null)
|
|
310
|
-
}
|
|
311
|
-
files.forEach(function (f) {
|
|
312
|
-
locateReal(f.name, function (matches) {
|
|
313
|
-
if (matches) {
|
|
314
|
-
// 优先:canvas-docs/ 下的文件(画布文档,含未注册的遗留文档)
|
|
315
|
-
// → host 注册为话布(已有则复用)+ @ 原生相对路径,三方(AI/点击/清单)全识别
|
|
316
|
-
var docHit = null
|
|
317
|
-
matches.forEach(function (m) { if (!docHit && m.docsRel) docHit = m })
|
|
318
|
-
if (docHit) {
|
|
319
|
-
fetch("/api/canvas/load-doc?rel=" + encodeURIComponent(docHit.docsRel) + "&" + sidQ()).then(function (r) { return r.json() }).then(function (d) {
|
|
320
|
-
if (d && d.ok && d.id) toast((d.created ? "已登记为话布并引用:" : "已引用话布文档:") + "canvas-docs/" + f.name)
|
|
321
|
-
refs.push("@" + docHit.docsRel)
|
|
322
|
-
settle()
|
|
323
|
-
}).catch(function () {
|
|
324
|
-
// 注册接口失败也不阻断:仍引用相对路径
|
|
325
|
-
refs.push("@" + docHit.docsRel)
|
|
326
|
-
settle()
|
|
327
|
-
})
|
|
328
|
-
return
|
|
329
|
-
}
|
|
330
|
-
// 其次:登记表命中(工作区内其他文件)→ @ 相对/绝对真实路径
|
|
331
|
-
pickMatch(matches, function (hit) {
|
|
332
|
-
if (!hit) { settle(); return } // 用户取消选择
|
|
333
|
-
refs.push("@" + (hit.rel || hit.path))
|
|
334
|
-
settle()
|
|
335
|
-
})
|
|
336
|
-
return
|
|
337
|
-
}
|
|
338
|
-
// 登记表未命中 → 回退:复制进工作区 uploads/,@ 副本路径
|
|
339
|
-
uploadDoc(f, function (p) {
|
|
340
|
-
usedFallback = true
|
|
341
|
-
refs.push("@" + p)
|
|
342
|
-
settle()
|
|
343
|
-
}, function (f2, why) {
|
|
344
|
-
log("上传失败 " + ((f2 && f2.name) || "?") + ": " + why)
|
|
345
|
-
settle()
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
} catch (e) {
|
|
350
|
-
log("drop 处理异常: " + (e && e.message))
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
window.addEventListener("dragenter", onDragEnter, true)
|
|
355
|
-
window.addEventListener("dragover", onDragOver, true)
|
|
356
|
-
window.addEventListener("drop", onDrop, true)
|
|
357
|
-
window.__ccvDocdropInstalled = true
|
|
358
|
-
log("文档拖拽接管已安装 v" + VERSION + "(window 捕获层,dragenter 起拦截)")
|
|
359
|
-
} catch (e) {
|
|
360
|
-
try { console.error("[docdrop] 安装失败", e) } catch (_) {}
|
|
361
|
-
}
|
|
362
|
-
})()
|