diff --git a/modules/mining-checker/assets/js/app.js b/modules/mining-checker/assets/js/app.js index e6596d9b..0c3caf40 100644 --- a/modules/mining-checker/assets/js/app.js +++ b/modules/mining-checker/assets/js/app.js @@ -1,32 +1,53 @@ (function () { - const root = document.getElementById('mining-checker-app'); - if (!root || !window.React || !window.ReactDOM) { - return; - } + window.initMiningCheckerApp = function initMiningCheckerApp(rootNode, options) { + const root = rootNode || document.getElementById('mining-checker-app'); + if (!root || !window.React || !window.ReactDOM) { + return; + } - const h = React.createElement; - const { useEffect, useMemo, useRef, useState } = React; - const apiBase = root.dataset.apiBase || '/api/mining-checker/v1'; - const initialProjectKey = root.dataset.defaultProjectKey || 'doge-main'; - const initialActiveTab = String(root.dataset.activeView || 'overview').trim() || 'overview'; - const configuredSections = (() => { - try { - const parsed = JSON.parse(root.dataset.sectionsJson || '[]'); - if (!Array.isArray(parsed)) { + if (root.dataset.moduleInitialized === '1') { + return; + } + root.dataset.moduleInitialized = '1'; + + const React = window.React; + const ReactDOM = window.ReactDOM; + const h = React.createElement; + const { useEffect, useMemo, useRef, useState } = React; + const normalizedOptions = options && typeof options === 'object' ? options : {}; + const apiBase = normalizedOptions.apiBase || root.dataset.apiBase || '/api/mining-checker/v1'; + const initialProjectKey = normalizedOptions.defaultProjectKey || root.dataset.defaultProjectKey || 'doge-main'; + const initialActiveTab = String(normalizedOptions.activeView || root.dataset.activeView || 'overview').trim() || 'overview'; + const configuredSections = (() => { + const optionSections = Array.isArray(normalizedOptions.sections) ? normalizedOptions.sections : null; + if (optionSections) { + return optionSections + .map((section) => { + const key = section && typeof section.key === 'string' ? section.key.trim() : ''; + const label = section && typeof section.label === 'string' ? section.label.trim() : ''; + return key && label ? [key, label] : null; + }) + .filter(Boolean); + } + + try { + const parsed = JSON.parse(root.dataset.sectionsJson || '[]'); + if (!Array.isArray(parsed)) { + return []; + } + return parsed + .map((section) => { + const key = section && typeof section.key === 'string' ? section.key.trim() : ''; + const label = section && typeof section.label === 'string' ? section.label.trim() : ''; + return key && label ? [key, label] : null; + }) + .filter(Boolean); + } catch (error) { return []; } - return parsed - .map((section) => { - const key = section && typeof section.key === 'string' ? section.key.trim() : ''; - const label = section && typeof section.label === 'string' ? section.label.trim() : ''; - return key && label ? [key, label] : null; - }) - .filter(Boolean); - } catch (error) { - return []; - } - })(); - const initialDebugMode = document.body && document.body.dataset.moduleDebugEnabled === '1'; + })(); + const initialDebugMode = normalizedOptions.moduleDebugEnabled === true + || (document.body && document.body.dataset.moduleDebugEnabled === '1'); function getCookie(name) { const pattern = `; ${document.cookie}`; const parts = pattern.split(`; ${name}=`); @@ -3263,5 +3284,11 @@ } } - ReactDOM.createRoot(root).render(h(App)); + ReactDOM.createRoot(root).render(h(App)); + }; + + const standaloneRoot = document.getElementById('mining-checker-app'); + if (standaloneRoot) { + window.initMiningCheckerApp(standaloneRoot); + } })(); diff --git a/modules/mining-checker/desktop.php b/modules/mining-checker/desktop.php index 9c71a63a..98d4aec6 100644 --- a/modules/mining-checker/desktop.php +++ b/modules/mining-checker/desktop.php @@ -21,6 +21,13 @@ $sections = array_values(array_filter( static fn (mixed $section): bool => is_array($section) )); +$assetVersion = static function (string $relativePath): string { + $fullPath = __DIR__ . '/' . ltrim($relativePath, '/'); + $mtime = is_file($fullPath) ? filemtime($fullPath) : false; + + return $mtime === false ? '0' : (string) $mtime; +}; + return [ 'app_id' => 'mining-checker', 'title' => (string) ($manifest['title'] ?? 'Mining-Checker'), @@ -45,12 +52,12 @@ return [ ], 'assets' => [ 'styles' => [ - ['href' => '/module-assets/index.php?module=mining-checker&path=assets/css/app.css'], + ['href' => '/module-assets/index.php?module=mining-checker&path=assets/css/app.css&v=' . rawurlencode($assetVersion('assets/css/app.css'))], ], 'scripts' => [ ['src' => 'https://unpkg.com/react@18/umd/react.production.min.js', 'crossorigin' => true], ['src' => 'https://unpkg.com/react-dom@18/umd/react-dom.production.min.js', 'crossorigin' => true], - ['src' => '/module-assets/index.php?module=mining-checker&path=assets/js/app.js'], + ['src' => '/module-assets/index.php?module=mining-checker&path=assets/js/app.js&v=' . rawurlencode($assetVersion('assets/js/app.js'))], ], ], ], diff --git a/modules/mining-checker/pages/index.php b/modules/mining-checker/pages/index.php index 3682d2b4..be753831 100644 --- a/modules/mining-checker/pages/index.php +++ b/modules/mining-checker/pages/index.php @@ -21,6 +21,12 @@ if ($designRaw !== false) { $moduleConfig = require dirname(__DIR__) . '/config/module.php'; $defaultProjectKey = (string) ($moduleConfig['default_project_key'] ?? 'doge-main'); +$assetVersion = static function (string $relativePath): string { + $fullPath = dirname(__DIR__) . '/' . ltrim($relativePath, '/'); + $mtime = is_file($fullPath) ? filemtime($fullPath) : false; + + return $mtime === false ? '0' : (string) $mtime; +}; $sections = is_array($design['sections'] ?? null) ? $design['sections'] : []; $activeView = trim((string) ($_GET['view'] ?? 'overview')); $sectionKeys = array_values(array_filter(array_map( @@ -77,7 +83,7 @@ $sectionsJson = json_encode($sections, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_S padding: 18px; } - +