website/app/pages/docs/connect/wiiu/[id].vue
kittentm b0acdfa9cf
All checks were successful
ci / ci (22, debian-trixie) (push) Successful in 28s
add wiiu guide & other changes
2026-07-21 03:57:03 +02:00

424 lines
8.8 KiB
Vue

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
const { t } = useLocale()
const parsedHtml = ref('')
let markedParser: ((text: string) => string | Promise<string>) | null = null
type GuideId = 'sssl' | 'aroma'
const selectedCategory = ref<GuideId>('sssl')
const isPageLoading = ref(true)
const guideRules = [
{ id: 'sssl', name: 'SSSL Guide', route: '/docs/connect/wiiu/sssl' },
{ id: 'aroma', name: 'Aroma Guide', route: '/docs/connect/wiiu/aroma' }
] as const satisfies ReadonlyArray<{ id: GuideId, name: string, route: string }>
interface PolicyItem {
name?: string
version?: string
date?: string
md?: string
}
const guideDocs = computed<Partial<Record<GuideId, PolicyItem>>>(() => {
return (t.value.guides?.docs as Partial<Record<GuideId, PolicyItem>> | undefined) ?? {}
})
const activeDoc = computed<PolicyItem>(() => {
return guideDocs.value[selectedCategory.value] || guideDocs.value.sssl || {}
})
onMounted(async () => {
try {
const { marked } = await import('marked')
const { default: markedAlert } = await import('marked-alert')
marked.use(markedAlert())
markedParser = marked.parse
const pathSegments = window.location.pathname.split('/')
const currentId = pathSegments[pathSegments.length - 1]
if (currentId === 'sssl' || currentId === 'aroma') {
selectedCategory.value = currentId
}
await renderMarkdown()
} catch (err) {
console.error(err)
} finally {
isPageLoading.value = false
}
})
const formatDate = (isoString?: string) => {
if (!isoString) return ''
return new Date(isoString).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
const renderMarkdown = async () => {
if (!markedParser || !activeDoc.value.md) return
parsedHtml.value = await markedParser(activeDoc.value.md)
}
const handleCategorySelect = (id: GuideId) => {
selectedCategory.value = id
window.history.pushState({}, '', `/docs/connect/wiiu/${id}`)
}
watch(selectedCategory, () => {
renderMarkdown()
})
watch(() => activeDoc.value.md, () => {
renderMarkdown()
})
</script>
<template>
<div class="settings-page-wrapper">
<div
v-if="isPageLoading"
class="loader-container full-page-loader"
>
<div class="spinner" />
<p>{{ t.guides?.loading }}</p>
</div>
<div
v-else
class="settings-container"
>
<aside class="settings-sidebar">
<div class="sidebar-header archivo-black">
{{ t.guides?.header }}
</div>
<nav class="sidebar-menu">
<button
v-for="guide in guideRules"
:key="guide.id"
class="nav-item"
:class="{ 'active-nav': selectedCategory === guide.id }"
@click="handleCategorySelect(guide.id)"
>
<span>{{ guide.name }}</span>
</button>
<a
href="/docs/error-codes/"
target="_blank"
rel="noopener noreferrer"
class="nav-item external-link"
>
<span>{{ t.guides?.errorCodesRedir }}</span>
<span class="chevron"></span>
</a>
</nav>
</aside>
<main class="settings-main-content">
<section class="settings-panel">
<div class="panel-header archivo-black split-header">
<div class="header-left">
<span>{{ activeDoc.name }}</span>
</div>
<div class="header-right">
<span
v-if="activeDoc.date"
class="last-updated-text"
>
{{ t.guides?.lastUpdated }}{{ formatDate(activeDoc.date) }}
</span>
<span
v-if="activeDoc.version"
class="version-tag"
>
v{{ activeDoc.version }}
</span>
</div>
</div>
<div class="panel-body">
<div
class="markdown-body"
v-html="parsedHtml"
/>
</div>
</section>
</main>
</div>
</div>
</template>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
.archivo-black {
font-family: "Archivo Black", sans-serif;
font-weight: 400;
}
.text-wiiu-blue {
color: #00aeef;
}
.settings-page-wrapper {
min-height: 100vh;
width: 100vw;
background-color: #1d1e1f;
color: #c9d1d9;
padding: 2rem 1rem;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
.settings-container {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 1280px;
margin: 0 auto;
width: 100%;
}
.settings-sidebar {
background-color: #1a1a1f;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 6px;
overflow: hidden;
height: max-content;
}
.sidebar-header {
padding: 0.85rem 1rem;
font-size: 1.1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background-color: rgba(0, 0, 0, 0.15);
}
.sidebar-menu {
display: flex;
flex-direction: column;
}
.nav-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem 1rem;
background: transparent;
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
color: #8b949e;
text-align: left;
cursor: pointer;
font-size: 0.95rem;
transition: all 0.2s;
width: 100%;
text-decoration: none;
box-sizing: border-box;
}
.nav-item:hover {
background: rgba(255, 255, 255, 0.03);
color: #ffffff;
}
.active-nav {
background: rgba(0, 174, 239, 0.1) !important;
border-left: 3px solid #00aeef;
color: #00aeef;
font-weight: 600;
padding-left: calc(1rem - 3px);
}
.chevron {
font-size: 1.25rem;
line-height: 1;
opacity: 0.7;
}
.settings-main-content {
flex: 1;
}
.settings-panel {
background-color: #1a1a1f;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 6px;
overflow: hidden;
}
.split-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.header-right {
display: flex;
align-items: center;
gap: 0.85rem;
}
.last-updated-text {
font-size: 0.8rem;
color: #8b949e;
font-family: system-ui, sans-serif;
font-weight: normal;
text-transform: none;
}
.version-tag {
font-size: 0.85rem;
background-color: rgba(0, 174, 239, 0.15);
color: #00aeef;
padding: 0.2rem 0.6rem;
border-radius: 4px;
border: 1px solid rgba(0, 174, 239, 0.3);
font-family: monospace;
}
.panel-header {
padding: 0.85rem 1.25rem;
font-size: 1.1rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
background-color: rgba(0, 0, 0, 0.15);
}
.panel-body {
padding: 1.5rem;
}
.markdown-body {
line-height: 1.6;
font-size: 1rem;
color: #c9d1d9;
}
.markdown-body :deep(h1),
.markdown-body :deep(h2),
.markdown-body :deep(h3) {
color: #ffffff;
margin-top: 1.5rem;
margin-bottom: 1rem;
font-weight: 600;
}
.markdown-body :deep(p) {
margin-bottom: 1rem;
}
.markdown-body :deep(a) {
color: #00aeef;
text-decoration: underline;
}
.markdown-body :deep(ul) {
list-style-type: disc;
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.markdown-body :deep(ol) {
list-style-type: decimal;
padding-left: 1.5rem;
margin-bottom: 1rem;
}
.markdown-body :deep(li) {
margin-bottom: 0.35rem;
}
.markdown-body :deep(.markdown-alert) {
padding: 0.75rem 1rem 0.75rem 1.25rem;
margin-bottom: 1.5rem;
border-left: 4px solid #30363d;
border-radius: 0 6px 6px 0;
background-color: rgba(255, 255, 255, 0.02);
}
.markdown-body :deep(.markdown-alert-title) {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
font-size: 0.95rem;
text-transform: capitalize;
}
.markdown-body :deep(.markdown-alert-note) {
border-left-color: #00aeef;
background-color: rgba(0, 174, 239, 0.06);
}
.markdown-body :deep(.markdown-alert-note .markdown-alert-title) {
color: #ffffff !important;
}
.markdown-body :deep(.markdown-alert-warning) {
border-left-color: #f0883e;
background-color: rgba(240, 136, 62, 0.06);
}
.markdown-body :deep(.markdown-alert-warning .markdown-alert-title) {
color: #f0883e;
}
.loader-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 3rem 0;
color: #8b949e;
}
.full-page-loader {
min-height: 50vh;
justify-content: center;
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top-color: #00aeef;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@media (min-width: 768px) {
.settings-container {
flex-direction: row;
align-items: flex-start;
}
.settings-sidebar {
width: 280px;
flex-shrink: 0;
}
}
</style>