feat: Markdown编辑器支持本地图片上传功能
continuous-integration/drone/push Build is passing

This commit is contained in:
seahi committed 2026-09-11 17:58:40 +08:00
1 parent 1a4466d85d
commit 2f41bda41e
1 file changed
+73 -1
+73 -1
View File
@@ -1,10 +1,12 @@
<script lang="ts">
/* eslint-disable @typescript-eslint/no-explicit-any */
import { onMount, onDestroy } from 'svelte';
import { EditorView, basicSetup } from 'codemirror';
import { EditorState } from '@codemirror/state';
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import { placeholder as cmPlaceholder } from '@codemirror/view';
import { addToast } from '$lib/stores/toast.svelte';
let { value = $bindable(''), placeholderText = '支持 Markdown 格式...' } = $props();
@@ -12,6 +14,40 @@
let view: EditorView;
let isUpdating = false;
let showImageMenu = $state(false);
let fileInput: HTMLInputElement;
let isUploadingImage = $state(false);
async function handleImageUpload(e: Event) {
const input = e.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
isUploadingImage = true;
try {
const res = await fetch('/admin/api/upload', {
method: 'POST',
body: formData
});
const data = (await res.json()) as any;
if (data.success) {
insertAtCursor('![', `](${data.url})`, file.name || '图片');
addToast('图片上传成功', 'success');
} else {
addToast(data.error || '上传失败', 'error');
}
} catch (err: any) {
console.error(err);
addToast('网络错误,上传失败', 'error');
} finally {
isUploadingImage = false;
input.value = '';
showImageMenu = false;
}
}
const customTheme = EditorView.theme({
'&': {
color: 'var(--text-color)',
@@ -228,10 +264,16 @@
></path></svg
>
</button>
<div class="toolbar-dropdown-wrapper">
{#if showImageMenu}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_interactive_supports_focus -->
<div class="dropdown-overlay" onclick={() => (showImageMenu = false)} role="button"></div>
{/if}
<button
type="button"
class="md-btn"
onclick={() => insertAtCursor('![', '](https://)', '图片描述')}
onclick={() => (showImageMenu = !showImageMenu)}
title="图片"
>
<svg viewBox="0 0 24 24"
@@ -241,6 +283,36 @@
></polygon></svg
>
</button>
{#if showImageMenu}
<div class="toolbar-dropdown">
<button
type="button"
class="dropdown-item"
onclick={() => {
insertAtCursor('![', '](https://)', '图片描述');
showImageMenu = false;
}}
>
插入图片链接
</button>
<button
type="button"
class="dropdown-item"
onclick={() => fileInput.click()}
disabled={isUploadingImage}
>
{isUploadingImage ? '正在上传...' : '上传本地图片'}
</button>
</div>
{/if}
</div>
<input
type="file"
accept="image/*"
style="display:none;"
bind:this={fileInput}
onchange={handleImageUpload}
/>
<button
type="button"
class="md-btn"