| @@ -59,6 +59,8 @@ namespace MarkdownDeep | |||
| // DocNet extensions | |||
| font_awesome, // Data is icon name | |||
| alert, // Data is the alert type specified. | |||
| tab, // Data is the tab header text. | |||
| tabs, // children are the tabs. | |||
| } | |||
| class Block | |||
| @@ -385,7 +387,12 @@ namespace MarkdownDeep | |||
| RenderAlert(m, b); | |||
| } | |||
| break; | |||
| case BlockType.tabs: | |||
| if(m.DocNetMode) | |||
| { | |||
| RenderTabs(m, b); | |||
| } | |||
| break; | |||
| // End DocNet Extensions | |||
| default: | |||
| b.Append("<" + BlockType.ToString() + ">"); | |||
| @@ -395,7 +402,6 @@ namespace MarkdownDeep | |||
| } | |||
| } | |||
| internal void RenderPlain(Markdown m, StringBuilder b) | |||
| { | |||
| switch (BlockType) | |||
| @@ -513,13 +519,60 @@ namespace MarkdownDeep | |||
| b.Append(alertType); | |||
| b.Append("\"><span class=\"alert-title\"><i class=\"fa fa-"); | |||
| b.Append(faIconName); | |||
| b.Append("\"></i>"); | |||
| b.Append("\"></i> "); | |||
| b.Append(title); | |||
| b.Append("</span>"); | |||
| RenderChildren(m, b); | |||
| b.Append("</div>"); | |||
| } | |||
| private void RenderTabs(Markdown m, StringBuilder b) | |||
| { | |||
| var tabId = m.GetNewTabId(); | |||
| var headerSB = new StringBuilder(); | |||
| var contentSB = new StringBuilder(); | |||
| var tabCounter = 0; | |||
| var checkedAttribute = " checked"; | |||
| foreach(var tabBlock in this.Children) | |||
| { | |||
| if(tabBlock.BlockType != BlockType.tab) | |||
| { | |||
| return; | |||
| } | |||
| var tabHeaderText = tabBlock.Data as string ?? "Tab"; | |||
| // header | |||
| string tabIdSuffix = tabCounter + "_" + tabId; | |||
| headerSB.Append("<input type=\"radio\" id=\"tab"); | |||
| headerSB.Append(tabIdSuffix); | |||
| headerSB.Append("\" name=\"tabGroup"); | |||
| headerSB.Append(tabId); | |||
| headerSB.Append("\" class=\"tab\""); | |||
| headerSB.Append(checkedAttribute); | |||
| headerSB.Append("><label for=\"tab"); | |||
| headerSB.Append(tabIdSuffix); | |||
| headerSB.Append("\">"); | |||
| headerSB.Append(tabHeaderText); | |||
| headerSB.Append("</label>"); | |||
| // content | |||
| var tabContentSB = new StringBuilder(); | |||
| tabBlock.RenderChildren(m, tabContentSB); | |||
| contentSB.Append("<div class=\"tab-content\">"); | |||
| contentSB.Append(tabContentSB.ToString()); | |||
| contentSB.Append("</div>"); | |||
| // done | |||
| checkedAttribute = string.Empty; | |||
| tabCounter++; | |||
| } | |||
| b.Append("<div class=\"tab-wrap\">"); | |||
| b.Append(headerSB.ToString()); | |||
| b.Append(contentSB.ToString()); | |||
| b.Append("</div>"); | |||
| } | |||
| #region Properties | |||
| public int ContentEnd | |||
| { | |||
| @@ -1268,10 +1268,15 @@ namespace MarkdownDeep | |||
| { | |||
| return HandleFontAwesomeExtension(b); | |||
| } | |||
| // first match @tabs, and then @tab, as both are handled by this processor. | |||
| if(DoesMatch("@tabs")) | |||
| { | |||
| return HandleTabsExtension(b); | |||
| } | |||
| if(DoesMatch("@tab")) | |||
| { | |||
| return HandleTabForTabsExtension(b); | |||
| } | |||
| if(DoesMatch("@alert")) | |||
| { | |||
| return HandleAlertExtension(b); | |||
| @@ -1280,6 +1285,7 @@ namespace MarkdownDeep | |||
| } | |||
| /// <summary> | |||
| /// Handles the alert extension: | |||
| /// @alert type | |||
| @@ -1327,21 +1333,7 @@ namespace MarkdownDeep | |||
| } | |||
| // Remove the trailing line end | |||
| if(Input[endContent - 1] == '\r' && Input[endContent - 2] == '\n') | |||
| { | |||
| endContent -= 2; | |||
| } | |||
| else | |||
| { | |||
| if(Input[endContent - 1] == '\n' && Input[endContent - 2] == '\r') | |||
| { | |||
| endContent -= 2; | |||
| } | |||
| else | |||
| { | |||
| endContent--; | |||
| } | |||
| } | |||
| endContent = UnskipCRLFBeforePos(endContent); | |||
| b.BlockType = BlockType.alert; | |||
| b.Data = alertType; | |||
| // scan the content, as it can contain markdown statements. | |||
| @@ -1350,10 +1342,101 @@ namespace MarkdownDeep | |||
| return true; | |||
| } | |||
| private bool HandleTabsExtension(Block b) | |||
| { | |||
| #warning IMPLEMENT | |||
| return false; | |||
| // skip '@tabs' | |||
| if(!SkipString("@tabs")) | |||
| { | |||
| return false; | |||
| } | |||
| // ignore what's specified behind @tabs | |||
| SkipToNextLine(); | |||
| int startContent = this.Position; | |||
| // find @end. | |||
| if(!Find("@endtabs")) | |||
| { | |||
| return false; | |||
| } | |||
| // Character before must be a eol char | |||
| if(!IsLineEnd(CharAtOffset(-1))) | |||
| { | |||
| return false; | |||
| } | |||
| int endContent = Position; | |||
| // skip @end | |||
| SkipString("@endtabs"); | |||
| SkipLinespace(); | |||
| if(!Eol) | |||
| { | |||
| return false; | |||
| } | |||
| // Remove the trailing line end | |||
| endContent = UnskipCRLFBeforePos(endContent); | |||
| b.BlockType = BlockType.tabs; | |||
| // scan the content, as it can contain markdown statements. | |||
| var contentProcessor = new BlockProcessor(m_markdown, m_markdown.MarkdownInHtml); | |||
| var scanLines = contentProcessor.ScanLines(this.Input, startContent, endContent - startContent); | |||
| // check whether the content is solely tab blocks. If not, we ignore this tabs specification. | |||
| if(scanLines.Any(x=>x.BlockType != BlockType.tab)) | |||
| { | |||
| return false; | |||
| } | |||
| b.Children = scanLines; | |||
| return true; | |||
| } | |||
| /// <summary> | |||
| /// Handles the tab for tabs extension. This is a docnet extension and it handles: | |||
| /// @tab tab head text | |||
| /// tab content | |||
| /// @end | |||
| /// </summary> | |||
| /// <param name="b">The current block.</param> | |||
| /// <returns></returns> | |||
| private bool HandleTabForTabsExtension(Block b) | |||
| { | |||
| // skip '@tab' | |||
| if(!SkipString("@tab")) | |||
| { | |||
| return false; | |||
| } | |||
| SkipLinespace(); | |||
| var tabHeaderTextStart = this.Position; | |||
| // skip to eol, then grab the content between positions. | |||
| SkipToEol(); | |||
| var tabHeaderText = this.Input.Substring(tabHeaderTextStart, this.Position - tabHeaderTextStart); | |||
| SkipToNextLine(); | |||
| int startContent = this.Position; | |||
| // find @end. | |||
| if(!Find("@end")) | |||
| { | |||
| return false; | |||
| } | |||
| // Character before must be a eol char | |||
| if(!IsLineEnd(CharAtOffset(-1))) | |||
| { | |||
| return false; | |||
| } | |||
| int endContent = Position; | |||
| // skip @end | |||
| SkipString("@end"); | |||
| SkipLinespace(); | |||
| if(!Eol) | |||
| { | |||
| return false; | |||
| } | |||
| // Remove the trailing line end | |||
| endContent = UnskipCRLFBeforePos(endContent); | |||
| b.BlockType = BlockType.tab; | |||
| b.Data = tabHeaderText; | |||
| // scan the content, as it can contain markdown statements. | |||
| var contentProcessor = new BlockProcessor(m_markdown, m_markdown.MarkdownInHtml); | |||
| b.Children = contentProcessor.ScanLines(Input, startContent, endContent - startContent); | |||
| return true; | |||
| } | |||
| @@ -42,6 +42,7 @@ namespace MarkdownDeep | |||
| private Dictionary<string, bool> m_UsedHeaderIDs; | |||
| private Dictionary<string, Abbreviation> m_AbbreviationMap; | |||
| private List<Abbreviation> m_AbbreviationList; | |||
| private int _tabIdCounter; | |||
| #endregion | |||
| // Constructor | |||
| @@ -57,6 +58,7 @@ namespace MarkdownDeep | |||
| m_UsedFootnotes = new List<Block>(); | |||
| m_UsedHeaderIDs = new Dictionary<string, bool>(); | |||
| this.CreatedH2IdCollector = new List<Tuple<string, string>>(); | |||
| _tabIdCounter = 0; | |||
| } | |||
| internal List<Block> ProcessBlocks(string str) | |||
| @@ -551,6 +553,16 @@ namespace MarkdownDeep | |||
| return sb.ToString(); | |||
| } | |||
| /// <summary> | |||
| /// Gets the new tab identifier, which is a unique number within the markdown parsing pass. | |||
| /// </summary> | |||
| /// <returns></returns> | |||
| internal int GetNewTabId() | |||
| { | |||
| _tabIdCounter++; | |||
| return _tabIdCounter; | |||
| } | |||
| // Add a link definition | |||
| internal void AddLinkDefinition(LinkDefinition link) | |||
| { | |||
| @@ -1019,7 +1031,6 @@ namespace MarkdownDeep | |||
| get; | |||
| set; | |||
| } | |||
| #endregion | |||
| } | |||
| @@ -537,5 +537,33 @@ namespace MarkdownDeep | |||
| int pos; | |||
| int end; | |||
| int mark; | |||
| /// <summary> | |||
| /// Unskips the CRLF before position. This simply means it returns the new position calculated from the specified position if before the specified | |||
| /// position a CRLF is present. | |||
| /// </summary> | |||
| /// <param name="position">The position.</param> | |||
| /// <returns>position minus 0, 1 or 2 depending on whether a CRLF is present right before position: if so, it returns position - the length of the CRLF | |||
| /// which can be 1 or 2 depending on the fact whether it's \r or \n or both.</returns> | |||
| protected int UnskipCRLFBeforePos(int position) | |||
| { | |||
| if(this.Input[position - 1] == '\r' && this.Input[position - 2] == '\n') | |||
| { | |||
| position -= 2; | |||
| } | |||
| else | |||
| { | |||
| if(this.Input[position - 1] == '\n' && this.Input[position - 2] == '\r') | |||
| { | |||
| position -= 2; | |||
| } | |||
| else | |||
| { | |||
| position--; | |||
| } | |||
| } | |||
| return position; | |||
| } | |||
| } | |||
| } | |||
| @@ -467,6 +467,12 @@ | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\Alert%28DocNetMode%29.html" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\Tabs%28DocNetMode%29.html" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <EmbeddedResource Include="testfiles\docnetmode\Tabs%28DocNetMode%29.txt" /> | |||
| </ItemGroup> | |||
| <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
| <!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
| Other similar extension points exist, see Microsoft.Common.targets. | |||
| @@ -18,6 +18,10 @@ | |||
| <span class="alert-title"><i class="fa fa-info-circle"></i>Info</span><p>This is an info text, it will be displayed in an info alert box!</p> | |||
| </div> | |||
| <div class="alert alert-info"> | |||
| <span class="alert-title"><i class="fa fa-info-circle"></i>Info</span><p>This is an <strong>info text</strong>, it will be displayed in an info alert box!</p> | |||
| </div> | |||
| <p>@alert | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end</p> | |||
| @@ -17,6 +17,10 @@ This is an info text, it will be displayed in an info alert box! | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end | |||
| @alert info | |||
| This is an **info text**, it will be displayed in an info alert box! | |||
| @end | |||
| @alert | |||
| This is an info text, it will be displayed in an info alert box! | |||
| @end | |||
| @@ -0,0 +1,14 @@ | |||
| <p>pre</p> | |||
| <p>This is some text</p> | |||
| <div class="tab-wrap"> | |||
| <input type="radio" id="tab0_1" name="tabGroup1" class="tab" checked><label for="tab0_1">First Tab</label><input type="radio" id="tab1_1" name="tabGroup1" class="tab"><label for="tab1_1">Second Tab</label><div class="tab-content"> | |||
| <p>This is the text for the first tab. It's nothing special</p> | |||
| <p>As you can see, it can deal with newlines as well. </p> | |||
| </div><div class="tab-content"> | |||
| <p>Now, the second tab however is very interesting. At least let's pretend it is!</p> | |||
| </div> | |||
| </div> | |||
| <p>post</p> | |||
| @@ -0,0 +1,15 @@ | |||
| pre | |||
| This is some text | |||
| @tabs | |||
| @tab First Tab | |||
| This is the text for the first tab. It's nothing special | |||
| As you can see, it can deal with newlines as well. | |||
| @end | |||
| @tab Second Tab | |||
| Now, the second tab however is very interesting. At least let's pretend it is! | |||
| @end | |||
| @endtabs | |||
| post | |||