whistyun / MdXaml

Markdown for WPF - alternate version of Markdown.Xaml
MIT License
245 stars 37 forks source link

New Plugin: CheckBox #50

Open Khaos66 opened 1 year ago

Khaos66 commented 1 year ago

I created this new plugin to display checkboxes defined by [ ] and [x] in markdown

You may include this in the repo and release a nuget for it ;)

You may also update it to reflect your code style or add new features.

Sadly the plugin api doesn't provide the original index of the text span a parser is parsing. So I had to add a wild hack to be able to ensure the checkbox can update the markdown at the correct place

public class CheckboxPluginSetup : IPluginSetup
{
    public void Setup(MdXamlPlugins plugins) => plugins.Inline.Add(new CheckboxInlineParser());
}
public class CheckboxInlineParser : IInlineParser
{
    public CheckboxInlineParser()
    {
        this.FirstMatchPattern = new Regex(@"\[(?<value>[ |x])\]\s*(?<caption>[^\n|[]*)");
    }

    public Regex FirstMatchPattern { get; }

    public IEnumerable<Inline> Parse(string text, Match firstMatch, IMarkdown engine, out int parseTextBegin, out int parseTextEnd)
    {
        parseTextBegin = firstMatch.Index;
        parseTextEnd = firstMatch.Index + firstMatch.Length;

        CheckBox chk = new()
        {
            IsChecked = "x".Equals(firstMatch.Groups["value"].Value, StringComparison.InvariantCultureIgnoreCase),
        };
        chk.Checked += (sender, e) => this.ReflectChkChangeInMarkdown(sender as CheckBox, true, firstMatch.Value);
        chk.Unchecked += (sender, e) => this.ReflectChkChangeInMarkdown(sender as CheckBox, false, firstMatch.Value);
        chk.Loaded += (sender, e) => this.UpdateChkEnabled(sender as CheckBox, firstMatch.Value);

        if (firstMatch.Groups["caption"].Value is string caption && !string.IsNullOrEmpty(caption))
        {
            chk.Content = new FlowDocumentScrollViewer()
            {
                Document = engine.Transform(caption),
                HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
                VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
                Focusable = false
            };
        }

        StackPanel sp = new();
        sp.Children.Add(chk);

        return new Inline[] { new InlineUIContainer(sp) };
    }

    private void UpdateChkEnabled(CheckBox chk, string text)
    {
        if (this.FindParentMarkdownViewer(chk) is not MarkdownScrollViewer viewer)
        {
            chk.IsEnabled = false;
        }
        else
        {
            chk.IsEnabled = this.UniqueIndex(viewer.Markdown, text) >=0;
        }
    }

    private void ReflectChkChangeInMarkdown(DependencyObject chk, bool isChecked, string text)
    {
        if (this.FindParentMarkdownViewer(chk) is not MarkdownScrollViewer viewer)
        {
            return;
        }

        string markdown = viewer.Markdown;
        int startIndex = this.UniqueIndex(markdown, text);
        if (startIndex == -1)
        {
            // Not unique
            return;
        }

        StringBuilder sb = new(viewer.Markdown);
        sb[startIndex + 1] = isChecked ? 'x' : ' ';
        viewer.Markdown = sb.ToString();
    }

    private MarkdownScrollViewer FindParentMarkdownViewer(DependencyObject child)
    {
        var parent = VisualTreeHelper.GetParent(child);
        while (parent is not MarkdownScrollViewer and not null)
        {
            parent = VisualTreeHelper.GetParent(parent);
        }

        if (parent is not MarkdownScrollViewer viewer)
        {
            return null;
        }

        return viewer;
    }

    private int UniqueIndex(string markdown, string text)
    {
        int firstIndex = markdown.IndexOf(text);
        if (firstIndex == -1)
        {
            // Not found
            return -1;
        }

        int secondIndex = markdown.IndexOf(text, firstIndex + 1);
        if (secondIndex != -1)
        {
            // Second hit
            return -1;
        }

        return firstIndex;
    }
}
henon commented 8 months ago

Thanks a lot, this is an excellent example on how to write a custom plugin ;)

shreck980 commented 4 months ago

Hi, thank you for the plugin. I just connected it to my project and tested it. It has some problems, like when I check the checkbox in a so-called "Markdown view", and then I come back to the text, it doesn't affect the text itself, the [ ] is still without an x, moreover, when I put more checkboxes in my list, they do not appear in the "Markdown view". I am writing something like an obsidian replica just for fun. Gonna try to solve this problem in a very simple way. Thank you so much again!!!!!!!!!

shreck980 commented 4 months ago

Hi, thank you for the plugin. I just connected it to my project and tested it. It has some problems, like when I check the checkbox in a so-called "Markdown view", and then I come back to the text, it doesn't affect the text itself, the [ ] is still without an x, moreover, when I put more checkboxes in my list, they do not appear in the "Markdown view". I am writing something like an obsidian replica just for fun. Gonna try to solve this problem in a very simple way. Thank you so much again!!!!!!!!!

I just disabled the possibility of checking the checkbox in a "Markdown view". Works fine for me