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