Skip to content

Commit a3db08c

Browse files
committed
feat: 支持自动上传远程图片
1 parent 4e12eaf commit a3db08c

File tree

9 files changed

+169
-49
lines changed

9 files changed

+169
-49
lines changed

.vscode/settings.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"editor.rulers": [100],
3-
"prettier.eslintIntegration": false,
43
"editor.defaultFormatter": "esbenp.prettier-vscode",
54
"eslint.validate": ["javascript", "javascriptreact", "typescript"],
65
"editor.formatOnSave": true,

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/preview.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slimkit/plus-editor",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "rich-text editor for plus",
55
"main": "dist/main.bundle.js",
66
"repository": "[email protected]:mutoe/plus-editor.git",

src/blots/image.ts

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export class ImageBlot extends BlockEmbed {
1717
static className = 'image-container'
1818

1919
static quill: Quill
20+
static remoteCounter = 1
2021
static eventEmitter = new EventEmitter()
2122
static uploadStatus: { [key: string]: any } = {}
2223

@@ -31,7 +32,7 @@ export class ImageBlot extends BlockEmbed {
3132

3233
const {
3334
img,
34-
value: { id, src, width = 0, height = 0 },
35+
value: { id, src, srcNode, width = 0, height = 0 },
3536
} = ImageBlot.getImgAndValue(node)
3637

3738
if (id) {
@@ -50,27 +51,9 @@ export class ImageBlot extends BlockEmbed {
5051

5152
if (img && src) {
5253
const appendEls = (width: number, height: number) => {
53-
const wrap = node.querySelector('.image-wrap')
54+
const wrap = node.querySelector('.image-wrap')!
5455

55-
if (!wrap) return
56-
57-
if (id && !node.querySelector('.progress-bar')) {
58-
const div = ImageBlot.createDiv('progress-bar')
59-
div.appendChild(ImageBlot.createDiv('progress'))
60-
wrap.appendChild(div)
61-
}
62-
63-
if (id && !node.querySelector('.error')) {
64-
const div = ImageBlot.createDiv('error')
65-
div.addEventListener('click', () => {
66-
ImageBlot.uploadStatus[id] = {}
67-
this.domNode.classList.remove('fail')
68-
ImageBlot.eventEmitter.emit('reupload', { id })
69-
})
70-
wrap.appendChild(div)
71-
}
72-
73-
if (width >= 100 && height >= 100) {
56+
if (width >= 50 && height >= 50) {
7457
if (!node.querySelector('.remove')) {
7558
const div = ImageBlot.createDiv('remove')
7659
div.addEventListener('click', () => {
@@ -80,7 +63,18 @@ export class ImageBlot extends BlockEmbed {
8063
}
8164
}
8265

83-
if (id) ImageBlot.refreshUpload(id)
66+
if (id) {
67+
ImageBlot.refreshUpload(id)
68+
} else if (!srcNode) {
69+
const remoteId = `remote-${ImageBlot.remoteCounter++}`
70+
node.classList.add(remoteId)
71+
ImageBlot.eventEmitter.emit('insertRemoteImage', {
72+
remoteId,
73+
src,
74+
width,
75+
height,
76+
})
77+
}
8478
}
8579

8680
if (width && height) {
@@ -112,6 +106,19 @@ export class ImageBlot extends BlockEmbed {
112106
}, 0)
113107
}
114108

109+
static uploadRemoteImage(remoteId: string, id: string) {
110+
if (ImageBlot.uploadStatus[id]) return
111+
112+
const node = document.querySelector(`div.${remoteId}`)
113+
if (node) {
114+
ImageBlot.uploadStatus[id] = {}
115+
node.classList.add(`image-${id}`)
116+
node.classList.remove(remoteId)
117+
118+
node.querySelector<HTMLImageElement>('img.image')!.dataset.id = id
119+
}
120+
}
121+
115122
static updateUploadProgress(id: string, progress: number) {
116123
if ((ImageBlot.uploadStatus[id] || {}).status === 'SUCCESS') return
117124

@@ -120,7 +127,17 @@ export class ImageBlot extends BlockEmbed {
120127
document.querySelectorAll<HTMLDivElement>(`div.image-${id}`).forEach(node => {
121128
node.classList.remove('fail')
122129

123-
node.querySelector<HTMLDivElement>('div.progress')!.style.width = `${Math.round(progress)}%`
130+
const wrap = node.querySelector('.image-wrap')!
131+
if (!wrap.querySelector('.progress-bar')) {
132+
const div = ImageBlot.createDiv('progress-bar')
133+
const child = ImageBlot.createDiv('progress')
134+
child.style.width = `${Math.round(progress)}%`
135+
div.appendChild(child)
136+
wrap.appendChild(div)
137+
} else {
138+
const child = wrap.querySelector<HTMLDivElement>('.progress')!
139+
child.style.width = `${Math.round(progress)}%`
140+
}
124141
})
125142
}
126143

@@ -132,25 +149,36 @@ export class ImageBlot extends BlockEmbed {
132149
document.querySelectorAll<HTMLDivElement>(`div.image-${id}`).forEach(node => {
133150
if (!node.classList.contains('fail')) node.classList.add('fail')
134151

135-
const div = node.querySelector<HTMLDivElement>('div.error')
152+
const wrap = node.querySelector<HTMLDivElement>('.image-wrap')!
153+
let div = wrap.querySelector<HTMLDivElement>('div.error')
136154

137-
if (div) {
138-
let html = '<span>上传失败,点击重试</span>'
139-
if (error) {
140-
html += `<span>${error}</span>`
141-
}
155+
if (!div) {
156+
div = ImageBlot.createDiv('error')
157+
div.addEventListener('click', () => {
158+
node.classList.remove('fail')
159+
ImageBlot.uploadStatus[id] = {}
160+
ImageBlot.eventEmitter.emit('reupload', { id })
161+
})
162+
wrap.appendChild(div)
163+
}
142164

143-
div.innerHTML = html
165+
let html = '<span>上传失败,点击重试</span>'
166+
if (error) {
167+
html += `<span>${error}</span>`
144168
}
169+
170+
div.innerHTML = html
145171
})
146172
}
147173

148174
static setUploadSuccess(id: string, src: string, srcNode?: string) {
149175
ImageBlot.uploadStatus[id] = { status: 'SUCCESS', src }
150176

151177
document.querySelectorAll<HTMLDivElement>(`div.image-${id}`).forEach(node => {
152-
node.querySelector<HTMLDivElement>('div.progress-bar')!.remove()
153-
node.querySelector<HTMLDivElement>('div.error')!.remove()
178+
const p = node.querySelector<HTMLDivElement>('div.progress-bar')
179+
const e = node.querySelector<HTMLDivElement>('div.error')
180+
if (p) p.remove()
181+
if (e) e.remove()
154182

155183
const img = node.querySelector<HTMLImageElement>('img.image')
156184

src/caller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ export function callMethod(fnName: string, params: any = undefined) {
2929
window.parent.postMessage({ funcName: fnName, params }, '*')
3030
}
3131
} else {
32-
tryHijackUploadCall(fnName, params)
3332
// not in webview
34-
return false
33+
tryHijackUploadCall(fnName, params)
34+
return true
3535
}
36+
3637
return true
3738
}
3839

src/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ ImageBlot.eventEmitter.on('reupload', data => {
138138
}
139139
})
140140

141+
ImageBlot.eventEmitter.on('insertRemoteImage', data => {
142+
if (!callMethod('insertRemoteImage', data)) {
143+
console.log('insertRemoteImage', data)
144+
}
145+
})
146+
141147
/** 收到图片后预览 */
142148
window.imagePreviewReceiver = data => {
143149
const srcList = JSON.parse(data)
@@ -160,6 +166,13 @@ window.imagePreviewReceiver = data => {
160166
setTimeout(fixImageSize, 0)
161167
}
162168

169+
window.uploadRemoteImage = (data: string) => {
170+
const { id, remoteId } = JSON.parse(data) || {}
171+
if (remoteId && `${id || ''}`) {
172+
ImageBlot.uploadRemoteImage(remoteId, `${id}`)
173+
}
174+
}
175+
163176
/** 更新图片上传进度 */
164177
window.imageProgressReceiver = (data: string) => {
165178
const { id, progress } = JSON.parse(data) || {}

src/uploader.ts

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ let userToken: string = ''
55
let apiV2BaseUrl: string = ''
66
let storage = { channel: 'public' }
77

8+
try {
9+
const sp = new URL(window.location.href).searchParams
10+
11+
userToken = sp.get('user_token') || ''
12+
apiV2BaseUrl = sp.get('api_v2_base_url') || ''
13+
const uploadStorage = sp.get('upload_storage')
14+
if (uploadStorage) {
15+
storage = JSON.parse(uploadStorage)
16+
}
17+
} catch (err) {
18+
//
19+
}
20+
821
window.setUploaderOptions = options => {
922
if (typeof options === 'string') {
1023
options = JSON.parse(options)
@@ -23,6 +36,7 @@ const uploadFuncNames = [
2336
'removeImage',
2437
'reinsertImage',
2538
'reuploadImage',
39+
'insertRemoteImage',
2640
'chooseVideo',
2741
'removeVideo',
2842
'reinsertVideo',
@@ -36,18 +50,14 @@ export function tryHijackUploadCall(funcName: string, params: any): boolean {
3650

3751
if (funcName.startsWith('choose')) {
3852
chooseAndUpload(funcName.substr(6).toLowerCase())
39-
}
40-
41-
if (funcName.startsWith('remove')) {
53+
} else if (funcName.startsWith('remove')) {
4254
handleRemove(funcName.substr(6).toLowerCase(), params)
43-
}
44-
45-
if (funcName.startsWith('reinsert')) {
55+
} else if (funcName.startsWith('reinsert')) {
4656
handleReinsert(funcName.substr(8).toLowerCase(), params)
47-
}
48-
49-
if (funcName.startsWith('reupload')) {
57+
} else if (funcName.startsWith('reupload')) {
5058
handleReupload(funcName.substr(8).toLowerCase(), params)
59+
} else if (funcName === 'insertRemoteImage') {
60+
uploadRemoteImage(params)
5161
}
5262

5363
return true
@@ -72,11 +82,12 @@ interface UploadFile extends MediaFile {
7282
useCounter: number
7383
status?: string
7484
cancelUpload?: () => void
85+
error?: string
7586
}
7687

7788
const uploadFiles: Map<string, UploadFile> = new Map()
7889

79-
let fileCounter = 0
90+
let fileCounter = 1
8091

8192
function chooseAndUpload(type: string) {
8293
const input = document.createElement('input')
@@ -271,6 +282,69 @@ async function getMediaInfo(file: File) {
271282
return mf
272283
}
273284

285+
async function uploadRemoteImage(params: {
286+
src: string
287+
remoteId: string
288+
width: number
289+
height: number
290+
}) {
291+
const id = `${fileCounter++}`
292+
293+
window.postMessage(
294+
{
295+
funcName: `uploadRemoteImage`,
296+
params: { id, remoteId: params.remoteId },
297+
},
298+
'*',
299+
)
300+
301+
try {
302+
const { headers, data } = await axios.get(params.src, { responseType: 'arraybuffer' })
303+
304+
const contentType = (headers['content-type'] || '').toLowerCase().trim()
305+
if (!data || data.byteLength <= 0 || !contentType || !contentType.startsWith('image/')) {
306+
throw new Error('无法下载远程图片,请手动上传')
307+
}
308+
309+
const spark = new Spark.ArrayBuffer()
310+
const hash = spark.append(data).end()
311+
const filename = `${Date.now()}.${contentType.substr(6)}`
312+
const file = new File([data], filename, {
313+
type: contentType,
314+
})
315+
316+
uploadFiles.set(id, {
317+
id,
318+
useCounter: 1,
319+
image: {
320+
file,
321+
buff: data,
322+
hash,
323+
url: params.src,
324+
width: params.width,
325+
height: params.height,
326+
},
327+
})
328+
} catch (error) {
329+
const buff = new ArrayBuffer(0)
330+
uploadFiles.set(id, {
331+
id,
332+
useCounter: 1,
333+
image: {
334+
file: new File([buff], 'empty'),
335+
buff: buff,
336+
hash: '',
337+
url: params.src,
338+
width: params.width,
339+
height: params.height,
340+
},
341+
error: error.message || '未知错误',
342+
})
343+
}
344+
345+
handleUpload('image', id)
346+
}
347+
274348
async function handleUpload(type: string, id: string) {
275349
const uf = uploadFiles.get(id)
276350

@@ -279,6 +353,10 @@ async function handleUpload(type: string, id: string) {
279353
}
280354

281355
try {
356+
if (uf.error) {
357+
throw new Error(uf.error)
358+
}
359+
282360
const upload = async (info: MediaFileInfo) => {
283361
let source = axios.CancelToken.source()
284362
uf.cancelUpload = source.cancel.bind(source)
@@ -377,7 +455,7 @@ async function handleUpload(type: string, id: string) {
377455
} else {
378456
uf.status = 'ERROR'
379457

380-
let message = '网络异常导致上传失败'
458+
let message = e.message || '网络异常导致上传失败'
381459
if (e.response && e.response.data) {
382460
message = e.response.data.message
383461
if (Array.isArray(message)) {

types/plus-editor/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ declare global {
2222

2323
/** 接收图片预览地址的钩子 */
2424
imagePreviewReceiver: (data: string) => void
25+
uploadRemoteImage: (data: string) => void
2526
/** 接收图片实际上传进度的钩子 */
2627
imageProgressReceiver: (data: string) => void
2728
/** 接收图片实际地址的钩子 */

0 commit comments

Comments
 (0)