Point the engine Explore action at the git repository

The engine pages' repo metadata (new in the wiki pages API) drives the
Explore button and card title links, with the wiki page as fallback. The
never-deployed /engines/:slug detail page and its store/api plumbing are
gone, and the engines RSS feed links to the repos too.
This commit is contained in:
2026-08-05 07:36:58 +02:00
parent f2c07c83c5
commit a15f0ce24b
10 changed files with 21 additions and 165 deletions
+4 -24
View File
@@ -15,6 +15,7 @@ interface RawWikiPage {
updatedAt: string
createdAt: string
locale: string
repo?: string | null
}
async function fetchPages(
@@ -67,10 +68,6 @@ const getBlogPage = async (slug: string): Promise<WikiPageContent | null> => {
}
}
// Engine pages live scattered in the wiki tree (infrastructure/, others/, …),
// so the site slug is the last path segment: others/rubbs -> rubbs.
const engineSlug = (path: string): string => path.split('/').filter(Boolean).pop() ?? path
const listEnginePages = async (): Promise<WikiPageWithContent[]> => {
const pages = await fetchPages('engine', { body: true })
return pages.map((p): WikiPageWithContent => ({
@@ -82,27 +79,10 @@ const listEnginePages = async (): Promise<WikiPageWithContent[]> => {
updatedAt: p.updatedAt,
createdAt: p.createdAt,
locale: p.locale,
repo: p.repo ?? null,
}))
}
const getEnginePage = async (slug: string): Promise<WikiPageContent | null> => {
const pages = await fetchPages('engine', { body: true })
const matched = pages.find((p) => engineSlug(p.path) === slug)
if (!matched) return null
return {
id: matched.id,
path: matched.path,
title: matched.title || matched.path,
description: matched.description ?? '',
render: matched.render ?? '',
updatedAt: matched.updatedAt,
createdAt: matched.createdAt,
locale: matched.locale,
}
}
const listHowtoPages = async (): Promise<WikiPage[]> => {
const pages = await fetchPages('howto', { limit: 30 })
return pages.map((p): WikiPage => ({
@@ -116,5 +96,5 @@ const listHowtoPages = async (): Promise<WikiPage[]> => {
}))
}
export { WIKI_BASE, engineSlug }
export default { listBlogPages, getBlogPage, listHowtoPages, listEnginePages, getEnginePage }
export { WIKI_BASE }
export default { listBlogPages, getBlogPage, listHowtoPages, listEnginePages }
-2
View File
@@ -180,8 +180,6 @@ export default {
new: 'New',
explore: 'Explore',
openWiki: 'Open in Wiki',
back: 'Back to Engines',
noContent: 'No content available for this engine.',
},
howtos: {
badge: 'Knowledge Base',
-2
View File
@@ -180,8 +180,6 @@ export default {
new: 'Új',
explore: 'Felfedezés',
openWiki: 'Megnyitás a Wikiben',
back: 'Vissza az Engine-ekhez',
noContent: 'Ehhez az engine-hez nincs elérhető tartalom.',
},
howtos: {
badge: 'Tudásbázis',
@@ -6,6 +6,7 @@ export interface WikiPage {
updatedAt: string
createdAt: string
locale: string
repo?: string | null
}
export interface WikiPageWithContent extends WikiPage {
@@ -1,110 +0,0 @@
<template>
<div class="engine-show-container">
<header class="engine-show-header">
<div class="engine-show-header-decor">
<div class="hero-decor-blob -top-24 -left-24 h-96 w-96 bg-emerald-600"></div>
<div class="hero-decor-blob -bottom-24 -right-24 h-96 w-96 bg-teal-600"></div>
</div>
<div class="engine-hero-container">
<RouterLink to="/engines" class="back-link"><i class="fa-solid fa-arrow-left mr-1"></i> {{ t('engines.back') }}</RouterLink>
<template v-if="pageContent">
<h1 class="hero-title mt-4">{{ pageContent.title }}</h1>
<p class="hero-subtitle mb-6">{{ pageContent.description }}</p>
<div class="engine-show-meta">
<span><i class="fa-solid fa-clock-rotate-left mr-1"></i>{{ formatDateTime(pageContent.updatedAt) }}</span>
<a :href="`${WIKI_BASE}/${pageContent.path}`" target="_blank" rel="noopener" class="engine-show-wiki-link">
<i class="fa-solid fa-book mr-1"></i>{{ t('engines.openWiki') }}
</a>
</div>
</template>
</div>
</header>
<main class="engine-show-main">
<div v-if="error" class="error-banner" role="alert">
<i class="fa-solid fa-triangle-exclamation text-2xl text-yellow-500"></i>
<div>
<h3 class="error-banner-title">{{ t('engines.errorTitle') }}</h3>
<p class="error-banner-desc">{{ error }}</p>
</div>
</div>
<article v-else-if="pageContent" class="engine-doc-card">
<div class="engine-doc-content">
<div v-if="pageContent.render" class="wiki-content max-w-none text-gray-700 leading-relaxed" v-html="sanitize(pageContent.render)" />
<div v-else class="engine-doc-fallback">{{ t('engines.noContent') }}</div>
</div>
</article>
<SkeletonCard v-else-if="loading" :count="1" />
</main>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { RouterLink, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { formatDateTime } from '../../lib/dateFormat'
import { sanitize } from '../../lib/sanitize'
import { WIKI_BASE } from '../../api/wiki.api'
import { useEnginesStore } from '../../stores/engines.store'
import SkeletonCard from '../../components/SkeletonCard.vue'
const { t } = useI18n()
const route = useRoute()
const store = useEnginesStore()
const { currentPage: pageContent, pageLoading: loading, pageError: error } = storeToRefs(store)
onMounted(() => store.fetchPage(route.params.slug as string))
</script>
<style scoped>
.engine-show-container {
@apply bg-slate-950 min-h-screen pb-20;
}
.engine-show-header {
@apply relative overflow-hidden py-24 text-white text-center bg-slate-900;
}
.engine-show-header-decor {
@apply absolute inset-0 opacity-30;
}
.engine-hero-container {
@apply relative z-10 max-w-4xl mx-auto px-4;
}
.back-link {
@apply inline-block text-emerald-300 hover:text-white transition-colors font-medium;
}
.hero-title {
@apply text-4xl md:text-5xl font-black mb-4 leading-tight;
}
.hero-subtitle {
@apply text-xl text-emerald-100 font-medium;
}
.engine-show-meta {
@apply flex items-center justify-center gap-6 text-sm font-semibold uppercase tracking-wider text-slate-400;
}
.engine-show-wiki-link {
@apply text-emerald-400 hover:text-emerald-300 transition-colors normal-case tracking-normal;
}
.engine-show-main {
@apply max-w-4xl mx-auto px-4 md:px-8 -mt-12 relative z-10;
}
.engine-doc-card {
@apply bg-white rounded-2xl shadow-xl border border-slate-800 overflow-hidden;
}
.engine-doc-content {
@apply p-6 md:p-12;
}
.engine-doc-fallback {
@apply italic text-gray-400;
}
.error-banner {
@apply max-w-7xl mx-auto bg-red-50 border-l-4 border-red-500 p-6 rounded-r-xl shadow-lg mb-12 flex items-start gap-4;
}
.error-banner-title { @apply text-red-800 font-bold text-lg; }
.error-banner-desc { @apply text-red-700 mt-1; }
</style>
@@ -48,17 +48,18 @@
</div>
<h2 class="engine-card-title">
<RouterLink :to="getPermalink(page.path)">{{ page.title }}</RouterLink>
<a :href="exploreUrl(page)" target="_blank" rel="noopener">{{ page.title }}</a>
</h2>
<p v-if="page.description" class="engine-card-desc">{{ page.description }}</p>
<p v-if="page.content" class="engine-card-preview">{{ getCleanPreview(page.content) }}</p>
<div class="engine-card-actions">
<RouterLink :to="getPermalink(page.path)" class="engine-explore-btn">
<a :href="exploreUrl(page)" target="_blank" rel="noopener" class="engine-explore-btn">
<i class="fa-solid fa-code-branch text-sm"></i>
{{ t('engines.explore') }}
<i class="fa-solid fa-arrow-right text-sm"></i>
</RouterLink>
</a>
<a :href="`${WIKI_BASE}/${page.path}`" target="_blank" rel="noopener" class="engine-wiki-link">
<i class="fa-solid fa-book mr-1"></i>{{ t('engines.openWiki') }}
</a>
@@ -73,18 +74,23 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { formatDateTime } from '../../lib/dateFormat'
import { WIKI_BASE } from '../../api/wiki.api'
import { useEnginesStore } from '../../stores/engines.store'
import type { WikiPageWithContent } from '../../lib/interfaces/wiki.interface'
import SkeletonCard from '../../components/SkeletonCard.vue'
const { t } = useI18n()
const store = useEnginesStore()
const { pages: enginePages, loading, error } = storeToRefs(store)
const { isNew, getPermalink, getCleanPreview } = store
const { isNew, getCleanPreview } = store
// Explore points at the engine's git repository (from wiki metadata);
// pages without one fall back to their wiki page.
const exploreUrl = (page: WikiPageWithContent): string =>
page.repo || `${WIKI_BASE}/${page.path}`
onMounted(() => store.fetch())
</script>
@@ -2,5 +2,4 @@ import type { RouteRecordRaw } from 'vue-router'
export const enginesRouter: RouteRecordRaw[] = [
{ path: '/engines', name: 'enginesIndex', component: () => import('../page/engines/EnginesIndexPage.vue') },
{ path: '/engines/:slug', name: 'engineShow', component: () => import('../page/engines/EngineShowPage.vue') },
]
+3 -19
View File
@@ -1,40 +1,24 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import wikiApi, { engineSlug } from '../api/wiki.api'
import wikiApi from '../api/wiki.api'
import { isNew } from '../lib/softwareUtils'
import { useLoadable } from '../composables/useLoadable'
import type { WikiPageWithContent, WikiPageContent } from '../lib/interfaces/wiki.interface'
import type { WikiPageWithContent } from '../lib/interfaces/wiki.interface'
export const useEnginesStore = defineStore('engines', () => {
const pages = ref<WikiPageWithContent[]>([])
const { loading, error, withCache, invalidate } = useLoadable()
const currentPage = ref<WikiPageContent | null>(null)
const { loading: pageLoading, error: pageError, withCache: withPageCache } = useLoadable(0)
async function fetch() {
await withCache(async () => {
pages.value = await wikiApi.listEnginePages()
})
}
async function fetchPage(slug: string) {
currentPage.value = null
await withPageCache(async () => {
const result = await wikiApi.getEnginePage(slug)
if (!result) throw new Error('Engine not found')
currentPage.value = result
})
}
function getPermalink(path: string): string {
return `/engines/${engineSlug(path)}`
}
function getCleanPreview(content: string): string {
if (!content) return ''
return content.replace(/[#*`_[\]()>|-]/g, '').replace(/\s+/g, ' ').trim().slice(0, 260) + '...'
}
return { pages, loading, error, currentPage, pageLoading, pageError, fetch, fetchPage, isNew, getPermalink, getCleanPreview, invalidate }
return { pages, loading, error, fetch, isNew, getCleanPreview, invalidate }
})