Menu : Boîte à outils en premier + sidebar collapsible icônes
- Boîte à outils (/tools) en 1ère position dans la nav (desktop + mobile)
- Sidebar desktop repliable (toggle pictos-only, 14rem → 3.75rem)
- labels masqués par transition CSS fluide (max-width + opacity)
- état persisté en localStorage (libred-sidebar-collapsed)
- Page document : toggle Vue structurée / Aperçu document
- sous-toggle En vigueur / Selon les votes
- composant DocumentPreview (rendu PDF-like)
- filigrane discret, items ordonnés par sort_order
- mode projection : proposed_text substitu + encadrement orange
- footer horodaté, print-friendly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
* - Permanent vote signage
|
||||
* - Tuto overlay
|
||||
*/
|
||||
import type { DocumentItem } from '~/stores/documents'
|
||||
import type { DocumentItem, ItemVersion } from '~/stores/documents'
|
||||
|
||||
const route = useRoute()
|
||||
const documents = useDocumentsStore()
|
||||
@@ -138,6 +138,40 @@ async function archiveToSanctuary() {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── View mode (editorial vs preview) ────────────────────────
|
||||
|
||||
type ViewMode = 'editorial' | 'preview'
|
||||
type PreviewMode = 'current' | 'projected'
|
||||
|
||||
const viewMode = ref<ViewMode>('editorial')
|
||||
const previewMode = ref<PreviewMode>('current')
|
||||
const versionsLoaded = ref(false)
|
||||
|
||||
async function activatePreview() {
|
||||
viewMode.value = 'preview'
|
||||
if (!versionsLoaded.value && documents.items.length > 0) {
|
||||
const itemIds = documents.items.map(i => i.id)
|
||||
await documents.fetchAllItemVersions(slug.value, itemIds)
|
||||
versionsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/** Map item_id → the active version under vote (or null). */
|
||||
const activeVersionByItem = computed((): Record<string, ItemVersion | null> => {
|
||||
const map: Record<string, ItemVersion | null> = {}
|
||||
for (const item of documents.items) {
|
||||
const versions = documents.allItemVersions[item.id] || []
|
||||
map[item.id] = versions.find(v => v.status === 'vote')
|
||||
|| versions.find(v => v.status === 'proposed')
|
||||
|| null
|
||||
}
|
||||
return map
|
||||
})
|
||||
|
||||
const hasProjectedChanges = computed(() =>
|
||||
Object.values(activeVersionByItem.value).some(v => v !== null),
|
||||
)
|
||||
|
||||
// ─── Active section (scroll spy) ──────────────────────────────
|
||||
|
||||
const activeSection = ref<string | null>(null)
|
||||
@@ -285,8 +319,69 @@ function toggleSection(tag: string) {
|
||||
:genesis-json="documents.current.genesis_json"
|
||||
/>
|
||||
|
||||
<!-- ═══ VIEW MODE TOGGLE ═══ -->
|
||||
<div class="doc-page__view-toggle">
|
||||
<div class="doc-page__view-tabs">
|
||||
<button
|
||||
class="doc-page__view-tab"
|
||||
:class="{ 'doc-page__view-tab--active': viewMode === 'editorial' }"
|
||||
@click="viewMode = 'editorial'"
|
||||
>
|
||||
<UIcon name="i-lucide-layout-list" class="text-sm" />
|
||||
Vue structurée
|
||||
</button>
|
||||
<button
|
||||
class="doc-page__view-tab"
|
||||
:class="{ 'doc-page__view-tab--active': viewMode === 'preview' }"
|
||||
@click="activatePreview"
|
||||
>
|
||||
<UIcon name="i-lucide-file-text" class="text-sm" />
|
||||
Aperçu document
|
||||
<span v-if="documents.loadingVersions" class="doc-page__view-loading">
|
||||
<UIcon name="i-lucide-loader-circle" class="text-xs animate-spin" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Preview sub-mode (shown only in preview mode) -->
|
||||
<Transition name="fade">
|
||||
<div v-if="viewMode === 'preview'" class="doc-page__preview-modes">
|
||||
<button
|
||||
class="doc-page__preview-mode"
|
||||
:class="{ 'doc-page__preview-mode--active': previewMode === 'current' }"
|
||||
@click="previewMode = 'current'"
|
||||
>
|
||||
<UIcon name="i-lucide-circle-check" class="text-xs" />
|
||||
En vigueur
|
||||
</button>
|
||||
<button
|
||||
class="doc-page__preview-mode"
|
||||
:class="{ 'doc-page__preview-mode--active': previewMode === 'projected' }"
|
||||
:disabled="!hasProjectedChanges"
|
||||
:title="!hasProjectedChanges ? 'Aucun vote en cours sur ce document' : 'Simuler les votes en cours'"
|
||||
@click="previewMode = 'projected'"
|
||||
>
|
||||
<UIcon name="i-lucide-flask-conical" class="text-xs" />
|
||||
Selon les votes
|
||||
<span v-if="hasProjectedChanges" class="doc-page__preview-dot" />
|
||||
</button>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<!-- ═══ DOCUMENT PREVIEW ═══ -->
|
||||
<Transition name="fade">
|
||||
<DocumentPreview
|
||||
v-if="viewMode === 'preview'"
|
||||
:document="documents.current"
|
||||
:items="documents.items"
|
||||
:mode="previewMode"
|
||||
:version-map="activeVersionByItem"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<!-- ═══ SECTION NAVIGATOR ═══ -->
|
||||
<div v-if="sections.length > 1" class="doc-page__section-nav">
|
||||
<div v-if="sections.length > 1 && viewMode === 'editorial'" class="doc-page__section-nav">
|
||||
<button
|
||||
v-for="section in sections"
|
||||
:key="section.tag"
|
||||
@@ -301,7 +396,7 @@ function toggleSection(tag: string) {
|
||||
</div>
|
||||
|
||||
<!-- ═══ SECTIONS WITH ITEMS ═══ -->
|
||||
<div class="doc-page__sections">
|
||||
<div v-if="viewMode === 'editorial'" class="doc-page__sections">
|
||||
<div
|
||||
v-for="section in sections"
|
||||
:key="section.tag"
|
||||
@@ -590,6 +685,115 @@ function toggleSection(tag: string) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* View mode toggle */
|
||||
.doc-page__view-toggle {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.doc-page__view-tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
background: var(--mood-surface);
|
||||
padding: 4px;
|
||||
border-radius: 14px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.doc-page__view-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
background: none;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.doc-page__view-tab:hover {
|
||||
color: var(--mood-text);
|
||||
background: var(--mood-accent-soft);
|
||||
}
|
||||
|
||||
.doc-page__view-tab--active {
|
||||
color: var(--mood-accent);
|
||||
background: var(--mood-accent-soft);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.doc-page__view-loading {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.doc-page__preview-modes {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.doc-page__preview-mode {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: var(--mood-text-muted);
|
||||
background: var(--mood-surface);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.doc-page__preview-mode:hover:not(:disabled) {
|
||||
color: var(--mood-text);
|
||||
background: color-mix(in srgb, var(--mood-accent) 10%, var(--mood-surface));
|
||||
}
|
||||
|
||||
.doc-page__preview-mode:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.doc-page__preview-mode--active {
|
||||
background: var(--mood-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.doc-page__preview-mode--active:hover:not(:disabled) {
|
||||
background: var(--mood-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.doc-page__preview-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--mood-warning, #f59e0b);
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
/* Fade transition */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Section collapse transition */
|
||||
.section-collapse-enter-active,
|
||||
.section-collapse-leave-active {
|
||||
|
||||
Reference in New Issue
Block a user