Skip to content

Commit

Permalink
Merge pull request #142 from gaelj/fix-diagrams-on-startup
Browse files Browse the repository at this point in the history
Fix diagrams on startup
  • Loading branch information
gaelj authored Feb 15, 2024
2 parents 94b0068 + d94d8dc commit d6a28b1
Show file tree
Hide file tree
Showing 17 changed files with 273 additions and 69 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.6.5 - 2024-02-15

### 🐛 Fix a bug

- Fix diagrams not always initially rendered

### 📝 Add or update documentation

- Add example: blazor server file upload with IBrowser file
- Cleanup example

## 0.6.4 - 2024-02-14

### ✨ Introduce new features
Expand Down
2 changes: 1 addition & 1 deletion CodeMirror6/CodeMirror6.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<AssemblyName>GaelJ.BlazorCodeMirror6</AssemblyName>
<IsPackable>true</IsPackable>
<PackageId>GaelJ.BlazorCodeMirror6</PackageId>
<Version>0.6.4</Version>
<Version>0.6.5</Version>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
Expand Down
32 changes: 16 additions & 16 deletions CodeMirror6/NodeLib/src/CmDiagrams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,9 @@ class DiagramWidget extends WidgetType {
function getLanguageAndCode(state: EditorState, node: SyntaxNodeRef) {
const { from, to } = node
const codeAndLanguage = state.doc.sliceString(from, to)
const language = detectDiagramLanguage(codeAndLanguage)
const diagramLanguage = detectDiagramLanguage(codeAndLanguage)
const code = codeAndLanguage.split('\n').slice(1, -1).join('\n')
return { language, code }
return { diagramLanguage, code }
}

const diagramPlugin = (krokiUrl: string) => ViewPlugin.fromClass(
Expand All @@ -227,19 +227,17 @@ const diagramPlugin = (krokiUrl: string) => ViewPlugin.fromClass(

update(update: ViewUpdate) {
update.transactions.forEach(tr => {
if (tr.docChanged) {
this.parseDocumentAndLoadDiagrams(this.view)
}
this.parseDocumentAndLoadDiagrams(this.view)
})
}

parseDocumentAndLoadDiagrams(view: EditorView) {
syntaxTree(view.state).iterate({
enter: (node) => {
if (node.type.name === 'FencedCode') {
const { language, code } = getLanguageAndCode(view.state, node)
if (language) {
fetchDiagramSvg(view, code, language, krokiUrl)
const { diagramLanguage, code } = getLanguageAndCode(view.state, node)
if (diagramLanguage) {
fetchDiagramSvg(view, code, diagramLanguage, krokiUrl)
}
}
},
Expand Down Expand Up @@ -269,21 +267,21 @@ export const dynamicDiagramsExtension = (enabled: boolean = true, krokiUrl: stri

function getDecorationsRange(state: EditorState, node: SyntaxNodeRef, from?: number, to?: number) {
if (node.type.name !== 'FencedCode') {
return []
return null
}
const { language, code } = getLanguageAndCode(state, node)
if (language === undefined) {
return []
const { diagramLanguage, code } = getLanguageAndCode(state, node)
if (diagramLanguage === undefined) {
return null
}
const cursorInRange = isCursorInRange(state, from, to)

let params: DiagramWidgetParams
params = { language, code, from: cursorInRange ? null : from, to: cursorInRange ? null : to, svgContent: null, height: null }
params = { language: diagramLanguage, code, from: cursorInRange ? null : from, to: cursorInRange ? null : to, svgContent: null, height: null }

if (cursorInRange)
return [diagramWidgetDecoration(params).range(state.doc.lineAt(from).from)]
return diagramWidgetDecoration(params).range(state.doc.lineAt(from).from)
else
return [diagramReplacementDecoration(params).range(from, to)]
return diagramReplacementDecoration(params).range(from, to)
}

const decorate = (state: EditorState) => {
Expand All @@ -292,7 +290,9 @@ export const dynamicDiagramsExtension = (enabled: boolean = true, krokiUrl: stri
syntaxTree(state).iterate({
enter: (node) => {
const { from, to } = node
decorationsRange.push(...getDecorationsRange(state, node, from, to))
const decorateRange = getDecorationsRange(state, node, from, to)
if (decorateRange)
decorationsRange.push(decorateRange)
},
})
}
Expand Down
45 changes: 45 additions & 0 deletions Examples.BlazorServer/BrowserFileStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Microsoft.AspNetCore.Components.Forms;
using System.IO;

namespace Examples.BlazorServer;

public class BrowserFileStream(IBrowserFile browserFile) : Stream
{
private readonly Stream _baseStream = browserFile.OpenReadStream();

public override bool CanRead => _baseStream.CanRead;
public override bool CanSeek => _baseStream.CanSeek;
public override bool CanWrite => _baseStream.CanWrite;
public override long Length => _baseStream.Length;

public override long Position
{
get => _baseStream.Position;
set => _baseStream.Position = value;
}

public override void Flush() => _baseStream.Flush();

public override int Read(byte[] buffer, int offset, int count) =>
_baseStream.Read(buffer, offset, count);

public override long Seek(long offset, SeekOrigin origin) =>
_baseStream.Seek(offset, origin);

public override void SetLength(long value) =>
_baseStream.SetLength(value);

public override void Write(byte[] buffer, int offset, int count) =>
_baseStream.Write(buffer, offset, count);

protected override void Dispose(bool disposing)
{
if (disposing)
{
_baseStream.Dispose();
}

base.Dispose(disposing);
}
}

24 changes: 24 additions & 0 deletions Examples.BlazorServer/Controllers/FileUploadController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace Examples.BlazorServer.Controllers;

[ApiController]
[Route("[controller]")]
public class FileUploadController : ControllerBase
{
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
if (file == null || file.Length == 0)
{
return BadRequest("Upload a file.");
}

// Process the file here
// Example: Save the file to the server, database, etc.

return Ok(new { file.FileName, file.Length });
}
}
12 changes: 0 additions & 12 deletions Examples.BlazorServer/Data/WeatherForecast.cs

This file was deleted.

19 changes: 0 additions & 19 deletions Examples.BlazorServer/Data/WeatherForecastService.cs

This file was deleted.

2 changes: 1 addition & 1 deletion Examples.BlazorServer/Examples.BlazorServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.6.4</Version>
<Version>0.6.5</Version>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
Expand Down
23 changes: 23 additions & 0 deletions Examples.BlazorServer/FileConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Http;
using System.IO;

namespace Examples.BlazorServer;

public static class WebFileConverter
{
public static IFormFile ConvertToFormFile(this IBrowserFile browserFile)
{
ArgumentNullException.ThrowIfNull(browserFile);

var fileStream = new BrowserFileStream(browserFile);

var formFile = new FormFile(fileStream, 0, browserFile.Size, browserFile.Name, browserFile.Name)
{
Headers = new HeaderDictionary(),
ContentType = browserFile.ContentType
};

return formFile;
}
}
134 changes: 131 additions & 3 deletions Examples.BlazorServer/Pages/Example2.razor
Original file line number Diff line number Diff line change
@@ -1,3 +1,131 @@
@page "/example2"

<Examples.Common.Example />
@page "/example2"

<h1>Code Mirror 6 Wrapper Demo</h1>

<h4>File upload with IFormFile</h4>

<CodeMirror6Wrapper
@bind-Doc=@Text
Placeholder="Upload a file by dropping it or pasting it here"
TabSize=@TabSize
IndentationUnit=@TabSize
Theme=@Theme
Language=@Language
AutoFormatMarkdown=@AutoFormatMarkdown
Setup=@Setup
UploadBrowserFile=@UploadBrowserFile
PreviewImages=@true
FullScreen=@FullScreen
LocalStorageKey=@LocalStorageKey
MaxHeight="60em"
>
<ContentBefore Context="c">
<div class="toolbar sticky-top alert alert-info my-3">
<button class=@ButtonClass(c.State, "StrongEmphasis") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownBold)) title="Toggle bold text (Ctrl-B)">
<i class="fa fa-bold"></i>
</button>
<button class=@ButtonClass(c.State, "Emphasis") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownItalic)) title="Toggle italic text (Ctrl-I)">
<i class="fa fa-italic"></i>
</button>
<button class=@ButtonClass(c.State, "Strikethrough") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownStrikethrough)) title="Toggle strike-through text">
<i class="fa fa-strikethrough"></i>
</button>
<button class=@ButtonClass(c.State, "InlineCode") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownCode)) title="Toggle inline code text">
<i class="fa fa-code"></i>
</button>
<button class=@ButtonClass(c.State, "FencedCode") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownCodeBlock)) title="Toggle code block text">
<i class="fab fa-codepen"></i>
</button>
<button class=@ButtonClass(c.State, "Blockquote") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownQuote)) title="Toggle quoted text">
<i class="fa fa-quote-right"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.IncreaseMarkdownHeadingLevel)) title="Increase Markdown heading level">
<i class="fa fa-heading"></i> +
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.DecreaseMarkdownHeadingLevel)) title="Decrease Markdown heading level">
<i class="fa fa-heading"></i> -
</button>
@for (var i = 1; i <= 6; i++) {
var headingLevel = i; // Capture the current value of i in a local variable
<button class=@ButtonClass(c.State, $"ATXHeading{headingLevel}") @onclick=@(() => c.Commands.Dispatch(CodeMirrorCommandOneParameter.ToggleMarkdownHeading, headingLevel)) title=@($"Toggle heading text {i}")>
<i class="fa fa-heading"></i> @($"{headingLevel}")
</button>
}
<button class=@ButtonClass(c.State, "BulletList") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownUnorderedList)) title="Toggle unordered list">
<i class="fa fa-list-ul"></i>
</button>
<button class=@ButtonClass(c.State, "OrderedList") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownOrderedList)) title="Toggle ordered list">
<i class="fa fa-list-ol"></i>
</button>
<button class=@ButtonClass(c.State, "Task") @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ToggleMarkdownTaskList)) title="Toggle task list">
<i class="fa fa-tasks"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorCommandOneParameter.InsertOrReplaceText, "test")) title="Insert or replace text">
Insert or replace text
</button>
<button class=@ButtonClass(c.State, "HorizontalRule") @onclick=@(() => c.Commands.Dispatch(CodeMirrorCommandOneParameter.InsertTextAbove, "\n---\n")) title="Insert separator above">
<i class="fas fa-minus"></i>
</button>
<button class=@ButtonClass(LineWrapping) @onclick=@(() => LineWrapping = !LineWrapping) title="Toggle long line wrapping">
<i class="fas fa-paragraph"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.Undo)) title="Undo (Ctrl-Z)">
<i class="fas fa-undo"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.Redo)) title="Redo (Ctrl-Y)">
<i class="fas fa-redo"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.Cut)) title="Cut (Ctrl-X)">
<i class="fas fa-scissors"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.Copy)) title="Copy (Ctrl-C)">
<i class="fas fa-copy"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.Paste)) title="Paste (Ctrl-V)">
<i class="fas fa-paste"></i>
</button>
<button class=@ButtonClass(c.State) @onclick=@(() => c.Commands.Dispatch(CodeMirrorSimpleCommand.ClearLocalStorage)) title="Clear local storage">
<i class="fas fa-trash"></i>
</button>
<button class=@ButtonClass(FullScreen) @onclick=@(() => FullScreen = !FullScreen) title="Toggle full-screen mode">
<i class=@(FullScreen ? "fas fa-window-restore" : "fas fa-window-maximize")></i>
</button>
</div>
</ContentBefore>
</CodeMirror6Wrapper>


