Skip to content

Commit 0d12f12

Browse files
committed
chore: adjust UI.
1 parent d2c2771 commit 0d12f12

2 files changed

Lines changed: 57 additions & 123 deletions

File tree

frontend/src/index.css

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -717,27 +717,22 @@ mark {
717717
gap: 0.5rem;
718718
}
719719

720-
/* Dock variant — stacked vertically inside the right rail, like a Mac Dock */
720+
/* Dock variant — horizontal mini toolbar pinned to top of the right rail */
721721
.page-actions-dock {
722-
flex-direction: column;
723-
align-items: stretch;
722+
flex-direction: row;
723+
align-items: center;
724724
background: var(--color-surface);
725725
padding: 0.5rem;
726726
border-radius: 12px;
727727
border: 1px solid var(--color-border);
728728
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
729-
animation: page-actions-slide-in 180ms ease-out;
730729
}
731730

732-
.page-actions-dock .fab-btn {
731+
.page-actions-dock .fab-btn-primary {
732+
flex: 1 1 auto;
733733
justify-content: center;
734734
}
735735

736-
@keyframes page-actions-slide-in {
737-
from { opacity: 0; transform: translateY(-6px); }
738-
to { opacity: 1; transform: translateY(0); }
739-
}
740-
741736
/* Dropdown menu positioning — inline opens above, dock opens to the left */
742737
.page-actions-menu {
743738
position: absolute;
@@ -754,8 +749,10 @@ mark {
754749
top: calc(100% + 0.5rem);
755750
}
756751
.page-actions-menu-dock {
757-
right: calc(100% + 0.5rem);
758-
bottom: 0;
752+
right: 0;
753+
top: calc(100% + 0.5rem);
754+
max-height: calc(100vh - 6rem);
755+
overflow-y: auto;
759756
}
760757

761758
/* Legacy floating action bar (kept for PageEdit) */

frontend/src/pages/PageView.jsx

Lines changed: 48 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -25,59 +25,13 @@ export default function PageView() {
2525
const [backlinks, setBacklinks] = useState([])
2626
const [menuOpen, setMenuOpen] = useState(false)
2727
const menuRef = useRef(null)
28-
const [publicMenuOpen, setPublicMenuOpen] = useState(false)
29-
const publicMenuRef = useRef(null)
3028
const [publicConfirmOpen, setPublicConfirmOpen] = useState(false)
3129
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false)
3230
const [toast, setToast] = useState('')
3331
const [headings, setHeadings] = useState([])
34-
const [scrolledPastTitle, setScrolledPastTitle] = useState(false)
35-
const [isDesktop, setIsDesktop] = useState(
36-
typeof window !== 'undefined' ? window.matchMedia('(min-width: 1024px)').matches : true,
37-
)
38-
const titleRef = useRef(null)
3932

4033
const handleHeadings = useCallback((items) => setHeadings(items), [])
4134

42-
// Track breakpoint so we know when the right rail is available.
43-
useEffect(() => {
44-
const mq = window.matchMedia('(min-width: 1024px)')
45-
const update = () => setIsDesktop(mq.matches)
46-
update()
47-
mq.addEventListener('change', update)
48-
return () => mq.removeEventListener('change', update)
49-
}, [])
50-
51-
// Observe the title row; once it scrolls out of view, morph actions to the right rail dock.
52-
useEffect(() => {
53-
const el = titleRef.current
54-
if (!el) return
55-
const io = new IntersectionObserver(
56-
([entry]) => setScrolledPastTitle(!entry.isIntersecting),
57-
{ threshold: 0 },
58-
)
59-
io.observe(el)
60-
return () => io.disconnect()
61-
}, [page])
62-
63-
// Close the action menu when the actions morph between inline and dock,
64-
// otherwise the outside-click listener chases a stale ref.
65-
useEffect(() => {
66-
setMenuOpen(false)
67-
}, [scrolledPastTitle, isDesktop])
68-
69-
// Close public menu on outside click
70-
useEffect(() => {
71-
if (!publicMenuOpen) return
72-
const handleClick = (e) => {
73-
if (publicMenuRef.current && !publicMenuRef.current.contains(e.target)) {
74-
setPublicMenuOpen(false)
75-
}
76-
}
77-
document.addEventListener('mousedown', handleClick)
78-
return () => document.removeEventListener('mousedown', handleClick)
79-
}, [publicMenuOpen])
80-
8135
// Auto-dismiss toast after 2.5s
8236
useEffect(() => {
8337
if (!toast) return
@@ -177,7 +131,7 @@ export default function PageView() {
177131
} catch {
178132
setToast(link)
179133
}
180-
setPublicMenuOpen(false)
134+
setMenuOpen(false)
181135
}
182136

183137
const handleMakePrivate = async () => {
@@ -189,7 +143,7 @@ export default function PageView() {
189143
console.error('Failed to make private:', err)
190144
setToast('Failed to update visibility')
191145
}
192-
setPublicMenuOpen(false)
146+
setMenuOpen(false)
193147
}
194148

195149
const handleMakePublic = async () => {
@@ -216,9 +170,6 @@ export default function PageView() {
216170
if (loading) return <div className="text-text-secondary">Loading...</div>
217171
if (!page) return null
218172

219-
const showInline = !isDesktop || !scrolledPastTitle
220-
const showDock = isDesktop && scrolledPastTitle
221-
222173
const renderActions = (variant) => (
223174
<div className={`page-actions page-actions-${variant}`}>
224175
<button
@@ -255,7 +206,41 @@ export default function PageView() {
255206
</svg>
256207
History
257208
</button>
258-
{!page.is_public && (
209+
<button
210+
onClick={() => { setMenuOpen(false); handleToggleWatch() }}
211+
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover flex items-center gap-2"
212+
>
213+
<svg className="w-4 h-4 text-text-secondary" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
214+
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
215+
<circle cx="12" cy="12" r="3" />
216+
</svg>
217+
{watching ? 'Stop watching' : 'Watch page'}
218+
</button>
219+
<div className="border-t border-border my-1" />
220+
{page.is_public ? (
221+
<>
222+
<button
223+
onClick={handleCopyPublicLink}
224+
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover flex items-center gap-2"
225+
>
226+
<svg className="w-4 h-4 text-text-secondary" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
227+
<path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71" />
228+
<path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71" />
229+
</svg>
230+
Copy public link
231+
</button>
232+
<button
233+
onClick={handleMakePrivate}
234+
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover flex items-center gap-2"
235+
>
236+
<svg className="w-4 h-4 text-text-secondary" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
237+
<rect x="3" y="11" width="18" height="11" rx="2" />
238+
<path d="M7 11V7a5 5 0 0110 0v4" />
239+
</svg>
240+
Make private
241+
</button>
242+
</>
243+
) : (
259244
<button
260245
onClick={() => { setMenuOpen(false); setPublicConfirmOpen(true) }}
261246
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover flex items-center gap-2"
@@ -311,69 +296,21 @@ export default function PageView() {
311296
return (
312297
<div className="max-w-6xl mx-auto lg:grid lg:grid-cols-[minmax(0,1fr)_220px] lg:gap-8">
313298
<article>
314-
<div ref={titleRef} className="flex items-center gap-3 mb-4 flex-wrap">
299+
<div className="flex items-center gap-3 mb-4 flex-wrap">
315300
<h1 className="text-3xl font-bold text-text">{page.title}</h1>
316-
{page.is_public && (
317-
<div className="relative" ref={publicMenuRef}>
318-
<button
319-
onClick={() => setPublicMenuOpen((v) => !v)}
320-
className="inline-flex items-center gap-1 text-xs px-2.5 py-1 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300 border border-green-300 dark:border-green-700 rounded-full hover:brightness-95"
321-
title="This page is public — click for options"
322-
>
323-
<span role="img" aria-hidden="true">🌐</span> Public
324-
<svg className="w-3 h-3 opacity-70" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
325-
<path d="M6 9l6 6 6-6" />
326-
</svg>
327-
</button>
328-
{publicMenuOpen && (
329-
<div className="absolute left-0 top-full mt-1 w-48 bg-surface border border-border rounded-lg shadow-lg py-1 z-50">
330-
<button
331-
onClick={handleCopyPublicLink}
332-
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover"
333-
>
334-
Copy public link
335-
</button>
336-
<button
337-
onClick={handleMakePrivate}
338-
className="w-full text-left px-3 py-2 text-sm text-text hover:bg-surface-hover"
339-
>
340-
Make private
341-
</button>
342-
</div>
343-
)}
344-
</div>
345-
)}
346301
<button
347302
onClick={handleToggleBookmark}
348303
className={`text-xl transition-colors ${bookmarked ? 'text-yellow-500' : 'text-gray-300 hover:text-yellow-400'}`}
349304
title={bookmarked ? 'Remove bookmark' : 'Add bookmark'}
350305
>
351306
{bookmarked ? '\u2605' : '\u2606'}
352307
</button>
353-
<button
354-
onClick={handleToggleWatch}
355-
className={`flex items-center gap-1 text-xs px-2 py-1 rounded-full border transition-colors ${
356-
watching
357-
? 'bg-primary-soft text-primary border-primary/40'
358-
: 'text-text-secondary border-border hover:border-text-secondary'
359-
}`}
360-
title={watching ? 'Unwatch this page' : 'Watch this page for changes'}
361-
>
362-
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
363-
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
364-
<circle cx="12" cy="12" r="3" />
365-
</svg>
366-
{watching ? 'Watching' : 'Watch'}
367-
{watcherCount > 0 && <span className="text-text-secondary">({watcherCount})</span>}
368-
</button>
369308
</div>
370309

371-
{/* Inline action row — visible on mobile always, and on desktop until the title scrolls out */}
372-
{showInline && (
373-
<div className="mb-4">
374-
{renderActions('inline')}
375-
</div>
376-
)}
310+
{/* Mobile: inline actions under the title. Desktop uses the right-rail dock. */}
311+
<div className="mb-4 lg:hidden">
312+
{renderActions('inline')}
313+
</div>
377314

378315
{/* Tags */}
379316
<div className="flex flex-wrap items-center gap-2 mb-4">
@@ -413,6 +350,8 @@ export default function PageView() {
413350
<div className="text-sm text-text-secondary mb-6">
414351
{page.author_name && <>{page.author_name} &middot; </>}
415352
/{page.slug} &middot; {page.view_count} views &middot; Updated {new Date(page.updated_at).toLocaleString()}
353+
{page.is_public && <> &middot; <span title="This page is public">🌐 Public</span></>}
354+
{watcherCount > 0 && <> &middot; {watcherCount} watching</>}
416355
</div>
417356
<div className="bg-surface rounded-xl shadow-sm border border-border p-8">
418357
<MarkdownViewer content={page.content_md} onHeadings={handleHeadings} />
@@ -442,15 +381,13 @@ export default function PageView() {
442381
<Comments slug={slug} />
443382
</article>
444383

445-
{/* Right rail: TOC + scroll-aware action dock */}
384+
{/* Right rail: actions pinned above TOC so Edit has one consistent home */}
446385
<aside className="hidden lg:block">
447386
<div className="page-right-rail">
387+
<div className="page-action-dock-wrap">
388+
{renderActions('dock')}
389+
</div>
448390
<TableOfContents headings={headings} />
449-
{showDock && (
450-
<div className="page-action-dock-wrap">
451-
{renderActions('dock')}
452-
</div>
453-
)}
454391
</div>
455392
</aside>
456393

0 commit comments

Comments
 (0)