에디터 보조 UI 컴포넌트 분리

This commit is contained in:
2026-07-27 10:04:37 +09:00
parent f4a2f1c034
commit 636d4f68a4
8 changed files with 130 additions and 94 deletions

View File

@@ -63,7 +63,9 @@ import {
normalizeUploadMaxFileCount,
normalizeUploadMaxTotalSize
} from '../../lib/upload-size-limit.js'
import AdminMarkdownModeToggle from './AdminMarkdownModeToggle.vue'
import AdminMarkdownSlashMenu from './AdminMarkdownSlashMenu.vue'
import AdminMarkdownUploadingBadge from './AdminMarkdownUploadingBadge.vue'
const { toast, showToast } = useAdminToast()
const runtimeConfig = useRuntimeConfig()
@@ -3485,50 +3487,11 @@ const handleKeydown = (event) => {
<template>
<div ref="editorRootRef" class="admin-markdown-editor grid gap-3">
<Teleport v-if="modeToggleTeleportTo" :to="modeToggleTeleportTo">
<button
class="admin-markdown-editor__mode-toggle inline-flex size-8 shrink-0 items-center justify-center rounded text-[#394047] transition-colors hover:bg-[#f1f3f4] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8e9cac]"
type="button"
:aria-label="activeMode === 'write' ? '미리보기로 전환' : '작성 모드로 전환'"
@click="toggleEditorMode"
>
<svg
v-if="activeMode === 'write'"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="admin-markdown-editor__mode-icon lucide-book-open"
aria-hidden="true"
>
<path d="M12 7v14" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="admin-markdown-editor__mode-icon lucide-edit-3"
aria-hidden="true"
>
<path d="M13 21h8" />
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
</svg>
</button>
</Teleport>
<AdminMarkdownModeToggle
:active-mode="activeMode"
:teleport-to="modeToggleTeleportTo"
@toggle="toggleEditorMode"
/>
<div v-if="activeMode === 'write'" class="admin-markdown-editor__write relative">
<div class="admin-markdown-editor__editor-surface min-h-[620px]">
@@ -3568,9 +3531,7 @@ const handleKeydown = (event) => {
/>
</div>
</div>
<div v-if="isUploading" class="admin-markdown-editor__uploading absolute right-3 top-3 rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white">
업로드 중
</div>
<AdminMarkdownUploadingBadge v-if="isUploading" />
</div>
<div
@@ -3662,48 +3623,4 @@ const handleKeydown = (event) => {
display: none;
}
.admin-markdown-editor__media-upload-zone--uploading::before {
position: absolute;
inset: 0;
content: '';
background: linear-gradient(110deg, transparent 0%, rgba(255, 255, 255, 0.72) 45%, transparent 72%);
transform: translateX(-100%);
animation: admin-markdown-editor-upload-sheen 1.4s ease-in-out infinite;
}
.admin-markdown-editor__media-upload-spinner {
animation: admin-markdown-editor-upload-spin 0.8s linear infinite;
}
.admin-markdown-editor__media-upload-skeleton::before {
display: block;
width: 40%;
height: 100%;
content: '';
border-radius: inherit;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.86), transparent);
animation: admin-markdown-editor-upload-skeleton 1.15s ease-in-out infinite;
}
@keyframes admin-markdown-editor-upload-spin {
to {
transform: rotate(360deg);
}
}
@keyframes admin-markdown-editor-upload-sheen {
to {
transform: translateX(100%);
}
}
@keyframes admin-markdown-editor-upload-skeleton {
from {
transform: translateX(-120%);
}
to {
transform: translateX(260%);
}
}
</style>

View File

@@ -313,3 +313,50 @@ const handleFileChange = (event) => {
</div>
</div>
</template>
<style scoped>
.admin-markdown-media-picker__upload-zone--uploading::before {
position: absolute;
inset: 0;
content: '';
background: linear-gradient(110deg, transparent 0%, rgba(255, 255, 255, 0.72) 45%, transparent 72%);
transform: translateX(-100%);
animation: admin-markdown-media-picker-upload-sheen 1.4s ease-in-out infinite;
}
.admin-markdown-media-picker__upload-spinner {
animation: admin-markdown-media-picker-upload-spin 0.8s linear infinite;
}
.admin-markdown-media-picker__upload-skeleton::before {
display: block;
width: 40%;
height: 100%;
content: '';
border-radius: inherit;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.86), transparent);
animation: admin-markdown-media-picker-upload-skeleton 1.15s ease-in-out infinite;
}
@keyframes admin-markdown-media-picker-upload-spin {
to {
transform: rotate(360deg);
}
}
@keyframes admin-markdown-media-picker-upload-sheen {
to {
transform: translateX(100%);
}
}
@keyframes admin-markdown-media-picker-upload-skeleton {
from {
transform: translateX(-120%);
}
to {
transform: translateX(260%);
}
}
</style>

