RunBlockGamut(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ return DoHeaders(text,
+ s1 => DoHorizontalRules(s1,
+ s2 => DoLists(s2,
+ sn => FormParagraphs(sn))));
+
+ //text = DoCodeBlocks(text);
+ //text = DoBlockQuotes(text);
+
+ //// We already ran HashHTMLBlocks() before, in Markdown(), but that
+ //// was to escape raw HTML in the original Markdown source. This time,
+ //// we're escaping the markup we've just created, so that we don't wrap
+ //// tags around block-level tags.
+ //text = HashHTMLBlocks(text);
+
+ //text = FormParagraphs(text);
+
+ //return text;
+ }
+
+ ///
+ /// Perform transformations that occur *within* block-level tags like paragraphs, headers, and list items.
+ ///
+ private IEnumerable RunSpanGamut(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ return DoCodeSpans(text,
+ s0 => DoImages(s0,
+ s1 => DoAnchors(s1,
+ s2 => DoItalicsAndBold(s2,
+ s3 => DoText(s3)))));
+
+ //text = EscapeSpecialCharsWithinTagAttributes(text);
+ //text = EscapeBackslashes(text);
+
+ //// Images must come first, because ![foo][f] looks like an anchor.
+ //text = DoImages(text);
+ //text = DoAnchors(text);
+
+ //// Must come after DoAnchors(), because you can use < and >
+ //// delimiters in inline links like [this]().
+ //text = DoAutoLinks(text);
+
+ //text = EncodeAmpsAndAngles(text);
+ //text = DoItalicsAndBold(text);
+ //text = DoHardBreaks(text);
+
+ //return text;
+ }
+
+ private static Regex _newlinesLeadingTrailing = new Regex(@"^\n+|\n+\z", RegexOptions.Compiled);
+ private static Regex _newlinesMultiple = new Regex(@"\n{2,}", RegexOptions.Compiled);
+ private static Regex _leadingWhitespace = new Regex(@"^[ ]*", RegexOptions.Compiled);
+
+ ///
+ /// splits on two or more newlines, to form "paragraphs";
+ ///
+ private IEnumerable FormParagraphs(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ // split on two or more newlines
+ string[] grafs = _newlinesMultiple.Split(_newlinesLeadingTrailing.Replace(text, ""));
+
+ foreach (var g in grafs)
+ {
+ yield return Create(RunSpanGamut(g));
+ }
+ }
+
+ private static string _nestedBracketsPattern;
+
+ ///
+ /// Reusable pattern to match balanced [brackets]. See Friedl's
+ /// "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
+ ///
+ private static string GetNestedBracketsPattern()
+ {
+ // in other words [this] and [this[also]] and [this[also[too]]]
+ // up to _nestDepth
+ if (_nestedBracketsPattern == null)
+ _nestedBracketsPattern =
+ RepeatString(@"
+ (?> # Atomic matching
+ [^\[\]]+ # Anything other than brackets
+ |
+ \[
+ ", _nestDepth) + RepeatString(
+ @" \]
+ )*"
+ , _nestDepth);
+ return _nestedBracketsPattern;
+ }
+
+ private static string _nestedParensPattern;
+
+ ///
+ /// Reusable pattern to match balanced (parens). See Friedl's
+ /// "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
+ ///
+ private static string GetNestedParensPattern()
+ {
+ // in other words (this) and (this(also)) and (this(also(too)))
+ // up to _nestDepth
+ if (_nestedParensPattern == null)
+ _nestedParensPattern =
+ RepeatString(@"
+ (?> # Atomic matching
+ [^()\s]+ # Anything other than parens or whitespace
+ |
+ \(
+ ", _nestDepth) + RepeatString(
+ @" \)
+ )*"
+ , _nestDepth);
+ return _nestedParensPattern;
+ }
+
+ private static string _nestedParensPatternWithWhiteSpace;
+
+ ///
+ /// Reusable pattern to match balanced (parens), including whitespace. See Friedl's
+ /// "Mastering Regular Expressions", 2nd Ed., pp. 328-331.
+ ///
+ private static string GetNestedParensPatternWithWhiteSpace()
+ {
+ // in other words (this) and (this(also)) and (this(also(too)))
+ // up to _nestDepth
+ if (_nestedParensPatternWithWhiteSpace == null)
+ _nestedParensPatternWithWhiteSpace =
+ RepeatString(@"
+ (?> # Atomic matching
+ [^()]+ # Anything other than parens
+ |
+ \(
+ ", _nestDepth) + RepeatString(
+ @" \)
+ )*"
+ , _nestDepth);
+ return _nestedParensPatternWithWhiteSpace;
+ }
+
+ private static Regex _imageInline = new Regex(string.Format(@"
+ ( # wrap whole match in $1
+ !\[
+ ({0}) # link text = $2
+ \]
+ \( # literal paren
+ [ ]*
+ ({1}) # href = $3
+ [ ]*
+ ( # $4
+ (['""]) # quote char = $5
+ (.*?) # title = $6
+ \5 # matching quote
+ #[ ]* # ignore any spaces between closing quote and )
+ )? # title is optional
+ \)
+ )", GetNestedBracketsPattern(), GetNestedParensPatternWithWhiteSpace()),
+ RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ private static Regex _anchorInline = new Regex(string.Format(@"
+ ( # wrap whole match in $1
+ \[
+ ({0}) # link text = $2
+ \]
+ \( # literal paren
+ [ ]*
+ ({1}) # href = $3
+ [ ]*
+ ( # $4
+ (['""]) # quote char = $5
+ (.*?) # title = $6
+ \5 # matching quote
+ [ ]* # ignore any spaces between closing quote and )
+ )? # title is optional
+ \)
+ )", GetNestedBracketsPattern(), GetNestedParensPattern()),
+ RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ ///
+ /// Turn Markdown images into images
+ ///
+ ///
+ /// 
+ ///
+ private IEnumerable DoImages(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ return Evaluate(text, _imageInline, ImageInlineEvaluator, defaultHandler);
+ }
+
+ private Inline ImageInlineEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string linkText = match.Groups[2].Value;
+ string url = match.Groups[3].Value;
+ BitmapImage imgSource = null;
+ try
+ {
+ if (!Uri.IsWellFormedUriString(url, UriKind.Absolute) && !System.IO.Path.IsPathRooted(url))
+ {
+ url = System.IO.Path.Combine(AssetPathRoot ?? string.Empty, url);
+ }
+
+ imgSource = new BitmapImage(new Uri(url, UriKind.RelativeOrAbsolute));
+ }
+ catch (Exception)
+ {
+ return new Run("!" + url) { Foreground = Brushes.Red };
+ }
+
+ Image image = new Image { Source = imgSource, Tag = linkText };
+ if (ImageStyle == null)
+ {
+ image.Margin = new Thickness(0);
+ }
+ else
+ {
+ image.Style = ImageStyle;
+ }
+
+ // Bind size so document is updated when image is downloaded
+ if (imgSource.IsDownloading)
+ {
+ Binding binding = new Binding(nameof(BitmapImage.Width));
+ binding.Source = imgSource;
+ binding.Mode = BindingMode.OneWay;
+
+ BindingExpressionBase bindingExpression = BindingOperations.SetBinding(image, Image.WidthProperty, binding);
+ EventHandler downloadCompletedHandler = null;
+ downloadCompletedHandler = (sender, e) =>
+ {
+ imgSource.DownloadCompleted -= downloadCompletedHandler;
+ bindingExpression.UpdateTarget();
+ };
+ imgSource.DownloadCompleted += downloadCompletedHandler;
+ }
+ else
+ {
+ image.Width = imgSource.Width;
+ }
+
+ return new InlineUIContainer(image);
+ }
+
+ ///
+ /// Turn Markdown link shortcuts into hyperlinks
+ ///
+ ///
+ /// [link text](url "title")
+ ///
+ private IEnumerable DoAnchors(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ // Next, inline-style links: [link text](url "optional title") or [link text](url "optional title")
+ return Evaluate(text, _anchorInline, AnchorInlineEvaluator, defaultHandler);
+ }
+
+ private Inline AnchorInlineEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string linkText = match.Groups[2].Value;
+ string url = match.Groups[3].Value;
+ string title = match.Groups[6].Value;
+
+ var result = Create(RunSpanGamut(linkText));
+ result.Command = HyperlinkCommand;
+ result.CommandParameter = url;
+ if (LinkStyle != null)
+ {
+ result.Style = LinkStyle;
+ }
+
+ return result;
+ }
+
+ private static Regex _headerSetext = new Regex(@"
+ ^(.+?)
+ [ ]*
+ \n
+ (=+|-+) # $1 = string of ='s or -'s
+ [ ]*
+ \n+",
+ RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ private static Regex _headerAtx = new Regex(@"
+ ^(\#{1,6}) # $1 = string of #'s
+ [ ]*
+ (.+?) # $2 = Header text
+ [ ]*
+ \#* # optional closing #'s (not counted)
+ \n+",
+ RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ ///
+ /// Turn Markdown headers into HTML header tags
+ ///
+ ///
+ /// Header 1
+ /// ========
+ ///
+ /// Header 2
+ /// --------
+ ///
+ /// # Header 1
+ /// ## Header 2
+ /// ## Header 2 with closing hashes ##
+ /// ...
+ /// ###### Header 6
+ ///
+ private IEnumerable DoHeaders(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ return Evaluate(text, _headerSetext, m => SetextHeaderEvaluator(m),
+ s => Evaluate(s, _headerAtx, m => AtxHeaderEvaluator(m), defaultHandler));
+ }
+
+ private Block SetextHeaderEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string header = match.Groups[1].Value;
+ int level = match.Groups[2].Value.StartsWith("=") ? 1 : 2;
+
+ //TODO: Style the paragraph based on the header level
+ return CreateHeader(level, RunSpanGamut(header.Trim()));
+ }
+
+ private Block AtxHeaderEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string header = match.Groups[2].Value;
+ int level = match.Groups[1].Value.Length;
+ return CreateHeader(level, RunSpanGamut(header));
+ }
+
+ public Block CreateHeader(int level, IEnumerable content)
+ {
+ if (content == null)
+ {
+ throw new ArgumentNullException("content");
+ }
+
+ var block = Create(content);
+
+ switch (level)
+ {
+ case 1:
+ if (Heading1Style != null)
+ {
+ block.Style = Heading1Style;
+ }
+ break;
+
+ case 2:
+ if (Heading2Style != null)
+ {
+ block.Style = Heading2Style;
+ }
+ break;
+
+ case 3:
+ if (Heading3Style != null)
+ {
+ block.Style = Heading3Style;
+ }
+ break;
+
+ case 4:
+ if (Heading4Style != null)
+ {
+ block.Style = Heading4Style;
+ }
+ break;
+ }
+
+ return block;
+ }
+
+ private static Regex _horizontalRules = new Regex(@"
+ ^[ ]{0,3} # Leading space
+ ([-*_]) # $1: First marker
+ (?> # Repeated marker group
+ [ ]{0,2} # Zero, one, or two spaces.
+ \1 # Marker character
+ ){2,} # Group repeated at least twice
+ [ ]* # Trailing spaces
+ $ # End of line.
+ ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ ///
+ /// Turn Markdown horizontal rules into HTML hr tags
+ ///
+ ///
+ /// ***
+ /// * * *
+ /// ---
+ /// - - -
+ ///
+ private IEnumerable DoHorizontalRules(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ return Evaluate(text, _horizontalRules, RuleEvaluator, defaultHandler);
+ }
+
+ private Block RuleEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ Line line = new Line();
+ if (SeparatorStyle == null)
+ {
+ line.X2 = 1;
+ line.StrokeThickness = 1.0;
+ }
+ else
+ {
+ line.Style = SeparatorStyle;
+ }
+
+ var container = new BlockUIContainer(line);
+ return container;
+ }
+
+ private static string _wholeList = string.Format(@"
+ ( # $1 = whole list
+ ( # $2
+ [ ]{{0,{1}}}
+ ({0}) # $3 = first list item marker
+ [ ]+
+ )
+ (?s:.+?)
+ ( # $4
+ \z
+ |
+ \n{{2,}}
+ (?=\S)
+ (?! # Negative lookahead for another list item marker
+ [ ]*
+ {0}[ ]+
+ )
+ )
+ )", string.Format("(?:{0}|{1})", _markerUL, _markerOL), _tabWidth - 1);
+
+ private static Regex _listNested = new Regex(@"^" + _wholeList,
+ RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ private static Regex _listTopLevel = new Regex(@"(?:(?<=\n\n)|\A\n?)" + _wholeList,
+ RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);
+
+ ///
+ /// Turn Markdown lists into HTML ul and ol and li tags
+ ///
+ private IEnumerable DoLists(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ // We use a different prefix before nested lists than top-level lists.
+ // See extended comment in _ProcessListItems().
+ if (_listLevel > 0)
+ return Evaluate(text, _listNested, ListEvaluator, defaultHandler);
+ else
+ return Evaluate(text, _listTopLevel, ListEvaluator, defaultHandler);
+ }
+
+ private Block ListEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string list = match.Groups[1].Value;
+ string listType = Regex.IsMatch(match.Groups[3].Value, _markerUL) ? "ul" : "ol";
+
+ // Turn double returns into triple returns, so that we can make a
+ // paragraph for the last item in a list, if necessary:
+ list = Regex.Replace(list, @"\n{2,}", "\n\n\n");
+
+ var resultList = Create(ProcessListItems(list, listType == "ul" ? _markerUL : _markerOL));
+
+ resultList.MarkerStyle = listType == "ul" ? TextMarkerStyle.Disc : TextMarkerStyle.Decimal;
+
+ return resultList;
+ }
+
+ ///
+ /// Process the contents of a single ordered or unordered list, splitting it
+ /// into individual list items.
+ ///
+ private IEnumerable ProcessListItems(string list, string marker)
+ {
+ // The listLevel global keeps track of when we're inside a list.
+ // Each time we enter a list, we increment it; when we leave a list,
+ // we decrement. If it's zero, we're not in a list anymore.
+
+ // We do this because when we're not inside a list, we want to treat
+ // something like this:
+
+ // I recommend upgrading to version
+ // 8. Oops, now this line is treated
+ // as a sub-list.
+
+ // As a single paragraph, despite the fact that the second line starts
+ // with a digit-period-space sequence.
+
+ // Whereas when we're inside a list (or sub-list), that line will be
+ // treated as the start of a sub-list. What a kludge, huh? This is
+ // an aspect of Markdown's syntax that's hard to parse perfectly
+ // without resorting to mind-reading. Perhaps the solution is to
+ // change the syntax rules such that sub-lists must start with a
+ // starting cardinal number; e.g. "1." or "a.".
+
+ _listLevel++;
+ try
+ {
+ // Trim trailing blank lines:
+ list = Regex.Replace(list, @"\n{2,}\z", "\n");
+
+ string pattern = string.Format(
+ @"(\n)? # leading line = $1
+ (^[ ]*) # leading whitespace = $2
+ ({0}) [ ]+ # list marker = $3
+ ((?s:.+?) # list item text = $4
+ (\n{{1,2}}))
+ (?= \n* (\z | \2 ({0}) [ ]+))", marker);
+
+ var regex = new Regex(pattern, RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
+ var matches = regex.Matches(list);
+ foreach (Match m in matches)
+ {
+ yield return ListItemEvaluator(m);
+ }
+ }
+ finally
+ {
+ _listLevel--;
+ }
+ }
+
+ private ListItem ListItemEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string item = match.Groups[4].Value;
+ string leadingLine = match.Groups[1].Value;
+
+ if (!String.IsNullOrEmpty(leadingLine) || Regex.IsMatch(item, @"\n{2,}"))
+ // we could correct any bad indentation here..
+ return Create(RunBlockGamut(item));
+ else
+ {
+ // recursion for sub-lists
+ return Create(RunBlockGamut(item));
+ }
+ }
+
+ private static Regex _codeSpan = new Regex(@"
+ (?
+ /// Turn Markdown `code spans` into HTML code tags
+ ///
+ private IEnumerable DoCodeSpans(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ // * You can use multiple backticks as the delimiters if you want to
+ // include literal backticks in the code span. So, this input:
+ //
+ // Just type ``foo `bar` baz`` at the prompt.
+ //
+ // Will translate to:
+ //
+ // Just type foo `bar` baz at the prompt.
+ //
+ // There's no arbitrary limit to the number of backticks you
+ // can use as delimters. If you need three consecutive backticks
+ // in your code, use four for delimiters, etc.
+ //
+ // * You can use spaces to get literal backticks at the edges:
+ //
+ // ... type `` `bar` `` ...
+ //
+ // Turns to:
+ //
+ // ... type `bar` ...
+ //
+
+ return Evaluate(text, _codeSpan, CodeSpanEvaluator, defaultHandler);
+ }
+
+ private Inline CodeSpanEvaluator(Match match)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ string span = match.Groups[2].Value;
+ span = Regex.Replace(span, @"^[ ]*", ""); // leading whitespace
+ span = Regex.Replace(span, @"[ ]*$", ""); // trailing whitespace
+
+ var result = new Run(span);
+ if (CodeStyle != null)
+ {
+ result.Style = CodeStyle;
+ }
+
+ return result;
+ }
+
+ private static Regex _bold = new Regex(@"(\*\*|__) (?=\S) (.+?[*_]*) (?<=\S) \1",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled);
+ private static Regex _strictBold = new Regex(@"([\W_]|^) (\*\*|__) (?=\S) ([^\r]*?\S[\*_]*) \2 ([\W_]|$)",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled);
+
+ private static Regex _italic = new Regex(@"(\*|_) (?=\S) (.+?) (?<=\S) \1",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled);
+ private static Regex _strictItalic = new Regex(@"([\W_]|^) (\*|_) (?=\S) ([^\r\*_]*?\S) \2 ([\W_]|$)",
+ RegexOptions.IgnorePatternWhitespace | RegexOptions.Singleline | RegexOptions.Compiled);
+
+ ///
+ /// Turn Markdown *italics* and **bold** into HTML strong and em tags
+ ///
+ private IEnumerable DoItalicsAndBold(string text, Func> defaultHandler)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ // must go first, then
+ if (StrictBoldItalic)
+ {
+ return Evaluate(text, _strictBold, m => BoldEvaluator(m, 3),
+ s1 => Evaluate(s1, _strictItalic, m => ItalicEvaluator(m, 3),
+ s2 => defaultHandler(s2)));
+ }
+ else
+ {
+ return Evaluate(text, _bold, m => BoldEvaluator(m, 2),
+ s1 => Evaluate(s1, _italic, m => ItalicEvaluator(m, 2),
+ s2 => defaultHandler(s2)));
+ }
+ }
+
+ private Inline ItalicEvaluator(Match match, int contentGroup)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ var content = match.Groups[contentGroup].Value;
+ return Create(RunSpanGamut(content));
+ }
+
+ private Inline BoldEvaluator(Match match, int contentGroup)
+ {
+ if (match == null)
+ {
+ throw new ArgumentNullException("match");
+ }
+
+ var content = match.Groups[contentGroup].Value;
+ return Create(RunSpanGamut(content));
+ }
+
+ private static Regex _outDent = new Regex(@"^[ ]{1," + _tabWidth + @"}", RegexOptions.Multiline | RegexOptions.Compiled);
+
+ ///
+ /// Remove one level of line-leading spaces
+ ///
+ private string Outdent(string block)
+ {
+ return _outDent.Replace(block, "");
+ }
+
+ ///
+ /// convert all tabs to _tabWidth spaces;
+ /// standardizes line endings from DOS (CR LF) or Mac (CR) to UNIX (LF);
+ /// makes sure text ends with a couple of newlines;
+ /// removes any blank lines (only spaces) in the text
+ ///
+ private string Normalize(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ var output = new StringBuilder(text.Length);
+ var line = new StringBuilder();
+ bool valid = false;
+
+ for (int i = 0; i < text.Length; i++)
+ {
+ switch (text[i])
+ {
+ case '\n':
+ if (valid)
+ output.Append(line);
+ output.Append('\n');
+ line.Length = 0;
+ valid = false;
+ break;
+ case '\r':
+ if ((i < text.Length - 1) && (text[i + 1] != '\n'))
+ {
+ if (valid)
+ output.Append(line);
+ output.Append('\n');
+ line.Length = 0;
+ valid = false;
+ }
+ break;
+ case '\t':
+ int width = (_tabWidth - line.Length % _tabWidth);
+ for (int k = 0; k < width; k++)
+ line.Append(' ');
+ break;
+ case '\x1A':
+ break;
+ default:
+ if (!valid && text[i] != ' ')
+ valid = true;
+ line.Append(text[i]);
+ break;
+ }
+ }
+
+ if (valid)
+ output.Append(line);
+ output.Append('\n');
+
+ // add two newlines to the end before return
+ return output.Append("\n\n").ToString();
+ }
+
+ ///
+ /// this is to emulate what's evailable in PHP
+ ///
+ private static string RepeatString(string text, int count)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ var sb = new StringBuilder(text.Length * count);
+ for (int i = 0; i < count; i++)
+ sb.Append(text);
+ return sb.ToString();
+ }
+
+ private TResult Create(IEnumerable content)
+ where TResult : IAddChild, new()
+ {
+ var result = new TResult();
+ foreach (var c in content)
+ {
+ result.AddChild(c);
+ }
+
+ return result;
+ }
+
+ private IEnumerable Evaluate(string text, Regex expression, Func build, Func> rest)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ var matches = expression.Matches(text);
+ var index = 0;
+ foreach (Match m in matches)
+ {
+ if (m.Index > index)
+ {
+ var prefix = text.Substring(index, m.Index - index);
+ foreach (var t in rest(prefix))
+ {
+ yield return t;
+ }
+ }
+
+ yield return build(m);
+
+ index = m.Index + m.Length;
+ }
+
+ if (index < text.Length)
+ {
+ var suffix = text.Substring(index, text.Length - index);
+ foreach (var t in rest(suffix))
+ {
+ yield return t;
+ }
+ }
+ }
+
+ private static Regex _eoln = new Regex("\\s+");
+
+ public IEnumerable DoText(string text)
+ {
+ if (text == null)
+ {
+ throw new ArgumentNullException("text");
+ }
+
+ var t = _eoln.Replace(text, " ");
+ yield return new Run(t);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs b/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs
new file mode 100644
index 000000000..cfafc9bf0
--- /dev/null
+++ b/Artemis/Artemis/Utilities/Markdown/TextToFlowDocumentConverter.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace Artemis.Utilities.Markdown
+{
+ public class TextToFlowDocumentConverter : DependencyObject, IValueConverter
+ {
+ public Markdown Markdown
+ {
+ get { return (Markdown)GetValue(MarkdownProperty); }
+ set { SetValue(MarkdownProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for Markdown. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty MarkdownProperty =
+ DependencyProperty.Register("Markdown", typeof(Markdown), typeof(TextToFlowDocumentConverter), new PropertyMetadata(null));
+
+ ///
+ /// Converts a value.
+ ///
+ ///
+ /// A converted value. If the method returns null, the valid null value is used.
+ ///
+ /// The value produced by the binding source.
+ /// The type of the binding target property.
+ /// The converter parameter to use.
+ /// The culture to use in the converter.
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+
+ var text = (string)value;
+
+ var engine = Markdown ?? mMarkdown.Value;
+
+ return engine.Transform(text);
+ }
+
+ ///
+ /// Converts a value.
+ ///
+ ///
+ /// A converted value. If the method returns null, the valid null value is used.
+ ///
+ /// The value that is produced by the binding target.
+ /// The type to convert to.
+ /// The converter parameter to use.
+ /// The culture to use in the converter.
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+
+ private Lazy mMarkdown
+ = new Lazy(() => new Markdown());
+ }
+}
\ No newline at end of file
diff --git a/Artemis/Artemis/Utilities/Updater.cs b/Artemis/Artemis/Utilities/Updater.cs
index fe9b5f51d..214307522 100644
--- a/Artemis/Artemis/Utilities/Updater.cs
+++ b/Artemis/Artemis/Utilities/Updater.cs
@@ -60,8 +60,8 @@ namespace Artemis.Utilities
{
var settings = SettingsProvider.Load();
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;
- if ((settings.LastRanVersion != null) && (currentVersion > settings.LastRanVersion))
- {
+// if ((settings.LastRanVersion != null) && (currentVersion > settings.LastRanVersion))
+// {
Logger.Info("Updated from {0} to {1}, showing changelog.", settings.LastRanVersion, currentVersion);
// Ask the user whether he/she wants to see what's new
@@ -73,7 +73,7 @@ namespace Artemis.Utilities
// If user wants to see changelog, show it to them
if ((showChanges != null) && showChanges.Value)
await ShowChanges(dialogService, currentVersion);
- }
+// }
settings.LastRanVersion = currentVersion;
settings.Save();
@@ -114,7 +114,7 @@ namespace Artemis.Utilities
}
if (release != null)
- dialogService.ShowMessageBox(release["name"].Value(), release["body"].Value());
+ dialogService.ShowMarkdownDialog(release["name"].Value(), release["body"].Value());
else
dialogService.ShowMessageBox("Couldn't fetch release",
"Sorry, Artemis was unable to fetch the release data off of GitHub.\n" +
diff --git a/Artemis/Artemis/ViewModels/DebugViewModel.cs b/Artemis/Artemis/ViewModels/DebugViewModel.cs
index e8b891f95..2dab9f89b 100644
--- a/Artemis/Artemis/ViewModels/DebugViewModel.cs
+++ b/Artemis/Artemis/ViewModels/DebugViewModel.cs
@@ -1,4 +1,7 @@
-using System.Windows;
+using System;
+using System.IO;
+using System.Linq;
+using System.Windows;
using System.Windows.Media;
using Caliburn.Micro;
@@ -6,7 +9,6 @@ namespace Artemis.ViewModels
{
public class DebugViewModel : Screen
{
-
private DrawingImage _razerDisplay;
public DrawingImage RazerDisplay
@@ -20,6 +22,18 @@ namespace Artemis.ViewModels
}
}
+ public void OpenLog()
+ {
+ // Get the logging directory
+ var logDir = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
+ + @"\Artemis\logs");
+ // Get the newest log file
+ var currentLog = logDir.GetFiles().OrderByDescending(f => f.LastWriteTime).FirstOrDefault();
+ // Open the file with the user's default program
+ if (currentLog != null)
+ System.Diagnostics.Process.Start(currentLog.FullName);
+ }
+
public void UpdateRazerDisplay(Color[,] colors)
{
// No point updating the display if the view isn't visible
@@ -31,10 +45,8 @@ namespace Artemis.ViewModels
{
dc.PushClip(new RectangleGeometry(new Rect(0, 0, 22, 6)));
for (var y = 0; y < 6; y++)
- {
for (var x = 0; x < 22; x++)
dc.DrawRectangle(new SolidColorBrush(colors[y, x]), null, new Rect(x, y, 1, 1));
- }
}
var drawnDisplay = new DrawingImage(visual.Drawing);
drawnDisplay.Freeze();
diff --git a/Artemis/Artemis/Views/DebugView.xaml b/Artemis/Artemis/Views/DebugView.xaml
index 5c9dd7b8d..32f3d9e9d 100644
--- a/Artemis/Artemis/Views/DebugView.xaml
+++ b/Artemis/Artemis/Views/DebugView.xaml
@@ -4,8 +4,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
+ xmlns:log="clr-namespace:Artemis.Controls.Log"
mc:Ignorable="d"
- Title="DebugView" Height="329.904" Width="446.624"
+ Title="DebugView" Height="600" Width="900"
GlowBrush="{DynamicResource AccentColorBrush}">
@@ -13,17 +14,27 @@
+
+
-
+
+
+
+
+
+
+
\ No newline at end of file