blog-sidebar.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <?php
  2. // Shared sidebar for the blog archive (/blog/<user>) and the blog post page
  3. // (/blog/<user>/post/<id>).
  4. //
  5. // Expects:
  6. // $owner blog owner's username
  7. // $show_write_buttons render the "写新博客 / 写新幻灯片" buttons (owner only)
  8. // $show_tags render the 标签 card (archive page only)
  9. // $all_tags tags for the 标签 card (rows with a 'tag' key)
  10. if (!isset($owner)) {
  11. $owner = UOJContext::userid();
  12. }
  13. $show_write_buttons = !empty($show_write_buttons);
  14. $show_tags = !empty($show_tags);
  15. if (!isset($all_tags)) {
  16. $all_tags = array();
  17. }
  18. $sidebar_user = queryUser($owner);
  19. // Queried exactly once per page view; the mini preview and the enlarged
  20. // modal both render from this single map.
  21. $activity = queryUserActivityHeatmap($owner);
  22. $heatmap_today_ts = strtotime(date('Y-m-d'));
  23. // Sunday of the current week (weeks are Monday-first), then back up to the
  24. // Monday 52 weeks before it: 53 columns x 7 rows ending at today.
  25. $heatmap_start_ts = $heatmap_today_ts + (7 - (int)date('N', $heatmap_today_ts)) * 86400 - 370 * 86400;
  26. // Month labels are only shown in the enlarged modal, spaced at least four
  27. // columns apart so short month names never overlap; a partial leading month
  28. // yields to the fuller month that starts right after it.
  29. $heatmap_month_labels = array();
  30. $heatmap_candidates = array();
  31. $heatmap_prev_month = null;
  32. for ($col = 0; $col < 53; $col++) {
  33. $month = (int)date('n', $heatmap_start_ts + $col * 7 * 86400);
  34. if ($month !== $heatmap_prev_month) {
  35. $heatmap_candidates[] = array($col, $month.'月');
  36. $heatmap_prev_month = $month;
  37. }
  38. }
  39. $heatmap_last_col = -100;
  40. foreach ($heatmap_candidates as $candidate) {
  41. if ($candidate[0] - $heatmap_last_col < 4) {
  42. array_pop($heatmap_month_labels);
  43. }
  44. $heatmap_month_labels[$candidate[0]] = $candidate[1];
  45. $heatmap_last_col = $candidate[0];
  46. }
  47. ksort($heatmap_month_labels);
  48. // Build the 371 cells once and reuse the same markup in both grids.
  49. $heatmap_cells = '';
  50. for ($col = 0; $col < 53; $col++) {
  51. for ($row = 0; $row < 7; $row++) {
  52. $ts = $heatmap_start_ts + ($col * 7 + $row) * 86400;
  53. $date = date('Y-m-d', $ts);
  54. $count = isset($activity[$date]) ? $activity[$date] : 0;
  55. if ($count <= 0) {
  56. $level = 0;
  57. } elseif ($count == 1) {
  58. $level = 1;
  59. } elseif ($count == 2) {
  60. $level = 2;
  61. } elseif ($count == 3) {
  62. $level = 3;
  63. } else {
  64. $level = 4;
  65. }
  66. if ($ts > $heatmap_today_ts) {
  67. $heatmap_cells .= '<span class="wyoj-heatmap-cell wyoj-heatmap-cell-future" data-level="0" data-date="'.$date.'" data-count="0"></span>';
  68. } else {
  69. $heatmap_cells .= '<span class="wyoj-heatmap-cell" data-level="'.$level.'" data-date="'.$date.'" data-count="'.$count.'" title="'.$count.' 次 · '.$date.'"></span>';
  70. }
  71. }
  72. }
  73. $heatmap_total = array_sum($activity);
  74. ?>
  75. <div class="wyoj-blog-sidebar">
  76. <?php if ($show_write_buttons && UOJContext::hasBlogPermission()): ?>
  77. <div class="btn-group d-flex">
  78. <a href="<?= HTML::blog_url($owner, '/post/new/write') ?>" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> 写新博客</a>
  79. <a href="<?= HTML::blog_url($owner, '/slide/new/write') ?>" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> 写新幻灯片</a>
  80. </div>
  81. <?php endif ?>
  82. <div class="card top-buffer-sm wyoj-blog-owner-card">
  83. <div class="card-body">
  84. <img class="wyoj-blog-owner-avatar" src="<?= HTML::avatar_addr($sidebar_user, 256) ?>" alt="<?= HTML::escape($owner) ?> Avatar" />
  85. <div class="wyoj-blog-owner-name"><a href="<?= HTML::url('/user/profile/'.$owner) ?>"><?= HTML::escape($owner) ?></a></div>
  86. <div class="wyoj-blog-owner-motto text-muted"><?= HTML::escape($sidebar_user['motto']) ?></div>
  87. </div>
  88. </div>
  89. <?php if ($show_tags): ?>
  90. <div class="card top-buffer-sm">
  91. <div class="card-header">标签</div>
  92. <div class="card-body">
  93. <?php if ($all_tags): ?>
  94. <?php foreach ($all_tags as $tag): ?>
  95. <?php echoBlogTag($tag['tag']) ?>
  96. <?php endforeach ?>
  97. <?php else: ?>
  98. <div class="text-muted">暂无</div>
  99. <?php endif ?>
  100. </div>
  101. </div>
  102. <?php endif ?>
  103. <div class="card top-buffer-sm wyoj-heatmap-card" id="wyoj-heatmap-preview" role="button" tabindex="0" aria-label="查看全年活跃度">
  104. <div class="card-header d-flex justify-content-between align-items-center">
  105. <span>活跃度</span>
  106. <small class="text-muted">近 53 周</small>
  107. </div>
  108. <div class="card-body">
  109. <div class="wyoj-heatmap-mini">
  110. <div class="wyoj-heatmap-grid wyoj-heatmap-grid-mini"><?= $heatmap_cells ?></div>
  111. </div>
  112. <div class="wyoj-heatmap-hint text-muted"><span class="glyphicon glyphicon-zoom-in"></span> 查看全年</div>
  113. </div>
  114. </div>
  115. </div>
  116. <div class="uoj-modal wyoj-heatmap-modal" id="wyoj-heatmap-modal" hidden>
  117. <div class="uoj-modal-backdrop" data-uoj-modal-close></div>
  118. <div class="uoj-modal-window" role="dialog" aria-modal="true" aria-label="活跃度">
  119. <button type="button" class="uoj-modal-close" data-uoj-modal-close aria-label="关闭">×</button>
  120. <div class="uoj-modal-body">
  121. <div class="wyoj-heatmap-modal-head">
  122. <span class="wyoj-heatmap-modal-title">活跃度</span>
  123. <small class="text-muted">近 53 周<?= $heatmap_total > 0 ? ' · 共 '.$heatmap_total.' 次' : '' ?></small>
  124. </div>
  125. <?php if ($heatmap_total <= 0): ?>
  126. <div class="text-muted top-buffer-sm">暂无活跃记录</div>
  127. <?php endif ?>
  128. <div class="wyoj-heatmap-lg-wrap">
  129. <div class="wyoj-heatmap-lg">
  130. <div class="wyoj-heatmap-lg-corner"></div>
  131. <div class="wyoj-heatmap-months wyoj-heatmap-months-lg">
  132. <?php foreach ($heatmap_month_labels as $col => $label): ?>
  133. <span style="grid-column: <?= $col + 1 ?>"><?= $label ?></span>
  134. <?php endforeach ?>
  135. </div>
  136. <div class="wyoj-heatmap-lg-days">
  137. <span style="grid-row: 1">一</span>
  138. <span style="grid-row: 3">三</span>
  139. <span style="grid-row: 5">五</span>
  140. </div>
  141. <div class="wyoj-heatmap-grid wyoj-heatmap-grid-lg"><?= $heatmap_cells ?></div>
  142. </div>
  143. </div>
  144. <div class="wyoj-heatmap-legend wyoj-heatmap-legend-lg">
  145. <span class="wyoj-heatmap-cell" data-level="0"></span>
  146. <span class="wyoj-heatmap-cell" data-level="1"></span>
  147. <span class="wyoj-heatmap-cell" data-level="2"></span>
  148. <span class="wyoj-heatmap-cell" data-level="3"></span>
  149. <span class="wyoj-heatmap-cell" data-level="4"></span>
  150. <span class="wyoj-heatmap-legend-label">活跃程度</span>
  151. </div>
  152. </div>
  153. </div>
  154. </div>
  155. <?php echoUOJModalHelper(); ?>
  156. <script type="text/javascript">
  157. (function () {
  158. var card = document.getElementById('wyoj-heatmap-preview');
  159. var modal = document.getElementById('wyoj-heatmap-modal');
  160. if (!card || !modal || !window.uojModalInit) {
  161. return;
  162. }
  163. var api = window.uojModalInit(modal);
  164. function openModal(e) {
  165. e.preventDefault();
  166. api.open();
  167. }
  168. card.addEventListener('click', openModal);
  169. card.addEventListener('keydown', function (e) {
  170. if (e.key === 'Enter' || e.key === ' ' || e.keyCode === 13 || e.keyCode === 32) {
  171. openModal(e);
  172. }
  173. });
  174. })();
  175. </script>