mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
6.0 KiB
168 lines
6.0 KiB
import {imageInfo} from '../../utils/image.ts'; |
|
import {replaceTextareaSelection, triggerEditorContentChanged} from './EditorMarkdown.ts'; |
|
import { |
|
DropzoneCustomEventRemovedFile, |
|
DropzoneCustomEventUploadDone, |
|
generateMarkdownLinkForAttachment, |
|
} from '../dropzone.ts'; |
|
import {subscribe} from '@github/paste-markdown'; |
|
import type CodeMirror from 'codemirror'; |
|
import type EasyMDE from 'easymde'; |
|
import type {DropzoneFile} from 'dropzone'; |
|
|
|
let uploadIdCounter = 0; |
|
|
|
export const EventUploadStateChanged = 'ce-upload-state-changed'; |
|
|
|
export function triggerUploadStateChanged(target: HTMLElement) { |
|
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true})); |
|
} |
|
|
|
function uploadFile(dropzoneEl: HTMLElement, file: File) { |
|
return new Promise((resolve) => { |
|
const curUploadId = uploadIdCounter++; |
|
(file as any)._giteaUploadId = curUploadId; |
|
const dropzoneInst = dropzoneEl.dropzone; |
|
const onUploadDone = ({file}: {file: any}) => { |
|
if (file._giteaUploadId === curUploadId) { |
|
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone); |
|
resolve(file); |
|
} |
|
}; |
|
dropzoneInst.on(DropzoneCustomEventUploadDone, onUploadDone); |
|
// FIXME: this is not entirely correct because `file` does not satisfy DropzoneFile (we have abused the Dropzone for long time) |
|
dropzoneInst.addFile(file as DropzoneFile); |
|
}); |
|
} |
|
|
|
class TextareaEditor { |
|
editor: HTMLTextAreaElement; |
|
|
|
constructor(editor: HTMLTextAreaElement) { |
|
this.editor = editor; |
|
} |
|
|
|
insertPlaceholder(value: string) { |
|
replaceTextareaSelection(this.editor, value); |
|
} |
|
|
|
replacePlaceholder(oldVal: string, newVal: string) { |
|
const editor = this.editor; |
|
const startPos = editor.selectionStart; |
|
const endPos = editor.selectionEnd; |
|
if (editor.value.substring(startPos, endPos) === oldVal) { |
|
editor.value = editor.value.substring(0, startPos) + newVal + editor.value.substring(endPos); |
|
editor.selectionEnd = startPos + newVal.length; |
|
} else { |
|
editor.value = editor.value.replace(oldVal, newVal); |
|
editor.selectionEnd -= oldVal.length; |
|
editor.selectionEnd += newVal.length; |
|
} |
|
editor.selectionStart = editor.selectionEnd; |
|
editor.focus(); |
|
triggerEditorContentChanged(editor); |
|
} |
|
} |
|
|
|
class CodeMirrorEditor { |
|
editor: CodeMirror.EditorFromTextArea; |
|
|
|
constructor(editor: CodeMirror.EditorFromTextArea) { |
|
this.editor = editor; |
|
} |
|
|
|
insertPlaceholder(value: string) { |
|
const editor = this.editor; |
|
const startPoint = editor.getCursor('start'); |
|
const endPoint = editor.getCursor('end'); |
|
editor.replaceSelection(value); |
|
endPoint.ch = startPoint.ch + value.length; |
|
editor.setSelection(startPoint, endPoint); |
|
editor.focus(); |
|
triggerEditorContentChanged(editor.getTextArea()); |
|
} |
|
|
|
replacePlaceholder(oldVal: string, newVal: string) { |
|
const editor = this.editor; |
|
const endPoint = editor.getCursor('end'); |
|
if (editor.getSelection() === oldVal) { |
|
editor.replaceSelection(newVal); |
|
} else { |
|
editor.setValue(editor.getValue().replace(oldVal, newVal)); |
|
} |
|
endPoint.ch -= oldVal.length; |
|
endPoint.ch += newVal.length; |
|
editor.setSelection(endPoint, endPoint); |
|
editor.focus(); |
|
triggerEditorContentChanged(editor.getTextArea()); |
|
} |
|
} |
|
|
|
async function handleUploadFiles(editor: CodeMirrorEditor | TextareaEditor, dropzoneEl: HTMLElement, files: Array<File> | FileList, e: Event) { |
|
e.preventDefault(); |
|
for (const file of files) { |
|
const name = file.name.slice(0, file.name.lastIndexOf('.')); |
|
const {width, dppx} = await imageInfo(file); |
|
const placeholder = `[${name}](uploading ...)`; |
|
|
|
editor.insertPlaceholder(placeholder); |
|
await uploadFile(dropzoneEl, file); // the "file" will get its "uuid" during the upload |
|
editor.replacePlaceholder(placeholder, generateMarkdownLinkForAttachment(file, {width, dppx})); |
|
} |
|
} |
|
|
|
export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string) { |
|
text = text.replace(new RegExp(`!?\\[([^\\]]+)\\]\\(/?attachments/${fileUuid}\\)`, 'g'), ''); |
|
text = text.replace(new RegExp(`[<]img[^>]+src="/?attachments/${fileUuid}"[^>]*>`, 'g'), ''); |
|
return text; |
|
} |
|
|
|
function getPastedImages(e: ClipboardEvent) { |
|
const images: Array<File> = []; |
|
for (const item of e.clipboardData?.items ?? []) { |
|
if (item.type?.startsWith('image/')) { |
|
const file = item.getAsFile(); |
|
if (file) { |
|
images.push(file); |
|
} |
|
} |
|
} |
|
return images; |
|
} |
|
|
|
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) { |
|
const editor = new CodeMirrorEditor(easyMDE.codemirror as any); |
|
easyMDE.codemirror.on('paste', (_, e) => { |
|
const images = getPastedImages(e); |
|
if (!images.length) return; |
|
handleUploadFiles(editor, dropzoneEl, images, e); |
|
}); |
|
easyMDE.codemirror.on('drop', (_, e) => { |
|
if (!e.dataTransfer?.files.length) return; |
|
handleUploadFiles(editor, dropzoneEl, e.dataTransfer.files, e); |
|
}); |
|
dropzoneEl.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}) => { |
|
const oldText = easyMDE.codemirror.getValue(); |
|
const newText = removeAttachmentLinksFromMarkdown(oldText, fileUuid); |
|
if (oldText !== newText) easyMDE.codemirror.setValue(newText); |
|
}); |
|
} |
|
|
|
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement | null) { |
|
subscribe(textarea); // enable paste features |
|
textarea.addEventListener('paste', (e: ClipboardEvent) => { |
|
const images = getPastedImages(e); |
|
if (images.length && dropzoneEl) { |
|
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e); |
|
} |
|
}); |
|
textarea.addEventListener('drop', (e: DragEvent) => { |
|
if (!e.dataTransfer?.files.length) return; |
|
if (!dropzoneEl) return; |
|
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, e.dataTransfer.files, e); |
|
}); |
|
dropzoneEl?.dropzone.on(DropzoneCustomEventRemovedFile, ({fileUuid}: {fileUuid: string}) => { |
|
const newText = removeAttachmentLinksFromMarkdown(textarea.value, fileUuid); |
|
if (textarea.value !== newText) textarea.value = newText; |
|
}); |
|
}
|
|
|