관리자 글쓰기 전체 화면 레이아웃 보정
This commit is contained in:
@@ -480,70 +480,69 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="admin-post-form flex min-h-[calc(100vh-2.5rem)] flex-col bg-white" @submit.prevent="submitPost">
|
<form class="admin-post-form flex h-screen min-h-screen overflow-hidden bg-white" @submit.prevent="submitPost">
|
||||||
<header class="admin-post-form__toolbar flex h-[82px] shrink-0 items-start border-b border-transparent bg-white p-6">
|
<div class="admin-post-form__workspace flex min-w-0 flex-1 flex-col bg-white">
|
||||||
<div class="admin-post-form__toolbar-inner flex h-[34px] min-w-0 flex-1 items-center justify-between">
|
<header class="admin-post-form__toolbar flex h-[56px] shrink-0 items-center bg-white px-8">
|
||||||
<div class="admin-post-form__toolbar-left flex h-full items-center gap-2">
|
<div class="admin-post-form__toolbar-inner flex h-[34px] min-w-0 flex-1 items-center justify-between">
|
||||||
<NuxtLink class="admin-post-form__toolbar-link inline-flex items-center gap-2 rounded px-3 py-1.5 text-base text-black" to="/admin/posts">
|
<div class="admin-post-form__toolbar-left flex h-full min-w-0 items-center gap-3">
|
||||||
<span class="admin-post-form__toolbar-back text-lg leading-none" aria-hidden="true"><</span>
|
<NuxtLink class="admin-post-form__toolbar-link inline-flex items-center gap-2 rounded px-2 py-1.5 text-sm font-medium text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black" to="/admin/posts">
|
||||||
<span>Posts</span>
|
<span class="admin-post-form__toolbar-back text-lg leading-none" aria-hidden="true"><</span>
|
||||||
</NuxtLink>
|
<span>Posts</span>
|
||||||
<span class="admin-post-form__toolbar-status rounded px-3 py-1.5 text-base text-[#8e9cac]">
|
</NuxtLink>
|
||||||
{{ editorStatusLabel }}
|
<span class="admin-post-form__toolbar-status truncate rounded px-2 py-1.5 text-sm text-[#8e9cac]">
|
||||||
</span>
|
{{ editorStatusLabel }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="admin-post-form__toolbar-actions flex h-full shrink-0 items-center gap-2">
|
||||||
|
<button
|
||||||
|
class="admin-post-form__toolbar-preview rounded px-3 py-1.5 text-sm font-bold text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black"
|
||||||
|
type="button"
|
||||||
|
@click="previewPost"
|
||||||
|
>
|
||||||
|
미리보기
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="admin-post-form__toolbar-submit rounded px-3 py-1.5 text-sm font-bold text-[#2bba3c] transition-colors hover:bg-[#eaf8ec] hover:text-[#159624] disabled:opacity-50"
|
||||||
|
type="submit"
|
||||||
|
:disabled="saving"
|
||||||
|
>
|
||||||
|
{{ saving ? '저장 중' : submitLabel }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="admin-post-form__settings-toggle grid size-[34px] place-items-center rounded text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black"
|
||||||
|
type="button"
|
||||||
|
:aria-pressed="isSettingsOpen"
|
||||||
|
aria-label="게시물 설정 패널 전환"
|
||||||
|
@click="toggleSettingsPanel"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14 2.16699C14.3242 2.16699 14.4998 2.39365 14.5 2.57129V13.4287C14.5 13.606 14.3242 13.834 14 13.834H11.5V2.16699H14ZM2 2.16699H10.5V13.834H2C1.6756 13.834 1.5 13.6064 1.5 13.4287V2.57129C1.50024 2.39409 1.67607 2.16699 2 2.16699Z" stroke="currentColor" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-post-form__toolbar-actions flex h-full items-center gap-2">
|
</header>
|
||||||
<button
|
|
||||||
class="admin-post-form__toolbar-preview rounded px-3 py-1.5 text-base font-bold text-[#394047]"
|
|
||||||
type="button"
|
|
||||||
@click="previewPost"
|
|
||||||
>
|
|
||||||
미리보기
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="admin-post-form__toolbar-submit rounded px-3 py-1.5 text-base font-bold text-[#2bba3c] disabled:opacity-50"
|
|
||||||
type="submit"
|
|
||||||
:disabled="saving"
|
|
||||||
>
|
|
||||||
{{ saving ? '저장 중' : submitLabel }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="admin-post-form__settings-toggle grid size-[34px] place-items-center rounded text-[#394047] hover:bg-[#eff1f2]"
|
|
||||||
type="button"
|
|
||||||
:aria-pressed="isSettingsOpen"
|
|
||||||
aria-label="게시물 설정 패널 전환"
|
|
||||||
@click="toggleSettingsPanel"
|
|
||||||
>
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M14 2.16699C14.3242 2.16699 14.4998 2.39365 14.5 2.57129V13.4287C14.5 13.606 14.3242 13.834 14 13.834H11.5V2.16699H14ZM2 2.16699H10.5V13.834H2C1.6756 13.834 1.5 13.6064 1.5 13.4287V2.57129C1.50024 2.39409 1.67607 2.16699 2 2.16699Z" stroke="#394047"/>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<!-- <span class="admin-post-form__settings-toggle-icon block h-4 w-4 border-x-2 border-[#394047]" aria-hidden="true" /> -->
|
<main class="admin-post-form__editor-scroll min-h-0 flex-1 overflow-y-auto">
|
||||||
</button>
|
<section class="admin-post-form__content mx-auto w-full max-w-[804px] px-8 pb-16 pt-32">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="admin-post-form__main flex min-h-0 flex-1 flex-col bg-white lg:flex-row">
|
|
||||||
<section class="admin-post-form__content mx-auto w-full max-w-[804px] px-8 pb-16 pt-14">
|
|
||||||
<div class="admin-post-form__feature-block mb-9">
|
<div class="admin-post-form__feature-block mb-9">
|
||||||
<figure v-if="form.featuredImage" class="admin-post-form__featured-editor overflow-hidden rounded border border-line bg-white">
|
<figure v-if="form.featuredImage" class="admin-post-form__featured-editor overflow-hidden rounded border border-line bg-white">
|
||||||
<img class="admin-post-form__featured-editor-image aspect-[16/9] w-full bg-surface object-cover" :src="form.featuredImage" alt="">
|
<img class="admin-post-form__featured-editor-image aspect-[16/9] w-full bg-surface object-cover" :src="form.featuredImage" alt="">
|
||||||
<figcaption class="admin-post-form__featured-editor-actions flex flex-wrap items-center gap-2 border-t border-line p-3">
|
<figcaption class="admin-post-form__featured-editor-actions flex flex-wrap items-center gap-2 border-t border-line p-3">
|
||||||
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('featuredImage')">
|
<button class="admin-post-form__featured-change rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('featuredImage')">
|
||||||
변경
|
변경
|
||||||
</button>
|
</button>
|
||||||
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
|
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]">
|
||||||
새 업로드
|
새 업로드
|
||||||
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
|
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
|
||||||
</label>
|
</label>
|
||||||
<button class="admin-post-form__featured-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700" type="button" @click="removeFeaturedImage">
|
<button class="admin-post-form__featured-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700 transition-colors hover:bg-red-50" type="button" @click="removeFeaturedImage">
|
||||||
삭제
|
삭제
|
||||||
</button>
|
</button>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<div v-else class="admin-post-form__feature-empty flex h-6 items-start">
|
<div v-else class="admin-post-form__feature-empty flex h-6 items-start">
|
||||||
<button class="admin-post-form__feature-add inline-flex items-center gap-1.5 text-sm text-[#8e9cac]" type="button" @click="openMediaPicker('featuredImage')">
|
<button class="admin-post-form__feature-add inline-flex items-center gap-1.5 rounded px-2 py-1 text-sm text-[#8e9cac] transition-colors hover:bg-[#eff1f2] hover:text-[#394047]" type="button" @click="openMediaPicker('featuredImage')">
|
||||||
<span aria-hidden="true">+</span>
|
<span aria-hidden="true">+</span>
|
||||||
<span>대표 이미지 추가</span>
|
<span>대표 이미지 추가</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -567,10 +566,10 @@ defineExpose({
|
|||||||
{{ formatAutosaveTime(autosaveNotice.savedAt) }}에 저장된 작성 중 내용이 있습니다.
|
{{ formatAutosaveTime(autosaveNotice.savedAt) }}에 저장된 작성 중 내용이 있습니다.
|
||||||
</p>
|
</p>
|
||||||
<div class="admin-post-form__autosave-actions flex gap-2">
|
<div class="admin-post-form__autosave-actions flex gap-2">
|
||||||
<button class="admin-post-form__autosave-restore rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white" type="button" @click="restoreAutosave">
|
<button class="admin-post-form__autosave-restore rounded bg-[#15171a] px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-black" type="button" @click="restoreAutosave">
|
||||||
복원
|
복원
|
||||||
</button>
|
</button>
|
||||||
<button class="admin-post-form__autosave-discard rounded border border-line bg-white px-3 py-1.5 text-xs font-semibold" type="button" @click="discardAutosave">
|
<button class="admin-post-form__autosave-discard rounded border border-line bg-white px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-[#eff1f2]" type="button" @click="discardAutosave">
|
||||||
삭제
|
삭제
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -579,22 +578,29 @@ defineExpose({
|
|||||||
<div class="admin-post-form__field admin-post-form__content-editor text-sm">
|
<div class="admin-post-form__field admin-post-form__content-editor text-sm">
|
||||||
<AdminBlockEditor ref="blockEditor" v-model="form.content" />
|
<AdminBlockEditor ref="blockEditor" v-model="form.content" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
<aside v-if="isSettingsOpen" class="admin-post-form__settings flex h-auto w-full shrink-0 flex-col border-t border-[#e3e6e8] bg-white lg:w-[420px] lg:border-l lg:border-t-0">
|
<aside
|
||||||
<div class="admin-post-form__settings-header flex h-[82px] shrink-0 items-center justify-between px-6">
|
class="admin-post-form__settings flex h-screen shrink-0 flex-col overflow-hidden border-[#e3e6e8] bg-white transition-[width,border-color] duration-300 ease-out"
|
||||||
|
:class="isSettingsOpen ? 'w-[420px] border-l' : 'w-0 border-l-0'"
|
||||||
|
:aria-hidden="!isSettingsOpen"
|
||||||
|
>
|
||||||
|
<div class="admin-post-form__settings-inner flex h-full w-[420px] flex-col">
|
||||||
|
<div class="admin-post-form__settings-header flex h-[56px] shrink-0 items-center justify-between px-6">
|
||||||
<h2 class="admin-post-form__settings-title text-xl font-bold text-black">
|
<h2 class="admin-post-form__settings-title text-xl font-bold text-black">
|
||||||
게시물 설정
|
게시물 설정
|
||||||
</h2>
|
</h2>
|
||||||
<button class="admin-post-form__settings-close grid size-6 place-items-center text-[#394047]" type="button" aria-label="게시물 설정 닫기" @click="toggleSettingsPanel">
|
<button class="admin-post-form__settings-close grid size-8 place-items-center rounded text-[#394047] transition-colors hover:bg-[#eff1f2] hover:text-black" type="button" aria-label="게시물 설정 닫기" @click="toggleSettingsPanel">
|
||||||
<span aria-hidden="true">x</span>
|
<span aria-hidden="true">x</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-post-form__settings-body grid content-start gap-4 overflow-y-auto px-6 pb-8">
|
<div class="admin-post-form__settings-body grid content-start gap-4 overflow-y-auto px-6 pb-8 pt-8">
|
||||||
<label class="admin-post-form__field grid gap-2 text-sm">
|
<label class="admin-post-form__field grid gap-2 text-sm">
|
||||||
<span class="admin-post-form__label font-medium">상태</span>
|
<span class="admin-post-form__label font-medium">상태</span>
|
||||||
<select v-model="form.status" class="admin-post-form__select h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2">
|
<select v-model="form.status" class="admin-post-form__select h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none">
|
||||||
<option value="draft">초안</option>
|
<option value="draft">초안</option>
|
||||||
<option value="published">발행</option>
|
<option value="published">발행</option>
|
||||||
<option value="private">비공개</option>
|
<option value="private">비공개</option>
|
||||||
@@ -607,7 +613,7 @@ defineExpose({
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.publishedAt"
|
v-model="form.publishedAt"
|
||||||
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
>
|
>
|
||||||
<span class="admin-post-form__hint text-xs text-muted">
|
<span class="admin-post-form__hint text-xs text-muted">
|
||||||
@@ -619,7 +625,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">슬러그</span>
|
<span class="admin-post-form__label font-medium">슬러그</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.slug"
|
v-model="form.slug"
|
||||||
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
type="text"
|
type="text"
|
||||||
pattern="[a-z0-9가-힣]+(-[a-z0-9가-힣]+)*"
|
pattern="[a-z0-9가-힣]+(-[a-z0-9가-힣]+)*"
|
||||||
required
|
required
|
||||||
@@ -631,7 +637,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">요약</span>
|
<span class="admin-post-form__label font-medium">요약</span>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="form.excerpt"
|
v-model="form.excerpt"
|
||||||
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -639,7 +645,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">태그</span>
|
<span class="admin-post-form__label font-medium">태그</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.tagsText"
|
v-model="form.tagsText"
|
||||||
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
@@ -658,7 +664,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">SEO 제목</span>
|
<span class="admin-post-form__label font-medium">SEO 제목</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.seoTitle"
|
v-model="form.seoTitle"
|
||||||
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="80"
|
maxlength="80"
|
||||||
placeholder="비워두면 글 제목을 사용"
|
placeholder="비워두면 글 제목을 사용"
|
||||||
@@ -672,7 +678,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">SEO 설명</span>
|
<span class="admin-post-form__label font-medium">SEO 설명</span>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="form.seoDescription"
|
v-model="form.seoDescription"
|
||||||
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
maxlength="180"
|
maxlength="180"
|
||||||
placeholder="비워두면 요약을 사용"
|
placeholder="비워두면 요약을 사용"
|
||||||
/>
|
/>
|
||||||
@@ -685,7 +691,7 @@ defineExpose({
|
|||||||
<span class="admin-post-form__label font-medium">Canonical URL</span>
|
<span class="admin-post-form__label font-medium">Canonical URL</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.canonicalUrl"
|
v-model="form.canonicalUrl"
|
||||||
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2 transition-colors hover:border-[#c8ced3] focus:border-[#8e9cac] focus:outline-none"
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="비워두면 기본 글 주소를 사용"
|
placeholder="비워두면 기본 글 주소를 사용"
|
||||||
>
|
>
|
||||||
@@ -713,32 +719,32 @@ defineExpose({
|
|||||||
{{ form.ogImage }}
|
{{ form.ogImage }}
|
||||||
</p>
|
</p>
|
||||||
<div class="admin-post-form__og-buttons flex flex-wrap gap-2">
|
<div class="admin-post-form__og-buttons flex flex-wrap gap-2">
|
||||||
<button class="admin-post-form__og-change rounded border border-line px-3 py-1.5 text-xs font-semibold" type="button" @click="openMediaPicker('ogImage')">
|
<button class="admin-post-form__og-change rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('ogImage')">
|
||||||
변경
|
변경
|
||||||
</button>
|
</button>
|
||||||
<label class="admin-post-form__og-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
|
<label class="admin-post-form__og-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]">
|
||||||
새 업로드
|
새 업로드
|
||||||
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
|
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
|
||||||
</label>
|
</label>
|
||||||
<button class="admin-post-form__og-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700" type="button" @click="removeOgImage">
|
<button class="admin-post-form__og-remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700 transition-colors hover:bg-red-50" type="button" @click="removeOgImage">
|
||||||
삭제
|
삭제
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<div v-else class="admin-post-form__og-empty grid gap-2 rounded border border-dashed border-line bg-white p-4">
|
<div v-else class="admin-post-form__og-empty grid gap-2 rounded border border-dashed border-line bg-white p-4">
|
||||||
<button class="admin-post-form__og-select rounded border border-line px-3 py-2 text-sm font-semibold" type="button" @click="openMediaPicker('ogImage')">
|
<button class="admin-post-form__og-select rounded border border-line px-3 py-2 text-sm font-semibold transition-colors hover:border-[#c8ced3] hover:bg-[#eff1f2]" type="button" @click="openMediaPicker('ogImage')">
|
||||||
미디어에서 선택
|
미디어에서 선택
|
||||||
</button>
|
</button>
|
||||||
<label class="admin-post-form__og-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white">
|
<label class="admin-post-form__og-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white transition-colors hover:bg-black">
|
||||||
{{ isUploadingOgImage ? '업로드 중' : '새 이미지 업로드' }}
|
{{ isUploadingOgImage ? '업로드 중' : '새 이미지 업로드' }}
|
||||||
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
|
<input class="sr-only" type="file" accept="image/*" @change="uploadOgImage">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isMediaPickerOpen"
|
v-if="isMediaPickerOpen"
|
||||||
@@ -752,7 +758,7 @@ defineExpose({
|
|||||||
<h2 class="admin-post-form__media-picker-title text-lg font-semibold">
|
<h2 class="admin-post-form__media-picker-title text-lg font-semibold">
|
||||||
{{ mediaPickerTarget === 'ogImage' ? 'OG 이미지 선택' : '대표 이미지 선택' }}
|
{{ mediaPickerTarget === 'ogImage' ? 'OG 이미지 선택' : '대표 이미지 선택' }}
|
||||||
</h2>
|
</h2>
|
||||||
<button class="admin-post-form__media-picker-close rounded border border-line px-3 py-1.5 text-sm font-semibold" type="button" @click="closeMediaPicker">
|
<button class="admin-post-form__media-picker-close rounded border border-line px-3 py-1.5 text-sm font-semibold transition-colors hover:bg-[#eff1f2]" type="button" @click="closeMediaPicker">
|
||||||
닫기
|
닫기
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -764,7 +770,7 @@ defineExpose({
|
|||||||
<button
|
<button
|
||||||
v-for="item in mediaItems"
|
v-for="item in mediaItems"
|
||||||
:key="item.url"
|
:key="item.url"
|
||||||
class="admin-post-form__media-picker-item overflow-hidden border border-line bg-white text-left"
|
class="admin-post-form__media-picker-item overflow-hidden border border-line bg-white text-left transition hover:border-[#8e9cac] hover:shadow-sm"
|
||||||
type="button"
|
type="button"
|
||||||
@click="selectPickedImage(item)"
|
@click="selectPickedImage(item)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-05-07 v0.0.35
|
||||||
|
|
||||||
|
### 관리자 글쓰기 전체 화면 모드 보정 결정
|
||||||
|
|
||||||
|
관리자 글 작성/수정 화면에서는 좌측 관리자 네비게이션과 공통 내부 패딩을 숨기고, 글쓰기 폼이 브라우저 높이를 직접 사용하는 전체 화면 편집 모드로 동작하게 한다. Ghost와 WordPress류 편집 화면은 작성 중 관리자 메뉴보다 글 본문과 설정 패널의 관계가 더 중요하므로, 네비게이션이 보이면 작성 영역을 불필요하게 압축하고 시선을 분산시키기 때문이다.
|
||||||
|
|
||||||
|
글쓰기 화면의 1차 레이아웃은 상단 헤더 전체 폭이 아니라 에디터 작업 영역과 우측 설정 패널의 좌우 분할로 둔다. 설정 패널이 열리거나 닫힐 때 에디터 작업 영역의 상단 도구막대와 본문 폭이 함께 변해야 사용자가 현재 편집 가능한 폭 변화를 자연스럽게 인식할 수 있다.
|
||||||
|
|
||||||
## 2026-05-07 v0.0.32
|
## 2026-05-07 v0.0.32
|
||||||
|
|
||||||
### 관리자 글 작성 화면 구조 정리 결정
|
### 관리자 글 작성 화면 구조 정리 결정
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|------|------|
|
|------|------|
|
||||||
| layouts/default.vue | 메인, 목록, 태그 페이지 |
|
| layouts/default.vue | 메인, 목록, 태그 페이지 |
|
||||||
| layouts/post.vue | 개별 게시물 |
|
| layouts/post.vue | 개별 게시물 |
|
||||||
| layouts/admin.vue | 관리자 전체 |
|
| layouts/admin.vue | 관리자 전체, 글 작성/수정 화면의 전체 화면 편집 모드 |
|
||||||
| layouts/page.vue | 고정 페이지 전체 화면 |
|
| layouts/page.vue | 고정 페이지 전체 화면 |
|
||||||
|
|
||||||
## 사이트 컴포넌트
|
## 사이트 컴포넌트
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
| 파일 | 화면 위치 |
|
| 파일 | 화면 위치 |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost 스타일 상단 바, 중앙 에디터, 우측 설정 패널, 대표 이미지/OG 이미지 선택, 로컬 자동 저장, 예약 발행 시각 입력, SEO 설정, 미리보기 요청 |
|
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost 스타일 전체 화면 에디터, 좌우 분할 설정 패널, 설정 패널 전환 애니메이션, 대표 이미지/OG 이미지 선택, 로컬 자동 저장, 예약 발행 시각 입력, SEO 설정, 미리보기 요청 |
|
||||||
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 대표 이미지 선택 |
|
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 대표 이미지 선택 |
|
||||||
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 한글 조합 입력 처리, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 한글 조합 입력 처리, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
||||||
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼 |
|
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼 |
|
||||||
|
|||||||
@@ -288,8 +288,10 @@ components/content/
|
|||||||
- 에디터 마지막에는 클릭 가능한 빈 문단 블록을 항상 유지하며, 해당 블록이 비어 있으면 저장 콘텐츠에는 포함하지 않는다.
|
- 에디터 마지막에는 클릭 가능한 빈 문단 블록을 항상 유지하며, 해당 블록이 비어 있으면 저장 콘텐츠에는 포함하지 않는다.
|
||||||
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
|
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
|
||||||
- 글 작성/수정 화면은 페이지 제목 헤더를 별도로 두지 않고, 폼 상단의 Ghost 스타일 도구막대에서 목록 이동, 상태, 미리보기, 저장, 설정 패널 토글을 제공한다.
|
- 글 작성/수정 화면은 페이지 제목 헤더를 별도로 두지 않고, 폼 상단의 Ghost 스타일 도구막대에서 목록 이동, 상태, 미리보기, 저장, 설정 패널 토글을 제공한다.
|
||||||
|
- 글 작성/수정 화면은 관리자 사이드바 네비게이션을 숨기고, 관리자 공통 내부 패딩 없이 전체 화면 편집 모드로 표시한다.
|
||||||
|
- 글 작성/수정 폼은 에디터 작업 영역과 우측 설정 패널을 먼저 좌우로 나누고, 에디터 작업 영역 안에 상단 도구막대와 본문 스크롤 영역을 배치한다.
|
||||||
- 본문 에디터는 데스크톱에서 좌우 32px 패딩을 포함한 804px 중앙 컬럼으로 배치해 실제 입력 영역 최대 폭을 740px로 유지한다.
|
- 본문 에디터는 데스크톱에서 좌우 32px 패딩을 포함한 804px 중앙 컬럼으로 배치해 실제 입력 영역 최대 폭을 740px로 유지한다.
|
||||||
- 게시물 설정은 데스크톱에서 420px 우측 패널로 표시하며, 도구막대 버튼으로 열고 닫을 수 있다.
|
- 게시물 설정은 데스크톱에서 420px 우측 패널로 표시하며, 도구막대 버튼으로 열고 닫을 수 있고 너비 전환 애니메이션을 적용한다.
|
||||||
- 대표 이미지는 본문 제목 위의 에디터 흐름 안에서 추가, 변경, 삭제한다.
|
- 대표 이미지는 본문 제목 위의 에디터 흐름 안에서 추가, 변경, 삭제한다.
|
||||||
- 제목 입력에서 Enter를 누르면 본문 첫 블록으로 포커스를 이동한다.
|
- 제목 입력에서 Enter를 누르면 본문 첫 블록으로 포커스를 이동한다.
|
||||||
- 새 글 작성처럼 본문이 비어 있는 경우에도 빈 문단 블록을 먼저 생성한다.
|
- 새 글 작성처럼 본문이 비어 있는 경우에도 빈 문단 블록을 먼저 생성한다.
|
||||||
@@ -437,6 +439,6 @@ APP_PORT=43118
|
|||||||
|
|
||||||
## 버전 관리
|
## 버전 관리
|
||||||
|
|
||||||
- 현재 버전: v0.0.34
|
- 현재 버전: v0.0.35
|
||||||
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
|
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
|
||||||
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정
|
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v0.0.35
|
||||||
|
|
||||||
|
- 관리자 공통 레이아웃의 기본 내부 패딩 제거.
|
||||||
|
- 관리자 글 작성/수정 화면에서 좌측 관리자 네비게이션을 숨기도록 수정.
|
||||||
|
- 관리자 글 작성/수정 폼을 에디터 작업 영역과 우측 설정 패널의 1차 좌우 분할 구조로 수정.
|
||||||
|
- 게시물 설정 패널 열림/닫힘 너비 전환 애니메이션 추가.
|
||||||
|
- 관리자 글 작성/수정 화면의 버튼, 입력, 미디어 선택 요소 hover/focus 효과 보강.
|
||||||
|
- 기술 명세 현재 버전을 v0.0.35로 갱신.
|
||||||
|
- 패키지 버전을 0.0.35로 갱신.
|
||||||
|
|
||||||
## v0.0.34
|
## v0.0.34
|
||||||
|
|
||||||
- 배포 문서에 개발/운영 DB 분리 검증 절차 추가.
|
- 배포 문서에 개발/운영 DB 분리 검증 절차 추가.
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isPostEditorRoute = computed(() => route.path === '/admin/posts/new'
|
||||||
|
|| (route.path.startsWith('/admin/posts/') && route.path !== '/admin/posts/preview'))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 관리자 로그아웃
|
* 관리자 로그아웃
|
||||||
* @returns {Promise<void>} 로그아웃 처리 결과
|
* @returns {Promise<void>} 로그아웃 처리 결과
|
||||||
@@ -13,35 +18,41 @@ const logoutAdmin = async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="admin-layout min-h-screen bg-[#f5f5f2] text-ink">
|
<div class="admin-layout min-h-screen bg-[#f5f5f2] text-ink">
|
||||||
<aside class="admin-layout__sidebar fixed inset-y-0 left-0 hidden w-64 border-r border-line bg-[#15171a] p-5 text-white lg:block">
|
<aside
|
||||||
|
v-if="!isPostEditorRoute"
|
||||||
|
class="admin-layout__sidebar fixed inset-y-0 left-0 hidden w-64 border-r border-line bg-[#15171a] p-5 text-white lg:block"
|
||||||
|
>
|
||||||
<NuxtLink class="admin-layout__brand block text-lg font-semibold" to="/admin">
|
<NuxtLink class="admin-layout__brand block text-lg font-semibold" to="/admin">
|
||||||
sori.studio
|
sori.studio
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<nav class="admin-layout__nav mt-8 grid gap-2 text-sm text-white/75">
|
<nav class="admin-layout__nav mt-8 grid gap-2 text-sm text-white/75">
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/posts">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/posts">
|
||||||
글
|
글
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/pages">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/pages">
|
||||||
페이지
|
페이지
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/tags">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/tags">
|
||||||
태그
|
태그
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/media">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/media">
|
||||||
미디어
|
미디어
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/navigation">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/navigation">
|
||||||
메뉴
|
메뉴
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 hover:bg-white/10 hover:text-white" to="/admin/settings">
|
<NuxtLink class="admin-layout__nav-link rounded px-3 py-2 transition-colors hover:bg-white/10 hover:text-white" to="/admin/settings">
|
||||||
설정
|
설정
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<button class="admin-layout__logout rounded px-3 py-2 text-left hover:bg-white/10 hover:text-white" type="button" @click="logoutAdmin">
|
<button class="admin-layout__logout rounded px-3 py-2 text-left transition-colors hover:bg-white/10 hover:text-white" type="button" @click="logoutAdmin">
|
||||||
로그아웃
|
로그아웃
|
||||||
</button>
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="admin-layout__main min-h-screen p-5 lg:ml-64">
|
<main
|
||||||
|
class="admin-layout__main min-h-screen"
|
||||||
|
:class="{ 'lg:ml-64': !isPostEditorRoute }"
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "0.0.34",
|
"version": "0.0.35",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "0.0.34",
|
"version": "0.0.35",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "0.0.34",
|
"version": "0.0.35",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user