This commit is contained in:
Tyrone Yeh 2024-04-23 21:18:44 +02:00 committed by GitHub
commit 7f94005fb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 77 additions and 52 deletions

View File

@ -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;
@ -250,12 +251,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) {

View File

@ -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(`<img [\\s\\w"=]+ alt="${fileName}" src="/attachments/${file.uuid}">`, 'g'), '').replace(new RegExp(`\\!${fileText}`, 'g'), '').replace(new RegExp(fileText, 'g'), ''));
}

View File

@ -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 <img> tag because it's the only
// method to change image size in Markdown that is supported by all implementations.
text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
if (file.type?.startsWith('image/')) {
if (width > 0 && dppx > 1) {
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
// method to change image size in Markdown that is supported by all implementations.
text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
} else {
text = `![${name}](${url})`;
}
} else {
text = `![${name}](${url})`;
text = `[${name}](${url})`;
}
editor.replacePlaceholder(placeholder, text);
file.uuid = uuid;
dropzone.dropzone.emit('addedfile', file);
if (file.type?.startsWith('image/')) {
const imgSrc = `/attachments/${file.uuid}`;
dropzone.dropzone.emit('thumbnail', file, imgSrc);
dropzone.querySelector(`img[src='${CSS.escape(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));
}

View File

@ -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|svg)$/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';

View File

@ -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';

View File

@ -1,4 +1,5 @@
import {debounce} from 'throttle-debounce';
import {extname} from '../utils.js';
function elementsCall(el, func, ...args) {
if (typeof el === 'string' || el instanceof String) {
@ -262,16 +263,26 @@ 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?.kind === 'file') {
const file = item.getAsFile();
if (acceptedFiles.includes(extname(file.name))) {
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