From 00ebee862811c254812fa38a87c0ed7716a71c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Jasi=C5=84ski?= Date: Sun, 14 Jun 2026 11:42:34 +0200 Subject: [PATCH] feat(creator): retire old workspace and polish CreatorEdit route & dashboard navigation --- src/NexusReader.UI.Shared/Pages/Creator.razor | 218 -------------- .../Pages/Creator.razor.css | 275 ------------------ .../Pages/CreatorDashboard.razor | 2 +- .../Pages/CreatorEdit.razor | 49 ++-- .../Pages/CreatorEdit.razor.css | 230 ++++++++++++--- 5 files changed, 217 insertions(+), 557 deletions(-) delete mode 100644 src/NexusReader.UI.Shared/Pages/Creator.razor delete mode 100644 src/NexusReader.UI.Shared/Pages/Creator.razor.css diff --git a/src/NexusReader.UI.Shared/Pages/Creator.razor b/src/NexusReader.UI.Shared/Pages/Creator.razor deleted file mode 100644 index 09fed3c..0000000 --- a/src/NexusReader.UI.Shared/Pages/Creator.razor +++ /dev/null @@ -1,218 +0,0 @@ -@page "/creator/edit/{BookId:guid}" -@attribute [Authorize] -@using System.Net.Http.Json -@using Microsoft.Extensions.Logging -@using NexusReader.Application.DTOs.Creator -@inject HttpClient Http -@inject NavigationManager NavigationManager -@inject ILogger Logger - -Workspace Autora | Nexus Reader - -
- - - - -
- @if (_contentLoading) - { -
-
-

Wczytywanie treści rozdziału...

-

Przygotowywanie edytora Zen Mode i sprawdzanie kopii zapasowych w LocalStorage...

-
- } - else if (_activeChapterId == Guid.Empty) - { -
- - - - -

Wybierz rozdział z listy

-

Kliknij na dowolny tytuł w panelu bocznym, aby rozpocząć pisanie lub edycję.

-
- } - else - { -
-
-

@_activeChapterTitle

- ID: @_activeChapterId.ToString().Substring(0, 8)... -
- -
- -
-
- } -
-
- -@code { - [Parameter] - public Guid? BookId { get; set; } - - private MarkdownEditor? _editorRef; - private bool _chaptersLoading = true; - private bool _contentLoading = false; - - private List _chapters = new(); - private Guid _activeChapterId = Guid.Empty; - private string _activeChapterTitle = string.Empty; - private string _chapterMarkdown = string.Empty; - private DateTime _serverTimestamp = DateTime.UtcNow; - - public class ChapterItemDto - { - public Guid Id { get; set; } - public string Title { get; set; } = string.Empty; - public int SortOrder { get; set; } - } - - public class ChapterDetailsDto - { - public Guid Id { get; set; } - public string Title { get; set; } = string.Empty; - public string MarkdownContent { get; set; } = string.Empty; - } - - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - if (BookId.HasValue && BookId.Value != Guid.Empty) - { - await LoadBookChaptersAsync(); - } - else - { - _chaptersLoading = false; - _chapters.Clear(); - _activeChapterId = Guid.Empty; - _chapterMarkdown = string.Empty; - } - } - - private async Task LoadBookChaptersAsync() - { - _chaptersLoading = true; - StateHasChanged(); - - try - { - _chapters = await Http.GetFromJsonAsync>($"api/creator/books/{BookId}/chapters") ?? new(); - - // Extract the query parameter chapterId if available - var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri); - Guid targetChapterId = Guid.Empty; - - if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("chapterId", out var chapterValue)) - { - Guid.TryParse(chapterValue, out targetChapterId); - } - - if (targetChapterId != Guid.Empty && _chapters.Any(c => c.Id == targetChapterId)) - { - await LoadChapterContentAsync(targetChapterId); - } - else if (_chapters.Any()) - { - await LoadChapterContentAsync(_chapters.First().Id); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Failed to load book chapters."); - } - finally - { - _chaptersLoading = false; - StateHasChanged(); - } - } - - private async Task LoadChapterContentAsync(Guid chapterId) - { - if (chapterId == Guid.Empty) return; - - _contentLoading = true; - _activeChapterId = chapterId; - _activeChapterTitle = _chapters.FirstOrDefault(c => c.Id == chapterId)?.Title ?? "Rozdział"; - StateHasChanged(); - - try - { - var details = await Http.GetFromJsonAsync($"api/chapters/{chapterId}"); - if (details != null) - { - _chapterMarkdown = details.MarkdownContent; - _serverTimestamp = DateTime.UtcNow; // Used to check database sync freshness - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Failed to load chapter content."); - _chapterMarkdown = string.Empty; - } - finally - { - _contentLoading = false; - StateHasChanged(); - } - } - - private void HandleSave(string markdown) - { - _chapterMarkdown = markdown; - Logger.LogInformation("Saved markdown content length: {Length}", markdown.Length); - } - - private void NavigateToDashboard() - { - NavigationManager.NavigateTo("/creator"); - } -} diff --git a/src/NexusReader.UI.Shared/Pages/Creator.razor.css b/src/NexusReader.UI.Shared/Pages/Creator.razor.css deleted file mode 100644 index d5fe00d..0000000 --- a/src/NexusReader.UI.Shared/Pages/Creator.razor.css +++ /dev/null @@ -1,275 +0,0 @@ -.workspace-container { - display: flex; - min-height: calc(100vh - 64px); /* assuming top navbar is 64px */ - width: 100%; - background: var(--bg-base); - animation: fade-in 0.4s ease-out; -} - -@keyframes fade-in { - from { opacity: 0; } - to { opacity: 1; } -} - -/* --- Left Sidebar --- */ -.workspace-sidebar { - width: 280px; - flex-shrink: 0; - border-right: 1px solid var(--border); - background: var(--bg-surface); - display: flex; - flex-direction: column; - padding: 1.5rem 0; - z-index: 10; -} - -.sidebar-header { - padding: 0 1.5rem 1.5rem; - border-bottom: 1px dashed var(--border); - display: flex; - flex-direction: column; - gap: 1rem; -} - -.back-dashboard-btn { - background: transparent; - border: none; - color: var(--text-muted); - font-size: 0.85rem; - font-weight: 600; - display: inline-flex; - align-items: center; - gap: 0.25rem; - cursor: pointer; - padding: 0.25rem 0; - transition: color 0.2s; - width: fit-content; -} - -.back-dashboard-btn:hover { - color: var(--text-main); -} - -.sidebar-title { - font-family: var(--nexus-font-serif, serif); - font-size: 1.25rem; - font-weight: 700; - color: var(--text-main); - margin: 0; -} - -.chapters-nav { - flex: 1; - overflow-y: auto; - padding: 1rem 0; -} - -.sidebar-loading, .sidebar-empty { - display: flex; - align-items: center; - justify-content: center; - gap: 0.5rem; - padding: 2rem 1.5rem; - color: var(--text-muted); - font-size: 0.85rem; - text-align: center; -} - -.chapters-list { - list-style: none; - padding: 0; - margin: 0; - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.chapter-item { - padding: 0.75rem 1.5rem; - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - transition: all 0.2s ease; - border-left: 3px solid transparent; - color: var(--text-muted); -} - -.chapter-item:hover { - background: rgba(255, 255, 255, 0.02); - color: var(--text-main); -} - -.chapter-item.active { - background: rgba(16, 185, 129, 0.03); - border-left-color: var(--accent); - color: var(--text-main); - font-weight: 600; -} - -.chapter-order { - font-size: 0.8rem; - opacity: 0.5; -} - -.chapter-name { - font-size: 0.9rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* --- Right Content Workspace --- */ -.workspace-content { - flex: 1; - padding: 2.5rem; - display: flex; - flex-direction: column; - overflow-y: auto; - max-width: 1200px; - margin: 0 auto; - width: 100%; -} - -.workspace-empty, .editor-loading-placeholder { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 4rem 2rem; - gap: 1.5rem; - height: 100%; - min-height: 400px; -} - -.workspace-empty svg { - color: var(--text-muted); - opacity: 0.4; -} - -.workspace-empty h3, .loading-title { - font-family: var(--nexus-font-serif, serif); - font-size: 1.5rem; - font-weight: 600; - color: var(--text-main); - margin: 0; -} - -.workspace-empty p { - font-size: 0.95rem; - color: var(--text-muted); - max-width: 400px; - line-height: 1.5; -} - -.editor-workspace-card { - display: flex; - flex-direction: column; - gap: 1.5rem; - padding: 2rem; - height: 100%; - min-height: 500px; -} - -.editor-header-meta { - display: flex; - justify-content: space-between; - align-items: center; - padding-bottom: 1rem; - border-bottom: 1px dashed var(--border); -} - -.active-chapter-title { - font-family: var(--nexus-font-serif, serif); - font-size: 1.75rem; - font-weight: 700; - color: var(--text-main); - margin: 0; -} - -.chapter-id-badge { - font-size: 0.75rem; - color: var(--text-muted); - padding: 4px 10px; - background: var(--bg-base); - border: 1px solid var(--border); - border-radius: 6px; - text-transform: uppercase; -} - -.editor-growing-area { - flex: 1; - display: flex; - flex-direction: column; -} - -/* Glassmorphism Panel styles */ -.glass-panel { - background: var(--bg-surface); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 4px 30px rgba(0, 0, 0, 0.03); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); -} - -.spinner-glow { - width: 36px; - height: 36px; - border: 3px solid rgba(16, 185, 129, 0.1); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin-glow 1s linear infinite; - box-shadow: 0 0 10px rgba(16, 185, 129, 0.2); -} - -@keyframes spin-glow { - 100% { transform: rotate(360deg); } -} - -/* --- Mobile View Adjustments --- */ -@media (max-width: 992px) { - .workspace-sidebar { - width: 220px; - } - .workspace-content { - padding: 1.5rem; - } -} - -@media (max-width: 768px) { - .workspace-container { - flex-direction: column; - } - .workspace-sidebar { - width: 100%; - border-right: none; - border-bottom: 1px solid var(--border); - padding: 1rem 0; - } - .chapters-list { - flex-direction: row; - overflow-x: auto; - padding: 0 1rem; - } - .chapter-item { - padding: 0.5rem 1rem; - border-left: none; - border-bottom: 3px solid transparent; - white-space: nowrap; - } - .chapter-item.active { - border-bottom-color: var(--accent); - } - .sidebar-header { - padding: 0 1rem 0.5rem; - border-bottom: none; - } - .workspace-content { - padding: 1rem; - } - .active-chapter-title { - font-size: 1.35rem; - } -} diff --git a/src/NexusReader.UI.Shared/Pages/CreatorDashboard.razor b/src/NexusReader.UI.Shared/Pages/CreatorDashboard.razor index ca00a3f..dac264f 100644 --- a/src/NexusReader.UI.Shared/Pages/CreatorDashboard.razor +++ b/src/NexusReader.UI.Shared/Pages/CreatorDashboard.razor @@ -372,7 +372,7 @@ { if (book.FirstChapterId.HasValue) { - NavigationManager.NavigateTo($"/creator/edit/{book.Id}?chapterId={book.FirstChapterId.Value}"); + NavigationManager.NavigateTo($"/creator/edit/{book.Id}/{book.FirstChapterId.Value}"); } else { diff --git a/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor b/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor index 9d4c636..4633326 100644 --- a/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor +++ b/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor @@ -1,3 +1,4 @@ +@page "/creator/edit/{BookId}" @page "/creator/edit/{BookId}/{ChapterId}" @layout MainHubLayout @attribute [Authorize] @@ -6,29 +7,37 @@

Rozdziały

-
- 1. Rozdział 1: Wprowadzenie d... -
-
- 2. Rozdział 2: Zabezpieczenia i... +
+
+ + 1. Rozdział 1: Wprowadzenie do Zen Mode +
+
+ + 2. Rozdział 2: Zabezpieczenia i Architektura +
-
+ +

Rozdział 1: Wprowadzenie do Zen Mode

- ID: @GetSafeChapterIdPrefix()... +
+ ID: @ChapterId +
+
@code { - [Parameter] - public string BookId { get; set; } = string.Empty; + [Parameter] public string BookId { get; set; } = string.Empty; + [Parameter] public string ChapterId { get; set; } = string.Empty; + private string _retrievedMarkdown = string.Empty; - [Parameter] - public string ChapterId { get; set; } = string.Empty; - - private void FetchContent() + private async Task FetchContent() { - // Actual retrieval logic can go here - } - - private string GetSafeChapterIdPrefix() - { - if (string.IsNullOrEmpty(ChapterId)) return "N/A"; - return ChapterId.Length >= 8 ? ChapterId.Substring(0, 8) : ChapterId; + // Tutaj trafia Twoja logika wyciągania zawartości z edytora Milkdown + await Task.CompletedTask; } } diff --git a/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor.css b/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor.css index 9472ae9..3e2c092 100644 --- a/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor.css +++ b/src/NexusReader.UI.Shared/Pages/CreatorEdit.razor.css @@ -1,55 +1,73 @@ -/* --- 1. BLOKADA VIEWPORTU (ZERO GLOBAL SCROLL) --- */ +/* ========================================================================== + NEXUSREADER CREATOR EDIT MODE - PREMIUM SAAS CORE STYLES + ========================================================================== */ + +/* 1. MASTER WRAPPER (Zgładzenie globalnego scrolla przeglądarki) */ .creator-edit-fullscreen-wrapper { width: 100% !important; max-width: 100% !important; - height: calc(100vh - 4rem) !important; /* Idealne odcięcie od topbaru platformy */ - margin: 0; - padding: 0 !important; /* Likwidujemy marginesy zewnętrzne dla pełnego ekranu roboczego */ - display: flex; - overflow: hidden !important; /* Całkowity zakaz przewijania głównego okna */ + height: calc(100vh - 4rem) !important; /* Sztywne cięcie pod wysokość topbaru platformy */ + margin: 0 !important; + padding: 0 !important; + display: flex !important; + overflow: hidden !important; /* Całkowity zakaz scrollowania okna głównego */ background-color: var(--bg-base); + box-sizing: border-box; } -/* --- 2. LUKSUSOWY PANEL BOCZNY ROZDZIAŁÓW --- */ +/* 2. MATTE CHAPTERS SIDEBAR (Luksusowy panel boczny) */ .chapters-sidebar { - width: 280px !important; + width: 290px !important; flex-shrink: 0; - background-color: #16161a !important; /* Matowy, głęboki odcień grafitu */ + background-color: #141417 !important; /* Matowy głęboki antracyt z readera */ border-right: 1px solid rgba(255, 255, 255, 0.05) !important; display: flex; flex-direction: column; - padding: 2rem 1rem !important; - overflow-y: auto; + padding: 2rem 1.25rem !important; + box-sizing: border-box; } .theme-light .chapters-sidebar { - background-color: #ede9df !important; /* Harmonijna, ciemniejsza sepia dla jasnego motywu */ + background-color: #ede9df !important; /* Dopasowanie do motywu Warm Paper */ border-right: 1px solid var(--border) !important; } .chapters-sidebar h2 { - font-size: 1.25rem; + font-size: 0.85rem; font-weight: 700; - color: var(--text-main); - margin-bottom: 1.5rem; + text-transform: uppercase; + letter-spacing: 1.5px; + color: var(--text-muted); + margin: 0 0 1.5rem 0; padding-left: 0.5rem; } +.chapters-list-wrapper { + display: flex; + flex-direction: column; + gap: 4px; +} + /* Elementy listy rozdziałów */ .chapter-item { display: flex; align-items: center; - padding: 10px 14px !important; - margin-bottom: 4px; + gap: 12px; + padding: 12px 14px !important; border-radius: 8px; color: var(--text-muted); font-size: 0.95rem; cursor: pointer; - transition: all 0.2s ease; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); border-left: 3px solid transparent !important; } -/* Stan aktywny wybranego rozdziału (Zunifikowany z systemem) */ +.chapter-item i.chapter-icon { + font-size: 0.9rem; + color: var(--text-muted); +} + +/* Aktywny stan unifikacji wizualnej */ .chapter-item.active { background-color: rgba(0, 255, 153, 0.04) !important; color: var(--accent) !important; @@ -57,6 +75,10 @@ border-left: 3px solid var(--accent) !important; } +.chapter-item.active i.chapter-icon { + color: var(--accent) !important; +} + .theme-light .chapter-item.active { background-color: rgba(16, 185, 129, 0.06) !important; } @@ -66,38 +88,52 @@ color: var(--text-main); } -/* --- 3. OBSZAR WORKSPACE (KREATOR CENTRUM) --- */ +/* 3. WORKSPACE CORE (Główna oś edytora) */ .editor-workspace-area { flex-grow: 1; display: flex; flex-direction: column; height: 100%; + padding: 2.5rem 3.5rem 2rem 3.5rem !important; /* Odpowiedni, dostojny margines Zen Mode */ + box-sizing: border-box; overflow: hidden; - padding: 2.5rem 3.5rem 1.5rem 3.5rem !important; /* Większy oddech boczny dla trybu Zen */ } -/* Tytuł rozdziału u góry */ +/* Nagłówek i ID bez ryzyka kolizji */ +.editor-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-shrink: 0; + width: 100%; +} + .editor-workspace-area h1.chapter-title { font-size: 2.2rem; font-weight: 700; color: var(--text-main); - margin: 0 0 1.5rem 0; + margin: 0; letter-spacing: -0.5px; } -/* Dyskretny indykator ID rozdziału */ .chapter-id-badge { font-family: 'Azeret Mono', monospace; font-size: 0.75rem; color: var(--text-muted); - background: rgba(255, 255, 255, 0.02); - padding: 2px 8px; - border-radius: 4px; - border: 1px solid rgba(255, 255, 255, 0.05); - opacity: 0.6; + background: rgba(255, 255, 255, 0.03); + padding: 5px 12px; + border-radius: 6px; + border: 1px solid rgba(255, 255, 255, 0.08); + white-space: nowrap; } -/* --- 4. KARTA ROBOCZA EDYTORA I SCROLLBAR --- */ +.theme-light .chapter-id-badge { + background: rgba(0, 0, 0, 0.02); + border: 1px solid var(--border); +} + +/* 4. PREMIUM CANVAS CARD (Rozciąganie Flex na 100% wysokości) */ .editor-canvas-card { background-color: var(--bg-surface) !important; border: 1px solid var(--border) !important; @@ -105,37 +141,151 @@ padding: 2.5rem !important; display: flex; flex-direction: column; + flex-grow: 1; /* Wymusza rozciągnięcie do samego paska stopki */ + overflow: hidden; + box-sizing: border-box; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.milkdown-premium-container { flex-grow: 1; - overflow: hidden; /* Kontrola przewijania wyłącznie wewnątrz tekstu */ + display: flex; + flex-direction: column; + overflow: hidden; + width: 100%; +} + +/* --- DEEP SELECTORS DLA PROSEMIRROR (MILKDOWN RENDER) --- */ + +::deep .milkdown { + background: transparent !important; + box-shadow: none !important; + border: none !important; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + width: 100%; } ::deep .ProseMirror { color: var(--text-main) !important; + background-color: transparent !important; + font-family: inherit !important; + font-size: 1.1rem !important; + line-height: 1.75 !important; flex-grow: 1; - overflow-y: auto !important; /* Przewija się tylko tekst książki! */ - padding-right: 20px !important; + overflow-y: auto !important; /* Scroll pojawia się TYLKO na tekście rozdziału */ + padding-right: 15px !important; outline: none !important; + box-sizing: border-box; + width: 100%; } -/* --- 5. ZINTEGROWANY PASEK STANU I AKCJI (FOOTER) --- */ +/* Kontekst zaznaczenia zunifikowany z readerem */ +::deep .ProseMirror ::selection { + background-color: rgba(0, 255, 153, 0.25) !important; + color: inherit !important; +} + +.theme-light ::deep .ProseMirror ::selection { + background-color: rgba(16, 185, 129, 0.2) !important; +} + +/* Popover / dymek formatowania zintegrowany z czytnikiem */ +::deep .milkdown .popover, +::deep .milkdown-popover, +::deep .prosemirror-bubble-menu { + background-color: #1e1e22 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 12px !important; + padding: 6px 10px !important; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5) !important; + display: flex !important; + align-items: center !important; + gap: 12px !important; +} + +.theme-light ::deep .milkdown .popover, +.theme-light ::deep .prosemirror-bubble-menu { + background-color: #ffffff !important; + border: 1px solid var(--border) !important; + box-shadow: 0 10px 30px rgba(45, 42, 38, 0.06) !important; +} + +/* Customowy, dyskretny scrollbar dla tekstu książki */ +::deep .ProseMirror::-webkit-scrollbar { + width: 6px; +} +::deep .ProseMirror::-webkit-scrollbar-track { + background: transparent; +} +::deep .ProseMirror::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +/* 5. FIXED FOOTER BAR (Zintegrowany dół pancernej karty) */ .editor-footer-bar { display: flex; justify-content: space-between; align-items: center; margin-top: 1.5rem; - padding-top: 1rem; + padding-top: 1.25rem; border-top: 1px solid var(--border); - flex-shrink: 0; + flex-shrink: 0; /* Gwarancja, że pasek nigdy nie ucieknie poza kartę */ + width: 100%; } -.cloud-status { +/* Status zapisu w chmurze z pulsującą diodą akcentową */ +.cloud-status-container { display: flex; align-items: center; - gap: 8px; + gap: 10px; +} + +.cloud-status-pulse { + width: 8px; + height: 8px; + background-color: var(--accent); + border-radius: 50%; + display: inline-block; + box-shadow: 0 0 10px var(--accent); + position: relative; +} + +.cloud-status-text { + font-family: 'Azeret Mono', monospace; font-size: 0.85rem; color: var(--text-muted); } -.cloud-status i { - color: var(--accent); /* Zielona dioda zapisu statusu */ +/* Przycisk akcji premium */ +.btn-nexus-premium { + background-color: var(--accent) !important; + color: #121214 !important; + font-weight: 700; + font-size: 0.95rem; + padding: 10px 22px; + border: none !important; + border-radius: 8px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 10px; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 14px var(--accent-glow); +} + +.theme-light .btn-nexus-premium { + color: #ffffff !important; +} + +.btn-nexus-premium:hover { + transform: translateY(-1px); + box-shadow: 0 6px 20px var(--accent-glow); +} + +.btn-nexus-premium:active { + transform: translateY(0); }