diff --git a/OneMore/Commands/Edit/ConvertMarkdownCommand.cs b/OneMore/Commands/Edit/ConvertMarkdownCommand.cs
index 6c481de5c3..65d71436fb 100644
--- a/OneMore/Commands/Edit/ConvertMarkdownCommand.cs
+++ b/OneMore/Commands/Edit/ConvertMarkdownCommand.cs
@@ -68,6 +68,8 @@ public override async Task Execute(params object[] args)
var text = reader.ReadTextFrom(paragraphs, allContent);
text = Regex.Replace(text, @"
([\n\r]+)", "$1");
+ text = Regex.Replace(text, @"\<*input\s+type*=*\""checkbox\""\s+unchecked\s+[a-zA-Z *]*\/\>", "[ ]");
+ text = Regex.Replace(text, @"\<*input\s+type*=*\""checkbox\""\s+checked\s+[a-zA-Z *]*\/\>", "[x]");
var body = OneMoreDig.ConvertMarkdownToHtml(filepath, text);
diff --git a/OneMore/Commands/File/ImportCommand.cs b/OneMore/Commands/File/ImportCommand.cs
index dbbd58b1e5..28b8de37b9 100644
--- a/OneMore/Commands/File/ImportCommand.cs
+++ b/OneMore/Commands/File/ImportCommand.cs
@@ -11,7 +11,8 @@ namespace River.OneMoreAddIn.Commands
using System;
using System.Drawing;
using System.IO;
- using System.Threading;
+ using System.Text.RegularExpressions;
+ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;
@@ -653,6 +654,8 @@ private async Task ImportMarkdownFile(string filepath, CancellationToken t
page.Title = Path.GetFileNameWithoutExtension(filepath);
var container = page.EnsureContentContainer();
+ body = Regex.Replace(body, @"\<*input\s+type*=*\""checkbox\""\s+unchecked\s+[a-zA-Z *]*\/\>", "[ ]");
+ body = Regex.Replace(body, @"\<*input\s+type*=*\""checkbox\""\s+checked\s+[a-zA-Z *]*\/\>", "[x]");
container.Add(new XElement(ns + "HTMLBlock",
new XElement(ns + "Data",
@@ -675,6 +678,7 @@ private async Task ImportMarkdownFile(string filepath, CancellationToken t
converter = new MarkdownConverter(page);
converter.RewriteHeadings();
+ converter.RewriteTodo();
logger.WriteLine($"updating...");
logger.WriteLine(page.Root);
diff --git a/OneMore/Commands/File/Markdown/MarkdownConverter.cs b/OneMore/Commands/File/Markdown/MarkdownConverter.cs
index 3b5fc2bf1f..c05485ab0b 100644
--- a/OneMore/Commands/File/Markdown/MarkdownConverter.cs
+++ b/OneMore/Commands/File/Markdown/MarkdownConverter.cs
@@ -7,6 +7,7 @@ namespace River.OneMoreAddIn.Commands
using River.OneMoreAddIn.Models;
using River.OneMoreAddIn.Styles;
using System.Collections.Generic;
+ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
@@ -51,6 +52,28 @@ public void RewriteHeadings()
{
RewriteHeadings(outline.Descendants(ns + "OE"));
}
+
+ // added header spacing specific to markdown
+ var quickstyles = page.Root.Elements(ns + "QuickStyleDef");
+ foreach (var quickstyle in quickstyles)
+ {
+ var name = quickstyle.Attribute("name").Value;
+ if (name.Equals("h1") || name.Equals("h2"))
+ {
+ replaceAtributes(quickstyle, spaceBefore: 0.8, spaceAfter: 0.5);
+ }
+ else
+ if (name.Equals("h3") || name.Equals("h4"))
+ {
+ replaceAtributes(quickstyle, spaceBefore: 0.3, spaceAfter: 0.3);
+ }
+ }
+ void replaceAtributes(XElement quickstyle, double spaceBefore, double spaceAfter)
+ {
+ quickstyle.SetAttributeValue("spaceBefore", spaceBefore.ToString("####0.00", CultureInfo.InvariantCulture));
+ quickstyle.SetAttributeValue("spaceAfter", spaceAfter.ToString("####0.00", CultureInfo.InvariantCulture));
+ }
+
}
@@ -141,8 +164,22 @@ public MarkdownConverter RewriteHeadings(IEnumerable paragraphs)
}
+ ///
+ /// Applies standard OneNote styling to all recognizable headings in all Outlines
+ /// on the page
+ ///
+ public void RewriteTodo()
+ {
+ foreach (var outline in page.BodyOutlines)
+ {
+ RewriteTodo(outline.Descendants(ns + "OE"));
+ }
+ }
+
+
///
/// Tag current line with To Do tag if beginning with [ ] or [x]
+ /// Also :TAGS: will be handled here
/// All other :emojis: should be translated inline by Markdig
///
///
@@ -158,21 +195,46 @@ public MarkdownConverter RewriteTodo(IEnumerable paragraphs)
{
var cdata = run.GetCData();
var wrapper = cdata.GetWrapper();
+ if (wrapper.FirstNode is XText)
+ {
+ cdata.Value = wrapper.GetInnerXml();
+ }
+ while (wrapper.FirstNode is not XText && wrapper.FirstNode is not null)
+ {
+ wrapper = (XElement)wrapper.FirstNode;
+ }
if (wrapper.FirstNode is XText text)
{
var match = boxpattern.Match(text.Value);
+ // special treatment of todo tag
if (match.Success)
{
text.Value = text.Value.Substring(match.Length);
+ var org = text.Value;
+ var completed = match.Groups["x"].Value == "x";
+ text.Value = text.Value.Replace((completed ? "[x]" : "[ ]"), "");
+ cdata.Value = cdata.Value.Replace(org, text.Value);
// ensure TagDef exists
- var index = page.AddTagDef("3", "To Do", 4);
-
- // inject tag prior to run
- run.AddBeforeSelf(new Tag(index, match.Groups["x"].Value == "x"));
+ page.SetTag(paragraph, tagSymbol: "3", tagStatus:completed,tagName:"todo");
+ }
+ else
+ {
+ // look for all other tags
+ foreach (var t in MarkdownEmojis.taglist)
+ {
+ // check for other tags
+ if (text.Value.Contains(t.name))
+ {
+ var org = text.Value;
+ text.Value = text.Value.Replace(t.name, "");
+ cdata.Value = cdata.Value.Replace(org, text.Value);
+ // ensure TagDef exists
+ page.SetTag(paragraph, tagSymbol: t.id, tagStatus: false, tagName: t.topic, tagType: t.type);
+ break;
+ }
+ }
- // update run text
- cdata.Value = wrapper.GetInnerXml();
}
}
}
diff --git a/OneMore/Commands/File/Markdown/MarkdownEmojis.cs b/OneMore/Commands/File/Markdown/MarkdownEmojis.cs
new file mode 100644
index 0000000000..93f2eff2c3
--- /dev/null
+++ b/OneMore/Commands/File/Markdown/MarkdownEmojis.cs
@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+
+namespace River.OneMoreAddIn.Commands
+{
+ public static class MarkdownEmojis
+ {
+ public static List<(string name, string id, string topic, int type)> taglist = new List<(string name, string id, string topic, int type)>
+ {
+// (":todo:", "3", "todo" , 0),
+ (":question:", "6", "question" , 0),
+ (":star:", "13", "important", 0 ),
+ (":exclamation:", "17", "critical", 0),
+ (":phone:", "18", "phone", 0),
+ (":bulb:", "21", "idea", 0),
+ (":house:", "23", "address", 0),
+ (":three:", "33", "three", 0),
+ (":zero:", "39", "zero", 0),
+ (":two:", "51", "two", 0),
+ (":arrow_right:", "59", "main agenda item", 0),
+ (":one:", "70", "one", 0),
+ (":information_desk_person:","94", "discuss person a/b", 21),
+ (":bellsymbol:", "97", "bellsymbol", 0),
+ (":busts_in_silhouette:", "116", "busts_in_silhouette", 0),
+ (":bell:", "117", "bell", 0),
+ (":letter:", "118", "letter", 0),
+ (":musical_note:", "121", "musical_note", 0),
+ (":secret:", "131", "idea", 0),
+ (":book:", "132", "book", 0),
+ (":movie_camera:", "133", "movie_camera", 0),
+ (":zap:", "140", "lightning_bolt", 0),
+ (":o:", "1", "default", 0)
+ };
+
+
+ }
+}
diff --git a/OneMore/Commands/File/Markdown/MarkdownWriter.cs b/OneMore/Commands/File/Markdown/MarkdownWriter.cs
index 7b0207107b..197cded54f 100644
--- a/OneMore/Commands/File/Markdown/MarkdownWriter.cs
+++ b/OneMore/Commands/File/Markdown/MarkdownWriter.cs
@@ -34,18 +34,32 @@ private sealed class Context
// accent enclosure char, asterisk* or backquote`
public string Accent;
}
+ // helper class to pass parameter
+ private sealed class PrefixClass
+ {
+ public string indents = string.Empty;
+ public string tags = string.Empty;
+ public string bullets = string.Empty;
+ public string tablelistid = string.Empty;
+ public bool justclosed = false;
+
+ public PrefixClass()
+ {
+ }
+ }
// Note that if pasting md text directly into OneNote, there's no good way to indent text
// and prevent OneNote from auto-formatting. Closest alt is to use a string of nbsp's
// but that conflicts with other directives like headings and list numbering. One way is
// to substitute indentations (e.g., OEChildren) with the blockquote directive instead.
- private const string Indent = " "; //">"; // ";
+ private const string Indent = " "; //">"; // ";
private const string Quote = ">";
private readonly Page page;
private readonly XNamespace ns;
private readonly List");
+ }
}
- WriteTable(element);
+ if (contained)
+ {
+ var tableindex = nestedtables.Count() + 1;
+ nestedtables.Add((element, tableindex));
+ writer.Write(prefix.indents + "[nested-table" + tableindex + "](#nested-table" + tableindex + ")");
+ }
+ else
+ {
+ WriteTable(element, prefix);
+ while (nestedtables.Count() != 0)
+ {
+ var nestedtable = nestedtables.First();
+ writer.WriteLine(prefix.indents + "");
+ writer.WriteLine(prefix.indents + "" + "Nested Table " + nestedtable.index + "
");
+ WriteTable(nestedtable.container, prefix);
+ writer.WriteLine(prefix.indents + " ");
+ nestedtables.RemoveAt(0);
+ }
+ }
+ if (bordersVisible.Equals("true"))
+ {
+ writer.Write(prefix.indents + "");
+ }
+ // Write extra line
+ writer.WriteLine();
break;
}
}
@@ -301,17 +380,7 @@ private void Write(XElement container,
private Context DetectQuickStyle(XElement element)
{
- // quickStyleIndex could be on T, OE, or OEChildren, Outline, Page
- // so ascend until we find one...
-
- int index = -1;
- while (element is not null &&
- !element.GetAttributeValue("quickStyleIndex", out index, -1))
- {
- element = element.Parent;
- }
-
- if (index >= 0)
+ if (element.GetAttributeValue("quickStyleIndex", out int index))
{
var context = new Context
{
@@ -337,40 +406,39 @@ private Context DetectQuickStyle(XElement element)
}
- private void Stylize(string prefix)
+ private string Stylize()
{
- writer.Write(prefix);
- if (contexts.Count == 0) return;
+ var styleprefix = "";
+ if (contexts.Count == 0) return "";
var context = contexts.Peek();
var quick = quickStyles.First(q => q.Index == context.QuickStyleIndex);
switch (quick.Name)
{
case "PageTitle":
- case "h1":
- writer.Write("# ");
- break;
-
- case "h2": writer.Write("## "); break;
- case "h3": writer.Write("### "); break;
- case "h4": writer.Write("#### "); break;
- case "h5": writer.Write("##### "); break;
- case "h6": writer.Write("###### "); break;
- case "blockquote": writer.Write("> "); break;
+ case "h1": styleprefix = ("# "); break;
+ case "h2": styleprefix = ("## "); break;
+ case "h3": styleprefix = ("### "); break;
+ case "h4": styleprefix = ("#### "); break;
+ case "h5": styleprefix = ("##### "); break;
+ case "h6": styleprefix = ("###### "); break;
+ case "blockquote": styleprefix = ("> "); break;
// cite and code are both block-scope style, on the OE
- case "cite": writer.Write("*"); break;
- case "code": writer.Write("`"); break;
- //case "p": logger.Write(Environment.NewLine); break;
+ case "cite": styleprefix = ("*"); break;
+ case "code": styleprefix = ("`"); break;
+ //case "p": lstyleprefix = (Environment.NewLine); break;
}
+ return styleprefix;
}
- private void WriteTag(XElement element)
+ private string WriteTag(XElement element, bool contained)
{
var symbol = page.Root.Elements(ns + "TagDef")
.Where(e => e.Attribute("index").Value == element.Attribute("index").Value)
.Select(e => int.Parse(e.Attribute("symbol").Value))
.FirstOrDefault();
-
+ var retValue = "";
+ var tagSymbol = MarkdownEmojis.taglist.Find(x => x.id == symbol.ToString());
switch (symbol)
{
case 3: // to do
@@ -381,31 +449,19 @@ private void WriteTag(XElement element)
case 94: // discuss person a/b
case 95: // discuss manager
var check = element.Attribute("completed").Value == "true" ? "x" : " ";
- writer.Write($"[{check}] ");
- break;
+ retValue = contained
+ ? @""
+ : ($"[{check}] ");
- case 6: writer.Write(":question: "); break; // question
- case 13: writer.Write(":star: "); break; // important
- case 17: writer.Write(":exclamation: "); break; // critical
- case 18: writer.Write(":phone: "); break; // phone
- case 21: writer.Write(":bulb: "); break; // idea
- case 23: writer.Write(":house: "); break; // address
- case 33: writer.Write(":three: "); break; // three
- case 39: writer.Write(":zero: "); break; // zero
- case 51: writer.Write(":two: "); break; // two
- case 70: writer.Write(":one: "); break; // one
- case 118: writer.Write(":mailbox: "); break; // contact
- case 121: writer.Write(":musical_note: "); break; // music to listen to
- case 131: writer.Write(":secret: "); break; // password
- case 133: writer.Write(":movie_camera: "); break; // movie to see
- case 132: writer.Write(":book: "); break; // book to read
- case 140: writer.Write(":zap: "); break; // lightning bolt
- default: writer.Write(":o: "); break; // big red circle
+ break;
+ default: retValue = tagSymbol.name + " ";
+ break;
}
+ return retValue;
}
- private void WriteText(XCData cdata, bool startOfLine)
+ private void WriteText(XCData cdata, bool startOfLine, bool contained)
{
cdata.Value = cdata.Value
.Replace("
", " ") // usually followed by NL so leave it there
@@ -428,7 +484,15 @@ private void WriteText(XCData cdata, bool startOfLine)
span.ReplaceWith(new XText(text));
}
- foreach (var anchor in wrapper.Elements("a"))
+ // escape directives
+ var raw = wrapper.GetInnerXml()
+ .Replace("<", "\\<")
+ .Replace("|", "\\|")
+ .Replace("à", "→ ") // right arrow
+ .Replace("\n", contained ? "
" : "\n"); // newlines in tables
+
+ // replace links with <> to allow special characters and hence place if after escape directives
+ foreach (var anchor in wrapper.Elements("a").ToList())
{
var href = anchor.Attribute("href")?.Value;
if (!string.IsNullOrEmpty(href))
@@ -440,21 +504,21 @@ private void WriteText(XCData cdata, bool startOfLine)
}
else
{
- anchor.ReplaceWith(new XText($"[{anchor.Value}]({href})"));
+ anchor.ReplaceWith(new XText($"[{anchor.Value}](<{href}>)"));
}
}
}
- // escape directives
- var raw = wrapper.GetInnerXml()
- .Replace("<", "\\<")
- .Replace("|", "\\|");
-
if (startOfLine && raw.Length > 0 && raw.StartsWith("#"))
{
writer.Write("\\");
}
+ if (startOfLine && raw.Length > 0)
+ {
+ raw += " "; // add extra space to end of line
+ }
+
logger.Debug($"text [{raw}]");
writer.Write(raw);
}
@@ -480,7 +544,7 @@ private void WriteImage(XElement element)
image.Save(filename, ImageFormat.Png);
#endif
- var imgPath = Path.Combine(attachmentFolder, name);
+ var imgPath = Path.Combine(attachmentFolder, name).Replace("\\", "/").Replace(" ", "%20");
writer.Write($"");
}
else
@@ -545,19 +609,16 @@ private void WriteFile(XElement element)
}
- private void WriteTable(XElement element)
+ private void WriteTable(XElement element, PrefixClass prefix)
{
#region WriteRow(TableRow row)
void WriteRow(TableRow row)
{
- writer.Write("| ");
+ writer.Write(prefix.indents + "| ");
foreach (var cell in row.Cells)
{
- cell.Root
- .Element(ns + "OEChildren")
- .Elements(ns + "OE")
- .ForEach(e => Write(e, contained: true));
-
+ PrefixClass nestedprefix = new PrefixClass();
+ Write(cell.Root, nestedprefix, contained: true);
writer.Write(" | ");
}
writer.WriteLine();
@@ -566,14 +627,15 @@ void WriteRow(TableRow row)
var table = new Table(element);
- // table needs a blank line before it
+ // table needs a blank line before it, even 2nd one sometimes needed
+ writer.WriteLine();
writer.WriteLine();
var rows = table.Rows;
// header - - - - - - - - - - - - - - - - - - -
- if (table.HasHeaderRow && rows.Any())
+ if ((table.HasHeaderRow && rows.Any()) || rows.Count() == 1)
{
// use first row data as header
WriteRow(rows.First());
@@ -593,7 +655,7 @@ void WriteRow(TableRow row)
// separator - - - - - - - - - - - - - - - - -
- writer.Write("|");
+ writer.Write(prefix.indents + "| ");
for (int i = 0; i < table.ColumnCount; i++)
{
writer.Write(" :--- |");
diff --git a/OneMore/Commands/File/Markdown/OneMoreDigExtensions.cs b/OneMore/Commands/File/Markdown/OneMoreDigExtensions.cs
index 42ab906fd7..ce83ef85f9 100644
--- a/OneMore/Commands/File/Markdown/OneMoreDigExtensions.cs
+++ b/OneMore/Commands/File/Markdown/OneMoreDigExtensions.cs
@@ -5,13 +5,31 @@
namespace River.OneMoreAddIn.Commands
{
using Markdig;
-
+ using Markdig.Extensions.Emoji;
+ using System.Collections.Generic;
+ using System.Linq;
internal static class OneMoreDigExtensions
{
+
public static MarkdownPipelineBuilder UseOneMoreExtensions(
this MarkdownPipelineBuilder pipeline)
{
+ var emojiDic = EmojiMapping.GetDefaultEmojiShortcodeToUnicode();
+ var emojiDicNew = new Dictionary();
+ foreach (var mappings in emojiDic)
+ {
+ var tagName = MarkdownEmojis.taglist.FirstOrDefault(x => x.name.Equals(mappings.Key)).name;
+ if (tagName.IsNullOrEmpty())
+ {
+ emojiDicNew.Add(mappings.Key,mappings.Value);
+ }
+ }
+ var DefaultEmojisAndSmileysMapping = new EmojiMapping(
+ emojiDicNew, EmojiMapping.GetDefaultSmileyToEmojiShortcode());
+ // var emojiMapping = EmojiMapping.DefaultEmojisAndSmileysMapping;
+ pipeline.Extensions.Add(new EmojiExtension(DefaultEmojisAndSmileysMapping));
+
pipeline.Extensions.Add(new OneMoreDigExtension());
return pipeline;
}
diff --git a/OneMore/Models/Page.cs b/OneMore/Models/Page.cs
index 0c7b0102bc..b91584bda8 100644
--- a/OneMore/Models/Page.cs
+++ b/OneMore/Models/Page.cs
@@ -122,7 +122,6 @@ public void OptimizeForSave(bool keep)
public bool IsValid => Root is not null;
-
///
/// Gets the namespace used to create new elements for the page
///
@@ -277,6 +276,55 @@ public void AddTagDef(TagDef tagdef)
Root.AddFirst(tagdef);
}
+ ///
+ /// Extended version from RemindCommand to handle also non Todo Tags
+ ///
+ public string SetTag(XElement paragraph, string tagSymbol, string tagName, int tagType = 0, bool tagStatus = false)
+ {
+ var index = this.GetTagDefIndex(tagSymbol);
+ if (index == null)
+ {
+ index = this.AddTagDef(tagSymbol, tagName, tagType);
+ }
+
+ var tag = paragraph.Elements(Namespace + "Tag")
+ .FirstOrDefault(e => e.Attribute("index").Value == index);
+
+ if (tag == null)
+ {
+ // tags must be ordered by index even within their containing paragraph
+ // so take all, remove from paragraph, append, sort, re-add...
+
+ var tags = paragraph.Elements(Namespace + "Tag").ToList();
+ tags.ForEach(t => t.Remove());
+
+ // synchronize tag with reminder
+ var completed = tagStatus == true
+ ? "true" : "false";
+
+ tag = new XElement(Namespace + "Tag",
+ new XAttribute("index", index),
+ new XAttribute("completed", completed),
+ new XAttribute("disabled", "false")
+ );
+
+ tags.Add(tag);
+
+ paragraph.AddFirst(tags.OrderBy(t => t.Attribute("index").Value));
+ }
+ else
+ {
+ // synchronize tag with reminder
+ var tcompleted = tag.Attribute("completed").Value == "true";
+ var rcompleted = tagStatus;
+ if (tcompleted != rcompleted)
+ {
+ tag.Attribute("completed").Value = rcompleted ? "true" : "false";
+ }
+ }
+ return index;
+ }
+
///
/// Apply the given quick style mappings to all descendents of the specified outline.
diff --git a/OneMore/OneMore.csproj b/OneMore/OneMore.csproj
index de9e00477f..01138c4bec 100644
--- a/OneMore/OneMore.csproj
+++ b/OneMore/OneMore.csproj
@@ -181,6 +181,7 @@
+
Form