mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 02:41:35 +01:00
Enable image pasting in markdown textarea
This commit is contained in:
@@ -113,5 +113,38 @@ export const md = markdownit({
|
|||||||
md.core.ruler.push("header_sections", addSections);
|
md.core.ruler.push("header_sections", addSections);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const handleImagePaste = async (event: ClipboardEvent) => {
|
||||||
|
const clipboardItems = Array.from(event.clipboardData?.items || []);
|
||||||
|
const file = clipboardItems.find((item) => item.type.startsWith("image/"));
|
||||||
|
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const fileObject = file.getAsFile();
|
||||||
|
|
||||||
|
if (!fileObject) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", fileObject);
|
||||||
|
|
||||||
|
const request = await fetch("?/pasteImage", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await request.json();
|
||||||
|
const fileId = JSON.parse(response.data)[1];
|
||||||
|
const fileUrl = `${API_BASE_PREFIX}/rpc/retrieve_file?id=${fileId}`;
|
||||||
|
|
||||||
|
const target = event.target as HTMLTextAreaElement;
|
||||||
|
const newContent =
|
||||||
|
target.value.slice(0, target.selectionStart) +
|
||||||
|
`` +
|
||||||
|
target.value.slice(target.selectionStart);
|
||||||
|
|
||||||
|
return newContent;
|
||||||
|
};
|
||||||
|
|
||||||
export const API_BASE_PREFIX = dev ? "http://localhost:3000" : "/api";
|
export const API_BASE_PREFIX = dev ? "http://localhost:3000" : "/api";
|
||||||
export const NGINX_BASE_PREFIX = dev ? "http://localhost:18000" : "";
|
export const NGINX_BASE_PREFIX = dev ? "http://localhost:18000" : "";
|
||||||
|
|||||||
@@ -180,5 +180,30 @@ export const actions: Actions = {
|
|||||||
success: true,
|
success: true,
|
||||||
message: "Successfully updated footer"
|
message: "Successfully updated footer"
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
pasteImage: async ({ request, fetch, cookies, params }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
const file = data.get("file") as File;
|
||||||
|
|
||||||
|
const fileData = await fetch(`${API_BASE_PREFIX}/rpc/upload_file`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||||
|
Accept: "application/vnd.pgrst.object+json",
|
||||||
|
"X-Website-Id": params.websiteId,
|
||||||
|
"X-Mimetype": file.type,
|
||||||
|
"X-Original-Filename": file.name
|
||||||
|
},
|
||||||
|
body: await file.arrayBuffer()
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileJSON = await fileData.json();
|
||||||
|
|
||||||
|
if (!fileData.ok) {
|
||||||
|
return { success: false, message: fileJSON.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fileId: fileJSON.file_id };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from "$app/forms";
|
import { enhance } from "$app/forms";
|
||||||
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
|
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
|
||||||
import { ALLOWED_MIME_TYPES } from "$lib/utils";
|
import { ALLOWED_MIME_TYPES, handleImagePaste } from "$lib/utils";
|
||||||
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
||||||
import type { ActionData, PageServerData } from "./$types";
|
import type { ActionData, PageServerData } from "./$types";
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
@@ -17,6 +17,11 @@
|
|||||||
const { scrollTop, scrollHeight, clientHeight } = mainContentTextarea;
|
const { scrollTop, scrollHeight, clientHeight } = mainContentTextarea;
|
||||||
textareaScrollTop = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
textareaScrollTop = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePaste = async (event: ClipboardEvent) => {
|
||||||
|
const newContent = await handleImagePaste(event);
|
||||||
|
previewContent = newContent;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SuccessOrError success={form?.success} message={form?.message} />
|
<SuccessOrError success={form?.success} message={form?.message} />
|
||||||
@@ -149,6 +154,7 @@
|
|||||||
bind:value={previewContent}
|
bind:value={previewContent}
|
||||||
bind:this={mainContentTextarea}
|
bind:this={mainContentTextarea}
|
||||||
onscroll={updateScrollPercentage}
|
onscroll={updateScrollPercentage}
|
||||||
|
onpaste={handlePaste}
|
||||||
required>{data.home.main_content}</textarea
|
required>{data.home.main_content}</textarea
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const load: PageServerLoad = async ({ parent, params, cookies, fetch }) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
default: async ({ fetch, cookies, request, params }) => {
|
editArticle: async ({ fetch, cookies, request, params }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
const coverFile = data.get("cover-image") as File;
|
const coverFile = data.get("cover-image") as File;
|
||||||
|
|
||||||
@@ -63,5 +63,30 @@ export const actions: Actions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: "Successfully updated article" };
|
return { success: true, message: "Successfully updated article" };
|
||||||
|
},
|
||||||
|
pasteImage: async ({ request, fetch, cookies, params }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
const file = data.get("file") as File;
|
||||||
|
|
||||||
|
const fileData = await fetch(`${API_BASE_PREFIX}/rpc/upload_file`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||||
|
Accept: "application/vnd.pgrst.object+json",
|
||||||
|
"X-Website-Id": params.websiteId,
|
||||||
|
"X-Mimetype": file.type,
|
||||||
|
"X-Original-Filename": file.name
|
||||||
|
},
|
||||||
|
body: await file.arrayBuffer()
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileJSON = await fileData.json();
|
||||||
|
|
||||||
|
if (!fileData.ok) {
|
||||||
|
return { success: false, message: fileJSON.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fileId: fileJSON.file_id };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
||||||
import type { ActionData, PageServerData } from "./$types";
|
import type { ActionData, PageServerData } from "./$types";
|
||||||
import Modal from "$lib/components/Modal.svelte";
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
import { API_BASE_PREFIX } from "$lib/utils";
|
import { API_BASE_PREFIX, handleImagePaste } from "$lib/utils";
|
||||||
|
|
||||||
const { data, form } = $props<{ data: PageServerData; form: ActionData }>();
|
const { data, form } = $props<{ data: PageServerData; form: ActionData }>();
|
||||||
|
|
||||||
@@ -17,6 +17,11 @@
|
|||||||
const { scrollTop, scrollHeight, clientHeight } = mainContentTextarea;
|
const { scrollTop, scrollHeight, clientHeight } = mainContentTextarea;
|
||||||
textareaScrollTop = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
textareaScrollTop = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePaste = async (event: ClipboardEvent) => {
|
||||||
|
const newContent = await handleImagePaste(event);
|
||||||
|
previewContent = newContent;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SuccessOrError success={form?.success} message={form?.message} />
|
<SuccessOrError success={form?.success} message={form?.message} />
|
||||||
@@ -33,6 +38,7 @@
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
|
action="?/editArticle"
|
||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
use:enhance={() => {
|
use:enhance={() => {
|
||||||
return async ({ update }) => {
|
return async ({ update }) => {
|
||||||
@@ -94,6 +100,7 @@
|
|||||||
bind:value={previewContent}
|
bind:value={previewContent}
|
||||||
bind:this={mainContentTextarea}
|
bind:this={mainContentTextarea}
|
||||||
onscroll={updateScrollPercentage}
|
onscroll={updateScrollPercentage}
|
||||||
|
onpaste={handlePaste}
|
||||||
required>{data.article.main_content}</textarea
|
required>{data.article.main_content}</textarea
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user