사이트 코드와 홈페이지 위젯 추가 v1.5.34

This commit is contained in:
2026-06-02 14:21:47 +09:00
parent 600b0fd1d9
commit 5b78a8c92f
21 changed files with 618 additions and 39 deletions

View File

@@ -17,6 +17,7 @@ const savingPost = ref(false)
const savingHomeCover = ref(false)
const savingAnnouncement = ref(false)
const savingSpam = ref(false)
const savingSiteCode = ref(false)
const uploadingLogo = ref(false)
const uploadingHomeCover = ref(false)
const uploadingHomeCoverDark = ref(false)
@@ -59,6 +60,8 @@ const editHomeCover = ref(false)
const customizeAnnouncement = ref(false)
/** 스팸 필터 카드 편집 모드 여부 */
const editSpam = ref(false)
/** 사이트 코드 카드 편집 모드 여부 */
const editSiteCode = ref(false)
/** 편집 시작 시점의 제목·설명(취소 시 복원용) */
const titleDescSnapshot = reactive({
title: '',
@@ -94,6 +97,12 @@ const announcementSnapshot = reactive({
const spamSnapshot = reactive({
signupBlockedUsernames: []
})
/** 편집 시작 시점의 사이트 코드(취소 시 복원용) */
const siteCodeSnapshot = reactive({
adsTxt: '',
customHeadCode: '',
customFooterCode: ''
})
let toastTimer = null
let scrollSpyFrame = null
let postExportRefreshTimer = null
@@ -123,7 +132,10 @@ const form = reactive({
announcementText: settings.value?.announcementText || '',
announcementUrl: settings.value?.announcementUrl || '',
announcementBackgroundColor: settings.value?.announcementBackgroundColor || '#15171a',
signupBlockedUsernames: normalizeSignupBlockedUsernames(settings.value?.signupBlockedUsernames)
signupBlockedUsernames: normalizeSignupBlockedUsernames(settings.value?.signupBlockedUsernames),
adsTxt: settings.value?.adsTxt || '',
customHeadCode: settings.value?.customHeadCode || '',
customFooterCode: settings.value?.customFooterCode || ''
})
/**
@@ -183,6 +195,16 @@ const hasAnnouncementChanges = computed(() => customizeAnnouncement.value && (
const hasSpamChanges = computed(() => editSpam.value
&& JSON.stringify(form.signupBlockedUsernames) !== JSON.stringify(spamSnapshot.signupBlockedUsernames))
/**
* 사이트 코드 변경 여부
* @returns {boolean} 변경 여부
*/
const hasSiteCodeChanges = computed(() => editSiteCode.value && (
form.adsTxt !== siteCodeSnapshot.adsTxt
|| form.customHeadCode !== siteCodeSnapshot.customHeadCode
|| form.customFooterCode !== siteCodeSnapshot.customFooterCode
))
/**
* 최신 게시물 export 작업 목록
* @returns {Array} export 작업 목록
@@ -449,7 +471,8 @@ const settingsNavGroups = [
heading: '사이트',
items: [
{ id: 'admin-settings-section-home-cover', label: '메인 화면', keywords: 'home cover hero banner image', iconId: 'home-cover' },
{ id: 'admin-settings-section-announcement', label: '어나운스 바', keywords: 'announcement banner notice', iconId: 'announcement' }
{ id: 'admin-settings-section-announcement', label: '어나운스 바', keywords: 'announcement banner notice', iconId: 'announcement' },
{ id: 'admin-settings-section-site-code', label: '사이트 코드', keywords: 'ads ads.txt head footer script code adsense', iconId: 'site-code' }
]
},
{
@@ -733,6 +756,16 @@ const getSelectedPostExportFileIds = (jobId) => Array.isArray(selectedPostExport
*/
const isPostExportFileSelected = (job, file) => getSelectedPostExportFileIds(job?.id).includes(file?.id)
/**
* Export 파일 선택 비활성 여부를 확인한다.
* @param {Object} job - Export 작업
* @param {Object} file - Export 파일
* @returns {boolean} 비활성 여부
*/
const isPostExportFileSelectionDisabled = (job, file) => file.status !== 'ready'
|| !file.filePath
|| isDownloadingPostExportJob(job.id)
/**
* Export 파일 선택 상태를 변경한다.
* @param {Object} job - Export 작업
@@ -756,6 +789,16 @@ const setPostExportFileSelected = (job, file, selected) => {
}
}
/**
* Export 파일 선택 상태를 반대로 전환한다.
* @param {Object} job - Export 작업
* @param {Object} file - Export 파일
* @returns {void}
*/
const togglePostExportFileSelection = (job, file) => {
setPostExportFileSelected(job, file, !isPostExportFileSelected(job, file))
}
/**
* Export 작업에서 선택된 다운로드 가능 파일을 가져온다.
* @param {Object} job - Export 작업
@@ -1003,7 +1046,10 @@ const buildSiteSettingsPayload = () => ({
announcementText: form.announcementText || '',
announcementUrl: form.announcementUrl || '',
announcementBackgroundColor: form.announcementBackgroundColor || '#15171a',
signupBlockedUsernames: normalizeSignupBlockedUsernames(form.signupBlockedUsernames)
signupBlockedUsernames: normalizeSignupBlockedUsernames(form.signupBlockedUsernames),
adsTxt: form.adsTxt || '',
customHeadCode: form.customHeadCode || '',
customFooterCode: form.customFooterCode || ''
})
/**
@@ -1408,6 +1454,50 @@ const saveSpamSection = async () => {
}
}
/**
* 사이트 코드 편집 모드 진입
* @returns {void}
*/
const beginEditSiteCode = () => {
siteCodeSnapshot.adsTxt = form.adsTxt
siteCodeSnapshot.customHeadCode = form.customHeadCode
siteCodeSnapshot.customFooterCode = form.customFooterCode
editSiteCode.value = true
}
/**
* 사이트 코드 편집 취소
* @returns {void}
*/
const cancelEditSiteCode = () => {
form.adsTxt = siteCodeSnapshot.adsTxt
form.customHeadCode = siteCodeSnapshot.customHeadCode
form.customFooterCode = siteCodeSnapshot.customFooterCode
editSiteCode.value = false
}
/**
* 사이트 코드 저장
* @returns {Promise<void>}
*/
const saveSiteCodeSection = async () => {
if (!hasSiteCodeChanges.value) {
return
}
const ok = await persistSiteSettings({
successToast: '사이트 코드 설정이 저장되었습니다.',
savingFlag: savingSiteCode
})
if (ok) {
siteCodeSnapshot.adsTxt = form.adsTxt
siteCodeSnapshot.customHeadCode = form.customHeadCode
siteCodeSnapshot.customFooterCode = form.customFooterCode
editSiteCode.value = false
}
}
/**
* Escape 키: 제목·설명 편집 중이면 취소, 아니면 설정 화면 닫기
* @param {KeyboardEvent} event - 키보드 이벤트
@@ -1447,6 +1537,11 @@ const onGlobalKeydown = (event) => {
cancelEditSpam()
return
}
if (editSiteCode.value) {
event.preventDefault()
cancelEditSiteCode()
return
}
closeSettings()
}
@@ -2340,6 +2435,16 @@ onBeforeUnmount(() => {
</div>
</section>
<AdminSiteCodeSettingsCard
:form="form"
:editing="editSiteCode"
:saving="savingSiteCode"
:has-changes="hasSiteCodeChanges"
@begin="beginEditSiteCode"
@cancel="cancelEditSiteCode"
@save="saveSiteCodeSection"
/>
<h2 class="admin-settings-screen__section-heading z-20 mb-px pt-10 text-2xl font-bold tracking-tight text-[#15171a]">
콘텐츠·안전
</h2>
@@ -2614,7 +2719,7 @@ onBeforeUnmount(() => {
:checked="areAllReadyPostExportFilesSelected(job)"
:disabled="getReadyPostExportFiles(job).length === 0 || isDownloadingPostExportJob(job.id)"
@change="toggleAllPostExportFiles(job)"
>
/>
전체 선택
</label>
<button
@@ -2626,34 +2731,14 @@ onBeforeUnmount(() => {
{{ isDownloadingPostExportJob(job.id) ? '다운로드 중' : `선택 파일 다운로드 ${getSelectedReadyPostExportFiles(job).length || ''}` }}
</button>
</div>
<div
<AdminPostExportFileRow
v-for="file in job.files"
:key="file.id"
class="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 border-b border-[#edf0f3] px-3 py-2 last:border-b-0"
>
<input
class="size-4 rounded border-[#cfd6de] text-[#15171a] focus:ring-[#15171a] disabled:cursor-not-allowed"
type="checkbox"
:checked="isPostExportFileSelected(job, file)"
:disabled="file.status !== 'ready' || !file.filePath || isDownloadingPostExportJob(job.id)"
:aria-label="`${file.fileName} 선택`"
@change="setPostExportFileSelected(job, file, $event.target.checked)"
>
<div class="min-w-0">
<p class="truncate text-sm font-medium text-[#15171a]">
{{ file.fileName }}
</p>
<p class="mt-0.5 text-xs text-[#9aa3ad]">
{{ file.postStart }}-{{ file.postEnd }}
</p>
</div>
<span
v-if="file.status !== 'ready' || !file.filePath"
class="inline-flex h-8 items-center justify-center rounded px-2 text-xs font-semibold text-[#a6b0bb]"
>
{{ file.status === 'processing' ? '생성 중' : file.status === 'failed' ? '실패' : '다운로드 대기' }}
</span>
</div>
:file="file"
:selected="isPostExportFileSelected(job, file)"
:disabled="isPostExportFileSelectionDisabled(job, file)"
@toggle="togglePostExportFileSelection(job, file)"
/>
</div>
</article>
</div>