Repository: kovetskiy/mark
Pull request #304 (2023-05-04 12:56:28)

Support customizable layouts
Fixes: https://github.com/kovetskiy/mark/issues/275

@kovetskiy any thoughts/preferences on the syntax used here?


Patch:
diff --git a/README.md b/README.md
index 531a0ab..30b1c12 100644
--- a/README.md
+++ b/README.md
@@ -168,6 +168,36 @@ The key's value must be a string which defines the template's content.
   </tblbox>
 ```
 
+### Customizing the page layout
+
+If you set the Layout to plain, the page layout can be customized using HTML comments inside the markdown:
+
+```
+<!-- Layout: plain -->
+<!-- ac:layout -->
+
+<!-- ac:layout-section type:three_with_sidebars -->
+<!-- ac:layout-cell -->
+More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-cell -->
+More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-cell -->
+Even More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-section end -->
+
+<!-- ac:layout-section type:single -->
+<!-- ac:layout-cell -->
+Still More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-section end -->
+
+<!-- ac:layout end -->
+```
+
+Please be aware that mark does not validate the layout, so it's your responsibility to create a valid layout.
 
 ### Code Blocks
 
diff --git a/pkg/mark/markdown.go b/pkg/mark/markdown.go
index 133581a..c0da166 100644
--- a/pkg/mark/markdown.go
+++ b/pkg/mark/markdown.go
@@ -77,7 +77,7 @@ func (r *ConfluenceRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegister
 	reg.Register(ast.KindBlockquote, r.renderBlockQuote)
 	reg.Register(ast.KindCodeBlock, r.renderCodeBlock)
 	reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
-	// reg.Register(ast.KindHTMLBlock, r.renderNode)
+	reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
 	// reg.Register(ast.KindList, r.renderNode)
 	// reg.Register(ast.KindListItem, r.renderNode)
 	// reg.Register(ast.KindParagraph, r.renderNode)
@@ -533,6 +533,82 @@ func (r *ConfluenceRenderer) renderImage(writer util.BufWriter, source []byte, n
 	return ast.WalkSkipChildren, nil
 }
 
+func (r *ConfluenceRenderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+	if !entering {
+		return r.goldmarkRenderHTMLBlock(w, source, node, entering)
+	}
+
+	n := node.(*ast.HTMLBlock)
+	l := n.Lines().Len()
+	for i := 0; i < l; i++ {
+		line := n.Lines().At(i)
+
+		switch strings.Trim(string(line.Value(source)), "\n") {
+		case "<!-- ac:layout -->":
+			_, _ = w.WriteString("<ac:layout>\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout end -->":
+			_, _ = w.WriteString("</ac:layout>\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:single -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"single\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:two_equal -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"two_equal\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:two_left_sidebar -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"two_left_sidebar\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:two_right_sidebar -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"two_right_sidebar\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:three -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"three\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section type:three_with_sidebars -->":
+			_, _ = w.WriteString("<ac:layout-section type=\"three_with_sidebars\">\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-section end -->":
+			_, _ = w.WriteString("</ac:layout-section>\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-cell -->":
+			_, _ = w.WriteString("<ac:layout-cell>\n")
+			return ast.WalkContinue, nil
+		case "<!-- ac:layout-cell end -->":
+			_, _ = w.WriteString("</ac:layout-cell>\n")
+			return ast.WalkContinue, nil
+
+		}
+	}
+	return r.goldmarkRenderHTMLBlock(w, source, node, entering)
+
+}
+
+func (r *ConfluenceRenderer) goldmarkRenderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+	n := node.(*ast.HTMLBlock)
+	if entering {
+		if r.Unsafe {
+			l := n.Lines().Len()
+			for i := 0; i < l; i++ {
+				line := n.Lines().At(i)
+				r.Writer.SecureWrite(w, line.Value(source))
+			}
+		} else {
+			_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
+		}
+	} else {
+		if n.HasClosure() {
+			if r.Unsafe {
+				closure := n.ClosureLine
+				r.Writer.SecureWrite(w, closure.Value(source))
+			} else {
+				_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
+			}
+		}
+	}
+	return ast.WalkContinue, nil
+}
+
 func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, path string, mermaidProvider string) (string, []Attachment) {
 	log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
 


Test patch:
diff --git a/pkg/mark/testdata/pagelayout.html b/pkg/mark/testdata/pagelayout.html
new file mode 100644
index 0000000..66f5783
--- /dev/null
+++ b/pkg/mark/testdata/pagelayout.html
@@ -0,0 +1,18 @@
+<ac:layout>
+<ac:layout-section type="three_with_sidebars">
+<ac:layout-cell>
+<p>More Content</p>
+</ac:layout-cell>
+<ac:layout-cell>
+<p>More Content</p>
+</ac:layout-cell>
+<ac:layout-cell>
+<p>Even More Content</p>
+</ac:layout-cell>
+</ac:layout-section>
+<ac:layout-section type="single">
+<ac:layout-cell>
+<p>Still More Content</p>
+</ac:layout-cell>
+</ac:layout-section>
+</ac:layout>
diff --git a/pkg/mark/testdata/pagelayout.md b/pkg/mark/testdata/pagelayout.md
new file mode 100644
index 0000000..a4e6f08
--- /dev/null
+++ b/pkg/mark/testdata/pagelayout.md
@@ -0,0 +1,21 @@
+<!-- ac:layout -->
+
+<!-- ac:layout-section type:three_with_sidebars -->
+<!-- ac:layout-cell -->
+More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-cell -->
+More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-cell -->
+Even More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-section end -->
+
+<!-- ac:layout-section type:single -->
+<!-- ac:layout-cell -->
+Still More Content
+<!-- ac:layout-cell end -->
+<!-- ac:layout-section end -->
+
+<!-- ac:layout end -->