Skip to content

Commit ccb3b91

Browse files
committed
Enable more markdown paste features in textarea
1 parent 69e595c commit ccb3b91

File tree

4 files changed

+16
-52
lines changed

4 files changed

+16
-52
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"@citation-js/plugin-csl": "0.7.18",
1212
"@citation-js/plugin-software-formats": "0.6.1",
1313
"@github/markdown-toolbar-element": "2.2.3",
14+
"@github/paste-markdown": "1.5.3",
1415
"@github/relative-time-element": "4.4.8",
1516
"@github/text-expander-element": "2.9.2",
1617
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web_src/js/features/comp/EditorUpload.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {imageInfo} from '../../utils/image.ts';
2-
import {replaceTextareaSelection} from '../../utils/dom.ts';
32
import {isUrl} from '../../utils/url.ts';
43
import {textareaInsertText, triggerEditorContentChanged} from './EditorMarkdown.ts';
54
import {
65
DropzoneCustomEventRemovedFile,
76
DropzoneCustomEventUploadDone,
87
generateMarkdownLinkForAttachment,
98
} from '../dropzone.ts';
9+
import {subscribe} from '@github/paste-markdown';
1010
import type CodeMirror from 'codemirror';
1111
import type EasyMDE from 'easymde';
1212
import type {DropzoneFile} from 'dropzone';
@@ -129,35 +129,20 @@ export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: nu
129129
return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null;
130130
}
131131

132-
function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) {
133-
// pasting with "shift" means "paste as original content" in most applications
134-
if (isShiftDown) return; // let the browser handle it
135-
136-
// when pasting links over selected text, turn it into [text](link)
137-
const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText);
138-
if (pastedAsMarkdown) {
139-
e.preventDefault();
140-
replaceTextareaSelection(textarea, pastedAsMarkdown);
141-
}
142-
// else, let the browser handle it
143-
}
144-
145-
// extract text and images from "paste" event
146-
function getPastedContent(e: ClipboardEvent) {
147-
const images = [];
132+
function getPastedImages(e: ClipboardEvent) {
133+
const images: Array<File> = [];
148134
for (const item of e.clipboardData?.items ?? []) {
149135
if (item.type?.startsWith('image/')) {
150136
images.push(item.getAsFile());
151137
}
152138
}
153-
const text = e.clipboardData?.getData?.('text') ?? '';
154-
return {text, images};
139+
return images;
155140
}
156141

157142
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
158143
const editor = new CodeMirrorEditor(easyMDE.codemirror as any);
159144
easyMDE.codemirror.on('paste', (_, e) => {
160-
const {images} = getPastedContent(e);
145+
const images = getPastedImages(e);
161146
if (!images.length) return;
162147
handleUploadFiles(editor, dropzoneEl, images, e);
163148
});
@@ -173,19 +158,11 @@ export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
173158
}
174159

175160
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) {
176-
let isShiftDown = false;
177-
textarea.addEventListener('keydown', (e: KeyboardEvent) => {
178-
if (e.shiftKey) isShiftDown = true;
179-
});
180-
textarea.addEventListener('keyup', (e: KeyboardEvent) => {
181-
if (!e.shiftKey) isShiftDown = false;
182-
});
161+
subscribe(textarea); // enable paste-related features
183162
textarea.addEventListener('paste', (e: ClipboardEvent) => {
184-
const {images, text} = getPastedContent(e);
163+
const images = getPastedImages(e);
185164
if (images.length && dropzoneEl) {
186165
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e);
187-
} else if (text) {
188-
handleClipboardText(textarea, e, text, isShiftDown);
189166
}
190167
});
191168
textarea.addEventListener('drop', (e: DragEvent) => {

web_src/js/utils/dom.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -286,28 +286,6 @@ export function isElemVisible(el: HTMLElement): boolean {
286286
return !el.classList.contains('tw-hidden') && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none';
287287
}
288288

289-
/** replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this */
290-
export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) {
291-
const before = textarea.value.slice(0, textarea.selectionStart ?? undefined);
292-
const after = textarea.value.slice(textarea.selectionEnd ?? undefined);
293-
let success = false;
294-
295-
textarea.contentEditable = 'true';
296-
try {
297-
success = document.execCommand('insertText', false, text); // eslint-disable-line @typescript-eslint/no-deprecated
298-
} catch {} // ignore the error if execCommand is not supported or failed
299-
textarea.contentEditable = 'false';
300-
301-
if (success && !textarea.value.slice(0, textarea.selectionStart ?? undefined).endsWith(text)) {
302-
success = false;
303-
}
304-
305-
if (!success) {
306-
textarea.value = `${before}${text}${after}`;
307-
textarea.dispatchEvent(new CustomEvent('change', {bubbles: true, cancelable: true}));
308-
}
309-
}
310-
311289
export function createElementFromHTML<T extends HTMLElement>(htmlString: string): T {
312290
htmlString = htmlString.trim();
313291
// There is no way to create some elements without a proper parent, jQuery's approach: https://github.com/jquery/jquery/blob/main/src/manipulation/wrapMap.js

0 commit comments

Comments
 (0)