Public Access
Improve settings logs agent and renderer
PR and Push Build/Test / build-and-test (push) Successful in 8m13s
PR and Push Build/Test / build-and-test (push) Successful in 8m13s
This commit is contained in:
@@ -5,6 +5,10 @@ internal enum WorkflowRulesEditorMarkdownInlineStyle
|
||||
Normal,
|
||||
Italic,
|
||||
Bold,
|
||||
BoldItalic,
|
||||
Strikethrough,
|
||||
Link,
|
||||
Image,
|
||||
Code
|
||||
}
|
||||
|
||||
@@ -13,6 +17,24 @@ internal abstract record WorkflowRulesEditorMarkdownBlock;
|
||||
internal sealed record WorkflowRulesEditorMarkdownParagraph(
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownInline> Inlines) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownHeading(
|
||||
int Level,
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownInline> Inlines) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownBlockQuote(
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownInline> Inlines) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownImage(
|
||||
string AltText,
|
||||
string Target) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownTaskList(
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownTaskItem> Items) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownTaskItem(
|
||||
bool IsChecked,
|
||||
IReadOnlyList<WorkflowRulesEditorMarkdownInline> Inlines);
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownCodeBlock(string Text) : WorkflowRulesEditorMarkdownBlock;
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownTable(
|
||||
@@ -21,7 +43,8 @@ internal sealed record WorkflowRulesEditorMarkdownTable(
|
||||
|
||||
internal sealed record WorkflowRulesEditorMarkdownInline(
|
||||
string Text,
|
||||
WorkflowRulesEditorMarkdownInlineStyle Style);
|
||||
WorkflowRulesEditorMarkdownInlineStyle Style,
|
||||
string? LinkTarget = null);
|
||||
|
||||
internal static class WorkflowRulesEditorMarkdown
|
||||
{
|
||||
@@ -64,6 +87,50 @@ internal static class WorkflowRulesEditorMarkdown
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryParseStandaloneImage(line, out var image))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
blocks.Add(image);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryParseHeading(line, out var heading))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
blocks.Add(heading);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsBlockQuote(line))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
var quote = new List<string>();
|
||||
while (index < lines.Length && IsBlockQuote(lines[index]))
|
||||
{
|
||||
quote.Add(lines[index].TrimStart()[1..].TrimStart());
|
||||
index++;
|
||||
}
|
||||
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownBlockQuote(ParseInline(string.Join('\n', quote))));
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryParseTaskLine(line, out _))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
var tasks = new List<WorkflowRulesEditorMarkdownTaskItem>();
|
||||
while (index < lines.Length && TryParseTaskLine(lines[index], out var task))
|
||||
{
|
||||
tasks.Add(task);
|
||||
index++;
|
||||
}
|
||||
|
||||
blocks.Add(new WorkflowRulesEditorMarkdownTaskList(tasks));
|
||||
index--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsTableStart(lines, index))
|
||||
{
|
||||
FlushParagraph(blocks, paragraph);
|
||||
@@ -128,6 +195,72 @@ internal static class WorkflowRulesEditorMarkdown
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text.AsSpan(next).StartsWith("![", StringComparison.Ordinal))
|
||||
{
|
||||
if (TryParseImage(text, next, out var altText, out var target, out var end))
|
||||
{
|
||||
AddInline(
|
||||
result,
|
||||
altText,
|
||||
WorkflowRulesEditorMarkdownInlineStyle.Image,
|
||||
target);
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text[next] == '[')
|
||||
{
|
||||
var endLabel = text.IndexOf(']', next + 1);
|
||||
if (endLabel > next &&
|
||||
endLabel + 1 < text.Length &&
|
||||
text[endLabel + 1] == '(')
|
||||
{
|
||||
var endTarget = text.IndexOf(')', endLabel + 2);
|
||||
if (endTarget > endLabel + 2)
|
||||
{
|
||||
AddInline(
|
||||
result,
|
||||
text[(next + 1)..endLabel],
|
||||
WorkflowRulesEditorMarkdownInlineStyle.Link,
|
||||
text[(endLabel + 2)..endTarget]);
|
||||
index = endTarget + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (text[next] == '<')
|
||||
{
|
||||
if (TryParseAutolink(text, next, out var label, out var target, out var end))
|
||||
{
|
||||
AddInline(
|
||||
result,
|
||||
label,
|
||||
WorkflowRulesEditorMarkdownInlineStyle.Link,
|
||||
target);
|
||||
index = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text.AsSpan(next).StartsWith("***", StringComparison.Ordinal))
|
||||
{
|
||||
var end = text.IndexOf("***", next + 3, StringComparison.Ordinal);
|
||||
if (end > next)
|
||||
{
|
||||
AddInline(result, text[(next + 3)..end], WorkflowRulesEditorMarkdownInlineStyle.BoldItalic);
|
||||
index = end + 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text.AsSpan(next).StartsWith("~~", StringComparison.Ordinal))
|
||||
{
|
||||
var end = text.IndexOf("~~", next + 2, StringComparison.Ordinal);
|
||||
if (end > next)
|
||||
{
|
||||
AddInline(result, text[(next + 2)..end], WorkflowRulesEditorMarkdownInlineStyle.Strikethrough);
|
||||
index = end + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (text.AsSpan(next).StartsWith("**", StringComparison.Ordinal))
|
||||
{
|
||||
var end = text.IndexOf("**", next + 2, StringComparison.Ordinal);
|
||||
@@ -169,6 +302,141 @@ internal static class WorkflowRulesEditorMarkdown
|
||||
paragraph.Clear();
|
||||
}
|
||||
|
||||
private static bool TryParseHeading(string line, out WorkflowRulesEditorMarkdownHeading heading)
|
||||
{
|
||||
heading = null!;
|
||||
var trimmed = line.TrimStart();
|
||||
var level = trimmed.StartsWith("# ", StringComparison.Ordinal)
|
||||
? 1
|
||||
: trimmed.StartsWith("## ", StringComparison.Ordinal)
|
||||
? 2
|
||||
: 0;
|
||||
if (level == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
heading = new WorkflowRulesEditorMarkdownHeading(level, ParseInline(trimmed));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsBlockQuote(string line)
|
||||
{
|
||||
return line.TrimStart().StartsWith('>');
|
||||
}
|
||||
|
||||
private static bool TryParseTaskLine(string line, out WorkflowRulesEditorMarkdownTaskItem task)
|
||||
{
|
||||
task = null!;
|
||||
var trimmedStart = line.TrimStart();
|
||||
if (trimmedStart.StartsWith("- [x] ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
task = new WorkflowRulesEditorMarkdownTaskItem(true, ParseInline(trimmedStart[6..]));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trimmedStart.StartsWith("- [ ] ", StringComparison.Ordinal))
|
||||
{
|
||||
task = new WorkflowRulesEditorMarkdownTaskItem(false, ParseInline(trimmedStart[6..]));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryParseStandaloneImage(
|
||||
string line,
|
||||
out WorkflowRulesEditorMarkdownImage image)
|
||||
{
|
||||
image = null!;
|
||||
var trimmed = line.Trim();
|
||||
if (!TryParseImage(trimmed, 0, out var altText, out var target, out var end) ||
|
||||
end != trimmed.Length - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
image = new WorkflowRulesEditorMarkdownImage(altText, target);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseImage(
|
||||
string text,
|
||||
int start,
|
||||
out string altText,
|
||||
out string target,
|
||||
out int end)
|
||||
{
|
||||
altText = "";
|
||||
target = "";
|
||||
end = -1;
|
||||
if (!text.AsSpan(start).StartsWith("![", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var endLabel = text.IndexOf(']', start + 2);
|
||||
if (endLabel <= start ||
|
||||
endLabel + 1 >= text.Length ||
|
||||
text[endLabel + 1] != '(')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var endTarget = text.IndexOf(')', endLabel + 2);
|
||||
if (endTarget <= endLabel + 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
altText = text[(start + 2)..endLabel];
|
||||
target = text[(endLabel + 2)..endTarget];
|
||||
end = endTarget;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseAutolink(
|
||||
string text,
|
||||
int start,
|
||||
out string label,
|
||||
out string target,
|
||||
out int end)
|
||||
{
|
||||
label = "";
|
||||
target = "";
|
||||
end = text.IndexOf('>', start + 1);
|
||||
if (end <= start + 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
label = text[(start + 1)..end];
|
||||
if (label.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
label.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
|
||||
label.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
target = label;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsEmailAddress(label))
|
||||
{
|
||||
target = "mailto:" + label;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsEmailAddress(string text)
|
||||
{
|
||||
var at = text.IndexOf('@');
|
||||
return at > 0 &&
|
||||
at < text.Length - 1 &&
|
||||
text.IndexOf('.', at + 1) > at + 1 &&
|
||||
!text.Any(char.IsWhiteSpace);
|
||||
}
|
||||
|
||||
private static bool IsTableStart(IReadOnlyList<string> lines, int index)
|
||||
{
|
||||
return index + 1 < lines.Count &&
|
||||
@@ -215,33 +483,41 @@ internal static class WorkflowRulesEditorMarkdown
|
||||
private static void AddInline(
|
||||
List<WorkflowRulesEditorMarkdownInline> result,
|
||||
string text,
|
||||
WorkflowRulesEditorMarkdownInlineStyle style)
|
||||
WorkflowRulesEditorMarkdownInlineStyle style,
|
||||
string? linkTarget = null)
|
||||
{
|
||||
if (text.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Count > 0 && result[^1].Style == style)
|
||||
if (result.Count > 0 && result[^1].Style == style && result[^1].LinkTarget == linkTarget)
|
||||
{
|
||||
result[^1] = result[^1] with { Text = result[^1].Text + text };
|
||||
return;
|
||||
}
|
||||
|
||||
result.Add(new WorkflowRulesEditorMarkdownInline(text, style));
|
||||
result.Add(new WorkflowRulesEditorMarkdownInline(text, style, linkTarget));
|
||||
}
|
||||
|
||||
private static int FindNextMarker(string text, int start)
|
||||
{
|
||||
var code = text.IndexOf('`', start);
|
||||
var image = text.IndexOf("![", start, StringComparison.Ordinal);
|
||||
var link = text.IndexOf('[', start);
|
||||
var autolink = text.IndexOf('<', start);
|
||||
var boldItalic = text.IndexOf("***", start, StringComparison.Ordinal);
|
||||
var strikethrough = text.IndexOf("~~", start, StringComparison.Ordinal);
|
||||
var bold = text.IndexOf("**", start, StringComparison.Ordinal);
|
||||
var italic = text.IndexOf('*', start);
|
||||
if (italic >= 0 && italic + 1 < text.Length && text[italic + 1] == '*')
|
||||
while (italic >= 0 &&
|
||||
italic + 1 < text.Length &&
|
||||
text[italic + 1] == '*')
|
||||
{
|
||||
italic = text.IndexOf('*', italic + 2);
|
||||
}
|
||||
|
||||
return new[] { code, bold, italic }
|
||||
return new[] { code, image, link, autolink, boldItalic, strikethrough, bold, italic }
|
||||
.Where(index => index >= 0)
|
||||
.DefaultIfEmpty(-1)
|
||||
.Min();
|
||||
|
||||
Reference in New Issue
Block a user