diff --git a/frontend/src/components/MdEditor.vue b/frontend/src/components/MdEditor.vue index 1abd502..2d13382 100644 --- a/frontend/src/components/MdEditor.vue +++ b/frontend/src/components/MdEditor.vue @@ -1,22 +1,28 @@ @@ -138,6 +209,11 @@ .md-toggle { margin-left: auto; } +.md-uploading { + font-size: 0.85em; + color: var(--text-muted, #888); + align-self: center; +} .md-editor :deep(textarea.input) { height: auto; min-height: 9rem; @@ -147,4 +223,31 @@ .md-preview { min-height: 9rem; } +/* миниатюры вставленных картинок */ +.md-pasted { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 0.5rem; +} +.md-thumb { + padding: 0; + border: 1px solid var(--border, #2a2e42); + background: none; + cursor: zoom-in; + line-height: 0; +} +.md-thumb img { + width: 72px; + height: 54px; + object-fit: cover; + display: block; +} +.md-thumb:hover { + border-color: var(--accent, #7aa2f7); +} +/* кликабельные картинки в предпросмотре */ +.md-preview :deep(img) { + cursor: zoom-in; +} \ No newline at end of file diff --git a/frontend/src/components/MdLightbox.vue b/frontend/src/components/MdLightbox.vue new file mode 100644 index 0000000..0340c10 --- /dev/null +++ b/frontend/src/components/MdLightbox.vue @@ -0,0 +1,67 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/TaskForm.vue b/frontend/src/components/TaskForm.vue index 4234543..54b0cf9 100644 --- a/frontend/src/components/TaskForm.vue +++ b/frontend/src/components/TaskForm.vue @@ -209,20 +209,19 @@ } } -// Вставка изображений из буфера обмена — только для существующей задачи -async function onPaste(event: ClipboardEvent) { - if (!props.task) return - const files = Array.from(event.clipboardData?.files ?? []).filter((f) => - f.type.startsWith('image/'), - ) - if (!files.length) return - event.preventDefault() +// Вставка изображений из буфера — только для существующей задачи. +// MdEditor сам вставляет markdown-ссылку в позицию курсора и показывает +// миниатюру; здесь только загрузка на сервер. +async function uploadImages(files: File[]): Promise { + if (!props.task) return [] uploading.value = true try { const saved = await api.uploadAttachments(props.task.id, files) attachments.value.push(...saved) + return saved } catch (e) { toast.error({ title: t('common.error'), text: String(e) }) + return [] } finally { uploading.value = false } @@ -258,7 +257,7 @@ ]" /> - + (project.value ? renderMarkdown(project.value.note) : '')) +// Клик по картинке в заметке → просмотр на весь экран +const noteImage = ref<{ url: string; name: string } | null>(null) + +function onNoteClick(e: MouseEvent) { + const target = e.target as HTMLElement + if (target.tagName === 'IMG') { + const img = target as HTMLImageElement + noteImage.value = { url: img.getAttribute('src') ?? '', name: img.alt } + } +} + async function load() { loading.value = true notFound.value = false @@ -384,13 +396,24 @@ + (null) + +function onDescClick(e: MouseEvent) { + const target = e.target as HTMLElement + if (target.tagName === 'IMG') { + const img = target as HTMLImageElement + descImage.value = { url: img.getAttribute('src') ?? '', name: img.alt } + } +} + async function savePriority() { await patchTask({ priority: draftPriority.value === '' ? null : gradeToPriority(draftPriority.value), @@ -574,7 +600,7 @@ />
- +
+