diff --git a/CHANGELOG.md b/CHANGELOG.md index ea02cc9c..a8745e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 0.2.1 - 2024-01-31 + +### ✨ Introduce new features + +- Add DisplayName() extension to CodeMirrorLanguage enum +- Allow scrolling past the end of the document +- Implement white space & trailing white space highlighting + +### 🐛 Fix a bug + +- Fix dragging text was highjacked by the file drag overlay +- Fix empty language definitions in example + +### ✏️ Fix typos + +- Display Plain Text language name with space + ## 0.2.0 - 2024-01-31 ### ✨ Introduce new features diff --git a/CodeMirror6/CodeMirror6.csproj b/CodeMirror6/CodeMirror6.csproj index f74d5dae..fdd85938 100644 --- a/CodeMirror6/CodeMirror6.csproj +++ b/CodeMirror6/CodeMirror6.csproj @@ -9,7 +9,7 @@ GaelJ.BlazorCodeMirror6 true GaelJ.BlazorCodeMirror6 - 0.2.0 + 0.2.1 true snupkg true @@ -63,4 +63,4 @@ - + \ No newline at end of file diff --git a/CodeMirror6/CodeMirror6Wrapper.razor b/CodeMirror6/CodeMirror6Wrapper.razor index f413fb8f..3b658636 100644 --- a/CodeMirror6/CodeMirror6Wrapper.razor +++ b/CodeMirror6/CodeMirror6Wrapper.razor @@ -32,6 +32,8 @@ UploadFile="@UploadFile" MergeViewConfiguration="@MergeViewConfiguration" FileNameOrExtension="@FileNameOrExtension" + HighlightWhitespace="@HighlightWhitespace" + HighlightTrailingWhitespace="@HighlightTrailingWhitespace" /> diff --git a/CodeMirror6/CodeMirror6Wrapper.razor.cs b/CodeMirror6/CodeMirror6Wrapper.razor.cs index 2ecdd4b0..094e190f 100644 --- a/CodeMirror6/CodeMirror6Wrapper.razor.cs +++ b/CodeMirror6/CodeMirror6Wrapper.razor.cs @@ -155,6 +155,16 @@ public partial class CodeMirror6Wrapper : ComponentBase /// [Parameter] public UnifiedMergeConfig? MergeViewConfiguration { get; set; } /// + /// Whether to allow horizontal resizing similar to a textarea + /// + /// + [Parameter] public bool HighlightTrailingWhitespace { get; set; } + /// + /// Whether to allow horizontal resizing similar to a textarea + /// + /// + [Parameter] public bool HighlightWhitespace { get; set; } + /// /// Additional attributes to be applied to the container element /// /// diff --git a/CodeMirror6/CodeMirror6WrapperInternal.razor.cs b/CodeMirror6/CodeMirror6WrapperInternal.razor.cs index 382eaa6c..05987d45 100644 --- a/CodeMirror6/CodeMirror6WrapperInternal.razor.cs +++ b/CodeMirror6/CodeMirror6WrapperInternal.razor.cs @@ -156,6 +156,16 @@ public partial class CodeMirror6WrapperInternal : ComponentBase, IAsyncDisposabl /// [Parameter] public UnifiedMergeConfig? MergeViewConfiguration { get; set; } /// + /// Whether to allow horizontal resizing similar to a textarea + /// + /// + [Parameter] public bool HighlightTrailingWhitespace { get; set; } + /// + /// Whether to allow horizontal resizing similar to a textarea + /// + /// + [Parameter] public bool HighlightWhitespace { get; set; } + /// /// Additional attributes to be applied to the container element /// /// @@ -201,7 +211,9 @@ protected override async Task OnInitializedAsync() LineWrapping, LintDocument is not null, MergeViewConfiguration, - FileNameOrExtension + FileNameOrExtension, + HighlightTrailingWhitespace, + HighlightWhitespace ); try { if (IsWASM) @@ -302,6 +314,14 @@ protected override async Task OnParametersSetAsync() Config.MergeViewConfiguration = MergeViewConfiguration; await CmJsInterop.PropertySetters.SetUnifiedMergeView(); } + if (Config.HighlightTrailingWhitespace != HighlightTrailingWhitespace) { + Config.HighlightTrailingWhitespace = HighlightTrailingWhitespace; + await CmJsInterop.PropertySetters.SetHighlightTrailingWhitespace(); + } + if (Config.HighlightWhitespace != HighlightWhitespace) { + Config.HighlightWhitespace = HighlightWhitespace; + await CmJsInterop.PropertySetters.SetHighlightWhitespace(); + } shouldRender = true; } diff --git a/CodeMirror6/Commands/CMConfigurationSetters.cs b/CodeMirror6/Commands/CMConfigurationSetters.cs index 657ec50e..505dbd3d 100644 --- a/CodeMirror6/Commands/CMConfigurationSetters.cs +++ b/CodeMirror6/Commands/CMConfigurationSetters.cs @@ -134,6 +134,16 @@ internal Task SetUnifiedMergeView() => cmJsInterop.ModuleInvokeVoidAsync( config.MergeViewConfiguration ); + internal Task SetHighlightTrailingWhitespace() => cmJsInterop.ModuleInvokeVoidAsync( + "setHighlightTrailingWhitespace", + config.HighlightTrailingWhitespace + ); + + internal Task SetHighlightWhitespace() => cmJsInterop.ModuleInvokeVoidAsync( + "setHighlightWhitespace", + config.HighlightWhitespace + ); + internal Task?> GetAllSupportedLanguageNames() => cmJsInterop.ModuleInvokeAsync>( "getAllSupportedLanguageNames" ); diff --git a/CodeMirror6/Converters/JsonStringValueAttribute.cs b/CodeMirror6/Converters/JsonStringValueAttribute.cs index f387387d..750a5e2f 100644 --- a/CodeMirror6/Converters/JsonStringValueAttribute.cs +++ b/CodeMirror6/Converters/JsonStringValueAttribute.cs @@ -1,12 +1,15 @@ namespace GaelJ.BlazorCodeMirror6.Converters; +/// +/// Specifies the string value of a field when serialized to JSON. +/// +/// [AttributeUsage(AttributeTargets.Field)] -public class JsonStringValueAttribute : Attribute +public class JsonStringValueAttribute(string value) : Attribute { - public string Value { get; } - - public JsonStringValueAttribute(string value) - { - Value = value; - } + /// + /// The string value of the field when serialized to JSON. + /// + /// + public string Value { get; } = value; } diff --git a/CodeMirror6/Models/CodeMirrorConfiguration.cs b/CodeMirror6/Models/CodeMirrorConfiguration.cs index 2c65ee58..5af51a8d 100644 --- a/CodeMirror6/Models/CodeMirrorConfiguration.cs +++ b/CodeMirror6/Models/CodeMirrorConfiguration.cs @@ -21,6 +21,8 @@ namespace GaelJ.BlazorCodeMirror6.Models; /// /// /// +/// +/// public class CodeMirrorConfiguration( string? doc, string? placeholder, @@ -36,7 +38,9 @@ public class CodeMirrorConfiguration( bool lineWrapping, bool lintingEnabled, UnifiedMergeConfig? mergeViewConfiguration, - string? fileNameOrExtension) + string? fileNameOrExtension, + bool highlightTrailingWhitespace, + bool highlightWhitespace) { /// @@ -114,4 +118,14 @@ public class CodeMirrorConfiguration( /// Unified merged view configuration /// [JsonPropertyName("mergeViewConfiguration")] internal UnifiedMergeConfig? MergeViewConfiguration { get; set; } = mergeViewConfiguration; + + /// + /// Whether to highlight trailing whitespace + /// + [JsonPropertyName("highlightTrailingWhitespace")] public bool HighlightTrailingWhitespace { get; internal set; } = highlightTrailingWhitespace; + + /// + /// Whether to highlight whitespace + /// + [JsonPropertyName("highlightWhitespace")] public bool HighlightWhitespace { get; internal set; } = highlightWhitespace; } diff --git a/CodeMirror6/Models/CodeMirrorLanguage.cs b/CodeMirror6/Models/CodeMirrorLanguage.cs index c1b09eb8..8aed7e1b 100644 --- a/CodeMirror6/Models/CodeMirrorLanguage.cs +++ b/CodeMirror6/Models/CodeMirrorLanguage.cs @@ -1,3 +1,4 @@ +using System.Reflection; using System.Text.Json.Serialization; using GaelJ.BlazorCodeMirror6.Converters; @@ -12,7 +13,7 @@ public enum CodeMirrorLanguage /// /// Plain text /// - [JsonStringValue("PlainText")] PlainText, + [JsonStringValue("Plain Text")] PlainText, /// /// APL @@ -607,7 +608,7 @@ public enum CodeMirrorLanguage /// /// Lezer /// - Lezer, + [JsonStringValue("Lezer")] Lezer, /// /// Markdown @@ -617,7 +618,7 @@ public enum CodeMirrorLanguage /// /// Mermaid /// - Mermaid, + [JsonStringValue("Mermaid")] Mermaid, /// /// Python @@ -734,3 +735,17 @@ public enum CodeMirrorLanguage /// [JsonStringValue("YAML")] Yaml, } + +/// +/// Extension methods for the enum +/// +public static class CodeMirrorLanguageExtensions +{ + /// + /// Returns the display name of the language + /// + /// + /// + public static string DisplayName(this CodeMirrorLanguage language) => + language.GetType().GetField(language.ToString())?.GetCustomAttribute()?.Value ?? language.ToString(); +} diff --git a/CodeMirror6/Models/CodeMirrorSetup.cs b/CodeMirror6/Models/CodeMirrorSetup.cs index aa84bc3c..94a8fadd 100644 --- a/CodeMirror6/Models/CodeMirrorSetup.cs +++ b/CodeMirror6/Models/CodeMirrorSetup.cs @@ -129,4 +129,9 @@ public CodeMirrorSetup() /// Bind value mode of the text area /// [JsonPropertyName("bindValueMode")] public DocumentBindMode BindMode { get; init; } = DocumentBindMode.OnLostFocus; + + /// + /// Can the user scroll past the end of the document + /// + [JsonPropertyName("scrollPastEnd")] public bool ScrollPastEnd { get; init; } = false; } diff --git a/CodeMirror6/NodeLib/src/CmConfiguration.ts b/CodeMirror6/NodeLib/src/CmConfiguration.ts index de0c4149..1e0360c7 100644 --- a/CodeMirror6/NodeLib/src/CmConfiguration.ts +++ b/CodeMirror6/NodeLib/src/CmConfiguration.ts @@ -19,6 +19,8 @@ export class CmConfiguration { public lineWrapping: boolean public lintingEnabled: boolean public mergeViewConfiguration: UnifiedMergeConfig | null + public highlightTrailingWhitespace: boolean + public highlightWhitespace: boolean } export interface UnifiedMergeConfig { diff --git a/CodeMirror6/NodeLib/src/CmFileUpload.ts b/CodeMirror6/NodeLib/src/CmFileUpload.ts index 7e4fcdcf..92ab5b07 100644 --- a/CodeMirror6/NodeLib/src/CmFileUpload.ts +++ b/CodeMirror6/NodeLib/src/CmFileUpload.ts @@ -37,11 +37,13 @@ export function getFileUploadExtensions(id: string, setup: CmSetup) const dragAndDropHandler = EditorView.domEventHandlers({ dragenter(event, view) { + if (!event.dataTransfer?.files.length) return event.preventDefault() overlay.style.display = 'flex' depth++ }, dragleave(event, view) { + if (!event.dataTransfer?.files.length) return event.preventDefault(); depth-- if (depth === 0) { @@ -49,10 +51,12 @@ export function getFileUploadExtensions(id: string, setup: CmSetup) } }, dragover(event, view) { + if (!event.dataTransfer?.files.length) return event.preventDefault() overlay.style.display = 'flex' }, drop(event, view) { + if (!event.dataTransfer?.files.length) return const transfer = event.dataTransfer if (transfer?.files) { overlay.style.display = 'none' diff --git a/CodeMirror6/NodeLib/src/CmInstance.ts b/CodeMirror6/NodeLib/src/CmInstance.ts index 2ed05e21..3d8036fb 100644 --- a/CodeMirror6/NodeLib/src/CmInstance.ts +++ b/CodeMirror6/NodeLib/src/CmInstance.ts @@ -24,6 +24,8 @@ export class CmInstance public emojiReplacerCompartment: Compartment = new Compartment public lineWrappingCompartment: Compartment = new Compartment public unifiedMergeViewCompartment: Compartment = new Compartment + public highlightTrailingWhitespaceCompartment: Compartment = new Compartment + public highlightWhitespaceCompartment: Compartment = new Compartment } export const CMInstances: { [id: string]: CmInstance} = {} diff --git a/CodeMirror6/NodeLib/src/CmLanguage.ts b/CodeMirror6/NodeLib/src/CmLanguage.ts index 27e0575d..300f3841 100644 --- a/CodeMirror6/NodeLib/src/CmLanguage.ts +++ b/CodeMirror6/NodeLib/src/CmLanguage.ts @@ -29,7 +29,7 @@ export async function getLanguage(languageName: string, fileNameOrExtension: str } console.log("getLanguage: " + languageName) switch (languageName) { - case "PlainText": + case "Plain Text": return null case "Lezer": return lezer() diff --git a/CodeMirror6/NodeLib/src/CmSetup.ts b/CodeMirror6/NodeLib/src/CmSetup.ts index 18b2f389..2ecf869c 100644 --- a/CodeMirror6/NodeLib/src/CmSetup.ts +++ b/CodeMirror6/NodeLib/src/CmSetup.ts @@ -27,4 +27,5 @@ export class CmSetup public fileIcon: string public bindValueMode: string public krokiUrl: string + public scrollPastEnd: boolean } diff --git a/CodeMirror6/NodeLib/src/index.ts b/CodeMirror6/NodeLib/src/index.ts index a8535347..e1c2d728 100644 --- a/CodeMirror6/NodeLib/src/index.ts +++ b/CodeMirror6/NodeLib/src/index.ts @@ -1,7 +1,7 @@ import { EditorView, keymap, highlightSpecialChars, drawSelection, highlightActiveLine, dropCursor, - rectangularSelection, crosshairCursor, ViewUpdate, - lineNumbers, highlightActiveLineGutter, placeholder + rectangularSelection, crosshairCursor, ViewUpdate, lineNumbers, highlightActiveLineGutter, + placeholder, scrollPastEnd, highlightTrailingWhitespace, highlightWhitespace } from "@codemirror/view" import { EditorState, SelectionRange, Text } from "@codemirror/state" import { @@ -16,10 +16,11 @@ import { indentUnit, defaultHighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching, foldGutter, foldKeymap, } from "@codemirror/language" +import { languages } from "@codemirror/language-data" import { unifiedMergeView } from "@codemirror/merge" import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap, Completion } from "@codemirror/autocomplete" import { searchKeymap, highlightSelectionMatches } from "@codemirror/search" -import { linter, lintKeymap } from "@codemirror/lint" +import { linter, lintGutter, lintKeymap } from "@codemirror/lint" import { CmInstance, CMInstances } from "./CmInstance" import { CmConfiguration, UnifiedMergeConfig } from "./CmConfiguration" import { getDynamicHeaderStyling } from "./CmDynamicMarkdownHeaderStyling" @@ -55,7 +56,6 @@ import { DotNet } from "@microsoft/dotnet-js-interop" import { markdownTableExtension } from "./CmMarkdownTable" import { dynamicDiagramsExtension } from "./CmDiagrams" import { hideMarksExtension } from "./CmHideMarkdownMarks" -import { languages } from "@codemirror/language-data" /** * Initialize a new CodeMirror instance @@ -91,6 +91,8 @@ export async function initCodeMirror( indentationMarkers(), CMInstances[id].lineWrappingCompartment.of(initialConfig.lineWrapping ? EditorView.lineWrapping : []), CMInstances[id].unifiedMergeViewCompartment.of(initialConfig.mergeViewConfiguration ? unifiedMergeView(initialConfig.mergeViewConfiguration) : []), + CMInstances[id].highlightTrailingWhitespaceCompartment.of(initialConfig.highlightTrailingWhitespace ? highlightTrailingWhitespace() : []), + CMInstances[id].highlightWhitespaceCompartment.of(initialConfig.highlightWhitespace ? highlightWhitespace() : []), EditorView.updateListener.of(async (update) => { await updateListenerExtension(id, update) }), keymap.of([ @@ -145,15 +147,18 @@ export async function initCodeMirror( if (setup.syntaxHighlighting === true) extensions.push(syntaxHighlighting(defaultHighlightStyle, { fallback: true })) if (setup.bracketMatching === true) extensions.push(bracketMatching()) if (setup.closeBrackets === true) extensions.push(closeBrackets()) - if (setup.autocompletion === true) extensions.push(autocompletion({})) + if (setup.autocompletion === true) extensions.push(autocompletion()) if (setup.rectangularSelection === true) extensions.push(rectangularSelection()) if (setup.crossHairSelection === true) extensions.push(crosshairCursor()) if (setup.highlightActiveLine === true) extensions.push(highlightActiveLine()) if (setup.highlightSelectionMatches === true) extensions.push(highlightSelectionMatches()) + if (setup.scrollPastEnd === true) extensions.push(scrollPastEnd()) + if (setup.allowMultipleSelections === true) extensions.push(EditorState.allowMultipleSelections.of(true)) if (initialConfig.lintingEnabled === true || setup.bindValueMode == "OnDelayedInput") extensions.push(linter(async view => await externalLintSource(view, dotnetHelper), getExternalLinterConfig())) - if (setup.allowMultipleSelections === true) extensions.push(EditorState.allowMultipleSelections.of(true)) + if (initialConfig.lintingEnabled === true) + extensions.push(lintGutter()) extensions.push(...getFileUploadExtensions(id, setup)) @@ -299,6 +304,18 @@ export function setMentionCompletions(id: string, mentionCompletions: Completion forceRedraw(id) } +export function setHighlightTrailingWhitespace(id: string, value: boolean) { + CMInstances[id].view.dispatch({ + effects: CMInstances[id].highlightTrailingWhitespaceCompartment.reconfigure(value ? highlightTrailingWhitespace() : []) + }) +} + +export function setHighlightWhitespace(id: string, value: boolean) { + CMInstances[id].view.dispatch({ + effects: CMInstances[id].highlightWhitespaceCompartment.reconfigure(value ? highlightWhitespace() : []) + }) +} + export function forceRedraw(id: string) { const view = CMInstances[id].view if (!view) return diff --git a/Examples.BlazorServer/Examples.BlazorServer.csproj b/Examples.BlazorServer/Examples.BlazorServer.csproj index 09202c29..1f3d7c73 100644 --- a/Examples.BlazorServer/Examples.BlazorServer.csproj +++ b/Examples.BlazorServer/Examples.BlazorServer.csproj @@ -4,7 +4,7 @@ enable false enable - 0.2.0 + 0.2.1 @@ -16,4 +16,4 @@ - + \ No newline at end of file diff --git a/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj b/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj index 858f6981..4f133da7 100644 --- a/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj +++ b/Examples.BlazorServerInteractive/Examples.BlazorServerInteractive.csproj @@ -4,7 +4,7 @@ enable enable false - 0.2.0 + 0.2.1 @@ -13,4 +13,4 @@ - + \ No newline at end of file diff --git a/Examples.BlazorWasm/Examples.BlazorWasm.csproj b/Examples.BlazorWasm/Examples.BlazorWasm.csproj index 0faf5454..1f2ee711 100644 --- a/Examples.BlazorWasm/Examples.BlazorWasm.csproj +++ b/Examples.BlazorWasm/Examples.BlazorWasm.csproj @@ -4,7 +4,7 @@ enable enable false - 0.2.0 + 0.2.1 @@ -17,4 +17,4 @@ - + \ No newline at end of file diff --git a/Examples.Common/Example.razor b/Examples.Common/Example.razor index 17ac159d..7b1184fe 100644 --- a/Examples.Common/Example.razor +++ b/Examples.Common/Example.razor @@ -10,11 +10,8 @@ @@ -75,6 +72,20 @@ } +
+ + { + HighlightTrailingWhitespace = (v.Value as bool?) == true; + await InvokeAsync(StateHasChanged); + }) /> + + + { + HighlightWhitespace = (v.Value as bool?) == true; + await InvokeAsync(StateHasChanged); + }) /> +
+ @@ -260,6 +273,8 @@ private bool ReplaceEmojiCodes = false; private bool LineWrapping = true; private bool MergeViewEnabled = false; + private bool HighlightTrailingWhitespace = true; + private bool HighlightWhitespace = false; private List Languages => Enum.GetValues() .OrderBy(l => l == CodeMirrorLanguage.PlainText ? 0 : 1) @@ -271,6 +286,7 @@ HighlightActiveLineGutter = false, HighlightSelectionMatches = false, ScrollToEnd = true, + ScrollPastEnd = true, BindMode = DocumentBindMode.OnDelayedInput, }; private UnifiedMergeConfig? PreviousMergeViewConfiguration; diff --git a/Examples.Common/Examples.Common.csproj b/Examples.Common/Examples.Common.csproj index 6f20717a..48e1ff97 100644 --- a/Examples.Common/Examples.Common.csproj +++ b/Examples.Common/Examples.Common.csproj @@ -5,7 +5,7 @@ enable enable false - 0.2.0 + 0.2.1 @@ -18,4 +18,4 @@ - + \ No newline at end of file diff --git a/Examples.Common/_Imports.razor b/Examples.Common/_Imports.razor index 74af7536..e1bc1b8a 100644 --- a/Examples.Common/_Imports.razor +++ b/Examples.Common/_Imports.razor @@ -9,4 +9,3 @@ @using GaelJ.BlazorCodeMirror6.Commands @using GaelJ.BlazorCodeMirror6.Models @using System.Linq -@using System.Reflection diff --git a/NEW_CHANGELOG.md b/NEW_CHANGELOG.md index 6023aa62..2a16d593 100644 --- a/NEW_CHANGELOG.md +++ b/NEW_CHANGELOG.md @@ -1,21 +1,14 @@ ### ✨ Introduce new features -- Implement configurable Unified Merge View -- Support and dynamically load 145 languages -- Allow specifying FileNameOrExtension to detect language automatically (instead of specifying Language parameter) +- Add DisplayName() extension to CodeMirrorLanguage enum +- Allow scrolling past the end of the document +- Implement white space & trailing white space highlighting -### ⚡️ Improve performance +### 🐛 Fix a bug -- Use record instead of class for interop DTOs: CodeMirrorCompletion/Section, CodeMirrorDiagnostic +- Fix dragging text was highjacked by the file drag overlay +- Fix empty language definitions in example -### ⬆️ Upgrade dependencies +### ✏️ Fix typos -- Update dependencies: Sentry in examples projects ; languages, merge, babel in js - -### 📝 Add or update documentation - -- Add csv mode in README.md todo - -### 🚚 Move or rename resources (e.g., files, paths) - -- Move GetMentionCompletions in example, to dedicated file +- Display Plain Text language name with space