mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-30 16:50:47 +08:00
Improve attachment interactions on textareas
Signed-off-by: Rūdolfs Ošiņš <rudolfs@osins.org>
This commit is contained in:
@@ -28,6 +28,9 @@
|
||||
let newEmbeds: EmbedWithOid[] = [];
|
||||
let selectionStart = 0;
|
||||
let selectionEnd = 0;
|
||||
let inputFiles: FileList | undefined = undefined;
|
||||
|
||||
const inputId = `input-label-${crypto.randomUUID()}`;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
submit: { comment: string; embeds: Embed[] };
|
||||
@@ -43,48 +46,79 @@
|
||||
|
||||
const MAX_BLOB_SIZE = 4_194_304;
|
||||
|
||||
function handleFileDrop(event: DragEvent) {
|
||||
function handleFileDrop(event: { detail: DragEvent }) {
|
||||
if (!enableAttachments) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.detail.preventDefault();
|
||||
if (event.detail.dataTransfer) {
|
||||
attachEmbeds(event.detail.dataTransfer.files);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFilePaste(event: ClipboardEvent) {
|
||||
// Always allow pasting text content.
|
||||
if (event.clipboardData && event.clipboardData.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enableAttachments) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) {
|
||||
const embeds = Array.from(event.dataTransfer.files).map(embed);
|
||||
void Promise.all(embeds).then(embeds =>
|
||||
embeds.forEach(embed => {
|
||||
if (embed.content.length > MAX_BLOB_SIZE) {
|
||||
modal.show({
|
||||
component: ErrorModal,
|
||||
props: {
|
||||
title: "File too large",
|
||||
subtitle: [
|
||||
"The file you tried to upload is too large.",
|
||||
"The maximum file size is 4MB.",
|
||||
],
|
||||
error: { message: `File ${embed.name} is too large` },
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
newEmbeds = [
|
||||
...newEmbeds,
|
||||
{
|
||||
oid: embed.oid,
|
||||
name: embed.name,
|
||||
content: embed.content,
|
||||
},
|
||||
];
|
||||
const embedText = `\n`;
|
||||
body = body
|
||||
.slice(0, selectionStart)
|
||||
.concat(embedText, body.slice(selectionEnd));
|
||||
selectionStart += embedText.length;
|
||||
selectionEnd = selectionStart;
|
||||
}),
|
||||
);
|
||||
if (event.clipboardData) {
|
||||
attachEmbeds(event.clipboardData.files);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(event: Event) {
|
||||
if (!enableAttachments) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
if (inputFiles) {
|
||||
attachEmbeds(inputFiles);
|
||||
}
|
||||
}
|
||||
|
||||
function attachEmbeds(files: FileList) {
|
||||
const embeds = Array.from(files).map(embed);
|
||||
void Promise.all(embeds).then(embeds =>
|
||||
embeds.forEach(embed => {
|
||||
if (embed.content.length > MAX_BLOB_SIZE) {
|
||||
modal.show({
|
||||
component: ErrorModal,
|
||||
props: {
|
||||
title: "File too large",
|
||||
subtitle: [
|
||||
"The file you tried to upload is too large.",
|
||||
"The maximum file size is 4MB.",
|
||||
],
|
||||
error: { message: `File ${embed.name} is too large` },
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
newEmbeds = [
|
||||
...newEmbeds,
|
||||
{
|
||||
oid: embed.oid,
|
||||
name: embed.name,
|
||||
content: embed.content,
|
||||
},
|
||||
];
|
||||
const embedText = `\n`;
|
||||
body = body
|
||||
.slice(0, selectionStart)
|
||||
.concat(embedText, body.slice(selectionEnd));
|
||||
selectionStart += embedText.length;
|
||||
selectionEnd = selectionStart;
|
||||
}),
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -107,6 +141,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
@@ -124,6 +159,12 @@
|
||||
margin-left: 1px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
label {
|
||||
color: var(--color-foreground-contrast);
|
||||
}
|
||||
label:hover {
|
||||
color: var(--color-foreground-primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="comment-section" class:inline>
|
||||
@@ -153,8 +194,16 @@
|
||||
<Markdown content={body} embeds={newEmbeds} />
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
multiple
|
||||
bind:files={inputFiles}
|
||||
style:display="none"
|
||||
type="file"
|
||||
id={inputId}
|
||||
on:change={handleFileSelect} />
|
||||
<Textarea
|
||||
on:drop={handleFileDrop}
|
||||
on:paste={handleFilePaste}
|
||||
bind:selectionEnd
|
||||
bind:selectionStart
|
||||
{focus}
|
||||
@@ -165,8 +214,15 @@
|
||||
<div class="actions">
|
||||
{#if !preview}
|
||||
<div class="caption">
|
||||
Markdown supported. {#if enableAttachments}Drop attachments into the
|
||||
text area.{/if} Press {utils.isMac() ? "⌘" : "ctrl"}↵ to submit.
|
||||
{#if enableAttachments}
|
||||
Add files by dragging & dropping, <label
|
||||
for={inputId}
|
||||
style:cursor="pointer">
|
||||
selecting
|
||||
</label>
|
||||
or pasting them.
|
||||
{/if}
|
||||
Markdown supported. Press {utils.isMac() ? "⌘" : "ctrl"}↵ to submit.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="buttons">
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import Loading from "./Loading.svelte";
|
||||
|
||||
export let title: string | undefined = undefined;
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
export let inline: boolean = false;
|
||||
export let loading: boolean = false;
|
||||
export let title: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -20,6 +21,9 @@
|
||||
gap: 0.25rem;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
.button:hover {
|
||||
color: var(--color-foreground-contrast);
|
||||
background-color: var(--color-fill-ghost);
|
||||
@@ -36,6 +40,7 @@
|
||||
aria-label={ariaLabel}
|
||||
{title}
|
||||
class="button"
|
||||
class:inline
|
||||
on:click>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -49,8 +49,11 @@
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
submit: null;
|
||||
drop: DragEvent;
|
||||
}>();
|
||||
|
||||
let dragging: boolean = false;
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const auxiliarKey = isMac() ? event.metaKey : event.ctrlKey;
|
||||
if (auxiliarKey && event.key === "Enter") {
|
||||
@@ -60,6 +63,11 @@
|
||||
textareaElement?.blur();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropAndForward(event: DragEvent) {
|
||||
dragging = false;
|
||||
dispatch("drop", event);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -106,6 +114,9 @@
|
||||
textarea:focus {
|
||||
border: 1px solid var(--color-fill-secondary);
|
||||
}
|
||||
.drag {
|
||||
border: 1px dashed var(--color-fill-secondary) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<textarea
|
||||
@@ -114,10 +125,14 @@
|
||||
aria-label="textarea-comment"
|
||||
class="txt-small"
|
||||
class:resizable
|
||||
class:drag={dragging}
|
||||
{placeholder}
|
||||
on:change
|
||||
on:click
|
||||
on:input
|
||||
on:drop
|
||||
on:drop={handleDropAndForward}
|
||||
on:paste
|
||||
on:dragenter={() => (dragging = true)}
|
||||
on:dragleave={() => (dragging = false)}
|
||||
on:keydown|stopPropagation={handleKeydown}
|
||||
on:keypress />
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
|
||||
const api = new HttpdClient(baseUrl);
|
||||
|
||||
function handleFileDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) {
|
||||
const embeds = Array.from(event.dataTransfer.files).map(embed);
|
||||
function handleFileDrop(event: { detail: DragEvent }) {
|
||||
event.detail.preventDefault();
|
||||
if (event.detail.dataTransfer) {
|
||||
const embeds = Array.from(event.detail.dataTransfer.files).map(embed);
|
||||
void Promise.all(embeds).then(embeds =>
|
||||
embeds.forEach(({ oid, name, content }) => {
|
||||
newEmbeds = [
|
||||
|
||||
Reference in New Issue
Block a user