Add draggable splitter for resizable editor/preview panes
Implemented a vertical splitter between the editor and preview panes, allowing users to resize them interactively. The split ratio is persisted in localStorage and clamped for usability. Added JavaScript interop for drag handling, updated styles for the splitter, and improved the preview toggle button with an eye icon. Layout remains responsive for mobile devices.
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
<div class="top-bar-left">
|
||||
<button class="btn-toggle @(showPreview ? "active" : "")"
|
||||
@onclick="TogglePreview">
|
||||
@(showPreview ? "Hide Preview" : "Show Preview")
|
||||
👁️ @(showPreview ? "Hide Preview" : "Show Preview")
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Editor Layout -->
|
||||
<div class="editor-layout @(showPreview ? "split-view" : "full-view")">
|
||||
<div class="editor-layout @(showPreview ? "split-view" : "full-view")" @ref="editorLayoutRef">
|
||||
<!-- Editor Pane -->
|
||||
<div class="editor-pane">
|
||||
<div class="editor-pane" style="@GetEditorPaneStyle()">
|
||||
<textarea class="markdown-editor"
|
||||
@bind="markdownContent"
|
||||
@oninput="OnContentChanged"
|
||||
@@ -76,10 +76,18 @@
|
||||
</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Simple Splitter -->
|
||||
@if (showPreview)
|
||||
{
|
||||
<div class="splitter" @onmousedown="OnSplitterMouseDown" @onmousedown:preventDefault="true">
|
||||
<div class="splitter-handle"></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Preview Pane -->
|
||||
@if (showPreview)
|
||||
{
|
||||
<div class="preview-pane">
|
||||
<div class="preview-pane" style="@GetPreviewPaneStyle()">
|
||||
<div class="preview-content">
|
||||
@((MarkupString)renderedHtml)
|
||||
</div>
|
||||
@@ -298,4 +306,75 @@
|
||||
{
|
||||
autoSaveTimer?.Dispose();
|
||||
}
|
||||
|
||||
// Simple splitter functionality
|
||||
private ElementReference editorLayoutRef;
|
||||
private double splitRatio = 0.5; // 50/50 by default
|
||||
private bool isDragging = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load saved split ratio from localStorage
|
||||
var savedRatio = await JSRuntime.InvokeAsync<string?>("localStorage.getItem", "notemark-split-ratio");
|
||||
if (!string.IsNullOrEmpty(savedRatio) && double.TryParse(savedRatio, out var ratio))
|
||||
{
|
||||
splitRatio = Math.Max(0.2, Math.Min(0.8, ratio));
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetEditorPaneStyle()
|
||||
{
|
||||
if (!showPreview) return "";
|
||||
return $"flex: 0 0 {splitRatio * 100:F1}%;";
|
||||
}
|
||||
|
||||
private string GetPreviewPaneStyle()
|
||||
{
|
||||
if (!showPreview) return "";
|
||||
return $"flex: 0 0 {(1 - splitRatio) * 100:F1}%;";
|
||||
}
|
||||
|
||||
private async Task OnSplitterMouseDown(MouseEventArgs e)
|
||||
{
|
||||
isDragging = true;
|
||||
await JSRuntime.InvokeVoidAsync("startSplitterDrag", DotNetObjectReference.Create(this));
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task UpdateSplitRatio(double clientX, double containerWidth)
|
||||
{
|
||||
if (!isDragging || containerWidth <= 0) return;
|
||||
|
||||
var newRatio = clientX / containerWidth;
|
||||
splitRatio = Math.Max(0.2, Math.Min(0.8, newRatio)); // Clamp between 20% and 80%
|
||||
|
||||
// Save to localStorage
|
||||
try
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "notemark-split-ratio", splitRatio.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void StopDragging()
|
||||
{
|
||||
isDragging = false;
|
||||
}
|
||||
}
|
||||
@@ -416,6 +416,35 @@
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Splitter Styles */
|
||||
.splitter {
|
||||
width: 8px;
|
||||
background-color: var(--border);
|
||||
cursor: col-resize;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.splitter:hover {
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.splitter-handle {
|
||||
width: 2px;
|
||||
height: 40px;
|
||||
background-color: var(--secondary-text);
|
||||
border-radius: 1px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.splitter:hover .splitter-handle {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.editor-layout.split-view {
|
||||
|
||||
@@ -32,3 +32,60 @@ window.notemarkTheme = {
|
||||
return localStorage.getItem('notemark-theme') || 'auto';
|
||||
}
|
||||
};
|
||||
|
||||
// Simple splitter drag functionality
|
||||
let splitterState = {
|
||||
isDragging: false,
|
||||
blazorInstance: null,
|
||||
container: null
|
||||
};
|
||||
|
||||
window.startSplitterDrag = function(blazorInstance) {
|
||||
splitterState.blazorInstance = blazorInstance;
|
||||
splitterState.isDragging = true;
|
||||
|
||||
// Find the editor layout container
|
||||
splitterState.container = document.querySelector('.editor-layout');
|
||||
|
||||
// Add global mouse events
|
||||
document.addEventListener('mousemove', handleSplitterMove);
|
||||
document.addEventListener('mouseup', handleSplitterUp);
|
||||
|
||||
// Prevent text selection
|
||||
document.body.style.userSelect = 'none';
|
||||
document.body.style.cursor = 'col-resize';
|
||||
};
|
||||
|
||||
function handleSplitterMove(e) {
|
||||
if (!splitterState.isDragging || !splitterState.container || !splitterState.blazorInstance) return;
|
||||
|
||||
const rect = splitterState.container.getBoundingClientRect();
|
||||
const clientX = e.clientX - rect.left;
|
||||
const containerWidth = rect.width;
|
||||
|
||||
// Call Blazor method to update split ratio
|
||||
splitterState.blazorInstance.invokeMethodAsync('UpdateSplitRatio', clientX, containerWidth);
|
||||
}
|
||||
|
||||
function handleSplitterUp(e) {
|
||||
if (!splitterState.isDragging) return;
|
||||
|
||||
splitterState.isDragging = false;
|
||||
|
||||
// Remove global mouse events
|
||||
document.removeEventListener('mousemove', handleSplitterMove);
|
||||
document.removeEventListener('mouseup', handleSplitterUp);
|
||||
|
||||
// Reset cursor and selection
|
||||
document.body.style.cursor = '';
|
||||
document.body.style.userSelect = '';
|
||||
|
||||
// Notify Blazor that dragging stopped
|
||||
if (splitterState.blazorInstance) {
|
||||
splitterState.blazorInstance.invokeMethodAsync('StopDragging');
|
||||
}
|
||||
|
||||
// Clean up
|
||||
splitterState.blazorInstance = null;
|
||||
splitterState.container = null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user