feat(05-02): add Columns/Details/Video/Badge MDC components + full showcase article
This commit is contained in:
@@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
color?: 'gray' | 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange'
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), { color: 'gray' })
|
||||||
|
|
||||||
|
const colorClass = {
|
||||||
|
gray: 'bg-neutral-100 text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300',
|
||||||
|
blue: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
||||||
|
green: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300',
|
||||||
|
red: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
||||||
|
yellow: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300',
|
||||||
|
purple: 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300',
|
||||||
|
orange: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
'inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium font-mono',
|
||||||
|
colorClass[props.color],
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
cols?: 2 | 3 | 4
|
||||||
|
gap?: 'sm' | 'md' | 'lg'
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
cols: 2,
|
||||||
|
gap: 'md',
|
||||||
|
})
|
||||||
|
|
||||||
|
const gridClass = computed(() => {
|
||||||
|
const cols = { 2: 'md:grid-cols-2', 3: 'md:grid-cols-3', 4: 'md:grid-cols-4' }[props.cols]
|
||||||
|
const gap = { sm: 'gap-4', md: 'gap-6', lg: 'gap-10' }[props.gap]
|
||||||
|
return `not-prose my-6 grid grid-cols-1 ${cols} ${gap}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="gridClass">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
summary?: string
|
||||||
|
open?: boolean
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
summary: 'Voir plus',
|
||||||
|
open: false,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<details
|
||||||
|
:open="props.open"
|
||||||
|
class="not-prose my-4 rounded-lg border border-neutral-200 dark:border-neutral-800 overflow-hidden"
|
||||||
|
>
|
||||||
|
<summary
|
||||||
|
class="flex cursor-pointer select-none items-center justify-between px-4 py-3
|
||||||
|
text-sm font-medium text-neutral-700 dark:text-neutral-300
|
||||||
|
hover:bg-neutral-50 dark:hover:bg-neutral-800/50 transition-colors list-none"
|
||||||
|
>
|
||||||
|
{{ props.summary }}
|
||||||
|
<svg
|
||||||
|
class="size-4 shrink-0 text-neutral-400 transition-transform details-arrow"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</summary>
|
||||||
|
<div class="px-4 py-3 prose prose-neutral dark:prose-invert max-w-none
|
||||||
|
prose-code:before:content-none prose-code:after:content-none
|
||||||
|
prose-pre:p-0 prose-pre:bg-transparent text-sm">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
details[open] .details-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
src: string
|
||||||
|
title?: string
|
||||||
|
aspect?: '16/9' | '4/3' | '1/1'
|
||||||
|
autoplay?: boolean
|
||||||
|
loop?: boolean
|
||||||
|
muted?: boolean
|
||||||
|
}
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
title: '',
|
||||||
|
aspect: '16/9',
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
muted: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const isYoutube = computed(() =>
|
||||||
|
/youtube\.com|youtu\.be/.test(props.src)
|
||||||
|
)
|
||||||
|
|
||||||
|
const youtubeId = computed(() => {
|
||||||
|
const match = props.src.match(
|
||||||
|
/(?:youtube\.com\/(?:watch\?v=|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
||||||
|
)
|
||||||
|
return match?.[1] ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const youtubeUrl = computed(() => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
...(props.autoplay ? { autoplay: '1' } : {}),
|
||||||
|
...(props.loop ? { loop: '1', playlist: youtubeId.value } : {}),
|
||||||
|
modestbranding: '1',
|
||||||
|
rel: '0',
|
||||||
|
})
|
||||||
|
return `https://www.youtube.com/embed/${youtubeId.value}?${params}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const aspectClass = computed(() => ({
|
||||||
|
'16/9': 'aspect-video',
|
||||||
|
'4/3': 'aspect-[4/3]',
|
||||||
|
'1/1': 'aspect-square',
|
||||||
|
}[props.aspect]))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<figure class="not-prose my-6 w-full overflow-hidden rounded-lg bg-black">
|
||||||
|
<!-- YouTube embed -->
|
||||||
|
<iframe
|
||||||
|
v-if="isYoutube"
|
||||||
|
:src="youtubeUrl"
|
||||||
|
:title="props.title"
|
||||||
|
:class="['w-full', aspectClass]"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||||
|
allowfullscreen
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<!-- Local video -->
|
||||||
|
<video
|
||||||
|
v-else
|
||||||
|
:src="props.src"
|
||||||
|
:title="props.title"
|
||||||
|
:class="['w-full', aspectClass]"
|
||||||
|
:autoplay="props.autoplay"
|
||||||
|
:loop="props.loop"
|
||||||
|
:muted="props.muted || props.autoplay"
|
||||||
|
controls
|
||||||
|
playsinline
|
||||||
|
/>
|
||||||
|
<figcaption
|
||||||
|
v-if="props.title"
|
||||||
|
class="bg-neutral-900 px-4 py-2 text-center text-xs text-neutral-400 italic"
|
||||||
|
>
|
||||||
|
{{ props.title }}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</template>
|
||||||
@@ -1,49 +1,238 @@
|
|||||||
---
|
---
|
||||||
title: "Test Kotlin Syntax Highlighting"
|
title: "Guide du format Markdown"
|
||||||
description: "Article de test pour valider le renderer @nuxt/content"
|
description: "Référence complète de tous les éléments et composants disponibles dans les articles"
|
||||||
date: "2026-04-21"
|
date: "2026-04-21"
|
||||||
tags: ["kotlin", "hytale", "test"]
|
tags: ["guide", "markdown", "mdc"]
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bloc de code Kotlin
|
## Typographie de base
|
||||||
|
|
||||||
|
Paragraphe normal avec du **gras**, de l'*italique*, du ~~barré~~ et du `code inline`.
|
||||||
|
|
||||||
|
Lien simple : [killiandalcin.fr](https://killiandalcin.fr)
|
||||||
|
|
||||||
|
Citation :
|
||||||
|
|
||||||
|
> Les meilleurs plugins Hytale naissent d'une obsession pour les détails.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blocs de code
|
||||||
|
|
||||||
|
Bloc Kotlin avec coloration syntaxique :
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun main() {
|
fun createPlugin(name: String): HytalePlugin {
|
||||||
println("Hello, Hytale!")
|
return HytalePlugin.builder()
|
||||||
}
|
.name(name)
|
||||||
|
.version("1.0.0")
|
||||||
fun createPlugin(name: String): Plugin {
|
.onLoad { println("Plugin $name loaded!") }
|
||||||
return Plugin(name = name, version = "1.0.0")
|
.build()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Image optimisée
|
TypeScript :
|
||||||
|
|
||||||

|
```typescript
|
||||||
|
interface PluginConfig {
|
||||||
|
name: string
|
||||||
|
version: `${number}.${number}.${number}`
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
## Tableau
|
const config: PluginConfig = {
|
||||||
|
name: 'hytale-core',
|
||||||
|
version: '1.0.0',
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| Fonctionnalité | Statut | Notes |
|
Shell :
|
||||||
|----------------|--------|-------|
|
|
||||||
| Syntax highlighting | ✅ Actif | Kotlin, Java, TypeScript, Shell |
|
```bash
|
||||||
| Images optimisées | ✅ Actif | Via NuxtImg (lazy + srcset) |
|
pnpm install @hytale/sdk
|
||||||
| Tableaux | ✅ Actif | Rendu prose |
|
pnpm run build
|
||||||
| Callouts | ✅ Actif | MDC ::alert{type} |
|
docker build -t portfolio:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
**Pleine largeur (défaut) :**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Centrée avec taille fixe et légende :**
|
||||||
|
|
||||||
|
{align="center" width="120" caption="Logo Killian' DAL-CIN"}
|
||||||
|
|
||||||
|
**Flottant à gauche :**
|
||||||
|
|
||||||
|
{align="left" caption="Float left"}
|
||||||
|
|
||||||
|
Texte qui entoure l'image flottante. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.
|
||||||
|
|
||||||
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
|
**Flottant à droite :**
|
||||||
|
|
||||||
|
{align="right" caption="Float right"}
|
||||||
|
|
||||||
|
Texte qui entoure l'image flottante à droite. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||||
|
|
||||||
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
|
**Classes Tailwind directes :**
|
||||||
|
|
||||||
|
{.w-16 .mx-auto .block}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tableaux
|
||||||
|
|
||||||
|
| Composant | Syntaxe | Usage |
|
||||||
|
|-----------|---------|-------|
|
||||||
|
| Alert | `::alert{type="info"}` | Callouts colorés |
|
||||||
|
| Image | `{align="left"}` | Photos flottantes |
|
||||||
|
| Columns | `::columns{cols=2}` | Mise en page |
|
||||||
|
| Details | `::details{summary="Voir"}` | Contenu repliable |
|
||||||
|
| Video | `::video{src="..."}` | Embed YouTube/local |
|
||||||
|
| Badge | `:badge[text]{color="blue"}` | Tags inline |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Callouts
|
## Callouts
|
||||||
|
|
||||||
::alert{type="info"}
|
::alert{type="info"}
|
||||||
Ceci est un callout d'information.
|
**Info** — Message informatif. Supporte le **gras** et le `code inline`.
|
||||||
::
|
::
|
||||||
|
|
||||||
::alert{type="warning"}
|
::alert{type="warning"}
|
||||||
Ceci est un avertissement.
|
**Attention** — Vérifiez la compatibilité Nuxt 4 avant d'installer un module.
|
||||||
::
|
::
|
||||||
|
|
||||||
::alert{type="tip"}
|
::alert{type="tip"}
|
||||||
Conseil pratique de développement Kotlin.
|
**Astuce** — Utilisez `pnpm` plutôt que `npm` pour les projets Nuxt (résolution plus rapide).
|
||||||
::
|
::
|
||||||
|
|
||||||
::alert{type="danger"}
|
::alert{type="danger"}
|
||||||
Erreur critique — à ne pas ignorer.
|
**Danger** — Ne jamais committer de clés API ou secrets en clair.
|
||||||
::
|
::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Colonnes
|
||||||
|
|
||||||
|
::columns{cols=2}
|
||||||
|
::div
|
||||||
|
**Colonne 1**
|
||||||
|
|
||||||
|
Contenu de la première colonne. Idéal pour comparer deux approches, montrer avant/après, ou lister des avantages et inconvénients.
|
||||||
|
::
|
||||||
|
|
||||||
|
::div
|
||||||
|
**Colonne 2**
|
||||||
|
|
||||||
|
Contenu de la seconde colonne. Les colonnes passent en empilé sur mobile automatiquement.
|
||||||
|
::
|
||||||
|
::
|
||||||
|
|
||||||
|
::columns{cols=3 gap="lg"}
|
||||||
|
::div
|
||||||
|
🚀 **Rapide**
|
||||||
|
|
||||||
|
Nuxt SSR génère le HTML côté serveur.
|
||||||
|
::
|
||||||
|
|
||||||
|
::div
|
||||||
|
🔍 **SEO**
|
||||||
|
|
||||||
|
Chaque page est crawlable sans JavaScript.
|
||||||
|
::
|
||||||
|
|
||||||
|
::div
|
||||||
|
🎨 **Flexible**
|
||||||
|
|
||||||
|
Tailwind + Nuxt UI pour tout styliser.
|
||||||
|
::
|
||||||
|
::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contenu repliable
|
||||||
|
|
||||||
|
::details{summary="Voir l'implémentation complète du plugin"}
|
||||||
|
```kotlin
|
||||||
|
class HytalePlugin(
|
||||||
|
val name: String,
|
||||||
|
val version: String,
|
||||||
|
private val onLoad: () -> Unit,
|
||||||
|
) {
|
||||||
|
fun load() {
|
||||||
|
println("Loading plugin: $name v$version")
|
||||||
|
onLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun builder() = Builder()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
private var name = ""
|
||||||
|
private var version = "0.0.1"
|
||||||
|
private var onLoad: () -> Unit = {}
|
||||||
|
|
||||||
|
fun name(n: String) = apply { name = n }
|
||||||
|
fun version(v: String) = apply { version = v }
|
||||||
|
fun onLoad(fn: () -> Unit) = apply { onLoad = fn }
|
||||||
|
fun build() = HytalePlugin(name, version, onLoad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
::details{summary="Pourquoi utiliser @nuxt/content ?" open=true}
|
||||||
|
`@nuxt/content` transforme des fichiers Markdown en pages SSR crawlables. Avantages :
|
||||||
|
|
||||||
|
- **Zéro CMS** — les articles sont dans le repo Git
|
||||||
|
- **Typé** — schema Zod sur chaque collection
|
||||||
|
- **MDC** — composants Vue dans le Markdown
|
||||||
|
::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Badges inline
|
||||||
|
|
||||||
|
Versions : :badge[v1.0]{color="green"} :badge[v0.9 LTS]{color="blue"} :badge[deprecated]{color="red"}
|
||||||
|
|
||||||
|
Statuts : :badge[stable]{color="green"} :badge[beta]{color="yellow"} :badge[wip]{color="orange"}
|
||||||
|
|
||||||
|
Technologies : :badge[Kotlin]{color="purple"} :badge[Nuxt 4]{color="green"} :badge[TypeScript]{color="blue"}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vidéo
|
||||||
|
|
||||||
|
**YouTube :**
|
||||||
|
|
||||||
|
::video{src="https://www.youtube.com/watch?v=dQw4w9WgXcQ" title="Exemple d'embed YouTube"}
|
||||||
|
::
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Listes
|
||||||
|
|
||||||
|
Liste non ordonnée :
|
||||||
|
|
||||||
|
- Premier point
|
||||||
|
- Deuxième point avec `code`
|
||||||
|
- Troisième point avec **gras**
|
||||||
|
- Sous-point imbriqué
|
||||||
|
- Autre sous-point
|
||||||
|
|
||||||
|
Liste ordonnée :
|
||||||
|
|
||||||
|
1. Installer les dépendances : `pnpm install`
|
||||||
|
2. Lancer le dev server : `pnpm dev`
|
||||||
|
3. Builder pour la prod : `pnpm build`
|
||||||
|
|||||||
Reference in New Issue
Block a user