PHP Classes

File: templates/base.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/base.twig   Download  
File: templates/base.twig
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 40,971 bytes
 

Contents

Class file image Download
<!DOCTYPE html> <html lang="{{ locale|default('en') }}"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="csrf-token" content="{{ csrf_token }}">{# session-scoped CSRF token, stable across tabs #} <title>{% block title %}{{ system_name }}{% endblock %}</title> {% block meta_description_tags %}{% endblock %} {# Meta tags block for SEO - can be overridden by child templates #} {% block meta_tags %}{% endblock %} <!-- Favicons --> <link rel="icon" type="image/svg+xml" href="{{ favicon_svg }}"> <link rel="icon" type="image/x-icon" href="{{ favicon_ico }}"> <link rel="apple-touch-icon" href="{{ favicon_png }}"> <link rel="manifest" href="/appmanifestjson"> <link href="/vendor/bootstrap-5.3.0/css/bootstrap.min.css" rel="stylesheet"> <link href="/vendor/fontawesome-6.4.0/css/all.min.css" rel="stylesheet"> <link id="theme-stylesheet" href="{{ stylesheet }}" rel="stylesheet"> <link href="/css/ansisys.css" rel="stylesheet"> <link href="/vendor/plyr-3.8.4/plyr.min.css" rel="stylesheet"> <link href="/css/binkplayer/binkplayer.css" rel="stylesheet"> {% block head %}{% endblock %} <style> .chat-menu-icon.unread { color: #ffc107 !important; } .messaging-menu-icon.unread { color: #ffc107 !important; } .mail-menu-icon.unread { color: #ffc107 !important; } .file-menu-icon.unread, .file-menu-parent-link.unread, .file-menu-link.unread { color: #ffc107 !important; } .file-approvals-menu-link.unread { color: #ffc107 !important; } .echomail-moderation-menu-link.unread, .area-management-menu-link.unread, .admin-menu-link.unread { color: #ffc107 !important; } /* Nested dropdown submenu styles */ .dropdown-submenu { position: relative; } .dropdown-submenu > .dropdown-menu { top: 0; left: 100%; margin-top: -1px; margin-left: -1px; } .dropdown-submenu:hover > .dropdown-menu { display: block; } .dropdown-submenu > .dropdown-toggle::after { transform: rotate(-90deg); position: absolute; right: 10px; top: 50%; margin-top: -3px; } /* Mobile responsive: stack submenu below on small screens */ @media (max-width: 768px) { .dropdown-submenu > .dropdown-menu { position: static !important; margin-left: 1rem; border-left: 2px solid rgba(0,0,0,0.1); } } </style> <script src="/vendor/bootstrap-5.3.0/js/bootstrap.bundle.min.js"></script> <script src="/vendor/jquery-3.7.1/jquery-3.7.1.min.js"></script> <script> window.appLocale = {{ locale|json_encode|raw }}; window.appDefaultLocale = {{ default_locale|json_encode|raw }}; window.appI18nNamespaces = {{ i18n_namespaces|json_encode|raw }}; window.siteConfig = { ansiRendererMode: {{ ansi_renderer_mode|json_encode|raw }}, mediaPlayerEnabled: {{ media_player_enabled ? 'true' : 'false' }}, sseTransportMode: {{ effective_sse_transport_mode|json_encode|raw }}, configuredRealtimeTransportMode: {{ configured_realtime_transport_mode|json_encode|raw }}, realtimeTransportMode: {{ effective_realtime_transport_mode|json_encode|raw }}, realtimeWsUrl: {{ realtime_ws_url|json_encode|raw }} }; </script> <script src="/js/user-storage.js"></script> <script src="/js/app.js"></script> <script src="/vendor/plyr-3.8.4/plyr.min.js"></script> <script src="/js/binkplayer/binkplayer.js"></script> <script src="/js/media-player.js"></script> {% if current_user %} <script src="/js/binkstream-client.js"></script> {% endif %} {% if current_user %} <script src="/js/notifier.js"></script> {% endif %} {% if current_user.is_admin and admin_terminal_enabled %} <script src="/js/xterm.js"></script> <script src="/js/xterm-addon-fit.js"></script> <link href="/css/xterm.css" rel="stylesheet"> <script src="/js/admin-terminal.js" defer></script> {% endif %} {# Include custom header insertions if available #} {% include 'custom/header.insert.twig' ignore missing %} {% if debug_ansi_not_perfect %} <style>.ansi-art-container pre, .ansi-art { font-family: inherit !important; }</style> {% elseif debug_ansi_use_consolas %} <style>.ansi-art-container pre, .ansi-art { font-family: Consolas, monospace !important; }</style> {% endif %} </head> <body> <!-- Update notification toast --> <div class="position-fixed top-0 end-0 p-3" style="z-index: 11000;"> <div id="updateToast" class="toast hide" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false"> <div class="toast-header bg-primary text-white"> <i class="fas fa-info-circle me-2"></i> <strong class="me-auto">{{ t('ui.base.update_available', {}, 'common') }}</strong> <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button> </div> <div class="toast-body bg-light"> <small class="text-muted">{{ t('ui.base.new_version_ready', {}, 'common') }}</small> <div class="mt-2"> <button class="btn btn-sm btn-primary w-100" onclick="activateUpdate()"> <i class="fas fa-sync-alt me-1"></i> {{ t('ui.base.reload_now', {}, 'common') }} </button> </div> </div> </div> </div> <nav class="navbar navbar-expand-xl navbar-dark bg-primary sticky-top"> <div class="container"> <a class="navbar-brand" href="/"> <i class="fas fa-network-wired"></i> {{ system_name }} </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav me-auto"> {% if current_user %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <i class="fas fa-envelope-open-text messaging-menu-icon" id="messagingMenuIcon"></i> {{ t('ui.base.messaging', {}, 'common') }} </a> <ul class="dropdown-menu"> <li> <a class="dropdown-item" href="/netmail"> <i class="fas fa-envelope mail-menu-icon" id="netmailMenuIcon"></i> {{ t('ui.base.netmail', {}, 'common') }} </a> </li> <li> <a class="dropdown-item" href="{{ default_echo_list == 'echolist' ? '/echolist' : '/echomail' }}"> <i class="fas fa-comments mail-menu-icon" id="echomailMenuIcon"></i> {{ t('ui.base.echomail', {}, 'common') }} </a> </li> <li> <a class="dropdown-item" href="/bulletins"> <i class="fas fa-clipboard-list"></i> {{ t('ui.base.bulletins', {}, 'common') }} </a> </li> {% if bbs_feature_enabled('qwk') %} <li> <a class="dropdown-item" href="/qwk"> <i class="fas fa-envelope-open-text"></i> QWK Offline Mail </a> </li> {% endif %} {% if appearance.message_reader.email_link_url %} <li> <a class="dropdown-item" href="{{ appearance.message_reader.email_link_url | e }}"> <i class="fas fa-at"></i> {{ t('ui.base.email_link', {}, 'common') }} </a> </li> {% endif %} {% if bbs_feature_enabled('chat') %} <li><hr class="dropdown-divider"></li> <li> <a class="dropdown-item" href="/chat"> <i class="fas fa-comments chat-menu-icon" id="chatMenuIcon"></i> {{ t('ui.base.local_chat', {}, 'common') }} </a> </li> {% endif %} {% if webdoors_active and bbs_feature_enabled('webdoors') and mrc_webdoor_enabled %} <li> <a class="dropdown-item" href="/games/mrc"> <i class="fas fa-satellite-dish"></i> {{ t('ui.base.mrc_chat', {}, 'common') }} </a> </li> {% endif %} {% if appearance.message_reader.discord_url %} <li> <a class="dropdown-item" href="{{ appearance.message_reader.discord_url | e }}" target="_blank" rel="noopener noreferrer"> <i class="fab fa-discord"></i> {{ t('ui.admin.appearance.message_reader.discord_url', {}, 'common') }} </a> </li> {% endif %} </ul> </li> {% if webdoors_active and bbs_feature_enabled('webdoors') %} <li class="nav-item"> <a class="nav-link" href="/games"> <i class="fas fa-gamepad"></i> {{ t('ui.base.doors_games', {}, 'common') }} </a> </li> {% endif %} {% if bbs_feature_enabled('file_areas') and current_user %} {% if current_user.is_admin %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle file-menu-parent-link" href="#" role="button" data-bs-toggle="dropdown"> <i class="fas fa-file file-menu-icon" id="filesMenuIcon"></i> {{ t('ui.base.files', {}, 'common') }} </a> <ul class="dropdown-menu"> <li><a class="dropdown-item file-menu-link" href="/files"><i class="fas fa-file me-1"></i> {{ t('ui.base.files', {}, 'common') }}</a></li> <li><a class="dropdown-item file-approvals-menu-link" href="/admin/file-approvals"><i class="fas fa-check-circle me-1"></i> {{ t('ui.base.file_approvals', {}, 'common') }}</a></li> </ul> </li> {% else %} <li class="nav-item"> <a class="nav-link file-menu-link" href="/files"> <i class="fas fa-file file-menu-icon" id="filesMenuIcon"></i> {{ t('ui.base.files', {}, 'common') }} </a> </li> {% endif %} {% endif %} {% if bbs_directory_enabled %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <i class="fas fa-list-alt"></i> {{ t('ui.base.bbs_lists', {}, 'common') }} </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/bbs-directory"><i class="fas fa-server"></i> {{ t('ui.base.bbs_directory', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/nodelist"><i class="fas fa-sitemap"></i> {{ t('ui.base.nodelist', {}, 'common') }}</a></li> {% if meshcore_nodes_available %} <li><a class="dropdown-item" href="/packetbbs-nodes"><i class="fas fa-broadcast-tower"></i> {{ t('ui.base.meshcore_nodes', {}, 'common') }}</a></li> {% endif %} </ul> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="/nodelist"> <i class="fas fa-sitemap"></i> {{ t('ui.base.nodelist', {}, 'common') }} </a> </li> {% endif %} {% if current_user.is_admin %} {% endif %} {% if current_user.is_admin %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle admin-menu-link" href="#" role="button" data-bs-toggle="dropdown"> <i class="fas fa-cog"></i> {{ t('ui.base.admin', {}, 'common') }} </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/whosonline"><i class="fas fa-user-friends"></i> {{ t('ui.base.admin.whos_online', {}, 'common') }}</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="/admin/"><i class="fas fa-tachometer-alt"></i> {{ t('ui.base.admin.dashboard', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/binkp"><i class="fas fa-network-wired"></i> {{ t('ui.base.admin.binkp_status', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/users"><i class="fas fa-users"></i> {{ t('ui.base.admin.manage_users', {}, 'common') }}</a></li> <li><hr class="dropdown-divider"></li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle area-management-menu-link" href="#"><i class="fas fa-folder-tree"></i> {{ t('ui.base.admin.area_management', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/echoareas"><i class="fas fa-comments"></i> {{ t('ui.base.admin.echo_areas', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/subscriptions"><i class="fas fa-bookmark"></i> {{ t('ui.base.admin.subscriptions', {}, 'common') }}</a></li> {% if interests_enabled %} <li><a class="dropdown-item" href="/admin/interests"><i class="fas fa-layer-group"></i> {{ t('ui.base.admin.interests', {}, 'common') }}</a></li> {% endif %} <li><a class="dropdown-item" href="/admin/auto-feed"><i class="fas fa-rss"></i> {{ t('ui.base.admin.auto_feed', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/echomail-robots"><i class="fas fa-robot"></i> {{ t('ui.base.admin.echomail_robots', {}, 'common') }}</a></li> <li><a class="dropdown-item echomail-moderation-menu-link" href="/admin/echomail-moderation"><i class="fas fa-shield-alt"></i> {{ t('ui.base.admin.echomail_moderation', {}, 'common') }}</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="/fileareas"><i class="fas fa-folder-open"></i> {{ t('ui.base.admin.file_areas', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/file-approvals"><i class="fas fa-user-check"></i> {{ t('ui.base.admin.file_approvals', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/filearea-rules"><i class="fas fa-filter"></i> {{ t('ui.base.admin.file_area_rules', {}, 'common') }}</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="/admin/lovlynet"><i class="fas fa-globe"></i> {{ t('ui.base.admin.lovlynet', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/areafix"><i class="fas fa-network-wired me-1"></i> {{ t('ui.base.admin.areafix', {}, 'common') }}</a></li> </ul> </li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-users"></i> {{ t('ui.base.admin.community', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/bbs-directory"><i class="fas fa-server"></i> {{ t('ui.base.admin.bbs_directory', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/chat-rooms"><i class="fas fa-comment-dots"></i> {{ t('ui.base.admin.chat_rooms', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/ai-bots"><i class="fas fa-robot"></i> {{ t('ui.base.admin.ai_bots', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/polls"><i class="fas fa-vote-yea"></i> {{ t('ui.base.admin.polls', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/shoutbox"><i class="fas fa-bullhorn"></i> {{ t('ui.base.admin.shoutbox', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/weather-config"><i class="fas fa-cloud-sun"></i> {{ t('ui.base.admin.weather_config', {}, 'common') }}</a></li> </ul> </li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-ad"></i> {{ t('ui.base.admin.ads_menu', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/ads"><i class="fas fa-image"></i> {{ t('ui.base.admin.ads', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/ad-campaigns"><i class="fas fa-bullhorn"></i> {{ t('ui.base.admin.ad_campaigns', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/bulletins"><i class="fas fa-clipboard-list"></i> {{ t('ui.base.admin.bulletins', {}, 'common') }}</a></li> </ul> </li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-gamepad"></i> {{ t('ui.base.admin.doors', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/dosdoors"><i class="fas fa-terminal"></i> {{ t('ui.base.admin.doors_dos', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/native-doors"><i class="fas fa-door-open"></i> {{ t('ui.base.admin.doors_native', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/webdoors"><i class="fas fa-globe"></i> {{ t('ui.base.admin.doors_web', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/jsdosdoors"><i class="fas fa-floppy-disk"></i> {{ t('ui.base.admin.doors_jsdos', {}, 'common') }}</a></li> </ul> </li> <li><hr class="dropdown-divider"></li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-chart-line"></i> {{ t('ui.base.admin.analytics', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/activity-stats"><i class="fas fa-chart-bar"></i> {{ t('ui.base.admin.activity_stats', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/ai-usage"><i class="fas fa-robot"></i> {{ t('ui.base.admin.ai_usage', {}, 'common') }}</a></li> <li><a class="dropdown-item{% if not license_valid %} dropdown-item-locked{% endif %}" href="{{ license_valid ? '/admin/ad-analytics' : '#' }}"{% if not license_valid %} title="{{ t('ui.base.admin.registered_feature', {}, 'common') }}" onclick="return false;"{% endif %}><i class="fas fa-ad"></i> {{ t('ui.base.admin.ad_analytics', {}, 'common') }}{% if not license_valid %} <i class="fas fa-lock ms-1 small"></i>{% endif %}</a></li> <li><a class="dropdown-item" href="/admin/sharing"><i class="fas fa-share-alt"></i> {{ t('ui.base.admin.sharing', {}, 'common') }}</a></li> <li><a class="dropdown-item{% if not license_valid %} dropdown-item-locked{% endif %}" href="{{ license_valid ? '/admin/economy' : '#' }}"{% if not license_valid %} title="{{ t('ui.base.admin.registered_feature', {}, 'common') }}" onclick="return false;"{% endif %}><i class="fas fa-coins"></i> {{ t('ui.base.admin.economy_viewer', {}, 'common') }}{% if not license_valid %} <i class="fas fa-lock ms-1 small"></i>{% endif %}</a></li> <li><a class="dropdown-item{% if not license_valid %} dropdown-item-locked{% endif %}" href="{{ license_valid ? '/admin/referrals' : '#' }}"{% if not license_valid %} title="{{ t('ui.base.admin.registered_feature', {}, 'common') }}" onclick="return false;"{% endif %}><i class="fas fa-user-plus"></i> {{ t('ui.base.admin.referrals', {}, 'common') }}{% if not license_valid %} <i class="fas fa-lock ms-1 small"></i>{% endif %}</a></li> </ul> </li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="/admin/bbs-settings"><i class="fas fa-sliders-h"></i> {{ t('ui.base.admin.bbs_settings', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/appearance"><i class="fas fa-palette"></i> {{ t('ui.base.admin.appearance', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/ai-settings"><i class="fas fa-robot"></i> {{ t('ui.base.admin.ai_settings', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/binkp-config"><i class="fas fa-network-wired"></i> {{ t('ui.base.admin.binkp_configuration', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/networks"><i class="fas fa-globe"></i> {{ t('ui.base.admin.networks', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/packet-bbs"><i class="fas fa-broadcast-tower"></i> {{ t('ui.base.admin.packet_bbs', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/i18n-overrides"><i class="fas fa-language"></i> {{ t('ui.base.admin.i18n_overrides', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/licensing"><i class="fas fa-id-card"></i> {{ t('ui.base.admin.licensing', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/template-editor"><i class="fas fa-file-code"></i> {{ t('ui.base.admin.template_editor', {}, 'common') }}</a></li> </ul> </li> <li><hr class="dropdown-divider"></li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-question-circle"></i> {{ t('ui.base.admin.help', {}, 'common') }}</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/docs/view/README"><i class="fas fa-book"></i> {{ t('ui.base.admin.readme', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/docs"><i class="fas fa-book-open"></i> {{ t('ui.base.admin.docs', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/admin/docs/view/FAQ"><i class="fas fa-question-circle"></i> {{ t('ui.base.admin.faq', {}, 'common') }}</a></li> {% if has_upgrading_doc %}<li><a class="dropdown-item" href="/admin/upgrade-notes"><i class="fas fa-arrow-up-from-bracket"></i> {{ t('ui.base.admin.upgrade_notes', {'version': app_version}, 'common') }}</a></li>{% endif %} <li><a class="dropdown-item" href="https://claudes.lovelybits.org" target="_blank"><i class="fas fa-comments"></i> {{ t('ui.base.admin.claudes_bbs', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="https://github.com/awehttam/binkterm-php/issues" target="_blank"><i class="fas fa-bug"></i> {{ t('ui.base.admin.report_issue', {}, 'common') }}</a></li> {% if not license_valid %} <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item text-primary" href="/admin/register"><i class="fas fa-id-card me-1"></i> {{ t('ui.base.admin.register', {}, 'common') }}</a></li> {% endif %} <li><hr class="dropdown-divider"></li> <li class="dropdown-submenu"> <a class="dropdown-item dropdown-toggle" href="#"><i class="fas fa-code"></i> Developer</a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/admin/sse-test"><i class="fas fa-satellite-dish"></i> BinkStream Test</a></li> <li><a class="dropdown-item" href="/admin/buffer-test"><i class="fas fa-filter"></i> Buffer Test</a></li> </ul> </li> </ul> </li> </ul> </li> {% endif %} {% else %} {% if bbs_directory_enabled %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> <i class="fas fa-list-alt"></i> {{ t('ui.base.bbs_lists', {}, 'common') }} </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/bbs-directory"><i class="fas fa-server"></i> {{ t('ui.base.bbs_directory', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/nodelist"><i class="fas fa-sitemap"></i> {{ t('ui.base.nodelist', {}, 'common') }}</a></li> {% if meshcore_nodes_available %} <li><a class="dropdown-item" href="/packetbbs-nodes"><i class="fas fa-broadcast-tower"></i> {{ t('ui.base.meshcore_nodes', {}, 'common') }}</a></li> {% endif %} </ul> </li> {% else %} <li class="nav-item"> <a class="nav-link" href="/nodelist"> <i class="fas fa-sitemap"></i> {{ t('ui.base.nodelist', {}, 'common') }} </a> </li> {% endif %} {% if bbs_feature_enabled('guest_doors_page') %} <li class="nav-item"> <a class="nav-link" href="/guest-doors"> <i class="fas fa-terminal"></i> {{ t('ui.base.guest_doors', {}, 'common') }} </a> </li> {% endif %} {% if bbs_feature_enabled('public_files_index') %} <li class="nav-item"> <a class="nav-link" href="/public-files"> <i class="fas fa-folder-open"></i> {{ t('ui.base.public_files', {}, 'common') }} </a> </li> {% endif %} {% if appearance.seo.about_page_enabled %} <li class="nav-item"> <a class="nav-link" href="/about"> <i class="fas fa-info-circle"></i> {{ t('ui.about.title', {}, 'common') }} </a> </li> {% endif %} {% endif %} </ul> <ul class="navbar-nav"> {% if current_user %} <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"> {% if credits_enabled %} <span class="me-2" id="headerCreditBalance">{{ credits_symbol }}{{ credit_balance }}</span> {% endif %} <i class="fas fa-user"></i> {{ current_user.username }} </a> <ul class="dropdown-menu"> <li><a class="dropdown-item" href="/profile"><i class="fas fa-id-card me-1"></i> {{ t('ui.base.profile', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/settings"><i class="fas fa-sliders-h me-1"></i> {{ t('ui.base.settings', {}, 'common') }}</a></li> <li><a class="dropdown-item" href="/subscriptions"><i class="fas fa-bookmark"></i> {{ t('ui.base.subscriptions', {}, 'common') }}</a></li> {% if interests_enabled and has_active_interests %} <li><a class="dropdown-item" href="/interests"><i class="fas fa-layer-group"></i> {{ t('ui.interests.nav_label', {}, 'common') }}</a></li> {% endif %} <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="/user-guide"><i class="fas fa-book-open me-1"></i> {{ t('ui.base.user_guide', {}, 'common') }}</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="#" onclick="logout()">{{ t('ui.base.logout', {}, 'common') }}</a></li> </ul> </li> {% else %} {% if bbs_feature_enabled('guest_doors_page') %} <li class="nav-item"> <a class="nav-link" href="/guest-doors"> <i class="fas fa-terminal"></i> {{ t('ui.base.guest_doors', {}, 'common') }} </a> </li> {% endif %} <li class="nav-item"> <a class="nav-link" href="/login">{{ t('ui.base.login', {}, 'common') }}</a> </li> {% endif %} </ul> </div> </div> </nav> <main class="container mt-4"> {% block content %}{% endblock %} </main> <footer class="bg-light mt-5 py-3"> <div class="container text-center"> <small class="text-muted"> {% if license_valid and appearance.branding.footer_text %} {{ appearance.branding.footer_text | e }} {% else %} {{ system_name }} - Node {{ fidonet_origin }} - Sysop: {{ sysop_name }} {% endif %} </small> <br> <small class="text-muted"> {% if not (license_valid and appearance.branding.hide_powered_by) %} {{ t('ui.footer.powered_by', {}, 'common') }} <a href="https://lovelybits.org/binktermphp">BinktermPHP</a> <a href="https://github.com/awehttam/binkterm-php">{{ app_version }}</a>{% if license_valid and license_system_name and appearance.branding.show_registration_badge != false %} - {{ t('ui.footer.registered_to', {'name': license_system_name}, 'common') }}{% endif %} | {% endif %} <a href="#" data-bs-toggle="modal" data-bs-target="#houseRulesModal">{{ t('ui.about.house_rules', {}, 'common') }}</a> </small> </div> </footer> <div class="modal fade" id="houseRulesModal" tabindex="-1" aria-labelledby="houseRulesModalLabel" aria-hidden="true"> <div class="modal-dialog modal-lg modal-dialog-scrollable"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="houseRulesModalLabel">{{ t('ui.about.house_rules', {}, 'common') }}</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> {% include 'rules.twig' %} </div> </div> </div> </div> <script> // Global user settings loaded from server window.userSettings = { timezone: 'America/Los_Angeles' // Default, will be loaded via AJAX }; {% if current_user %} window.currentUserId = {{ current_user.user_id ?? current_user.id }}; window.currentUserIsAdmin = {{ current_user.is_admin ? 'true' : 'false' }}; {% endif %} window.isDevMode = {{ is_dev ? 'true' : 'false' }}; // Handle nested dropdown submenus $(document).ready(function() { // Show submenu on hover (desktop) $('.dropdown-submenu').hover( function() { $(this).find('> .dropdown-menu').show(); }, function() { $(this).find('> .dropdown-menu').hide(); } ); // Handle click on submenu toggle $('.dropdown-submenu > .dropdown-toggle').on('click', function(e) { const href = $(this).attr('href'); // If it's a real link (not #), allow normal navigation on direct click if (href && href !== '#') { // On mobile or if submenu is not visible, toggle submenu instead if ($(window).width() <= 768 || !$(this).next('.dropdown-menu').is(':visible')) { e.preventDefault(); e.stopPropagation(); const $submenu = $(this).next('.dropdown-menu'); $('.dropdown-submenu .dropdown-menu').not($submenu).hide(); $submenu.toggle(); } // Otherwise let the link work normally } else { // For # links, just toggle submenu e.preventDefault(); e.stopPropagation(); const $submenu = $(this).next('.dropdown-menu'); $('.dropdown-submenu .dropdown-menu').not($submenu).hide(); $submenu.toggle(); } }); // Hide submenu when parent dropdown closes $('.dropdown').on('hide.bs.dropdown', function() { $(this).find('.dropdown-submenu .dropdown-menu').hide(); }); }); </script> <script> if ('serviceWorker' in navigator) { // Reload/update toast intentionally disabled. // The service worker still updates and claims clients normally, but // we no longer interrupt users with a reload prompt. // // let updateNotified = false; // // function showUpdateToast() { // if (updateNotified) return; // updateNotified = true; // const toastEl = document.getElementById('updateToast'); // if (toastEl && window.bootstrap) { // bootstrap.Toast.getOrCreateInstance(toastEl).show(); // } // } // // Path 1: SW sends UPDATE_AVAILABLE after activate + clients.claim(). // navigator.serviceWorker.addEventListener('message', (event) => { // if (event.data && event.data.type === 'UPDATE_AVAILABLE') { // showUpdateToast(); // } // }); window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js', { updateViaCache: 'none' }) .then(registration => { // Path 1 (postMessage from SW) is the only notification mechanism. // Path 2 (watchWorker statechange) was removed: it fired on every // navigation where the browser detected a SW update and the new SW // activated, causing a spurious reload prompt as the user navigated // away. Path 1 is sufficient ? the SW already gates UPDATE_AVAILABLE // on replacedOld (old caches were purged), so it only fires for real // version upgrades on pages that were open before the update landed. registration.update(); setInterval(() => registration.update(), 60000); }) .catch(err => { console.warn('[SW] Registration failed:', err); }); }); } function activateUpdate() { // The SW already activated via skipWaiting() during install. // A plain reload is all that is needed to pick up the new cached assets. window.location.reload(); } </script> {% block scripts %}{% endblock %} </body> </html>