mirror of
https://github.com/dergigi/boris.git
synced 2025-12-27 11:34:50 +01:00
feat: add three-dot menu to highlight cards
- Replace external link button with three-dot menu in highlight cards - Move 'Open source/Nostr' and 'Delete' actions into dropdown menu - Add click-outside functionality to close menu - Style menu for both dark and light themes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faQuoteLeft, faExternalLinkAlt, faPlane, faSpinner, faServer, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faQuoteLeft, faExternalLinkAlt, faPlane, faSpinner, faServer, faTrash, faEllipsisV } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Highlight } from '../types/highlights'
|
||||
import { useEventModel } from 'applesauce-react/hooks'
|
||||
import { Models, IEventStore } from 'applesauce-core'
|
||||
@@ -40,11 +40,13 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
|
||||
onHighlightDelete
|
||||
}) => {
|
||||
const itemRef = useRef<HTMLDivElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const [isSyncing, setIsSyncing] = useState(() => isEventSyncing(highlight.id))
|
||||
const [showOfflineIndicator, setShowOfflineIndicator] = useState(() => highlight.isOfflineCreated && !isSyncing)
|
||||
const [isRebroadcasting, setIsRebroadcasting] = useState(false)
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
const [showMenu, setShowMenu] = useState(false)
|
||||
|
||||
const activeAccount = Hooks.useActiveAccount()
|
||||
|
||||
@@ -97,6 +99,22 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
|
||||
}
|
||||
}, [isSelected])
|
||||
|
||||
// Close menu when clicking outside
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||
setShowMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (showMenu) {
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside)
|
||||
}
|
||||
}
|
||||
}, [showMenu])
|
||||
|
||||
const handleItemClick = () => {
|
||||
if (onHighlightClick) {
|
||||
onHighlightClick(highlight.id)
|
||||
@@ -295,6 +313,29 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
|
||||
setShowDeleteConfirm(false)
|
||||
}
|
||||
|
||||
const handleMenuToggle = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setShowMenu(!showMenu)
|
||||
}
|
||||
|
||||
const handleOpenExternal = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (sourceLink) {
|
||||
if (highlight.urlReference && onSelectUrl) {
|
||||
onSelectUrl(highlight.urlReference)
|
||||
} else {
|
||||
window.open(sourceLink, '_blank', 'noopener,noreferrer')
|
||||
}
|
||||
}
|
||||
setShowMenu(false)
|
||||
}
|
||||
|
||||
const handleMenuDeleteClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setShowMenu(false)
|
||||
setShowDeleteConfirm(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -316,15 +357,6 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
|
||||
<FontAwesomeIcon icon={relayIndicator.icon} spin={relayIndicator.spin} />
|
||||
</div>
|
||||
)}
|
||||
{canDelete && (
|
||||
<div
|
||||
className="highlight-delete-btn"
|
||||
title="Delete highlight"
|
||||
onClick={handleDeleteClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={isDeleting ? faSpinner : faTrash} spin={isDeleting} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="highlight-content">
|
||||
@@ -348,18 +380,39 @@ export const HighlightItem: React.FC<HighlightItemProps> = ({
|
||||
{formatDateCompact(highlight.created_at)}
|
||||
</span>
|
||||
|
||||
{sourceLink && (
|
||||
<a
|
||||
href={sourceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => highlight.urlReference && onSelectUrl ? handleLinkClick(highlight.urlReference, e) : undefined}
|
||||
className="highlight-source"
|
||||
title={highlight.eventReference ? 'Open on Nostr' : 'Open source'}
|
||||
<div className="highlight-menu-wrapper" ref={menuRef}>
|
||||
<button
|
||||
className="highlight-menu-btn"
|
||||
onClick={handleMenuToggle}
|
||||
title="More options"
|
||||
>
|
||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||
</a>
|
||||
)}
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</button>
|
||||
|
||||
{showMenu && (
|
||||
<div className="highlight-menu">
|
||||
{sourceLink && (
|
||||
<button
|
||||
className="highlight-menu-item"
|
||||
onClick={handleOpenExternal}
|
||||
>
|
||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||
<span>{highlight.eventReference ? 'Open on Nostr' : 'Open source'}</span>
|
||||
</button>
|
||||
)}
|
||||
{canDelete && (
|
||||
<button
|
||||
className="highlight-menu-item highlight-menu-item-danger"
|
||||
onClick={handleMenuDeleteClick}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
<FontAwesomeIcon icon={isDeleting ? faSpinner : faTrash} spin={isDeleting} />
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
103
src/index.css
103
src/index.css
@@ -1783,6 +1783,35 @@ body.mobile-sidebar-open {
|
||||
.highlight-text {
|
||||
color: #213547;
|
||||
}
|
||||
|
||||
.highlight-menu-btn {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.highlight-menu-btn:hover {
|
||||
color: #646cff;
|
||||
background: rgba(100, 108, 255, 0.08);
|
||||
}
|
||||
|
||||
.highlight-menu {
|
||||
background: #fff;
|
||||
border-color: #ddd;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.highlight-menu-item {
|
||||
color: #213547;
|
||||
}
|
||||
|
||||
.highlight-menu-item:hover {
|
||||
background: rgba(100, 108, 255, 0.08);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.highlight-menu-item-danger:hover {
|
||||
background: rgba(255, 68, 68, 0.08);
|
||||
color: #cc0000;
|
||||
}
|
||||
}
|
||||
|
||||
/* Highlights Panel Styles */
|
||||
@@ -2236,25 +2265,77 @@ body.mobile-sidebar-open {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.highlight-source {
|
||||
.highlight-menu-wrapper {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.highlight-menu-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
color: #646cff;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.highlight-source:hover {
|
||||
color: #535bf2;
|
||||
text-decoration: underline;
|
||||
.highlight-menu-btn:hover {
|
||||
color: #646cff;
|
||||
background: rgba(100, 108, 255, 0.1);
|
||||
}
|
||||
|
||||
.highlight-source svg {
|
||||
.highlight-menu {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 4px);
|
||||
background: #2a2a2a;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
min-width: 160px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.highlight-menu-item {
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ddd;
|
||||
padding: 0.625rem 0.875rem;
|
||||
font-size: 0.875rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.highlight-menu-item:hover {
|
||||
background: rgba(100, 108, 255, 0.15);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.highlight-menu-item:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.highlight-menu-item-danger:hover {
|
||||
background: rgba(255, 68, 68, 0.15);
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.highlight-menu-item svg {
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Inline content highlights - fluorescent marker style */
|
||||
|
||||
Reference in New Issue
Block a user