상단 복귀 링크를 이전 페이지 기준으로 정리

This commit is contained in:
seo
2026-07-01 00:55:05 +09:00
parent a03c056e88
commit eeef4c008d
2 changed files with 99 additions and 35 deletions
+2 -1
View File
@@ -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
View File
@@ -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);