| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import {imageInfo} from '../../utils/image.ts';
- import {textareaInsertText, 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) {
- textareaInsertText(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/')) {
- images.push(item.getAsFile());
- }
- }
- 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) {
- 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;
- });
- }
|