mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-09 15:07:10 +02:00
* feat: centralize publish flow with RxJS-based PublishService Create a unified PublishService that: - Provides consistent relay selection (outbox + state + hints + fallbacks) - Emits RxJS observables for per-relay status updates - Handles EventStore integration automatically - Supports both fire-and-forget and observable-based publishing Refactor all publish locations to use the centralized service: - hub.ts: Use PublishService for ActionRunner publish - delete-event.ts: Use PublishService (fixes missing eventStore.add) - publish-spell.ts: Use PublishService with relay hint support - PostViewer.tsx: Use publishWithUpdates() for per-relay UI tracking This lays the groundwork for the event log feature by providing observable hooks into all publish operations. * feat: add LOG command for relay event introspection Add an ephemeral event log system that tracks relay operations: - EventLogService (src/services/event-log.ts): - Subscribes to PublishService for PUBLISH events with per-relay status - Monitors relay pool for CONNECT/DISCONNECT events - Tracks AUTH challenges and results - Captures NOTICE messages from relays - Uses RxJS BehaviorSubject for reactive updates - Circular buffer with configurable max entries (default 500) - useEventLog hook (src/hooks/useEventLog.ts): - React hook for filtering and accessing log entries - Filter by type, relay, or limit - Retry failed relays directly from the hook - EventLogViewer component (src/components/EventLogViewer.tsx): - Tab-based filtering (All/Publish/Connect/Auth/Notice) - Expandable PUBLISH entries showing per-relay status - Click to retry failed relays - Auto-scroll to new entries (pause on scroll) - Clear log button - LOG command accessible via Cmd+K palette * fix: prevent duplicate log entries and check relay OK response - EventLogService: Check for existing entry before creating new one when handling publish events (prevents duplicates from start/complete) - PublishService: Check response.ok from pool.publish() to detect relay rejections instead of assuming success on resolve - Update test mock to return proper publish response format * feat: keep relay selection in call site, compact logs * chore: cleanup * fix: make Timestamp component locale-aware via formatTimestamp Timestamp was hardcoded to "es" locale. Now uses formatTimestamp() from useLocale.ts for consistent locale-aware time formatting. Added Timestamp to CLAUDE.md shared components documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: improve event-log reliability, add ERROR type and per-relay timing Service improvements: - Fix notice$ duplicate logging with per-relay dedup tracking - Remove dead Array.isArray code path (notice$ emits strings) - Increase relay poll interval from 1s to 5s - Clean publishIdToEntryId map on terminal state, not just overflow - Immutable entry updates (spread instead of in-place mutation) - Extract NewEntry<T>/AddEntryInput helper types for clean addEntry signature - Clear lastNoticePerRelay on log clear New capabilities: - ERROR log type: subscribes to relay.error$ for connection failure reasons - RelayStatusEntry with updatedAt timestamp for per-relay response timing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: improve EventLogViewer with virtualization, timing, and error display - Virtualize log list with react-virtuoso for 500-entry buffer performance - Add ErrorEntry renderer for new ERROR log type (AlertTriangle icon) - Show per-relay response time (e.g. "142ms", "2.3s") in publish details - Make all entry types expandable (connect/disconnect now have details) - Show absolute timestamp in all expanded detail views - Group ERROR events under Connect tab filter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: prevent duplicate PUBLISH log entries from completion event PublishService emits publish$ twice: once at start, once on completion. The eager publishIdToEntryId cleanup in handleStatusUpdate fired before the completion emission, causing handlePublishEvent to create a second entry. Removed eager cleanup — overflow eviction is sufficient. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
611 lines
16 KiB
CSS
611 lines
16 KiB
CSS
/* Tailwind CSS v4 - CSS-first configuration */
|
|
@import "tailwindcss";
|
|
|
|
/* Shiki syntax highlighting theme */
|
|
@import "./styles/shiki-theme.css";
|
|
|
|
/* ==========================================================================
|
|
Theme Configuration (@theme)
|
|
These define Tailwind's design tokens - colors, fonts, animations, etc.
|
|
They reference the runtime CSS variables set by ThemeProvider.
|
|
========================================================================== */
|
|
|
|
@theme {
|
|
/* Font Family */
|
|
--font-mono: "Oxygen Mono", monospace;
|
|
|
|
/* Border Radius - using CSS variable for runtime theming */
|
|
--radius-lg: var(--radius);
|
|
--radius-md: calc(var(--radius) - 2px);
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|
|
|
/* Animations */
|
|
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
--animate-skeleton-pulse: skeleton-pulse 1.5s ease-in-out infinite;
|
|
|
|
@keyframes accordion-down {
|
|
from {
|
|
height: 0;
|
|
}
|
|
to {
|
|
height: var(--radix-accordion-content-height);
|
|
}
|
|
}
|
|
|
|
@keyframes accordion-up {
|
|
from {
|
|
height: var(--radix-accordion-content-height);
|
|
}
|
|
to {
|
|
height: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes skeleton-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Color Tokens
|
|
These map runtime CSS variables (set by ThemeProvider) to Tailwind colors.
|
|
The runtime variables use HSL values WITHOUT the hsl() wrapper.
|
|
========================================================================== */
|
|
|
|
/* Core Colors */
|
|
--color-background: hsl(var(--background));
|
|
--color-foreground: hsl(var(--foreground));
|
|
|
|
/* Card */
|
|
--color-card: hsl(var(--card));
|
|
--color-card-foreground: hsl(var(--card-foreground));
|
|
|
|
/* Popover */
|
|
--color-popover: hsl(var(--popover));
|
|
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
|
|
/* Primary */
|
|
--color-primary: hsl(var(--primary));
|
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
|
|
/* Secondary */
|
|
--color-secondary: hsl(var(--secondary));
|
|
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
|
|
/* Accent */
|
|
--color-accent: hsl(var(--accent));
|
|
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
|
|
/* Muted */
|
|
--color-muted: hsl(var(--muted));
|
|
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
|
|
/* Destructive */
|
|
--color-destructive: hsl(var(--destructive));
|
|
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
|
|
/* Form Elements */
|
|
--color-border: hsl(var(--border));
|
|
--color-input: hsl(var(--input));
|
|
--color-ring: hsl(var(--ring));
|
|
|
|
/* Status Colors */
|
|
--color-success: hsl(var(--success));
|
|
--color-warning: hsl(var(--warning));
|
|
--color-info: hsl(var(--info));
|
|
|
|
/* Nostr-specific Colors */
|
|
--color-zap: hsl(var(--zap));
|
|
--color-live: hsl(var(--live));
|
|
--color-highlight: hsl(var(--highlight));
|
|
|
|
/* Tooltip */
|
|
--color-tooltip: hsl(var(--tooltip));
|
|
--color-tooltip-foreground: hsl(var(--tooltip-foreground));
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Runtime Theme Variables
|
|
These are HSL values WITHOUT the hsl() wrapper, allowing alpha transparency.
|
|
Set dynamically by ThemeProvider via applyTheme().
|
|
========================================================================== */
|
|
|
|
:root {
|
|
/* Core colors - light theme defaults (overridden by ThemeProvider) */
|
|
--background: 0 0% 100%;
|
|
--foreground: 222.2 84% 4.9%;
|
|
--card: 0 0% 100%;
|
|
--card-foreground: 222.2 84% 4.9%;
|
|
--popover: 0 0% 100%;
|
|
--popover-foreground: 222.2 84% 4.9%;
|
|
--primary: 222.2 47.4% 11.2%;
|
|
--primary-foreground: 210 40% 98%;
|
|
--secondary: 210 40% 96.1%;
|
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
--muted: 210 40% 96.1%;
|
|
--muted-foreground: 215.4 16.3% 46.9%;
|
|
--accent: 210 40% 96.1%;
|
|
--accent-foreground: 222.2 47.4% 11.2%;
|
|
--destructive: 0 84.2% 60.2%;
|
|
--destructive-foreground: 210 40% 98%;
|
|
--border: 214.3 31.8% 91.4%;
|
|
--input: 214.3 31.8% 91.4%;
|
|
--ring: 222.2 84% 4.9%;
|
|
--radius: 0.5rem;
|
|
|
|
/* Status colors */
|
|
--success: 142 76% 36%;
|
|
--warning: 45 93% 47%;
|
|
--info: 199 89% 48%;
|
|
|
|
/* Nostr-specific colors */
|
|
--zap: 45 93% 40%;
|
|
--live: 0 72% 45%;
|
|
|
|
/* UI highlight (active user, self-references) */
|
|
--highlight: 25 90% 35%;
|
|
|
|
/* Tooltip colors */
|
|
--tooltip: 222.2 47.4% 11.2%;
|
|
--tooltip-foreground: 210 40% 98%;
|
|
|
|
/* Syntax highlighting */
|
|
--syntax-comment: 215.4 16.3% 46.9%;
|
|
--syntax-punctuation: 222.2 84% 30%;
|
|
--syntax-property: 222.2 47.4% 11.2%;
|
|
--syntax-string: 142 60% 30%;
|
|
--syntax-keyword: 270 80% 50%;
|
|
--syntax-function: 222.2 47.4% 11.2%;
|
|
--syntax-variable: 222.2 84% 4.9%;
|
|
--syntax-operator: 222.2 84% 20%;
|
|
--syntax-constant: 199 89% 48%;
|
|
--syntax-tag: 142 76% 36%;
|
|
|
|
/* Diff colors */
|
|
--diff-inserted: 142 60% 30%;
|
|
--diff-inserted-bg: 142 60% 50% / 0.15;
|
|
--diff-deleted: 0 70% 45%;
|
|
--diff-deleted-bg: 0 70% 50% / 0.15;
|
|
--diff-meta: 199 80% 40%;
|
|
--diff-meta-bg: 199 80% 50% / 0.1;
|
|
|
|
/* Scrollbar */
|
|
--scrollbar-thumb: 222.2 84% 4.9% / 0.2;
|
|
--scrollbar-thumb-hover: 222.2 84% 4.9% / 0.3;
|
|
--scrollbar-track: 0 0% 0% / 0;
|
|
|
|
/* Gradient colors (RGB values) */
|
|
--gradient-1: 234 179 8;
|
|
--gradient-2: 249 115 22;
|
|
--gradient-3: 147 51 234;
|
|
--gradient-4: 6 182 212;
|
|
}
|
|
|
|
/* Dark theme - applied via .dark class or ThemeProvider */
|
|
.dark {
|
|
--background: 222.2 84% 4.9%;
|
|
--foreground: 210 40% 98%;
|
|
--card: 222.2 84% 4.9%;
|
|
--card-foreground: 210 40% 98%;
|
|
--popover: 222.2 84% 4.9%;
|
|
--popover-foreground: 210 40% 98%;
|
|
--primary: 210 40% 98%;
|
|
--primary-foreground: 222.2 47.4% 11.2%;
|
|
--secondary: 217.2 32.6% 17.5%;
|
|
--secondary-foreground: 210 40% 98%;
|
|
--muted: 217.2 32.6% 17.5%;
|
|
--muted-foreground: 215 20.2% 70%;
|
|
--accent: 270 100% 70%;
|
|
--accent-foreground: 222.2 84% 4.9%;
|
|
--destructive: 0 72% 63%;
|
|
--destructive-foreground: 210 40% 98%;
|
|
--border: 217.2 32.6% 17.5%;
|
|
--input: 217.2 32.6% 17.5%;
|
|
--ring: 212.7 26.8% 83.9%;
|
|
|
|
/* Status colors */
|
|
--success: 142 76% 46%;
|
|
--warning: 38 92% 60%;
|
|
--info: 199 89% 58%;
|
|
|
|
/* Nostr-specific colors */
|
|
--zap: 45 93% 58%;
|
|
--live: 0 72% 51%;
|
|
|
|
/* UI highlight (active user, self-references) */
|
|
--highlight: 27 96% 61%;
|
|
|
|
/* Tooltip colors */
|
|
--tooltip: 217.2 32.6% 30%;
|
|
--tooltip-foreground: 210 40% 98%;
|
|
|
|
/* Syntax highlighting - high contrast for dark backgrounds */
|
|
--syntax-comment: 215 20% 55%;
|
|
--syntax-punctuation: 210 30% 75%;
|
|
--syntax-property: 210 40% 98%;
|
|
--syntax-string: 140 70% 65%;
|
|
--syntax-keyword: 270 100% 80%;
|
|
--syntax-function: 210 40% 98%;
|
|
--syntax-variable: 210 40% 98%;
|
|
--syntax-operator: 210 40% 98%;
|
|
--syntax-constant: 199 90% 70%;
|
|
--syntax-tag: 140 70% 65%;
|
|
|
|
/* Diff colors */
|
|
--diff-inserted: 134 60% 76%;
|
|
--diff-inserted-bg: 145 63% 42% / 0.1;
|
|
--diff-deleted: 0 100% 76%;
|
|
--diff-deleted-bg: 0 100% 60% / 0.1;
|
|
--diff-meta: 190 77% 70%;
|
|
--diff-meta-bg: 190 77% 70% / 0.08;
|
|
|
|
/* Scrollbar */
|
|
--scrollbar-thumb: 0 0% 100% / 0.2;
|
|
--scrollbar-thumb-hover: 0 0% 100% / 0.3;
|
|
--scrollbar-track: 0 0% 0% / 0;
|
|
|
|
/* Gradient colors (RGB values) */
|
|
--gradient-1: 250 204 21;
|
|
--gradient-2: 251 146 60;
|
|
--gradient-3: 168 85 247;
|
|
--gradient-4: 34 211 238;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Custom Scrollbar Styling
|
|
========================================================================== */
|
|
|
|
* {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: hsl(var(--scrollbar-thumb)) hsl(var(--scrollbar-track));
|
|
}
|
|
|
|
*::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-track {
|
|
background: hsl(var(--scrollbar-track));
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb {
|
|
background-color: hsl(var(--scrollbar-thumb));
|
|
border-radius: 4px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background-color: hsl(var(--scrollbar-thumb-hover));
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Base Layer - Global Styles
|
|
========================================================================== */
|
|
|
|
@layer base {
|
|
* {
|
|
@apply border-border;
|
|
}
|
|
body {
|
|
@apply bg-background text-foreground font-mono;
|
|
/* iOS PWA safe area insets for notch support */
|
|
padding-top: env(safe-area-inset-top, 0);
|
|
padding-right: env(safe-area-inset-right, 0);
|
|
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
padding-left: env(safe-area-inset-left, 0);
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Custom Utilities (v4 @utility syntax)
|
|
========================================================================== */
|
|
|
|
@utility no-scrollbar {
|
|
-ms-overflow-style: none !important;
|
|
scrollbar-width: none !important;
|
|
|
|
&::-webkit-scrollbar {
|
|
display: none !important;
|
|
width: 0 !important;
|
|
height: 0 !important;
|
|
}
|
|
}
|
|
|
|
@utility hide-scrollbar {
|
|
-ms-overflow-style: none !important;
|
|
scrollbar-width: none !important;
|
|
|
|
&::-webkit-scrollbar {
|
|
display: none !important;
|
|
width: 0 !important;
|
|
height: 0 !important;
|
|
}
|
|
}
|
|
|
|
@utility text-grimoire-gradient {
|
|
background: linear-gradient(
|
|
to bottom,
|
|
rgb(var(--gradient-1)),
|
|
rgb(var(--gradient-2)),
|
|
rgb(var(--gradient-3)),
|
|
rgb(var(--gradient-4))
|
|
);
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Third-Party Component Overrides
|
|
========================================================================== */
|
|
|
|
/* react-medium-image-zoom theme customization */
|
|
[data-rmiz-modal-overlay] {
|
|
background-color: hsl(var(--background) / 0.92) !important;
|
|
}
|
|
|
|
[data-rmiz-modal-content] {
|
|
box-shadow: 0 0 40px hsl(var(--foreground) / 0.2);
|
|
}
|
|
|
|
/* ==========================================================================
|
|
React Mosaic Theme Customization
|
|
========================================================================== */
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme {
|
|
background: hsl(var(--background));
|
|
}
|
|
|
|
/* Smooth animations for window resizing and repositioning */
|
|
/* Only animate during preset application, not manual resize/drag */
|
|
body.animating-layout
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-tile {
|
|
transition:
|
|
width 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
|
height 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
|
top 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
|
|
left 150ms cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
contain: layout; /* Isolate layout calculations for better performance */
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window
|
|
.mosaic-window-toolbar {
|
|
background: hsl(var(--muted));
|
|
border: none;
|
|
border-bottom: 1px solid hsl(var(--border));
|
|
border-radius: 0;
|
|
color: hsl(var(--foreground));
|
|
height: 30px;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window
|
|
.mosaic-window-title {
|
|
color: hsl(var(--foreground));
|
|
font-family: inherit;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window {
|
|
background: hsl(var(--background));
|
|
outline: none;
|
|
border-radius: 0 !important;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window * {
|
|
border-radius: 0 !important;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window::before,
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window::after {
|
|
display: none;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme.bp4-dark .mosaic-window,
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme.bp4-dark .mosaic-preview {
|
|
box-shadow: none;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window
|
|
.mosaic-window-body {
|
|
background: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-window-controls {
|
|
color: hsl(var(--muted-foreground));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window-controls:hover {
|
|
color: hsl(var(--foreground));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window-toolbar
|
|
.separator {
|
|
border-left: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme
|
|
.mosaic-window-body-overlay {
|
|
background: hsl(var(--background));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-preview {
|
|
background: hsl(var(--accent) / 0.3);
|
|
border: 2px solid hsl(var(--primary));
|
|
outline: none;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-drop-target {
|
|
border: 2px solid var(--border);
|
|
outline: none;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split:hover {
|
|
background: hsl(var(--primary));
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-row {
|
|
width: 4px;
|
|
margin: 0 -2px;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-column {
|
|
height: 4px;
|
|
margin: -2px 0;
|
|
}
|
|
|
|
/* Mobile: Wider split dividers for touch dragging */
|
|
@media (max-width: 767px) {
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-row {
|
|
width: 12px;
|
|
margin: 0 -6px;
|
|
}
|
|
|
|
.mosaic.mosaic-blueprint-theme.mosaic-blueprint-theme .mosaic-split.-column {
|
|
height: 12px;
|
|
margin: -6px 0;
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
Accessibility: Focus Indicators
|
|
========================================================================== */
|
|
|
|
@layer base {
|
|
/* Focus-visible for buttons and interactive elements */
|
|
button:focus-visible,
|
|
a:focus-visible,
|
|
[role="button"]:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Focus-visible for input elements */
|
|
input:focus-visible,
|
|
textarea:focus-visible,
|
|
select:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 0;
|
|
}
|
|
|
|
/* Focus-visible for command launcher items */
|
|
[cmdk-item]:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
/* Focus-visible for tab buttons */
|
|
.tabbar-button:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
/* ==========================================================================
|
|
TipTap Editor Styles
|
|
========================================================================== */
|
|
|
|
.ProseMirror {
|
|
min-height: 1.25rem;
|
|
line-height: 1.25rem;
|
|
position: relative;
|
|
}
|
|
|
|
.ProseMirror:focus {
|
|
outline: none;
|
|
}
|
|
|
|
.ProseMirror p {
|
|
margin: 0;
|
|
line-height: 1.25rem;
|
|
}
|
|
|
|
.ProseMirror p.is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
color: hsl(var(--muted-foreground));
|
|
pointer-events: none;
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
}
|
|
|
|
/* Disable link navigation in editor - allow editing instead of clicking */
|
|
.ProseMirror a {
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Mention styles */
|
|
.ProseMirror .mention {
|
|
color: hsl(var(--primary));
|
|
background-color: hsl(var(--primary) / 0.1);
|
|
padding: 0.125rem 0.25rem;
|
|
border-radius: 0.25rem;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.ProseMirror .mention:hover {
|
|
background-color: hsl(var(--primary) / 0.2);
|
|
}
|
|
|
|
/* Emoji styles */
|
|
.ProseMirror .emoji-node {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.ProseMirror .emoji-image {
|
|
height: 1.2em;
|
|
width: auto;
|
|
vertical-align: middle;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.ProseMirror .emoji-unicode {
|
|
font-size: 1.1em;
|
|
line-height: 1;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
/* Nostr event preview styles */
|
|
.ProseMirror .nostr-event-preview {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.125rem 0.375rem;
|
|
background-color: hsl(var(--primary) / 0.1);
|
|
border: 1px solid hsl(var(--primary) / 0.3);
|
|
border-radius: 0.25rem;
|
|
font-size: 0.75rem;
|
|
vertical-align: middle;
|
|
cursor: default;
|
|
transition: background-color 0.2s;
|
|
}
|
|
|
|
.ProseMirror .nostr-event-preview:hover {
|
|
background-color: hsl(var(--primary) / 0.15);
|
|
}
|
|
|
|
/* Hide scrollbar in RichEditor */
|
|
.rich-editor .ProseMirror {
|
|
scrollbar-width: none; /* Firefox */
|
|
-ms-overflow-style: none; /* IE/Edge */
|
|
}
|
|
|
|
.rich-editor .ProseMirror::-webkit-scrollbar {
|
|
display: none; /* Chrome/Safari/Opera */
|
|
}
|