merge: PC6 journal n8n + JournalList (round 2)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
---
|
||||
// ColJournal - colonne gauche : CTA Manifeste + Hashtags accordeon + Journal skeleton
|
||||
// ColJournal - colonne gauche : CTA Manifeste + Hashtags accordeon + Journal (PC6)
|
||||
// 7 hashtags = 7 plateformes (cf delta 15 du PILOTE-PC.md)
|
||||
import JournalList from '../vue/JournalList.vue';
|
||||
|
||||
const hashtags = [
|
||||
{ tag: '#manifeste', plateforme: 'Blog trans-former.fr', canal: 'ecriture longue' },
|
||||
{ tag: '#building-public', plateforme: 'LinkedIn', canal: 'journal pro' },
|
||||
@@ -55,10 +57,7 @@ const hashtags = [
|
||||
<span class="text-xs text-neutral-400 font-normal">chrono</span>
|
||||
</h2>
|
||||
<div id="journal-list" class="space-y-3 text-sm">
|
||||
<!-- PC6 remplit ce slot via <JournalList client:visible /> -->
|
||||
<p class="text-neutral-400 italic text-xs">
|
||||
Chargement du journal...
|
||||
</p>
|
||||
<JournalList client:visible />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,168 @@
|
||||
<script setup lang="ts">
|
||||
// Placeholder journal list — PC6 lit public/data/journal.json
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
interface JournalItem {
|
||||
id: string
|
||||
platform: 'substack' | 'gitea' | 'github' | 'instagram' | 'castopod' | 'blog' | 'linkedin'
|
||||
hashtag: string
|
||||
date: string
|
||||
titre: string
|
||||
extrait: string
|
||||
url: string
|
||||
thumbnail: string | null
|
||||
}
|
||||
|
||||
interface JournalPayload {
|
||||
generatedAt: string
|
||||
items: JournalItem[]
|
||||
counts?: Record<string, number>
|
||||
}
|
||||
|
||||
const items = ref<JournalItem[]>([])
|
||||
const filters = ref<Record<string, boolean>>({})
|
||||
const loading = ref(true)
|
||||
const errored = ref(false)
|
||||
const empty = ref(false)
|
||||
|
||||
const JOURNAL_URL =
|
||||
(import.meta as unknown as { env: Record<string, string | undefined> }).env
|
||||
.PUBLIC_JOURNAL_URL || 'https://data.trans-former.fr/journal.json'
|
||||
|
||||
const STORAGE_KEY = 'tf-hashtag-filters'
|
||||
|
||||
const loadFilters = () => {
|
||||
try {
|
||||
filters.value = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
|
||||
} catch {
|
||||
filters.value = {}
|
||||
}
|
||||
}
|
||||
|
||||
const onFilterChange = (e: Event) => {
|
||||
const ce = e as CustomEvent
|
||||
if (ce.detail && typeof ce.detail === 'object') {
|
||||
filters.value = { ...ce.detail }
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadFilters()
|
||||
window.addEventListener('hashtag-filter-change', onFilterChange as EventListener)
|
||||
try {
|
||||
const res = await fetch(JOURNAL_URL, { cache: 'no-store' })
|
||||
if (!res.ok) {
|
||||
errored.value = true
|
||||
return
|
||||
}
|
||||
const data = (await res.json()) as JournalPayload
|
||||
items.value = Array.isArray(data.items) ? data.items : []
|
||||
if (items.value.length === 0) empty.value = true
|
||||
} catch (e) {
|
||||
errored.value = true
|
||||
console.error('JournalList fetch failed', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('hashtag-filter-change', onFilterChange as EventListener)
|
||||
})
|
||||
|
||||
const visibleItems = computed(() => {
|
||||
// Tous les filtres faux = aucun affiche ; tous true ou aucune cle = tout afficher
|
||||
const keys = Object.keys(filters.value)
|
||||
if (keys.length === 0) return items.value
|
||||
const activeKeys = keys.filter((k) => filters.value[k])
|
||||
// Si l'utilisateur a explicitement décoché tous les hashtags, on n'affiche rien
|
||||
if (activeKeys.length === 0 && keys.some((k) => filters.value[k] === false)) return []
|
||||
if (activeKeys.length === 0) return items.value
|
||||
return items.value.filter((it) => activeKeys.includes(it.hashtag))
|
||||
})
|
||||
|
||||
const formatDate = (iso: string) => {
|
||||
try {
|
||||
const d = new Date(iso)
|
||||
if (isNaN(d.getTime())) return ''
|
||||
return `${String(d.getDate()).padStart(2, '0')}/${String(d.getMonth() + 1).padStart(2, '0')}`
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const platformLabel = (p: string) => {
|
||||
switch (p) {
|
||||
case 'gitea':
|
||||
case 'github':
|
||||
return 'code'
|
||||
case 'substack':
|
||||
return 'substack'
|
||||
case 'instagram':
|
||||
return 'insta'
|
||||
case 'castopod':
|
||||
return 'pod'
|
||||
case 'blog':
|
||||
return 'blog'
|
||||
case 'linkedin':
|
||||
return 'linkedin'
|
||||
default:
|
||||
return p
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full w-full flex items-center justify-center text-sm text-neutral-400">
|
||||
Journal list placeholder (PC6)
|
||||
<div class="space-y-3">
|
||||
<p v-if="loading" class="text-neutral-400 italic text-xs">
|
||||
Chargement du journal...
|
||||
</p>
|
||||
<p v-else-if="errored" class="text-neutral-400 italic text-xs">
|
||||
Journal en cours d'agregation. Reviens dans quelques heures.
|
||||
</p>
|
||||
<p v-else-if="empty" class="text-neutral-400 italic text-xs">
|
||||
Aucun item agrege. Le cron nocturne tourne a 3h.
|
||||
</p>
|
||||
<p
|
||||
v-else-if="!visibleItems.length"
|
||||
class="text-neutral-400 italic text-xs"
|
||||
>
|
||||
Aucun item ; ajuste les filtres.
|
||||
</p>
|
||||
<article
|
||||
v-for="item in visibleItems"
|
||||
:key="item.id"
|
||||
class="border-b border-neutral-100 pb-3 last:border-b-0"
|
||||
>
|
||||
<a
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="block group"
|
||||
>
|
||||
<div class="flex items-baseline gap-2 text-[11px] text-neutral-500 mb-1">
|
||||
<time>{{ formatDate(item.date) }}</time>
|
||||
<span class="font-mono text-neutral-700">{{ item.hashtag }}</span>
|
||||
<span class="text-neutral-400">{{ platformLabel(item.platform) }}</span>
|
||||
</div>
|
||||
<h4
|
||||
class="text-sm text-neutral-900 group-hover:text-neutral-600 leading-snug"
|
||||
>
|
||||
{{ item.titre }}
|
||||
</h4>
|
||||
<p
|
||||
v-if="item.extrait"
|
||||
class="text-xs text-neutral-500 mt-1 line-clamp-2"
|
||||
>
|
||||
{{ item.extrait }}
|
||||
</p>
|
||||
<img
|
||||
v-if="item.thumbnail"
|
||||
:src="item.thumbnail"
|
||||
:alt="item.titre"
|
||||
loading="lazy"
|
||||
class="mt-2 w-full max-h-32 object-cover rounded"
|
||||
/>
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user