-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathinline_block.go
250 lines (226 loc) · 5.8 KB
/
inline_block.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package notionapi
import (
"fmt"
)
const (
// TextSpanSpecial is what Notion uses for text to represent @user and @date blocks
TextSpanSpecial = "‣"
)
const (
// AttrBold represents bold block
AttrBold = "b"
// AttrCode represents code block
AttrCode = "c"
// AttrItalic represents italic block
AttrItalic = "i"
// AttrStrikeThrought represents strikethrough block
AttrStrikeThrought = "s"
// AttrComment represents a comment block
AttrComment = "m"
// AttrLink represnts a link (url)
AttrLink = "a"
// AttrUser represents an id of a user
AttrUser = "u"
// AttrHighlight represents text high-light
AttrHighlight = "h"
// AttrDate represents a date
AttrDate = "d"
// AtttrPage represents a link to a Notion page
AttrPage = "p"
)
// TextAttr describes attributes of a span of text
// First element is name of the attribute (e.g. AttrLink)
// The rest are optional information about attribute (e.g.
// for AttrLink it's URL, for AttrUser it's user id etc.)
type TextAttr = []string
// TextSpan describes a text with attributes
type TextSpan struct {
Text string `json:"Text"`
Attrs []TextAttr `json:"Attrs"`
}
// IsPlain returns true if this InlineBlock is plain text i.e. has no attributes
func (t *TextSpan) IsPlain() bool {
return len(t.Attrs) == 0
}
func AttrGetType(attr TextAttr) string {
return attr[0]
}
func panicIfAttrNot(attr TextAttr, fnName string, expectedType string) {
if AttrGetType(attr) != expectedType {
panic(fmt.Sprintf("don't call %s on attribute of type %s", fnName, AttrGetType(attr)))
}
}
func AttrGetLink(attr TextAttr) string {
panicIfAttrNot(attr, "AttrGetLink", AttrLink)
// there are links with
if len(attr) == 1 {
return ""
}
return attr[1]
}
func AttrGetUserID(attr TextAttr) string {
panicIfAttrNot(attr, "AttrGetUserID", AttrUser)
return attr[1]
}
func AttrGetPageID(attr TextAttr) string {
panicIfAttrNot(attr, "AttrGetPageID", AttrPage)
return attr[1]
}
func AttrGetComment(attr TextAttr) string {
panicIfAttrNot(attr, "AttrGetComment", AttrComment)
return attr[1]
}
func AttrGetHighlight(attr TextAttr) string {
panicIfAttrNot(attr, "AttrGetHighlight", AttrHighlight)
return attr[1]
}
func AttrGetDate(attr TextAttr) *Date {
panicIfAttrNot(attr, "AttrGetDate", AttrDate)
js := []byte(attr[1])
var d *Date
err := jsonit.Unmarshal(js, &d)
if err != nil {
panic(err.Error())
}
return d
}
func parseTextSpanAttribute(b *TextSpan, a []interface{}) error {
if len(a) == 0 {
return fmt.Errorf("attribute array is empty")
}
s, ok := a[0].(string)
if !ok {
return fmt.Errorf("a[0] is not string. a[0] is of type %T and value %#v", a[0], a)
}
attr := TextAttr{s}
if s == AttrDate {
// date is a special case in that second value is
if len(a) != 2 {
return fmt.Errorf("unexpected date attribute. Expected 2 values, got: %#v", a)
}
v, ok := a[1].(map[string]interface{})
if !ok {
return fmt.Errorf("got unexpected type %T (expected map[string]interface{}", a[1])
}
js, err := jsonit.MarshalIndent(v, "", " ")
if err != nil {
return err
}
attr = append(attr, string(js))
b.Attrs = append(b.Attrs, attr)
return nil
}
for _, v := range a[1:] {
s, ok := v.(string)
if !ok {
return fmt.Errorf("got unexpected type %T (expected string)", v)
}
attr = append(attr, s)
}
b.Attrs = append(b.Attrs, attr)
return nil
}
func parseTextSpanAttributes(b *TextSpan, a []interface{}) error {
for _, rawAttr := range a {
attrList, ok := rawAttr.([]interface{})
if !ok {
return fmt.Errorf("rawAttr is not []interface{} but %T of value %#v", rawAttr, rawAttr)
}
err := parseTextSpanAttribute(b, attrList)
if err != nil {
return err
}
}
return nil
}
func parseTextSpan(a []interface{}) (*TextSpan, error) {
if len(a) == 0 {
return nil, fmt.Errorf("a is empty")
}
if len(a) == 1 {
s, ok := a[0].(string)
if !ok {
return nil, fmt.Errorf("a is of length 1 but not string. a[0] el type: %T, el value: '%#v'", a[0], a[0])
}
return &TextSpan{
Text: s,
}, nil
}
if len(a) != 2 {
return nil, fmt.Errorf("a is of length != 2. a value: '%#v'", a)
}
s, ok := a[0].(string)
if !ok {
return nil, fmt.Errorf("a[0] is not string. a[0] type: %T, value: '%#v'", a[0], a[0])
}
res := &TextSpan{
Text: s,
}
a, ok = a[1].([]interface{})
if !ok {
return nil, fmt.Errorf("a[1] is not []interface{}. a[1] type: %T, value: '%#v'", a[1], a[1])
}
err := parseTextSpanAttributes(res, a)
if err != nil {
return nil, err
}
return res, nil
}
// ParseTextSpans parses content from JSON into an easier to use form
func ParseTextSpans(raw interface{}) ([]*TextSpan, error) {
if raw == nil {
return nil, nil
}
var res []*TextSpan
a, ok := raw.([]interface{})
if !ok {
return nil, fmt.Errorf("raw is not of []interface{}. raw type: %T, value: '%#v'", raw, raw)
}
if len(a) == 0 {
return nil, fmt.Errorf("raw is empty")
}
for _, v := range a {
a2, ok := v.([]interface{})
if !ok {
return nil, fmt.Errorf("v is not []interface{}. v type: %T, value: '%#v'", v, v)
}
span, err := parseTextSpan(a2)
if err != nil {
return nil, err
}
res = append(res, span)
}
return res, nil
}
// TextSpansToString returns flattened content of inline blocks, without formatting
func TextSpansToString(blocks []*TextSpan) string {
s := ""
for _, block := range blocks {
if block.Text == TextSpanSpecial {
// TODO: how to handle dates, users etc.?
continue
}
s += block.Text
}
return s
}
func getFirstInline(inline []*TextSpan) string {
if len(inline) == 0 {
return ""
}
return inline[0].Text
}
func getFirstInlineBlock(v interface{}) (string, error) {
inline, err := ParseTextSpans(v)
if err != nil {
return "", err
}
return getFirstInline(inline), nil
}
func getInlineText(v interface{}) (string, error) {
inline, err := ParseTextSpans(v)
if err != nil {
return "", err
}
return TextSpansToString(inline), nil
}