feat: Publication de pages — notes publiques sur URL
Some checks failed
CI / Lint, Unit Tests & Build (push) Successful in 5m0s
CI / Deploy production (on server) (push) Failing after 40s

- Migration: champs isPublic + publicSlug + publishedAt sur Note
- Route publique /p/[slug] — rendu SSR sans auth, prose styled
- Server actions: publishNote / unpublishNote / getPublishedNote
- API /api/notes/publish — toggle publication + génération slug
- PublishDialog — modal avec lien copiable + bouton dépublier
- Bouton Globe dans le toolbar (vert si publiée)
- i18n FR/EN
- Pattern inspiré de BrainstormSession.isPublic
This commit is contained in:
Antigravity
2026-06-19 22:03:53 +00:00
parent 299836bd74
commit 1d614dd9c0
9 changed files with 381 additions and 1 deletions

View File

@@ -19,10 +19,11 @@ import { Badge } from '@/components/ui/badge'
import {
X, Plus, Palette, Image as ImageIcon, Bell, Eye, Link as LinkIcon, Sparkles,
Maximize2, Copy, ArrowLeft, ChevronRight, PanelRight, Check, Loader2, Save, MoreHorizontal,
Trash2, LogOut, Wand2, Share2, Wind, Paperclip, GraduationCap, FileDown, FileUp, Mic, MicOff, Printer, PenTool, Loader2 as Loader2Icon
Trash2, LogOut, Wand2, Share2, Wind, Paperclip, GraduationCap, FileDown, FileUp, Mic, MicOff, Printer, PenTool, Loader2 as Loader2Icon, Globe
} from 'lucide-react'
import { FlashcardGenerateDialog } from '@/components/flashcards/flashcard-generate-dialog'
import { NoteShareDialog } from './note-share-dialog'
import { PublishDialog } from './publish-dialog'
import { deleteNote, leaveSharedNote } from '@/app/actions/notes'
import { emitNoteChange } from '@/lib/note-change-sync'
import { useLanguage } from '@/lib/i18n'
@@ -46,6 +47,7 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
const [isConverting, setIsConverting] = useState(false)
const [shareOpen, setShareOpen] = useState(false)
const [flashcardsOpen, setFlashcardsOpen] = useState(false)
const [publishOpen, setPublishOpen] = useState(false)
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
const undoSnapshotRef = useRef<{ content: string; isMarkdown: boolean } | null>(null)
@@ -496,6 +498,21 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
</div>
)}
{!readOnly && (
<button
title={t('richTextEditor.publishTitle') || 'Publication publique'}
onClick={() => setPublishOpen(true)}
className={cn(
"p-1.5 rounded-full border transition-all",
note.isPublic
? "border-green-400/40 text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-950/20"
: "border-black/20 dark:border-white/20 text-foreground hover:bg-black/5 dark:hover:bg-white/5"
)}
>
<Globe size={16} />
</button>
)}
{!readOnly && (
<button
title={t('notes.shareNoteTitle')}
@@ -588,6 +605,17 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme
}}
/>
{publishOpen && (
<PublishDialog
open={publishOpen}
onClose={() => setPublishOpen(false)}
noteId={note.id}
noteTitle={state.title || note.title || 'Untitled'}
isPublic={note.isPublic}
publicSlug={note.publicSlug ?? null}
/>
)}
<button
aria-label={t('notes.documentInfoAria')}
onClick={() => { actions.setInfoOpen(!state.infoOpen); actions.setAiOpen(false) }}