Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.9/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
"files": {
"ignoreUnknown": true,
"includes": [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "midnight-network",
"private": true,
"version": "0.0.0",
"packageManager": "pnpm@10.30.3",
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d",
"scripts": {
"check": "biome check .",
"check:write": "biome check --write .",
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/cron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,9 @@ export async function processCronMainRerun(runId?: number) {

if (latestMatchDate && matchTargetTime === currenttargetTime) {
const snapshot = await readUserRankStatusSnapshot(latestMatchDate.id, runId);
if (snapshot !== null) {
if (snapshot === null) {
console.info('cron.reRunProcess: no snapshot for matchDate.');
} else {
console.info(
`cron.reRunProcess: apply snapshot data [ runId = ${snapshot.runId}, matchDateId = ${snapshot.matchDateId}, createdAt = ${snapshot.createdAt} ]`,
);
Expand All @@ -660,8 +662,6 @@ export async function processCronMainRerun(runId?: number) {
],
data: snapshot.status,
});
} else {
console.info('cron.reRunProcess: no snapshot for matchDate.');
}
} else {
console.info('cron.reRunProcess: no matchDate data.');
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"version": "0.0.0",
"type": "module",
"dependencies": {
"@humanspeak/svelte-markdown": "catalog:",
"@midnight-network/shared": "workspace:*",
"@opentelemetry/api": "catalog:",
"@orpc/client": "catalog:",
Expand All @@ -16,6 +17,7 @@
"@tsparticles/svelte": "catalog:",
"better-auth": "catalog:",
"echarts": "catalog:",
"katex": "catalog:",
"misskey-js": "catalog:",
"motion": "catalog:"
},
Expand Down
79 changes: 64 additions & 15 deletions packages/frontend/src/lib/components/layout/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
<script lang="ts">
import type { ApiSimpleUserInfoT } from '@midnight-network/shared/rpc/models';
import { IconSearch } from '@tabler/icons-svelte';
import { IconMenu2, IconSearch } from '@tabler/icons-svelte';
import { createQuery, useQueryClient } from '@tanstack/svelte-query';
import { blur, fade } from 'svelte/transition';
import { afterNavigate, goto } from '$app/navigation';
Expand All @@ -14,6 +14,7 @@ import RankBadge from '../rank/RankBadge.svelte';
import UserSearch from '../search/UserSearch.svelte';
import LoggedInPanel from '../user/LoggedInPanel.svelte';
import UserAvatar from '../user/UserAvatar.svelte';
import NavMenuPanel from './NavMenuPanel.svelte';

/**
* ヘッダーコンポーネント
Expand Down Expand Up @@ -50,9 +51,11 @@ $effect(() => {
primeMisskeyUsers(queryClient, [userId]).catch(() => null);
});

let isPanelOpen = $state(false);
let isUserPanelOpen = $state(false);
let isNavPanelOpen = $state(false);
let isSearchOpen = $state(false);
let userMenuContainer: HTMLDivElement;
let navMenuContainer: HTMLDivElement;

afterNavigate(() => {
if (isSearchOpen) {
Expand All @@ -64,18 +67,26 @@ afterNavigate(() => {
* ユーザーアイコンクリック時のハンドラ
*/
function handleUserClick(): void {
isPanelOpen = !isPanelOpen;
isUserPanelOpen = !isUserPanelOpen;
}

/**
* パネルを閉じる
* メニューアイコンクリック時のハンドラ
*/
function closePanel(): void {
isPanelOpen = false;
function handleMenuClick(): void {
isNavPanelOpen = !isNavPanelOpen;
}

/**
* ユーザーパネルを閉じる
*/
function closeUserPanel(): void {
isUserPanelOpen = false;
}

function handleSearchClick(): void {
isPanelOpen = false;
isUserPanelOpen = false;
isNavPanelOpen = false;
isSearchOpen = true;
onSearchClick?.();
}
Expand All @@ -99,8 +110,11 @@ function handleSearchKeydown(event: KeyboardEvent): void {
* ウィンドウクリック時のハンドラ(パネル外クリックで閉じる)
*/
function handleWindowClick(event: MouseEvent): void {
if (isPanelOpen && userMenuContainer && !userMenuContainer.contains(event.target as Node)) {
isPanelOpen = false;
if (isUserPanelOpen && userMenuContainer && !userMenuContainer.contains(event.target as Node)) {
isUserPanelOpen = false;
}
if (isNavPanelOpen && navMenuContainer && !navMenuContainer.contains(event.target as Node)) {
isNavPanelOpen = false;
}
}
</script>
Expand All @@ -116,10 +130,25 @@ function handleWindowClick(event: MouseEvent): void {
<div class="line"></div>
<nav>
<a href="/leaderboard">リーダーボード</a>
<a href="/ranksystem">ランクシステムについて</a>
<!-- 以下は現状無視 -->
<!-- <a href="/about">MidNightについて</a> -->
<!-- <a href="/ranks">ランク</a> -->
</nav>
<div class="mobile-nav" bind:this={navMenuContainer}>
<button class="mobile-nav-icon-button" type="button" onclick={handleMenuClick}>
<div class="icon">
<IconMenu2 />
</div>
</button>
{#if isNavPanelOpen}
<div class="panel-overlay-position">
<div class="panel-overlay">
<NavMenuPanel />
</div>
</div>
{/if}
</div>
</div>
<div class="right-side">
{#if showSearchButton}
Expand All @@ -145,10 +174,10 @@ function handleWindowClick(event: MouseEvent): void {
</div>
<RankBadge rank={userInfoQuery.data.latestRank} class="user-rank-badge" />
</button>
{#if isPanelOpen}
{#if isUserPanelOpen}
<div class="panel-overlay-position">
<div class="panel-overlay">
<LoggedInPanel user={userInfoQuery.data} onClose={closePanel} />
<LoggedInPanel user={userInfoQuery.data} onClose={closeUserPanel} />
</div>
</div>
{/if}
Expand Down Expand Up @@ -230,6 +259,15 @@ function handleWindowClick(event: MouseEvent): void {
background-color: #4E4B71;
}

header > div > .left-side > .mobile-nav {
display: none;
}

header > div > .left-side > nav {
display: flex;
gap: 10px;
}

header > div > .left-side > a {
height: 100%;
}
Expand Down Expand Up @@ -260,10 +298,10 @@ function handleWindowClick(event: MouseEvent): void {
height: 24px;
}

header > div > .right-side > .logged-user > .panel-overlay-position {
.panel-overlay-position {
position: relative;
}
header > div > .right-side > .logged-user > .panel-overlay-position > .panel-overlay {
.panel-overlay-position > .panel-overlay {
position: absolute;
top: 20px;
right: 0;
Expand Down Expand Up @@ -307,9 +345,20 @@ function handleWindowClick(event: MouseEvent): void {
}
header > div > .left-side {
gap: 20px;
flex-direction: row-reverse;
}
header > div > .left-side > .mobile-nav {
display: block;
}
header > div > .left-side > .mobile-nav > .mobile-nav-icon-button {
vertical-align: -0.75em;
}
header > div > .left-side > .mobile-nav > .panel-overlay-position > .panel-overlay {
right: unset;
left: 0;
}
nav a {
font-size: 0.85rem;
header > div > .left-side > nav {
display: none;
}
header > div > .right-side > .logged-user > .user-icon-button {
height: 45px;
Expand Down
59 changes: 59 additions & 0 deletions packages/frontend/src/lib/components/layout/NavMenuPanel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!-- SPDX-FileCopyrightText: 2026 Secineralyr -->
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
<script lang="ts">
import { IconBadges, IconHome, IconListNumbers } from '@tabler/icons-svelte';
import { fly } from 'svelte/transition';
</script>

<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<div class="panel" transition:fly={{ y: -10, duration: 200 }}>
<div class="panel-actions">
<a href="/" class="panel-action">
<IconHome size={16} stroke={3} />
<span class="panel-text">ホーム</span>
</a>
<a href="/leaderboard" class="panel-action">
<IconListNumbers size={16} stroke={3} />
<span class="panel-text">リーダーボード</span>
</a>
<a href="/ranksystem" class="panel-action">
<IconBadges size={16} stroke={3} />
<span class="panel-text">ランクシステムについて</span>
</a>
</div>
</div>

<style>
.panel {
background: #201E3A;
border-radius: 8px;
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
overflow: hidden;
padding: 10px 20px;
color: #ffffff;
font-size: 0.9rem;
display: flex;
flex-direction: column;
gap: 10px;
}

.panel-actions {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 5px;
}

.panel-action {
display: flex;
align-items: center;
gap: 5px;
font-weight: 600;
transition: opacity 0.15s ease;
white-space: nowrap;
}

.panel-action:hover {
opacity: 0.85;
}
</style>
46 changes: 46 additions & 0 deletions packages/frontend/src/lib/components/layout/NormalPageTop.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!-- SPDX-FileCopyrightText: 2026 Secineralyr -->
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children?: Snippet;
}

const { children }: Props = $props();
</script>

<div class="page-hero">
<h1 class="page-title">{@render children?.()}</h1>
</div>

<style>
.page-hero {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
text-align: center;
margin-bottom: 80px;
margin-top: 80px;
}

.page-title {
font-family: 'Lexend', sans-serif;
font-size: 2.25rem;
font-weight: 700;
color: #ffffff;
}

/* モバイル表示 */
@media (max-width: 899px) {
.page-hero {
margin-top: 40px;
margin-bottom: 30px;
padding: 15px;
}
.page-title {
font-size: 1.75rem;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function handleClick(): void {
}

/** 値の表示テキスト */
const valueText = $derived(type === 'time' && time !== undefined ? formatTimeDiff(time) : pt !== undefined ? formatPt(pt) : '');
const valueText = $derived(type === 'time' && time !== undefined ? formatTimeDiff(time) : pt === undefined ? '' : formatPt(pt));
</script>

<button
Expand Down
58 changes: 58 additions & 0 deletions packages/frontend/src/lib/components/markdown/Markdown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!-- SPDX-FileCopyrightText: 2026 Secineralyr -->
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
<script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown';
import { markedKatex } from '@humanspeak/svelte-markdown/extensions';
import 'katex/dist/katex.min.css';
import { renderers } from './renderers';

/**
* Markdown 描画コンポーネント
* @description source として渡された markdown 文字列を SvelteMarkdown でパースし、
* 独自スタイル付きの renderers + markedKatex 拡張で描画する
* ページ側でタイトル等を <svelte:head> で別途定義する想定のため、
* 本コンポーネントは本文ブロックのみを担う
*/

interface Props {
source: string;
}

const { source }: Props = $props();

const extensions = [markedKatex()];
</script>

<div class="markdown-body">
<SvelteMarkdown {source} {renderers} {extensions} />
</div>

<style>
.markdown-body {
color: #fff;
line-height: 1.7;
word-wrap: break-word;
}

/* KaTeX が生成する .katex-display は子コンポーネント外の DOM として出力されるため、
:global 経由で横スクロール対応する (画面幅を超える数式に必要) */
.markdown-body :global(.katex-display) {
overflow-x: auto;
overflow-y: hidden;
padding: 6px 0;
margin: 1em 0;
}

/* KaTeX のインライン数式 (.katex) はフォントサイズを本文に揃える。
ライブラリ既定 CSS が 1.21em を強制するため :global で打ち消す必要がある */
.markdown-body :global(.katex) {
font-size: 1.5em;
}

/* モバイル表示 */
@media (max-width: 899px) {
.markdown-body :global(.katex) {
font-size: 1.2em;
}
}
</style>
Loading