Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Plugin: CheckBox #50

Open
Khaos66 opened this issue Dec 21, 2022 · 3 comments
Open

New Plugin: CheckBox #50

Khaos66 opened this issue Dec 21, 2022 · 3 comments

Comments

@Khaos66
Copy link

Khaos66 commented Dec 21, 2022

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
Copy link

henon commented Feb 26, 2024

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

@shreck980
Copy link

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants