차량 모니터 복귀 링크 경로 정리
This commit is contained in:
@@ -15,7 +15,7 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저
|
|||||||
- 허용된 차량 명령 TCP 전송
|
- 허용된 차량 명령 TCP 전송
|
||||||
- monitor 화면과 AJAX 상태 갱신
|
- monitor 화면과 AJAX 상태 갱신
|
||||||
- monitor 화면 WakeLock, 언어, 테마 토글
|
- monitor 화면 WakeLock, 언어, 테마 토글
|
||||||
- monitor 좌측 상단 블로그 복귀 링크
|
- monitor 좌측 상단 이전 페이지 복귀 링크
|
||||||
- 모바일 및 좁은 카드 폭에 맞춘 사용량 2줄 배치, 로그 가로 스크롤, 시간 문구 축약
|
- 모바일 및 좁은 카드 폭에 맞춘 사용량 2줄 배치, 로그 가로 스크롤, 시간 문구 축약
|
||||||
- 사용량/요금 표시와 통신사 과금 단위 보정 metadata
|
- 사용량/요금 표시와 통신사 과금 단위 보정 metadata
|
||||||
- 4.95초 UI timeout과 60초 사용량 정산 분리
|
- 4.95초 UI timeout과 60초 사용량 정산 분리
|
||||||
@@ -48,6 +48,8 @@ Car는 차량 모뎀/게이트웨이에서 받은 상태를 수집해 DB에 저
|
|||||||
|
|
||||||
헤더 우측 컨트롤은 WakeLock, 언어, 테마 순서입니다. WakeLock은 Screen Wake Lock API를 지원하는 브라우저에서 화면 꺼짐을 방지하며, 활성 상태는 초록색 아이콘 버튼으로 표시합니다. 사용자의 선택은 `localStorage`에 저장하고, 새로고침이나 탭 복귀 후에는 브라우저 정책에 맞춰 다시 획득을 시도합니다.
|
헤더 우측 컨트롤은 WakeLock, 언어, 테마 순서입니다. WakeLock은 Screen Wake Lock API를 지원하는 브라우저에서 화면 꺼짐을 방지하며, 활성 상태는 초록색 아이콘 버튼으로 표시합니다. 사용자의 선택은 `localStorage`에 저장하고, 새로고침이나 탭 복귀 후에는 브라우저 정책에 맞춰 다시 획득을 시도합니다.
|
||||||
|
|
||||||
|
좌측 상단 복귀 링크는 이전 페이지가 `chaegeon.com` 계열이면 해당 URL을 사용합니다. 같은 출처에서 이동한 경우에는 이전 문서의 title을 읽어 표시하고, 직접 접속이나 이전 페이지 확인이 어려운 경우에는 `https://chaegeon.com/blog`를 기본 목적지로 사용합니다. `seo.chaegeon.com`의 상대 경로로 이동하지 않도록 절대 URL을 기본값으로 둡니다.
|
||||||
|
|
||||||
## 구성
|
## 구성
|
||||||
|
|
||||||
- `api.php`: 차량 상태/제어 API
|
- `api.php`: 차량 상태/제어 API
|
||||||
|
|||||||
+80
-1
@@ -956,6 +956,10 @@ car_monitor_require_access(false);
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.55);
|
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:hover,
|
||||||
.blog-back-link:focus {
|
.blog-back-link:focus {
|
||||||
@@ -1368,7 +1372,7 @@ car_monitor_require_access(false);
|
|||||||
<i class="fas fa-car"></i>
|
<i class="fas fa-car"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="/blog" class="blog-back-link"><span class="backArrow">‹</span> 블로그</a>
|
<a href="https://chaegeon.com/blog" id="blogBackLink" class="blog-back-link"><span class="backArrow">‹</span> 블로그</a>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<div class="app-title" data-i18n="appTitle">Car Monitor</div>
|
<div class="app-title" data-i18n="appTitle">Car Monitor</div>
|
||||||
<span id="live-badge" class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-20 rounded-pill" style="font-size:0.6rem; vertical-align: top;">WAIT</span>
|
<span id="live-badge" class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-20 rounded-pill" style="font-size:0.6rem; vertical-align: top;">WAIT</span>
|
||||||
@@ -1822,8 +1826,15 @@ car_monitor_require_access(false);
|
|||||||
let wakeLockWanted = false;
|
let wakeLockWanted = false;
|
||||||
let latestDashboardData = null;
|
let latestDashboardData = null;
|
||||||
let latestDashboardMeta = null;
|
let latestDashboardMeta = null;
|
||||||
|
const FALLBACK_BACK_TARGET = 'https://chaegeon.com/blog';
|
||||||
|
const BACK_LINK_PATH_TITLE = {
|
||||||
|
'/': '채건닷컴',
|
||||||
|
'/home': '홈',
|
||||||
|
'/blog': '블로그',
|
||||||
|
};
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
setupBackLink();
|
||||||
initLanguage();
|
initLanguage();
|
||||||
initTheme();
|
initTheme();
|
||||||
initWakeLock();
|
initWakeLock();
|
||||||
@@ -1858,6 +1869,74 @@ car_monitor_require_access(false);
|
|||||||
return I18N[currentLang]?.[key] || I18N.en[key] || key;
|
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 = `<span class="backArrow">‹</span> ${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() {
|
function applyLanguage() {
|
||||||
document.documentElement.setAttribute('lang', currentLang);
|
document.documentElement.setAttribute('lang', currentLang);
|
||||||
document.title = t('appTitle');
|
document.title = t('appTitle');
|
||||||
|
|||||||
Reference in New Issue
Block a user