From dba2c74d9ed22983630476d4c1ded7a1f1358dc1 Mon Sep 17 00:00:00 2001 From: seo Date: Wed, 1 Jul 2026 00:55:28 +0900 Subject: [PATCH] =?UTF-8?q?=EC=B0=A8=EB=9F=89=20=EB=AA=A8=EB=8B=88?= =?UTF-8?q?=ED=84=B0=20=EB=B3=B5=EA=B7=80=20=EB=A7=81=ED=81=AC=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++- monitor.php | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d733336..d0cce2d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저 - 허용된 차량 명령 TCP 전송 - monitor 화면과 AJAX 상태 갱신 - monitor 화면 WakeLock, 언어, 테마 토글 -- monitor 좌측 상단 블로그 복귀 링크 +- monitor 좌측 상단 이전 페이지 복귀 링크 - 모바일 및 좁은 카드 폭에 맞춘 사용량 2줄 배치, 로그 가로 스크롤, 시간 문구 축약 - 사용량/요금 표시와 통신사 과금 단위 보정 metadata - 4.95초 UI timeout과 60초 사용량 정산 분리 @@ -48,6 +48,8 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저 헤더 우측 컨트롤은 WakeLock, 언어, 테마 순서입니다. WakeLock은 Screen Wake Lock API를 지원하는 브라우저에서 화면 꺼짐을 방지하며, 활성 상태는 초록색 아이콘 버튼으로 표시합니다. 사용자의 선택은 `localStorage`에 저장하고, 새로고침이나 탭 복귀 후에는 브라우저 정책에 맞춰 다시 획득을 시도합니다. +좌측 상단 복귀 링크는 이전 페이지가 `chaegeon.com` 계열이면 해당 URL을 사용합니다. 같은 출처에서 이동한 경우에는 이전 문서의 title을 읽어 표시하고, 직접 접속이나 이전 페이지 확인이 어려운 경우에는 `https://chaegeon.com/blog`를 기본 목적지로 사용합니다. `seo.chaegeon.com`의 상대 경로로 이동하지 않도록 절대 URL을 기본값으로 둡니다. + ## 구성 - `api.php`: 차량 상태/제어 API diff --git a/monitor.php b/monitor.php index bf2b59e..85ca4b8 100644 --- a/monitor.php +++ b/monitor.php @@ -956,6 +956,10 @@ car_monitor_require_access(false); line-height: 1; text-decoration: none; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55); + max-width: min(44vw, 260px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .blog-back-link:hover, .blog-back-link:focus { @@ -1368,7 +1372,7 @@ car_monitor_require_access(false);
- 블로그 + 블로그
Car Monitor
WAIT @@ -1822,8 +1826,15 @@ car_monitor_require_access(false); let wakeLockWanted = false; let latestDashboardData = null; let latestDashboardMeta = null; + const FALLBACK_BACK_TARGET = 'https://chaegeon.com/blog'; + const BACK_LINK_PATH_TITLE = { + '/': '채건닷컴', + '/home': '홈', + '/blog': '블로그', + }; document.addEventListener('DOMContentLoaded', () => { + setupBackLink(); initLanguage(); initTheme(); initWakeLock(); @@ -1858,6 +1869,74 @@ car_monitor_require_access(false); return I18N[currentLang]?.[key] || I18N.en[key] || key; } + function cleanPageTitle(title) { + return String(title || '') + .replace(/^채건닷컴\s*-\s*/i, '') + .replace(/\s*-\s*채건닷컴$/i, '') + .trim(); + } + + function sameSiteUrl(url) { + try { + const parsed = new URL(url, location.href); + return parsed.hostname === location.hostname || parsed.hostname.endsWith('.chaegeon.com') || parsed.hostname === 'chaegeon.com' + ? parsed + : null; + } catch (e) { + return null; + } + } + + function titleFromBackUrl(url) { + const parsed = sameSiteUrl(url); + if (!parsed) return ''; + return BACK_LINK_PATH_TITLE[parsed.pathname.replace(/\/$/, '') || '/'] || cleanPageTitle(parsed.pathname.split('/').filter(Boolean).pop() || ''); + } + + function updateBackLinkLabel(label) { + const link = document.getElementById('blogBackLink'); + if (!link) return; + link.innerHTML = ` ${label}`; + } + + async function resolveBackTarget() { + const ref = sameSiteUrl(document.referrer || ''); + const current = new URL(location.href); + const target = ref && ref.href !== current.href ? ref.href : FALLBACK_BACK_TARGET; + let label = titleFromBackUrl(target); + + if (ref && ref.origin === location.origin) { + try { + const res = await fetch(ref.href, { credentials: 'same-origin', cache: 'force-cache' }); + if (res.ok) { + const html = await res.text(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + label = cleanPageTitle(doc.querySelector('title')?.textContent) || label; + } + } catch (e) {} + } + + return { href: target, label: label || '블로그' }; + } + + function setupBackLink() { + const link = document.getElementById('blogBackLink'); + if (!link) return; + + link.href = FALLBACK_BACK_TARGET; + updateBackLinkLabel('블로그'); + + resolveBackTarget().then(({ href, label }) => { + link.href = href; + updateBackLinkLabel(label); + }); + + link.addEventListener('click', (event) => { + event.preventDefault(); + location.href = link.href || FALLBACK_BACK_TARGET; + }); + } + function applyLanguage() { document.documentElement.setAttribute('lang', currentLang); document.title = t('appTitle');