This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { describe, it, expect, afterEach } from 'vitest'
|
||||
import { i18n } from '../../i18n'
|
||||
import { WIKI_BASE, wikiUrl } from '../wiki.api'
|
||||
|
||||
afterEach(() => {
|
||||
i18n.global.locale.value = 'en'
|
||||
})
|
||||
|
||||
describe('wikiUrl', () => {
|
||||
it('leaves English unprefixed', () => {
|
||||
expect(wikiUrl('development/godot', 'en')).toBe(`${WIKI_BASE}/development/godot`)
|
||||
})
|
||||
|
||||
it('prefixes Hungarian with /hu', () => {
|
||||
expect(wikiUrl('development/godot', 'hu')).toBe(`${WIKI_BASE}/hu/development/godot`)
|
||||
})
|
||||
|
||||
it('accepts a route that already starts with a slash', () => {
|
||||
expect(wikiUrl('/development/godot', 'hu')).toBe(`${WIKI_BASE}/hu/development/godot`)
|
||||
})
|
||||
|
||||
it('returns the wiki root when no page is given', () => {
|
||||
expect(wikiUrl('', 'hu')).toBe(`${WIKI_BASE}/hu`)
|
||||
expect(wikiUrl()).toBe(WIKI_BASE)
|
||||
})
|
||||
|
||||
it('falls back to English for an unsupported language', () => {
|
||||
expect(wikiUrl('development/godot', 'de')).toBe(`${WIKI_BASE}/development/godot`)
|
||||
})
|
||||
|
||||
it('follows the active interface language when none is passed', () => {
|
||||
i18n.global.locale.value = 'hu'
|
||||
expect(wikiUrl('development/godot')).toBe(`${WIKI_BASE}/hu/development/godot`)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,27 @@
|
||||
import type { WikiPage, WikiPageWithContent, WikiPageContent } from '../lib/interfaces/wiki.interface'
|
||||
|
||||
import { CONFIG } from '../lib/config'
|
||||
import { i18n } from '../i18n'
|
||||
|
||||
const WIKI_BASE = CONFIG.wikiBase
|
||||
|
||||
const WIKI_LOCALES = ['en', 'hu'] as const
|
||||
const DEFAULT_WIKI_LOCALE = 'en'
|
||||
|
||||
function resolveLocale(locale?: string): string {
|
||||
const value = (locale ?? String(i18n.global.locale.value)).toLowerCase()
|
||||
return (WIKI_LOCALES as readonly string[]).includes(value) ? value : DEFAULT_WIKI_LOCALE
|
||||
}
|
||||
|
||||
// Grav serves English unprefixed and Hungarian under /hu, so a page link is the
|
||||
// base plus the prefix plus the language-neutral route.
|
||||
function wikiUrl(path = '', locale?: string): string {
|
||||
const lang = resolveLocale(locale)
|
||||
const prefix = lang === DEFAULT_WIKI_LOCALE ? '' : `/${lang}`
|
||||
const route = path ? `/${path.replace(/^\/+/, '')}` : ''
|
||||
return `${WIKI_BASE}${prefix}${route}`
|
||||
}
|
||||
|
||||
interface RawWikiPage {
|
||||
id: number
|
||||
path: string
|
||||
@@ -22,9 +40,9 @@ const HIGHLIGHTED_TAG = 'highlighted'
|
||||
|
||||
async function fetchPages(
|
||||
tag: string,
|
||||
opts: { body?: boolean; limit?: number } = {},
|
||||
opts: { body?: boolean; limit?: number; locale?: string } = {},
|
||||
): Promise<RawWikiPage[]> {
|
||||
const params = new URLSearchParams({ tag })
|
||||
const params = new URLSearchParams({ tag, lang: resolveLocale(opts.locale) })
|
||||
if (opts.body) params.set('body', '1')
|
||||
if (opts.limit) params.set('limit', String(opts.limit))
|
||||
|
||||
@@ -34,8 +52,8 @@ async function fetchPages(
|
||||
return json?.pages ?? []
|
||||
}
|
||||
|
||||
const listBlogPages = async (): Promise<WikiPageWithContent[]> => {
|
||||
const pages = await fetchPages('blog', { body: true })
|
||||
const listBlogPages = async (locale?: string): Promise<WikiPageWithContent[]> => {
|
||||
const pages = await fetchPages('blog', { body: true, locale })
|
||||
return pages.map((p): WikiPageWithContent => ({
|
||||
id: p.id,
|
||||
path: p.path,
|
||||
@@ -48,8 +66,8 @@ const listBlogPages = async (): Promise<WikiPageWithContent[]> => {
|
||||
}))
|
||||
}
|
||||
|
||||
const getBlogPage = async (slug: string): Promise<WikiPageContent | null> => {
|
||||
const pages = await fetchPages('blog', { body: true })
|
||||
const getBlogPage = async (slug: string, locale?: string): Promise<WikiPageContent | null> => {
|
||||
const pages = await fetchPages('blog', { body: true, locale })
|
||||
|
||||
const matched = pages.find((p) => {
|
||||
const pageSlug = p.path.startsWith('blog/') ? p.path.replace('blog/', '') : p.path
|
||||
@@ -70,8 +88,8 @@ const getBlogPage = async (slug: string): Promise<WikiPageContent | null> => {
|
||||
}
|
||||
}
|
||||
|
||||
const listEnginePages = async (): Promise<WikiPageWithContent[]> => {
|
||||
const pages = await fetchPages('engine', { body: true })
|
||||
const listEnginePages = async (locale?: string): Promise<WikiPageWithContent[]> => {
|
||||
const pages = await fetchPages('engine', { body: true, locale })
|
||||
return pages
|
||||
.filter((p) => (p.tags ?? []).includes(HIGHLIGHTED_TAG))
|
||||
.map((p): WikiPageWithContent => ({
|
||||
@@ -87,8 +105,8 @@ const listEnginePages = async (): Promise<WikiPageWithContent[]> => {
|
||||
}))
|
||||
}
|
||||
|
||||
const listHowtoPages = async (): Promise<WikiPage[]> => {
|
||||
const pages = await fetchPages('howto', { limit: 30 })
|
||||
const listHowtoPages = async (locale?: string): Promise<WikiPage[]> => {
|
||||
const pages = await fetchPages('howto', { limit: 30, locale })
|
||||
return pages.map((p): WikiPage => ({
|
||||
id: p.id,
|
||||
path: p.path,
|
||||
@@ -100,5 +118,5 @@ const listHowtoPages = async (): Promise<WikiPage[]> => {
|
||||
}))
|
||||
}
|
||||
|
||||
export { WIKI_BASE }
|
||||
export { WIKI_BASE, WIKI_LOCALES, DEFAULT_WIKI_LOCALE, wikiUrl }
|
||||
export default { listBlogPages, getBlogPage, listHowtoPages, listEnginePages }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { watch } from 'vue'
|
||||
import { i18n } from '../i18n'
|
||||
|
||||
// Wiki content is fetched per language, so anything cached from the wiki has to
|
||||
// be dropped and re-read when the visitor switches EN/HU.
|
||||
export function useLocaleReload(reload: () => void | Promise<void>) {
|
||||
watch(
|
||||
() => i18n.global.locale.value,
|
||||
() => {
|
||||
void reload()
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
</nav>
|
||||
|
||||
<div class="footer-meta">
|
||||
<a :href="WIKI_BASE" target="_blank" rel="noopener noreferrer" class="footer-link">{{ t('footer.wiki') }}</a>
|
||||
<a :href="wikiUrl()" target="_blank" rel="noopener noreferrer" class="footer-link">{{ t('footer.wiki') }}</a>
|
||||
<a :href="GIT_BASE" target="_blank" rel="noopener noreferrer" class="footer-link">{{ t('footer.git') }}</a>
|
||||
<span class="footer-rss">
|
||||
<Rss v-bind="ICON" aria-hidden="true" />
|
||||
@@ -89,7 +89,7 @@ import { Menu, Moon, Rss, Sun, X } from 'lucide-vue-next'
|
||||
import { useUiStore } from '../stores/ui.store'
|
||||
import { getCookie } from '../lib/cookie'
|
||||
import { ICON, ICON_CONTROL } from '../components/icons'
|
||||
import { WIKI_BASE } from '../api/wiki.api'
|
||||
import { wikiUrl } from '../api/wiki.api'
|
||||
|
||||
const GIT_BASE = 'https://git.teletypegames.org'
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<a :href="exploreUrl(page)" target="_blank" rel="noopener" class="btn-accent">
|
||||
{{ t('engines.explore') }}
|
||||
</a>
|
||||
<a :href="`${WIKI_BASE}/${page.path}`" target="_blank" rel="noopener" class="btn-ghost">
|
||||
<a :href="wikiUrl(page.path)" target="_blank" rel="noopener" class="btn-ghost">
|
||||
{{ t('engines.openWiki') }}
|
||||
</a>
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { WIKI_BASE } from '../../api/wiki.api'
|
||||
import { wikiUrl } from '../../api/wiki.api'
|
||||
import { useEnginesStore, getEngineDigest } from '../../stores/engines.store'
|
||||
import type { WikiPageWithContent } from '../../lib/interfaces/wiki.interface'
|
||||
import SkeletonCard from '../../components/SkeletonCard.vue'
|
||||
@@ -79,7 +79,7 @@ const cards = computed(() =>
|
||||
)
|
||||
|
||||
const exploreUrl = (page: WikiPageWithContent): string =>
|
||||
page.repo || `${WIKI_BASE}/${page.path}`
|
||||
page.repo || wikiUrl(page.path)
|
||||
|
||||
onMounted(() => store.fetch())
|
||||
</script>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<h1 class="hero-title">{{ t('howtos.title') }}</h1>
|
||||
<p class="hero-subtitle">{{ t('howtos.subtitle') }}</p>
|
||||
<div class="hero-actions">
|
||||
<a :href="WIKI_BASE" target="_blank" rel="noopener noreferrer" class="btn-accent">
|
||||
<a :href="wikiUrl()" target="_blank" rel="noopener noreferrer" class="btn-accent">
|
||||
{{ t('howtos.knowledgeBase') }}
|
||||
</a>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@
|
||||
<a
|
||||
v-for="page in recentPages"
|
||||
:key="page.id"
|
||||
:href="`${WIKI_BASE}/${page.path}`"
|
||||
:href="wikiUrl(page.path)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="howto-card"
|
||||
@@ -50,7 +50,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="!error && recentPages.length > 0" class="focus-row">
|
||||
<a :href="WIKI_BASE" target="_blank" rel="noopener noreferrer" class="link text-sm">
|
||||
<a :href="wikiUrl()" target="_blank" rel="noopener noreferrer" class="link text-sm">
|
||||
{{ t('howtos.browseWiki') }}
|
||||
</a>
|
||||
</div>
|
||||
@@ -63,7 +63,7 @@ import { onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { formatDateTime } from '../../lib/dateFormat'
|
||||
import { WIKI_BASE } from '../../api/wiki.api'
|
||||
import { wikiUrl } from '../../api/wiki.api'
|
||||
import { useHowtosStore } from '../../stores/howtos.store'
|
||||
import SkeletonCard from '../../components/SkeletonCard.vue'
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<a :href="client.releasesUrl" target="_blank" rel="noopener noreferrer" class="btn-accent">
|
||||
{{ t('stores.clientDownload') }}
|
||||
</a>
|
||||
<a :href="client.wikiUrl" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
<a :href="wikiUrl(client.wikiPath)" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
{{ t('stores.docs') }}
|
||||
</a>
|
||||
<a :href="client.repoUrl" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
@@ -82,7 +82,7 @@
|
||||
</div>
|
||||
|
||||
<div class="st-links st-links-after">
|
||||
<a :href="current.wikiUrl" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
<a :href="wikiUrl(current.wikiPath)" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
{{ t('stores.docs') }}
|
||||
</a>
|
||||
<a :href="current.repoUrl" target="_blank" rel="noopener noreferrer" class="btn-ghost">
|
||||
@@ -116,7 +116,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { CONFIG } from '../../lib/config'
|
||||
import { wikiUrl } from '../../api/wiki.api'
|
||||
import CommandBlock from './CommandBlock.vue'
|
||||
import { BrandIcon } from '../../components/icons'
|
||||
import clientScreenshot from '../../assets/warpengine-client.webp'
|
||||
@@ -130,7 +130,7 @@ const FORGE = 'https://git.teletypegames.org/stores'
|
||||
const CLIENT = {
|
||||
repoUrl: `${FORGE}/warp-engine-client`,
|
||||
releasesUrl: `${FORGE}/warp-engine-client/releases`,
|
||||
wikiUrl: `${CONFIG.wikiBase}/stores/warp-engine-client`,
|
||||
wikiPath: 'stores/warp-engine-client',
|
||||
}
|
||||
|
||||
type DeviceId = 'batocera' | 'retroarch'
|
||||
@@ -150,7 +150,7 @@ const devices = [
|
||||
id: 'batocera' as DeviceId,
|
||||
projectUrl: 'https://batocera.org',
|
||||
repoUrl: `${FORGE}/ttg-batocera-store`,
|
||||
wikiUrl: `${CONFIG.wikiBase}/stores/ttg-batocera-store`,
|
||||
wikiPath: 'stores/ttg-batocera-store',
|
||||
platforms: [
|
||||
...CARTRIDGE_PLATFORMS,
|
||||
{ platform: 'ebitengine', label: 'Ebitengine', ext: '.zip', note: 'x86_64 · ARM64' },
|
||||
@@ -172,7 +172,7 @@ const devices = [
|
||||
id: 'retroarch' as DeviceId,
|
||||
projectUrl: 'https://www.retroarch.com',
|
||||
repoUrl: `${FORGE}/ttg-retroarch-store`,
|
||||
wikiUrl: `${CONFIG.wikiBase}/stores/ttg-retroarch-store`,
|
||||
wikiPath: 'stores/ttg-retroarch-store',
|
||||
platforms: CARTRIDGE_PLATFORMS,
|
||||
installCmd: `curl -fsSL ${FORGE}/ttg-retroarch-store/raw/branch/master/install.sh | sh`,
|
||||
afterInstallCmd: '',
|
||||
|
||||
@@ -74,3 +74,31 @@ describe('getEngineDigest', () => {
|
||||
expect(getEngineDigest('')).toEqual({ intro: '', highlightsTitle: '', highlights: [] })
|
||||
})
|
||||
})
|
||||
|
||||
const SAMPLE_MARKDOWN_HU = `
|
||||
> A példamotor egy **minta** keretrendszer, ami markdownból landing-kártyát csinál.
|
||||
|
||||
# Amit kapsz
|
||||
|
||||
- **Első funkció**: rövid magyarázattal.
|
||||
- Sima felsorolás, félkövér nyitás nélkül.
|
||||
|
||||
# Későbbi szakasz
|
||||
|
||||
- **Nem kerül be**: egy lista egy későbbi cím alatt.
|
||||
`
|
||||
|
||||
describe('getEngineDigest, Hungarian page', () => {
|
||||
it('recognises the Hungarian highlights heading', () => {
|
||||
const digest = getEngineDigest(SAMPLE_MARKDOWN_HU)
|
||||
expect(digest.highlightsTitle).toBe('Amit kapsz')
|
||||
expect(digest.highlights).toHaveLength(2)
|
||||
expect(digest.highlights[0]).toEqual({ title: 'Első funkció', text: 'rövid magyarázattal.' })
|
||||
})
|
||||
|
||||
it('still stops at the next heading', () => {
|
||||
expect(getEngineDigest(SAMPLE_MARKDOWN_HU).highlights.map((h) => h.title)).not.toContain(
|
||||
'Nem kerül be',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
||||
import wikiApi from '../api/wiki.api'
|
||||
import { isNew } from '../lib/softwareUtils'
|
||||
import { useLoadable } from '../composables/useLoadable'
|
||||
import { useLocaleReload } from '../composables/useLocaleReload'
|
||||
import type { WikiPageWithContent, WikiPageContent } from '../lib/interfaces/wiki.interface'
|
||||
|
||||
export const useBlogStore = defineStore('blog', () => {
|
||||
@@ -18,7 +19,10 @@ export const useBlogStore = defineStore('blog', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const currentSlug = ref<string | null>(null)
|
||||
|
||||
async function fetchPage(slug: string) {
|
||||
currentSlug.value = slug
|
||||
currentPage.value = null
|
||||
await withPageCache(async () => {
|
||||
const result = await wikiApi.getBlogPage(slug)
|
||||
@@ -37,5 +41,11 @@ export const useBlogStore = defineStore('blog', () => {
|
||||
return content.replace(/[#*`_[\]()]/g, '').trim().slice(0, 300) + '...'
|
||||
}
|
||||
|
||||
useLocaleReload(async () => {
|
||||
invalidate()
|
||||
await fetch()
|
||||
if (currentSlug.value) await fetchPage(currentSlug.value)
|
||||
})
|
||||
|
||||
return { pages, loading, error, currentPage, pageLoading, pageError, fetch, fetchPage, isNew, getPermalink, getCleanPreview, invalidate }
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import wikiApi from '../api/wiki.api'
|
||||
import { useLoadable } from '../composables/useLoadable'
|
||||
import { useLocaleReload } from '../composables/useLocaleReload'
|
||||
import type { WikiPageWithContent } from '../lib/interfaces/wiki.interface'
|
||||
|
||||
export interface EngineHighlight {
|
||||
@@ -15,7 +16,7 @@ export interface EngineDigest {
|
||||
highlights: EngineHighlight[]
|
||||
}
|
||||
|
||||
const HIGHLIGHTS_HEADING = 'what you get'
|
||||
const HIGHLIGHTS_HEADINGS = ['what you get', 'amit kapsz']
|
||||
const MAX_HIGHLIGHTS = 6
|
||||
|
||||
function stripInline(md: string): string {
|
||||
@@ -39,7 +40,7 @@ export function getEngineDigest(content: string): EngineDigest {
|
||||
if (heading) {
|
||||
if (inHighlights) break
|
||||
const title = stripInline(heading[1])
|
||||
if (title.toLowerCase() !== HIGHLIGHTS_HEADING) break
|
||||
if (!HIGHLIGHTS_HEADINGS.includes(title.toLowerCase())) break
|
||||
inHighlights = true
|
||||
digest.highlightsTitle = title
|
||||
continue
|
||||
@@ -81,5 +82,10 @@ export const useEnginesStore = defineStore('engines', () => {
|
||||
return content.replace(/[#*`_[\]()>|-]/g, '').replace(/\s+/g, ' ').trim().slice(0, 260) + '...'
|
||||
}
|
||||
|
||||
useLocaleReload(async () => {
|
||||
invalidate()
|
||||
await fetch()
|
||||
})
|
||||
|
||||
return { pages, loading, error, fetch, getCleanPreview, getEngineDigest, invalidate }
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
||||
import wikiApi from '../api/wiki.api'
|
||||
import { isNew } from '../lib/softwareUtils'
|
||||
import { useLoadable } from '../composables/useLoadable'
|
||||
import { useLocaleReload } from '../composables/useLocaleReload'
|
||||
import type { WikiPage } from '../lib/interfaces/wiki.interface'
|
||||
|
||||
export const useHowtosStore = defineStore('howtos', () => {
|
||||
@@ -15,5 +16,10 @@ export const useHowtosStore = defineStore('howtos', () => {
|
||||
})
|
||||
}
|
||||
|
||||
useLocaleReload(async () => {
|
||||
invalidate()
|
||||
await fetch()
|
||||
})
|
||||
|
||||
return { pages, loading, error, fetch, isNew, invalidate }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user