@code
{
private string? Text = string.Empty;
private int TabSize = 4;
private ThemeMirrorTheme Theme = ThemeMirrorTheme.OneDark;
private CodeMirrorLanguage Language = CodeMirrorLanguage.Markdown;
private bool AutoFormatMarkdown = true;
private bool LineWrapping = true;
private bool FullScreen = false;
private string LocalStorageKey = "CodeMirror6WrapperDemo_FileUpload";
private readonly CodeMirrorSetup Setup = new() {
HighlightSelectionMatches = true,
ScrollToEnd = true,
BindMode = DocumentBindMode.OnDelayedInput,
};
private string ButtonClass(CodeMirrorState state, string docStyleTag = "") => ButtonClass(state.MarkdownStylesAtSelections?.Contains(docStyleTag) == true);
private string ButtonClass(bool enabled) => enabled
? "btn btn-sm btn-primary"
: "btn btn-sm btn-outline-secondary";

private async Task<string> UploadBrowserFile(IBrowserFile file)
{
var formFile = file.ConvertToFormFile();

var content = new MultipartFormDataContent();
var fileContent = new StreamContent(formFile.OpenReadStream());
content.Add(fileContent, "\"file\"", file.Name);

using var httpClient = new HttpClient();
var response = await httpClient.PostAsync("http://localhost:5188/FileUpload/upload", content); // Adjust the URL as needed
return response.Content.ReadAsStringAsync().Result;
}
}
8 changes: 8 additions & 0 deletions Examples.BlazorServer/Pages/Example2.razor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.sticky-top {
padding-top: 10px;
}

.cm-full-screen .sticky-top {
margin: 0px !important;
padding: 0px !important;
}
Loading

0 comments on commit d6a28b1

Please sign in to comment.