View File

@@ -0,0 +1,60 @@
<script setup>
defineProps({
activeMode: {
type: String,
required: true
},
teleportTo: {
type: String,
default: ''
}
})
const emit = defineEmits(['toggle'])
</script>
<template>
<Teleport v-if="teleportTo" :to="teleportTo">
<button
class="admin-markdown-editor__mode-toggle inline-flex size-8 shrink-0 items-center justify-center rounded text-[#394047] transition-colors hover:bg-[#f1f3f4] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#8e9cac]"
type="button"
:aria-label="activeMode === 'write' ? '미리보기로 전환' : '작성 모드로 전환'"
@click="emit('toggle')"
>
<svg
v-if="activeMode === 'write'"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="admin-markdown-editor__mode-icon lucide-book-open"
aria-hidden="true"
>
<path d="M12 7v14" />
<path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z" />
</svg>
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="admin-markdown-editor__mode-icon lucide-edit-3"
aria-hidden="true"
>
<path d="M13 21h8" />
<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" />
</svg>
</button>
</Teleport>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div class="admin-markdown-editor__uploading absolute right-3 top-3 rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white">
업로드 중
</div>
</template>

View File

@@ -219,7 +219,9 @@
| components/admin/AdminMediaVideoThumbnail.vue | 관리자 미디어 목록 비디오 항목의 초반 프레임 캔버스 썸네일 |
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost형 툴바(왼쪽 상태 텍스트·Publish/Update/Unpublish/Unschedule, 서버 반영 상태 기준 분기), 초안만 서버 디바운스 자동 저장·자동 저장 실패 payload 반복 재시도 차단·자동 저장 실패/진행 중 이탈 차단·신규 임시 슬러그·중복 슬러그 필드 하단 오류 표시·중복 슬러그 중 Publish/Update/최종 저장 비활성화·저장 중 추가 변경 재자동저장·발행 저장 중 이탈 확인·발행·예약·멤버십·비공개 상태 저장, 미저장 비교에서 태그 표시명 보조값 제외, 공개 상태 전환 시 빈 발행 시각 현재 시각 보정, 발행 모달(중앙 배치), 설정 패널 상태와 이벤트 연결, 미리보기 emit·미저장 이탈 가드, 자동 생성 썸네일을 숨기는 원본 전용 대표 이미지 선택(서버 페이지네이션·파일명/사용처 검색·번호형 페이지 이동·작은 파생 이미지 우선 미리보기·고정 높이 모달), 대표 이미지 본문 상단 표시 토글, 추천 글 토글, 사이트 설정 기준 태그 최대 개수 제한, 태그 색상 배지 다중 입력·최근 사용 태그 5개 우선 추천·메인 태그 드롭다운·부분 검색 추천, 새 태그 입력명 유지와 `/`→`-` 슬러그 변환 |
| components/admin/AdminMarkdownMediaPickerModal.vue | 관리자 Markdown 에디터 미디어 선택 모달 UI(업로드·라이브러리 탭, 검색 입력, 미디어 그리드, 업로드 영역, 페이지네이션, 적용/취소 버튼) |
| components/admin/AdminMarkdownModeToggle.vue | 관리자 Markdown 에디터 작성/미리보기 모드 전환 버튼 UI |
| components/admin/AdminMarkdownSlashMenu.vue | 관리자 Markdown 에디터 슬래시 명령 메뉴 UI(명령 아이콘·라벨·슬러그 표시, 키보드 하이라이트 상태 표시, 루트 DOM 노출) |
| components/admin/AdminMarkdownUploadingBadge.vue | 관리자 Markdown 에디터 소스 작성 표면의 업로드 중 상태 배지 |
| components/admin/AdminPostSettingsSidebar.vue | 관리자 게시물 작성/수정 우측 설정 사이드바(Post URL·상태·발행 시각·요약·태그·대표 이미지 표시·추천 글·검색엔진 노출·본문 통계·삭제 버튼·블록 설정 패널 오버레이) |
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 게시글 작성과 같은 전체 화면 에디터·상단 저장 툴바·접이식 오른쪽 설정 패널, 저장 payload 기준 미저장 비교·라우트/브라우저 이탈 확인, 페이지 공개 상태 선택, HTML 문서 기본 모드, 홈 Pages 진열장 노출 메타와 등록된 페이지 섹션 검색형 선택, 빈 본문/`!`+Tab HTML 골격 자동 완성, 항상 보이는 일반 텍스트/HTML 모드 선택, 한글 제목 영문 슬러그 자동 변환, HTML textarea 커서 위치 파일 URL 삽입, HTML 태그 내부 스마트 따옴표 정규화 |
| components/admin/AdminMarkdownEditor.vue | 관리자 글 Markdown-first 에디터, 라이브·소스 모드 `/` 슬래시 명령(`/표` 기본 표 템플릿 포함)·미디어 모달(이미지·갤러리·비디오·오디오·파일, 업로드 탭 기본·첫 순서, 업로드 완료 파일 자동 선택, 라이브러리 탭은 `/admin/api/media?paged=1` 서버 페이지 응답·파일명/사용처 제목 검색·번호형 페이지 이동, 카드·본문·라이트박스·Pages 커버 자동 생성 이미지 숨김, 원본 URL 선택 유지·갤러리 선택 순서 배지·목록 미리보기는 720px 본문 파생 이미지 또는 카드 썸네일 우선, 검색 결과 수와 무관한 고정 높이 패널, 업로드 중 중복 업로드·닫기 차단·로딩 표시), 코드 블록 본문 슬래시 명령 비활성화, 커서 블록 컨텍스트·선택 텍스트 색상 컨텍스트 `block-panel` emit, 소스 모드 입력 직후 textarea 최신 본문 기준 커서 논리 줄·블록 컨텍스트 갱신, 에디터 본문 `Cmd/Ctrl+Z` 히스토리와 선택 위치 복원, 라이브 이미지 설정 패널·이미지↔갤러리 드래그 변환(`merge-images-to-gallery`·`insert-image-to-gallery`·`extract-gallery-image`), 소스·라이브 `Cmd+Shift+K` 줄 삭제, 소스·라이브 `Cmd/Ctrl+K` 링크 삽입, 코드·콜아웃·토글 내부 줄 삭제, 라이브 fenced 블록 현재 닫는 줄 기준 교체, 블록 패널 바깥 클릭·지원 블록 밖 커서 이동 시 닫기·미디어 모달 중 유지, 인용 마지막 줄 아래 방향키 외부 문단 포커스 이동, 인용·콜아웃·코드·토글 선언 줄 옵션 수정, IME 조합 중 현재 블록 범위 안에서만 블록 패널 유지, 소스 모드 wrap 라인 번호 보정·라이브↔소스 위치 복원(코드·콜아웃·토글 선언 줄은 본문 줄로 보정), 라이브 슬래시 명령 후 포커스 복원 지연·미디어 명령 원문 줄 재반영 방지 |

View File

@@ -1,5 +1,10 @@
# 업데이트 이력
## v1.11.80
- 관리자 Markdown 에디터의 작성/미리보기 모드 전환 버튼과 업로드 상태 배지를 하위 컴포넌트로 분리.
- 미디어 선택 모달 업로드 애니메이션 스타일을 실제 모달 컴포넌트로 이동해 scoped style 연결 정리.
## v1.11.79
- 관리자 Markdown 에디터의 슬래시 명령 메뉴 UI를 `AdminMarkdownSlashMenu` 컴포넌트로 분리.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sori.studio",
"version": "1.11.79",
"version": "1.11.80",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sori.studio",
"version": "1.11.79",
"version": "1.11.80",
"hasInstallScript": true,
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",

View File

@@ -1,6 +1,6 @@
{
"name": "sori.studio",
"version": "1.11.79",
"version": "1.11.80",
"private": true,
"type": "module",
"imports": {