diff --git a/web_src/js/features/common-global.js b/web_src/js/features/common-global.js
index e7db9b2336..43b0329dba 100644
--- a/web_src/js/features/common-global.js
+++ b/web_src/js/features/common-global.js
@@ -5,12 +5,13 @@ import {createDropzone} from './dropzone.js';
import {showGlobalErrorMessage} from '../bootstrap.js';
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import {svg} from '../svg.js';
-import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js';
+import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter, getComboMarkdownEditor} from '../utils/dom.js';
import {htmlEscape} from 'escape-goat';
import {showTemporaryTooltip} from '../modules/tippy.js';
import {confirmModal} from './comp/ConfirmModal.js';
import {showErrorToast} from '../modules/toast.js';
import {request, POST, GET} from '../modules/fetch.js';
+import {removeLinksInTextarea} from './comp/ComboMarkdownEditor.js';
import '../htmx.js';
const {appUrl, appSubUrl, csrfToken, i18n} = window.config;
@@ -249,12 +250,13 @@ export function initDropzone(el) {
});
file.previewTemplate.append(copyLinkElement);
});
- this.on('removedfile', (file) => {
- $(`#${file.uuid}`).remove();
+ this.on('removedfile', async (file) => {
+ document.getElementById(file.uuid)?.remove();
if ($dropzone.data('remove-url')) {
- POST($dropzone.data('remove-url'), {
+ await POST($dropzone.data('remove-url'), {
data: new URLSearchParams({file: file.uuid}),
});
+ removeLinksInTextarea(getComboMarkdownEditor(el.closest('form').querySelector('.combo-markdown-editor')), file);
}
});
this.on('error', function (file, message) {
diff --git a/web_src/js/features/comp/ComboMarkdownEditor.js b/web_src/js/features/comp/ComboMarkdownEditor.js
index d3fab375a9..b336bf0f78 100644
--- a/web_src/js/features/comp/ComboMarkdownEditor.js
+++ b/web_src/js/features/comp/ComboMarkdownEditor.js
@@ -296,11 +296,6 @@ class ComboMarkdownEditor {
}
}
-export function getComboMarkdownEditor(el) {
- if (el instanceof $) el = el[0];
- return el?._giteaComboMarkdownEditor;
-}
-
export async function initComboMarkdownEditor(container, options = {}) {
if (container instanceof $) {
if (container.length !== 1) {
@@ -315,3 +310,9 @@ export async function initComboMarkdownEditor(container, options = {}) {
await editor.init();
return editor;
}
+
+export function removeLinksInTextarea(editor, file) {
+ const fileName = file.name.slice(0, file.name.lastIndexOf('.'));
+ const fileText = `\\[${fileName}\\]\\(/attachments/${file.uuid}\\)`;
+ editor.value(editor.value().replace(new RegExp(``, 'g'), '').replace(new RegExp(`\\!${fileText}`, 'g'), '').replace(new RegExp(fileText, 'g'), ''));
+}
diff --git a/web_src/js/features/comp/Paste.js b/web_src/js/features/comp/Paste.js
index b26296d1fc..6a5c63af19 100644
--- a/web_src/js/features/comp/Paste.js
+++ b/web_src/js/features/comp/Paste.js
@@ -82,35 +82,48 @@ class CodeMirrorEditor {
}
}
-async function handleClipboardImages(editor, dropzone, images, e) {
+async function handleClipboardFiles(editor, dropzone, files, e) {
const uploadUrl = dropzone.getAttribute('data-upload-url');
const filesContainer = dropzone.querySelector('.files');
- if (!dropzone || !uploadUrl || !filesContainer || !images.length) return;
+ if (!dropzone || !uploadUrl || !filesContainer || !files.length) return;
e.preventDefault();
e.stopPropagation();
- for (const img of images) {
- const name = img.name.slice(0, img.name.lastIndexOf('.'));
+ for (const file of files) {
+ if (!file) continue;
+ const name = file.name.slice(0, file.name.lastIndexOf('.'));
const placeholder = `![${name}](uploading ...)`;
editor.insertPlaceholder(placeholder);
- const {uuid} = await uploadFile(img, uploadUrl);
- const {width, dppx} = await imageInfo(img);
+ const {uuid} = await uploadFile(file, uploadUrl);
+ const {width, dppx} = await imageInfo(file);
const url = `/attachments/${uuid}`;
let text;
- if (width > 0 && dppx > 1) {
- // Scale down images from HiDPI monitors. This uses the tag because it's the only
- // method to change image size in Markdown that is supported by all implementations.
- text = ``;
+ if (file.type?.startsWith('image/')) {
+ if (width > 0 && dppx > 1) {
+ // Scale down images from HiDPI monitors. This uses the tag because it's the only
+ // method to change image size in Markdown that is supported by all implementations.
+ text = ``;
+ } else {
+ text = `![${name}](${url})`;
+ }
} else {
- text = `![${name}](${url})`;
+ text = `[${name}](${url})`;
}
editor.replacePlaceholder(placeholder, text);
+ file.uuid = uuid;
+ dropzone.dropzone.emit('addedfile', file);
+ if (/\.(jpg|jpeg|png|gif|bmp)$/i.test(file.name)) {
+ const imgSrc = `/attachments/${file.uuid}`;
+ dropzone.dropzone.emit('thumbnail', file, imgSrc);
+ dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
+ }
+ dropzone.dropzone.emit('complete', file);
const input = document.createElement('input');
input.setAttribute('name', 'files');
input.setAttribute('type', 'hidden');
@@ -134,21 +147,25 @@ function handleClipboardText(textarea, text, e) {
}
export function initEasyMDEPaste(easyMDE, dropzone) {
- easyMDE.codemirror.on('paste', (_, e) => {
- const {images} = getPastedContent(e);
- if (images.length) {
- handleClipboardImages(new CodeMirrorEditor(easyMDE.codemirror), dropzone, images, e);
+ const pasteFunc = (e) => {
+ const {files} = getPastedContent(e);
+ if (files.length) {
+ handleClipboardFiles(new CodeMirrorEditor(easyMDE.codemirror), dropzone, files, e);
}
- });
+ };
+ easyMDE.codemirror.on('paste', (_, e) => pasteFunc(e));
+ easyMDE.codemirror.on('drop', (_, e) => pasteFunc(e));
}
export function initTextareaPaste(textarea, dropzone) {
- textarea.addEventListener('paste', (e) => {
- const {images, text} = getPastedContent(e);
- if (images.length) {
- handleClipboardImages(new TextareaEditor(textarea), dropzone, images, e);
+ const pasteFunc = (e) => {
+ const {files, text} = getPastedContent(e);
+ if (files.length) {
+ handleClipboardFiles(new TextareaEditor(textarea), dropzone, files, e);
} else if (text) {
handleClipboardText(textarea, text, e);
}
- });
+ };
+ textarea.addEventListener('paste', (e) => pasteFunc(e));
+ textarea.addEventListener('drop', (e) => pasteFunc(e));
}
diff --git a/web_src/js/features/repo-issue-edit.js b/web_src/js/features/repo-issue-edit.js
index 4c03325c7a..6be928ac86 100644
--- a/web_src/js/features/repo-issue-edit.js
+++ b/web_src/js/features/repo-issue-edit.js
@@ -1,9 +1,9 @@
import $ from 'jquery';
import {handleReply} from './repo-issue.js';
-import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
+import {initComboMarkdownEditor, removeLinksInTextarea} from './comp/ComboMarkdownEditor.js';
import {createDropzone} from './dropzone.js';
import {GET, POST} from '../modules/fetch.js';
-import {hideElem, showElem} from '../utils/dom.js';
+import {hideElem, showElem, getComboMarkdownEditor} from '../utils/dom.js';
import {attachRefIssueContextPopup} from './contextpopup.js';
import {initCommentContent, initMarkupContent} from '../markup/content.js';
@@ -26,7 +26,6 @@ async function onEditContent(event) {
if (!dropzone) return null;
let disableRemovedfileEvent = false; // when resetting the dropzone (removeAllFiles), disable the "removedfile" event
- let fileUuidDict = {}; // to record: if a comment has been saved, then the uploaded files won't be deleted from server when clicking the Remove in the dropzone
const dz = await createDropzone(dropzone, {
url: dropzone.getAttribute('data-upload-url'),
headers: {'X-Csrf-Token': csrfToken},
@@ -45,7 +44,6 @@ async function onEditContent(event) {
init() {
this.on('success', (file, data) => {
file.uuid = data.uuid;
- fileUuidDict[file.uuid] = {submitted: false};
const input = document.createElement('input');
input.id = data.uuid;
input.name = 'files';
@@ -56,19 +54,15 @@ async function onEditContent(event) {
this.on('removedfile', async (file) => {
document.getElementById(file.uuid)?.remove();
if (disableRemovedfileEvent) return;
- if (dropzone.getAttribute('data-remove-url') && !fileUuidDict[file.uuid].submitted) {
+ if (dropzone.getAttribute('data-remove-url')) {
try {
await POST(dropzone.getAttribute('data-remove-url'), {data: new URLSearchParams({file: file.uuid})});
+ removeLinksInTextarea(getComboMarkdownEditor(editContentZone.querySelector('.combo-markdown-editor')), file);
} catch (error) {
console.error(error);
}
}
});
- this.on('submit', () => {
- for (const fileUuid of Object.keys(fileUuidDict)) {
- fileUuidDict[fileUuid].submitted = true;
- }
- });
this.on('reload', async () => {
try {
const response = await GET(editContentZone.getAttribute('data-attachment-url'));
@@ -78,16 +72,16 @@ async function onEditContent(event) {
dz.removeAllFiles(true);
dropzone.querySelector('.files').innerHTML = '';
for (const el of dropzone.querySelectorAll('.dz-preview')) el.remove();
- fileUuidDict = {};
disableRemovedfileEvent = false;
for (const attachment of data) {
- const imgSrc = `${dropzone.getAttribute('data-link-url')}/${attachment.uuid}`;
dz.emit('addedfile', attachment);
- dz.emit('thumbnail', attachment, imgSrc);
+ if (/\.(jpg|jpeg|png|gif|bmp)$/i.test(attachment.name)) {
+ const imgSrc = `${dropzone.getAttribute('data-link-url')}/${attachment.uuid}`;
+ dz.emit('thumbnail', attachment, imgSrc);
+ dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
+ }
dz.emit('complete', attachment);
- fileUuidDict[attachment.uuid] = {submitted: true};
- dropzone.querySelector(`img[src='${imgSrc}']`).style.maxWidth = '100%';
const input = document.createElement('input');
input.id = attachment.uuid;
input.name = 'files';
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js
index 2b2eed58bb..a98c1a73fc 100644
--- a/web_src/js/features/repo-issue.js
+++ b/web_src/js/features/repo-issue.js
@@ -1,9 +1,9 @@
import $ from 'jquery';
import {htmlEscape} from 'escape-goat';
import {showTemporaryTooltip, createTippy} from '../modules/tippy.js';
-import {hideElem, showElem, toggleElem} from '../utils/dom.js';
+import {hideElem, showElem, toggleElem, getComboMarkdownEditor} from '../utils/dom.js';
import {setFileFolding} from './file-fold.js';
-import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
+import {initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
import {toAbsoluteUrl} from '../utils.js';
import {initDropzone} from './common-global.js';
import {POST, GET} from '../modules/fetch.js';
diff --git a/web_src/js/utils/dom.js b/web_src/js/utils/dom.js
index fb23a71725..d9fe774580 100644
--- a/web_src/js/utils/dom.js
+++ b/web_src/js/utils/dom.js
@@ -258,16 +258,27 @@ export function isElemVisible(element) {
return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
+export function getComboMarkdownEditor(el) {
+ if (el.jquery) el = el[0];
+ return el?._giteaComboMarkdownEditor;
+}
+
// extract text and images from "paste" event
export function getPastedContent(e) {
- const images = [];
- for (const item of e.clipboardData?.items ?? []) {
- if (item.type?.startsWith('image/')) {
- images.push(item.getAsFile());
+ const acceptedFiles = getComboMarkdownEditor(e.currentTarget).dropzone.getAttribute('data-accepts');
+ const files = [];
+ const data = e.clipboardData?.items || e.dataTransfer?.items;
+ for (const item of data ?? []) {
+ if (!item.type?.startsWith('text/')) {
+ const file = item.getAsFile();
+ const extName = file.name.slice(file.name.lastIndexOf('.'), file.name.length);
+ if (acceptedFiles.includes(extName)) {
+ files.push(file);
+ }
}
}
const text = e.clipboardData?.getData?.('text') ?? '';
- return {text, images};
+ return {text, files};
}
// replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this