feat(creator): retire old workspace and polish CreatorEdit route & dashboard navigation
This commit is contained in:
@@ -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<Creator> Logger
|
|
||||||
|
|
||||||
<PageTitle>Workspace Autora | Nexus Reader</PageTitle>
|
|
||||||
|
|
||||||
<div class="workspace-container">
|
|
||||||
<!-- Left Sidebar for Chapter Selection -->
|
|
||||||
<aside class="workspace-sidebar glass-panel">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<button type="button" class="back-dashboard-btn" @onclick="NavigateToDashboard">
|
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<polyline points="15 18 9 12 15 6"></polyline>
|
|
||||||
</svg>
|
|
||||||
<span>Dashboard</span>
|
|
||||||
</button>
|
|
||||||
<h3 class="sidebar-title">Rozdziały</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="chapters-nav">
|
|
||||||
@if (_chaptersLoading)
|
|
||||||
{
|
|
||||||
<div class="sidebar-loading">
|
|
||||||
<div class="spinner-glow small"></div>
|
|
||||||
<span>Ładowanie spisu...</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (_chapters == null || !_chapters.Any())
|
|
||||||
{
|
|
||||||
<div class="sidebar-empty">Brak rozdziałów w tej wersji.</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<ul class="chapters-list">
|
|
||||||
@foreach (var ch in _chapters)
|
|
||||||
{
|
|
||||||
<li class="chapter-item @(ch.Id == _activeChapterId ? "active" : "")" @onclick="() => LoadChapterContentAsync(ch.Id)">
|
|
||||||
<span class="chapter-order">@ch.SortOrder.</span>
|
|
||||||
<span class="chapter-name" title="@ch.Title">@ch.Title</span>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
}
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
<!-- Right Workspace Area -->
|
|
||||||
<main class="workspace-content">
|
|
||||||
@if (_contentLoading)
|
|
||||||
{
|
|
||||||
<div class="editor-loading-placeholder glass-panel">
|
|
||||||
<div class="spinner-glow"></div>
|
|
||||||
<h3 class="loading-title">Wczytywanie treści rozdziału...</h3>
|
|
||||||
<p>Przygotowywanie edytora Zen Mode i sprawdzanie kopii zapasowych w LocalStorage...</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else if (_activeChapterId == Guid.Empty)
|
|
||||||
{
|
|
||||||
<div class="workspace-empty glass-panel">
|
|
||||||
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4z"></path>
|
|
||||||
</svg>
|
|
||||||
<h3>Wybierz rozdział z listy</h3>
|
|
||||||
<p>Kliknij na dowolny tytuł w panelu bocznym, aby rozpocząć pisanie lub edycję.</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="editor-workspace-card glass-panel" spellcheck="false">
|
|
||||||
<div class="editor-header-meta">
|
|
||||||
<h2 class="active-chapter-title">@_activeChapterTitle</h2>
|
|
||||||
<span class="chapter-id-badge">ID: @_activeChapterId.ToString().Substring(0, 8)...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="editor-growing-area">
|
|
||||||
<MarkdownEditor @ref="_editorRef"
|
|
||||||
InitialMarkdown="@_chapterMarkdown"
|
|
||||||
ChapterId="@_activeChapterId"
|
|
||||||
ServerTimestamp="@_serverTimestamp"
|
|
||||||
OnSave="HandleSave"
|
|
||||||
ShowFetchButton="true"
|
|
||||||
Height="100%" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public Guid? BookId { get; set; }
|
|
||||||
|
|
||||||
private MarkdownEditor? _editorRef;
|
|
||||||
private bool _chaptersLoading = true;
|
|
||||||
private bool _contentLoading = false;
|
|
||||||
|
|
||||||
private List<ChapterItemDto> _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<List<ChapterItemDto>>($"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<ChapterDetailsDto>($"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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -372,7 +372,7 @@
|
|||||||
{
|
{
|
||||||
if (book.FirstChapterId.HasValue)
|
if (book.FirstChapterId.HasValue)
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo($"/creator/edit/{book.Id}?chapterId={book.FirstChapterId.Value}");
|
NavigationManager.NavigateTo($"/creator/edit/{book.Id}/{book.FirstChapterId.Value}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
@page "/creator/edit/{BookId}"
|
||||||
@page "/creator/edit/{BookId}/{ChapterId}"
|
@page "/creator/edit/{BookId}/{ChapterId}"
|
||||||
@layout MainHubLayout
|
@layout MainHubLayout
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@@ -6,29 +7,37 @@
|
|||||||
|
|
||||||
<div class="chapters-sidebar">
|
<div class="chapters-sidebar">
|
||||||
<h2>Rozdziały</h2>
|
<h2>Rozdziały</h2>
|
||||||
<div class="chapter-item active">
|
<div class="chapters-list-wrapper">
|
||||||
<span>1. Rozdział 1: Wprowadzenie d...</span>
|
<div class="chapter-item active">
|
||||||
</div>
|
<i class="fa-solid fa-book-open chapter-icon"></i>
|
||||||
<div class="chapter-item">
|
<span>1. Rozdział 1: Wprowadzenie do Zen Mode</span>
|
||||||
<span>2. Rozdział 2: Zabezpieczenia i...</span>
|
</div>
|
||||||
|
<div class="chapter-item">
|
||||||
|
<i class="fa-solid fa-lock chapter-icon"></i>
|
||||||
|
<span>2. Rozdział 2: Zabezpieczenia i Architektura</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-workspace-area">
|
<div class="editor-workspace-area">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
|
||||||
|
<div class="editor-header-row">
|
||||||
<h1 class="chapter-title">Rozdział 1: Wprowadzenie do Zen Mode</h1>
|
<h1 class="chapter-title">Rozdział 1: Wprowadzenie do Zen Mode</h1>
|
||||||
<span class="chapter-id-badge">ID: @GetSafeChapterIdPrefix()...</span>
|
<div class="chapter-meta-zone">
|
||||||
|
<span class="chapter-id-badge">ID: @ChapterId</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-canvas-card">
|
<div class="editor-canvas-card">
|
||||||
|
|
||||||
<div class="milkdown-premium-container" spellcheck="false">
|
<div class="milkdown-premium-container" spellcheck="false">
|
||||||
<div id="editor"></div>
|
<div id="editor"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="editor-footer-bar">
|
<div class="editor-footer-bar">
|
||||||
<div class="cloud-status">
|
<div class="cloud-status-container">
|
||||||
<i class="fa-solid fa-circle-check"></i>
|
<span class="cloud-status-pulse"></span>
|
||||||
<span>Saved to Cloud</span>
|
<span class="cloud-status-text">Saved to Cloud</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-nexus-premium" @onclick="FetchContent">
|
<button class="btn-nexus-premium" @onclick="FetchContent">
|
||||||
@@ -36,25 +45,19 @@
|
|||||||
<i class="fa-solid fa-arrow-right"></i>
|
<i class="fa-solid fa-arrow-right"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter] public string BookId { get; set; } = string.Empty;
|
||||||
public string BookId { get; set; } = string.Empty;
|
[Parameter] public string ChapterId { get; set; } = string.Empty;
|
||||||
|
private string _retrievedMarkdown = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
private async Task FetchContent()
|
||||||
public string ChapterId { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
private void FetchContent()
|
|
||||||
{
|
{
|
||||||
// Actual retrieval logic can go here
|
// Tutaj trafia Twoja logika wyciągania zawartości z edytora Milkdown
|
||||||
}
|
await Task.CompletedTask;
|
||||||
|
|
||||||
private string GetSafeChapterIdPrefix()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(ChapterId)) return "N/A";
|
|
||||||
return ChapterId.Length >= 8 ? ChapterId.Substring(0, 8) : ChapterId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
.creator-edit-fullscreen-wrapper {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
height: calc(100vh - 4rem) !important; /* Idealne odcięcie od topbaru platformy */
|
height: calc(100vh - 4rem) !important; /* Sztywne cięcie pod wysokość topbaru platformy */
|
||||||
margin: 0;
|
margin: 0 !important;
|
||||||
padding: 0 !important; /* Likwidujemy marginesy zewnętrzne dla pełnego ekranu roboczego */
|
padding: 0 !important;
|
||||||
display: flex;
|
display: flex !important;
|
||||||
overflow: hidden !important; /* Całkowity zakaz przewijania głównego okna */
|
overflow: hidden !important; /* Całkowity zakaz scrollowania okna głównego */
|
||||||
background-color: var(--bg-base);
|
background-color: var(--bg-base);
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 2. LUKSUSOWY PANEL BOCZNY ROZDZIAŁÓW --- */
|
/* 2. MATTE CHAPTERS SIDEBAR (Luksusowy panel boczny) */
|
||||||
.chapters-sidebar {
|
.chapters-sidebar {
|
||||||
width: 280px !important;
|
width: 290px !important;
|
||||||
flex-shrink: 0;
|
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;
|
border-right: 1px solid rgba(255, 255, 255, 0.05) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 2rem 1rem !important;
|
padding: 2rem 1.25rem !important;
|
||||||
overflow-y: auto;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-light .chapters-sidebar {
|
.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;
|
border-right: 1px solid var(--border) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chapters-sidebar h2 {
|
.chapters-sidebar h2 {
|
||||||
font-size: 1.25rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-main);
|
text-transform: uppercase;
|
||||||
margin-bottom: 1.5rem;
|
letter-spacing: 1.5px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chapters-list-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Elementy listy rozdziałów */
|
/* Elementy listy rozdziałów */
|
||||||
.chapter-item {
|
.chapter-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 14px !important;
|
gap: 12px;
|
||||||
margin-bottom: 4px;
|
padding: 12px 14px !important;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
cursor: pointer;
|
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;
|
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 {
|
.chapter-item.active {
|
||||||
background-color: rgba(0, 255, 153, 0.04) !important;
|
background-color: rgba(0, 255, 153, 0.04) !important;
|
||||||
color: var(--accent) !important;
|
color: var(--accent) !important;
|
||||||
@@ -57,6 +75,10 @@
|
|||||||
border-left: 3px solid var(--accent) !important;
|
border-left: 3px solid var(--accent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chapter-item.active i.chapter-icon {
|
||||||
|
color: var(--accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-light .chapter-item.active {
|
.theme-light .chapter-item.active {
|
||||||
background-color: rgba(16, 185, 129, 0.06) !important;
|
background-color: rgba(16, 185, 129, 0.06) !important;
|
||||||
}
|
}
|
||||||
@@ -66,38 +88,52 @@
|
|||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 3. OBSZAR WORKSPACE (KREATOR CENTRUM) --- */
|
/* 3. WORKSPACE CORE (Główna oś edytora) */
|
||||||
.editor-workspace-area {
|
.editor-workspace-area {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding: 2.5rem 3.5rem 2rem 3.5rem !important; /* Odpowiedni, dostojny margines Zen Mode */
|
||||||
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
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 {
|
.editor-workspace-area h1.chapter-title {
|
||||||
font-size: 2.2rem;
|
font-size: 2.2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
margin: 0 0 1.5rem 0;
|
margin: 0;
|
||||||
letter-spacing: -0.5px;
|
letter-spacing: -0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dyskretny indykator ID rozdziału */
|
|
||||||
.chapter-id-badge {
|
.chapter-id-badge {
|
||||||
font-family: 'Azeret Mono', monospace;
|
font-family: 'Azeret Mono', monospace;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
background: rgba(255, 255, 255, 0.02);
|
background: rgba(255, 255, 255, 0.03);
|
||||||
padding: 2px 8px;
|
padding: 5px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 6px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
opacity: 0.6;
|
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 {
|
.editor-canvas-card {
|
||||||
background-color: var(--bg-surface) !important;
|
background-color: var(--bg-surface) !important;
|
||||||
border: 1px solid var(--border) !important;
|
border: 1px solid var(--border) !important;
|
||||||
@@ -105,37 +141,151 @@
|
|||||||
padding: 2.5rem !important;
|
padding: 2.5rem !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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;
|
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 {
|
::deep .ProseMirror {
|
||||||
color: var(--text-main) !important;
|
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;
|
flex-grow: 1;
|
||||||
overflow-y: auto !important; /* Przewija się tylko tekst książki! */
|
overflow-y: auto !important; /* Scroll pojawia się TYLKO na tekście rozdziału */
|
||||||
padding-right: 20px !important;
|
padding-right: 15px !important;
|
||||||
outline: none !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 {
|
.editor-footer-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
padding-top: 1rem;
|
padding-top: 1.25rem;
|
||||||
border-top: 1px solid var(--border);
|
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;
|
display: flex;
|
||||||
align-items: center;
|
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;
|
font-size: 0.85rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cloud-status i {
|
/* Przycisk akcji premium */
|
||||||
color: var(--accent); /* Zielona dioda zapisu statusu */
|
.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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user