@@ -13,28 +13,13 @@ import (
1313type BlockType string
1414
1515const (
16- TextBlock BlockType = "text"
17- TaskBlock BlockType = "task"
16+ TextBlock BlockType = "text"
17+ TaskBlock BlockType = "task"
18+ HeadingBlock BlockType = "heading"
1819)
1920
20- // BlockContent stores the structured content of a block
2121type BlockContent map [string ]interface {}
2222
23- // InlineStyle represents a formatting span within block text
24- type InlineStyle struct {
25- Type string `json:"type"`
26- Start int `json:"start"`
27- End int `json:"end"`
28- Href string `json:"href,omitempty"` // For link styles
29- }
30-
31- // StyleOptions defines the styling configuration for a block's content
32- type StyleOptions struct {
33- RawMarkdown string `json:"raw_markdown,omitempty"` // Original markdown text
34- InlineStyles []InlineStyle `json:"styles,omitempty"` // Extracted style information
35- PreserveFormat bool `json:"preserve_format,omitempty"` // Whether to preserve formatting in storage
36- }
37-
3823// Value implements the driver.Valuer interface for JSONB storage
3924func (bc BlockContent ) Value () (driver.Value , error ) {
4025 if bc == nil {
@@ -58,55 +43,92 @@ func (bc *BlockContent) Scan(value interface{}) error {
5843 return json .Unmarshal (bytes , bc )
5944}
6045
46+ // BlockMetadata stores flexible metadata specific to each block type
47+ type BlockMetadata map [string ]interface {}
48+
49+ // Value implements the driver.Valuer interface for JSONB storage
50+ func (bm BlockMetadata ) Value () (driver.Value , error ) {
51+ if bm == nil {
52+ return nil , nil
53+ }
54+ return json .Marshal (bm )
55+ }
56+
57+ // Scan implements the sql.Scanner interface for JSONB retrieval
58+ func (bm * BlockMetadata ) Scan (value interface {}) error {
59+ if value == nil {
60+ * bm = make (BlockMetadata )
61+ return nil
62+ }
63+
64+ bytes , ok := value .([]byte )
65+ if ! ok {
66+ return errors .New ("type assertion to []byte failed" )
67+ }
68+
69+ return json .Unmarshal (bytes , bm )
70+ }
71+
6172// Block represents a content block within a note
6273type Block struct {
6374 ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
6475 UserID uuid.UUID `gorm:"type:uuid;not null;constraint:OnDelete:CASCADE;" json:"user_id"`
6576 NoteID uuid.UUID `gorm:"type:uuid;not null;constraint:OnDelete:CASCADE;" json:"note_id"`
6677 Type BlockType `gorm:"type:varchar(20);not null" json:"type"`
67- Content BlockContent `gorm:"type:jsonb;not null;default:'{}'::jsonb" json:"content"`
68- Metadata BlockContent `gorm:"type:jsonb;default:'{}'::jsonb" json:"metadata,omitempty"`
69- Tasks []Task `gorm:"foreignKey:BlockID" json:"tasks,omitempty"`
70- Order float64 `gorm:"not null" json:"order"` // Changed from int to float64
78+ Content BlockContent `gorm:"type:jsonb;default:'{}'::jsonb" json:"content"`
79+ Metadata BlockMetadata `gorm:"type:jsonb;default:'{}'::jsonb" json:"metadata"`
80+ Order float64 `gorm:"not null" json:"order"`
7181 CreatedAt time.Time `gorm:"not null;default:now()" json:"created_at"`
7282 UpdatedAt time.Time `gorm:"not null;default:now()" json:"updated_at"`
7383 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
7484}
7585
76- // GetInlineStyles returns the block's inline style information
77- func (b * Block ) GetInlineStyles () []InlineStyle {
78- if b .Content == nil {
79- return nil
86+ // GetHeadingLevel returns the heading level (for heading blocks)
87+ func (b * Block ) GetHeadingLevel () int {
88+ if b .Type != HeadingBlock {
89+ return 0
90+ }
91+
92+ if level , ok := b .Metadata ["level" ].(float64 ); ok {
93+ return int (level )
8094 }
95+ return 1 // Default heading level
96+ }
8197
82- // Try getting styles directly from the content map
83- if spans , ok := b .Content ["spans" ].([]interface {}); ok {
84- styles := make ([]InlineStyle , 0 , len (spans ))
98+ // GetSpans returns formatting spans from metadata
99+ func (b * Block ) GetSpans () []map [string ]interface {} {
100+ if spans , ok := b .Metadata ["spans" ].([]interface {}); ok {
101+ result := make ([]map [string ]interface {}, 0 , len (spans ))
85102 for _ , span := range spans {
86103 if spanMap , ok := span .(map [string ]interface {}); ok {
87- style := InlineStyle {}
88-
89- if t , ok := spanMap ["type" ].(string ); ok {
90- style .Type = t
91- }
92-
93- if start , ok := spanMap ["start" ].(float64 ); ok {
94- style .Start = int (start )
95- }
104+ result = append (result , spanMap )
105+ }
106+ }
107+ return result
108+ }
109+ return nil
110+ }
96111
97- if end , ok := spanMap ["end" ].(float64 ); ok {
98- style .End = int (end )
99- }
112+ // IsTaskCompleted returns whether a task is completed (for task blocks)
113+ func (b * Block ) IsTaskCompleted () bool {
114+ if b .Type != TaskBlock {
115+ return false
116+ }
100117
101- if href , ok := spanMap ["href" ].(string ); ok {
102- style .Href = href
103- }
118+ if completed , ok := b .Metadata ["is_completed" ].(bool ); ok {
119+ return completed
120+ }
121+ return false
122+ }
104123
105- styles = append ( styles , style )
106- }
107- }
108- return styles
124+ // GetTaskID returns the associated task ID (for task blocks )
125+ func ( b * Block ) GetTaskID () string {
126+ if b . Type != TaskBlock {
127+ return ""
109128 }
110129
111- return nil
130+ if taskID , ok := b .Metadata ["task_id" ].(string ); ok {
131+ return taskID
132+ }
133+ return ""
112134}
0 commit comments