diff --git a/docs/history.md b/docs/history.md index 846813c..511f8c2 100644 --- a/docs/history.md +++ b/docs/history.md @@ -1,5 +1,9 @@ # 의사결정 이력 +## 2026-04-02 v1.3.81 +- 저장된 티어표 공유는 별도 새 페이지를 만들기보다, 이미 완성본 열람에 쓰고 있는 `preview=1` 주소를 그대로 공유 링크로 재사용하는 편이 가장 단순하고 일관적이라고 정리했다. +- 공유 액션은 저장/삭제처럼 저장본 전제의 보조 기능이므로, 메인 저장 버튼 영역보다 하단 유틸리티 링크 영역에 두는 편이 더 자연스럽다고 판단했다. + ## 2026-04-02 v1.3.79 - 카피라이트처럼 앱 전체 브랜딩 성격의 footer는 관리자 텔레포트 안에 두기보다, `App.vue`의 공통 오른쪽 레일 footer로 두는 편이 위치도 안정적이고 화면 간 일관성도 높다고 정리했다. diff --git a/docs/todo.md b/docs/todo.md index 22911a2..68d0319 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -1,6 +1,7 @@ # 할 일 및 이슈 ## 단기 확인 +- 티어표 만들기 화면의 `공유하기`는 저장된 티어표에서만 노출되므로, 저장 직후/수정 중/복사본/읽기 전용 상태 각각에서 노출 조건과 클립보드 복사가 자연스러운지 한 번 더 QA한다. - 우측 카피라이트는 이제 공통 오른쪽 레일 footer이므로, 관리자 화면뿐 아니라 홈/프로필 등 오른쪽 사이드가 보이는 화면에서도 같은 최하단 위치에 유지되는지 한 번 더 QA한다. - 왼쪽 레일 축소 상태의 하단 액션 아이콘은 홈과 게임 허브에서 서로 다른 아이콘을 쓰도록 나눴으므로, 실제로 두 문맥이 한눈에 구분되는지 한 번 더 QA한다. - 왼쪽 레일 축소 상태 최하단의 `티어표 만들기` 아이콘 버튼은 새로 추가했으므로, 홈/게임 허브에서 실제로 같은 위치 감각으로 동작하는지 한 번 더 QA한다. diff --git a/docs/update.md b/docs/update.md index 18e9b06..ac165f8 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,9 @@ # 업데이트 로그 +## 2026-04-02 v1.3.81 +- 티어표 만들기 화면에는 저장된 티어표에서만 보이는 `공유하기` 액션을 추가하고, 누르면 현재 티어표의 완성본 링크(`preview=1`)를 클립보드에 복사한 뒤 토스트로 안내하도록 정리함. +- 공유 링크는 관리자가 새 창에서 보던 완성본 주소와 같은 문법을 사용하므로, 저장된 티어표를 그대로 외부에 전달하거나 다시 열람하는 흐름으로 바로 이어짐. + ## 2026-04-02 v1.3.79 - 우측 카피라이트는 관리자 전용 레이아웃에서 분리해 앱 공통 `rightRail` footer로 올렸고, 이제 관리자 페이지뿐 아니라 오른쪽 사이드가 보이는 모든 화면에서 같은 최하단 위치에 표시됨. - 따라서 관리자 패널 길이나 페이지별 로컬 사이드바 내용과 무관하게, 카피라이트는 항상 오른쪽 레일 전체 기준 바닥에 고정되는 공통 footer 역할로 정리됨. diff --git a/frontend/src/assets/icons/share.svg b/frontend/src/assets/icons/share.svg new file mode 100644 index 0000000..9ad9c90 --- /dev/null +++ b/frontend/src/assets/icons/share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/views/TierEditorView.vue b/frontend/src/views/TierEditorView.vue index fe4a623..cd2e311 100644 --- a/frontend/src/views/TierEditorView.vue +++ b/frontend/src/views/TierEditorView.vue @@ -7,6 +7,7 @@ import SvgIcon from '../components/SvgIcon.vue' import addColumnRightIcon from '../assets/icons/add_column_right.svg' import addRowBelowIcon from '../assets/icons/add_row_below.svg' import addPhotoAlternateIcon from '../assets/icons/add_photo_alternate.svg' +import shareIcon from '../assets/icons/share.svg' import { api } from '../lib/api' import { toApiUrl } from '../lib/runtime' import { useAuthStore } from '../stores/auth' @@ -130,6 +131,12 @@ const canRequestTemplateUpdate = computed( const canSubmitTemplateCreateRequest = computed(() => !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim()) const canSubmitTemplateUpdateRequest = computed(() => !!templateRequestDraftTitle.value.trim() && !!templateRequestDraftDescription.value.trim()) const templateRequestTargetLabel = computed(() => (gameId.value === 'freeform' ? '새로운 템플릿' : (gameName.value || gameId.value || '선택한 게임'))) +const shareTierListUrl = computed(() => { + const savedTierListId = persistedTierListId.value || (tierListId.value && tierListId.value !== 'new' ? tierListId.value : '') + if (!savedTierListId) return '' + if (typeof window === 'undefined') return `/editor/${gameId.value}/${savedTierListId}?preview=1` + return new URL(`/editor/${gameId.value}/${savedTierListId}?preview=1`, window.location.origin).toString() +}) watch(error, (message) => { if (!message) return @@ -712,6 +719,32 @@ async function save() { } } +async function copyShareUrl() { + if (!shareTierListUrl.value) { + toast.error('먼저 티어표를 저장한 뒤 공유할 수 있어요.') + return + } + + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(shareTierListUrl.value) + } else { + const helper = document.createElement('textarea') + helper.value = shareTierListUrl.value + helper.setAttribute('readonly', '') + helper.style.position = 'absolute' + helper.style.left = '-9999px' + document.body.appendChild(helper) + helper.select() + document.execCommand('copy') + helper.remove() + } + toast.success('공유 링크를 클립보드에 복사했어요.') + } catch (e) { + toast.error('공유 링크를 복사하지 못했어요.') + } +} + function closeSaveModal() { isSaveModalOpen.value = false } @@ -1324,6 +1357,10 @@ onUnmounted(() => {