Files
jrshikoku/lib/webViewInjectjavascript.ts

1211 lines
52 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { INTERVALS, API_ENDPOINTS } from '@/constants';
import { TRAIN_TYPE_CONFIG } from './webview/trainTypeConfig';
import { TRAIN_ICON_MAP, TRAIN_ICON_REGEX } from './webview/trainIconMap';
import { STATION_DATA } from './webview/stationData';
export interface InjectJavascriptOptions {
/** 地図スイッチ ("true" | "false") */
mapSwitch: string;
/** 列車アイコン表示スイッチ ("true" | "false") */
iconSetting: string;
/** 駅メニュースイッチ ("true" | "false") */
stationMenu: string;
/** 列車メニュースイッチ ("true" | "false") */
trainMenu: string;
/** UIデザイン設定 ("tokyo" | ...) */
uiSetting: string;
/** 運用hub使用スイッチ ("true" | "false") */
useUnyohub: string;
}
export const injectJavascriptData = ({
mapSwitch,
iconSetting,
stationMenu,
trainMenu,
uiSetting,
useUnyohub,
}: InjectJavascriptOptions): string => {
// 一番上のメニュー非表示 地図スイッチによって切り替え
const topMenu =
mapSwitch != "true"
? `
document.querySelector('#header a').style.display = 'none';
document.querySelector('#main').style.left = '0px';
document.querySelector('#header').style.height = '50px';
document.querySelector('#main').style.paddingTop = '54px';
document.querySelector('#headerStr').style.display = 'none';
`
: `
document.querySelector('.accordionClass').style.display = 'none';
document.querySelector('#header').style.display = 'none';
document.querySelector('#main').style.left = '0px';
document.querySelector('#main').style.paddingTop = '0px';
document.querySelector('#headerStr').style.display = 'none';
`;
// 上部ヘッダーの取り扱い、自動再読み込み、setStringsの実行
const bootData = `
// 停止中 点滅アニメーション CSS を動的注入
(function(){
const s = document.createElement('style');
s.textContent = '@keyframes _jrs_blink{0%,55%{opacity:1}70%,90%{opacity:0}100%{opacity:1}}' +
'@keyframes _jrs_glow{0%,55%{box-shadow:0 0 12px 3px rgba(255,0,0,0.9)}70%,90%{box-shadow:0 0 6px 2px rgba(255,0,0,0.45)}100%{box-shadow:0 0 12px 3px rgba(255,0,0,0.9)}}';
document.head.appendChild(s);
})();
// Font Awesome は FA6/7 ともに CSS Custom Properties (content:var(--fa)) 依存で
// 古い WebView (Chrome 49未満) では表示されないため、インライン SVG に置き換え済み。
// CDN依存・外部リソースロードも不要になった。
// 軽量な変更検出ユーティリティ (lodash_.isEqual の代替)
const _hashes = {};
const hasChanged = (key, newVal) => {
const h = JSON.stringify(newVal);
if (_hashes[key] === h) return false;
_hashes[key] = h;
return true;
};
// データ変数の宣言
let stationList = {};
let trainDataList = [];
let operationList = [];
let trainDiagramData2 = {};
let probremsData = [];
let unyohubData = [];
const useUnyohubSetting = ${useUnyohub === "true"};
// 列車種別設定 (typeColor / borderColor / bgColor / label / isWanman)
const TRAIN_TYPE_CFG = ${JSON.stringify(TRAIN_TYPE_CONFIG)};
// operationList から列番が一致する運行情報を返すヘルパー
const getOperationsByTrainId = (trainId) => {
return operationList.filter(e => {
const ids = [...(e.train_ids||[]),...(e.related_train_ids||[])].map(x=>x.split(",")[0]);
return ids.includes(String(trainId));
});
};
// --- localStorage キャッシュヘルパー(起動直後の即時表示用)---
// データ種別ごとの有効期限(ミリ秒)
const CACHE_TTL = {
stationList: 60 * 60 * 1000, // 1時間駅情報はほぼ変わらない
operationList: 2 * 60 * 1000, // 2分
probremsData: 2 * 60 * 1000, // 2分
trainDataList: 2 * 60 * 1000, // 2分
trainDiagramData2: 10 * 60 * 1000, // 10分ダイアグラムは変化少ない
unyohubData: 10 * 60 * 1000, // 10分
};
const _wcache = {
get(key) {
try {
const raw = localStorage.getItem('_jrs_' + key);
if (!raw) return null;
const { data, ts, ttl } = JSON.parse(raw);
if (Date.now() - ts > ttl) return null; // 期限切れ
return data;
} catch(e) { return null; }
},
set(key, data) {
try {
localStorage.setItem('_jrs_' + key, JSON.stringify({
data, ts: Date.now(), ttl: CACHE_TTL[key] ?? 60000
}));
} catch(e) {} // QuotaExceededError などを無視
}
};
const setReload = () => {
try {
document.getElementById('refreshIcon').click();
setStrings();
} catch(e) {}
};
// ポーリング処理 (Phase 3 以降)
const startPolling = () => {
const DatalistUpdate = () => {
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json())
.then(data => {
if (hasChanged('trainDataList', data.data)) {
trainDataList = data.data;
_wcache.set('trainDataList', trainDataList);
setReload();
}
})
.catch(() => {})
.finally(() => { setTimeout(DatalistUpdate, ${INTERVALS.DELAY_UPDATE}); });
};
DatalistUpdate();
const operationListUpdate = () => {
fetch("${API_ENDPOINTS.OPERATION_LOGS}").then(r => r.json())
.then(data => {
if (data?.data === null) return;
const filtered = (data.data || []).filter(d => d.state !== 100);
if (hasChanged('operationList', filtered)) {
operationList = filtered;
_wcache.set('operationList', filtered);
setReload();
}
})
.catch(() => {})
.finally(() => { setTimeout(operationListUpdate, ${INTERVALS.DELAY_UPDATE}); });
};
operationListUpdate();
const TrainDiagramData2Update = () => {
fetch("${API_ENDPOINTS.DIAGRAM_TODAY}").then(r => r.json())
.then(res => {
const data = {};
res.forEach(d => { const keys = Object.keys(d); data[keys] = d[keys]; });
if (hasChanged('trainDiagramData2', data)) {
trainDiagramData2 = data;
_wcache.set('trainDiagramData2', data);
setReload();
}
})
.catch(() => {})
.finally(() => { setTimeout(TrainDiagramData2Update, ${INTERVALS.DELAY_UPDATE}); });
};
TrainDiagramData2Update();
const getProblemsData = () => {
fetch("${API_ENDPOINTS.POSITION_PROBLEMS}").then(r => r.json())
.then(data => {
if (hasChanged('probremsData', data?.data)) {
probremsData = data.data;
_wcache.set('probremsData', probremsData);
setReload();
}
})
.catch(() => {})
.finally(() => { setTimeout(getProblemsData, ${INTERVALS.FETCH_DIAGRAM}); });
};
getProblemsData();
// unyohub は Phase 3 で遅延スタート (他の読み込みを邪魔しない)
const unyohubDataUpdate = () => {
if (!useUnyohubSetting) {
setTimeout(unyohubDataUpdate, ${INTERVALS.DELAY_UPDATE});
return;
}
fetch("${API_ENDPOINTS.UNYOHUB_DATA}" + '?_=' + Date.now())
.then(r => r.json())
.then(data => {
if (hasChanged('unyohubData', data)) {
unyohubData = data;
_wcache.set('unyohubData', data);
console.log('[UnyoHub] Data updated:', unyohubData.length, 'items');
setReload();
}
})
.catch(e => console.error('[UnyoHub] Fetch error:', e))
.finally(() => { setTimeout(unyohubDataUpdate, ${INTERVALS.DELAY_UPDATE}); });
};
unyohubDataUpdate();
// バックグラウンド復帰時に全データを即時再取得iOS WKWebView 対応)
const refreshAllData = () => {
// ハッシュをクリアして強制的に再描画させる
Object.keys(_hashes).forEach(k => { _hashes[k] = null; });
Promise.allSettled([
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => {
trainDataList = data.data ?? trainDataList;
_hashes['trainDataList'] = JSON.stringify(trainDataList);
}).catch(() => {}),
fetch("${API_ENDPOINTS.OPERATION_LOGS}").then(r => r.json()).then(data => {
if (data?.data != null) {
operationList = (data.data || []).filter(d => d.state !== 100);
_hashes['operationList'] = JSON.stringify(operationList);
}
}).catch(() => {}),
fetch("${API_ENDPOINTS.POSITION_PROBLEMS}").then(r => r.json()).then(data => {
probremsData = data?.data ?? probremsData;
_hashes['probremsData'] = JSON.stringify(probremsData);
}).catch(() => {}),
]).then(() => setReload());
};
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
refreshAllData();
}
});
// iOS Safari/WKWebView は pageshow でバックフォワードキャッシュ復帰も検出
window.addEventListener('pageshow', (e) => {
if (e.persisted) refreshAllData();
});
};
// === Phase 0: localStorageキャッシュから即時ロード ===
// キャッシュヒット時は即座に描画することで、ネットワーク待ちの白画面を解消する
{
let anyHit = false;
const _c0 = {
stationList: _wcache.get('stationList'),
operationList: _wcache.get('operationList'),
probremsData: _wcache.get('probremsData'),
trainDataList: _wcache.get('trainDataList'),
trainDiagramData2: _wcache.get('trainDiagramData2'),
unyohubData: _wcache.get('unyohubData'),
};
if (_c0.stationList) { stationList = _c0.stationList; anyHit = true; }
if (_c0.operationList) { operationList = _c0.operationList; _hashes['operationList'] = JSON.stringify(_c0.operationList); anyHit = true; }
if (_c0.probremsData) { probremsData = _c0.probremsData; _hashes['probremsData'] = JSON.stringify(_c0.probremsData); anyHit = true; }
if (_c0.trainDataList) { trainDataList = _c0.trainDataList; _hashes['trainDataList'] = JSON.stringify(_c0.trainDataList); anyHit = true; }
if (_c0.trainDiagramData2) { trainDiagramData2 = _c0.trainDiagramData2; _hashes['trainDiagramData2'] = JSON.stringify(_c0.trainDiagramData2); anyHit = true; }
if (_c0.unyohubData) { unyohubData = _c0.unyohubData; anyHit = true; }
// setTimeout で defer することで、スクリプト全体の実行が完了し
// setStrings (const) が TDZ を抜けた後に呼び出す。
// Phase 0 の setReload() はここまで const の TDZ により無効だった。
if (anyHit) setTimeout(() => setReload(), 0);
}
// === Phase 1: 軽量API (合計~210ms) を並列取得 → setReload 1回 ===
// OPERATION_LOGS: 0.07s/3KB, STATION_LIST: 0.18s/80KB, POSITION_PROBLEMS: 0.21s/11B
Promise.allSettled([
fetch("${API_ENDPOINTS.STATION_LIST}").then(r => r.json()).then(data => {
stationList = data;
_wcache.set('stationList', data);
}).catch(() => {}),
fetch("${API_ENDPOINTS.OPERATION_LOGS}").then(r => r.json()).then(data => {
if (data?.data != null) {
const filtered = (data.data || []).filter(d => d.state !== 100);
operationList = filtered;
_hashes['operationList'] = JSON.stringify(filtered);
_wcache.set('operationList', filtered);
}
}).catch(() => {}),
fetch("${API_ENDPOINTS.POSITION_PROBLEMS}").then(r => r.json()).then(data => {
probremsData = data?.data ?? [];
_hashes['probremsData'] = JSON.stringify(probremsData);
_wcache.set('probremsData', probremsData);
}).catch(() => {}),
]).then(() => {
setReload(); // Phase 1 完了: 1回目の描画更新
// === Phase 2: 重いAPI (合計~840ms) を並列取得 → setReload 1回 ===
// TRAIN_DATA_API: 0.84s/1MB, DIAGRAM_TODAY: 0.27s/522KB
Promise.allSettled([
fetch("${API_ENDPOINTS.TRAIN_DATA_API}").then(r => r.json()).then(data => {
trainDataList = data.data ?? [];
_hashes['trainDataList'] = JSON.stringify(trainDataList);
_wcache.set('trainDataList', trainDataList);
}).catch(() => {}),
fetch("${API_ENDPOINTS.DIAGRAM_TODAY}").then(r => r.json()).then(res => {
const data = {};
res.forEach(d => { const keys = Object.keys(d); data[keys] = d[keys]; });
trainDiagramData2 = data;
_hashes['trainDiagramData2'] = JSON.stringify(data);
_wcache.set('trainDiagramData2', data);
}).catch(() => {}),
]).then(() => {
setReload(); // Phase 2 完了: 2回目の描画更新
startPolling(); // Phase 3: ポーリング開始
});
});
const getUnyohubFormation = (trainNumber) => {
if (!${useUnyohub === "true"} || !unyohubData || unyohubData.length === 0) {
return null;
}
const foundUnyos = [];
for (const unyo of unyohubData) {
if (!unyo.trains) {
continue;
}
const found = unyo.trains.find(train => train.train_number === trainNumber);
if (found) {
foundUnyos.push({
formations: unyo.formations,
position_forward: found.position_forward,
position_rear: found.position_rear,
});
}
}
if (foundUnyos.length === 0) return null;
// position_forward順にソート
foundUnyos.sort((a, b) => a.position_forward - b.position_forward);
const result = foundUnyos.map(u => u.formations + '(' + u.position_forward + '-' + u.position_rear + ')').join(', ');
return result;
};
const sortOperationalList = (a, b,targetTrainID) => {
// trainIdからカンマ以降の数字を抽出する関数
const extractOrderNumber = (trainId) => {
const parts = trainId.split(',');
if (parts.length > 1) {
const num = parseInt(parts[1].trim(), 10);
return isNaN(num) ? Infinity : num;
}
return Infinity; // カンマなし = 末尾に移動
};
// data.trainNumと一致するtrainIdを探す関数
const findMatchingTrainId = (operation)=> {
const allTrainIds = [
...(operation.train_ids || []),
...(operation.related_train_ids || []),
];
// data.trainNumの接頭辞と一致するものを探す
for (const trainId of allTrainIds) {
const prefix = trainId.split(',')[0]; // カンマ前の部分
if (prefix === targetTrainID) {
return trainId;
}
}
return null;
};
const aTrainId = findMatchingTrainId(a);
const bTrainId = findMatchingTrainId(b);
// マッチしたものがない場合は元の順序を保持
if (!aTrainId || !bTrainId) {
return aTrainId ? -1 : bTrainId ? 1 : 0;
}
const aOrder = extractOrderNumber(aTrainId);
const bOrder = extractOrderNumber(bTrainId);
return aOrder - bOrder;
};
`;
// 左か右かを判定してアイコンを設置する
const trainIcon = `
const setStationIcon = (setIconElem,img,hasProblem,backCount = 100) =>{
const position = setIconElem.getAttribute("style").includes("left");
let marginData = ${uiSetting === "tokyo" ? `"5px"`: `"2px"`};
let backgroundColor = "transparent";
let heightData = "22px";
if(backCount == 0){
marginData = position ? ${uiSetting === "tokyo" ? `"0px 0px -10px 0px" : "-10px 0px 0px 0px"`: `"0px 2px 0px 0px" : "0px 2px 0px 0px"`};
heightData = "16px";
}
setIconElem.insertAdjacentHTML('beforebegin', "<img src="+img+" style='float:"+(position ? 'left' : 'right')+";height:"+heightData+";margin: "+marginData+";background-color: "+backgroundColor+";'>");
if (backCount == 0 || backCount == 100) setIconElem.remove();
}
const _TIM = ${JSON.stringify(TRAIN_ICON_MAP)};
const _TIR = ${JSON.stringify(TRAIN_ICON_REGEX)};
const _AP = "https://n8n.haruk.in/webhook/anpanman-pictures.png?trainNum=";
const setTrainIcon = (列番データ) => {
const _u = _TIM[列番データ];
if (_u != null) return _u === "__anpanman__" ? _AP + 列番データ : _u;
for (const {pattern, url} of _TIR) {
if (new RegExp(pattern).test(列番データ)) return url;
}
};
`;
const normal_train_name = `
const nameReplace = (列車名データ,列番データ,行き先情報,hasProblem,isLeft) =>{
let isWanman = false;
let trainName = "";
let trainType = "";
let trainTypeColor = "black";
let viaData = "";
let ToData = "";
let TrainNumber = 列番データ;
let isEdit = false;
let isSeason = false;
let TrainNumberOverride;
let optionalText = "";
try{
const diagram = trainDiagramData2[列番データ] || trainTimeInfo[列番データ];
if(diagram){
const diagramData = diagram.split("#");
ToData = diagramData[diagramData.length - 2].split(",")[0];
}
}catch(e){}
if(列車名データ.split(":")[1]){
const textBase = 列車名データ.split(":")[1].replace("\\r","");
trainName = textBase;
}
if(列車名データ.match("サンポート")){
const textBase = 列車名データ.split(":")[1].replace("\\r","");
trainName = textBase;
}
if(new RegExp(/^4[1-9]\\d\\d[DM]$/).test(列番データ) || new RegExp(/^5[1-7]\\d\\d[DM]$/).test(列番データ) || new RegExp(/^3[2-9]\\d\\d[DM]$/).test(TrainNumber) ){
flag=true;
isWanman = true;
}
if(new RegExp(/^49[0-4]\\dD$/).test(列番データ) || new RegExp(/^9[0-4]\\dD$/).test(列番データ)){
viaData = "(海経由)";
}
if(new RegExp(/^46\\d\\dD$/).test(列番データ) || new RegExp(/^6\\d\\dD$/).test(列番データ)){
viaData = "(内子経由)";
}
const getThrew = num =>{
switch(num){
//牟岐線直通列車情報
//徳島線発牟岐線行き
case "468D":
case "478D":
case "484D":
viaData = "牟岐線直通";
ToData = "牟岐";
break;
case "4430D":
case "4472D":
viaData = "牟岐線直通";
isWanman = true;
ToData = "牟岐";
break;
case "434D":
case "474D":
case "476D":
case "480D":
viaData = "牟岐線直通";
ToData = "阿南";
break;
case "4452D":
case "4466D":
case "4470D":
viaData = "牟岐線直通";
isWanman = true;
ToData = "阿南";
break;
case "4456D":
viaData = "牟岐線直通";
isWanman = true;
ToData = "阿波海南"
break;
//鳴門線発牟岐線行き
case "951D":
viaData = "牟岐線直通";
ToData = "桑野";
break;
//牟岐線発高徳線行き
case "358D":
viaData = "高徳線直通";
break;
case "4314D":
case "4326D":
case "4334D":
case "4342D":
case "4350D":
case "4368D":
viaData = "高徳線直通";
isWanman = true;
break;
//牟岐線発徳島線行き
case "451D":
case "475D":
viaData = "徳島線直通";
break;
case "4447D":
case "4455D":
case "5467D":
case "5471D":
case "5479D":
viaData = "徳島線直通";
isWanman = true;
break;
//牟岐線発鳴門線行き
case "952D":
viaData = "鳴門線直通";
break;
case "4954D":
case "4978D":
viaData = "鳴門線直通";
isWanman = true;
break;
//安芸行と併結列車を個別に表示、それ以外をdefaultで下りなら既定の行き先を、上りなら奈半利行を設定
case "5814D":
case "5816D":
viaData = "ごめん・なはり線[快速]";
ToData = "奈半利";
break;
case "5812D":
viaData = "ごめん・なはり線[快速]";
ToData = "安芸";
break;
case "5874D":
case "5882D":
viaData = "ごめん・なはり線[各停]";
ToData = "安芸";
break;
case "248D":
case "250D":
viaData = "ごめん・なはり線[快速]";
ToData = "(後免にて解結)\\n土佐山田/奈半利";
break;
default:
if(new RegExp(/^58[1-3][1,3,5,7,9][DM]$/).test(列番データ)){
viaData = "ごめん・なはり線[快速]";
break;
}
else if(new RegExp(/^58[4-9][1,3,5,7,9][DM]$/).test(列番データ)){
viaData = "ごめん・なはり線[各停]";
break;
}
else if(new RegExp(/^58[3-4][0,2,4,6,8][DM]$/).test(列番データ)){
viaData = "ごめん・なはり線[快速]";
ToData = "奈半利";
break;
}
else if(new RegExp(/^58[5-9][0,2,4,6,8][DM]$/).test(列番データ)){
viaData = "ごめん・なはり線[各停]";
ToData = "奈半利";
break;
}
}
}
getThrew(列番データ);
if(trainDataList.find(e => e.train_id === 列番データ) !== undefined){
const data = trainDataList.find(e => e.train_id === 列番データ);
const _tCfg = TRAIN_TYPE_CFG[data.type];
if (_tCfg) {
trainTypeColor = _tCfg.typeColor;
isWanman = _tCfg.isWanman;
trainType = _tCfg.label;
}
isEdit = data.priority == 400;
isSeason = data.priority == 300;
if (getOperationsByTrainId(data.train_id).length > 0) isEdit = true;
if(data.train_name != ""){
trainName = data.train_name;
if(data.train_num_distance != ""){
trainName += (parseInt(列番データ.replace("M", "").replace("D", "")) - parseInt(data.train_num_distance))+"号";
}
}
if(data.via_data != ""){
viaData = data.via_data;
}
if(data.to_data != ""){
ToData = data.to_data;
}
if(data.train_number_override){
TrainNumberOverride = data.train_number_override;
}
if(data.optional_text){
optionalText = data.optional_text;
}
}
//列番付与
const returnText1 = (isWanman ? "ワンマン " : "") + trainName + viaData;
行き先情報.innerText = "";
${uiSetting === "tokyo" ? `
let stationIDs = [];
let stationLines = [];
Object.keys(stationList).forEach((key) => {
const data = stationList[key].find(e => e.Station_JP === ToData )?.StationNumber;
if(data){
stationIDs.push(data);
stationLines.push(key);
}
});
let getColors = [];
// to_data_colorが配列で値があればそれを使用
if(trainDataList.find(e => e.train_id === 列番データ) !== undefined){
const data = trainDataList.find(e => e.train_id === 列番データ);
if(data.to_data_color && Array.isArray(data.to_data_color) && data.to_data_color.length > 0){
getColors = data.to_data_color;
}
}
// getColorsが空の場合は既存の路線色を使用駅番号の最初の1文字で色を取得
if(getColors.length === 0){
if(stationLines.length === 0){
getColors = ["rgba(97, 96, 96, 0.81)"];
}else{
getColors = stationLines.map(e => GetLineBarColor(e));
}
}
let yosan2Color = undefined;
switch(viaData){
case "(内子経由)":
yosan2Color = "#F5AC13";
break;
case "(海経由)":
yosan2Color = "#9AA7D7";
break;
case "牟岐線直通":
yosan2Color = "#00b8bb";
break;
case "徳島線直通":
yosan2Color = "#2d506e";
break;
case "高徳線直通":
yosan2Color = "#87CA3B";
break;
case "鳴門線直通":
yosan2Color = "#881F61";
break;
case "予土線":
yosan2Color = "#008a5a";
break;
default:
break;
}
// 複数色に対応したグラデーション生成
let gradient;
if(getColors.length > 1){
const colorStops = [];
const step = 100 / getColors.length;
getColors.forEach((color, index) => {
const start = step * index;
const end = step * (index + 1);
colorStops.push(color + " " + start + "%");
colorStops.push(color + " " + end + "%");
});
gradient = "linear-gradient(130deg, " + colorStops.join(", ") + ")";
}else{
gradient = getColors[0];
}
const optionalTextColor = optionalText.includes("最終") ? "red" : "black";
const unyohubFormation = getUnyohubFormation(列番データ);
const hasUnyohub = unyohubFormation !== null;
if(hasUnyohub) {
console.log('[UnyoHub] Badge shown for', 列番データ, ':', unyohubFormation);
}
// バッジHTMLを構築複数のバッジを上下に配置
let badgeHtml = "";
const badgePosition = isLeft ? "right" : "left";
const badgeVerticalPos = isLeft ? "bottom" : "top";
// コミュニティバッジ(青)- fa-user-group をインライン SVG で代替 (全 WebView 対応)
if(isEdit) {
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + badgeVerticalPos + ":0;background-color:#00b8bb;border-radius:15px;padding:2px 4px;display:flex;align-items:center;justify-content:center;'><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 512' width='12' height='12' fill='white'><path d='M96 128a128 128 0 1 1 256 0A128 128 0 1 1 96 128zM0 482.3C0 383.8 79.8 304 178.3 304l91.4 0C368.2 304 448 383.8 448 482.3c0 16.4-13.3 29.7-29.7 29.7L29.7 512C13.3 512 0 498.7 0 482.3zM609.3 512l-137.8 0c5.4-9.4 8.6-20.3 8.6-32l0-8c0-60.7-27.1-115.2-69.8-151.8c2.4-.1 4.7-.2 7.1-.2l61.4 0C567.8 320 640 392.2 640 481.3c0 17-13.8 30.7-30.7 30.7zM432 256c-31 0-59-12.6-79.3-32.9C372.4 196.5 384 163.6 384 128c0-26.8-6.6-52.1-18.3-74.3C384.3 40.1 407.2 32 432 32c61.9 0 112 50.1 112 112s-50.1 112-112 112z'/></svg></div>";
}
// 季節バッジ(青)- fa-calendar をインライン SVG で代替 (全 WebView 対応)
else if(isSeason) {
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + badgeVerticalPos + ":0;background-color:#00b8bb;border-radius:15px;padding:2px 4px;display:flex;align-items:center;justify-content:center;'><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512' width='12' height='12' fill='white'><path d='M96 32l0 32L48 64C21.5 64 0 85.5 0 112l0 48 448 0 0-48c0-26.5-21.5-48-48-48l-48 0 0-32c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 32L160 64l0-32c0-17.7-14.3-32-32-32S96 14.3 96 32zM448 192L0 192 0 464c0 26.5 21.5 48 48 48l352 0c26.5 0 48-21.5 48-48l0-272z'/></svg></div>";
}
// 運用hubバッジ黄色- 他のバッジがある場合は下にずらす
if(hasUnyohub) {
const offsetStyle = (isEdit || isSeason) ? (badgeVerticalPos + ":20px;") : (badgeVerticalPos + ":0;");
badgeHtml += "<div style='position:absolute;" + badgePosition + ":0;" + offsetStyle + "background-color:#ffcc00;border-radius:50%;border:1px solid #000000;width:19px;height:19px;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;justify-content:center;'><img src='https://fossil.2pd.jp/unyohub/raw/5cb01771a1ae8cb73b7bc2048b88f7878ab180aef46247c3901d7cba50d6b71f' style='width:24px;height:24px;margin-left:5px;'/></div>";
}
行き先情報.insertAdjacentHTML('beforebegin', "<div style='width:100%;display:flex;flex:1;flex-direction:"+(isLeft ? "column-reverse" : "column") + ";font-weight:bold;'>" + badgeHtml + "<p style='font-size:6px;padding:0;color:black;text-align:center;'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p><div style='flex:1;'></div><p style='font-size:8px;font-weight:bold;padding:0;color: black;text-align:center;'>" + (isWanman ? "ワンマン " : "") + "</p><p style='font-size:6px;font-weight:bold;padding:0;color: black;text-align:center;border-style:solid;border-width: "+(!!yosan2Color ? "2px" : "0px")+";border-color:" + yosan2Color + "'>" + viaData + "</p><p style='font-size:6px;font-weight:bold;padding:0;color: " + optionalTextColor + ";text-align:center;'>" + optionalText + "</p><p style='font-size:8px;font-weight:bold;padding:0;color: black;text-align:center;'>" + trainName + "</p><div style='width:100%;background:" + gradient + ";'><p style='font-size:10px;font-weight:bold;padding:0;margin:0;color:white;align-items:center;align-content:center;text-align:center;text-shadow:1px 1px 0px #00000030, -1px -1px 0px #00000030,-1px 1px 0px #00000030, 1px -1px 0px #00000030,1px 0px 0px #00000030, -1px 0px 0px #00000030,0px 1px 0px #00000030, 0px -1px 0px #00000030;'>" + (ToData ? ToData + "行" : ToData) + "</p></div><div style='width:100%;background:" + trainTypeColor + ";border-radius:"+(isLeft ? "4px 4px 0 0" : "0 0 4px 4px")+";'><p style='font-size:10px;font-weight:bold;font-style:italic;padding:0;color: white;text-align:center;'>" + trainType + "</p></div><p style='font-size:8px;font-weight:bold;padding:0;text-align:center;color: "+(hasProblem ? "red":"black")+"; "+(hasProblem ? "animation:_jrs_blink 3s linear infinite;" : "")+"'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p></div>");
`: `
行き先情報.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: black;'>" + returnText1 + "</p>");
行き先情報.insertAdjacentHTML('beforebegin', "<div style='display:inline-flex;flex-direction:row;'><p style='font-size:10px;font-weight: bold;padding:0;color:black;'>" + (ToData ? ToData + "行 " : ToData) + "</p><p style='font-size:10px;padding:0;color:black;'>" + (TrainNumberOverride ? TrainNumberOverride : TrainNumber) + "</p></div>");
行き先情報.insertAdjacentHTML('beforebegin', "<p style='font-size:10px;font-weight:bold;padding:0;color: "+(hasProblem ? "red":"black")+"; "+(hasProblem ? "animation:_jrs_blink 3s linear infinite;" : "")+"'>" + (hasProblem ? "‼️停止中‼️" : "") + "</p>");
`}
}
`;
const textInsert =
`
const setNewTrainItem = (element,hasProblem,type)=>{
var 列番データ = element.getAttribute('offclick').split('"')[1];
if(trainDataList.find(e => e.train_id === 列番データ) !== undefined){
const data = trainDataList.find(e => e.train_id === 列番データ);
const _bc = TRAIN_TYPE_CFG[data.type];
element.style.borderColor = _bc ? _bc.borderColor : 'black';
element.style.backgroundColor = _bc ? _bc.bgColor : '#ffffffcc';
}else{
if(element.getAttribute('offclick').includes("express")){
element.style.borderColor = '#ff0000ff';
}else if(element.getAttribute('offclick').includes("rapid")){
element.style.borderColor = '#008cffff';
}else{
element.style.borderColor = 'black';
}
}
element.style.borderWidth = '2px';
element.style.borderStyle = 'solid';
element.style.borderRadius = '10%';
if(hasProblem){
element.style.boxShadow = '';
element.style.animation = '_jrs_glow 3s linear infinite';
}else{
element.style.boxShadow = '0 0 4px rgba(0, 0, 0, 0.2)';
element.style.animation = '';
}
element.style.margin = '2px';
element.style.display = 'flex';
element.style.alignItems = 'center';
element.style.justifyContent = 'center';
element.style.width = '4.5em';
element.style.minHeight = '80px';
element.style.height = '100%';
element.getElementsByTagName("img")[0].style.float = 'unset';
element.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
element.style.transition = 'transform 0.1s ease-in-out';
element.addEventListener('touchstart', () => element.style.transform = 'scale(0.8)');
element.addEventListener('touchend', () => element.style.transform = 'scale(1)');
if(element.getAttribute("style").includes("left")){
// borderを使って五角形を生成 下り
element.style.borderRadius = '30px 30px 120px 120px';
element.style.flexDirection = 'column-reverse';
}
else if(element.getAttribute("style").includes("right")){
// borderを使って五角形を生成 上り
element.style.borderRadius = '120px 120px 30px 30px';
element.style.flexDirection = 'column';
}
}
//列番付与
const setStrings = () =>{
try {
const elements = document.querySelectorAll('#disp > div > div > div[onclick]');
const setNewTrainItemUI = ()=>{
const aaa = (x2,pos) => {
x2.style.display = 'flex';
x2.style.flexDirection = 'row';
if(pos == "right"){
x2.style.alignItems = 'flex-start';
x2.style.justifyContent = 'flex-start';
}else if(pos == "left"){
x2.style.alignItems = 'flex-end';
x2.style.justifyContent = 'flex-end';
}
x2.style.flexWrap = 'wrap';
x2.style.width = '100%';
x2.style.height = "100%";
}
const aaa2 = (x2) => {
x2.style.display = 'flex';
x2.style.flexDirection = 'row';
x2.style.alignItems = 'center';
x2.style.justifyContent = 'center';
x2.style.flexWrap = 'wrap';
x2.style.width = '100%';
x2.style.height = "unset";
const x3 = x2.querySelectorAll(":scope > div");
x3.forEach(i=>{
i.style.position = "unset";
i.style.display = "flex";
i.style.flexDirection = "column";
i.style.alignItems = "center";
i.style.justifyContent = "center";
i.style.flex = "1";
i.style.backgroundColor = "#00000000";
i.querySelectorAll(":scope > *").forEach(j=>{
j.style.display = "flex";
j.style.flex = "1";
j.style.textAlign = "center";
j.style.margin = "5px";
j.style.padding = "5px";
});
});
}
const layoutBase = (e)=>{
e.style.display = 'flex';
e.style.height = "unset";
e.style.flexDirection = 'row';
e.style.justifyContent = 'center';
}
const elementBaseBase = document.querySelectorAll('[id^="stationBlock"]');
const elementNotBase = document.querySelectorAll('#disp > [id*=""]');
elementNotBase.forEach(e=>{
layoutBase(e);
const x = e.querySelectorAll(':scope > [id^="Up"], :scope > [id^="Id"], :scope > [id^="Down"]');//配下のdiv要素を選択
aaa(x[0],"left");
aaa2(x[1]);
aaa(x[2],"right");
const upTrainCrossBarElement = e.querySelector(':scope > [id="upTrainCrossBar"]');
if (upTrainCrossBarElement) {
upTrainCrossBarElement.style.left = '0px';
}
});
elementBaseBase.forEach(e=>{ //それぞれの駅ブロック横一列
layoutBase(e);
const x = e.querySelectorAll(':scope > div');//配下のdiv要素を選択
//x[0] 登りブロック x[2] 下りブロック x[1] 駅ブロック
aaa(x[0],"left");
aaa2(x[1]);
aaa(x[2],"right");
});
}
${uiSetting === "tokyo" ? `setNewTrainItemUI();`: ``}
for (let element of elements) {
if(element.getAttribute('offclick')){ continue; }
element.setAttribute('offclick',element.getAttribute('onclick'))
var 行き先情報 = element.getElementsByTagName("p")[0];
${uiSetting === "tokyo" ? `
element.querySelector("img").insertAdjacentHTML('beforebegin',"<div style='flex:1;'></div>");
element.querySelector("img").insertAdjacentHTML('afterend',"<div style='flex:1;'></div>");
element.querySelector("img").style.padding = '5px';
element.style.position = 'relative';
if(element.getElementsByTagName("p")[1] != undefined){
element.getElementsByTagName("p")[1].innerText = element.getElementsByTagName("p")[1].innerText.replace("(","").replace(")","");
element.getElementsByTagName("p")[1].style.position = 'absolute';
element.getElementsByTagName("p")[1].style.backgroundColor = 'red';
element.getElementsByTagName("p")[1].style.color = 'white';
element.getElementsByTagName("p")[1].style.fontSize = '10px';
element.getElementsByTagName("p")[1].style.fontWeight = 'bold';
element.getElementsByTagName("p")[1].style.padding = '2px';
element.getElementsByTagName("p")[1].style.textAlign = 'center';
element.getElementsByTagName("p")[1].style.borderRadius = '10px';
if(element.getAttribute("style").includes("left")){
element.getElementsByTagName("p")[1].style.bottom = '0px';
element.getElementsByTagName("p")[1].style.left = '0px';
}
else if(element.getAttribute("style").includes("right")){
element.getElementsByTagName("p")[1].style.right = '0px';
element.getElementsByTagName("p")[1].style.top = '0px';
}
}`: ``}
const isLeft = element.getAttribute("style").includes("left");
var 列番データ = element.getAttribute('offclick').split('"')[1];
var 列車名データ = element.getAttribute('offclick').split('"')[3];
const trainData = trainPositionDatas.filter(e=>!(e.Pos && e.Pos.includes("予告窓"))).find(e => e.TrainNum == 列番データ);
const hasProblem = probremsData.find((e)=>{
return e.TrainNum == trainData.TrainNum && e.Pos == trainData.Pos;
});
var flag=false;
var TrainType = undefined;
setTrainMenuDialog(element)
${iconSetting == "true" ? `
let trainIconUrl = [];
operationList
.sort((a,b)=>sortOperationalList(a,b,列番データ.toString()))
.reverse()
.forEach(e => {
if (e.train_ids?.length > 0) {
const trainIds = e.train_ids.map((x) => x.split(",")[0]);
if (trainIds.includes(列番データ.toString())) {
let iconTrainDirection = parseInt(列番データ.toString().replace(/[^\\d]/g,""))%2 == 0 ? true : false;
const {directions} = trainDataList.find(e => e.train_id === 列番データ);
if(directions != null && directions != undefined){
iconTrainDirection = directions ? true : false;
}
if(iconTrainDirection){
if(e.vehicle_img) trainIconUrl.push(e.vehicle_img);
else if(e.vehicle_img_right) trainIconUrl.push(e.vehicle_img_right);
}else{
if(e.vehicle_img_right) trainIconUrl.push(e.vehicle_img_right);
else if(e.vehicle_img) trainIconUrl.push(e.vehicle_img);
}
}
} else if (e.related_train_ids?.length > 0) {
const trainIds = e.related_train_ids.map(
(x) => x.split(",")[0]
);
if (trainIds.includes(列番データ.toString())) {
let iconTrainDirection = parseInt(列番データ.toString().replace(/[^\\d]/g,""))%2 === 0 ? true : false;
const {directions} = trainDataList.find(e => e.train_id === 列番データ);
if(directions != null && directions != undefined){
iconTrainDirection = directions ? true : false;
}
if(iconTrainDirection){
if(e.vehicle_img) trainIconUrl.push(e.vehicle_img);
else if(e.vehicle_img_right) trainIconUrl.push(e.vehicle_img_right);
}else{
if(e.vehicle_img_right) trainIconUrl.push(e.vehicle_img_right);
else if(e.vehicle_img) trainIconUrl.push(e.vehicle_img);
}
}
}
});
if(trainIconUrl.length > 0){
[trainIconUrl[0], trainIconUrl[trainIconUrl.length - 1]].forEach((url,index,array) => {
if(url && url != ""){
setStationIcon(element.querySelector("img"),url,hasProblem,trainIconUrl.length == 1 ? 100 : index);
}
});
}
else{
if(trainDataList.find(e => e.train_id === 列番データ) !== undefined){
const trainIconUrl = [trainDataList.find(e => e.train_id === 列番データ).train_info_img];
if(trainIconUrl.length > 0){
trainIconUrl.forEach((url,index,array) => {
if(url && url != ""){
setStationIcon(element.querySelector("img"),url,hasProblem);
}
});
}
}
else{
const trainIconUrl = [setTrainIcon(列番データ)];
if(trainIconUrl.length > 0){
if(trainIconUrl[0] && trainIconUrl[0] != ""){
setStationIcon(element.querySelector("img"),trainIconUrl[0],hasProblem);
}
}
}
}
` : ""}
nameReplace(列車名データ,列番データ,行き先情報,hasProblem,isLeft);
${uiSetting === "tokyo" ? `setNewTrainItem(element,hasProblem);`: ``}
}
try{
for(let d of document.getElementById('disp').childNodes){
switch(d.id){
case 'pMENU_2':
case 'pMENU_2_En':
case 'pMENU_3':
case 'pMENU_3_En':
case 'pMENU_k':
case 'pMENU_k_En':
continue;
default:
break;
}
d.style.width = '100vw';
for(let f of d.childNodes){
try{
if(f.style.alignItems || f.style.textAlign){
f.style.width = '38vw';
}
else{
if(f.id == 'upTrainCrossBar'){
f.style.width = '38vw';
}
else if(f.id == 'dwTrainCrossBar'){
f.style.left = '62vw';
f.style.width = '38vw';
}
else {
f.style.width = '0vw';
}
}
if(f.style.textAlign == 'center'){
f.style.width = '24vw';
f.style.display = 'flex';
f.childNodes.forEach(i =>{
i.style.width = 'unset';
i.style.left = 'unset';
i.style.top = 'unset';
i.style.position = 'unset';
i.style.flex = '1';
i.style.margin = '5px'
if(i.style.backgroundColor != 'rgb(247, 247, 247)'){
i.childNodes.forEach(m=> m.style.width = '20vw')
}
})
}
}catch(e){}
}
}
document.querySelector('#pMENU_2').style.borderStyle='solid';
document.querySelector('#pMENU_2').style.borderColor='#00d3e8';
document.querySelector('#pMENU_2').style.borderWidth='2px';
document.querySelector('#pMENU_2').style.borderRadius='10%';
document.querySelector('#pMENU_3').style.borderStyle='solid';
document.querySelector('#pMENU_3').style.borderColor='#00d3e8';
document.querySelector('#pMENU_3').style.borderWidth='2px';
document.querySelector('#pMENU_3').style.borderRadius='10%';
document.querySelectorAll('#pMENU_2 div').forEach((d)=>d.style.padding = '10px');
document.querySelectorAll('#pMENU_3 div').forEach((d)=>d.style.padding = '10px');
document.querySelectorAll('#topHeader div').forEach((d)=>d.style.width = '100vw');
document.querySelectorAll('#disp div')[0].style.width = '100vw';
document.getElementById('disp').style.width = '100vw';
document.getElementById('disp').style.overflowX = 'hidden';
}catch(e){
alert("本家サーバーからのデータ取得に失敗しました。");
window.ReactNativeWebView.postMessage(JSON.stringify({type:"LoadError"}));
}
} catch (e) {}
}
const textInsert = new MutationObserver( (mutations) =>{
setStrings();
const currentLines = document.querySelector('#topHeader div').innerText;
window.ReactNativeWebView.postMessage(JSON.stringify({type:"currentLines",currentLines}));
});
// 監視を開始
textInsert.observe(document.getElementById('disp'), {
attributes: true, // 属性変化の監視
//attributeOldValue: true, // 変化前の属性値を matation.oldValue に格納する
//characterData: true, // テキストノードの変化を監視
//characterDataOldValue: true, // 変化前のテキストを matation.oldValue に格納する
childList: true, // 子ノードの変化を監視
//subtree: true // 子孫ノードも監視対象に含める
});
`;
// 列車メニュー表示の起動用スクリプト
const makeTrainView = `
const makeTrainView = new MutationObserver( (mutations) => {
for(let d of modal_content.getElementsByTagName("button") ){
if(d.getAttribute('data-rn-handled')) continue;
d.setAttribute('data-rn-handled','1');
// getAttribute で生のHTML属性文字列を取得 (toString()はWebViewごとに書式が異なる)
const raw = d.getAttribute('onclick') || '';
const data = raw.split('"')[1] || raw.split("'")[1] || '';
if(!data) continue;
d.removeAttribute('onclick'); // 古いWebViewで属性が再評価されるのを防ぐ
d.onclick = (e) => { e && e.stopPropagation && e.stopPropagation(); window.ReactNativeWebView.postMessage(data); };
}
});
// 監視を開始
makeTrainView.observe(document.getElementById('modal_content'), {
//attributes: true, // 属性変化の監視
//attributeOldValue: true, // 変化前の属性値を matation.oldValue に格納する
//characterData: true, // テキストノードの変化を監視
//characterDataOldValue: true, // 変化前のテキストを matation.oldValue に格納する
childList: true, // 子ノードの変化を監視
//subtree: true // 子孫ノードも監視対象に含める
});
`;
const makeTrainMenu =
trainMenu == "true"
? `
// これの中身抽出ShowTrainTimeInfo("1228M","normal")
// ShowTrainTimeInfo("142M","rapid:サンポート南風リレー")
function setTrainMenuDialog(d){
try{
const offclick = d.getAttribute('offclick');
if(!offclick) return;
// シングル/ダブルクォート両対応で引数を抽出
const s = offclick.replace('ShowTrainTimeInfo(','').replaceAll('"','').replaceAll("'",'').replace(')','').split(',');
const returnData = {type:"ShowTrainTimeInfo",trainNum:s[0],limited:s[1]};
d.removeAttribute('onclick');
d.onclick = null;
// capture=true でターゲットフェーズより先に発火 + stopImmediatePropagation で元ハンドラを封じる
d.addEventListener('click', function(e){
e.stopImmediatePropagation();
e.preventDefault();
window.ReactNativeWebView.postMessage(JSON.stringify(returnData));
return false;
}, true);
}catch(e){
}
}
// Object.defineProperty でページ側JSが後から上書きできないようにロック
// Chrome 81 等の古いWebViewでも onclick属性から呼ばれる関数の乗っ取りが確実になる
try {
Object.defineProperty(window, 'ShowTrainTimeInfo', {
configurable: false,
writable: false,
value: function(trainNum, limited){
window.ReactNativeWebView.postMessage(JSON.stringify({type:"ShowTrainTimeInfo",trainNum:trainNum,limited:limited}));
}
});
} catch(e) { /* 既にnon-configurableで定義済みの場合は無視 */ }
`
: `function setTrainMenuDialog(d){}`;
const makeStationMenu =
stationMenu == "true"
? `
//駅メニューダイアログの配置
// PopUpMenu もObject.definePropertyでロック: onclick属性から呼ばれても確実に横取りできる
try {
Object.defineProperty(window, 'PopUpMenu', {
configurable: false,
writable: false,
value: function(event, id, name, pdf, map, url, chk){
window.ReactNativeWebView.postMessage(JSON.stringify({type:"PopUpMenu",event:String(event||''),id:String(id||''),name:String(name||''),pdf:String(pdf||''),map:String(map||''),url:String(url||''),chk:String(chk||'')}));
}
});
} catch(e) { /* 既にnon-configurableで定義済みの場合は無視 */ }
const StationData = ${JSON.stringify(STATION_DATA)};
const setStationMenuDialog = new MutationObserver( (mutations) => {
const data =[];
document.querySelectorAll('#disp div div').forEach(d=>d.id.indexOf("st")!= -1 && data.push(d));
for(let d of data ){
if(!d.offclick){
// getAttribute で生の属性文字列を取得 (toString() は WebView ごとに書式が異なる)
d.offclick = d.getAttribute('onclick') || d.onclick.toString();
}
const s = d.offclick.replace('(event)','').replaceAll("'", "").split('(')[1].split(')')[0].split(',');
const stationBadge = StationData.find(e=>e.StationName === s[2])?.Feature;
if(stationBadge){
const midoriStyle = JSON.parse(stationBadge).Midori.style;
const IC = JSON.parse(stationBadge).IC;
if(!d.childNodes[0].childNodes[0].childNodes[1]){
d.childNodes[0].childNodes[0].childNodes[0].insertAdjacentHTML('afterend',"<div style='position: absolute;width:0px;height:0px;' class='trainstatus'><div style='z-index:10;position:relative;bottom:0px;height:100%;width:0px;'>"+(IC ? "<img src='https://storage.haruk.in/R.png' width='15' height='15' style='position: absolute;bottom:-30px;left:25px;' />" : "")+(midoriStyle === "normal" ? ("<img src='https://storage.haruk.in/midori1.png' width='15' height='15' style='position: absolute;bottom:"+(!IC ? "-30px":"-15px")+";left:25px;' />") : "")+(midoriStyle === "plus" ? ("<img src='https://storage.haruk.in/midori2.png?hoge' width='15' height='15' style='position: absolute;bottom:"+(!IC ? "-30px":"-15px")+";left:25px;' />") : "")+"</div></div>");
}
}
d.removeAttribute('onclick'); // 古い WebView で属性が再評価されるのを防ぐ
d.onclick = (e) =>{
e && e.stopPropagation && e.stopPropagation();
// これの中身抽出 PopUpMenu(event,'2','端岡','http://www.jr-shikoku.co.jp/01_trainbus/jikoku/pdf/hashioka.pdf','https://www.google.co.jp/maps/place/34.305027,133.967643','','1')
window.ReactNativeWebView.postMessage(JSON.stringify({type:"PopUpMenu",event:s[0],id:s[1],name:s[2],pdf:s[3],map:s[4],url:s[5],chk:s[6]}));
return false;
};
}
const data2 =[];
document.querySelectorAll('#disp > div > div > div > div > font > div').forEach(d=>{
if(!!d.onclick) data2.push(d);
})
for(let d of data2 ){
if(!d.offclick){
// getAttribute で生の属性文字列を取得 (toString() は WebView ごとに書式が異なる)
d.offclick = d.getAttribute('onclick') || d.onclick.toString();
}
const stationID = d.childNodes[0].innerText;
const PDFAddress = d.offclick.split("'")[1];
const Name = d.childNodes[2].innerText.replaceAll("\\n","");
const stationBadge = StationData.find(e=>e.StationName === Name)?.Feature;
if(stationBadge){
const midoriStyle = JSON.parse(stationBadge).Midori.style;
const IC = JSON.parse(stationBadge).IC;
if(!d.childNodes[3]) {
d.childNodes[2].insertAdjacentHTML('afterend',"<div style='z-index:10;height:100%;width:0px;margin-bottom:5px;padding-bottom:0px;margin-left:-10px;background-color:blue;' class='trainstatus'>"+(IC && "<img src='https://storage.haruk.in/R.png' width='15' height='15' />")+"</div>");
}
}
d.removeAttribute('onclick'); // 古い WebView で属性が再評価されるのを防ぐ
d.onclick = (e) =>{
e && e.stopPropagation && e.stopPropagation();
window.ReactNativeWebView.postMessage(JSON.stringify({type:"PopUpMenu",id:stationID,name:Name,pdf:PDFAddress}));
return false;
};
}
});
// 監視を開始
setStationMenuDialog.observe(document.querySelector('#disp'), {
attributes: true, // 属性変化の監視
//attributeOldValue: true, // 変化前の属性値を matation.oldValue に格納する
//characterData: true, // テキストノードの変化を監視
//characterDataOldValue: true, // 変化前のテキストを matation.oldValue に格納する
childList: true, // 子ノードの変化を監視
//subtree: true // 子孫ノードも監視対象に含める
});
`
: ``;
return (
bootData +
topMenu +
trainIcon +
normal_train_name +
makeTrainView +
makeTrainMenu +
textInsert +
makeStationMenu
);
};