Skip to content

Commit 5710e0f

Browse files
committed
fix
1 parent 15f2c3f commit 5710e0f

File tree

5 files changed

+60
-35
lines changed

5 files changed

+60
-35
lines changed

web_src/js/features/comp/EditorUpload.js

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import {htmlEscape} from 'escape-goat';
22
import {POST} from '../../modules/fetch.js';
33
import {imageInfo} from '../../utils/image.js';
4-
import {replaceTextareaSelection} from '../../utils/dom.js';
4+
import {createElementFromAttrs, replaceTextareaSelection} from '../../utils/dom.js';
55
import {isUrl} from '../../utils/url.js';
6-
import {extname} from '../../utils.js';
6+
import {extname, isWellKnownImageFilename} from '../../utils.js';
77
import {triggerEditorContentChanged} from './EditorMarkdown.js';
8-
import {getComboMarkdownEditor} from './ComboMarkdownEditor.js';
98

109
async function uploadFile(file, uploadUrl) {
1110
const formData = new FormData();
@@ -81,43 +80,51 @@ class CodeMirrorEditor {
8180
}
8281
}
8382

84-
// FIXME: handle non-image files
85-
async function handleClipboardImages(editor, dropzone, images, e) {
83+
function isImageFile(file) {
84+
return file.type?.startsWith('image/') || isWellKnownImageFilename(file.name);
85+
}
86+
87+
async function handleUploadFiles(editor, dropzone, files, e) {
8688
const uploadUrl = dropzone.getAttribute('data-upload-url');
8789
const filesContainer = dropzone.querySelector('.files');
8890

8991
e.preventDefault();
9092
e.stopPropagation();
9193

92-
for (const img of images) {
93-
const name = img.name.slice(0, img.name.lastIndexOf('.'));
94-
const {width, dppx} = await imageInfo(img);
95-
const placeholder = `![${name}](uploading ...)`;
94+
for (const file of files) {
95+
const name = file.name.slice(0, file.name.lastIndexOf('.'));
96+
const isImage = isImageFile(file);
97+
98+
let placeholder = `[${name}](uploading ...)`;
99+
if (isImage) placeholder = `!${placeholder}`;
96100

97101
editor.insertPlaceholder(placeholder);
98-
const {uuid} = await uploadFile(img, uploadUrl);
99-
100-
let text;
101-
if (width > 0 && dppx > 1) {
102-
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
103-
// method to change image size in Markdown that is supported by all implementations.
104-
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
105-
const url = `attachments/${uuid}`;
106-
text = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
102+
const {uuid} = await uploadFile(file, uploadUrl);
103+
104+
let fileMarkdownLink;
105+
if (isImage) {
106+
const {width, dppx} = await imageInfo(file);
107+
if (width > 0 && dppx > 1) {
108+
// Scale down images from HiDPI monitors. This uses the <img> tag because it's the only
109+
// method to change image size in Markdown that is supported by all implementations.
110+
// Make the image link relative to the repo path, then the final URL is "/sub-path/owner/repo/attachments/{uuid}"
111+
const url = `attachments/${uuid}`;
112+
fileMarkdownLink = `<img width="${Math.round(width / dppx)}" alt="${htmlEscape(name)}" src="${htmlEscape(url)}">`;
113+
} else {
114+
// Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}"
115+
// TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments"
116+
const url = `/attachments/${uuid}`;
117+
fileMarkdownLink = `![${name}](${url})`;
118+
}
107119
} else {
108-
// Markdown always renders the image with a relative path, so the final URL is "/sub-path/owner/repo/attachments/{uuid}"
109-
// TODO: it should also use relative path for consistency, because absolute is ambiguous for "/sub-path/attachments" or "/attachments"
110120
const url = `/attachments/${uuid}`;
111-
text = `![${name}](${url})`;
121+
fileMarkdownLink = `[${name}](${url})`;
112122
}
113-
editor.replacePlaceholder(placeholder, text);
123+
editor.replacePlaceholder(placeholder, fileMarkdownLink);
114124

115-
const input = document.createElement('input');
116-
input.setAttribute('name', 'files');
117-
input.setAttribute('type', 'hidden');
118-
input.setAttribute('id', uuid);
119-
input.value = uuid;
125+
const input = createElementFromAttrs('input', {type: 'hidden', name: 'files', id: `dropzone-file-${uuid}`, value: uuid});
120126
filesContainer.append(input);
127+
// TODO: add upload preview item
121128
}
122129
}
123130

@@ -152,7 +159,7 @@ export function initEasyMDEPaste(easyMDE, dropzone) {
152159
easyMDE.codemirror.on('paste', (_, e) => {
153160
const {images} = getPastedContent(e);
154161
if (images.length) {
155-
handleClipboardImages(new CodeMirrorEditor(easyMDE.codemirror), dropzone, images, e);
162+
handleUploadFiles(new CodeMirrorEditor(easyMDE.codemirror), dropzone, images, e);
156163
}
157164
});
158165
}
@@ -168,22 +175,22 @@ export function initTextareaPaste(textarea, dropzone) {
168175
textarea.addEventListener('paste', (e) => {
169176
const {images, text} = getPastedContent(e);
170177
if (images.length) {
171-
handleClipboardImages(new TextareaEditor(textarea), dropzone, images, e);
178+
handleUploadFiles(new TextareaEditor(textarea), dropzone, images, e);
172179
} else if (text) {
173180
handleClipboardText(textarea, e, {text, isShiftDown});
174181
}
175182
});
176183
textarea.addEventListener('drop', (e) => {
177-
const acceptedFiles = getComboMarkdownEditor(textarea).dropzone.getAttribute('data-accepts');
184+
const acceptedFiles = dropzone.getAttribute('data-accepts');
178185
const files = [];
179-
for (const item of e.dataTransfer?.items ?? []) {
186+
for (const item of e.dataTransfer.items) {
180187
if (item?.kind !== 'file') continue;
181188
const file = item.getAsFile();
189+
// FIXME: the check is not right. acceptedFiles could be ".zip, audio/*, image/png, */*"
182190
if (acceptedFiles.includes(extname(file.name))) {
183191
files.push(file);
184192
}
185193
}
186-
// FIXME: handle upload files
187-
handleClipboardImages(new TextareaEditor(textarea), dropzone, files, e);
194+
handleUploadFiles(new TextareaEditor(textarea), dropzone, files, e);
188195
});
189196
}

web_src/js/features/dropzone.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ export async function initDropzone(dropzoneEl) {
7878

7979
dzInst.on('removedfile', async (file) => {
8080
if (disableRemovedfileEvent) return;
81+
82+
// TODO: remove the link from editor
83+
8184
document.querySelector(`#dropzone-file-${file.uuid}`)?.remove();
8285
// when the uploaded file number reaches the limit, there is no uuid in the dict, and it doesn't need to be removed from server
8386
if (removeAttachmentUrl && fileUuidDict[file.uuid] && !fileUuidDict[file.uuid].submitted) {

web_src/js/utils.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export function basename(path = '') {
88

99
// transform /path/to/file.ext to .ext
1010
export function extname(path = '') {
11+
const lastSlashIndex = path.lastIndexOf('/');
1112
const lastPointIndex = path.lastIndexOf('.');
13+
if (lastSlashIndex > lastPointIndex) return '';
1214
return lastPointIndex < 0 ? '' : path.substring(lastPointIndex);
1315
}
1416

@@ -142,3 +144,7 @@ export function serializeXml(node) {
142144
}
143145

144146
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
147+
148+
export function isWellKnownImageFilename(fn) {
149+
return /\.(jpe?g|png|gif|webp|svg)$/i.test(fn);
150+
}

web_src/js/utils.test.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
basename, extname, isObject, stripTags, parseIssueHref,
33
parseUrl, translateMonth, translateDay, blobToDataURI,
4-
toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64,
4+
toAbsoluteUrl, encodeURLEncodedBase64, decodeURLEncodedBase64, isWellKnownImageFilename,
55
} from './utils.js';
66

77
test('basename', () => {
@@ -15,6 +15,7 @@ test('extname', () => {
1515
expect(extname('/path/')).toEqual('');
1616
expect(extname('/path')).toEqual('');
1717
expect(extname('file.js')).toEqual('.js');
18+
expect(extname('/my.path/file')).toEqual('');
1819
});
1920

2021
test('isObject', () => {
@@ -112,3 +113,12 @@ test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => {
112113
expect(Array.from(decodeURLEncodedBase64('YQ'))).toEqual(Array.from(uint8array('a')));
113114
expect(Array.from(decodeURLEncodedBase64('YQ=='))).toEqual(Array.from(uint8array('a')));
114115
});
116+
117+
test('isWellKnownImageFilename', () => {
118+
for (const filename of ['a.jpg', '/a.jpeg', '.file.png', '.webp', 'file.svg']) {
119+
expect(isWellKnownImageFilename(filename)).toBeTruthy();
120+
}
121+
for (const filename of ['', 'a.jpg.x', '/path.png/x', 'webp']) {
122+
expect(isWellKnownImageFilename(filename)).toBeFalsy();
123+
}
124+
});

web_src/js/utils/dom.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {debounce} from 'throttle-debounce';
2-
import {extname} from '../utils.js';
32

43
function elementsCall(el, func, ...args) {
54
if (typeof el === 'string' || el instanceof String) {

0 commit comments

Comments
 (0)