관리자 글쓰기 화면과 개발 환경 문서 정리
This commit is contained in:
@@ -1247,7 +1247,7 @@ onBeforeUnmount(() => {
|
|||||||
:class="getBlockClass(block)"
|
:class="getBlockClass(block)"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
spellcheck="true"
|
spellcheck="true"
|
||||||
:data-placeholder="index === 0 ? '본문을 입력하거나 / 를 눌러 블록을 선택하세요' : '/ 를 눌러 블록 선택'"
|
:data-placeholder="index === 0 ? '본문을 입력하세요...' : '/ 를 눌러 블록 선택'"
|
||||||
:data-show-placeholder="shouldShowPlaceholder(block, index)"
|
:data-show-placeholder="shouldShowPlaceholder(block, index)"
|
||||||
@focus="activateBlock(block)"
|
@focus="activateBlock(block)"
|
||||||
@input="updateBlockText($event, index)"
|
@input="updateBlockText($event, index)"
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const autosaveTimer = ref(null)
|
|||||||
const autosaveNotice = ref(null)
|
const autosaveNotice = ref(null)
|
||||||
const autosaveStatus = ref('')
|
const autosaveStatus = ref('')
|
||||||
const isRestoringAutosave = ref(false)
|
const isRestoringAutosave = ref(false)
|
||||||
|
const isSettingsOpen = ref(true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ISO 날짜를 datetime-local 입력값으로 변환
|
* ISO 날짜를 datetime-local 입력값으로 변환
|
||||||
@@ -136,6 +137,22 @@ const isScheduledPost = () => {
|
|||||||
return form.status === 'published' && Boolean(publishedAt) && new Date(publishedAt) > new Date()
|
return form.status === 'published' && Boolean(publishedAt) && new Date(publishedAt) > new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const editorStatusLabel = computed(() => {
|
||||||
|
if (autosaveStatus.value) {
|
||||||
|
return autosaveStatus.value
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.status === 'published') {
|
||||||
|
return isScheduledPost() ? '예약 발행' : '발행됨'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (form.status === 'private') {
|
||||||
|
return '비공개'
|
||||||
|
}
|
||||||
|
|
||||||
|
return '초안'
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 게시물 입력값 생성
|
* 게시물 입력값 생성
|
||||||
* @returns {Object} 게시물 입력값
|
* @returns {Object} 게시물 입력값
|
||||||
@@ -422,6 +439,14 @@ const previewPost = () => {
|
|||||||
emit('preview', createPostPayload())
|
emit('preview', createPostPayload())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 설정 패널 표시 상태 전환
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
const toggleSettingsPanel = () => {
|
||||||
|
isSettingsOpen.value = !isSettingsOpen.value
|
||||||
|
}
|
||||||
|
|
||||||
watch(form, scheduleAutosave, { deep: true })
|
watch(form, scheduleAutosave, { deep: true })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -455,12 +480,79 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form class="admin-post-form grid gap-6" @submit.prevent="submitPost">
|
<form class="admin-post-form flex min-h-[calc(100vh-2.5rem)] flex-col bg-white" @submit.prevent="submitPost">
|
||||||
<div class="admin-post-form__main grid gap-5 lg:grid-cols-[minmax(0,1fr)_18rem]">
|
<header class="admin-post-form__toolbar flex h-[82px] shrink-0 items-start border-b border-transparent bg-white p-6">
|
||||||
<section class="admin-post-form__content grid gap-4">
|
<div class="admin-post-form__toolbar-inner flex h-[34px] min-w-0 flex-1 items-center justify-between">
|
||||||
|
<div class="admin-post-form__toolbar-left flex h-full items-center gap-2">
|
||||||
|
<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">
|
||||||
|
<span class="admin-post-form__toolbar-back text-lg leading-none" aria-hidden="true"><</span>
|
||||||
|
<span>Posts</span>
|
||||||
|
</NuxtLink>
|
||||||
|
<span class="admin-post-form__toolbar-status rounded px-3 py-1.5 text-base text-[#8e9cac]">
|
||||||
|
{{ editorStatusLabel }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="admin-post-form__toolbar-actions flex h-full items-center gap-2">
|
||||||
|
<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" /> -->
|
||||||
|
</button>
|
||||||
|
</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">
|
||||||
|
<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="">
|
||||||
|
<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>
|
||||||
|
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
|
||||||
|
새 업로드
|
||||||
|
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
|
||||||
|
</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>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<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')">
|
||||||
|
<span aria-hidden="true">+</span>
|
||||||
|
<span>대표 이미지 추가</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
v-model="form.title"
|
v-model="form.title"
|
||||||
class="admin-post-form__title-input border-0 bg-transparent px-0 py-2 text-5xl font-semibold leading-tight text-ink outline-none placeholder:text-soft"
|
class="admin-post-form__title-input mb-2 w-full border-0 bg-transparent px-0 py-0 text-5xl font-bold leading-tight text-ink outline-none placeholder:text-[#8e9cac]"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="제목"
|
placeholder="제목"
|
||||||
required
|
required
|
||||||
@@ -484,15 +576,25 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-post-form__field grid gap-2 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>
|
||||||
|
|
||||||
<aside class="admin-post-form__settings grid content-start gap-4">
|
<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">
|
||||||
|
<div class="admin-post-form__settings-header flex h-[82px] shrink-0 items-center justify-between px-6">
|
||||||
|
<h2 class="admin-post-form__settings-title text-xl font-bold text-black">
|
||||||
|
게시물 설정
|
||||||
|
</h2>
|
||||||
|
<button class="admin-post-form__settings-close grid size-6 place-items-center text-[#394047]" type="button" aria-label="게시물 설정 닫기" @click="toggleSettingsPanel">
|
||||||
|
<span aria-hidden="true">x</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="admin-post-form__settings-body grid content-start gap-4 overflow-y-auto px-6 pb-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 rounded border border-line bg-white 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">
|
||||||
<option value="draft">초안</option>
|
<option value="draft">초안</option>
|
||||||
<option value="published">발행</option>
|
<option value="published">발행</option>
|
||||||
<option value="private">비공개</option>
|
<option value="private">비공개</option>
|
||||||
@@ -505,7 +607,7 @@ defineExpose({
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
v-model="form.publishedAt"
|
v-model="form.publishedAt"
|
||||||
class="admin-post-form__input rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
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">
|
||||||
@@ -517,7 +619,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 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
type="text"
|
type="text"
|
||||||
pattern="[a-z0-9가-힣]+(-[a-z0-9가-힣]+)*"
|
pattern="[a-z0-9가-힣]+(-[a-z0-9가-힣]+)*"
|
||||||
required
|
required
|
||||||
@@ -529,7 +631,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-24 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -537,12 +639,12 @@ 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 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
type="text"
|
type="text"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="admin-post-form__seo grid gap-3 rounded border border-line bg-white p-4 text-sm">
|
<div class="admin-post-form__seo grid gap-3 border-t border-[#e3e6e8] pt-5 text-sm">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="admin-post-form__section-title text-sm font-semibold text-ink">
|
<h2 class="admin-post-form__section-title text-sm font-semibold text-ink">
|
||||||
SEO
|
SEO
|
||||||
@@ -556,7 +658,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 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
type="text"
|
type="text"
|
||||||
maxlength="80"
|
maxlength="80"
|
||||||
placeholder="비워두면 글 제목을 사용"
|
placeholder="비워두면 글 제목을 사용"
|
||||||
@@ -570,7 +672,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-24 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__textarea min-h-[108px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
maxlength="180"
|
maxlength="180"
|
||||||
placeholder="비워두면 요약을 사용"
|
placeholder="비워두면 요약을 사용"
|
||||||
/>
|
/>
|
||||||
@@ -583,7 +685,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 rounded border border-line bg-white px-3 py-2"
|
class="admin-post-form__input h-[38px] rounded border border-[#e3e6e8] bg-[#eff1f2] px-3 py-2"
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="비워두면 기본 글 주소를 사용"
|
placeholder="비워두면 기본 글 주소를 사용"
|
||||||
>
|
>
|
||||||
@@ -602,39 +704,6 @@ defineExpose({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-post-form__field grid gap-2 text-sm">
|
|
||||||
<span class="admin-post-form__label font-medium">대표 이미지</span>
|
|
||||||
<figure v-if="form.featuredImage" class="admin-post-form__featured overflow-hidden rounded border border-line bg-white">
|
|
||||||
<img class="admin-post-form__featured-image aspect-[4/3] w-full bg-surface object-cover" :src="form.featuredImage" alt="">
|
|
||||||
<figcaption class="admin-post-form__featured-actions grid gap-2 p-3">
|
|
||||||
<p class="admin-post-form__featured-url break-all text-xs text-muted">
|
|
||||||
{{ form.featuredImage }}
|
|
||||||
</p>
|
|
||||||
<div class="admin-post-form__featured-buttons flex flex-wrap gap-2">
|
|
||||||
<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>
|
|
||||||
<label class="admin-post-form__featured-reupload cursor-pointer rounded border border-line px-3 py-1.5 text-xs font-semibold">
|
|
||||||
새 업로드
|
|
||||||
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
<div v-else class="admin-post-form__featured-empty grid gap-2 rounded border border-dashed border-line bg-white p-4">
|
|
||||||
<button class="admin-post-form__featured-select rounded border border-line px-3 py-2 text-sm font-semibold" type="button" @click="openMediaPicker">
|
|
||||||
미디어에서 선택
|
|
||||||
</button>
|
|
||||||
<label class="admin-post-form__featured-upload cursor-pointer rounded bg-[#15171a] px-3 py-2 text-center text-sm font-semibold text-white">
|
|
||||||
{{ isUploadingFeaturedImage ? '업로드 중' : '새 이미지 업로드' }}
|
|
||||||
<input class="sr-only" type="file" accept="image/*" @change="uploadFeaturedImage">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="admin-post-form__field grid gap-2 text-sm">
|
<div class="admin-post-form__field grid gap-2 text-sm">
|
||||||
<span class="admin-post-form__label font-medium">OG 이미지</span>
|
<span class="admin-post-form__label font-medium">OG 이미지</span>
|
||||||
<figure v-if="form.ogImage" class="admin-post-form__og-image overflow-hidden rounded border border-line bg-white">
|
<figure v-if="form.ogImage" class="admin-post-form__og-image overflow-hidden rounded border border-line bg-white">
|
||||||
@@ -667,32 +736,10 @@ defineExpose({
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="admin-post-form__actions flex justify-end gap-3 border-t border-line pt-5">
|
|
||||||
<p v-if="autosaveStatus" class="admin-post-form__autosave-status mr-auto self-center text-xs text-muted">
|
|
||||||
{{ autosaveStatus }}
|
|
||||||
</p>
|
|
||||||
<NuxtLink class="admin-post-form__cancel rounded border border-line bg-white px-4 py-2 text-sm font-semibold" to="/admin/posts">
|
|
||||||
취소
|
|
||||||
</NuxtLink>
|
|
||||||
<button
|
|
||||||
class="admin-post-form__preview rounded border border-line bg-white px-4 py-2 text-sm font-semibold"
|
|
||||||
type="button"
|
|
||||||
@click="previewPost"
|
|
||||||
>
|
|
||||||
미리보기
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="admin-post-form__submit rounded bg-[#15171a] px-4 py-2 text-sm font-semibold text-white disabled:opacity-50"
|
|
||||||
type="submit"
|
|
||||||
:disabled="saving"
|
|
||||||
>
|
|
||||||
{{ saving ? '저장 중' : submitLabel }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="isMediaPickerOpen"
|
v-if="isMediaPickerOpen"
|
||||||
class="admin-post-form__media-picker fixed inset-0 z-50 grid place-items-center bg-black/40 px-5 py-8"
|
class="admin-post-form__media-picker fixed inset-0 z-50 grid place-items-center bg-black/40 px-5 py-8"
|
||||||
|
|||||||
101
docs/deploy.md
101
docs/deploy.md
@@ -9,7 +9,7 @@
|
|||||||
| 개발 | `npm run dev` | 로컬 테스트, 개발 서버 |
|
| 개발 | `npm run dev` | 로컬 테스트, 개발 서버 |
|
||||||
| 프로덕션 | `npm run build` | NAS 배포, 운영 서버 |
|
| 프로덕션 | `npm run build` | NAS 배포, 운영 서버 |
|
||||||
|
|
||||||
> `npm run dev`는 프로젝트 전용 실행 스크립트를 통해 Localhost, Local IP, Admin, Tailwind Viewer 링크만 요약 출력한다.
|
> `npm run dev`는 프로젝트 전용 실행 스크립트를 통해 개발 서버, Admin, Tailwind Viewer 링크만 요약 출력한다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -50,6 +50,8 @@ npm run dev
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Docker daemon 시작
|
# Docker daemon 시작
|
||||||
|
# Docker Desktop을 사용하면 Docker.app을 먼저 실행
|
||||||
|
# Colima를 사용하면 아래 명령 실행
|
||||||
colima start
|
colima start
|
||||||
|
|
||||||
# 개발 DB 컨테이너 실행
|
# 개발 DB 컨테이너 실행
|
||||||
@@ -71,6 +73,33 @@ docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT count(*
|
|||||||
- 관리자: http://127.0.0.1:43117/admin
|
- 관리자: http://127.0.0.1:43117/admin
|
||||||
- Tailwind Viewer: http://127.0.0.1:43117/_tailwind/
|
- Tailwind Viewer: http://127.0.0.1:43117/_tailwind/
|
||||||
|
|
||||||
|
### 로컬 DB 확인 방법
|
||||||
|
|
||||||
|
로컬 개발 DB는 PostgreSQL이며 호스트에서는 `127.0.0.1:43119`로 접근한다. 접속 정보는 Git에 포함하지 않는 `.env.development` 값을 사용한다.
|
||||||
|
|
||||||
|
| 항목 | 값 |
|
||||||
|
|------|----|
|
||||||
|
| Host | `127.0.0.1` |
|
||||||
|
| Port | `43119` |
|
||||||
|
| Database | `.env.development`의 `POSTGRES_DB` |
|
||||||
|
| User | `.env.development`의 `POSTGRES_USER` |
|
||||||
|
| Password | `.env.development`의 `POSTGRES_PASSWORD` |
|
||||||
|
|
||||||
|
터미널에서 바로 확인할 때는 컨테이너 내부 `psql`을 사용한다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# DB 준비 상태 확인
|
||||||
|
docker exec sori-studio-db pg_isready -U sori_studio -d sori_studio
|
||||||
|
|
||||||
|
# 게시물 개수 확인
|
||||||
|
docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT count(*) AS posts_count FROM posts;'
|
||||||
|
|
||||||
|
# psql 콘솔 접속
|
||||||
|
docker exec -it sori-studio-db psql -U sori_studio -d sori_studio
|
||||||
|
```
|
||||||
|
|
||||||
|
GUI로 확인할 때는 DBeaver, TablePlus, DataGrip, CloudBeaver 같은 PostgreSQL 클라이언트에서 위 접속 정보를 입력한다. phpMyAdmin은 MySQL/MariaDB용 도구라 이 프로젝트의 PostgreSQL DB 확인 용도로는 사용하지 않는다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## UGREEN NAS Docker 배포
|
## UGREEN NAS Docker 배포
|
||||||
@@ -141,6 +170,76 @@ docker run -d -p 3000:3000 sori.studio:latest
|
|||||||
- 로컬 개발 Docker Compose 실행 시 `ENV_FILE=.env.development`와 `--env-file .env.development`를 함께 사용
|
- 로컬 개발 Docker Compose 실행 시 `ENV_FILE=.env.development`와 `--env-file .env.development`를 함께 사용
|
||||||
- 로컬 개발 DB 마이그레이션은 `npm run db:migrate:dev`로 실행
|
- 로컬 개발 DB 마이그레이션은 `npm run db:migrate:dev`로 실행
|
||||||
|
|
||||||
|
### 개발/운영 DB 분리 검증 절차
|
||||||
|
|
||||||
|
검증 전제는 실제 비밀번호나 전체 `DATABASE_URL`을 화면 공유, 문서, 커밋 메시지에 노출하지 않는 것이다. 확인할 때는 호스트, 포트, DB 이름, 파일명만 대조한다.
|
||||||
|
|
||||||
|
1. `.env.development` 확인.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 로컬 개발 DB는 호스트 기준 127.0.0.1:43119를 사용해야 한다.
|
||||||
|
# DATABASE_URL 전체 값은 공유하지 않는다.
|
||||||
|
rg -n "^(DATABASE_URL|POSTGRES_DB|POSTGRES_USER|DB_PORT)=" .env.development
|
||||||
|
```
|
||||||
|
|
||||||
|
기준:
|
||||||
|
|
||||||
|
- `DATABASE_URL` 호스트가 `127.0.0.1`
|
||||||
|
- `DATABASE_URL` 포트가 `43119`
|
||||||
|
- `DB_PORT=43119`
|
||||||
|
- 운영 NAS 호스트명, 운영 IP, 운영 DB 이름이 포함되지 않음
|
||||||
|
|
||||||
|
2. `.env.production` 확인.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 운영 파일은 Git에 올리지 않는 운영 전용 파일이다.
|
||||||
|
# 값이 없으면 NAS 배포 전 작성해야 한다.
|
||||||
|
test -f .env.production && rg -n "^(DATABASE_URL|POSTGRES_DB|POSTGRES_USER|APP_PORT)=" .env.production
|
||||||
|
```
|
||||||
|
|
||||||
|
기준:
|
||||||
|
|
||||||
|
- NAS Docker 내부 실행 기준이면 `DATABASE_URL` 호스트가 `sori-studio-db`
|
||||||
|
- NAS 외부 DB를 별도 인스턴스로 쓰는 경우에도 로컬 개발 DB(`127.0.0.1:43119`)를 가리키지 않음
|
||||||
|
- `APP_PORT=43118`
|
||||||
|
- `.env.development`와 DB 비밀번호, 관리자 비밀번호가 서로 다름
|
||||||
|
|
||||||
|
3. 로컬 개발 DB 연결 확인.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec sori-studio-db pg_isready -U sori_studio -d sori_studio
|
||||||
|
docker exec sori-studio-db psql -U sori_studio -d sori_studio -c 'SELECT current_database(), current_user;'
|
||||||
|
```
|
||||||
|
|
||||||
|
기준:
|
||||||
|
|
||||||
|
- `accepting connections` 표시
|
||||||
|
- `current_database`가 로컬 개발 DB 이름
|
||||||
|
- `current_user`가 로컬 개발 DB 계정
|
||||||
|
|
||||||
|
4. 로컬 개발 서버 연결 확인.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
기준:
|
||||||
|
|
||||||
|
- 출력 주소가 `http://127.0.0.1:43117`
|
||||||
|
- 관리자 API 요청에서 `127.0.0.1:43119` 연결 오류가 발생하지 않음
|
||||||
|
|
||||||
|
5. 커밋 전 민감 정보 확인.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status --short
|
||||||
|
git diff -- . ':!package-lock.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
기준:
|
||||||
|
|
||||||
|
- `.env.development`, `.env.production`이 변경 목록에 포함되지 않음
|
||||||
|
- 문서와 코드 diff에 실제 DB 비밀번호, 관리자 비밀번호, 운영 접속 주소가 포함되지 않음
|
||||||
|
|
||||||
## 업로드 파일
|
## 업로드 파일
|
||||||
|
|
||||||
- 관리자 글쓰기에서 업로드한 이미지는 `/uploads/posts/YYYY/MM/` URL로 제공한다.
|
- 관리자 글쓰기에서 업로드한 이미지는 `/uploads/posts/YYYY/MM/` URL로 제공한다.
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-05-07 v0.0.32
|
||||||
|
|
||||||
|
### 관리자 글 작성 화면 구조 정리 결정
|
||||||
|
|
||||||
|
관리자 글 작성/수정 화면은 별도 페이지 제목 영역을 제거하고, 글쓰기 폼 자체의 상단 도구막대와 본문 중심 레이아웃으로 정리한다. 기존 “새 글 작성” 제목은 현재 작업 맥락을 반복해 화면 높이만 차지했고, 실제 작성 흐름에서는 제목 입력과 본문 시작 위치를 아래로 밀어 Ghost 스타일 편집감과 멀어졌기 때문이다.
|
||||||
|
|
||||||
|
대표 이미지는 설정 패널의 부가 항목이 아니라 글 제목 위의 본문 흐름에서 바로 추가하도록 둔다. 게시물 설정은 420px 우측 패널로 분리하고 토글할 수 있게 해, 설정을 열었을 때는 Figma 설정 패널 상태를 따르고 닫았을 때는 집중형 작성 화면을 유지한다.
|
||||||
|
|
||||||
## 2026-05-03 v0.0.31
|
## 2026-05-03 v0.0.31
|
||||||
|
|
||||||
### 글 미리보기 저장 방식 결정
|
### 글 미리보기 저장 방식 결정
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
|
|
||||||
| 파일 | 화면 위치 |
|
| 파일 | 화면 위치 |
|
||||||
|------|-----------|
|
|------|-----------|
|
||||||
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, 대표 이미지/OG 이미지 선택, 로컬 자동 저장, 예약 발행 시각 입력, SEO 설정, 미리보기 요청 |
|
| components/admin/AdminPostForm.vue | 관리자 글 작성/수정 폼, Ghost 스타일 상단 바, 중앙 에디터, 우측 설정 패널, 대표 이미지/OG 이미지 선택, 로컬 자동 저장, 예약 발행 시각 입력, SEO 설정, 미리보기 요청 |
|
||||||
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 대표 이미지 선택 |
|
| components/admin/AdminPageForm.vue | 관리자 페이지 작성/수정 폼, 대표 이미지 선택 |
|
||||||
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 한글 조합 입력 처리, 하단 빈 입력 블록 유지 |
|
| components/admin/AdminBlockEditor.vue | 관리자 글 블록형 에디터, 이미지/갤러리/콜아웃/토글/임베드 블록, 한글 조합 입력 처리, 하단 빈 입력 블록 유지, 본문 placeholder 표시 |
|
||||||
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼 |
|
| components/admin/AdminTagForm.vue | 관리자 태그 생성/수정 폼 |
|
||||||
|
|
||||||
## 콘텐츠 컴포넌트
|
## 콘텐츠 컴포넌트
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
| pages/admin/index.vue | 대시보드 |
|
| pages/admin/index.vue | 대시보드 |
|
||||||
| pages/admin/login.vue | 관리자 로그인 |
|
| pages/admin/login.vue | 관리자 로그인 |
|
||||||
| pages/admin/posts/index.vue | 글 목록, 예약 발행 상태 표시 |
|
| pages/admin/posts/index.vue | 글 목록, 예약 발행 상태 표시 |
|
||||||
| pages/admin/posts/new.vue | 글 작성, 저장 토스트 |
|
| pages/admin/posts/new.vue | 글 작성, Ghost 스타일 작성 폼, 저장 토스트 |
|
||||||
| pages/admin/posts/[id].vue | 글 수정, 저장/삭제 토스트 |
|
| pages/admin/posts/[id].vue | 글 수정, Ghost 스타일 작성 폼, 저장/삭제 토스트 |
|
||||||
| pages/admin/posts/preview.vue | 글 미리보기 |
|
| pages/admin/posts/preview.vue | 글 미리보기 |
|
||||||
| pages/admin/pages/index.vue | 페이지 목록 |
|
| pages/admin/pages/index.vue | 페이지 목록 |
|
||||||
| pages/admin/pages/new.vue | 페이지 작성, 저장 토스트 |
|
| pages/admin/pages/new.vue | 페이지 작성, 저장 토스트 |
|
||||||
|
|||||||
@@ -287,6 +287,10 @@ components/content/
|
|||||||
- 빈 블록 placeholder는 현재 활성 블록 또는 첫 빈 블록에만 표시한다.
|
- 빈 블록 placeholder는 현재 활성 블록 또는 첫 빈 블록에만 표시한다.
|
||||||
- 에디터 마지막에는 클릭 가능한 빈 문단 블록을 항상 유지하며, 해당 블록이 비어 있으면 저장 콘텐츠에는 포함하지 않는다.
|
- 에디터 마지막에는 클릭 가능한 빈 문단 블록을 항상 유지하며, 해당 블록이 비어 있으면 저장 콘텐츠에는 포함하지 않는다.
|
||||||
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
|
- 제목은 별도 라벨 영역이 아니라 에디터 상단의 큰 제목 입력으로 표시한다.
|
||||||
|
- 글 작성/수정 화면은 페이지 제목 헤더를 별도로 두지 않고, 폼 상단의 Ghost 스타일 도구막대에서 목록 이동, 상태, 미리보기, 저장, 설정 패널 토글을 제공한다.
|
||||||
|
- 본문 에디터는 데스크톱에서 좌우 32px 패딩을 포함한 804px 중앙 컬럼으로 배치해 실제 입력 영역 최대 폭을 740px로 유지한다.
|
||||||
|
- 게시물 설정은 데스크톱에서 420px 우측 패널로 표시하며, 도구막대 버튼으로 열고 닫을 수 있다.
|
||||||
|
- 대표 이미지는 본문 제목 위의 에디터 흐름 안에서 추가, 변경, 삭제한다.
|
||||||
- 제목 입력에서 Enter를 누르면 본문 첫 블록으로 포커스를 이동한다.
|
- 제목 입력에서 Enter를 누르면 본문 첫 블록으로 포커스를 이동한다.
|
||||||
- 새 글 작성처럼 본문이 비어 있는 경우에도 빈 문단 블록을 먼저 생성한다.
|
- 새 글 작성처럼 본문이 비어 있는 경우에도 빈 문단 블록을 먼저 생성한다.
|
||||||
- 글 작성/수정 중인 입력값은 브라우저 `localStorage`에 자동 저장한다.
|
- 글 작성/수정 중인 입력값은 브라우저 `localStorage`에 자동 저장한다.
|
||||||
@@ -418,6 +422,7 @@ APP_PORT=43118
|
|||||||
- `.env.development`와 `.env.production`의 DB 비밀번호와 관리자 비밀번호는 서로 다른 랜덤 값을 사용
|
- `.env.development`와 `.env.production`의 DB 비밀번호와 관리자 비밀번호는 서로 다른 랜덤 값을 사용
|
||||||
- 로컬 개발 `DATABASE_URL`은 호스트 기준 `127.0.0.1:43119`를 사용
|
- 로컬 개발 `DATABASE_URL`은 호스트 기준 `127.0.0.1:43119`를 사용
|
||||||
- NAS Docker 내부 `DATABASE_URL`은 서비스명 기준 `sori-studio-db:5432`를 사용
|
- NAS Docker 내부 `DATABASE_URL`은 서비스명 기준 `sori-studio-db:5432`를 사용
|
||||||
|
- 로컬 DB 확인은 PostgreSQL 클라이언트에서 `127.0.0.1:43119`로 접속하거나 `docker exec sori-studio-db psql` 명령으로 확인한다.
|
||||||
|
|
||||||
### 포트 기준
|
### 포트 기준
|
||||||
|
|
||||||
@@ -432,6 +437,6 @@ APP_PORT=43118
|
|||||||
|
|
||||||
## 버전 관리
|
## 버전 관리
|
||||||
|
|
||||||
- 현재 버전: v0.0.13
|
- 현재 버전: v0.0.34
|
||||||
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
|
- 첫 커밋 이후 변경사항을 커밋할 때마다 패치 버전 증가
|
||||||
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정
|
- 메이저/마이너 버전은 구조 변경 또는 기능 묶음 단위로 결정
|
||||||
|
|||||||
@@ -46,8 +46,6 @@
|
|||||||
## 데이터베이스
|
## 데이터베이스
|
||||||
|
|
||||||
- [ ] NAS 운영 DB 연결 설정 실제 값 작성
|
- [ ] NAS 운영 DB 연결 설정 실제 값 작성
|
||||||
- [ ] 개발 DB와 운영 DB 분리 검증 절차 작성
|
|
||||||
- [ ] CloudBeaver PostgreSQL 연결 방식 확정
|
|
||||||
- [ ] 이전에 원격에 올라간 관리자 비밀번호가 실제 사용 값이면 즉시 폐기 및 변경
|
- [ ] 이전에 원격에 올라간 관리자 비밀번호가 실제 사용 값이면 즉시 폐기 및 변경
|
||||||
|
|
||||||
## 배포
|
## 배포
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v0.0.34
|
||||||
|
|
||||||
|
- 배포 문서에 개발/운영 DB 분리 검증 절차 추가.
|
||||||
|
- 로컬 개발 DB 실행 안내에 Docker Desktop과 Colima 기준 병기.
|
||||||
|
- 할 일에서 개발 DB와 운영 DB 분리 검증 절차 작성 항목 정리.
|
||||||
|
- 기술 명세 현재 버전을 v0.0.34로 갱신.
|
||||||
|
- 패키지 버전을 0.0.34로 갱신.
|
||||||
|
|
||||||
|
## v0.0.33
|
||||||
|
|
||||||
|
- 개발 서버 실행 링크 출력을 개발 서버, Admin, Tailwind Viewer 3개 주소로 정리.
|
||||||
|
- 개발 서버 실행 링크가 Nuxt 출력 URL 대신 프로젝트 고정 포트 43117 기준으로 표시되도록 수정.
|
||||||
|
- 배포 문서에 로컬 PostgreSQL DB 확인 방법 추가.
|
||||||
|
- 할 일에서 CloudBeaver PostgreSQL 연결 방식 확정 항목 정리.
|
||||||
|
- 기술 명세 현재 버전을 v0.0.33으로 갱신.
|
||||||
|
- 패키지 버전을 0.0.33으로 갱신.
|
||||||
|
|
||||||
|
## v0.0.32
|
||||||
|
|
||||||
|
- 관리자 글 작성/수정 화면의 별도 제목 헤더 제거.
|
||||||
|
- 관리자 글 작성/수정 폼을 Ghost 스타일 상단 바와 중앙 에디터 구조로 수정.
|
||||||
|
- 대표 이미지 추가 영역을 에디터 본문 상단으로 이동.
|
||||||
|
- 게시물 설정 패널을 420px 우측 패널과 토글 구조로 정리.
|
||||||
|
- 관리자 글 에디터 컬럼을 좌우 32px 패딩 포함 804px로 보정해 실제 본문 폭 740px 유지.
|
||||||
|
- 블록 에디터 첫 placeholder 문구를 간결하게 수정.
|
||||||
|
- 패키지 버전을 0.0.32로 갱신.
|
||||||
|
|
||||||
## v0.0.31
|
## v0.0.31
|
||||||
|
|
||||||
- 관리자 글 작성/수정 폼에 미리보기 버튼 추가.
|
- 관리자 글 작성/수정 폼에 미리보기 버튼 추가.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "0.0.31",
|
"version": "0.0.34",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "0.0.31",
|
"version": "0.0.34",
|
||||||
"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.31",
|
"version": "0.0.34",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -137,17 +137,8 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="admin-post-edit bg-paper p-6">
|
<section class="admin-post-edit relative bg-white">
|
||||||
<div class="admin-post-edit__header mb-8 flex items-start justify-between gap-4">
|
<div class="admin-post-edit__quick-actions absolute right-8 top-24 z-10 flex gap-2">
|
||||||
<div>
|
|
||||||
<p class="admin-post-edit__eyebrow text-xs font-semibold uppercase text-muted">
|
|
||||||
Posts
|
|
||||||
</p>
|
|
||||||
<h1 class="admin-post-edit__title mt-2 text-3xl font-semibold">
|
|
||||||
글 수정
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<div class="admin-post-edit__actions flex gap-2">
|
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
v-if="isPublicPost(post)"
|
v-if="isPublicPost(post)"
|
||||||
class="admin-post-edit__view rounded border border-line bg-white px-4 py-2 text-sm font-semibold"
|
class="admin-post-edit__view rounded border border-line bg-white px-4 py-2 text-sm font-semibold"
|
||||||
@@ -164,9 +155,8 @@ onBeforeUnmount(() => {
|
|||||||
>
|
>
|
||||||
{{ deleting ? '삭제 중' : '삭제' }}
|
{{ deleting ? '삭제 중' : '삭제' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p v-if="errorMessage" class="admin-post-edit__error mb-5 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
<p v-if="errorMessage" class="admin-post-edit__error mx-6 mt-6 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
<AdminPostForm ref="postForm" :initial-post="post" submit-label="변경 저장" :saving="saving" @submit="savePost" @preview="previewPost" />
|
<AdminPostForm ref="postForm" :initial-post="post" submit-label="변경 저장" :saving="saving" @submit="savePost" @preview="previewPost" />
|
||||||
|
|||||||
@@ -72,16 +72,8 @@ onBeforeUnmount(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="admin-post-editor bg-paper p-6">
|
<section class="admin-post-editor bg-white">
|
||||||
<div class="admin-post-editor__header mb-8">
|
<p v-if="errorMessage" class="admin-post-editor__error mx-6 mt-6 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||||
<p class="admin-post-editor__eyebrow text-xs font-semibold uppercase text-muted">
|
|
||||||
Posts
|
|
||||||
</p>
|
|
||||||
<h1 class="admin-post-editor__title mt-2 text-3xl font-semibold">
|
|
||||||
새 글 작성
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
<p v-if="errorMessage" class="admin-post-editor__error mb-5 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
|
||||||
{{ errorMessage }}
|
{{ errorMessage }}
|
||||||
</p>
|
</p>
|
||||||
<AdminPostForm ref="postForm" submit-label="글 저장" :saving="saving" @submit="savePost" @preview="previewPost" />
|
<AdminPostForm ref="postForm" submit-label="글 저장" :saving="saving" @submit="savePost" @preview="previewPost" />
|
||||||
|
|||||||
@@ -19,18 +19,13 @@ const nuxtProcess = spawn('nuxt', [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 개발 서버 링크 출력
|
* 개발 서버 링크 출력
|
||||||
* @param {string} localUrl - Nuxt 로컬 URL
|
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
const printDevLinks = (localUrl) => {
|
const printDevLinks = () => {
|
||||||
const url = new URL(localUrl)
|
const origin = `http://${host}:${port}`
|
||||||
const origin = `${url.protocol}//${url.hostname}:${url.port}`
|
|
||||||
const localhostOrigin = `${url.protocol}//localhost:${url.port}`
|
|
||||||
|
|
||||||
console.log('')
|
console.log('')
|
||||||
console.log('sori.studio 개발 서버')
|
console.log('sori.studio 개발 서버')
|
||||||
console.log(`- Localhost: ${localhostOrigin}/`)
|
console.log(`- 개발 서버: ${origin}`)
|
||||||
console.log(`- Local IP: ${origin}/`)
|
|
||||||
console.log(`- Admin: ${origin}/admin`)
|
console.log(`- Admin: ${origin}/admin`)
|
||||||
console.log(`- Tailwind Viewer: ${origin}/_tailwind/`)
|
console.log(`- Tailwind Viewer: ${origin}/_tailwind/`)
|
||||||
console.log('')
|
console.log('')
|
||||||
@@ -49,7 +44,7 @@ const handleNuxtLog = (chunk) => {
|
|||||||
|
|
||||||
if (localMatch && !printedLinks) {
|
if (localMatch && !printedLinks) {
|
||||||
printedLinks = true
|
printedLinks = true
|
||||||
printDevLinks(localMatch[1])
|
printDevLinks()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user