File size: 3,658 Bytes
ce0719e
 
 
 
 
 
4312f62
 
ce0719e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02a8414
ce0719e
 
 
 
 
 
3d6b7f2
ce0719e
02a8414
ce0719e
 
 
 
 
 
 
 
 
 
 
 
 
 
02a8414
cf289c1
02a8414
 
3d6b7f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02a8414
3d6b7f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02a8414
 
 
ce0719e
3d6b7f2
 
 
 
 
 
 
 
 
 
 
4312f62
 
cf289c1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{{ page_title or app_name }} · {{ app_name }}</title>
    <link rel="icon" href="/static/favicon.ico?v={{ static_asset_version('favicon.ico') }}" sizes="any" />
    <link rel="stylesheet" href="/static/style.css?v={{ static_asset_version('style.css') }}" />
  </head>
  <body class="{% if admin %}admin-mode{% else %}user-mode{% endif %}">
    <div class="page-backdrop"></div>
    <div class="shell">
      <header class="topbar">
        <div class="brand-block">
          <div class="brand-mark"></div>
          <div>
            <p class="eyebrow">Spring Check-In</p>
            <h1 class="brand-title">{{ app_name }}</h1>
          </div>
        </div>
        <nav class="topnav">
          {% if user %}
            <a href="/dashboard">活动广场</a>
            <a href="/account">账号中心</a>
            <a href="/logout">退出登录</a>
          {% elif admin %}
            <a href="/admin/dashboard">总览</a>
            <a href="/admin/users">用户</a>
            <a href="/admin/groups">小组</a>
            <a href="/admin/activities">活动</a>
            <a href="/admin/images">图片</a>
            <a href="/admin/reviews">审核</a>
            <a href="/account">账号中心</a>
            {% if admin.role == 'superadmin' %}
              <a href="/admin/admins">管理员</a>
            {% endif %}
            <a href="/admin/logout">退出</a>
          {% endif %}
        </nav>
      </header>

      {% include 'partials/flash.html' %}

      <main class="content-shell">
        {% block content %}{% endblock %}
      </main>
    </div>

    {% if admin %}
      <script>
        (() => {
          const PRESENCE_INTERVAL_MS = 5000;
          const INITIAL_JITTER_MS = 1200;
          let timerId = null;
          let inFlight = false;

          const shouldPing = () => !document.hidden && navigator.onLine;

          const schedule = (delay = PRESENCE_INTERVAL_MS) => {
            if (timerId) {
              window.clearTimeout(timerId);
            }
            timerId = window.setTimeout(runPing, delay);
          };

          const runPing = async () => {
            if (!shouldPing()) {
              timerId = null;
              return;
            }
            if (inFlight) {
              schedule(1000);
              return;
            }

            inFlight = true;
            try {
              await fetch('/api/presence/ping', {
                method: 'POST',
                keepalive: true,
                credentials: 'same-origin',
                cache: 'no-store',
                headers: { 'X-Requested-With': 'fetch' },
              });
            } catch (error) {
              console.debug('presence ping skipped', error);
            } finally {
              inFlight = false;
              schedule();
            }
          };

          document.addEventListener('visibilitychange', () => {
            if (document.hidden) {
              if (timerId) {
                window.clearTimeout(timerId);
                timerId = null;
              }
              return;
            }
            runPing();
          });
          window.addEventListener('focus', () => {
            if (!document.hidden) {
              runPing();
            }
          });
          window.addEventListener('online', runPing);
          schedule(500 + Math.floor(Math.random() * INITIAL_JITTER_MS));
        })();
      </script>
    {% endif %}
  </body>
</html>