Hexylena's Project Management Tool

πŸ—’ Blocks or Markdown

Metadata
Key Value
πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦ Parents
🏷 Tags
Created 2024-08-05 09:40:14
Modified 2024-08-06 13:54:02

Contents

Blocks or Markdown, the eternal question

AspectBlocksMarkdown
Data structurerecursive nestedstring, but we'll need blocks no matter what.
Painimplement every node supported by my markdown engine. Suck shit if it isn't supportedsomeone else's problem this will never go wrong i swearβ„’

i guess what i’m thinking is that i really don’t want to have to hook into the markdown internals (πŸ“ Remove parsing of markdown AST, just render blocks as we have them)

Currently we have this garbage:

$ ag '== "table"'
models/note_migrate.go
178:                    } else if m["type"] == "table" {
369:                    } else if m["type"] == "table" {
552:                    } else if m["type"] == "table" {

models/note.go
324:                    } else if m["type"] == "table" {

where new data structures have to be handled in 4 different places (grows linearly with migrations which is insane.)

$ git show f2391d06dab664432be247535edc8a584cf6d26c
commit f2391d06dab664432be247535edc8a584cf6d26c
Author: Helena Rasche <hexylena@galaxians.org>
Date:   Mon Aug 5 12:24:17 2024 +0200

    add support for tables

diff --git a/md/block.go b/md/block.go
index 8635502..6b4769b 100644
--- a/md/block.go
+++ b/md/block.go
@@ -3,11 +3,12 @@ package md
 import (
 	"encoding/json"
 	"fmt"
+	"strconv"
+	"strings"
+
 	"github.com/gomarkdown/markdown"
 	"github.com/gomarkdown/markdown/html"
 	"github.com/gomarkdown/markdown/parser"
-	"strconv"
-	"strings"
 )
 
 /*
@@ -129,6 +130,7 @@ type SyntaxNode interface {
 	Html() string
 	Md() string
 	Type() string
+	String() string
 }
 
 type Heading struct {
@@ -160,12 +162,16 @@ func (h *Heading) MarshalJSON() (b []byte, e error) {
 	})
 }
 
+func (s *Heading) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
+
 type Paragraph struct {
 	Contents string `json:"contents"`
 }
 
 func (p *Paragraph) Html() string {
-	return fmt.Sprintf("<p>%s</p>", mdToHTML([]byte(p.Contents)))
+	return string(mdToHTML([]byte(p.Contents)))
 
 }
 
@@ -184,6 +190,10 @@ func (p *Paragraph) Type() string {
 	return "paragraph"
 }
 
+func (s *Paragraph) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
+
 type List struct {
 	Contents []string `json:"contents"`
 	Ordered  bool     `json:"ordered"`
@@ -224,6 +234,10 @@ func (l *List) Type() string {
 	return "list"
 }
 
+func (s *List) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
+
 type Image struct {
 	AltText string `json:"alt_text"`
 	Url     string `json:"url"`
@@ -248,6 +262,9 @@ func (i *Image) MarshalJSON() ([]byte, error) {
 func (i *Image) Type() string {
 	return "image"
 }
+func (s *Image) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
 
 type HorizontalRule struct{}
 
@@ -268,6 +285,9 @@ func (h *HorizontalRule) MarshalJSON() ([]byte, error) {
 func (h *HorizontalRule) Type() string {
 	return "horizontal_rule"
 }
+func (s *HorizontalRule) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
 
 const TABLE_VIEW = "table_view"
 
@@ -293,6 +313,9 @@ func (t *TableView) MarshalJSON() ([]byte, error) {
 func (t *TableView) Type() string {
 	return TABLE_VIEW
 }
+func (s *TableView) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
 
 type Code struct {
 	Lang     string `json:"lang"`
@@ -318,6 +341,9 @@ func (c *Code) MarshalJSON() ([]byte, error) {
 func (c *Code) Type() string {
 	return "code"
 }
+func (s *Code) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
 
 type Link struct {
 	Url      string `json:"url"`
@@ -343,6 +369,9 @@ func (l *Link) MarshalJSON() ([]byte, error) {
 func (l *Link) Type() string {
 	return "link"
 }
+func (s *Link) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
 
 // type BlockType string
 // const (
@@ -363,3 +392,57 @@ func (l *Link) Type() string {
 // 	Contents any `json:"contents"`
 // 	Type BlockType `json:"type"`
 // }
+
+type Table struct {
+	Header []string   `json:"thead"`
+	Body   [][]string `json:"tbody"`
+}
+
+func (t *Table) Html() string {
+	out := "<table>"
+	// Add table header
+	out += "<thead><tr>"
+	for _, h := range t.Header {
+		out += "<th>" + h + "</th>"
+	}
+	out += "</tr></thead>"
+
+	// Add table body
+	out += "<tbody>"
+	for _, row := range t.Body {
+		out += "<tr>"
+		for _, cell := range row {
+			out += "<td>" + cell + "</td>"
+		}
+		out += "</tr>"
+	}
+	out += "</tbody>"
+
+	out += "</table>"
+	return out
+}
+
+func (t *Table) Md() string {
+	out := "| " + strings.Join(t.Header, " | ") + " |\n"
+	out += "| " + strings.Repeat("--- | ", len(t.Header)) + "\n"
+	for _, row := range t.Body {
+		out += "| " + strings.Join(row, " | ") + " |\n"
+	}
+	return out
+}
+
+func (t *Table) Type() string {
+	return "table"
+}
+
+func (t *Table) MarshalJSON() ([]byte, error) {
+	m := make(map[string]interface{})
+	m["thead"] = t.Header
+	m["tbody"] = t.Body
+	m["type"] = t.Type()
+	return json.Marshal(m)
+}
+
+func (s *Table) String() string {
+	return fmt.Sprintf("Sn{%s}: %s", s.Type(), s.Md())
+}
diff --git a/md/parse.go b/md/parse.go
index 26c60d9..a30920d 100644
--- a/md/parse.go
+++ b/md/parse.go
@@ -5,9 +5,10 @@ package md
 import (
 	// "os"
 
+	"strings"
+
 	"github.com/gomarkdown/markdown/ast"
 	"github.com/gomarkdown/markdown/parser"
-	"strings"
 
 	"fmt"
 )
@@ -150,6 +151,33 @@ func parseBlock(node ast.Node) []SyntaxNode {
 					Ordered:  v.ListFlags&ast.ListTypeOrdered != 0,
 				},
 			}
+		case *ast.Table:
+			// need header, body
+			// header
+			header := []string{}
+			for _, row := range c.Children[0].AsContainer().Children {
+				for _, cell := range row.AsContainer().Children {
+					header_actual := cell.AsContainer().Children[0]
+					header = append(header, getContentOrig(header_actual))
+				}
+			}
+			// body
+			body := [][]string{}
+			for _, row := range c.Children[1].AsContainer().Children {
+				row_contents := []string{}
+				for _, cell := range row.AsContainer().Children {
+					cell_actual := cell.AsContainer().Children[0]
+					fmt.Println("cell_actual", cell_actual, getContentOrig(cell_actual))
+					row_contents = append(row_contents, getContentOrig(cell_actual))
+				}
+				body = append(body, row_contents)
+			}
+			return []SyntaxNode{
+				&Table{
+					Header: header,
+					Body:   body,
+				},
+			}
 		default:
 			panic(fmt.Sprintf("Unhandled container type %T", v))
 		}
@@ -180,7 +208,6 @@ func parseBlock(node ast.Node) []SyntaxNode {
 			panic(fmt.Sprintf("Unhandled leaf type %T", v))
 		}
 	}
-	panic("Should not reach here ever")
 }
 
 func MdToBlocks(md []byte) []SyntaxNode {
diff --git a/md/parse2.go b/md/parse2.go
index 0b517a4..da47e91 100644
--- a/md/parse2.go
+++ b/md/parse2.go
@@ -5,9 +5,10 @@ package md
 import (
 	// "os"
 
+	"strings"
+
 	"github.com/gomarkdown/markdown/ast"
 	"github.com/gomarkdown/markdown/parser"
-	"strings"
 
 	"fmt"
 )
diff --git a/models/note.go b/models/note.go
index d5c5d2b..30daf69 100644
--- a/models/note.go
+++ b/models/note.go
@@ -178,9 +178,6 @@ func (ce *Note) UnmarshalJSON(b []byte) error {
 	// First, deserialize everything into a map of map
 	var objMap map[string]*json.RawMessage
 	err := json.Unmarshal(b, &objMap)
-	if err != nil {
-		return err
-	}
 
 	// Must manually deserialise each item
 	if objMap["id"] != nil {
@@ -324,8 +321,15 @@ func (ce *Note) UnmarshalJSON(b []byte) error {
 					return err
 				}
 				ce.Blocks[index] = &a
+			} else if m["type"] == "table" {
+				var a pmd.Table
+				err := json.Unmarshal(*rawMessage, &a)
+				if err != nil {
+					return err
+				}
+				ce.Blocks[index] = &a
 			} else {
-				return fmt.Errorf("Unknown type: %s", m["type"])
+				return fmt.Errorf("Unknown block type: %s", m["type"])
 			}
 		}
 	}
diff --git a/models/note_migrate.go b/models/note_migrate.go
index c832617..6f7f251 100644
--- a/models/note_migrate.go
+++ b/models/note_migrate.go
@@ -175,6 +175,13 @@ func (ce *Note0) UnmarshalJSON(b []byte) error {
 					return err
 				}
 				ce.Blocks[index] = &a
+			} else if m["type"] == "table" {
+				var a pmd.Table
+				err := json.Unmarshal(*rawMessage, &a)
+				if err != nil {
+					return err
+				}
+				ce.Blocks[index] = &a
 			} else {
 				return fmt.Errorf("Unknown type: %s", m["type"])
 			}
@@ -359,6 +366,13 @@ func (ce *Note1) UnmarshalJSON(b []byte) error {
 					return err
 				}
 				ce.Blocks[index] = &a
+			} else if m["type"] == "table" {
+				var a pmd.Table
+				err := json.Unmarshal(*rawMessage, &a)
+				if err != nil {
+					return err
+				}
+				ce.Blocks[index] = &a
 			} else {
 				return fmt.Errorf("Unknown type: %s", m["type"])
 			}
@@ -535,6 +549,13 @@ func (ce *Note2) UnmarshalJSON(b []byte) error {
 					return err
 				}
 				ce.Blocks[index] = &a
+			} else if m["type"] == "table" {
+				var a pmd.Table
+				err := json.Unmarshal(*rawMessage, &a)
+				if err != nil {
+					return err
+				}
+				ce.Blocks[index] = &a
 			} else {
 				return fmt.Errorf("Unknown type: %s", m["type"])
 			}
diff --git a/projects/4/3/432137cb-5a3a-4e75-9150-b49e2aabffeb b/projects/4/3/432137cb-5a3a-4e75-9150-b49e2aabffeb
index 901f291..bae9f36 100644
--- a/projects/4/3/432137cb-5a3a-4e75-9150-b49e2aabffeb
+++ b/projects/4/3/432137cb-5a3a-4e75-9150-b49e2aabffeb
@@ -1 +1 @@
-{"id":"432137cb-5a3a-4e75-9150-b49e2aabffeb","title":"Project Management Blog","type":"project","parents":[],"blocking":[],"_blocks":[{"contents":"The current list of blog posts can be found below, they're all \"children\" of this \"project\".","type":"paragraph"},{"query":"select title, created, Author from asdf where parent = '432137cb-5a3a-4e75-9150-b49e2aabffeb'","type":"table_view"}],"_tags":[{"type":"tag","title":"Author","value":"@13bcdf42-28c0-490b-8912-d60c7bf3a9c7","icon":""}],"created":1722431454,"modified":1722437194,"version":2}
\ No newline at end of file
+{"id":"432137cb-5a3a-4e75-9150-b49e2aabffeb","title":"Project Management Blog","type":"project","parents":[],"blocking":[],"_blocks":[{"contents":"The current list of blog posts can be found below, they're all \"children\" of this \"project\".","type":"paragraph"}],"_tags":[{"type":"tag","title":"Author","value":"@13bcdf42-28c0-490b-8912-d60c7bf3a9c7","icon":""},{"type":"icon","title":"","value":"","icon":"πŸ““"}],"created":1722431454,"modified":1722851090,"version":2}
\ No newline at end of file

and I don’t want to be duplicating types for every single node so we really want the absolute minimal set of necessary nodes.

Any nodes that can be just plain old markdown, must be. Is there any reason for them not to be plain ol’ markdown?

Here were the types from the readme i wanted support for.

GroupTypeFine as Plain Ol' Markdown?
markdownh1/2/3 {4/5/6}βœ…
markdowntodo list (automatic subtasks)?
markdowntableβœ…
markdownbullet/numberedβœ…
markdowndetails/summaryβœ… (html in md yea)
markdownblockquoteβœ…
markdowncodeβœ…
markdowndividerβœ…
markdownTeXβœ… going to be rendered by mathjax anyway
advancedurl (link preview?)?
advancedimage (local)?
advancedimage (external)βœ…
advancedfile (embedded)?
db querytableβœ…
db querykanban boardβœ…
db querygallery?
db querylist?
db querycalendar?
db querytimeline?
miscbreadcrumbs?
misc2/3/4/5 columns❌ no, this needs custom representation. don't want to use HTML for this.
miscmermaidβœ… just use code blocks again and mermaid plugin
misclink to person/page/dateβœ… currently working πŸ—’ Blocks or Markdown
misc@ a day/time, and then have that show up in queries somehow?????