상단 복귀 링크를 이전 페이지 기준으로 정리
This commit is contained in:
@@ -25,6 +25,7 @@ Home Assistant에 기록된 Galaxy Z Fold7의 위치, 배터리, 활동 상태,
|
||||
- 강제 위치/센서 업데이트 요청
|
||||
- 한국어/영어 전환
|
||||
- Wake Lock 지원
|
||||
- 상단 복귀 링크는 이전 페이지가 있으면 해당 페이지 제목과 URL을 사용하고, 직접 접속이면 `https://chaegeon.com/blog`로 이동합니다.
|
||||
|
||||
## 구조
|
||||
|
||||
@@ -41,7 +42,7 @@ Home Assistant에 기록된 Galaxy Z Fold7의 위치, 배터리, 활동 상태,
|
||||
|
||||
표시할 모바일 앱 센서 목록은 `/custom/common/config.php`의 `findmydevice_sensor_sections`에서 섹션 단위로 관리합니다. 화면 CSS 원본은 `/custom/common/css/findmydevice.css`를 사용합니다. 기존 운영 HTML이 `/custom/findmydevice/css/style.css`를 참조해도 호환 파일이 공용 CSS를 import합니다.
|
||||
|
||||
페이지 본문에는 `<div id="findmydevice"></div>`만 두면 됩니다. `main.js`는 이 컨테이너 안에 지도, 헤더, 설정/기록/위치/업데이트/배터리/활동 섹션과 iOS PWA 안내 영역을 항상 자동 생성합니다.
|
||||
페이지 본문에는 `<div id="findmydevice"></div>`만 두면 됩니다. `main.js`는 이 컨테이너 안에 지도, 헤더, 설정/기록/위치/업데이트/배터리/활동 섹션과 iOS PWA 안내 영역을 항상 자동 생성합니다. 상단 복귀 링크는 같은 출처의 이전 페이지라면 문서 제목을 읽어 표시하고, 다른 `chaegeon.com` 하위 도메인이나 직접 접속에서는 경로 이름과 기본 블로그 주소를 사용합니다.
|
||||
|
||||
위치 갱신 명령은 `/custom/common/config.php`의 `findmydevice_location_refresh` 설정을 사용합니다. `seoul`, `main` Home Assistant 프로필의 `notify.mobile_app_seocaegeonyi_z_fold7` 서비스로 Companion App notification command를 보냅니다. 자동 업데이트가 켜지면 Android high accuracy mode를 5초 간격으로 설정하고 `force_on`을 보낸 뒤 `request_location_update`와 `command_update_sensors`를 함께 호출합니다. 자동 업데이트 중에는 10초마다 위치 요청과 센서 업데이트 요청을 반복하고, 자동 업데이트를 끄거나 페이지를 벗어나면 `force_off`를 보냅니다.
|
||||
|
||||
|
||||
+85
-22
@@ -459,6 +459,83 @@ let langCode = localStorage.getItem("currentLang") || deviceLang,
|
||||
_overrideRange,
|
||||
wakeLock = null;
|
||||
|
||||
const FALLBACK_BACK_TARGET = 'https://chaegeon.com/blog';
|
||||
const BACK_LINK_PATH_TITLE = {
|
||||
'/': '채건닷컴',
|
||||
'/home': '홈',
|
||||
'/blog': '블로그',
|
||||
};
|
||||
|
||||
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() || '');
|
||||
}
|
||||
|
||||
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 title = 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');
|
||||
title = cleanPageTitle(doc.querySelector('title')?.textContent) || title;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return { href: target, label: title || '블로그' };
|
||||
}
|
||||
|
||||
function updateBackLinkLabel(label) {
|
||||
const blogBackLink = document.getElementById('blogBackLink');
|
||||
if (!blogBackLink) return;
|
||||
blogBackLink.innerHTML = `<span class="backArrow">‹</span> ${label}`;
|
||||
}
|
||||
|
||||
function setupBackLink() {
|
||||
const blogBackLink = document.getElementById('blogBackLink');
|
||||
if (!blogBackLink) return;
|
||||
|
||||
blogBackLink.href = FALLBACK_BACK_TARGET;
|
||||
blogBackLink.dataset.backLabel = '블로그';
|
||||
updateBackLinkLabel(blogBackLink.dataset.backLabel);
|
||||
|
||||
resolveBackTarget().then(({ href, label }) => {
|
||||
blogBackLink.href = href;
|
||||
blogBackLink.dataset.backLabel = label;
|
||||
updateBackLinkLabel(label);
|
||||
});
|
||||
|
||||
blogBackLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
location.href = blogBackLink.href || FALLBACK_BACK_TARGET;
|
||||
});
|
||||
}
|
||||
|
||||
function ensureFindMyDeviceShell() {
|
||||
let mount = document.getElementById('findmydevice');
|
||||
if (!mount) {
|
||||
@@ -535,15 +612,8 @@ ensureFindMyDeviceShell();
|
||||
applyLanguage(langCode);
|
||||
|
||||
document.querySelectorAll('.locateRow').forEach(row => {
|
||||
row.addEventListener('pointerdown', () => {
|
||||
row.style.background = '#d3d3d3';
|
||||
});
|
||||
row.addEventListener('pointerup', () => {
|
||||
row.style.background = '#fff';
|
||||
});
|
||||
row.addEventListener('pointerleave', () => {
|
||||
row.style.background = '#fff';
|
||||
});
|
||||
row.setAttribute('role', 'button');
|
||||
row.tabIndex = 0;
|
||||
});
|
||||
|
||||
document.getElementById('langToggle').addEventListener('click', () => {
|
||||
@@ -607,10 +677,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
container.style.webkitTouchCallout = 'none';
|
||||
container.addEventListener('dragstart', e => e.preventDefault());
|
||||
|
||||
document.getElementById('blogBackLink').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
location.href = '/blog';
|
||||
});
|
||||
setupBackLink();
|
||||
|
||||
function renderAuthErrorPage() {
|
||||
document.body.innerHTML = `
|
||||
@@ -971,7 +1038,7 @@ function applyLanguage(langCode) {
|
||||
};
|
||||
|
||||
const blogBackLink = document.getElementById('blogBackLink');
|
||||
if (blogBackLink) blogBackLink.innerHTML = `<span class="backArrow">‹</span> ${t.blog}`;
|
||||
if (blogBackLink) updateBackLinkLabel(blogBackLink.dataset.backLabel || t.blog);
|
||||
const headerTitle = document.querySelector('.headerTitle');
|
||||
if (headerTitle) headerTitle.textContent = t.title;
|
||||
|
||||
@@ -2281,6 +2348,8 @@ function syncLocateBtn() {
|
||||
const newDiv = document.createElement('div');
|
||||
newDiv.className = 'settingRow locateRow';
|
||||
newDiv.id = 'locateBtn';
|
||||
newDiv.setAttribute('role', 'button');
|
||||
newDiv.tabIndex = 0;
|
||||
newDiv.innerHTML = `<span style="color:#007AFF; cursor:default;" id="labelMoveToCurrent">${langCode === 'ko' ? '현재 위치로 이동하기' : 'Move to Current Location'}</span>`;
|
||||
|
||||
newDiv.addEventListener('click', () => {
|
||||
@@ -2288,10 +2357,6 @@ function syncLocateBtn() {
|
||||
mapState.map.panTo(new naver.maps.LatLng(lastLat, lastLng));
|
||||
}
|
||||
});
|
||||
newDiv.addEventListener('pointerdown', () => newDiv.style.background = '#d3d3d3');
|
||||
newDiv.addEventListener('pointerup', () => newDiv.style.background = '#fff');
|
||||
newDiv.addEventListener('pointerleave', () => newDiv.style.background = '#fff');
|
||||
|
||||
parent.parentNode.insertBefore(newDiv, parent.nextSibling);
|
||||
}
|
||||
}
|
||||
@@ -2307,12 +2372,10 @@ function syncUpdateBtn() {
|
||||
const newDiv = document.createElement('div');
|
||||
newDiv.className = 'settingRow locateRow';
|
||||
newDiv.id = 'updateBtn';
|
||||
newDiv.setAttribute('role', 'button');
|
||||
newDiv.tabIndex = 0;
|
||||
newDiv.innerHTML = `<span style="color:#007AFF; cursor:default;" id="labelForceUpdate">${langCode === 'ko' ? '업데이트 하기' : 'Force Update'}</span>`;
|
||||
|
||||
newDiv.addEventListener('pointerdown', () => newDiv.style.background = '#d3d3d3');
|
||||
newDiv.addEventListener('pointerup', () => newDiv.style.background = '#fff');
|
||||
newDiv.addEventListener('pointerleave', () => newDiv.style.background = '#fff');
|
||||
|
||||
newDiv.addEventListener('click', triggerForceUpdate);
|
||||
|
||||
parent.parentNode.insertBefore(newDiv, parent.nextSibling);
|
||||
|
||||
Reference in New Issue
Block a user