refactor: resolve review comments in PR #81 (issuecomment-542)
This commit is contained in:
@@ -2,6 +2,7 @@ namespace NexusReader.Application.Abstractions.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for sanitizing raw input text (e.g. Markdown/HTML) to protect against XSS injection.
|
||||
/// Intended to have a Singleton lifetime.
|
||||
/// </summary>
|
||||
public interface ISanitizerService
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace NexusReader.Application.Abstractions.Services;
|
||||
|
||||
/// <summary>
|
||||
/// General file storage service interface for handling media uploads.
|
||||
/// Intended to have a Scoped lifetime.
|
||||
/// </summary>
|
||||
public interface IStorageService
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
namespace NexusReader.Application.DTOs.Media;
|
||||
|
||||
// Note: These DTOs are registered in AppJsonContext.cs for JSON source generation.
|
||||
|
||||
/// <summary>
|
||||
/// Request DTO for chapter validation/sanitization.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NexusReader.Infrastructure.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Settings for configuring allowed tags, attributes, CSS properties, and schemes in HtmlSanitizerService.
|
||||
/// </summary>
|
||||
public class HtmlSanitizerSettings
|
||||
{
|
||||
public const string SectionName = "HtmlSanitizer";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of HTML tags that are allowed.
|
||||
/// If null or empty, the default allowed tags list is used.
|
||||
/// </summary>
|
||||
public List<string>? AllowedTags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of HTML attributes that are allowed.
|
||||
/// If null or empty, the default allowed attributes list is used.
|
||||
/// </summary>
|
||||
public List<string>? AllowedAttributes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of CSS properties that are allowed.
|
||||
/// If null or empty, the default allowed CSS properties list is used.
|
||||
/// </summary>
|
||||
public List<string>? AllowedCssProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of URI schemes that are allowed (e.g. "http", "https").
|
||||
/// If null or empty, the default allowed schemes list is used.
|
||||
/// </summary>
|
||||
public List<string>? AllowedSchemes { get; set; }
|
||||
}
|
||||
@@ -78,6 +78,7 @@ public static class DependencyInjection
|
||||
services.Configure<AiSettings>(configuration.GetSection(AiSettings.SectionName));
|
||||
services.Configure<StripeSettings>(configuration.GetSection(StripeSettings.SectionName));
|
||||
services.Configure<RagMonetizationOptions>(configuration.GetSection(RagMonetizationOptions.SectionName));
|
||||
services.Configure<HtmlSanitizerSettings>(configuration.GetSection(HtmlSanitizerSettings.SectionName));
|
||||
var aiSettings = configuration.GetSection(AiSettings.SectionName).Get<AiSettings>() ?? new AiSettings();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aiSettings.ApiKey) || aiSettings.ApiKey == "PLACEHOLDER")
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Ganss.Xss;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NexusReader.Application.Abstractions.Services;
|
||||
using NexusReader.Infrastructure.Configuration;
|
||||
|
||||
namespace NexusReader.Infrastructure.Services;
|
||||
|
||||
@@ -10,12 +12,50 @@ public class HtmlSanitizerService : ISanitizerService
|
||||
{
|
||||
private readonly HtmlSanitizer _sanitizer;
|
||||
|
||||
public HtmlSanitizerService()
|
||||
public HtmlSanitizerService(IOptions<HtmlSanitizerSettings>? options = null)
|
||||
{
|
||||
_sanitizer = new HtmlSanitizer();
|
||||
|
||||
// Use default configuration which is extremely secure and strips
|
||||
// all JavaScript (script tags, onerror, onload, iframe, etc.)
|
||||
if (options?.Value != null)
|
||||
{
|
||||
var settings = options.Value;
|
||||
|
||||
if (settings.AllowedTags != null && settings.AllowedTags.Count > 0)
|
||||
{
|
||||
_sanitizer.AllowedTags.Clear();
|
||||
foreach (var tag in settings.AllowedTags)
|
||||
{
|
||||
_sanitizer.AllowedTags.Add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.AllowedAttributes != null && settings.AllowedAttributes.Count > 0)
|
||||
{
|
||||
_sanitizer.AllowedAttributes.Clear();
|
||||
foreach (var attr in settings.AllowedAttributes)
|
||||
{
|
||||
_sanitizer.AllowedAttributes.Add(attr);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.AllowedCssProperties != null && settings.AllowedCssProperties.Count > 0)
|
||||
{
|
||||
_sanitizer.AllowedCssProperties.Clear();
|
||||
foreach (var prop in settings.AllowedCssProperties)
|
||||
{
|
||||
_sanitizer.AllowedCssProperties.Add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.AllowedSchemes != null && settings.AllowedSchemes.Count > 0)
|
||||
{
|
||||
_sanitizer.AllowedSchemes.Clear();
|
||||
foreach (var scheme in settings.AllowedSchemes)
|
||||
{
|
||||
_sanitizer.AllowedSchemes.Add(scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Sanitize(string input)
|
||||
|
||||
@@ -25,18 +25,29 @@ public class LocalStorageService : IStorageService
|
||||
public async Task<string> UploadFileAsync(Stream fileStream, string fileName, string contentType)
|
||||
{
|
||||
var mediaFolder = Path.Combine(_environment.WebRootPath, "uploads", "media");
|
||||
var resolvedMediaFolder = Path.GetFullPath(mediaFolder);
|
||||
var folderWithSeparator = resolvedMediaFolder.EndsWith(Path.DirectorySeparatorChar)
|
||||
? resolvedMediaFolder
|
||||
: resolvedMediaFolder + Path.DirectorySeparatorChar;
|
||||
|
||||
if (!Directory.Exists(mediaFolder))
|
||||
if (!Directory.Exists(resolvedMediaFolder))
|
||||
{
|
||||
Directory.CreateDirectory(mediaFolder);
|
||||
Directory.CreateDirectory(resolvedMediaFolder);
|
||||
}
|
||||
|
||||
// Clean file name to prevent path traversal issues
|
||||
var safeFileName = Path.GetFileName(fileName);
|
||||
var uniqueFileName = $"{Guid.NewGuid()}_{safeFileName}";
|
||||
var filePath = Path.Combine(mediaFolder, uniqueFileName);
|
||||
var filePath = Path.Combine(resolvedMediaFolder, uniqueFileName);
|
||||
|
||||
using (var outputStream = new FileStream(filePath, FileMode.Create))
|
||||
// Guard against path traversal
|
||||
var fullPath = Path.GetFullPath(filePath);
|
||||
if (!fullPath.StartsWith(folderWithSeparator, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Path traversal detected.");
|
||||
}
|
||||
|
||||
using (var outputStream = new FileStream(fullPath, FileMode.Create))
|
||||
{
|
||||
await fileStream.CopyToAsync(outputStream);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
@code {
|
||||
private readonly string EditorId = $"milkdown-editor-{Guid.NewGuid():N}";
|
||||
private readonly CancellationTokenSource _cts = new();
|
||||
private IJSObjectReference? _module;
|
||||
private DotNetObjectReference<MarkdownEditor>? _dotNetHelper;
|
||||
|
||||
@@ -90,11 +91,11 @@
|
||||
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
|
||||
content.Add(fileContent, "file", filename);
|
||||
|
||||
var response = await Http.PostAsync("/api/media/upload", content);
|
||||
var response = await Http.PostAsync("/api/media/upload", content, _cts.Token);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var result = await response.Content.ReadFromJsonAsync<NexusReader.Application.DTOs.Media.UploadResultDto>(
|
||||
NexusReader.Application.Common.AppJsonContext.Default.UploadResultDto);
|
||||
NexusReader.Application.Common.AppJsonContext.Default.UploadResultDto, _cts.Token);
|
||||
return result?.Url ?? string.Empty;
|
||||
}
|
||||
else
|
||||
@@ -113,6 +114,16 @@
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cts.Cancel();
|
||||
_cts.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fail silently if cancellation token disposal fails
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_module is not null)
|
||||
|
||||
@@ -79,3 +79,8 @@
|
||||
filter: brightness(1.1);
|
||||
box-shadow: 0 4px 15px var(--nexus-primary-glow);
|
||||
}
|
||||
|
||||
.nexus-btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@page "/creator"
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@attribute [AllowAnonymous]
|
||||
@attribute [Authorize]
|
||||
|
||||
<PageTitle>Kreator Treści (Zen Mode)</PageTitle>
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
/* 3. Deep Cascading Overrides to target dynamic editor components */
|
||||
|
||||
::deep .markdown-editor-container {
|
||||
.creator-fullscreen-wrapper ::deep .markdown-editor-container {
|
||||
height: 100% !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
@@ -68,7 +68,7 @@
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
::deep .milkdown-editor-wrapper {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-editor-wrapper {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
flex-grow: 1 !important;
|
||||
@@ -76,8 +76,8 @@
|
||||
}
|
||||
|
||||
/* Force crepe and milkdown inner wrappers to stretch */
|
||||
::deep .crepe,
|
||||
::deep .milkdown {
|
||||
.creator-fullscreen-wrapper ::deep .crepe,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: flex !important;
|
||||
@@ -89,9 +89,9 @@
|
||||
}
|
||||
|
||||
/* Pin the toolbar at the top */
|
||||
::deep .crepe .toolbar,
|
||||
::deep .milkdown-menu,
|
||||
::deep .crepe-menu-wrapper {
|
||||
.creator-fullscreen-wrapper ::deep .crepe .toolbar,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-menu,
|
||||
.creator-fullscreen-wrapper ::deep .crepe-menu-wrapper {
|
||||
flex-shrink: 0 !important;
|
||||
background-color: var(--bg-base) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
@@ -100,20 +100,20 @@
|
||||
margin-bottom: 1rem !important;
|
||||
}
|
||||
|
||||
::deep .crepe .toolbar button:hover,
|
||||
::deep .milkdown-menu button:hover,
|
||||
::deep .crepe-menu-wrapper button:hover,
|
||||
::deep .crepe .toolbar .button:hover,
|
||||
::deep .milkdown-menu .button:hover {
|
||||
.creator-fullscreen-wrapper ::deep .crepe .toolbar button:hover,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-menu button:hover,
|
||||
.creator-fullscreen-wrapper ::deep .crepe-menu-wrapper button:hover,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .toolbar .button:hover,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-menu .button:hover {
|
||||
color: var(--accent) !important;
|
||||
background-color: rgba(16, 185, 129, 0.1) !important;
|
||||
border-radius: var(--radius-sm, 4px) !important;
|
||||
}
|
||||
|
||||
/* Relocate scrolling directly to ProseMirror editor layer and fix text clipping */
|
||||
::deep .ProseMirror,
|
||||
::deep .crepe .editor,
|
||||
::deep .milkdown .editor {
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .editor,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor {
|
||||
position: relative !important;
|
||||
top: 0 !important;
|
||||
transform: none !important;
|
||||
@@ -131,36 +131,36 @@
|
||||
}
|
||||
|
||||
/* Custom narrow scrollbar mapped to var(--border) */
|
||||
::deep .ProseMirror::-webkit-scrollbar,
|
||||
::deep .crepe .editor::-webkit-scrollbar,
|
||||
::deep .milkdown .editor::-webkit-scrollbar {
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror::-webkit-scrollbar,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .editor::-webkit-scrollbar,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::deep .ProseMirror::-webkit-scrollbar-track,
|
||||
::deep .crepe .editor::-webkit-scrollbar-track,
|
||||
::deep .milkdown .editor::-webkit-scrollbar-track {
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror::-webkit-scrollbar-track,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .editor::-webkit-scrollbar-track,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::deep .ProseMirror::-webkit-scrollbar-thumb,
|
||||
::deep .crepe .editor::-webkit-scrollbar-thumb,
|
||||
::deep .milkdown .editor::-webkit-scrollbar-thumb {
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror::-webkit-scrollbar-thumb,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .editor::-webkit-scrollbar-thumb,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::deep .ProseMirror::-webkit-scrollbar-thumb:hover,
|
||||
::deep .crepe .editor::-webkit-scrollbar-thumb:hover,
|
||||
::deep .milkdown .editor::-webkit-scrollbar-thumb:hover {
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror::-webkit-scrollbar-thumb:hover,
|
||||
.creator-fullscreen-wrapper ::deep .crepe .editor::-webkit-scrollbar-thumb:hover,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Editorial Typography */
|
||||
::deep .milkdown .editor h1,
|
||||
::deep .crepe h1,
|
||||
::deep .ProseMirror h1 {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor h1,
|
||||
.creator-fullscreen-wrapper ::deep .crepe h1,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror h1 {
|
||||
margin-top: 1.8rem !important;
|
||||
margin-bottom: 1rem !important;
|
||||
font-size: 2.25rem !important;
|
||||
@@ -169,9 +169,9 @@
|
||||
line-height: 1.25 !important;
|
||||
}
|
||||
|
||||
::deep .milkdown .editor h2,
|
||||
::deep .crepe h2,
|
||||
::deep .ProseMirror h2 {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor h2,
|
||||
.creator-fullscreen-wrapper ::deep .crepe h2,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror h2 {
|
||||
margin-top: 1.5rem !important;
|
||||
margin-bottom: 0.8rem !important;
|
||||
font-size: 1.6rem !important;
|
||||
@@ -180,9 +180,9 @@
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
::deep .milkdown .editor h3,
|
||||
::deep .crepe h3,
|
||||
::deep .ProseMirror h3 {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor h3,
|
||||
.creator-fullscreen-wrapper ::deep .crepe h3,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror h3 {
|
||||
margin-top: 1.3rem !important;
|
||||
margin-bottom: 0.7rem !important;
|
||||
font-size: 1.3rem !important;
|
||||
@@ -191,80 +191,80 @@
|
||||
line-height: 1.35 !important;
|
||||
}
|
||||
|
||||
::deep .milkdown .editor code,
|
||||
::deep .crepe code,
|
||||
::deep .ProseMirror code {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown .editor code,
|
||||
.creator-fullscreen-wrapper ::deep .crepe code,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror code {
|
||||
background-color: rgba(16, 185, 129, 0.1) !important;
|
||||
color: var(--accent) !important;
|
||||
padding: 0.2rem 0.4rem !important;
|
||||
border-radius: var(--radius-sm, 4px) !important;
|
||||
font-family: 'Azeret Mono', monospace !important;
|
||||
font-family: var(--nexus-font-mono) !important;
|
||||
font-size: 0.85em !important;
|
||||
}
|
||||
|
||||
/* Premium GFM Table Layouts */
|
||||
::deep .milkdown-premium-container table,
|
||||
::deep .crepe table,
|
||||
::deep .milkdown table,
|
||||
::deep .ProseMirror table {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container table,
|
||||
.creator-fullscreen-wrapper ::deep .crepe table,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown table,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror table {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
margin: 1.5rem 0 !important;
|
||||
}
|
||||
|
||||
::deep .milkdown-premium-container th,
|
||||
::deep .crepe th,
|
||||
::deep .milkdown th,
|
||||
::deep .ProseMirror th,
|
||||
::deep .milkdown-premium-container td,
|
||||
::deep .crepe td,
|
||||
::deep .milkdown td,
|
||||
::deep .ProseMirror td {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container th,
|
||||
.creator-fullscreen-wrapper ::deep .crepe th,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown th,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror th,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container td,
|
||||
.creator-fullscreen-wrapper ::deep .crepe td,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown td,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror td {
|
||||
padding: 14px 18px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
::deep .milkdown-premium-container th,
|
||||
::deep .crepe th,
|
||||
::deep .milkdown th,
|
||||
::deep .ProseMirror th {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container th,
|
||||
.creator-fullscreen-wrapper ::deep .crepe th,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown th,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror th {
|
||||
background-color: var(--bg-base) !important;
|
||||
color: var(--text-main) !important;
|
||||
font-weight: 700 !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
::deep .milkdown-premium-container td,
|
||||
::deep .crepe td,
|
||||
::deep .milkdown td,
|
||||
::deep .ProseMirror td {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container td,
|
||||
.creator-fullscreen-wrapper ::deep .crepe td,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown td,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror td {
|
||||
color: var(--text-main) !important;
|
||||
}
|
||||
|
||||
/* Zebra row background tints (Dark Mode default) */
|
||||
::deep .milkdown-premium-container tr:nth-child(even),
|
||||
::deep .crepe tr:nth-child(even),
|
||||
::deep .milkdown tr:nth-child(even),
|
||||
::deep .ProseMirror tr:nth-child(even) {
|
||||
.creator-fullscreen-wrapper ::deep .milkdown-premium-container tr:nth-child(even),
|
||||
.creator-fullscreen-wrapper ::deep .crepe tr:nth-child(even),
|
||||
.creator-fullscreen-wrapper ::deep .milkdown tr:nth-child(even),
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror tr:nth-child(even) {
|
||||
background-color: rgba(255, 255, 255, 0.01) !important;
|
||||
}
|
||||
|
||||
/* Zebra row background tints (Light Mode override) */
|
||||
.theme-light ::deep .milkdown-premium-container tr:nth-child(even),
|
||||
.theme-light ::deep .crepe tr:nth-child(even),
|
||||
.theme-light ::deep .milkdown tr:nth-child(even),
|
||||
.theme-light ::deep .ProseMirror tr:nth-child(even) {
|
||||
.theme-light .creator-fullscreen-wrapper ::deep .milkdown-premium-container tr:nth-child(even),
|
||||
.theme-light .creator-fullscreen-wrapper ::deep .crepe tr:nth-child(even),
|
||||
.theme-light .creator-fullscreen-wrapper ::deep .milkdown tr:nth-child(even),
|
||||
.theme-light .creator-fullscreen-wrapper ::deep .ProseMirror tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.015) !important;
|
||||
}
|
||||
|
||||
/* Lists and Task Lists */
|
||||
::deep .crepe ul,
|
||||
::deep .crepe ol,
|
||||
::deep .milkdown ul,
|
||||
::deep .milkdown ol,
|
||||
::deep .ProseMirror ul,
|
||||
::deep .ProseMirror ol {
|
||||
.creator-fullscreen-wrapper ::deep .crepe ul,
|
||||
.creator-fullscreen-wrapper ::deep .crepe ol,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown ul,
|
||||
.creator-fullscreen-wrapper ::deep .milkdown ol,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror ul,
|
||||
.creator-fullscreen-wrapper ::deep .ProseMirror ol {
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
--nexus-paper: #F9F9F9;
|
||||
--nexus-font-sans: 'Inter', sans-serif;
|
||||
--nexus-font-serif: 'Merriweather', serif;
|
||||
--nexus-font-mono: 'Azeret Mono', monospace;
|
||||
|
||||
/* Global Selection Style Override */
|
||||
--nexus-selection: rgba(0, 255, 153, 0.25);
|
||||
|
||||
Reference in New Issue
Block a user