Zettelstore

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From version-0.0.13 To trunk

2021-06-11
19:43
Remove support for list paging in WebUI ... (Leaf check-in: 88c5a4462e user: stern tags: trunk)
2021-06-09
17:00
Add supported metadata key 'place-number' ... (check-in: 03e324830a user: stern tags: trunk)
2021-06-07
09:11
Increase version to 0.0.14-dev to begin next development cycle ... (check-in: 7dd6f4dd5c user: stern tags: trunk)
2021-06-01
12:35
Version 0.0.13 ... (check-in: 11d9b6da63 user: stern tags: trunk, release, version-0.0.13)
10:14
Log output while starting Command Line Server ... (check-in: 968a91bbaa user: stern tags: trunk)

Changes to VERSION.

1
0.0.13
|
1
0.0.14-dev

Changes to ast/ast.go.

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	Zid     id.Zid         // Zettel identification.
	InhMeta *meta.Meta     // Metadata of the zettel, with inherited values.
	Ast     BlockSlice     // Zettel abstract syntax tree is a sequence of block nodes.
}

// Node is the interface, all nodes must implement.
type Node interface {
	Accept(v Visitor)
}

// BlockNode is the interface that all block nodes must implement.
type BlockNode interface {
	Node
	blockNode()
}







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
	Zid     id.Zid         // Zettel identification.
	InhMeta *meta.Meta     // Metadata of the zettel, with inherited values.
	Ast     BlockSlice     // Zettel abstract syntax tree is a sequence of block nodes.
}

// Node is the interface, all nodes must implement.
type Node interface {
	WalkChildren(v Visitor)
}

// BlockNode is the interface that all block nodes must implement.
type BlockNode interface {
	Node
	blockNode()
}

Changes to ast/block.go.

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
...
145
146
147
148
149
150
151
152
153







154
155
156
157
158
159
160
...
181
182
183
184
185
186
187
188
189









190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
	Inlines InlineSlice
}

func (pn *ParaNode) blockNode()       {}
func (pn *ParaNode) itemNode()        {}
func (pn *ParaNode) descriptionNode() {}

// Accept a visitor and visit the node.
func (pn *ParaNode) Accept(v Visitor) { v.VisitPara(pn) }



//--------------------------------------------------------------------------

// VerbatimNode contains lines of uninterpreted text
type VerbatimNode struct {
	Code  VerbatimCode
	Attrs *Attributes
	Lines []string
}

// VerbatimCode specifies the format that is applied to code inline nodes.
type VerbatimCode int

// Constants for VerbatimCode
const (
	_               VerbatimCode = iota
	VerbatimProg                 // Program code.
	VerbatimComment              // Block comment
	VerbatimHTML                 // Block HTML, e.g. for Markdown
)

func (vn *VerbatimNode) blockNode() {}
func (vn *VerbatimNode) itemNode()  {}

// Accept a visitor an visit the node.
func (vn *VerbatimNode) Accept(v Visitor) { v.VisitVerbatim(vn) }

//--------------------------------------------------------------------------

// RegionNode encapsulates a region of block nodes.
type RegionNode struct {
	Code    RegionCode
	Attrs   *Attributes
	Blocks  BlockSlice
	Inlines InlineSlice // Additional text at the end of the region
}

// RegionCode specifies the actual region type.
type RegionCode int

// Values for RegionCode
const (
	_           RegionCode = iota
	RegionSpan             // Just a span of blocks
	RegionQuote            // A longer quotation
	RegionVerse            // Line breaks matter
)

func (rn *RegionNode) blockNode() {}
func (rn *RegionNode) itemNode()  {}

// Accept a visitor and visit the node.
func (rn *RegionNode) Accept(v Visitor) { v.VisitRegion(rn) }




//--------------------------------------------------------------------------

// HeadingNode stores the heading text and level.
type HeadingNode struct {
	Level   int
	Inlines InlineSlice // Heading text, possibly formatted
................................................................................
	Slug    string      // Heading text, suitable to be used as an URL fragment
	Attrs   *Attributes
}

func (hn *HeadingNode) blockNode() {}
func (hn *HeadingNode) itemNode()  {}

// Accept a visitor and visit the node.
func (hn *HeadingNode) Accept(v Visitor) { v.VisitHeading(hn) }



//--------------------------------------------------------------------------

// HRuleNode specifies a horizontal rule.
type HRuleNode struct {
	Attrs *Attributes
}

func (hn *HRuleNode) blockNode() {}
func (hn *HRuleNode) itemNode()  {}

// Accept a visitor and visit the node.
func (hn *HRuleNode) Accept(v Visitor) { v.VisitHRule(hn) }

//--------------------------------------------------------------------------

// NestedListNode specifies a nestable list, either ordered or unordered.
type NestedListNode struct {
	Code  NestedListCode
	Items []ItemSlice
	Attrs *Attributes
}

// NestedListCode specifies the actual list type.
type NestedListCode int

// Values for ListCode
const (
	_                   NestedListCode = iota
	NestedListOrdered                  // Ordered list.
	NestedListUnordered                // Unordered list.
	NestedListQuote                    // Quote list.
)

func (ln *NestedListNode) blockNode() {}
func (ln *NestedListNode) itemNode()  {}

// Accept a visitor and visit the node.
func (ln *NestedListNode) Accept(v Visitor) { v.VisitNestedList(ln) }





//--------------------------------------------------------------------------

// DescriptionListNode specifies a description list.
type DescriptionListNode struct {
	Descriptions []Description
}
................................................................................
type Description struct {
	Term         InlineSlice
	Descriptions []DescriptionSlice
}

func (dn *DescriptionListNode) blockNode() {}

// Accept a visitor and visit the node.
func (dn *DescriptionListNode) Accept(v Visitor) { v.VisitDescriptionList(dn) }








//--------------------------------------------------------------------------

// TableNode specifies a full table
type TableNode struct {
	Header TableRow    // The header row
	Align  []Alignment // Default column alignment
................................................................................
	AlignLeft              // Left alignment
	AlignCenter            // Center the content
	AlignRight             // Right alignment
)

func (tn *TableNode) blockNode() {}

// Accept a visitor and visit the node.
func (tn *TableNode) Accept(v Visitor) { v.VisitTable(tn) }










//--------------------------------------------------------------------------

// BLOBNode contains just binary data that must be interpreted according to
// a syntax.
type BLOBNode struct {
	Title  string
	Syntax string
	Blob   []byte
}

func (bn *BLOBNode) blockNode() {}

// Accept a visitor and visit the node.
func (bn *BLOBNode) Accept(v Visitor) { v.VisitBLOB(bn) }







|
|
>
>





|




|
|



|








|
|





|





|
|



|








|
|
>
>
>







 







|
|
>
>











|
|





|




|
|



|








|
|
>
>
>
>







 







|
|
>
>
>
>
>
>
>







 







|
|
>
>
>
>
>
>
>
>
>













|
|
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
...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
...
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
	Inlines InlineSlice
}

func (pn *ParaNode) blockNode()       {}
func (pn *ParaNode) itemNode()        {}
func (pn *ParaNode) descriptionNode() {}

// WalkChildren walks down the inline elements.
func (pn *ParaNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, pn.Inlines)
}

//--------------------------------------------------------------------------

// VerbatimNode contains lines of uninterpreted text
type VerbatimNode struct {
	Kind  VerbatimKind
	Attrs *Attributes
	Lines []string
}

// VerbatimKind specifies the format that is applied to code inline nodes.
type VerbatimKind uint8

// Constants for VerbatimCode
const (
	_               VerbatimKind = iota
	VerbatimProg                 // Program code.
	VerbatimComment              // Block comment
	VerbatimHTML                 // Block HTML, e.g. for Markdown
)

func (vn *VerbatimNode) blockNode() {}
func (vn *VerbatimNode) itemNode()  {}

// WalkChildren does nothing.
func (vn *VerbatimNode) WalkChildren(v Visitor) {}

//--------------------------------------------------------------------------

// RegionNode encapsulates a region of block nodes.
type RegionNode struct {
	Kind    RegionKind
	Attrs   *Attributes
	Blocks  BlockSlice
	Inlines InlineSlice // Additional text at the end of the region
}

// RegionKind specifies the actual region type.
type RegionKind uint8

// Values for RegionCode
const (
	_           RegionKind = iota
	RegionSpan             // Just a span of blocks
	RegionQuote            // A longer quotation
	RegionVerse            // Line breaks matter
)

func (rn *RegionNode) blockNode() {}
func (rn *RegionNode) itemNode()  {}

// WalkChildren walks down the blocks and the text.
func (rn *RegionNode) WalkChildren(v Visitor) {
	WalkBlockSlice(v, rn.Blocks)
	WalkInlineSlice(v, rn.Inlines)
}

//--------------------------------------------------------------------------

// HeadingNode stores the heading text and level.
type HeadingNode struct {
	Level   int
	Inlines InlineSlice // Heading text, possibly formatted
................................................................................
	Slug    string      // Heading text, suitable to be used as an URL fragment
	Attrs   *Attributes
}

func (hn *HeadingNode) blockNode() {}
func (hn *HeadingNode) itemNode()  {}

// WalkChildren walks the heading text.
func (hn *HeadingNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, hn.Inlines)
}

//--------------------------------------------------------------------------

// HRuleNode specifies a horizontal rule.
type HRuleNode struct {
	Attrs *Attributes
}

func (hn *HRuleNode) blockNode() {}
func (hn *HRuleNode) itemNode()  {}

// WalkChildren does nothing.
func (hn *HRuleNode) WalkChildren(v Visitor) {}

//--------------------------------------------------------------------------

// NestedListNode specifies a nestable list, either ordered or unordered.
type NestedListNode struct {
	Kind  NestedListKind
	Items []ItemSlice
	Attrs *Attributes
}

// NestedListKind specifies the actual list type.
type NestedListKind uint8

// Values for ListCode
const (
	_                   NestedListKind = iota
	NestedListOrdered                  // Ordered list.
	NestedListUnordered                // Unordered list.
	NestedListQuote                    // Quote list.
)

func (ln *NestedListNode) blockNode() {}
func (ln *NestedListNode) itemNode()  {}

// WalkChildren walks down the items.
func (ln *NestedListNode) WalkChildren(v Visitor) {
	for _, item := range ln.Items {
		WalkItemSlice(v, item)
	}
}

//--------------------------------------------------------------------------

// DescriptionListNode specifies a description list.
type DescriptionListNode struct {
	Descriptions []Description
}
................................................................................
type Description struct {
	Term         InlineSlice
	Descriptions []DescriptionSlice
}

func (dn *DescriptionListNode) blockNode() {}

// WalkChildren walks down to the descriptions.
func (dn *DescriptionListNode) WalkChildren(v Visitor) {
	for _, desc := range dn.Descriptions {
		WalkInlineSlice(v, desc.Term)
		for _, dns := range desc.Descriptions {
			WalkDescriptionSlice(v, dns)
		}
	}
}

//--------------------------------------------------------------------------

// TableNode specifies a full table
type TableNode struct {
	Header TableRow    // The header row
	Align  []Alignment // Default column alignment
................................................................................
	AlignLeft              // Left alignment
	AlignCenter            // Center the content
	AlignRight             // Right alignment
)

func (tn *TableNode) blockNode() {}

// WalkChildren walks down to the cells.
func (tn *TableNode) WalkChildren(v Visitor) {
	for _, cell := range tn.Header {
		WalkInlineSlice(v, cell.Inlines)
	}
	for _, row := range tn.Rows {
		for _, cell := range row {
			WalkInlineSlice(v, cell.Inlines)
		}
	}
}

//--------------------------------------------------------------------------

// BLOBNode contains just binary data that must be interpreted according to
// a syntax.
type BLOBNode struct {
	Title  string
	Syntax string
	Blob   []byte
}

func (bn *BLOBNode) blockNode() {}

// WalkChildren does nothing.
func (bn *BLOBNode) WalkChildren(v Visitor) {}

Changes to ast/inline.go.

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
...
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
// TextNode just contains some text.
type TextNode struct {
	Text string // The text itself.
}

func (tn *TextNode) inlineNode() {}

// Accept a visitor and visit the node.
func (tn *TextNode) Accept(v Visitor) { v.VisitText(tn) }

// --------------------------------------------------------------------------

// TagNode contains a tag.
type TagNode struct {
	Tag string // The text itself.
}

func (tn *TagNode) inlineNode() {}

// Accept a visitor and visit the node.
func (tn *TagNode) Accept(v Visitor) { v.VisitTag(tn) }

// --------------------------------------------------------------------------

// SpaceNode tracks inter-word space characters.
type SpaceNode struct {
	Lexeme string
}

func (sn *SpaceNode) inlineNode() {}

// Accept a visitor and visit the node.
func (sn *SpaceNode) Accept(v Visitor) { v.VisitSpace(sn) }

// --------------------------------------------------------------------------

// BreakNode signals a new line that must / should be interpreted as a new line break.
type BreakNode struct {
	Hard bool // Hard line break?
}

func (bn *BreakNode) inlineNode() {}

// Accept a visitor and visit the node.
func (bn *BreakNode) Accept(v Visitor) { v.VisitBreak(bn) }

// --------------------------------------------------------------------------

// LinkNode contains the specified link.
type LinkNode struct {
	Ref     *Reference
	Inlines InlineSlice // The text associated with the link.
	OnlyRef bool        // True if no text was specified.
	Attrs   *Attributes // Optional attributes
}

func (ln *LinkNode) inlineNode() {}

// Accept a visitor and visit the node.
func (ln *LinkNode) Accept(v Visitor) { v.VisitLink(ln) }



// --------------------------------------------------------------------------

// ImageNode contains the specified image reference.
type ImageNode struct {
	Ref     *Reference  // Reference to image
	Blob    []byte      // BLOB data of the image, as an alternative to Ref.
................................................................................
	Syntax  string      // Syntax of Blob
	Inlines InlineSlice // The text associated with the image.
	Attrs   *Attributes // Optional attributes
}

func (in *ImageNode) inlineNode() {}

// Accept a visitor and visit the node.
func (in *ImageNode) Accept(v Visitor) { v.VisitImage(in) }



// --------------------------------------------------------------------------

// CiteNode contains the specified citation.
type CiteNode struct {
	Key     string      // The citation key
	Inlines InlineSlice // The text associated with the citation.
	Attrs   *Attributes // Optional attributes
}

func (cn *CiteNode) inlineNode() {}

// Accept a visitor and visit the node.
func (cn *CiteNode) Accept(v Visitor) { v.VisitCite(cn) }



// --------------------------------------------------------------------------

// MarkNode contains the specified merked position.
// It is a BlockNode too, because although it is typically parsed during inline
// mode, it is moved into block mode afterwards.
type MarkNode struct {
	Text string
}

func (mn *MarkNode) inlineNode() {}

// Accept a visitor and visit the node.
func (mn *MarkNode) Accept(v Visitor) { v.VisitMark(mn) }

// --------------------------------------------------------------------------

// FootnoteNode contains the specified footnote.
type FootnoteNode struct {
	Inlines InlineSlice // The footnote text.
	Attrs   *Attributes // Optional attributes
}

func (fn *FootnoteNode) inlineNode() {}

// Accept a visitor and visit the node.
func (fn *FootnoteNode) Accept(v Visitor) { v.VisitFootnote(fn) }



// --------------------------------------------------------------------------

// FormatNode specifies some inline formatting.
type FormatNode struct {
	Code    FormatCode
	Attrs   *Attributes // Optional attributes.
	Inlines InlineSlice
}

// FormatCode specifies the format that is applied to the inline nodes.
type FormatCode int

// Constants for FormatCode
const (
	_               FormatCode = iota
	FormatItalic               // Italic text.
	FormatEmph                 // Semantically emphasized text.
	FormatBold                 // Bold text.
	FormatStrong               // Semantically strongly emphasized text.
	FormatUnder                // Underlined text.
	FormatInsert               // Inserted text.
	FormatStrike               // Text that is no longer relevant or no longer accurate.
................................................................................
	FormatSmall                // Smaller text.
	FormatSpan                 // Generic inline container.
	FormatMonospace            // Monospaced text.
)

func (fn *FormatNode) inlineNode() {}

// Accept a visitor and visit the node.
func (fn *FormatNode) Accept(v Visitor) { v.VisitFormat(fn) }



// --------------------------------------------------------------------------

// LiteralNode specifies some uninterpreted text.
type LiteralNode struct {
	Code  LiteralCode
	Attrs *Attributes // Optional attributes.
	Text  string
}

// LiteralCode specifies the format that is applied to code inline nodes.
type LiteralCode int

// Constants for LiteralCode
const (
	_              LiteralCode = iota
	LiteralProg                // Inline program code.
	LiteralKeyb                // Keyboard strokes.
	LiteralOutput              // Sample output.
	LiteralComment             // Inline comment
	LiteralHTML                // Inline HTML, e.g. for Markdown
)

func (rn *LiteralNode) inlineNode() {}

// Accept a visitor and visit the node.
func (rn *LiteralNode) Accept(v Visitor) { v.VisitLiteral(rn) }







|
|










|
|










|
|










|
|













|
|
>
>







 







|
|
>
>












|
|
>
>












|
|











|
|
>
>





|




|
|



|







 







|
|
>
>





|




|
|



|







|

|
|
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
...
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
// TextNode just contains some text.
type TextNode struct {
	Text string // The text itself.
}

func (tn *TextNode) inlineNode() {}

// WalkChildren does nothing.
func (tn *TextNode) WalkChildren(v Visitor) {}

// --------------------------------------------------------------------------

// TagNode contains a tag.
type TagNode struct {
	Tag string // The text itself.
}

func (tn *TagNode) inlineNode() {}

// WalkChildren does nothing.
func (tn *TagNode) WalkChildren(v Visitor) {}

// --------------------------------------------------------------------------

// SpaceNode tracks inter-word space characters.
type SpaceNode struct {
	Lexeme string
}

func (sn *SpaceNode) inlineNode() {}

// WalkChildren does nothing.
func (sn *SpaceNode) WalkChildren(v Visitor) {}

// --------------------------------------------------------------------------

// BreakNode signals a new line that must / should be interpreted as a new line break.
type BreakNode struct {
	Hard bool // Hard line break?
}

func (bn *BreakNode) inlineNode() {}

// WalkChildren does nothing.
func (bn *BreakNode) WalkChildren(v Visitor) {}

// --------------------------------------------------------------------------

// LinkNode contains the specified link.
type LinkNode struct {
	Ref     *Reference
	Inlines InlineSlice // The text associated with the link.
	OnlyRef bool        // True if no text was specified.
	Attrs   *Attributes // Optional attributes
}

func (ln *LinkNode) inlineNode() {}

// WalkChildren walks to the link text.
func (ln *LinkNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, ln.Inlines)
}

// --------------------------------------------------------------------------

// ImageNode contains the specified image reference.
type ImageNode struct {
	Ref     *Reference  // Reference to image
	Blob    []byte      // BLOB data of the image, as an alternative to Ref.
................................................................................
	Syntax  string      // Syntax of Blob
	Inlines InlineSlice // The text associated with the image.
	Attrs   *Attributes // Optional attributes
}

func (in *ImageNode) inlineNode() {}

// WalkChildren walks to the image text.
func (in *ImageNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, in.Inlines)
}

// --------------------------------------------------------------------------

// CiteNode contains the specified citation.
type CiteNode struct {
	Key     string      // The citation key
	Inlines InlineSlice // The text associated with the citation.
	Attrs   *Attributes // Optional attributes
}

func (cn *CiteNode) inlineNode() {}

// WalkChildren walks to the cite text.
func (cn *CiteNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, cn.Inlines)
}

// --------------------------------------------------------------------------

// MarkNode contains the specified merked position.
// It is a BlockNode too, because although it is typically parsed during inline
// mode, it is moved into block mode afterwards.
type MarkNode struct {
	Text string
}

func (mn *MarkNode) inlineNode() {}

// WalkChildren does nothing.
func (mn *MarkNode) WalkChildren(v Visitor) {}

// --------------------------------------------------------------------------

// FootnoteNode contains the specified footnote.
type FootnoteNode struct {
	Inlines InlineSlice // The footnote text.
	Attrs   *Attributes // Optional attributes
}

func (fn *FootnoteNode) inlineNode() {}

// WalkChildren walks to the footnote text.
func (fn *FootnoteNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, fn.Inlines)
}

// --------------------------------------------------------------------------

// FormatNode specifies some inline formatting.
type FormatNode struct {
	Kind    FormatKind
	Attrs   *Attributes // Optional attributes.
	Inlines InlineSlice
}

// FormatKind specifies the format that is applied to the inline nodes.
type FormatKind uint8

// Constants for FormatCode
const (
	_               FormatKind = iota
	FormatItalic               // Italic text.
	FormatEmph                 // Semantically emphasized text.
	FormatBold                 // Bold text.
	FormatStrong               // Semantically strongly emphasized text.
	FormatUnder                // Underlined text.
	FormatInsert               // Inserted text.
	FormatStrike               // Text that is no longer relevant or no longer accurate.
................................................................................
	FormatSmall                // Smaller text.
	FormatSpan                 // Generic inline container.
	FormatMonospace            // Monospaced text.
)

func (fn *FormatNode) inlineNode() {}

// WalkChildren walks to the formatted text.
func (fn *FormatNode) WalkChildren(v Visitor) {
	WalkInlineSlice(v, fn.Inlines)
}

// --------------------------------------------------------------------------

// LiteralNode specifies some uninterpreted text.
type LiteralNode struct {
	Kind  LiteralKind
	Attrs *Attributes // Optional attributes.
	Text  string
}

// LiteralKind specifies the format that is applied to code inline nodes.
type LiteralKind uint8

// Constants for LiteralCode
const (
	_              LiteralKind = iota
	LiteralProg                // Inline program code.
	LiteralKeyb                // Keyboard strokes.
	LiteralOutput              // Sample output.
	LiteralComment             // Inline comment
	LiteralHTML                // Inline HTML, e.g. for Markdown
)

func (ln *LiteralNode) inlineNode() {}

// WalkChildren does nothing.
func (ln *LiteralNode) WalkChildren(v Visitor) {}

Deleted ast/traverser.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
//-----------------------------------------------------------------------------
// Copyright (c) 2020-2021 Detlef Stern
//
// This file is part of zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package ast provides the abstract syntax tree.
package ast

// A traverser is a Visitor that just traverses the AST and delegates node
// spacific actions to a Visitor. This Visitor should not traverse the AST.

// TopDownTraverser visits first the node and then the children nodes.
type TopDownTraverser struct {
	v Visitor
}

// NewTopDownTraverser creates a new traverser.
func NewTopDownTraverser(visitor Visitor) TopDownTraverser {
	return TopDownTraverser{visitor}
}

// VisitVerbatim has nothing to traverse.
func (t TopDownTraverser) VisitVerbatim(vn *VerbatimNode) { t.v.VisitVerbatim(vn) }

// VisitRegion traverses the content and the additional text.
func (t TopDownTraverser) VisitRegion(rn *RegionNode) {
	t.v.VisitRegion(rn)
	t.VisitBlockSlice(rn.Blocks)
	t.VisitInlineSlice(rn.Inlines)
}

// VisitHeading traverses the heading.
func (t TopDownTraverser) VisitHeading(hn *HeadingNode) {
	t.v.VisitHeading(hn)
	t.VisitInlineSlice(hn.Inlines)
}

// VisitHRule traverses nothing.
func (t TopDownTraverser) VisitHRule(hn *HRuleNode) { t.v.VisitHRule(hn) }

// VisitNestedList traverses all nested list elements.
func (t TopDownTraverser) VisitNestedList(ln *NestedListNode) {
	t.v.VisitNestedList(ln)
	for _, item := range ln.Items {
		t.visitItemSlice(item)
	}
}

// VisitDescriptionList traverses all description terms and their associated
// descriptions.
func (t TopDownTraverser) VisitDescriptionList(dn *DescriptionListNode) {
	t.v.VisitDescriptionList(dn)
	for _, defs := range dn.Descriptions {
		t.VisitInlineSlice(defs.Term)
		for _, descr := range defs.Descriptions {
			t.visitDescriptionSlice(descr)
		}
	}
}

// VisitPara traverses the inlines of a paragraph.
func (t TopDownTraverser) VisitPara(pn *ParaNode) {
	t.v.VisitPara(pn)
	t.VisitInlineSlice(pn.Inlines)
}

// VisitTable traverses all cells of the header and then row-wise all cells of
// the table body.
func (t TopDownTraverser) VisitTable(tn *TableNode) {
	t.v.VisitTable(tn)
	for _, col := range tn.Header {
		t.VisitInlineSlice(col.Inlines)
	}
	for _, row := range tn.Rows {
		for _, col := range row {
			t.VisitInlineSlice(col.Inlines)
		}
	}
}

// VisitBLOB traverses nothing.
func (t TopDownTraverser) VisitBLOB(bn *BLOBNode) { t.v.VisitBLOB(bn) }

// VisitText traverses nothing.
func (t TopDownTraverser) VisitText(tn *TextNode) { t.v.VisitText(tn) }

// VisitTag traverses nothing.
func (t TopDownTraverser) VisitTag(tn *TagNode) { t.v.VisitTag(tn) }

// VisitSpace traverses nothing.
func (t TopDownTraverser) VisitSpace(sn *SpaceNode) { t.v.VisitSpace(sn) }

// VisitBreak traverses nothing.
func (t TopDownTraverser) VisitBreak(bn *BreakNode) { t.v.VisitBreak(bn) }

// VisitLink traverses the link text.
func (t TopDownTraverser) VisitLink(ln *LinkNode) {
	t.v.VisitLink(ln)
	t.VisitInlineSlice(ln.Inlines)
}

// VisitImage traverses the image text.
func (t TopDownTraverser) VisitImage(in *ImageNode) {
	t.v.VisitImage(in)
	t.VisitInlineSlice(in.Inlines)
}

// VisitCite traverses the cite text.
func (t TopDownTraverser) VisitCite(cn *CiteNode) {
	t.v.VisitCite(cn)
	t.VisitInlineSlice(cn.Inlines)
}

// VisitFootnote traverses the footnote text.
func (t TopDownTraverser) VisitFootnote(fn *FootnoteNode) {
	t.v.VisitFootnote(fn)
	t.VisitInlineSlice(fn.Inlines)
}

// VisitMark traverses nothing.
func (t TopDownTraverser) VisitMark(mn *MarkNode) { t.v.VisitMark(mn) }

// VisitFormat traverses the formatted text.
func (t TopDownTraverser) VisitFormat(fn *FormatNode) {
	t.v.VisitFormat(fn)
	t.VisitInlineSlice(fn.Inlines)
}

// VisitLiteral traverses nothing.
func (t TopDownTraverser) VisitLiteral(ln *LiteralNode) { t.v.VisitLiteral(ln) }

// VisitBlockSlice traverses a block slice.
func (t TopDownTraverser) VisitBlockSlice(bns BlockSlice) {
	for _, bn := range bns {
		bn.Accept(t)
	}
}

func (t TopDownTraverser) visitItemSlice(ins ItemSlice) {
	for _, in := range ins {
		in.Accept(t)
	}
}

func (t TopDownTraverser) visitDescriptionSlice(dns DescriptionSlice) {
	for _, dn := range dns {
		dn.Accept(t)
	}
}

// VisitInlineSlice traverses a block slice.
func (t TopDownTraverser) VisitInlineSlice(ins InlineSlice) {
	for _, in := range ins {
		in.Accept(t)
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































































































































































































































Deleted ast/visitor.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
//-----------------------------------------------------------------------------
// Copyright (c) 2020 Detlef Stern
//
// This file is part of zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package ast provides the abstract syntax tree.
package ast

// Visitor is the interface all visitors must implement.
type Visitor interface {
	// Block nodes
	VisitVerbatim(vn *VerbatimNode)
	VisitRegion(rn *RegionNode)
	VisitHeading(hn *HeadingNode)
	VisitHRule(hn *HRuleNode)
	VisitNestedList(ln *NestedListNode)
	VisitDescriptionList(dn *DescriptionListNode)
	VisitPara(pn *ParaNode)
	VisitTable(tn *TableNode)
	VisitBLOB(bn *BLOBNode)

	// Inline nodes
	VisitText(tn *TextNode)
	VisitTag(tn *TagNode)
	VisitSpace(sn *SpaceNode)
	VisitBreak(bn *BreakNode)
	VisitLink(ln *LinkNode)
	VisitImage(in *ImageNode)
	VisitCite(cn *CiteNode)
	VisitFootnote(fn *FootnoteNode)
	VisitMark(mn *MarkNode)
	VisitFormat(fn *FormatNode)
	VisitLiteral(ln *LiteralNode)
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































































Added ast/walk.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
//-----------------------------------------------------------------------------
// Copyright (c) 2021 Detlef Stern
//
// This file is part of zettelstore.
//
// Zettelstore is licensed under the latest version of the EUPL (European Union
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package ast provides the abstract syntax tree.
package ast

// Visitor is a visitor for walking the AST.
type Visitor interface {
	Visit(node Node) Visitor
}

// Walk traverses the AST.
func Walk(v Visitor, node Node) {
	if v = v.Visit(node); v == nil {
		return
	}
	node.WalkChildren(v)
	v.Visit(nil)
}

// WalkBlockSlice traverse a block slice.
func WalkBlockSlice(v Visitor, bns BlockSlice) {
	for _, bn := range bns {
		Walk(v, bn)
	}
}

// WalkInlineSlice traverses an inline slice.
func WalkInlineSlice(v Visitor, ins InlineSlice) {
	for _, in := range ins {
		Walk(v, in)
	}
}

// WalkItemSlice traverses an item slice.
func WalkItemSlice(v Visitor, ins ItemSlice) {
	for _, in := range ins {
		Walk(v, in)
	}
}

// WalkDescriptionSlice traverses an item slice.
func WalkDescriptionSlice(v Visitor, dns DescriptionSlice) {
	for _, dn := range dns {
		Walk(v, dn)
	}
}

Changes to collect/collect.go.

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
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package collect provides functions to collect items from a syntax tree.
package collect

import (
	"zettelstore.de/z/ast"
)

// Summary stores the relevant parts of the syntax tree
type Summary struct {
	Links  []*ast.Reference // list of all referenced links
	Images []*ast.Reference // list of all referenced images
	Cites  []*ast.CiteNode  // list of all referenced citations
}

// References returns all references mentioned in the given zettel. This also
// includes references to images.
func References(zn *ast.ZettelNode) Summary {
	lv := linkVisitor{}
	ast.NewTopDownTraverser(&lv).VisitBlockSlice(zn.Ast)
	return lv.summary
}


type linkVisitor struct {
	summary Summary
}

// VisitVerbatim does nothing.
func (lv *linkVisitor) VisitVerbatim(vn *ast.VerbatimNode) {}

// VisitRegion does nothing.
func (lv *linkVisitor) VisitRegion(rn *ast.RegionNode) {}

// VisitHeading does nothing.
func (lv *linkVisitor) VisitHeading(hn *ast.HeadingNode) {}

// VisitHRule does nothing.
func (lv *linkVisitor) VisitHRule(hn *ast.HRuleNode) {}

// VisitList does nothing.
func (lv *linkVisitor) VisitNestedList(ln *ast.NestedListNode) {}

// VisitDescriptionList does nothing.
func (lv *linkVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {}

// VisitPara does nothing.
func (lv *linkVisitor) VisitPara(pn *ast.ParaNode) {}

// VisitTable does nothing.
func (lv *linkVisitor) VisitTable(tn *ast.TableNode) {}

// VisitBLOB does nothing.
func (lv *linkVisitor) VisitBLOB(bn *ast.BLOBNode) {}

// VisitText does nothing.
func (lv *linkVisitor) VisitText(tn *ast.TextNode) {}

// VisitTag does nothing.
func (lv *linkVisitor) VisitTag(tn *ast.TagNode) {}

// VisitSpace does nothing.
func (lv *linkVisitor) VisitSpace(sn *ast.SpaceNode) {}

// VisitBreak does nothing.
func (lv *linkVisitor) VisitBreak(bn *ast.BreakNode) {}

// VisitLink collects the given link as a reference.
func (lv *linkVisitor) VisitLink(ln *ast.LinkNode) {
	lv.summary.Links = append(lv.summary.Links, ln.Ref)
}

// VisitImage collects the image links as a reference.
func (lv *linkVisitor) VisitImage(in *ast.ImageNode) {
	if in.Ref != nil {
		lv.summary.Images = append(lv.summary.Images, in.Ref)
	}
}

// VisitCite collects the citation.
func (lv *linkVisitor) VisitCite(cn *ast.CiteNode) {
	lv.summary.Cites = append(lv.summary.Cites, cn)
}


// VisitFootnote does nothing.
func (lv *linkVisitor) VisitFootnote(fn *ast.FootnoteNode) {}

// VisitMark does nothing.
func (lv *linkVisitor) VisitMark(mn *ast.MarkNode) {}

// VisitFormat does nothing.
func (lv *linkVisitor) VisitFormat(fn *ast.FormatNode) {}

// VisitLiteral does nothing.
func (lv *linkVisitor) VisitLiteral(ln *ast.LiteralNode) {}







<
|
<










|
|
<
|


>
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
<
<
<
|
|
|
|
<
<
<
|
|
|
>
|
<
<
<
<
<
<
<
<
<
<
<
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











// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package collect provides functions to collect items from a syntax tree.
package collect


import "zettelstore.de/z/ast"


// Summary stores the relevant parts of the syntax tree
type Summary struct {
	Links  []*ast.Reference // list of all referenced links
	Images []*ast.Reference // list of all referenced images
	Cites  []*ast.CiteNode  // list of all referenced citations
}

// References returns all references mentioned in the given zettel. This also
// includes references to images.
func References(zn *ast.ZettelNode) (s Summary) {
	ast.WalkBlockSlice(&s, zn.Ast)

	return s
}

// Visit all node to collect data for the summary.
func (s *Summary) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {










































	case *ast.LinkNode:
		s.Links = append(s.Links, n.Ref)



	case *ast.ImageNode:
		if n.Ref != nil {
			s.Images = append(s.Images, n.Ref)
		}



	case *ast.CiteNode:
		s.Cites = append(s.Cites, n)
	}
	return s
}











Changes to collect/order.go.

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

import "zettelstore.de/z/ast"

// Order of internal reference within the given zettel.
func Order(zn *ast.ZettelNode) (result []*ast.Reference) {
	for _, bn := range zn.Ast {
		if ln, ok := bn.(*ast.NestedListNode); ok {
			switch ln.Code {
			case ast.NestedListOrdered, ast.NestedListUnordered:
				for _, is := range ln.Items {
					if ref := firstItemZettelReference(is); ref != nil {
						result = append(result, ref)
					}
				}
			}







|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

import "zettelstore.de/z/ast"

// Order of internal reference within the given zettel.
func Order(zn *ast.ZettelNode) (result []*ast.Reference) {
	for _, bn := range zn.Ast {
		if ln, ok := bn.(*ast.NestedListNode); ok {
			switch ln.Kind {
			case ast.NestedListOrdered, ast.NestedListUnordered:
				for _, is := range ln.Items {
					if ref := firstItemZettelReference(is); ref != nil {
						result = append(result, ref)
					}
				}
			}

Changes to config/config.go.

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

	// GetMarkerExternal returns the current value of the "marker-external" key.
	GetMarkerExternal() string

	// GetFooterHTML returns HTML code that should be embedded into the footer
	// of each WebUI page.
	GetFooterHTML() string

	// GetListPageSize returns the maximum length of a list to be returned in WebUI.
	// A value less or equal to zero signals no limit.
	GetListPageSize() int
}

// AuthConfig are relevant configuration values for authentication.
type AuthConfig interface {
	// GetExpertMode returns the current value of the "expert-mode" key
	GetExpertMode() bool








<
<
<
<







52
53
54
55
56
57
58




59
60
61
62
63
64
65

	// GetMarkerExternal returns the current value of the "marker-external" key.
	GetMarkerExternal() string

	// GetFooterHTML returns HTML code that should be embedded into the footer
	// of each WebUI page.
	GetFooterHTML() string




}

// AuthConfig are relevant configuration values for authentication.
type AuthConfig interface {
	// GetExpertMode returns the current value of the "expert-mode" key
	GetExpertMode() bool

Changes to docs/manual/00001004010000.zettel.

1
2
3
4
5
6
7
8
9
10
11
12
13
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
id: 00001004010000
title: Zettelstore startup configuration
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
modified: 20210525121644

The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options.
These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons.
For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are placed.
An attacker that is able to change the owner can do anything.
Therefore only the owner of the computer on which Zettelstore runs can change this information.

................................................................................
  On these devices, the operating system is free to stop the web browser and to remove temporary cookies.
  Therefore, an authenticated user will be logged off.

  If ''true'', a persistent cookie is used.
  Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds.

  Default: ''false''
; [!place-uri-X]''place-uri-//X//'', where //X// is a number greater or equal to one
: Specifies a [[place|00001004011200]] where zettel are stored.
  During startup //X// is counted up, starting with one, until no key is found.
  This allows to configure more than one place.

  If no ''place-uri-1'' key is given, the overall effect will be the same as if only ''place-uri-1'' was specified with the value ''dir://.zettel''.
  In this case, even a key ''place-uri-2'' will be ignored.
; [!read-only-mode]''read-only-mode''





|







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
id: 00001004010000
title: Zettelstore startup configuration
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
modified: 20210609185100

The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options.
These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons.
For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are placed.
An attacker that is able to change the owner can do anything.
Therefore only the owner of the computer on which Zettelstore runs can change this information.

................................................................................
  On these devices, the operating system is free to stop the web browser and to remove temporary cookies.
  Therefore, an authenticated user will be logged off.

  If ''true'', a persistent cookie is used.
  Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds.

  Default: ''false''
; [!place-uri-x]''place-uri-//X//'', where //X// is a number greater or equal to one
: Specifies a [[place|00001004011200]] where zettel are stored.
  During startup //X// is counted up, starting with one, until no key is found.
  This allows to configure more than one place.

  If no ''place-uri-1'' key is given, the overall effect will be the same as if only ''place-uri-1'' was specified with the value ''dir://.zettel''.
  In this case, even a key ''place-uri-2'' will be ignored.
; [!read-only-mode]''read-only-mode''

Changes to docs/manual/00001004020000.zettel.

1
2
3
4
5

6
7
8
9
10
11
12
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
id: 00001004020000
title: Configure the running Zettelstore
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk


You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]].
This zettel is called ""configuration zettel"".
The following metadata keys change the appearance / behavior of Zettelstore:

; [!default-copyright]''default-copyright''
: Copyright value to be used when rendering content.
................................................................................
  Default: (the empty string).
; [!home-zettel]''home-zettel''
: Specifies the identifier of the zettel, that should be presented for the default view / home view.
  If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown.
; [!marker-external]''marker-external''
: Some HTML code that is displayed after a reference to external material.
  Default: ''&\#10138;'', to display a ""&#10138;"" sign.
; [!list-page-size]''list-page-size''
: If set to a value greater than zero, specifies the number of items shown in WebUI lists.
  Basically, this is the list of all zettel (possibly restricted) and the list of search results.
  Default: ''0''.
; [!site-name]''site-name''
: Name of the Zettelstore instance.
  Will be used when displaying some lists.
  Default: ''Zettelstore''.
; [!yaml-header]''yaml-header''
: If true, metadata and content will be separated by ''-\--\\n'' instead of an empty line (''\\n\\n'').
  Default: ''false''.





>







 







<
<
<
<







1
2
3
4
5
6
7
8
9
10
11
12
13
..
51
52
53
54
55
56
57




58
59
60
61
62
63
64
id: 00001004020000
title: Configure the running Zettelstore
role: manual
tags: #configuration #manual #zettelstore
syntax: zmk
modified: 20210611213730

You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]].
This zettel is called ""configuration zettel"".
The following metadata keys change the appearance / behavior of Zettelstore:

; [!default-copyright]''default-copyright''
: Copyright value to be used when rendering content.
................................................................................
  Default: (the empty string).
; [!home-zettel]''home-zettel''
: Specifies the identifier of the zettel, that should be presented for the default view / home view.
  If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown.
; [!marker-external]''marker-external''
: Some HTML code that is displayed after a reference to external material.
  Default: ''&\#10138;'', to display a ""&#10138;"" sign.




; [!site-name]''site-name''
: Name of the Zettelstore instance.
  Will be used when displaying some lists.
  Default: ''Zettelstore''.
; [!yaml-header]''yaml-header''
: If true, metadata and content will be separated by ''-\--\\n'' instead of an empty line (''\\n\\n'').
  Default: ''false''.

Changes to docs/manual/00001006020000.zettel.

1
2
3
4
5

6
7
8
9
10
11
12
..
44
45
46
47
48
49
50



51
52
53
54
55
56
57
id: 00001006020000
title: Supported Metadata Keys
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk


Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore.
See the [[computed list of supported metadata keys|00000000000090]] for details.

Most keys conform to a [[type|00001006030000]].

; [!back]''back''
................................................................................
: Date and time when a zettel was modified through Zettelstore.
  If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value.

  This is a computed value.
  There is no need to set it via Zettelstore.
; [!no-index]''no-index''
: If set to true, the zettel will not be indexed and therefore not be found in full-text searches.



; [!precursor]''precursor''
: References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel.
  Basically the inverse of key [[''folge''|#folge]].
; [!published]''published''
: This property contains the timestamp of the mast modification / creation of the zettel.
  If [[''modified''|#modified]]is set, it contains the same value.
  Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used.





>







 







>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
..
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
id: 00001006020000
title: Supported Metadata Keys
role: manual
tags: #manual #meta #reference #zettel #zettelstore
syntax: zmk
modified: 20210609185142

Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore.
See the [[computed list of supported metadata keys|00000000000090]] for details.

Most keys conform to a [[type|00001006030000]].

; [!back]''back''
................................................................................
: Date and time when a zettel was modified through Zettelstore.
  If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value.

  This is a computed value.
  There is no need to set it via Zettelstore.
; [!no-index]''no-index''
: If set to true, the zettel will not be indexed and therefore not be found in full-text searches.
; [!place-number]''place-number''
: Is a computed value and contains the number of the place where the zettel was found.
  For all but the [[predefined zettel|00001005090000]], this number is equal to the number //X// specified in startup configuration key [[''place-uri-//X//''|00001004010000#place-uri-x]].
; [!precursor]''precursor''
: References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel.
  Basically the inverse of key [[''folge''|#folge]].
; [!published]''published''
: This property contains the timestamp of the mast modification / creation of the zettel.
  If [[''modified''|#modified]]is set, it contains the same value.
  Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used.

Changes to domain/meta/meta.go.

129
130
131
132
133
134
135
136
137
138
139

140
141
142
143
144
145
146
	KeyExpertMode        = registerKey("expert-mode", TypeBool, usageUser, "")
	KeyFolge             = registerKey("folge", TypeIDSet, usageProperty, "")
	KeyFooterHTML        = registerKey("footer-html", TypeString, usageUser, "")
	KeyForward           = registerKey("forward", TypeIDSet, usageProperty, "")
	KeyHomeZettel        = registerKey("home-zettel", TypeID, usageUser, "")
	KeyLang              = registerKey("lang", TypeWord, usageUser, "")
	KeyLicense           = registerKey("license", TypeEmpty, usageUser, "")
	KeyListPageSize      = registerKey("list-page-size", TypeNumber, usageUser, "")
	KeyMarkerExternal    = registerKey("marker-external", TypeEmpty, usageUser, "")
	KeyModified          = registerKey("modified", TypeTimestamp, usageComputed, "")
	KeyNoIndex           = registerKey("no-index", TypeBool, usageUser, "")

	KeyPrecursor         = registerKey("precursor", TypeIDSet, usageUser, KeyFolge)
	KeyPublished         = registerKey("published", TypeTimestamp, usageProperty, "")
	KeyReadOnly          = registerKey("read-only", TypeWord, usageUser, "")
	KeySiteName          = registerKey("site-name", TypeString, usageUser, "")
	KeyURL               = registerKey("url", TypeURL, usageUser, "")
	KeyUserID            = registerKey("user-id", TypeWord, usageUser, "")
	KeyUserRole          = registerKey("user-role", TypeWord, usageUser, "")







<



>







129
130
131
132
133
134
135

136
137
138
139
140
141
142
143
144
145
146
	KeyExpertMode        = registerKey("expert-mode", TypeBool, usageUser, "")
	KeyFolge             = registerKey("folge", TypeIDSet, usageProperty, "")
	KeyFooterHTML        = registerKey("footer-html", TypeString, usageUser, "")
	KeyForward           = registerKey("forward", TypeIDSet, usageProperty, "")
	KeyHomeZettel        = registerKey("home-zettel", TypeID, usageUser, "")
	KeyLang              = registerKey("lang", TypeWord, usageUser, "")
	KeyLicense           = registerKey("license", TypeEmpty, usageUser, "")

	KeyMarkerExternal    = registerKey("marker-external", TypeEmpty, usageUser, "")
	KeyModified          = registerKey("modified", TypeTimestamp, usageComputed, "")
	KeyNoIndex           = registerKey("no-index", TypeBool, usageUser, "")
	KeyPlaceNumber       = registerKey("place-number", TypeNumber, usageComputed, "")
	KeyPrecursor         = registerKey("precursor", TypeIDSet, usageUser, KeyFolge)
	KeyPublished         = registerKey("published", TypeTimestamp, usageProperty, "")
	KeyReadOnly          = registerKey("read-only", TypeWord, usageUser, "")
	KeySiteName          = registerKey("site-name", TypeString, usageUser, "")
	KeyURL               = registerKey("url", TypeURL, usageUser, "")
	KeyUserID            = registerKey("user-id", TypeWord, usageUser, "")
	KeyUserRole          = registerKey("user-role", TypeWord, usageUser, "")

Changes to encoder/htmlenc/block.go.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
..
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
...
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
...
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
...
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
	"fmt"
	"strconv"
	"strings"

	"zettelstore.de/z/ast"
)

// VisitPara emits HTML code for a paragraph: <p>...</p>
func (v *visitor) VisitPara(pn *ast.ParaNode) {
	v.b.WriteString("<p>")
	v.acceptInlineSlice(pn.Inlines)
	v.writeEndPara()
}

// VisitVerbatim emits HTML code for verbatim lines.
func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
	switch vn.Code {
	case ast.VerbatimProg:
		oldVisible := v.visibleSpace
		if vn.Attrs != nil {
			v.visibleSpace = vn.Attrs.HasDefault()
		}
		v.b.WriteString("<pre><code")
		v.visitAttributes(vn.Attrs)
................................................................................
	case ast.VerbatimHTML:
		for _, line := range vn.Lines {
			if !ignoreHTMLText(line) {
				v.b.WriteStrings(line, "\n")
			}
		}
	default:
		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
	}
}

var htmlSnippetsIgnore = []string{
	"<script",
	"</script",
	"<iframe",
................................................................................
			attrs.Remove("")
			attrs = attrs.AddClass("zs-indication").AddClass("zs-" + attrVal)
		}
	}
	return attrs
}

// VisitRegion writes HTML code for block regions.
func (v *visitor) VisitRegion(rn *ast.RegionNode) {
	var code string
	attrs := rn.Attrs
	oldVerse := v.inVerse
	switch rn.Code {
	case ast.RegionSpan:
		code = "div"
		attrs = processSpanAttributes(attrs)
	case ast.RegionVerse:
		v.inVerse = true
		code = "div"
	case ast.RegionQuote:
		code = "blockquote"
	default:
		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
	}

	v.lang.push(attrs)
	defer v.lang.pop()

	v.b.WriteStrings("<", code)
	v.visitAttributes(attrs)
	v.b.WriteString(">\n")
	v.acceptBlockSlice(rn.Blocks)
	if len(rn.Inlines) > 0 {
		v.b.WriteString("<cite>")
		v.acceptInlineSlice(rn.Inlines)
		v.b.WriteString("</cite>\n")
	}
	v.b.WriteStrings("</", code, ">\n")
	v.inVerse = oldVerse
}

// VisitHeading writes the HTML code for a heading.
func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
	v.lang.push(hn.Attrs)
	defer v.lang.pop()

	lvl := hn.Level
	if lvl > 6 {
		lvl = 6 // HTML has H1..H6
	}
................................................................................
	strLvl := strconv.Itoa(lvl)
	v.b.WriteStrings("<h", strLvl)
	v.visitAttributes(hn.Attrs)
	if slug := hn.Slug; len(slug) > 0 {
		v.b.WriteStrings(" id=\"", slug, "\"")
	}
	v.b.WriteByte('>')
	v.acceptInlineSlice(hn.Inlines)
	v.b.WriteStrings("</h", strLvl, ">\n")
}

// VisitHRule writes HTML code for a horizontal rule: <hr>.
func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
	v.b.WriteString("<hr")
	v.visitAttributes(hn.Attrs)
	if v.env.IsXHTML() {
		v.b.WriteString(" />\n")
	} else {
		v.b.WriteString(">\n")
	}
}

var listCode = map[ast.NestedListCode]string{
	ast.NestedListOrdered:   "ol",
	ast.NestedListUnordered: "ul",
}

// VisitNestedList writes HTML code for lists and blockquotes.
func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
	v.lang.push(ln.Attrs)
	defer v.lang.pop()

	if ln.Code == ast.NestedListQuote {
		// NestedListQuote -> HTML <blockquote> doesn't use <li>...</li>
		v.writeQuotationList(ln)
		return
	}

	code, ok := listCode[ln.Code]
	if !ok {
		panic(fmt.Sprintf("Invalid list code %v", ln.Code))
	}

	compact := isCompactList(ln.Items)
	v.b.WriteStrings("<", code)
	v.visitAttributes(ln.Attrs)
	v.b.WriteString(">\n")
	for _, item := range ln.Items {
................................................................................
		if pn := getParaItem(item); pn != nil {
			if inPara {
				v.b.WriteByte('\n')
			} else {
				v.b.WriteString("<p>")
				inPara = true
			}
			v.acceptInlineSlice(pn.Inlines)
		} else {
			if inPara {
				v.writeEndPara()
				inPara = false
			}
			v.acceptItemSlice(item)
		}
	}
	if inPara {
		v.writeEndPara()
	}
	v.b.WriteString("</blockquote>\n")
}
................................................................................

// writeItemSliceOrPara emits the content of a paragraph if the paragraph is
// the only element of the block slice and if compact mode is true. Otherwise,
// the item slice is emitted normally.
func (v *visitor) writeItemSliceOrPara(ins ast.ItemSlice, compact bool) {
	if compact && len(ins) == 1 {
		if para, ok := ins[0].(*ast.ParaNode); ok {
			v.acceptInlineSlice(para.Inlines)
			return
		}
	}
	v.acceptItemSlice(ins)
}

func (v *visitor) writeDescriptionsSlice(ds ast.DescriptionSlice) {
	if len(ds) == 1 {
		if para, ok := ds[0].(*ast.ParaNode); ok {
			v.acceptInlineSlice(para.Inlines)
			return
		}
	}
	for _, dn := range ds {
		dn.Accept(v)
	}
}

// VisitDescriptionList emits a HTML description list.
func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	v.b.WriteString("<dl>\n")
	for _, descr := range dn.Descriptions {
		v.b.WriteString("<dt>")
		v.acceptInlineSlice(descr.Term)
		v.b.WriteString("</dt>\n")

		for _, b := range descr.Descriptions {
			v.b.WriteString("<dd>")
			v.writeDescriptionsSlice(b)
			v.b.WriteString("</dd>\n")
		}
	}
	v.b.WriteString("</dl>\n")
}

// VisitTable emits a HTML table.
func (v *visitor) VisitTable(tn *ast.TableNode) {
	v.b.WriteString("<table>\n")
	if len(tn.Header) > 0 {
		v.b.WriteString("<thead>\n")
		v.writeRow(tn.Header, "<th", "</th>")
		v.b.WriteString("</thead>\n")
	}
	if len(tn.Rows) > 0 {
................................................................................
	v.b.WriteString("<tr>")
	for _, cell := range row {
		v.b.WriteString(cellStart)
		if len(cell.Inlines) == 0 {
			v.b.WriteByte('>')
		} else {
			v.b.WriteString(alignStyle[cell.Align])
			v.acceptInlineSlice(cell.Inlines)
		}
		v.b.WriteString(cellEnd)
	}
	v.b.WriteString("</tr>\n")
}

// VisitBLOB writes the binary object as a value.
func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
	switch bn.Syntax {
	case "gif", "jpeg", "png":
		v.b.WriteStrings("<img src=\"data:image/", bn.Syntax, ";base64,")
		v.b.WriteBase64(bn.Blob)
		v.b.WriteString("\" title=\"")
		v.writeQuotedEscaped(bn.Title)
		v.b.WriteString("\">\n")







<
<
<
<
<
<
<
<
|
|







 







|







 







<
|



|









|








|


|






<
|







 







|



<
<
<
<
<
<
<
<
<
<
<
|




<
|



|





|

|







 







|





|







 







|



|





|



|
<
|
|
<
<
|



|











<
|







 







|






<
|







15
16
17
18
19
20
21








22
23
24
25
26
27
28
29
30
..
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
..
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
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262

263
264


265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280

281
282
283
284
285
286
287
288
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319

320
321
322
323
324
325
326
327
	"fmt"
	"strconv"
	"strings"

	"zettelstore.de/z/ast"
)









func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
	switch vn.Kind {
	case ast.VerbatimProg:
		oldVisible := v.visibleSpace
		if vn.Attrs != nil {
			v.visibleSpace = vn.Attrs.HasDefault()
		}
		v.b.WriteString("<pre><code")
		v.visitAttributes(vn.Attrs)
................................................................................
	case ast.VerbatimHTML:
		for _, line := range vn.Lines {
			if !ignoreHTMLText(line) {
				v.b.WriteStrings(line, "\n")
			}
		}
	default:
		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
	}
}

var htmlSnippetsIgnore = []string{
	"<script",
	"</script",
	"<iframe",
................................................................................
			attrs.Remove("")
			attrs = attrs.AddClass("zs-indication").AddClass("zs-" + attrVal)
		}
	}
	return attrs
}


func (v *visitor) visitRegion(rn *ast.RegionNode) {
	var code string
	attrs := rn.Attrs
	oldVerse := v.inVerse
	switch rn.Kind {
	case ast.RegionSpan:
		code = "div"
		attrs = processSpanAttributes(attrs)
	case ast.RegionVerse:
		v.inVerse = true
		code = "div"
	case ast.RegionQuote:
		code = "blockquote"
	default:
		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
	}

	v.lang.push(attrs)
	defer v.lang.pop()

	v.b.WriteStrings("<", code)
	v.visitAttributes(attrs)
	v.b.WriteString(">\n")
	ast.WalkBlockSlice(v, rn.Blocks)
	if len(rn.Inlines) > 0 {
		v.b.WriteString("<cite>")
		ast.WalkInlineSlice(v, rn.Inlines)
		v.b.WriteString("</cite>\n")
	}
	v.b.WriteStrings("</", code, ">\n")
	v.inVerse = oldVerse
}


func (v *visitor) visitHeading(hn *ast.HeadingNode) {
	v.lang.push(hn.Attrs)
	defer v.lang.pop()

	lvl := hn.Level
	if lvl > 6 {
		lvl = 6 // HTML has H1..H6
	}
................................................................................
	strLvl := strconv.Itoa(lvl)
	v.b.WriteStrings("<h", strLvl)
	v.visitAttributes(hn.Attrs)
	if slug := hn.Slug; len(slug) > 0 {
		v.b.WriteStrings(" id=\"", slug, "\"")
	}
	v.b.WriteByte('>')
	ast.WalkInlineSlice(v, hn.Inlines)
	v.b.WriteStrings("</h", strLvl, ">\n")
}












var mapNestedListKind = map[ast.NestedListKind]string{
	ast.NestedListOrdered:   "ol",
	ast.NestedListUnordered: "ul",
}


func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
	v.lang.push(ln.Attrs)
	defer v.lang.pop()

	if ln.Kind == ast.NestedListQuote {
		// NestedListQuote -> HTML <blockquote> doesn't use <li>...</li>
		v.writeQuotationList(ln)
		return
	}

	code, ok := mapNestedListKind[ln.Kind]
	if !ok {
		panic(fmt.Sprintf("Invalid list kind %v", ln.Kind))
	}

	compact := isCompactList(ln.Items)
	v.b.WriteStrings("<", code)
	v.visitAttributes(ln.Attrs)
	v.b.WriteString(">\n")
	for _, item := range ln.Items {
................................................................................
		if pn := getParaItem(item); pn != nil {
			if inPara {
				v.b.WriteByte('\n')
			} else {
				v.b.WriteString("<p>")
				inPara = true
			}
			ast.WalkInlineSlice(v, pn.Inlines)
		} else {
			if inPara {
				v.writeEndPara()
				inPara = false
			}
			ast.WalkItemSlice(v, item)
		}
	}
	if inPara {
		v.writeEndPara()
	}
	v.b.WriteString("</blockquote>\n")
}
................................................................................

// writeItemSliceOrPara emits the content of a paragraph if the paragraph is
// the only element of the block slice and if compact mode is true. Otherwise,
// the item slice is emitted normally.
func (v *visitor) writeItemSliceOrPara(ins ast.ItemSlice, compact bool) {
	if compact && len(ins) == 1 {
		if para, ok := ins[0].(*ast.ParaNode); ok {
			ast.WalkInlineSlice(v, para.Inlines)
			return
		}
	}
	ast.WalkItemSlice(v, ins)
}

func (v *visitor) writeDescriptionsSlice(ds ast.DescriptionSlice) {
	if len(ds) == 1 {
		if para, ok := ds[0].(*ast.ParaNode); ok {
			ast.WalkInlineSlice(v, para.Inlines)
			return
		}
	}
	ast.WalkDescriptionSlice(v, ds)

}



func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
	v.b.WriteString("<dl>\n")
	for _, descr := range dn.Descriptions {
		v.b.WriteString("<dt>")
		ast.WalkInlineSlice(v, descr.Term)
		v.b.WriteString("</dt>\n")

		for _, b := range descr.Descriptions {
			v.b.WriteString("<dd>")
			v.writeDescriptionsSlice(b)
			v.b.WriteString("</dd>\n")
		}
	}
	v.b.WriteString("</dl>\n")
}


func (v *visitor) visitTable(tn *ast.TableNode) {
	v.b.WriteString("<table>\n")
	if len(tn.Header) > 0 {
		v.b.WriteString("<thead>\n")
		v.writeRow(tn.Header, "<th", "</th>")
		v.b.WriteString("</thead>\n")
	}
	if len(tn.Rows) > 0 {
................................................................................
	v.b.WriteString("<tr>")
	for _, cell := range row {
		v.b.WriteString(cellStart)
		if len(cell.Inlines) == 0 {
			v.b.WriteByte('>')
		} else {
			v.b.WriteString(alignStyle[cell.Align])
			ast.WalkInlineSlice(v, cell.Inlines)
		}
		v.b.WriteString(cellEnd)
	}
	v.b.WriteString("</tr>\n")
}


func (v *visitor) visitBLOB(bn *ast.BLOBNode) {
	switch bn.Syntax {
	case "gif", "jpeg", "png":
		v.b.WriteStrings("<img src=\"data:image/", bn.Syntax, ";base64,")
		v.b.WriteBase64(bn.Blob)
		v.b.WriteString("\" title=\"")
		v.writeQuotedEscaped(bn.Title)
		v.b.WriteString("\">\n")

Changes to encoder/htmlenc/htmlenc.go.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
	v.b.WriteStrings("<title>", encfun.MetaAsText(zn.InhMeta, meta.KeyTitle), "</title>")
	if inhMeta {
		v.acceptMeta(zn.InhMeta)
	} else {
		v.acceptMeta(zn.Meta)
	}
	v.b.WriteString("\n</head>\n<body>\n")
	v.acceptBlockSlice(zn.Ast)
	v.writeEndnotes()
	v.b.WriteString("</body>\n</html>")
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as HTML5.
................................................................................
func (he *htmlEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return he.WriteBlocks(w, zn.Ast)
}

// WriteBlocks encodes a block slice.
func (he *htmlEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(he, w)
	v.acceptBlockSlice(bs)
	v.writeEndnotes()
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (he *htmlEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(he, w)
	if env := he.env; env != nil {
		v.inInteractive = env.Interactive
	}
	v.acceptInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}







|







 







|











|



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
	v.b.WriteStrings("<title>", encfun.MetaAsText(zn.InhMeta, meta.KeyTitle), "</title>")
	if inhMeta {
		v.acceptMeta(zn.InhMeta)
	} else {
		v.acceptMeta(zn.Meta)
	}
	v.b.WriteString("\n</head>\n<body>\n")
	ast.WalkBlockSlice(v, zn.Ast)
	v.writeEndnotes()
	v.b.WriteString("</body>\n</html>")
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as HTML5.
................................................................................
func (he *htmlEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return he.WriteBlocks(w, zn.Ast)
}

// WriteBlocks encodes a block slice.
func (he *htmlEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(he, w)
	ast.WalkBlockSlice(v, bs)
	v.writeEndnotes()
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (he *htmlEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(he, w)
	if env := he.env; env != nil {
		v.inInteractive = env.Interactive
	}
	ast.WalkInlineSlice(v, is)
	length, err := v.b.Flush()
	return length, err
}

Changes to encoder/htmlenc/inline.go.

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
..
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
...
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

124
125
126
127
128
129
130
...
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
...
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
...
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
	"strconv"
	"strings"

	"zettelstore.de/z/ast"
	"zettelstore.de/z/domain/meta"
)

// VisitText writes text content.
func (v *visitor) VisitText(tn *ast.TextNode) {
	v.writeHTMLEscaped(tn.Text)
}

// VisitTag writes tag content.
func (v *visitor) VisitTag(tn *ast.TagNode) {
	// TODO: erst mal als span. Link wäre gut, muss man vermutlich via Callback lösen.
	v.b.WriteString("<span class=\"zettel-tag\">#")
	v.writeHTMLEscaped(tn.Tag)
	v.b.WriteString("</span>")
}

// VisitSpace emits a white space.
func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
	if v.inVerse || v.env.IsXHTML() {
		v.b.WriteString(sn.Lexeme)
	} else {
		v.b.WriteByte(' ')
	}
}

// VisitBreak writes HTML code for line breaks.
func (v *visitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		if v.env.IsXHTML() {
			v.b.WriteString("<br />\n")
		} else {
			v.b.WriteString("<br>\n")
		}
	} else {
		v.b.WriteByte('\n')
	}
}

// VisitLink writes HTML code for links.
func (v *visitor) VisitLink(ln *ast.LinkNode) {
	ln, n := v.env.AdaptLink(ln)
	if n != nil {
		n.Accept(v)

		return
	}
	v.lang.push(ln.Attrs)
	defer v.lang.pop()

	switch ln.Ref.State {
	case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased:
................................................................................
		}
		v.b.WriteString("<a href=\"")
		v.writeQuotedEscaped(ln.Ref.Value)
		v.b.WriteByte('"')
		v.visitAttributes(ln.Attrs)
		v.b.WriteByte('>')
		v.inInteractive = true
		v.acceptInlineSlice(ln.Inlines)
		v.inInteractive = false
		v.b.WriteString("</a>")
	}
}

func (v *visitor) writeAHref(ref *ast.Reference, attrs *ast.Attributes, ins ast.InlineSlice) {
	if v.env.IsInteractive(v.inInteractive) {
................................................................................
	}
	v.b.WriteString("<a href=\"")
	v.writeReference(ref)
	v.b.WriteByte('"')
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	v.inInteractive = true
	v.acceptInlineSlice(ins)
	v.inInteractive = false
	v.b.WriteString("</a>")
}

// VisitImage writes HTML code for images.
func (v *visitor) VisitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {
		n.Accept(v)

		return
	}
	v.lang.push(in.Attrs)
	defer v.lang.pop()

	if in.Ref == nil {
		v.b.WriteString("<img src=\"data:image/")
................................................................................
			v.b.WriteBase64(in.Blob)
		}
	} else {
		v.b.WriteString("<img src=\"")
		v.writeReference(in.Ref)
	}
	v.b.WriteString("\" alt=\"")
	v.acceptInlineSlice(in.Inlines)
	v.b.WriteByte('"')
	v.visitAttributes(in.Attrs)
	if v.env.IsXHTML() {
		v.b.WriteString(" />")
	} else {
		v.b.WriteByte('>')
	}
}

// VisitCite writes code for citations.
func (v *visitor) VisitCite(cn *ast.CiteNode) {
	cn, n := v.env.AdaptCite(cn)
	if n != nil {
		n.Accept(v)

		return
	}
	if cn == nil {
		return
	}
	v.lang.push(cn.Attrs)
	defer v.lang.pop()
	v.b.WriteString(cn.Key)
	if len(cn.Inlines) > 0 {
		v.b.WriteString(", ")
		v.acceptInlineSlice(cn.Inlines)
	}
}

// VisitFootnote write HTML code for a footnote.
func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
	v.lang.push(fn.Attrs)
	defer v.lang.pop()
	if v.env.IsInteractive(v.inInteractive) {
		return
	}

	n := strconv.Itoa(v.env.AddFootnote(fn))
	v.b.WriteStrings("<sup id=\"fnref:", n, "\"><a href=\"#fn:", n, "\" class=\"zs-footnote-ref\" role=\"doc-noteref\">", n, "</a></sup>")
	// TODO: what to do with Attrs?
}

// VisitMark writes HTML code to mark a position.
func (v *visitor) VisitMark(mn *ast.MarkNode) {
	if v.env.IsInteractive(v.inInteractive) {
		return
	}
	if len(mn.Text) > 0 {
		v.b.WriteStrings("<a id=\"", mn.Text, "\"></a>")
	}
}

// VisitFormat write HTML code for formatting text.
func (v *visitor) VisitFormat(fn *ast.FormatNode) {
	v.lang.push(fn.Attrs)
	defer v.lang.pop()

	var code string
	attrs := fn.Attrs
	switch fn.Code {
	case ast.FormatItalic:
		code = "i"
	case ast.FormatEmph:
		code = "em"
	case ast.FormatBold:
		code = "b"
	case ast.FormatStrong:
................................................................................
	case ast.FormatMonospace:
		code = "span"
		attrs = attrs.Set("style", "font-family:monospace")
	case ast.FormatQuote:
		v.visitQuotes(fn)
		return
	default:
		panic(fmt.Sprintf("Unknown format code %v", fn.Code))
	}
	v.b.WriteStrings("<", code)
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteStrings("</", code, ">")
}

func (v *visitor) writeSpan(ins ast.InlineSlice, attrs *ast.Attributes) {
	v.b.WriteString("<span")
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	v.acceptInlineSlice(ins)
	v.b.WriteString("</span>")

}

var langQuotes = map[string][2]string{
	meta.ValueLangEN: {"&ldquo;", "&rdquo;"},
	"de":             {"&bdquo;", "&ldquo;"},
................................................................................
	if withSpan {
		v.b.WriteString("<span")
		v.visitAttributes(fn.Attrs)
		v.b.WriteByte('>')
	}
	openingQ, closingQ := getQuotes(v.lang.top())
	v.b.WriteString(openingQ)
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteString(closingQ)
	if withSpan {
		v.b.WriteString("</span>")
	}
}

// VisitLiteral write HTML code for literal inline text.
func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
	switch ln.Code {
	case ast.LiteralProg:
		v.writeLiteral("<code", "</code>", ln.Attrs, ln.Text)
	case ast.LiteralKeyb:
		v.writeLiteral("<kbd", "</kbd>", ln.Attrs, ln.Text)
	case ast.LiteralOutput:
		v.writeLiteral("<samp", "</samp>", ln.Attrs, ln.Text)
	case ast.LiteralComment:
................................................................................
		v.writeHTMLEscaped(ln.Text) // writeCommentEscaped
		v.b.WriteString(" -->")
	case ast.LiteralHTML:
		if !ignoreHTMLText(ln.Text) {
			v.b.WriteString(ln.Text)
		}
	default:
		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
	}
}

func (v *visitor) writeLiteral(codeS, codeE string, attrs *ast.Attributes, text string) {
	oldVisible := v.visibleSpace
	if attrs != nil {
		v.visibleSpace = attrs.HasDefault()







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|











<
|


<
>







 







|







 







|




<
|


<
>







 







|









<
|


<
>










|



<
|











<
|








<
|





|







 







|




|







|







 







|






<
|
|







 







|







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
..
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
..
83
84
85
86
87
88
89
90
91
92
93
94

95
96
97

98
99
100
101
102
103
104
105
...
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
...
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
...
248
249
250
251
252
253
254
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269
270
...
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
	"strconv"
	"strings"

	"zettelstore.de/z/ast"
	"zettelstore.de/z/domain/meta"
)
























func (v *visitor) visitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		if v.env.IsXHTML() {
			v.b.WriteString("<br />\n")
		} else {
			v.b.WriteString("<br>\n")
		}
	} else {
		v.b.WriteByte('\n')
	}
}


func (v *visitor) visitLink(ln *ast.LinkNode) {
	ln, n := v.env.AdaptLink(ln)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	v.lang.push(ln.Attrs)
	defer v.lang.pop()

	switch ln.Ref.State {
	case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased:
................................................................................
		}
		v.b.WriteString("<a href=\"")
		v.writeQuotedEscaped(ln.Ref.Value)
		v.b.WriteByte('"')
		v.visitAttributes(ln.Attrs)
		v.b.WriteByte('>')
		v.inInteractive = true
		ast.WalkInlineSlice(v, ln.Inlines)
		v.inInteractive = false
		v.b.WriteString("</a>")
	}
}

func (v *visitor) writeAHref(ref *ast.Reference, attrs *ast.Attributes, ins ast.InlineSlice) {
	if v.env.IsInteractive(v.inInteractive) {
................................................................................
	}
	v.b.WriteString("<a href=\"")
	v.writeReference(ref)
	v.b.WriteByte('"')
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	v.inInteractive = true
	ast.WalkInlineSlice(v, ins)
	v.inInteractive = false
	v.b.WriteString("</a>")
}


func (v *visitor) visitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	v.lang.push(in.Attrs)
	defer v.lang.pop()

	if in.Ref == nil {
		v.b.WriteString("<img src=\"data:image/")
................................................................................
			v.b.WriteBase64(in.Blob)
		}
	} else {
		v.b.WriteString("<img src=\"")
		v.writeReference(in.Ref)
	}
	v.b.WriteString("\" alt=\"")
	ast.WalkInlineSlice(v, in.Inlines)
	v.b.WriteByte('"')
	v.visitAttributes(in.Attrs)
	if v.env.IsXHTML() {
		v.b.WriteString(" />")
	} else {
		v.b.WriteByte('>')
	}
}


func (v *visitor) visitCite(cn *ast.CiteNode) {
	cn, n := v.env.AdaptCite(cn)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	if cn == nil {
		return
	}
	v.lang.push(cn.Attrs)
	defer v.lang.pop()
	v.b.WriteString(cn.Key)
	if len(cn.Inlines) > 0 {
		v.b.WriteString(", ")
		ast.WalkInlineSlice(v, cn.Inlines)
	}
}


func (v *visitor) visitFootnote(fn *ast.FootnoteNode) {
	v.lang.push(fn.Attrs)
	defer v.lang.pop()
	if v.env.IsInteractive(v.inInteractive) {
		return
	}

	n := strconv.Itoa(v.env.AddFootnote(fn))
	v.b.WriteStrings("<sup id=\"fnref:", n, "\"><a href=\"#fn:", n, "\" class=\"zs-footnote-ref\" role=\"doc-noteref\">", n, "</a></sup>")
	// TODO: what to do with Attrs?
}


func (v *visitor) visitMark(mn *ast.MarkNode) {
	if v.env.IsInteractive(v.inInteractive) {
		return
	}
	if len(mn.Text) > 0 {
		v.b.WriteStrings("<a id=\"", mn.Text, "\"></a>")
	}
}


func (v *visitor) visitFormat(fn *ast.FormatNode) {
	v.lang.push(fn.Attrs)
	defer v.lang.pop()

	var code string
	attrs := fn.Attrs
	switch fn.Kind {
	case ast.FormatItalic:
		code = "i"
	case ast.FormatEmph:
		code = "em"
	case ast.FormatBold:
		code = "b"
	case ast.FormatStrong:
................................................................................
	case ast.FormatMonospace:
		code = "span"
		attrs = attrs.Set("style", "font-family:monospace")
	case ast.FormatQuote:
		v.visitQuotes(fn)
		return
	default:
		panic(fmt.Sprintf("Unknown format kind %v", fn.Kind))
	}
	v.b.WriteStrings("<", code)
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	ast.WalkInlineSlice(v, fn.Inlines)
	v.b.WriteStrings("</", code, ">")
}

func (v *visitor) writeSpan(ins ast.InlineSlice, attrs *ast.Attributes) {
	v.b.WriteString("<span")
	v.visitAttributes(attrs)
	v.b.WriteByte('>')
	ast.WalkInlineSlice(v, ins)
	v.b.WriteString("</span>")

}

var langQuotes = map[string][2]string{
	meta.ValueLangEN: {"&ldquo;", "&rdquo;"},
	"de":             {"&bdquo;", "&ldquo;"},
................................................................................
	if withSpan {
		v.b.WriteString("<span")
		v.visitAttributes(fn.Attrs)
		v.b.WriteByte('>')
	}
	openingQ, closingQ := getQuotes(v.lang.top())
	v.b.WriteString(openingQ)
	ast.WalkInlineSlice(v, fn.Inlines)
	v.b.WriteString(closingQ)
	if withSpan {
		v.b.WriteString("</span>")
	}
}


func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
	switch ln.Kind {
	case ast.LiteralProg:
		v.writeLiteral("<code", "</code>", ln.Attrs, ln.Text)
	case ast.LiteralKeyb:
		v.writeLiteral("<kbd", "</kbd>", ln.Attrs, ln.Text)
	case ast.LiteralOutput:
		v.writeLiteral("<samp", "</samp>", ln.Attrs, ln.Text)
	case ast.LiteralComment:
................................................................................
		v.writeHTMLEscaped(ln.Text) // writeCommentEscaped
		v.b.WriteString(" -->")
	case ast.LiteralHTML:
		if !ignoreHTMLText(ln.Text) {
			v.b.WriteString(ln.Text)
		}
	default:
		panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind))
	}
}

func (v *visitor) writeLiteral(codeS, codeE string, attrs *ast.Attributes, text string) {
	oldVisible := v.visibleSpace
	if attrs != nil {
		v.visibleSpace = attrs.HasDefault()

Changes to encoder/htmlenc/visitor.go.

40
41
42
43
44
45
46































































47
48
49
50
51
52
53
..
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
	}
	return &visitor{
		env:  he.env,
		b:    encoder.NewBufWriter(w),
		lang: newLangStack(lang),
	}
}
































































var mapMetaKey = map[string]string{
	meta.KeyCopyright: "copyright",
	meta.KeyLicense:   "license",
}

func (v *visitor) acceptMeta(m *meta.Meta) {
................................................................................

func (v *visitor) writeMeta(prefix, key, value string) {
	v.b.WriteStrings("\n<meta name=\"", prefix, key, "\" content=\"")
	v.writeQuotedEscaped(value)
	v.b.WriteString("\">")
}

func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
	for _, bn := range bns {
		bn.Accept(v)
	}
}
func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
	for _, in := range ins {
		in.Accept(v)
	}
}
func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
	for _, in := range ins {
		in.Accept(v)
	}
}

func (v *visitor) writeEndnotes() {
	footnotes := v.env.GetCleanFootnotes()
	if len(footnotes) > 0 {
		v.b.WriteString("<ol class=\"zs-endnotes\">\n")
		for i := 0; i < len(footnotes); i++ {
			// Do not use a range loop above, because a footnote may contain
			// a footnote. Therefore v.enc.footnote may grow during the loop.
			fn := footnotes[i]
			n := strconv.Itoa(i + 1)
			v.b.WriteStrings("<li id=\"fn:", n, "\" role=\"doc-endnote\">")
			v.acceptInlineSlice(fn.Inlines)
			v.b.WriteStrings(
				" <a href=\"#fnref:",
				n,
				"\" class=\"zs-footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></li>\n")
		}
		v.b.WriteString("</ol>\n")
	}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










|







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
...
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
	}
	return &visitor{
		env:  he.env,
		b:    encoder.NewBufWriter(w),
		lang: newLangStack(lang),
	}
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		v.b.WriteString("<p>")
		ast.WalkInlineSlice(v, n.Inlines)
		v.writeEndPara()
	case *ast.VerbatimNode:
		v.visitVerbatim(n)
	case *ast.RegionNode:
		v.visitRegion(n)
	case *ast.HeadingNode:
		v.visitHeading(n)
	case *ast.HRuleNode:
		v.b.WriteString("<hr")
		v.visitAttributes(n.Attrs)
		if v.env.IsXHTML() {
			v.b.WriteString(" />\n")
		} else {
			v.b.WriteString(">\n")
		}
	case *ast.NestedListNode:
		v.visitNestedList(n)
	case *ast.DescriptionListNode:
		v.visitDescriptionList(n)
	case *ast.TableNode:
		v.visitTable(n)
	case *ast.BLOBNode:
		v.visitBLOB(n)
	case *ast.TextNode:
		v.writeHTMLEscaped(n.Text)
	case *ast.TagNode:
		// TODO: erst mal als span. Link wäre gut, muss man vermutlich via Callback lösen.
		v.b.WriteString("<span class=\"zettel-tag\">#")
		v.writeHTMLEscaped(n.Tag)
		v.b.WriteString("</span>")
	case *ast.SpaceNode:
		if v.inVerse || v.env.IsXHTML() {
			v.b.WriteString(n.Lexeme)
		} else {
			v.b.WriteByte(' ')
		}
	case *ast.BreakNode:
		v.visitBreak(n)
	case *ast.LinkNode:
		v.visitLink(n)
	case *ast.ImageNode:
		v.visitImage(n)
	case *ast.CiteNode:
		v.visitCite(n)
	case *ast.FootnoteNode:
		v.visitFootnote(n)
	case *ast.MarkNode:
		v.visitMark(n)
	case *ast.FormatNode:
		v.visitFormat(n)
	case *ast.LiteralNode:
		v.visitLiteral(n)
	default:
		return v
	}
	return nil
}

var mapMetaKey = map[string]string{
	meta.KeyCopyright: "copyright",
	meta.KeyLicense:   "license",
}

func (v *visitor) acceptMeta(m *meta.Meta) {
................................................................................

func (v *visitor) writeMeta(prefix, key, value string) {
	v.b.WriteStrings("\n<meta name=\"", prefix, key, "\" content=\"")
	v.writeQuotedEscaped(value)
	v.b.WriteString("\">")
}

















func (v *visitor) writeEndnotes() {
	footnotes := v.env.GetCleanFootnotes()
	if len(footnotes) > 0 {
		v.b.WriteString("<ol class=\"zs-endnotes\">\n")
		for i := 0; i < len(footnotes); i++ {
			// Do not use a range loop above, because a footnote may contain
			// a footnote. Therefore v.enc.footnote may grow during the loop.
			fn := footnotes[i]
			n := strconv.Itoa(i + 1)
			v.b.WriteStrings("<li id=\"fn:", n, "\" role=\"doc-endnote\">")
			ast.WalkInlineSlice(v, fn.Inlines)
			v.b.WriteStrings(
				" <a href=\"#fnref:",
				n,
				"\" class=\"zs-footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></li>\n")
		}
		v.b.WriteString("</ol>\n")
	}

Changes to encoder/jsonenc/djsonenc.go.

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
...
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
...
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
	env *encoder.Environment
}

// WriteZettel writes the encoded zettel to the writer.
func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newDetailVisitor(w, je)
	v.b.WriteString("{\"meta\":{\"title\":")
	v.acceptInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
	if inhMeta {
		v.writeMeta(zn.InhMeta)
	} else {
		v.writeMeta(zn.Meta)
	}
	v.b.WriteByte('}')
	v.b.WriteString(",\"content\":")
	v.acceptBlockSlice(zn.Ast)
	v.b.WriteByte('}')
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as JSON.
func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	v := newDetailVisitor(w, je)
	v.b.WriteString("{\"title\":")
	v.acceptInlineSlice(encfun.MetaAsInlineSlice(m, meta.KeyTitle))
	v.writeMeta(m)
	v.b.WriteByte('}')
	length, err := v.b.Flush()
	return length, err
}

func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return je.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes a block slice to the writer
func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newDetailVisitor(w, je)
	v.acceptBlockSlice(bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (je *jsonDetailEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newDetailVisitor(w, je)
	v.acceptInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// detailVisitor writes the abstract syntax tree to an io.Writer.
type detailVisitor struct {
	b   encoder.BufWriter
................................................................................
	env *encoder.Environment
}

func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *detailVisitor {
	return &detailVisitor{b: encoder.NewBufWriter(w), env: je.env}
}

// VisitPara emits JSON code for a paragraph.
func (v *detailVisitor) VisitPara(pn *ast.ParaNode) {


	v.writeNodeStart("Para")
	v.writeContentStart('i')
	v.acceptInlineSlice(pn.Inlines)

































































































	v.b.WriteByte('}')

}

var verbatimCode = map[ast.VerbatimCode]string{
	ast.VerbatimProg:    "CodeBlock",
	ast.VerbatimComment: "CommentBlock",
	ast.VerbatimHTML:    "HTMLBlock",
}

// VisitVerbatim emits JSON code for verbatim lines.
func (v *detailVisitor) VisitVerbatim(vn *ast.VerbatimNode) {
	code, ok := verbatimCode[vn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
	}
	v.writeNodeStart(code)
	v.visitAttributes(vn.Attrs)
	v.writeContentStart('l')
	for i, line := range vn.Lines {
		if i > 0 {
			v.b.WriteByte(',')
		}
		writeEscaped(&v.b, line)
	}
	v.b.WriteString("]}")
}

var regionCode = map[ast.RegionCode]string{
	ast.RegionSpan:  "SpanBlock",
	ast.RegionQuote: "QuoteBlock",
	ast.RegionVerse: "VerseBlock",
}

// VisitRegion writes JSON code for block regions.
func (v *detailVisitor) VisitRegion(rn *ast.RegionNode) {
	code, ok := regionCode[rn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
	}
	v.writeNodeStart(code)
	v.visitAttributes(rn.Attrs)
	v.writeContentStart('b')
	v.acceptBlockSlice(rn.Blocks)
	if len(rn.Inlines) > 0 {
		v.writeContentStart('i')
		v.acceptInlineSlice(rn.Inlines)
	}
	v.b.WriteByte('}')
}

// VisitHeading writes the JSON code for a heading.
func (v *detailVisitor) VisitHeading(hn *ast.HeadingNode) {
	v.writeNodeStart("Heading")
	v.visitAttributes(hn.Attrs)
	v.writeContentStart('n')
	v.b.WriteString(strconv.Itoa(hn.Level))
	if slug := hn.Slug; len(slug) > 0 {
		v.writeContentStart('s')
		v.b.WriteStrings("\"", slug, "\"")
	}
	v.writeContentStart('i')
	v.acceptInlineSlice(hn.Inlines)
	v.b.WriteByte('}')
}

// VisitHRule writes JSON code for a horizontal rule: <hr>.
func (v *detailVisitor) VisitHRule(hn *ast.HRuleNode) {
	v.writeNodeStart("Hrule")
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte('}')
}

var listCode = map[ast.NestedListCode]string{
	ast.NestedListOrdered:   "OrderedList",
	ast.NestedListUnordered: "BulletList",
	ast.NestedListQuote:     "QuoteList",
}

// VisitNestedList writes JSON code for lists and blockquotes.
func (v *detailVisitor) VisitNestedList(ln *ast.NestedListNode) {
	v.writeNodeStart(listCode[ln.Code])
	v.writeContentStart('c')
	for i, item := range ln.Items {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.acceptItemSlice(item)



	}


	v.b.WriteString("]}")
}


// VisitDescriptionList emits a JSON description list.

func (v *detailVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	v.writeNodeStart("DescriptionList")
	v.writeContentStart('g')
	for i, def := range dn.Descriptions {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.b.WriteByte('[')
		v.acceptInlineSlice(def.Term)

		if len(def.Descriptions) > 0 {
			for _, b := range def.Descriptions {
				v.b.WriteByte(',')
				v.acceptDescriptionSlice(b)







			}
		}
		v.b.WriteByte(']')
	}
	v.b.WriteString("]}")
}

// VisitTable emits a JSON table.
func (v *detailVisitor) VisitTable(tn *ast.TableNode) {
	v.writeNodeStart("Table")
	v.writeContentStart('p')

	// Table header
	v.b.WriteByte('[')
	for i, cell := range tn.Header {
		if i > 0 {
................................................................................
			if j > 0 {
				v.b.WriteByte(',')
			}
			v.writeCell(cell)
		}
		v.b.WriteByte(']')
	}
	v.b.WriteString("]]}")
}

var alignmentCode = map[ast.Alignment]string{
	ast.AlignDefault: "[\"\",",
	ast.AlignLeft:    "[\"<\",",
	ast.AlignCenter:  "[\":\",",
	ast.AlignRight:   "[\">\",",
}

func (v *detailVisitor) writeCell(cell *ast.TableCell) {
	v.b.WriteString(alignmentCode[cell.Align])
	v.acceptInlineSlice(cell.Inlines)
	v.b.WriteByte(']')
}

// VisitBLOB writes the binary object as a value.
func (v *detailVisitor) VisitBLOB(bn *ast.BLOBNode) {
	v.writeNodeStart("Blob")
	v.writeContentStart('q')
	writeEscaped(&v.b, bn.Title)
	v.writeContentStart('s')
	writeEscaped(&v.b, bn.Syntax)
	v.writeContentStart('o')
	v.b.WriteBase64(bn.Blob)
	v.b.WriteString("\"}")
}

// VisitText writes text content.
func (v *detailVisitor) VisitText(tn *ast.TextNode) {
	v.writeNodeStart("Text")
	v.writeContentStart('s')
	writeEscaped(&v.b, tn.Text)
	v.b.WriteByte('}')
}

// VisitTag writes tag content.
func (v *detailVisitor) VisitTag(tn *ast.TagNode) {
	v.writeNodeStart("Tag")
	v.writeContentStart('s')
	writeEscaped(&v.b, tn.Tag)
	v.b.WriteByte('}')
}

// VisitSpace emits a white space.
func (v *detailVisitor) VisitSpace(sn *ast.SpaceNode) {
	v.writeNodeStart("Space")
	if l := len(sn.Lexeme); l > 1 {
		v.writeContentStart('n')
		v.b.WriteString(strconv.Itoa(l))
	}
	v.b.WriteByte('}')
}

// VisitBreak writes JSON code for line breaks.
func (v *detailVisitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		v.writeNodeStart("Hard")
	} else {
		v.writeNodeStart("Soft")
	}
	v.b.WriteByte('}')
}

var mapRefState = map[ast.RefState]string{
	ast.RefStateInvalid:  "invalid",
	ast.RefStateZettel:   "zettel",
	ast.RefStateSelf:     "self",
	ast.RefStateFound:    "zettel",
	ast.RefStateBroken:   "broken",
	ast.RefStateHosted:   "local",
	ast.RefStateBased:    "based",
	ast.RefStateExternal: "external",
}

// VisitLink writes JSON code for links.
func (v *detailVisitor) VisitLink(ln *ast.LinkNode) {
	ln, n := v.env.AdaptLink(ln)
	if n != nil {
		n.Accept(v)
		return
	}
	v.writeNodeStart("Link")
	v.visitAttributes(ln.Attrs)
	v.writeContentStart('q')
	writeEscaped(&v.b, mapRefState[ln.Ref.State])
	v.writeContentStart('s')
	writeEscaped(&v.b, ln.Ref.String())
	v.writeContentStart('i')
	v.acceptInlineSlice(ln.Inlines)
	v.b.WriteByte('}')
}

// VisitImage writes JSON code for images.
func (v *detailVisitor) VisitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {
		n.Accept(v)

		return
	}
	v.writeNodeStart("Image")
	v.visitAttributes(in.Attrs)
	if in.Ref == nil {
		v.writeContentStart('j')
		v.b.WriteString("\"s\":")
................................................................................
		v.b.WriteByte('}')
	} else {
		v.writeContentStart('s')
		writeEscaped(&v.b, in.Ref.String())
	}
	if len(in.Inlines) > 0 {
		v.writeContentStart('i')
		v.acceptInlineSlice(in.Inlines)
	}
	v.b.WriteByte('}')
}

// VisitCite writes code for citations.
func (v *detailVisitor) VisitCite(cn *ast.CiteNode) {
	v.writeNodeStart("Cite")
	v.visitAttributes(cn.Attrs)
	v.writeContentStart('s')
	writeEscaped(&v.b, cn.Key)
	if len(cn.Inlines) > 0 {
		v.writeContentStart('i')
		v.acceptInlineSlice(cn.Inlines)
	}
	v.b.WriteByte('}')
}

// VisitFootnote write JSON code for a footnote.
func (v *detailVisitor) VisitFootnote(fn *ast.FootnoteNode) {
	v.writeNodeStart("Footnote")
	v.visitAttributes(fn.Attrs)
	v.writeContentStart('i')
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteByte('}')
}

// VisitMark writes JSON code to mark a position.
func (v *detailVisitor) VisitMark(mn *ast.MarkNode) {
	v.writeNodeStart("Mark")
	if len(mn.Text) > 0 {
		v.writeContentStart('s')
		writeEscaped(&v.b, mn.Text)
	}
	v.b.WriteByte('}')
}

var formatCode = map[ast.FormatCode]string{
	ast.FormatItalic:    "Italic",
	ast.FormatEmph:      "Emph",
	ast.FormatBold:      "Bold",
	ast.FormatStrong:    "Strong",
	ast.FormatMonospace: "Mono",
	ast.FormatStrike:    "Strikethrough",
	ast.FormatDelete:    "Delete",
................................................................................
	ast.FormatSub:       "Sub",
	ast.FormatQuote:     "Quote",
	ast.FormatQuotation: "Quotation",
	ast.FormatSmall:     "Small",
	ast.FormatSpan:      "Span",
}

// VisitFormat write JSON code for formatting text.
func (v *detailVisitor) VisitFormat(fn *ast.FormatNode) {
	v.writeNodeStart(formatCode[fn.Code])
	v.visitAttributes(fn.Attrs)
	v.writeContentStart('i')
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteByte('}')
}

var literalCode = map[ast.LiteralCode]string{
	ast.LiteralProg:    "Code",
	ast.LiteralKeyb:    "Input",
	ast.LiteralOutput:  "Output",
	ast.LiteralComment: "Comment",
	ast.LiteralHTML:    "HTML",
}

// VisitLiteral write JSON code for literal inline text.
func (v *detailVisitor) VisitLiteral(ln *ast.LiteralNode) {
	code, ok := literalCode[ln.Code]
	if !ok {
		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
	}
	v.writeNodeStart(code)
	v.visitAttributes(ln.Attrs)
	v.writeContentStart('s')
	writeEscaped(&v.b, ln.Text)
	v.b.WriteByte('}')
}

func (v *detailVisitor) acceptBlockSlice(bns ast.BlockSlice) {
	v.b.WriteByte('[')
	for i, bn := range bns {
		if i > 0 {
			v.b.WriteByte(',')
		}
		bn.Accept(v)
	}
	v.b.WriteByte(']')
}

func (v *detailVisitor) acceptItemSlice(ins ast.ItemSlice) {
	v.b.WriteByte('[')
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
		}
		in.Accept(v)
	}
	v.b.WriteByte(']')
}

func (v *detailVisitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
	v.b.WriteByte('[')
	for i, dn := range dns {
		if i > 0 {
			v.b.WriteByte(',')
		}
		dn.Accept(v)
	}
	v.b.WriteByte(']')
}

func (v *detailVisitor) acceptInlineSlice(ins ast.InlineSlice) {
	v.b.WriteByte('[')
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
		}
		in.Accept(v)
	}
	v.b.WriteByte(']')
}

// visitAttributes write JSON attributes
func (v *detailVisitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {







|







|









|













|







|







 







<
|
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

>


|





<
|
<
>

|

|








|


|





<
|
<
>

|

|


|


|

<


<
|









|
<


<
<
<
<
<
<
<
|





<
|
|





|
>
>
>
|
>
>
|
|
>
|
<
>
|







|




|
>
>
>
>
>
>
>




|


<
|







 







|











|



<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|


<
>







 







|

<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|







 







<
<
<
<
<
<
<
<
<
|







<
<
<
<
<
<
<
<
<
<
<
<
<
|





|




|





|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







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
251
252
253
254
255
256

257
258







259
260
261
262
263
264

265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319
...
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
















































357
358
359
360
361
362
363
364
365
366
367



















368
369
370

371
372
373
374
375
376
377
378
...
389
390
391
392
393
394
395
396
397

398
399
































400
401
402
403
404
405
406
407
...
411
412
413
414
415
416
417









418
419
420
421
422
423
424
425













426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443






















444
445
446
447
448
449
450
	env *encoder.Environment
}

// WriteZettel writes the encoded zettel to the writer.
func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newDetailVisitor(w, je)
	v.b.WriteString("{\"meta\":{\"title\":")
	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
	if inhMeta {
		v.writeMeta(zn.InhMeta)
	} else {
		v.writeMeta(zn.Meta)
	}
	v.b.WriteByte('}')
	v.b.WriteString(",\"content\":")
	v.walkBlockSlice(zn.Ast)
	v.b.WriteByte('}')
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as JSON.
func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	v := newDetailVisitor(w, je)
	v.b.WriteString("{\"title\":")
	v.walkInlineSlice(encfun.MetaAsInlineSlice(m, meta.KeyTitle))
	v.writeMeta(m)
	v.b.WriteByte('}')
	length, err := v.b.Flush()
	return length, err
}

func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return je.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes a block slice to the writer
func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newDetailVisitor(w, je)
	v.walkBlockSlice(bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (je *jsonDetailEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newDetailVisitor(w, je)
	v.walkInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// detailVisitor writes the abstract syntax tree to an io.Writer.
type detailVisitor struct {
	b   encoder.BufWriter
................................................................................
	env *encoder.Environment
}

func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *detailVisitor {
	return &detailVisitor{b: encoder.NewBufWriter(w), env: je.env}
}


func (v *detailVisitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		v.writeNodeStart("Para")
		v.writeContentStart('i')
		v.walkInlineSlice(n.Inlines)
	case *ast.VerbatimNode:
		v.visitVerbatim(n)
	case *ast.RegionNode:
		v.visitRegion(n)
	case *ast.HeadingNode:
		v.visitHeading(n)
	case *ast.HRuleNode:
		v.writeNodeStart("Hrule")
		v.visitAttributes(n.Attrs)
	case *ast.NestedListNode:
		v.visitNestedList(n)
	case *ast.DescriptionListNode:
		v.visitDescriptionList(n)
	case *ast.TableNode:
		v.visitTable(n)
	case *ast.BLOBNode:
		v.writeNodeStart("Blob")
		v.writeContentStart('q')
		writeEscaped(&v.b, n.Title)
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Syntax)
		v.writeContentStart('o')
		v.b.WriteBase64(n.Blob)
		v.b.WriteByte('"')
	case *ast.TextNode:
		v.writeNodeStart("Text")
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Text)
	case *ast.TagNode:
		v.writeNodeStart("Tag")
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Tag)
	case *ast.SpaceNode:
		v.writeNodeStart("Space")
		if l := len(n.Lexeme); l > 1 {
			v.writeContentStart('n')
			v.b.WriteString(strconv.Itoa(l))
		}
	case *ast.BreakNode:
		if n.Hard {
			v.writeNodeStart("Hard")
		} else {
			v.writeNodeStart("Soft")
		}
	case *ast.LinkNode:
		n, n2 := v.env.AdaptLink(n)
		if n2 != nil {
			ast.Walk(v, n2)
			return nil
		}
		v.writeNodeStart("Link")
		v.visitAttributes(n.Attrs)
		v.writeContentStart('q')
		writeEscaped(&v.b, mapRefState[n.Ref.State])
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Ref.String())
		v.writeContentStart('i')
		v.walkInlineSlice(n.Inlines)
	case *ast.ImageNode:
		v.visitImage(n)
	case *ast.CiteNode:
		v.writeNodeStart("Cite")
		v.visitAttributes(n.Attrs)
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Key)
		if len(n.Inlines) > 0 {
			v.writeContentStart('i')
			v.walkInlineSlice(n.Inlines)
		}
	case *ast.FootnoteNode:
		v.writeNodeStart("Footnote")
		v.visitAttributes(n.Attrs)
		v.writeContentStart('i')
		v.walkInlineSlice(n.Inlines)
	case *ast.MarkNode:
		v.writeNodeStart("Mark")
		if len(n.Text) > 0 {
			v.writeContentStart('s')
			writeEscaped(&v.b, n.Text)
		}
	case *ast.FormatNode:
		v.writeNodeStart(mapFormatKind[n.Kind])
		v.visitAttributes(n.Attrs)
		v.writeContentStart('i')
		v.walkInlineSlice(n.Inlines)
	case *ast.LiteralNode:
		kind, ok := mapLiteralKind[n.Kind]
		if !ok {
			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
		}
		v.writeNodeStart(kind)
		v.visitAttributes(n.Attrs)
		v.writeContentStart('s')
		writeEscaped(&v.b, n.Text)
	default:
		return v
	}
	v.b.WriteByte('}')
	return nil
}

var mapVerbatimKind = map[ast.VerbatimKind]string{
	ast.VerbatimProg:    "CodeBlock",
	ast.VerbatimComment: "CommentBlock",
	ast.VerbatimHTML:    "HTMLBlock",
}


func (v *detailVisitor) visitVerbatim(vn *ast.VerbatimNode) {

	kind, ok := mapVerbatimKind[vn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
	}
	v.writeNodeStart(kind)
	v.visitAttributes(vn.Attrs)
	v.writeContentStart('l')
	for i, line := range vn.Lines {
		if i > 0 {
			v.b.WriteByte(',')
		}
		writeEscaped(&v.b, line)
	}
	v.b.WriteByte(']')
}

var mapRegionKind = map[ast.RegionKind]string{
	ast.RegionSpan:  "SpanBlock",
	ast.RegionQuote: "QuoteBlock",
	ast.RegionVerse: "VerseBlock",
}


func (v *detailVisitor) visitRegion(rn *ast.RegionNode) {

	kind, ok := mapRegionKind[rn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
	}
	v.writeNodeStart(kind)
	v.visitAttributes(rn.Attrs)
	v.writeContentStart('b')
	v.walkBlockSlice(rn.Blocks)
	if len(rn.Inlines) > 0 {
		v.writeContentStart('i')
		v.walkInlineSlice(rn.Inlines)
	}

}


func (v *detailVisitor) visitHeading(hn *ast.HeadingNode) {
	v.writeNodeStart("Heading")
	v.visitAttributes(hn.Attrs)
	v.writeContentStart('n')
	v.b.WriteString(strconv.Itoa(hn.Level))
	if slug := hn.Slug; len(slug) > 0 {
		v.writeContentStart('s')
		v.b.WriteStrings("\"", slug, "\"")
	}
	v.writeContentStart('i')
	v.walkInlineSlice(hn.Inlines)

}








var mapNestedListKind = map[ast.NestedListKind]string{
	ast.NestedListOrdered:   "OrderedList",
	ast.NestedListUnordered: "BulletList",
	ast.NestedListQuote:     "QuoteList",
}


func (v *detailVisitor) visitNestedList(ln *ast.NestedListNode) {
	v.writeNodeStart(mapNestedListKind[ln.Kind])
	v.writeContentStart('c')
	for i, item := range ln.Items {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.b.WriteByte('[')
		for j, in := range item {
			if j > 0 {
				v.b.WriteByte(',')
			}
			ast.Walk(v, in)
		}
		v.b.WriteByte(']')
	}
	v.b.WriteByte(']')
}


func (v *detailVisitor) visitDescriptionList(dn *ast.DescriptionListNode) {
	v.writeNodeStart("DescriptionList")
	v.writeContentStart('g')
	for i, def := range dn.Descriptions {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.b.WriteByte('[')
		v.walkInlineSlice(def.Term)

		if len(def.Descriptions) > 0 {
			for _, b := range def.Descriptions {
				v.b.WriteByte(',')
				v.b.WriteByte('[')
				for j, dn := range b {
					if j > 0 {
						v.b.WriteByte(',')
					}
					ast.Walk(v, dn)
				}
				v.b.WriteByte(']')
			}
		}
		v.b.WriteByte(']')
	}
	v.b.WriteByte(']')
}


func (v *detailVisitor) visitTable(tn *ast.TableNode) {
	v.writeNodeStart("Table")
	v.writeContentStart('p')

	// Table header
	v.b.WriteByte('[')
	for i, cell := range tn.Header {
		if i > 0 {
................................................................................
			if j > 0 {
				v.b.WriteByte(',')
			}
			v.writeCell(cell)
		}
		v.b.WriteByte(']')
	}
	v.b.WriteString("]]")
}

var alignmentCode = map[ast.Alignment]string{
	ast.AlignDefault: "[\"\",",
	ast.AlignLeft:    "[\"<\",",
	ast.AlignCenter:  "[\":\",",
	ast.AlignRight:   "[\">\",",
}

func (v *detailVisitor) writeCell(cell *ast.TableCell) {
	v.b.WriteString(alignmentCode[cell.Align])
	v.walkInlineSlice(cell.Inlines)
	v.b.WriteByte(']')
}

















































var mapRefState = map[ast.RefState]string{
	ast.RefStateInvalid:  "invalid",
	ast.RefStateZettel:   "zettel",
	ast.RefStateSelf:     "self",
	ast.RefStateFound:    "zettel",
	ast.RefStateBroken:   "broken",
	ast.RefStateHosted:   "local",
	ast.RefStateBased:    "based",
	ast.RefStateExternal: "external",
}




















func (v *detailVisitor) visitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	v.writeNodeStart("Image")
	v.visitAttributes(in.Attrs)
	if in.Ref == nil {
		v.writeContentStart('j')
		v.b.WriteString("\"s\":")
................................................................................
		v.b.WriteByte('}')
	} else {
		v.writeContentStart('s')
		writeEscaped(&v.b, in.Ref.String())
	}
	if len(in.Inlines) > 0 {
		v.writeContentStart('i')
		v.walkInlineSlice(in.Inlines)
	}

}

































var mapFormatKind = map[ast.FormatKind]string{
	ast.FormatItalic:    "Italic",
	ast.FormatEmph:      "Emph",
	ast.FormatBold:      "Bold",
	ast.FormatStrong:    "Strong",
	ast.FormatMonospace: "Mono",
	ast.FormatStrike:    "Strikethrough",
	ast.FormatDelete:    "Delete",
................................................................................
	ast.FormatSub:       "Sub",
	ast.FormatQuote:     "Quote",
	ast.FormatQuotation: "Quotation",
	ast.FormatSmall:     "Small",
	ast.FormatSpan:      "Span",
}










var mapLiteralKind = map[ast.LiteralKind]string{
	ast.LiteralProg:    "Code",
	ast.LiteralKeyb:    "Input",
	ast.LiteralOutput:  "Output",
	ast.LiteralComment: "Comment",
	ast.LiteralHTML:    "HTML",
}














func (v *detailVisitor) walkBlockSlice(bns ast.BlockSlice) {
	v.b.WriteByte('[')
	for i, bn := range bns {
		if i > 0 {
			v.b.WriteByte(',')
		}
		ast.Walk(v, bn)
	}
	v.b.WriteByte(']')
}

func (v *detailVisitor) walkInlineSlice(ins ast.InlineSlice) {
	v.b.WriteByte('[')
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
		}
		ast.Walk(v, in)






















	}
	v.b.WriteByte(']')
}

// visitAttributes write JSON attributes
func (v *detailVisitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {

Changes to encoder/nativeenc/nativeenc.go.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
..
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
...
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271






272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
...
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

411
412
413
414
415
416
417
...
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
...
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
}

// WriteZettel encodes the zettel to the writer.
func (ne *nativeEncoder) WriteZettel(
	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newVisitor(w, ne)
	v.b.WriteString("[Title ")
	v.acceptInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
	v.b.WriteByte(']')
	if inhMeta {
		v.acceptMeta(zn.InhMeta, false)
	} else {
		v.acceptMeta(zn.Meta, false)
	}
	v.b.WriteByte('\n')
	v.acceptBlockSlice(zn.Ast)
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data in native format.
func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	v := newVisitor(w, ne)
................................................................................
func (ne *nativeEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return ne.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes a block slice to the writer
func (ne *nativeEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(w, ne)
	v.acceptBlockSlice(bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (ne *nativeEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w, ne)
	v.acceptInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b     encoder.BufWriter
................................................................................
	level int
	env   *encoder.Environment
}

func newVisitor(w io.Writer, enc *nativeEncoder) *visitor {
	return &visitor{b: encoder.NewBufWriter(w), env: enc.env}
}









































































































var (
	rawBackslash   = []byte{'\\', '\\'}
	rawDoubleQuote = []byte{'\\', '"'}
	rawNewline     = []byte{'\\', 'n'}
)

func (v *visitor) acceptMeta(m *meta.Meta, withTitle bool) {
	if withTitle {
		v.b.WriteString("[Title ")
		v.acceptInlineSlice(parser.ParseMetadata(m.GetDefault(meta.KeyTitle, "")))
		v.b.WriteByte(']')
	}
	v.writeMetaString(m, meta.KeyRole, "Role")
	v.writeMetaList(m, meta.KeyTags, "Tags")
	v.writeMetaString(m, meta.KeySyntax, "Syntax")
	pairs := m.PairsRest(true)
	if len(pairs) == 0 {
................................................................................
			v.b.WriteByte(' ')
			v.b.WriteString(val)
		}
		v.b.WriteByte(']')
	}
}

// VisitPara emits native code for a paragraph.
func (v *visitor) VisitPara(pn *ast.ParaNode) {
	v.b.WriteString("[Para ")
	v.acceptInlineSlice(pn.Inlines)
	v.b.WriteByte(']')
}

var verbatimCode = map[ast.VerbatimCode][]byte{
	ast.VerbatimProg:    []byte("[CodeBlock"),
	ast.VerbatimComment: []byte("[CommentBlock"),
	ast.VerbatimHTML:    []byte("[HTMLBlock"),
}

// VisitVerbatim emits native code for verbatim lines.
func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
	code, ok := verbatimCode[vn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
	}
	v.b.Write(code)
	v.visitAttributes(vn.Attrs)
	v.b.WriteString(" \"")
	for i, line := range vn.Lines {
		if i > 0 {
			v.b.Write(rawNewline)
		}
		v.writeEscaped(line)
	}
	v.b.WriteString("\"]")
}

var regionCode = map[ast.RegionCode][]byte{
	ast.RegionSpan:  []byte("[SpanBlock"),
	ast.RegionQuote: []byte("[QuoteBlock"),
	ast.RegionVerse: []byte("[VerseBlock"),
}

// VisitRegion writes native code for block regions.
func (v *visitor) VisitRegion(rn *ast.RegionNode) {
	code, ok := regionCode[rn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
	}
	v.b.Write(code)
	v.visitAttributes(rn.Attrs)
	v.level++
	v.writeNewLine()
	v.b.WriteByte('[')
	v.level++
	v.acceptBlockSlice(rn.Blocks)
	v.level--
	v.b.WriteByte(']')
	if len(rn.Inlines) > 0 {
		v.b.WriteByte(',')
		v.writeNewLine()
		v.b.WriteString("[Cite ")
		v.acceptInlineSlice(rn.Inlines)
		v.b.WriteByte(']')
	}
	v.level--
	v.b.WriteByte(']')
}

// VisitHeading writes the native code for a heading.
func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
	v.b.WriteStrings("[Heading ", strconv.Itoa(hn.Level), " \"", hn.Slug, "\"")
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte(' ')
	v.acceptInlineSlice(hn.Inlines)
	v.b.WriteByte(']')
}

// VisitHRule writes native code for a horizontal rule: <hr>.
func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
	v.b.WriteString("[Hrule")
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte(']')
}

var listCode = map[ast.NestedListCode][]byte{
	ast.NestedListOrdered:   []byte("[OrderedList"),
	ast.NestedListUnordered: []byte("[BulletList"),
	ast.NestedListQuote:     []byte("[QuoteList"),
}

// VisitNestedList writes native code for lists and blockquotes.
func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
	v.b.Write(listCode[ln.Code])
	v.level++
	for i, item := range ln.Items {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.writeNewLine()
		v.level++
		v.b.WriteByte('[')
		v.acceptItemSlice(item)






		v.b.WriteByte(']')
		v.level--
	}
	v.level--
	v.b.WriteByte(']')
}

// VisitDescriptionList emits a native description list.
func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	v.b.WriteString("[DescriptionList")
	v.level++
	for i, descr := range dn.Descriptions {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.writeNewLine()
		v.b.WriteString("[Term [")
		v.acceptInlineSlice(descr.Term)
		v.b.WriteByte(']')

		if len(descr.Descriptions) > 0 {
			v.level++
			for _, b := range descr.Descriptions {
				v.b.WriteByte(',')
				v.writeNewLine()
				v.b.WriteString("[Description")
				v.level++
				v.writeNewLine()
				v.acceptDescriptionSlice(b)






				v.b.WriteByte(']')
				v.level--
			}
			v.level--
		}
		v.b.WriteByte(']')
	}
	v.level--
	v.b.WriteByte(']')
}

// VisitTable emits a native table.
func (v *visitor) VisitTable(tn *ast.TableNode) {
	v.b.WriteString("[Table")
	v.level++
	if len(tn.Header) > 0 {
		v.writeNewLine()
		v.b.WriteString("[Header ")
		for i, cell := range tn.Header {
			if i > 0 {
................................................................................
	ast.AlignRight:   " Right",
}

func (v *visitor) writeCell(cell *ast.TableCell) {
	v.b.WriteStrings("[Cell", alignString[cell.Align])
	if len(cell.Inlines) > 0 {
		v.b.WriteByte(' ')
		v.acceptInlineSlice(cell.Inlines)
	}
	v.b.WriteByte(']')
}

// VisitBLOB writes the binary object as a value.
func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
	v.b.WriteString("[BLOB \"")
	v.writeEscaped(bn.Title)
	v.b.WriteString("\" \"")
	v.writeEscaped(bn.Syntax)
	v.b.WriteString("\" \"")
	v.b.WriteBase64(bn.Blob)
	v.b.WriteString("\"]")
}

// VisitText writes text content.
func (v *visitor) VisitText(tn *ast.TextNode) {
	v.b.WriteString("Text \"")
	v.writeEscaped(tn.Text)
	v.b.WriteByte('"')
}

// VisitTag writes tag content.
func (v *visitor) VisitTag(tn *ast.TagNode) {
	v.b.WriteString("Tag \"")
	v.writeEscaped(tn.Tag)
	v.b.WriteByte('"')
}

// VisitSpace emits a white space.
func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
	v.b.WriteString("Space")
	if l := len(sn.Lexeme); l > 1 {
		v.b.WriteByte(' ')
		v.b.WriteString(strconv.Itoa(l))
	}
}

// VisitBreak writes native code for line breaks.
func (v *visitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		v.b.WriteString("Break")
	} else {
		v.b.WriteString("Space")
	}
}

var mapRefState = map[ast.RefState]string{
	ast.RefStateInvalid:  "INVALID",
	ast.RefStateZettel:   "ZETTEL",
	ast.RefStateSelf:     "SELF",
	ast.RefStateFound:    "ZETTEL",
	ast.RefStateBroken:   "BROKEN",
	ast.RefStateHosted:   "LOCAL",
	ast.RefStateBased:    "BASED",
	ast.RefStateExternal: "EXTERNAL",
}

// VisitLink writes native code for links.
func (v *visitor) VisitLink(ln *ast.LinkNode) {
	ln, n := v.env.AdaptLink(ln)
	if n != nil {
		n.Accept(v)

		return
	}
	v.b.WriteString("Link")
	v.visitAttributes(ln.Attrs)
	v.b.WriteByte(' ')
	v.b.WriteString(mapRefState[ln.Ref.State])
	v.b.WriteString(" \"")
	v.writeEscaped(ln.Ref.String())
	v.b.WriteString("\" [")
	if !ln.OnlyRef {
		v.acceptInlineSlice(ln.Inlines)
	}
	v.b.WriteByte(']')
}

// VisitImage writes native code for images.
func (v *visitor) VisitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {
		n.Accept(v)

		return
	}
	v.b.WriteString("Image")
	v.visitAttributes(in.Attrs)
	if in.Ref == nil {
		v.b.WriteStrings(" {\"", in.Syntax, "\" \"")
		switch in.Syntax {
................................................................................
		}
		v.b.WriteString("\"}")
	} else {
		v.b.WriteStrings(" \"", in.Ref.String(), "\"")
	}
	if len(in.Inlines) > 0 {
		v.b.WriteString(" [")
		v.acceptInlineSlice(in.Inlines)
		v.b.WriteByte(']')
	}
}

// VisitCite writes code for citations.
func (v *visitor) VisitCite(cn *ast.CiteNode) {
	v.b.WriteString("Cite")
	v.visitAttributes(cn.Attrs)
	v.b.WriteString(" \"")
	v.writeEscaped(cn.Key)
	v.b.WriteByte('"')
	if len(cn.Inlines) > 0 {
		v.b.WriteString(" [")
		v.acceptInlineSlice(cn.Inlines)
		v.b.WriteByte(']')
	}
}

// VisitFootnote write native code for a footnote.
func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
	v.b.WriteString("Footnote")
	v.visitAttributes(fn.Attrs)
	v.b.WriteString(" [")
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteByte(']')
}

// VisitMark writes native code to mark a position.
func (v *visitor) VisitMark(mn *ast.MarkNode) {
	v.b.WriteString("Mark")
	if len(mn.Text) > 0 {
		v.b.WriteString(" \"")
		v.writeEscaped(mn.Text)
		v.b.WriteByte('"')
	}
}

var formatCode = map[ast.FormatCode][]byte{
	ast.FormatItalic:    []byte("Italic"),
	ast.FormatEmph:      []byte("Emph"),
	ast.FormatBold:      []byte("Bold"),
	ast.FormatStrong:    []byte("Strong"),
	ast.FormatUnder:     []byte("Underline"),
	ast.FormatInsert:    []byte("Insert"),
	ast.FormatMonospace: []byte("Mono"),
................................................................................
	ast.FormatSub:       []byte("Sub"),
	ast.FormatQuote:     []byte("Quote"),
	ast.FormatQuotation: []byte("Quotation"),
	ast.FormatSmall:     []byte("Small"),
	ast.FormatSpan:      []byte("Span"),
}

// VisitFormat write native code for formatting text.
func (v *visitor) VisitFormat(fn *ast.FormatNode) {
	v.b.Write(formatCode[fn.Code])
	v.visitAttributes(fn.Attrs)
	v.b.WriteString(" [")
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteByte(']')
}

var literalCode = map[ast.LiteralCode][]byte{
	ast.LiteralProg:    []byte("Code"),
	ast.LiteralKeyb:    []byte("Input"),
	ast.LiteralOutput:  []byte("Output"),
	ast.LiteralComment: []byte("Comment"),
	ast.LiteralHTML:    []byte("HTML"),
}

// VisitLiteral write native code for code inline text.
func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
	code, ok := literalCode[ln.Code]
	if !ok {
		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
	}
	v.b.Write(code)
	v.visitAttributes(ln.Attrs)
	v.b.WriteString(" \"")
	v.writeEscaped(ln.Text)
	v.b.WriteByte('"')
}

func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
	for i, bn := range bns {
		if i > 0 {
			v.b.WriteByte(',')
			v.writeNewLine()
		}
		bn.Accept(v)
	}
}
func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
			v.writeNewLine()
		}
		in.Accept(v)
	}
}
func (v *visitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
	for i, dn := range dns {
		if i > 0 {
			v.b.WriteByte(',')
			v.writeNewLine()
		}
		dn.Accept(v)
	}
}
func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
		}
		in.Accept(v)
	}
}

// visitAttributes write native attributes
func (v *visitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return







|







|







 







|







|







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|







 







<
<
<
<
<
<
<
|





<
|
<
>

|

|











|





<
|
<
>

|

|





|






|






<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|





|
|
<








|
>
>
>
>
>
>







<
|








|










|
>
>
>
>
>
>











<
|







 







|




<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<











<
|


<
>










|




<
|


<
>







 







|




<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|







 







<
<
<
<
<
<
<
<
<
|







<
<
<
<
<
<
<
<
<
<
<
<
<
|





|


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|




|







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
..
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
...
243
244
245
246
247
248
249







250
251
252
253
254
255

256

257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278

279

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
















304
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

372
373
374
375
376
377
378
379
...
408
409
410
411
412
413
414
415
416
417
418
419











































420
421
422
423
424
425
426
427
428
429
430

431
432
433

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449

450
451
452

453
454
455
456
457
458
459
460
...
466
467
468
469
470
471
472
473
474
475
476
477

































478
479
480
481
482
483
484
485
...
489
490
491
492
493
494
495









496
497
498
499
500
501
502
503













504
505
506
507
508
509
510
511
512


















513
514
515
516
517
518
519
520
521
522
523
524
525
}

// WriteZettel encodes the zettel to the writer.
func (ne *nativeEncoder) WriteZettel(
	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newVisitor(w, ne)
	v.b.WriteString("[Title ")
	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
	v.b.WriteByte(']')
	if inhMeta {
		v.acceptMeta(zn.InhMeta, false)
	} else {
		v.acceptMeta(zn.Meta, false)
	}
	v.b.WriteByte('\n')
	v.walkBlockSlice(zn.Ast)
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data in native format.
func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	v := newVisitor(w, ne)
................................................................................
func (ne *nativeEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return ne.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes a block slice to the writer
func (ne *nativeEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(w, ne)
	v.walkBlockSlice(bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (ne *nativeEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w, ne)
	v.walkInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b     encoder.BufWriter
................................................................................
	level int
	env   *encoder.Environment
}

func newVisitor(w io.Writer, enc *nativeEncoder) *visitor {
	return &visitor{b: encoder.NewBufWriter(w), env: enc.env}
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		v.b.WriteString("[Para ")
		v.walkInlineSlice(n.Inlines)
		v.b.WriteByte(']')
	case *ast.VerbatimNode:
		v.visitVerbatim(n)
	case *ast.RegionNode:
		v.visitRegion(n)
	case *ast.HeadingNode:
		v.b.WriteStrings("[Heading ", strconv.Itoa(n.Level), " \"", n.Slug, "\"")
		v.visitAttributes(n.Attrs)
		v.b.WriteByte(' ')
		v.walkInlineSlice(n.Inlines)
		v.b.WriteByte(']')
	case *ast.HRuleNode:
		v.b.WriteString("[Hrule")
		v.visitAttributes(n.Attrs)
		v.b.WriteByte(']')
	case *ast.NestedListNode:
		v.visitNestedList(n)
	case *ast.DescriptionListNode:
		v.visitDescriptionList(n)
	case *ast.TableNode:
		v.visitTable(n)
	case *ast.BLOBNode:
		v.b.WriteString("[BLOB \"")
		v.writeEscaped(n.Title)
		v.b.WriteString("\" \"")
		v.writeEscaped(n.Syntax)
		v.b.WriteString("\" \"")
		v.b.WriteBase64(n.Blob)
		v.b.WriteString("\"]")
	case *ast.TextNode:
		v.b.WriteString("Text \"")
		v.writeEscaped(n.Text)
		v.b.WriteByte('"')
	case *ast.TagNode:
		v.b.WriteString("Tag \"")
		v.writeEscaped(n.Tag)
		v.b.WriteByte('"')
	case *ast.SpaceNode:
		v.b.WriteString("Space")
		if l := len(n.Lexeme); l > 1 {
			v.b.WriteByte(' ')
			v.b.WriteString(strconv.Itoa(l))
		}
	case *ast.BreakNode:
		if n.Hard {
			v.b.WriteString("Break")
		} else {
			v.b.WriteString("Space")
		}
	case *ast.LinkNode:
		v.visitLink(n)
	case *ast.ImageNode:
		v.visitImage(n)
	case *ast.CiteNode:
		v.b.WriteString("Cite")
		v.visitAttributes(n.Attrs)
		v.b.WriteString(" \"")
		v.writeEscaped(n.Key)
		v.b.WriteByte('"')
		if len(n.Inlines) > 0 {
			v.b.WriteString(" [")
			v.walkInlineSlice(n.Inlines)
			v.b.WriteByte(']')
		}
	case *ast.FootnoteNode:
		v.b.WriteString("Footnote")
		v.visitAttributes(n.Attrs)
		v.b.WriteString(" [")
		v.walkInlineSlice(n.Inlines)
		v.b.WriteByte(']')
	case *ast.MarkNode:
		v.b.WriteString("Mark")
		if len(n.Text) > 0 {
			v.b.WriteString(" \"")
			v.writeEscaped(n.Text)
			v.b.WriteByte('"')
		}
	case *ast.FormatNode:
		v.b.Write(mapFormatKind[n.Kind])
		v.visitAttributes(n.Attrs)
		v.b.WriteString(" [")
		v.walkInlineSlice(n.Inlines)
		v.b.WriteByte(']')
	case *ast.LiteralNode:
		kind, ok := mapLiteralKind[n.Kind]
		if !ok {
			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
		}
		v.b.Write(kind)
		v.visitAttributes(n.Attrs)
		v.b.WriteString(" \"")
		v.writeEscaped(n.Text)
		v.b.WriteByte('"')
	default:
		return v
	}
	return nil
}

var (
	rawBackslash   = []byte{'\\', '\\'}
	rawDoubleQuote = []byte{'\\', '"'}
	rawNewline     = []byte{'\\', 'n'}
)

func (v *visitor) acceptMeta(m *meta.Meta, withTitle bool) {
	if withTitle {
		v.b.WriteString("[Title ")
		v.walkInlineSlice(parser.ParseMetadata(m.GetDefault(meta.KeyTitle, "")))
		v.b.WriteByte(']')
	}
	v.writeMetaString(m, meta.KeyRole, "Role")
	v.writeMetaList(m, meta.KeyTags, "Tags")
	v.writeMetaString(m, meta.KeySyntax, "Syntax")
	pairs := m.PairsRest(true)
	if len(pairs) == 0 {
................................................................................
			v.b.WriteByte(' ')
			v.b.WriteString(val)
		}
		v.b.WriteByte(']')
	}
}








var mapVerbatimKind = map[ast.VerbatimKind][]byte{
	ast.VerbatimProg:    []byte("[CodeBlock"),
	ast.VerbatimComment: []byte("[CommentBlock"),
	ast.VerbatimHTML:    []byte("[HTMLBlock"),
}


func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {

	kind, ok := mapVerbatimKind[vn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
	}
	v.b.Write(kind)
	v.visitAttributes(vn.Attrs)
	v.b.WriteString(" \"")
	for i, line := range vn.Lines {
		if i > 0 {
			v.b.Write(rawNewline)
		}
		v.writeEscaped(line)
	}
	v.b.WriteString("\"]")
}

var mapRegionKind = map[ast.RegionKind][]byte{
	ast.RegionSpan:  []byte("[SpanBlock"),
	ast.RegionQuote: []byte("[QuoteBlock"),
	ast.RegionVerse: []byte("[VerseBlock"),
}


func (v *visitor) visitRegion(rn *ast.RegionNode) {

	kind, ok := mapRegionKind[rn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
	}
	v.b.Write(kind)
	v.visitAttributes(rn.Attrs)
	v.level++
	v.writeNewLine()
	v.b.WriteByte('[')
	v.level++
	v.walkBlockSlice(rn.Blocks)
	v.level--
	v.b.WriteByte(']')
	if len(rn.Inlines) > 0 {
		v.b.WriteByte(',')
		v.writeNewLine()
		v.b.WriteString("[Cite ")
		v.walkInlineSlice(rn.Inlines)
		v.b.WriteByte(']')
	}
	v.level--
	v.b.WriteByte(']')
}

















var mapNestedListKind = map[ast.NestedListKind][]byte{
	ast.NestedListOrdered:   []byte("[OrderedList"),
	ast.NestedListUnordered: []byte("[BulletList"),
	ast.NestedListQuote:     []byte("[QuoteList"),
}

func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
	v.b.Write(mapNestedListKind[ln.Kind])

	v.level++
	for i, item := range ln.Items {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.writeNewLine()
		v.level++
		v.b.WriteByte('[')
		for i, in := range item {
			if i > 0 {
				v.b.WriteByte(',')
				v.writeNewLine()
			}
			ast.Walk(v, in)
		}
		v.b.WriteByte(']')
		v.level--
	}
	v.level--
	v.b.WriteByte(']')
}


func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
	v.b.WriteString("[DescriptionList")
	v.level++
	for i, descr := range dn.Descriptions {
		if i > 0 {
			v.b.WriteByte(',')
		}
		v.writeNewLine()
		v.b.WriteString("[Term [")
		v.walkInlineSlice(descr.Term)
		v.b.WriteByte(']')

		if len(descr.Descriptions) > 0 {
			v.level++
			for _, b := range descr.Descriptions {
				v.b.WriteByte(',')
				v.writeNewLine()
				v.b.WriteString("[Description")
				v.level++
				v.writeNewLine()
				for i, dn := range b {
					if i > 0 {
						v.b.WriteByte(',')
						v.writeNewLine()
					}
					ast.Walk(v, dn)
				}
				v.b.WriteByte(']')
				v.level--
			}
			v.level--
		}
		v.b.WriteByte(']')
	}
	v.level--
	v.b.WriteByte(']')
}


func (v *visitor) visitTable(tn *ast.TableNode) {
	v.b.WriteString("[Table")
	v.level++
	if len(tn.Header) > 0 {
		v.writeNewLine()
		v.b.WriteString("[Header ")
		for i, cell := range tn.Header {
			if i > 0 {
................................................................................
	ast.AlignRight:   " Right",
}

func (v *visitor) writeCell(cell *ast.TableCell) {
	v.b.WriteStrings("[Cell", alignString[cell.Align])
	if len(cell.Inlines) > 0 {
		v.b.WriteByte(' ')
		v.walkInlineSlice(cell.Inlines)
	}
	v.b.WriteByte(']')
}












































var mapRefState = map[ast.RefState]string{
	ast.RefStateInvalid:  "INVALID",
	ast.RefStateZettel:   "ZETTEL",
	ast.RefStateSelf:     "SELF",
	ast.RefStateFound:    "ZETTEL",
	ast.RefStateBroken:   "BROKEN",
	ast.RefStateHosted:   "LOCAL",
	ast.RefStateBased:    "BASED",
	ast.RefStateExternal: "EXTERNAL",
}


func (v *visitor) visitLink(ln *ast.LinkNode) {
	ln, n := v.env.AdaptLink(ln)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	v.b.WriteString("Link")
	v.visitAttributes(ln.Attrs)
	v.b.WriteByte(' ')
	v.b.WriteString(mapRefState[ln.Ref.State])
	v.b.WriteString(" \"")
	v.writeEscaped(ln.Ref.String())
	v.b.WriteString("\" [")
	if !ln.OnlyRef {
		v.walkInlineSlice(ln.Inlines)
	}
	v.b.WriteByte(']')
}


func (v *visitor) visitImage(in *ast.ImageNode) {
	in, n := v.env.AdaptImage(in)
	if n != nil {

		ast.Walk(v, n)
		return
	}
	v.b.WriteString("Image")
	v.visitAttributes(in.Attrs)
	if in.Ref == nil {
		v.b.WriteStrings(" {\"", in.Syntax, "\" \"")
		switch in.Syntax {
................................................................................
		}
		v.b.WriteString("\"}")
	} else {
		v.b.WriteStrings(" \"", in.Ref.String(), "\"")
	}
	if len(in.Inlines) > 0 {
		v.b.WriteString(" [")
		v.walkInlineSlice(in.Inlines)
		v.b.WriteByte(']')
	}
}


































var mapFormatKind = map[ast.FormatKind][]byte{
	ast.FormatItalic:    []byte("Italic"),
	ast.FormatEmph:      []byte("Emph"),
	ast.FormatBold:      []byte("Bold"),
	ast.FormatStrong:    []byte("Strong"),
	ast.FormatUnder:     []byte("Underline"),
	ast.FormatInsert:    []byte("Insert"),
	ast.FormatMonospace: []byte("Mono"),
................................................................................
	ast.FormatSub:       []byte("Sub"),
	ast.FormatQuote:     []byte("Quote"),
	ast.FormatQuotation: []byte("Quotation"),
	ast.FormatSmall:     []byte("Small"),
	ast.FormatSpan:      []byte("Span"),
}










var mapLiteralKind = map[ast.LiteralKind][]byte{
	ast.LiteralProg:    []byte("Code"),
	ast.LiteralKeyb:    []byte("Input"),
	ast.LiteralOutput:  []byte("Output"),
	ast.LiteralComment: []byte("Comment"),
	ast.LiteralHTML:    []byte("HTML"),
}














func (v *visitor) walkBlockSlice(bns ast.BlockSlice) {
	for i, bn := range bns {
		if i > 0 {
			v.b.WriteByte(',')
			v.writeNewLine()
		}
		ast.Walk(v, bn)
	}
}


















func (v *visitor) walkInlineSlice(ins ast.InlineSlice) {
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte(',')
		}
		ast.Walk(v, in)
	}
}

// visitAttributes write native attributes
func (v *visitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return

Changes to encoder/textenc/textenc.go.

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
251
252
253
254
255


256
257
258
259

260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

276
277
278
279
280
281
282
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (te *textEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w)
	v.acceptInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b encoder.BufWriter
}

func newVisitor(w io.Writer) *visitor {
	return &visitor{b: encoder.NewBufWriter(w)}
}

// VisitPara emits text code for a paragraph
func (v *visitor) VisitPara(pn *ast.ParaNode) {
	v.acceptInlineSlice(pn.Inlines)
}

// VisitVerbatim emits text for verbatim lines.

func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
	if vn.Code == ast.VerbatimComment {
		return
	}
	for i, line := range vn.Lines {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		v.b.WriteString(line)
	}
}

// VisitRegion writes text code for block regions.

func (v *visitor) VisitRegion(rn *ast.RegionNode) {
	v.acceptBlockSlice(rn.Blocks)
	if len(rn.Inlines) > 0 {
		v.b.WriteByte('\n')
		v.acceptInlineSlice(rn.Inlines)
	}
}

// VisitHeading writes the text code for a heading.
func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
	v.acceptInlineSlice(hn.Inlines)
}

// VisitHRule writes nothing for a horizontal rule.
func (v *visitor) VisitHRule(hn *ast.HRuleNode) {}

// VisitNestedList writes text code for lists and blockquotes.

func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
	for i, item := range ln.Items {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		v.acceptItemSlice(item)



	}

}

// VisitDescriptionList emits a text for a description list.

func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	for i, descr := range dn.Descriptions {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		v.acceptInlineSlice(descr.Term)

		for _, b := range descr.Descriptions {
			v.b.WriteByte('\n')
			v.acceptDescriptionSlice(b)



		}

	}
}

// VisitTable emits a text table.

func (v *visitor) VisitTable(tn *ast.TableNode) {
	if len(tn.Header) > 0 {
		for i, cell := range tn.Header {
			if i > 0 {
				v.b.WriteByte(' ')
			}
			v.acceptInlineSlice(cell.Inlines)
		}

		v.b.WriteByte('\n')
	}
	for i, row := range tn.Rows {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		for j, cell := range row {
			if j > 0 {
				v.b.WriteByte(' ')

			}
			v.acceptInlineSlice(cell.Inlines)
		}
	}
}

// VisitBLOB writes nothing, because it contains no text.
func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {}

// VisitText writes text content.

func (v *visitor) VisitText(tn *ast.TextNode) {
	v.b.WriteString(tn.Text)
}

// VisitTag writes tag content.

func (v *visitor) VisitTag(tn *ast.TagNode) {
	v.b.WriteStrings("#", tn.Tag)
}

// VisitSpace emits a white space.

func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
	v.b.WriteByte(' ')
}

// VisitBreak writes text code for line breaks.

func (v *visitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		v.b.WriteByte('\n')
	} else {
		v.b.WriteByte(' ')
	}
}

// VisitLink writes text code for links.

func (v *visitor) VisitLink(ln *ast.LinkNode) {
	if !ln.OnlyRef {
		v.acceptInlineSlice(ln.Inlines)
	}
}

// VisitImage writes text code for images.
func (v *visitor) VisitImage(in *ast.ImageNode) {
	v.acceptInlineSlice(in.Inlines)
}

// VisitCite writes code for citations.
func (v *visitor) VisitCite(cn *ast.CiteNode) {
	v.acceptInlineSlice(cn.Inlines)
}

// VisitFootnote write text code for a footnote.

func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
	v.b.WriteByte(' ')
	v.acceptInlineSlice(fn.Inlines)
}

// VisitMark writes nothing for a mark.
func (v *visitor) VisitMark(mn *ast.MarkNode) {}

// VisitFormat write text code for formatting text.
func (v *visitor) VisitFormat(fn *ast.FormatNode) {
	v.acceptInlineSlice(fn.Inlines)
}

// VisitLiteral write text code for literal inline text.

func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
	if ln.Code != ast.LiteralComment {
		v.b.WriteString(ln.Text)
	}
}


// VisitAttributes never writes any attribute data.
func (v *visitor) VisitAttributes(a *ast.Attributes) {}

func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
	for i, bn := range bns {


		if i > 0 {
			v.b.WriteByte('\n')
		}
		bn.Accept(v)

	}
}
func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
	for i, in := range ins {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		in.Accept(v)
	}
}
func (v *visitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
	for i, dn := range dns {
		if i > 0 {
			v.b.WriteByte('\n')
		}
		dn.Accept(v)

	}
}
func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
	for _, in := range ins {
		in.Accept(v)
	}
}







|













<
|
<
<
<
<
>
|
|
|
|
|
|
|
|
|
|
<
<
<
>
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
>
|
|
|
|
|
<
>
>
>
|
>
|
|
<
>
|
|
|
|
|
|
<
|
|
<
>
>
>
|
>
|
|
|
<
>
|
|
<
<
<
<
<
<
>
|
|
|
|
|
|
<
<
<
>
|
<
<
<
<
<
<
<
<
<
>
|
|
<
<
<
>
|
|
<
<
<
>
|
|
<
<
<
>
|
|
|
|
|
|
<
<
<
>
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
>
|
|
<
<
<
<
<
<
<
<
<
<
<
<
>
|
|
|
|
|
>
|
<
<

<
<
>
>

|

<
>


<
<
<
<
|
<
<
<
|
|



<
>


<
<
<
<
<
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





	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (te *textEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w)
	ast.WalkInlineSlice(v, is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b encoder.BufWriter
}

func newVisitor(w io.Writer) *visitor {
	return &visitor{b: encoder.NewBufWriter(w)}
}


func (v *visitor) Visit(node ast.Node) ast.Visitor {




	switch n := node.(type) {
	case *ast.VerbatimNode:
		if n.Kind == ast.VerbatimComment {
			return nil
		}
		for i, line := range n.Lines {
			if i > 0 {
				v.b.WriteByte('\n')
			}
			v.b.WriteString(line)
		}



		return nil
	case *ast.RegionNode:
		v.acceptBlockSlice(n.Blocks)
		if len(n.Inlines) > 0 {
			v.b.WriteByte('\n')
			ast.WalkInlineSlice(v, n.Inlines)
		}











		return nil
	case *ast.NestedListNode:
		for i, item := range n.Items {
			if i > 0 {
				v.b.WriteByte('\n')
			}

			for j, it := range item {
				if j > 0 {
					v.b.WriteByte('\n')
				}
				ast.Walk(v, it)
			}
		}

		return nil
	case *ast.DescriptionListNode:
		for i, descr := range n.Descriptions {
			if i > 0 {
				v.b.WriteByte('\n')
			}
			ast.WalkInlineSlice(v, descr.Term)

			for _, b := range descr.Descriptions {
				v.b.WriteByte('\n')

				for k, d := range b {
					if k > 0 {
						v.b.WriteByte('\n')
					}
					ast.Walk(v, d)
				}
			}
		}

		return nil
	case *ast.TableNode:
		if len(n.Header) > 0 {






			v.writeRow(n.Header)
			v.b.WriteByte('\n')
		}
		for i, row := range n.Rows {
			if i > 0 {
				v.b.WriteByte('\n')
			}



			v.writeRow(row)
		}









		return nil
	case *ast.TextNode:
		v.b.WriteString(n.Text)



		return nil
	case *ast.TagNode:
		v.b.WriteStrings("#", n.Tag)



		return nil
	case *ast.SpaceNode:
		v.b.WriteByte(' ')



		return nil
	case *ast.BreakNode:
		if n.Hard {
			v.b.WriteByte('\n')
		} else {
			v.b.WriteByte(' ')
		}



		return nil
	case *ast.LinkNode:
		if !n.OnlyRef {
			ast.WalkInlineSlice(v, n.Inlines)
		}













		return nil
	case *ast.FootnoteNode:
		v.b.WriteByte(' ')












		return v // No 'return nil' to write text
	case *ast.LiteralNode:
		if n.Kind != ast.LiteralComment {
			v.b.WriteString(n.Text)
		}
	}
	return v
}





func (v *visitor) writeRow(row ast.TableRow) {
	for i, cell := range row {
		if i > 0 {
			v.b.WriteByte(' ')
		}

		ast.WalkInlineSlice(v, cell.Inlines)
	}
}








func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
	for i, bn := range bns {
		if i > 0 {
			v.b.WriteByte('\n')
		}

		ast.Walk(v, bn)
	}
}





Changes to encoder/zmkenc/zmkenc.go.

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
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
...
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
...
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
...
365
366
367
368
369
370
371
372
373
374

375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newVisitor(w, ze)
	if inhMeta {
		zn.InhMeta.WriteAsHeader(&v.b, true)
	} else {
		zn.Meta.WriteAsHeader(&v.b, true)
	}
	v.acceptBlockSlice(zn.Ast)
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as zmk.
func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	return m.Write(w, true)
................................................................................
func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return ze.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes the content of a block slice to the writer.
func (ze *zmkEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(w, ze)
	v.acceptBlockSlice(bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (ze *zmkEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w, ze)
	v.acceptInlineSlice(is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b      encoder.BufWriter
................................................................................
func newVisitor(w io.Writer, enc *zmkEncoder) *visitor {
	return &visitor{
		b:   encoder.NewBufWriter(w),
		enc: enc,
	}
}

// VisitPara emits HTML code for a paragraph: <p>...</p>

func (v *visitor) VisitPara(pn *ast.ParaNode) {
	v.acceptInlineSlice(pn.Inlines)
	v.b.WriteByte('\n')
	if len(v.prefix) == 0 {
		v.b.WriteByte('\n')
	}















































}


// VisitVerbatim emits HTML code for verbatim lines.

func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
	// TODO: scan cn.Lines to find embedded "`"s at beginning
	v.b.WriteString("```")
	v.visitAttributes(vn.Attrs)
	v.b.WriteByte('\n')
	for _, line := range vn.Lines {
		v.b.WriteStrings(line, "\n")
	}
	v.b.WriteString("```\n")
}

var regionCode = map[ast.RegionCode]string{
	ast.RegionSpan:  ":::",
	ast.RegionQuote: "<<<",
	ast.RegionVerse: "\"\"\"",
}

// VisitRegion writes HTML code for block regions.
func (v *visitor) VisitRegion(rn *ast.RegionNode) {
	// Scan rn.Blocks for embedded regions to adjust length of regionCode
	code, ok := regionCode[rn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown region code %d", rn.Code))
	}
	v.b.WriteString(code)
	v.visitAttributes(rn.Attrs)
	v.b.WriteByte('\n')
	v.acceptBlockSlice(rn.Blocks)
	v.b.WriteString(code)
	if len(rn.Inlines) > 0 {
		v.b.WriteByte(' ')
		v.acceptInlineSlice(rn.Inlines)
	}
	v.b.WriteByte('\n')
}

// VisitHeading writes the HTML code for a heading.
func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
	for i := 0; i <= hn.Level; i++ {
		v.b.WriteByte('=')
	}
	v.b.WriteByte(' ')
	v.acceptInlineSlice(hn.Inlines)
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte('\n')
}

// VisitHRule writes HTML code for a horizontal rule: <hr>.
func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
	v.b.WriteString("---")
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte('\n')
}

var listCode = map[ast.NestedListCode]byte{
	ast.NestedListOrdered:   '#',
	ast.NestedListUnordered: '*',
	ast.NestedListQuote:     '>',
}

// VisitNestedList writes HTML code for lists and blockquotes.
func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
	v.prefix = append(v.prefix, listCode[ln.Code])
	for _, item := range ln.Items {
		v.b.Write(v.prefix)
		v.b.WriteByte(' ')
		for i, in := range item {
			if i > 0 {
				if _, ok := in.(*ast.ParaNode); ok {
					v.b.WriteByte('\n')
					for j := 0; j <= len(v.prefix); j++ {
						v.b.WriteByte(' ')
					}
				}
			}
			in.Accept(v)
		}
	}
	v.prefix = v.prefix[:len(v.prefix)-1]
	v.b.WriteByte('\n')
}

// VisitDescriptionList emits a HTML description list.
func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	for _, descr := range dn.Descriptions {
		v.b.WriteString("; ")
		v.acceptInlineSlice(descr.Term)
		v.b.WriteByte('\n')

		for _, b := range descr.Descriptions {
			v.b.WriteString(": ")
			for _, dn := range b {
				dn.Accept(v)
			}
			v.b.WriteByte('\n')
		}
	}
}

var alignCode = map[ast.Alignment]string{
	ast.AlignDefault: "",
	ast.AlignLeft:    "<",
	ast.AlignCenter:  ":",
	ast.AlignRight:   ">",
}

// VisitTable emits a HTML table.
func (v *visitor) VisitTable(tn *ast.TableNode) {
	if len(tn.Header) > 0 {
		for pos, cell := range tn.Header {
			v.b.WriteString("|=")
			colAlign := tn.Align[pos]
			if cell.Align != colAlign {
				v.b.WriteString(alignCode[cell.Align])
			}
			v.acceptInlineSlice(cell.Inlines)
			if colAlign != ast.AlignDefault {
				v.b.WriteString(alignCode[colAlign])
			}
		}
		v.b.WriteByte('\n')
	}
	for _, row := range tn.Rows {
		for pos, cell := range row {
			v.b.WriteByte('|')
			if cell.Align != tn.Align[pos] {
				v.b.WriteString(alignCode[cell.Align])
			}
			v.acceptInlineSlice(cell.Inlines)
		}
		v.b.WriteByte('\n')
	}
	v.b.WriteByte('\n')
}

// VisitBLOB writes the binary object as a value.
func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
	v.b.WriteStrings(
		"%% Unable to display BLOB with title '",
		bn.Title,
		"' and syntax '",
		bn.Syntax,
		"'\n")
}

var escapeSeqs = map[string]bool{
	"\\":   true,
	"//":   true,
	"**":   true,
	"__":   true,
	"~~":   true,
	"^^":   true,
................................................................................
	"::":   true,
	"''":   true,
	"``":   true,
	"++":   true,
	"==":   true,
}

// VisitText writes text content.
func (v *visitor) VisitText(tn *ast.TextNode) {
	last := 0
	for i := 0; i < len(tn.Text); i++ {
		if b := tn.Text[i]; b == '\\' {
			v.b.WriteString(tn.Text[last:i])
			v.b.WriteBytes('\\', b)
			last = i + 1
			continue
................................................................................
				continue
			}
		}
	}
	v.b.WriteString(tn.Text[last:])
}

// VisitTag writes tag content.
func (v *visitor) VisitTag(tn *ast.TagNode) {
	v.b.WriteStrings("#", tn.Tag)
}

// VisitSpace emits a white space.
func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
	v.b.WriteString(sn.Lexeme)
}

// VisitBreak writes HTML code for line breaks.
func (v *visitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		v.b.WriteString("\\\n")
	} else {
		v.b.WriteByte('\n')
	}
	if prefixLen := len(v.prefix); prefixLen > 0 {
		for i := 0; i <= prefixLen; i++ {
			v.b.WriteByte(' ')
		}
	}
}

// VisitLink writes HTML code for links.
func (v *visitor) VisitLink(ln *ast.LinkNode) {
	v.b.WriteString("[[")
	if !ln.OnlyRef {
		v.acceptInlineSlice(ln.Inlines)
		v.b.WriteByte('|')
	}
	v.b.WriteStrings(ln.Ref.String(), "]]")
}

// VisitImage writes HTML code for images.
func (v *visitor) VisitImage(in *ast.ImageNode) {
	if in.Ref != nil {
		v.b.WriteString("{{")
		if len(in.Inlines) > 0 {
			v.acceptInlineSlice(in.Inlines)
			v.b.WriteByte('|')
		}
		v.b.WriteStrings(in.Ref.String(), "}}")
	}
}

// VisitCite writes code for citations.
func (v *visitor) VisitCite(cn *ast.CiteNode) {
	v.b.WriteStrings("[@", cn.Key)
	if len(cn.Inlines) > 0 {
		v.b.WriteString(", ")
		v.acceptInlineSlice(cn.Inlines)
	}
	v.b.WriteByte(']')
	v.visitAttributes(cn.Attrs)
}

// VisitFootnote write HTML code for a footnote.
func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
	v.b.WriteString("[^")
	v.acceptInlineSlice(fn.Inlines)
	v.b.WriteByte(']')
	v.visitAttributes(fn.Attrs)
}

// VisitMark writes HTML code to mark a position.
func (v *visitor) VisitMark(mn *ast.MarkNode) {
	v.b.WriteStrings("[!", mn.Text, "]")
}

var formatCode = map[ast.FormatCode][]byte{
	ast.FormatItalic:    []byte("//"),
	ast.FormatEmph:      []byte("//"),
	ast.FormatBold:      []byte("**"),
	ast.FormatStrong:    []byte("**"),
	ast.FormatUnder:     []byte("__"),
	ast.FormatInsert:    []byte("__"),
	ast.FormatStrike:    []byte("~~"),
................................................................................
	ast.FormatQuotation: []byte("<<"),
	ast.FormatQuote:     []byte("\"\""),
	ast.FormatSmall:     []byte(";;"),
	ast.FormatSpan:      []byte("::"),
	ast.FormatMonospace: []byte("''"),
}

// VisitFormat write HTML code for formatting text.
func (v *visitor) VisitFormat(fn *ast.FormatNode) {
	code, ok := formatCode[fn.Code]

	if !ok {
		panic(fmt.Sprintf("Unknown format code %d", fn.Code))
	}
	attrs := fn.Attrs
	switch fn.Code {
	case ast.FormatEmph, ast.FormatStrong, ast.FormatInsert, ast.FormatDelete:
		attrs = attrs.Clone()
		attrs.Set("-", "")
	}

	v.b.Write(code)
	v.acceptInlineSlice(fn.Inlines)
	v.b.Write(code)
	v.visitAttributes(attrs)
}

// VisitLiteral write Zettelmarkup for inline literal text.
func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
	switch ln.Code {
	case ast.LiteralProg:
		v.writeLiteral('`', ln.Attrs, ln.Text)
	case ast.LiteralKeyb:
		v.writeLiteral('+', ln.Attrs, ln.Text)
	case ast.LiteralOutput:
		v.writeLiteral('=', ln.Attrs, ln.Text)
	case ast.LiteralComment:
		v.b.WriteStrings("%% ", ln.Text)
	case ast.LiteralHTML:
		v.b.WriteString("``")
		v.writeEscaped(ln.Text, '`')
		v.b.WriteString("``{=html,.warning}")
	default:
		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
	}
}

func (v *visitor) writeLiteral(code byte, attrs *ast.Attributes, text string) {
	v.b.WriteBytes(code, code)
	v.writeEscaped(text, code)
	v.b.WriteBytes(code, code)
	v.visitAttributes(attrs)
}

func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
	for _, bn := range bns {
		bn.Accept(v)
	}
}
func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
	for _, in := range ins {
		in.Accept(v)
	}
}

// visitAttributes write HTML attributes
func (v *visitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return
	}
	keys := make([]string, 0, len(a.Attrs))
	for k := range a.Attrs {







|







 







|







|







 







|
>
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
<
>
|










|





<
|

<
>

|

|


|
|


|




<
|




|




<
<
<
<
<
<
<
|





<
|
|












|






<
|


|




|
<
<












<
|







|












|






<
<
<
<
<
<
<
<
<
<







 







<
|







 







<
<
<
<
<
<
<
<
<
<
<
|












<
|


|





<
|



|






<
|



|





<
<
<
<
<
<
<
<
<
<
<
<
<
|







 







<
|
<
>

|


|





|
|
|



<
|
|













|










<
<
<
<
<
<
<
<
<
<
<







34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
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
251
252
253
254
255
256
257
258
259
260
261
262
263
264










265
266
267
268
269
270
271
...
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290
...
301
302
303
304
305
306
307











308
309
310
311
312
313
314
315
316
317
318
319
320

321
322
323
324
325
326
327
328
329

330
331
332
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348
349
350













351
352
353
354
355
356
357
358
...
362
363
364
365
366
367
368

369

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386

387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412











413
414
415
416
417
418
419
	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
	v := newVisitor(w, ze)
	if inhMeta {
		zn.InhMeta.WriteAsHeader(&v.b, true)
	} else {
		zn.Meta.WriteAsHeader(&v.b, true)
	}
	ast.WalkBlockSlice(v, zn.Ast)
	length, err := v.b.Flush()
	return length, err
}

// WriteMeta encodes meta data as zmk.
func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
	return m.Write(w, true)
................................................................................
func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
	return ze.WriteBlocks(w, zn.Ast)
}

// WriteBlocks writes the content of a block slice to the writer.
func (ze *zmkEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
	v := newVisitor(w, ze)
	ast.WalkBlockSlice(v, bs)
	length, err := v.b.Flush()
	return length, err
}

// WriteInlines writes an inline slice to the writer
func (ze *zmkEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
	v := newVisitor(w, ze)
	ast.WalkInlineSlice(v, is)
	length, err := v.b.Flush()
	return length, err
}

// visitor writes the abstract syntax tree to an io.Writer.
type visitor struct {
	b      encoder.BufWriter
................................................................................
func newVisitor(w io.Writer, enc *zmkEncoder) *visitor {
	return &visitor{
		b:   encoder.NewBufWriter(w),
		enc: enc,
	}
}

func (v *visitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		ast.WalkInlineSlice(v, n.Inlines)
		v.b.WriteByte('\n')
		if len(v.prefix) == 0 {
			v.b.WriteByte('\n')
		}
	case *ast.VerbatimNode:
		v.visitVerbatim(n)
	case *ast.RegionNode:
		v.visitRegion(n)
	case *ast.HeadingNode:
		v.visitHeading(n)
	case *ast.HRuleNode:
		v.b.WriteString("---")
		v.visitAttributes(n.Attrs)
		v.b.WriteByte('\n')
	case *ast.NestedListNode:
		v.visitNestedList(n)
	case *ast.DescriptionListNode:
		v.visitDescriptionList(n)
	case *ast.TableNode:
		v.visitTable(n)
	case *ast.BLOBNode:
		v.b.WriteStrings(
			"%% Unable to display BLOB with title '", n.Title,
			"' and syntax '", n.Syntax, "'\n")
	case *ast.TextNode:
		v.visitText(n)
	case *ast.TagNode:
		v.b.WriteStrings("#", n.Tag)
	case *ast.SpaceNode:
		v.b.WriteString(n.Lexeme)
	case *ast.BreakNode:
		v.visitBreak(n)
	case *ast.LinkNode:
		v.visitLink(n)
	case *ast.ImageNode:
		v.visitImage(n)
	case *ast.CiteNode:
		v.visitCite(n)
	case *ast.FootnoteNode:
		v.b.WriteString("[^")
		ast.WalkInlineSlice(v, n.Inlines)
		v.b.WriteByte(']')
		v.visitAttributes(n.Attrs)
	case *ast.MarkNode:
		v.b.WriteStrings("[!", n.Text, "]")
	case *ast.FormatNode:
		v.visitFormat(n)
	case *ast.LiteralNode:
		v.visitLiteral(n)
	default:
		return v
	}
	return nil
}


func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
	// TODO: scan cn.Lines to find embedded "`"s at beginning
	v.b.WriteString("```")
	v.visitAttributes(vn.Attrs)
	v.b.WriteByte('\n')
	for _, line := range vn.Lines {
		v.b.WriteStrings(line, "\n")
	}
	v.b.WriteString("```\n")
}

var mapRegionKind = map[ast.RegionKind]string{
	ast.RegionSpan:  ":::",
	ast.RegionQuote: "<<<",
	ast.RegionVerse: "\"\"\"",
}


func (v *visitor) visitRegion(rn *ast.RegionNode) {
	// Scan rn.Blocks for embedded regions to adjust length of regionCode

	kind, ok := mapRegionKind[rn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown region kind %d", rn.Kind))
	}
	v.b.WriteString(kind)
	v.visitAttributes(rn.Attrs)
	v.b.WriteByte('\n')
	ast.WalkBlockSlice(v, rn.Blocks)
	v.b.WriteString(kind)
	if len(rn.Inlines) > 0 {
		v.b.WriteByte(' ')
		ast.WalkInlineSlice(v, rn.Inlines)
	}
	v.b.WriteByte('\n')
}


func (v *visitor) visitHeading(hn *ast.HeadingNode) {
	for i := 0; i <= hn.Level; i++ {
		v.b.WriteByte('=')
	}
	v.b.WriteByte(' ')
	ast.WalkInlineSlice(v, hn.Inlines)
	v.visitAttributes(hn.Attrs)
	v.b.WriteByte('\n')
}








var mapNestedListKind = map[ast.NestedListKind]byte{
	ast.NestedListOrdered:   '#',
	ast.NestedListUnordered: '*',
	ast.NestedListQuote:     '>',
}


func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
	v.prefix = append(v.prefix, mapNestedListKind[ln.Kind])
	for _, item := range ln.Items {
		v.b.Write(v.prefix)
		v.b.WriteByte(' ')
		for i, in := range item {
			if i > 0 {
				if _, ok := in.(*ast.ParaNode); ok {
					v.b.WriteByte('\n')
					for j := 0; j <= len(v.prefix); j++ {
						v.b.WriteByte(' ')
					}
				}
			}
			ast.Walk(v, in)
		}
	}
	v.prefix = v.prefix[:len(v.prefix)-1]
	v.b.WriteByte('\n')
}


func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
	for _, descr := range dn.Descriptions {
		v.b.WriteString("; ")
		ast.WalkInlineSlice(v, descr.Term)
		v.b.WriteByte('\n')

		for _, b := range descr.Descriptions {
			v.b.WriteString(": ")
			ast.WalkDescriptionSlice(v, b)


			v.b.WriteByte('\n')
		}
	}
}

var alignCode = map[ast.Alignment]string{
	ast.AlignDefault: "",
	ast.AlignLeft:    "<",
	ast.AlignCenter:  ":",
	ast.AlignRight:   ">",
}


func (v *visitor) visitTable(tn *ast.TableNode) {
	if len(tn.Header) > 0 {
		for pos, cell := range tn.Header {
			v.b.WriteString("|=")
			colAlign := tn.Align[pos]
			if cell.Align != colAlign {
				v.b.WriteString(alignCode[cell.Align])
			}
			ast.WalkInlineSlice(v, cell.Inlines)
			if colAlign != ast.AlignDefault {
				v.b.WriteString(alignCode[colAlign])
			}
		}
		v.b.WriteByte('\n')
	}
	for _, row := range tn.Rows {
		for pos, cell := range row {
			v.b.WriteByte('|')
			if cell.Align != tn.Align[pos] {
				v.b.WriteString(alignCode[cell.Align])
			}
			ast.WalkInlineSlice(v, cell.Inlines)
		}
		v.b.WriteByte('\n')
	}
	v.b.WriteByte('\n')
}











var escapeSeqs = map[string]bool{
	"\\":   true,
	"//":   true,
	"**":   true,
	"__":   true,
	"~~":   true,
	"^^":   true,
................................................................................
	"::":   true,
	"''":   true,
	"``":   true,
	"++":   true,
	"==":   true,
}


func (v *visitor) visitText(tn *ast.TextNode) {
	last := 0
	for i := 0; i < len(tn.Text); i++ {
		if b := tn.Text[i]; b == '\\' {
			v.b.WriteString(tn.Text[last:i])
			v.b.WriteBytes('\\', b)
			last = i + 1
			continue
................................................................................
				continue
			}
		}
	}
	v.b.WriteString(tn.Text[last:])
}












func (v *visitor) visitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		v.b.WriteString("\\\n")
	} else {
		v.b.WriteByte('\n')
	}
	if prefixLen := len(v.prefix); prefixLen > 0 {
		for i := 0; i <= prefixLen; i++ {
			v.b.WriteByte(' ')
		}
	}
}


func (v *visitor) visitLink(ln *ast.LinkNode) {
	v.b.WriteString("[[")
	if !ln.OnlyRef {
		ast.WalkInlineSlice(v, ln.Inlines)
		v.b.WriteByte('|')
	}
	v.b.WriteStrings(ln.Ref.String(), "]]")
}


func (v *visitor) visitImage(in *ast.ImageNode) {
	if in.Ref != nil {
		v.b.WriteString("{{")
		if len(in.Inlines) > 0 {
			ast.WalkInlineSlice(v, in.Inlines)
			v.b.WriteByte('|')
		}
		v.b.WriteStrings(in.Ref.String(), "}}")
	}
}


func (v *visitor) visitCite(cn *ast.CiteNode) {
	v.b.WriteStrings("[@", cn.Key)
	if len(cn.Inlines) > 0 {
		v.b.WriteString(", ")
		ast.WalkInlineSlice(v, cn.Inlines)
	}
	v.b.WriteByte(']')
	v.visitAttributes(cn.Attrs)
}














var mapFormatKind = map[ast.FormatKind][]byte{
	ast.FormatItalic:    []byte("//"),
	ast.FormatEmph:      []byte("//"),
	ast.FormatBold:      []byte("**"),
	ast.FormatStrong:    []byte("**"),
	ast.FormatUnder:     []byte("__"),
	ast.FormatInsert:    []byte("__"),
	ast.FormatStrike:    []byte("~~"),
................................................................................
	ast.FormatQuotation: []byte("<<"),
	ast.FormatQuote:     []byte("\"\""),
	ast.FormatSmall:     []byte(";;"),
	ast.FormatSpan:      []byte("::"),
	ast.FormatMonospace: []byte("''"),
}


func (v *visitor) visitFormat(fn *ast.FormatNode) {

	kind, ok := mapFormatKind[fn.Kind]
	if !ok {
		panic(fmt.Sprintf("Unknown format kind %d", fn.Kind))
	}
	attrs := fn.Attrs
	switch fn.Kind {
	case ast.FormatEmph, ast.FormatStrong, ast.FormatInsert, ast.FormatDelete:
		attrs = attrs.Clone()
		attrs.Set("-", "")
	}

	v.b.Write(kind)
	ast.WalkInlineSlice(v, fn.Inlines)
	v.b.Write(kind)
	v.visitAttributes(attrs)
}


func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
	switch ln.Kind {
	case ast.LiteralProg:
		v.writeLiteral('`', ln.Attrs, ln.Text)
	case ast.LiteralKeyb:
		v.writeLiteral('+', ln.Attrs, ln.Text)
	case ast.LiteralOutput:
		v.writeLiteral('=', ln.Attrs, ln.Text)
	case ast.LiteralComment:
		v.b.WriteStrings("%% ", ln.Text)
	case ast.LiteralHTML:
		v.b.WriteString("``")
		v.writeEscaped(ln.Text, '`')
		v.b.WriteString("``{=html,.warning}")
	default:
		panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind))
	}
}

func (v *visitor) writeLiteral(code byte, attrs *ast.Attributes, text string) {
	v.b.WriteBytes(code, code)
	v.writeEscaped(text, code)
	v.b.WriteBytes(code, code)
	v.visitAttributes(attrs)
}












// visitAttributes write HTML attributes
func (v *visitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return
	}
	keys := make([]string, 0, len(a.Attrs))
	for k := range a.Attrs {

Changes to kernel/impl/cfg.go.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
..
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
..
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
...
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

// Package impl provides the kernel implementation.
package impl

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"sync"

	"zettelstore.de/z/domain/id"
	"zettelstore.de/z/domain/meta"
	"zettelstore.de/z/kernel"
	"zettelstore.de/z/place"
................................................................................
				if vis == meta.VisibilityUnknown {
					return nil
				}
				return vis
			},
			true,
		},
		meta.KeyExpertMode: {"Expert mode", parseBool, true},
		meta.KeyFooterHTML: {"Footer HTML", parseString, true},
		meta.KeyHomeZettel: {"Home zettel", parseZid, true},
		meta.KeyListPageSize: {
			"List page size",
			func(val string) interface{} {
				iVal, err := strconv.Atoi(val)
				if err != nil {
					return nil
				}
				return iVal
			},
			true,
		},
		meta.KeyMarkerExternal: {"Marker external URL", parseString, true},
		meta.KeySiteName:       {"Site name", parseString, true},
		meta.KeyYAMLHeader:     {"YAML header", parseBool, true},
		meta.KeyZettelFileSyntax: {
			"Zettel file syntax",
			func(val string) interface{} { return strings.Fields(val) },
			true,
................................................................................
		meta.KeyDefaultRole:       meta.ValueRoleZettel,
		meta.KeyDefaultSyntax:     meta.ValueSyntaxZmk,
		meta.KeyDefaultTitle:      "Untitled",
		meta.KeyDefaultVisibility: meta.VisibilityLogin,
		meta.KeyExpertMode:        false,
		meta.KeyFooterHTML:        "",
		meta.KeyHomeZettel:        id.DefaultHomeZid,
		meta.KeyListPageSize:      0,
		meta.KeyMarkerExternal:    "&#10138;",
		meta.KeySiteName:          "Zettelstore",
		meta.KeyYAMLHeader:        false,
		meta.KeyZettelFileSyntax:  nil,
	}
}

................................................................................
	return cfg.getString(meta.KeyMarkerExternal)
}

// GetFooterHTML returns HTML code that should be embedded into the footer
// of each WebUI page.
func (cfg *myConfig) GetFooterHTML() string { return cfg.getString(meta.KeyFooterHTML) }

// GetListPageSize returns the maximum length of a list to be returned in WebUI.
// A value less or equal to zero signals no limit.
func (cfg *myConfig) GetListPageSize() int {
	cfg.mx.RLock()
	defer cfg.mx.RUnlock()

	if value, ok := cfg.data.GetNumber(meta.KeyListPageSize); ok {
		return value
	}
	value, _ := cfg.orig.GetNumber(meta.KeyListPageSize)
	return value
}

// GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key.
func (cfg *myConfig) GetZettelFileSyntax() []string {
	cfg.mx.RLock()
	defer cfg.mx.RUnlock()
	return cfg.data.GetListOrNil(meta.KeyZettelFileSyntax)
}








<







 







|
|
|
<
<
<
<
<
<
<
<
<
<
<







 







<







 







<
<
<
<
<
<
<
<
<
<
<
<
<







10
11
12
13
14
15
16

17
18
19
20
21
22
23
..
43
44
45
46
47
48
49
50
51
52











53
54
55
56
57
58
59
..
65
66
67
68
69
70
71

72
73
74
75
76
77
78
...
243
244
245
246
247
248
249













250
251
252
253
254
255
256

// Package impl provides the kernel implementation.
package impl

import (
	"context"
	"fmt"

	"strings"
	"sync"

	"zettelstore.de/z/domain/id"
	"zettelstore.de/z/domain/meta"
	"zettelstore.de/z/kernel"
	"zettelstore.de/z/place"
................................................................................
				if vis == meta.VisibilityUnknown {
					return nil
				}
				return vis
			},
			true,
		},
		meta.KeyExpertMode:     {"Expert mode", parseBool, true},
		meta.KeyFooterHTML:     {"Footer HTML", parseString, true},
		meta.KeyHomeZettel:     {"Home zettel", parseZid, true},











		meta.KeyMarkerExternal: {"Marker external URL", parseString, true},
		meta.KeySiteName:       {"Site name", parseString, true},
		meta.KeyYAMLHeader:     {"YAML header", parseBool, true},
		meta.KeyZettelFileSyntax: {
			"Zettel file syntax",
			func(val string) interface{} { return strings.Fields(val) },
			true,
................................................................................
		meta.KeyDefaultRole:       meta.ValueRoleZettel,
		meta.KeyDefaultSyntax:     meta.ValueSyntaxZmk,
		meta.KeyDefaultTitle:      "Untitled",
		meta.KeyDefaultVisibility: meta.VisibilityLogin,
		meta.KeyExpertMode:        false,
		meta.KeyFooterHTML:        "",
		meta.KeyHomeZettel:        id.DefaultHomeZid,

		meta.KeyMarkerExternal:    "&#10138;",
		meta.KeySiteName:          "Zettelstore",
		meta.KeyYAMLHeader:        false,
		meta.KeyZettelFileSyntax:  nil,
	}
}

................................................................................
	return cfg.getString(meta.KeyMarkerExternal)
}

// GetFooterHTML returns HTML code that should be embedded into the footer
// of each WebUI page.
func (cfg *myConfig) GetFooterHTML() string { return cfg.getString(meta.KeyFooterHTML) }














// GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key.
func (cfg *myConfig) GetZettelFileSyntax() []string {
	cfg.mx.RLock()
	defer cfg.mx.RUnlock()
	return cfg.data.GetListOrNil(meta.KeyZettelFileSyntax)
}

Changes to parser/cleaner/cleaner.go.

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
	"zettelstore.de/z/ast"
	"zettelstore.de/z/encoder"
	"zettelstore.de/z/strfun"
)

// CleanupBlockSlice cleans the given block slice.
func CleanupBlockSlice(bs ast.BlockSlice) {
	cv := &cleanupVisitor{
		textEnc: encoder.Create("text", nil),

		doMark:  false,
	}
	t := ast.NewTopDownTraverser(cv)
	t.VisitBlockSlice(bs)
	if cv.hasMark {
		cv.doMark = true
		t.VisitBlockSlice(bs)
	}
}

type cleanupVisitor struct {
	textEnc encoder.Encoder
	ids     map[string]ast.Node
	hasMark bool
	doMark  bool
}

// VisitVerbatim does nothing.
func (cv *cleanupVisitor) VisitVerbatim(vn *ast.VerbatimNode) {}

// VisitRegion does nothing.
func (cv *cleanupVisitor) VisitRegion(rn *ast.RegionNode) {}

// VisitHeading calculates the heading slug.

func (cv *cleanupVisitor) VisitHeading(hn *ast.HeadingNode) {
	if cv.doMark || hn == nil || hn.Inlines == nil {

		return
	}
	var sb strings.Builder
	_, err := cv.textEnc.WriteInlines(&sb, hn.Inlines)
	if err != nil {
		return
	}
	s := strfun.Slugify(sb.String())
	if len(s) > 0 {
		hn.Slug = cv.addIdentifier(s, hn)
	}
}

// VisitHRule does nothing.
func (cv *cleanupVisitor) VisitHRule(hn *ast.HRuleNode) {}

// VisitList does nothing.
func (cv *cleanupVisitor) VisitNestedList(ln *ast.NestedListNode) {}

// VisitDescriptionList does nothing.
func (cv *cleanupVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {}

// VisitPara does nothing.
func (cv *cleanupVisitor) VisitPara(pn *ast.ParaNode) {}

// VisitTable does nothing.
func (cv *cleanupVisitor) VisitTable(tn *ast.TableNode) {}

// VisitBLOB does nothing.
func (cv *cleanupVisitor) VisitBLOB(bn *ast.BLOBNode) {}

// VisitText does nothing.
func (cv *cleanupVisitor) VisitText(tn *ast.TextNode) {}

// VisitTag does nothing.
func (cv *cleanupVisitor) VisitTag(tn *ast.TagNode) {}

// VisitSpace does nothing.
func (cv *cleanupVisitor) VisitSpace(sn *ast.SpaceNode) {}

// VisitBreak does nothing.
func (cv *cleanupVisitor) VisitBreak(bn *ast.BreakNode) {}

// VisitLink collects the given link as a reference.
func (cv *cleanupVisitor) VisitLink(ln *ast.LinkNode) {}

// VisitImage collects the image links as a reference.
func (cv *cleanupVisitor) VisitImage(in *ast.ImageNode) {}

// VisitCite does nothing.
func (cv *cleanupVisitor) VisitCite(cn *ast.CiteNode) {}

// VisitFootnote does nothing.
func (cv *cleanupVisitor) VisitFootnote(fn *ast.FootnoteNode) {}

// VisitMark checks for duplicate marks and changes them.
func (cv *cleanupVisitor) VisitMark(mn *ast.MarkNode) {
	if mn == nil {
		return
	}

	if !cv.doMark {
		cv.hasMark = true
		return
	}
	if mn.Text == "" {
		mn.Text = cv.addIdentifier("*", mn)
		return
	}
	mn.Text = cv.addIdentifier(mn.Text, mn)

}


// VisitFormat does nothing.
func (cv *cleanupVisitor) VisitFormat(fn *ast.FormatNode) {}

// VisitLiteral does nothing.
func (cv *cleanupVisitor) VisitLiteral(ln *ast.LiteralNode) {}

func (cv *cleanupVisitor) addIdentifier(id string, node ast.Node) string {
	if cv.ids == nil {
		cv.ids = map[string]ast.Node{id: node}
		return id
	}
	if n, ok := cv.ids[id]; ok && n != node {
		prefix := id + "-"







|

>


<
|


|










<
<
<
<
|
<
<
>
|
<
>
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
>
|
|
|
|
|
|
|
|
|
>
|
>
|
<
<

<
<
<







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
	"zettelstore.de/z/ast"
	"zettelstore.de/z/encoder"
	"zettelstore.de/z/strfun"
)

// CleanupBlockSlice cleans the given block slice.
func CleanupBlockSlice(bs ast.BlockSlice) {
	cv := cleanupVisitor{
		textEnc: encoder.Create("text", nil),
		hasMark: false,
		doMark:  false,
	}

	ast.WalkBlockSlice(&cv, bs)
	if cv.hasMark {
		cv.doMark = true
		ast.WalkBlockSlice(&cv, bs)
	}
}

type cleanupVisitor struct {
	textEnc encoder.Encoder
	ids     map[string]ast.Node
	hasMark bool
	doMark  bool
}





func (cv *cleanupVisitor) Visit(node ast.Node) ast.Visitor {


	switch n := node.(type) {
	case *ast.HeadingNode:

		if cv.doMark || n == nil || n.Inlines == nil {
			return nil
		}
		var sb strings.Builder
		_, err := cv.textEnc.WriteInlines(&sb, n.Inlines)
		if err != nil {
			return nil
		}
		s := strfun.Slugify(sb.String())
		if len(s) > 0 {
			n.Slug = cv.addIdentifier(s, n)
		}















































		return nil

	case *ast.MarkNode:
		if !cv.doMark {
			cv.hasMark = true
			return nil
		}
		if n.Text == "" {
			n.Text = cv.addIdentifier("*", n)
			return nil
		}
		n.Text = cv.addIdentifier(n.Text, n)
		return nil
	}
	return cv
}






func (cv *cleanupVisitor) addIdentifier(id string, node ast.Node) string {
	if cv.ids == nil {
		cv.ids = map[string]ast.Node{id: node}
		return id
	}
	if n, ok := cv.ids[id]; ok && n != node {
		prefix := id + "-"

Changes to parser/markdown/markdown.go.

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
...
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
...
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
...
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
...
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
478
479
480
481
482
483
484
485
486
487
488
489
490
	return &ast.HRuleNode{
		Attrs: nil, //TODO
	}
}

func (p *mdP) acceptCodeBlock(node *gmAst.CodeBlock) *ast.VerbatimNode {
	return &ast.VerbatimNode{
		Code:  ast.VerbatimProg,
		Attrs: nil, //TODO
		Lines: p.acceptRawText(node),
	}
}

func (p *mdP) acceptFencedCodeBlock(node *gmAst.FencedCodeBlock) *ast.VerbatimNode {
	var attrs *ast.Attributes
	if language := node.Language(p.source); len(language) > 0 {
		attrs = attrs.Set("class", "language-"+cleanText(string(language), true))
	}
	return &ast.VerbatimNode{
		Code:  ast.VerbatimProg,
		Attrs: attrs,
		Lines: p.acceptRawText(node),
	}
}

func (p *mdP) acceptRawText(node gmAst.Node) []string {
	lines := node.Lines()
................................................................................
		result = append(result, string(line))
	}
	return result
}

func (p *mdP) acceptBlockquote(node *gmAst.Blockquote) *ast.NestedListNode {
	return &ast.NestedListNode{
		Code: ast.NestedListQuote,
		Items: []ast.ItemSlice{
			p.acceptItemSlice(node),
		},
	}
}

func (p *mdP) acceptList(node *gmAst.List) ast.ItemNode {
	code := ast.NestedListUnordered
	var attrs *ast.Attributes
	if node.IsOrdered() {
		code = ast.NestedListOrdered
		if node.Start != 1 {
			attrs = attrs.Set("start", fmt.Sprintf("%d", node.Start))
		}
	}
	items := make([]ast.ItemSlice, 0, node.ChildCount())
	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
		item, ok := child.(*gmAst.ListItem)
		if !ok {
			panic(fmt.Sprintf("Expected list item node, but got %v", child.Kind()))
		}
		items = append(items, p.acceptItemSlice(item))
	}
	return &ast.NestedListNode{
		Code:  code,
		Items: items,
		Attrs: attrs,
	}
}

func (p *mdP) acceptItemSlice(node gmAst.Node) ast.ItemSlice {
	result := make(ast.ItemSlice, 0, node.ChildCount())
................................................................................
		closure := string(node.ClosureLine.Value(p.source))
		if l := len(closure); l > 1 && closure[l-1] == '\n' {
			closure = closure[:l-1]
		}
		lines = append(lines, closure)
	}
	return &ast.VerbatimNode{
		Code:  ast.VerbatimHTML,
		Lines: lines,
	}
}

func (p *mdP) acceptInlineSlice(node gmAst.Node) ast.InlineSlice {
	result := make(ast.InlineSlice, 0, node.ChildCount())
	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
................................................................................
	}
	return sb.String()
}

func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice {
	return ast.InlineSlice{
		&ast.LiteralNode{
			Code:  ast.LiteralProg,
			Attrs: nil, //TODO
			Text:  cleanCodeSpan(string(node.Text(p.source))),
		},
	}
}

func cleanCodeSpan(text string) string {
................................................................................
		return text
	}
	sb.WriteString(text[lastPos:])
	return sb.String()
}

func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) ast.InlineSlice {
	code := ast.FormatEmph
	if node.Level == 2 {
		code = ast.FormatStrong
	}
	return ast.InlineSlice{
		&ast.FormatNode{
			Code:    code,
			Attrs:   nil, //TODO
			Inlines: p.acceptInlineSlice(node),
		},
	}
}

func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice {
................................................................................
	segs := make([]string, 0, node.Segments.Len())
	for i := 0; i < node.Segments.Len(); i++ {
		segment := node.Segments.At(i)
		segs = append(segs, string(segment.Value(p.source)))
	}
	return ast.InlineSlice{
		&ast.LiteralNode{
			Code:  ast.LiteralHTML,
			Attrs: nil, // TODO: add HTML as language
			Text:  strings.Join(segs, ""),
		},
	}
}







|











|







 







|







|


|













|







 







|







 







|







 







|

|



|







 







|





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
...
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
...
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
...
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
...
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
...
478
479
480
481
482
483
484
485
486
487
488
489
490
	return &ast.HRuleNode{
		Attrs: nil, //TODO
	}
}

func (p *mdP) acceptCodeBlock(node *gmAst.CodeBlock) *ast.VerbatimNode {
	return &ast.VerbatimNode{
		Kind:  ast.VerbatimProg,
		Attrs: nil, //TODO
		Lines: p.acceptRawText(node),
	}
}

func (p *mdP) acceptFencedCodeBlock(node *gmAst.FencedCodeBlock) *ast.VerbatimNode {
	var attrs *ast.Attributes
	if language := node.Language(p.source); len(language) > 0 {
		attrs = attrs.Set("class", "language-"+cleanText(string(language), true))
	}
	return &ast.VerbatimNode{
		Kind:  ast.VerbatimProg,
		Attrs: attrs,
		Lines: p.acceptRawText(node),
	}
}

func (p *mdP) acceptRawText(node gmAst.Node) []string {
	lines := node.Lines()
................................................................................
		result = append(result, string(line))
	}
	return result
}

func (p *mdP) acceptBlockquote(node *gmAst.Blockquote) *ast.NestedListNode {
	return &ast.NestedListNode{
		Kind: ast.NestedListQuote,
		Items: []ast.ItemSlice{
			p.acceptItemSlice(node),
		},
	}
}

func (p *mdP) acceptList(node *gmAst.List) ast.ItemNode {
	kind := ast.NestedListUnordered
	var attrs *ast.Attributes
	if node.IsOrdered() {
		kind = ast.NestedListOrdered
		if node.Start != 1 {
			attrs = attrs.Set("start", fmt.Sprintf("%d", node.Start))
		}
	}
	items := make([]ast.ItemSlice, 0, node.ChildCount())
	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
		item, ok := child.(*gmAst.ListItem)
		if !ok {
			panic(fmt.Sprintf("Expected list item node, but got %v", child.Kind()))
		}
		items = append(items, p.acceptItemSlice(item))
	}
	return &ast.NestedListNode{
		Kind:  kind,
		Items: items,
		Attrs: attrs,
	}
}

func (p *mdP) acceptItemSlice(node gmAst.Node) ast.ItemSlice {
	result := make(ast.ItemSlice, 0, node.ChildCount())
................................................................................
		closure := string(node.ClosureLine.Value(p.source))
		if l := len(closure); l > 1 && closure[l-1] == '\n' {
			closure = closure[:l-1]
		}
		lines = append(lines, closure)
	}
	return &ast.VerbatimNode{
		Kind:  ast.VerbatimHTML,
		Lines: lines,
	}
}

func (p *mdP) acceptInlineSlice(node gmAst.Node) ast.InlineSlice {
	result := make(ast.InlineSlice, 0, node.ChildCount())
	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
................................................................................
	}
	return sb.String()
}

func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice {
	return ast.InlineSlice{
		&ast.LiteralNode{
			Kind:  ast.LiteralProg,
			Attrs: nil, //TODO
			Text:  cleanCodeSpan(string(node.Text(p.source))),
		},
	}
}

func cleanCodeSpan(text string) string {
................................................................................
		return text
	}
	sb.WriteString(text[lastPos:])
	return sb.String()
}

func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) ast.InlineSlice {
	kind := ast.FormatEmph
	if node.Level == 2 {
		kind = ast.FormatStrong
	}
	return ast.InlineSlice{
		&ast.FormatNode{
			Kind:    kind,
			Attrs:   nil, //TODO
			Inlines: p.acceptInlineSlice(node),
		},
	}
}

func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice {
................................................................................
	segs := make([]string, 0, node.Segments.Len())
	for i := 0; i < node.Segments.Len(); i++ {
		segment := node.Segments.At(i)
		segs = append(segs, string(segment.Value(p.source)))
	}
	return ast.InlineSlice{
		&ast.LiteralNode{
			Kind:  ast.LiteralHTML,
			Attrs: nil, // TODO: add HTML as language
			Text:  strings.Join(segs, ""),
		},
	}
}

Changes to parser/none/none.go.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
	}
	return ast.BlockSlice{descrlist}
}

func getDescription(key, value string) ast.Description {
	return ast.Description{
		Term: ast.InlineSlice{&ast.TextNode{Text: key}},
		Descriptions: []ast.DescriptionSlice{
			ast.DescriptionSlice{
				&ast.ParaNode{
					Inlines: convertToInlineSlice(value, meta.Type(key)),
				},
			},
		},
	}
}

func convertToInlineSlice(value string, dt *meta.DescriptionType) ast.InlineSlice {
	var sliceData []string
	if dt.IsSet {
................................................................................
	return result
}

func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
	inp.SkipToEOL()
	return ast.InlineSlice{
		&ast.FormatNode{
			Code:  ast.FormatSpan,
			Attrs: &ast.Attributes{Attrs: map[string]string{"class": "warning"}},
			Inlines: ast.InlineSlice{
				&ast.TextNode{Text: "parser.meta.ParseInlines:"},
				&ast.SpaceNode{Lexeme: " "},
				&ast.TextNode{Text: "not"},
				&ast.SpaceNode{Lexeme: " "},
				&ast.TextNode{Text: "possible"},







|
<
|
|
|
<







 







|







35
36
37
38
39
40
41
42

43
44
45

46
47
48
49
50
51
52
..
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
	}
	return ast.BlockSlice{descrlist}
}

func getDescription(key, value string) ast.Description {
	return ast.Description{
		Term: ast.InlineSlice{&ast.TextNode{Text: key}},
		Descriptions: []ast.DescriptionSlice{{

			&ast.ParaNode{
				Inlines: convertToInlineSlice(value, meta.Type(key)),
			}},

		},
	}
}

func convertToInlineSlice(value string, dt *meta.DescriptionType) ast.InlineSlice {
	var sliceData []string
	if dt.IsSet {
................................................................................
	return result
}

func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
	inp.SkipToEOL()
	return ast.InlineSlice{
		&ast.FormatNode{
			Kind:  ast.FormatSpan,
			Attrs: &ast.Attributes{Attrs: map[string]string{"class": "warning"}},
			Inlines: ast.InlineSlice{
				&ast.TextNode{Text: "parser.meta.ParseInlines:"},
				&ast.SpaceNode{Lexeme: " "},
				&ast.TextNode{Text: "not"},
				&ast.SpaceNode{Lexeme: " "},
				&ast.TextNode{Text: "possible"},

Changes to parser/plain/plain.go.

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
..
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
		ParseInlines: parseInlines,
	})
}

func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
	return ast.BlockSlice{
		&ast.VerbatimNode{
			Code:  ast.VerbatimProg,
			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
			Lines: readLines(inp),
		},
	}
}

func readLines(inp *input.Input) (lines []string) {
................................................................................
	}
}

func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
	inp.SkipToEOL()
	return ast.InlineSlice{
		&ast.LiteralNode{
			Code:  ast.LiteralProg,
			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
			Text:  inp.Src[0:inp.Pos],
		},
	}
}

func parseSVGBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {







|







 







|







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
..
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
		ParseInlines: parseInlines,
	})
}

func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
	return ast.BlockSlice{
		&ast.VerbatimNode{
			Kind:  ast.VerbatimProg,
			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
			Lines: readLines(inp),
		},
	}
}

func readLines(inp *input.Input) (lines []string) {
................................................................................
	}
}

func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
	inp.SkipToEOL()
	return ast.InlineSlice{
		&ast.LiteralNode{
			Kind:  ast.LiteralProg,
			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
			Text:  inp.Src[0:inp.Pos],
		},
	}
}

func parseSVGBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {

Changes to parser/zettelmark/block.go.

165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
...
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
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331




332
333
334
335
336
337
338
339
340
341
342
343
344
345
...
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
...
480
481
482
483
484
485
486



487
488
489
490
491
492
493
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	if inp.Ch == input.EOS {
		return nil, false
	}
	var code ast.VerbatimCode
	switch fch {
	case '`', runeModGrave:
		code = ast.VerbatimProg
	case '%':
		code = ast.VerbatimComment
	default:
		panic(fmt.Sprintf("%q is not a verbatim char", fch))
	}
	rn = &ast.VerbatimNode{Code: code, Attrs: attrs}
	for {
		inp.EatEOL()
		posL := inp.Pos
		switch inp.Ch {
		case fch:
			if cp.countDelim(fch) >= cnt {
				inp.SkipToEOL()
................................................................................
			return nil, false
		}
		inp.SkipToEOL()
		rn.Lines = append(rn.Lines, inp.Src[posL:inp.Pos])
	}
}

var runeRegion = map[rune]ast.RegionCode{
	':': ast.RegionSpan,
	'<': ast.RegionQuote,
	'"': ast.RegionVerse,
}

// parseRegion parses a block region.
func (cp *zmkP) parseRegion() (rn *ast.RegionNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	code, ok := runeRegion[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a region char", fch))
	}
	cnt := cp.countDelim(fch)
	if cnt < 3 {
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	if inp.Ch == input.EOS {
		return nil, false
	}
	rn = &ast.RegionNode{Code: code, Attrs: attrs}
	var lastPara *ast.ParaNode
	inp.EatEOL()
	for {
		posL := inp.Pos
		switch inp.Ch {
		case fch:
			if cp.countDelim(fch) >= cnt {
................................................................................
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	return &ast.HRuleNode{Attrs: attrs}, true
}

var mapRuneNestedList = map[rune]ast.NestedListCode{
	'*': ast.NestedListUnordered,
	'#': ast.NestedListOrdered,
	'>': ast.NestedListQuote,
}

// parseNestedList parses a list.
func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) {
	inp := cp.inp
	codes := cp.parseNestedListCodes()
	if codes == nil {
		return nil, false
	}
	cp.skipSpace()
	if codes[len(codes)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) {
		return nil, false
	}

	if len(codes) < len(cp.lists) {
		cp.lists = cp.lists[:len(codes)]
	}
	ln, newLnCount := cp.buildNestedList(codes)




	ln.Items = append(ln.Items, ast.ItemSlice{cp.parseLinePara()})
	return cp.cleanupParsedNestedList(newLnCount)
}

func (cp *zmkP) parseNestedListCodes() []ast.NestedListCode {
	inp := cp.inp
	codes := make([]ast.NestedListCode, 0, 4)
	for {
		code, ok := mapRuneNestedList[inp.Ch]
		if !ok {
			panic(fmt.Sprintf("%q is not a region char", inp.Ch))
		}
		codes = append(codes, code)
		inp.Next()
................................................................................
		default:
			return nil
		}
	}

}

func (cp *zmkP) buildNestedList(codes []ast.NestedListCode) (ln *ast.NestedListNode, newLnCount int) {
	for i, code := range codes {
		if i < len(cp.lists) {
			if cp.lists[i].Code != code {
				ln = &ast.NestedListNode{Code: code}
				newLnCount++
				cp.lists[i] = ln
				cp.lists = cp.lists[:i+1]
			} else {
				ln = cp.lists[i]
			}
		} else {
			ln = &ast.NestedListNode{Code: code}
			newLnCount++
			cp.lists = append(cp.lists, ln)
		}
	}
	return ln, newLnCount
}

................................................................................
	}
	cp.lists = cp.lists[:cnt]
	if cnt == 0 {
		return false
	}
	ln := cp.lists[cnt-1]
	pn := cp.parseLinePara()



	lbn := ln.Items[len(ln.Items)-1]
	if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok {
		lpn.Inlines = append(lpn.Inlines, pn.Inlines...)
	} else {
		ln.Items[len(ln.Items)-1] = append(ln.Items[len(ln.Items)-1], pn)
	}
	return true







|


|

|



|







 







|









|












|







 







|








|
|



|



|
|

|
>
>
>
>
|



|

|







 







|
|

|
|







|







 







>
>
>







165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
...
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
...
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
...
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
...
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	if inp.Ch == input.EOS {
		return nil, false
	}
	var kind ast.VerbatimKind
	switch fch {
	case '`', runeModGrave:
		kind = ast.VerbatimProg
	case '%':
		kind = ast.VerbatimComment
	default:
		panic(fmt.Sprintf("%q is not a verbatim char", fch))
	}
	rn = &ast.VerbatimNode{Kind: kind, Attrs: attrs}
	for {
		inp.EatEOL()
		posL := inp.Pos
		switch inp.Ch {
		case fch:
			if cp.countDelim(fch) >= cnt {
				inp.SkipToEOL()
................................................................................
			return nil, false
		}
		inp.SkipToEOL()
		rn.Lines = append(rn.Lines, inp.Src[posL:inp.Pos])
	}
}

var runeRegion = map[rune]ast.RegionKind{
	':': ast.RegionSpan,
	'<': ast.RegionQuote,
	'"': ast.RegionVerse,
}

// parseRegion parses a block region.
func (cp *zmkP) parseRegion() (rn *ast.RegionNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	kind, ok := runeRegion[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a region char", fch))
	}
	cnt := cp.countDelim(fch)
	if cnt < 3 {
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	if inp.Ch == input.EOS {
		return nil, false
	}
	rn = &ast.RegionNode{Kind: kind, Attrs: attrs}
	var lastPara *ast.ParaNode
	inp.EatEOL()
	for {
		posL := inp.Pos
		switch inp.Ch {
		case fch:
			if cp.countDelim(fch) >= cnt {
................................................................................
		return nil, false
	}
	attrs := cp.parseAttributes(true)
	inp.SkipToEOL()
	return &ast.HRuleNode{Attrs: attrs}, true
}

var mapRuneNestedList = map[rune]ast.NestedListKind{
	'*': ast.NestedListUnordered,
	'#': ast.NestedListOrdered,
	'>': ast.NestedListQuote,
}

// parseNestedList parses a list.
func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) {
	inp := cp.inp
	kinds := cp.parseNestedListKinds()
	if kinds == nil {
		return nil, false
	}
	cp.skipSpace()
	if kinds[len(kinds)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) {
		return nil, false
	}

	if len(kinds) < len(cp.lists) {
		cp.lists = cp.lists[:len(kinds)]
	}
	ln, newLnCount := cp.buildNestedList(kinds)
	pn := cp.parseLinePara()
	if pn == nil {
		pn = &ast.ParaNode{}
	}
	ln.Items = append(ln.Items, ast.ItemSlice{pn})
	return cp.cleanupParsedNestedList(newLnCount)
}

func (cp *zmkP) parseNestedListKinds() []ast.NestedListKind {
	inp := cp.inp
	codes := make([]ast.NestedListKind, 0, 4)
	for {
		code, ok := mapRuneNestedList[inp.Ch]
		if !ok {
			panic(fmt.Sprintf("%q is not a region char", inp.Ch))
		}
		codes = append(codes, code)
		inp.Next()
................................................................................
		default:
			return nil
		}
	}

}

func (cp *zmkP) buildNestedList(kinds []ast.NestedListKind) (ln *ast.NestedListNode, newLnCount int) {
	for i, kind := range kinds {
		if i < len(cp.lists) {
			if cp.lists[i].Kind != kind {
				ln = &ast.NestedListNode{Kind: kind}
				newLnCount++
				cp.lists[i] = ln
				cp.lists = cp.lists[:i+1]
			} else {
				ln = cp.lists[i]
			}
		} else {
			ln = &ast.NestedListNode{Kind: kind}
			newLnCount++
			cp.lists = append(cp.lists, ln)
		}
	}
	return ln, newLnCount
}

................................................................................
	}
	cp.lists = cp.lists[:cnt]
	if cnt == 0 {
		return false
	}
	ln := cp.lists[cnt-1]
	pn := cp.parseLinePara()
	if pn == nil {
		pn = &ast.ParaNode{}
	}
	lbn := ln.Items[len(ln.Items)-1]
	if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok {
		lpn.Inlines = append(lpn.Inlines, pn.Inlines...)
	} else {
		ln.Items[len(ln.Items)-1] = append(ln.Items[len(ln.Items)-1], pn)
	}
	return true

Changes to parser/zettelmark/inline.go.

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
...
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
	for inp.Ch == '%' {
		inp.Next()
	}
	cp.skipSpace()
	pos := inp.Pos
	for {
		if input.IsEOLEOS(inp.Ch) {
			return &ast.LiteralNode{Code: ast.LiteralComment, Text: inp.Src[pos:inp.Pos]}, true
		}
		inp.Next()
	}
}

var mapRuneFormat = map[rune]ast.FormatCode{
	'/':  ast.FormatItalic,
	'*':  ast.FormatBold,
	'_':  ast.FormatUnder,
	'~':  ast.FormatStrike,
	'\'': ast.FormatMonospace,
	'^':  ast.FormatSuper,
	',':  ast.FormatSub,
................................................................................
	';':  ast.FormatSmall,
	':':  ast.FormatSpan,
}

func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	code, ok := mapRuneFormat[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a formatting char", fch))
	}
	inp.Next() // read 2nd formatting character
	if inp.Ch != fch {
		return nil, false
	}
	inp.Next()
	fn := &ast.FormatNode{Code: code}
	for {
		if inp.Ch == input.EOS {
			return nil, false
		}
		if inp.Ch == fch {
			inp.Next()
			if inp.Ch == fch {
................................................................................
				return nil, false
			}
			fn.Inlines = append(fn.Inlines, in)
		}
	}
}

var mapRuneLiteral = map[rune]ast.LiteralCode{
	'`':          ast.LiteralProg,
	runeModGrave: ast.LiteralProg,
	'+':          ast.LiteralKeyb,
	'=':          ast.LiteralOutput,
}

func (cp *zmkP) parseLiteral() (res ast.InlineNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	code, ok := mapRuneLiteral[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a formatting char", fch))
	}
	inp.Next() // read 2nd formatting character
	if inp.Ch != fch {
		return nil, false
	}
	fn := &ast.LiteralNode{Code: code}
	inp.Next()
	var sb strings.Builder
	for {
		if inp.Ch == input.EOS {
			return nil, false
		}
		if inp.Ch == fch {







|





|







 







|








|







 







|









|







|







358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
...
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
	for inp.Ch == '%' {
		inp.Next()
	}
	cp.skipSpace()
	pos := inp.Pos
	for {
		if input.IsEOLEOS(inp.Ch) {
			return &ast.LiteralNode{Kind: ast.LiteralComment, Text: inp.Src[pos:inp.Pos]}, true
		}
		inp.Next()
	}
}

var mapRuneFormat = map[rune]ast.FormatKind{
	'/':  ast.FormatItalic,
	'*':  ast.FormatBold,
	'_':  ast.FormatUnder,
	'~':  ast.FormatStrike,
	'\'': ast.FormatMonospace,
	'^':  ast.FormatSuper,
	',':  ast.FormatSub,
................................................................................
	';':  ast.FormatSmall,
	':':  ast.FormatSpan,
}

func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	kind, ok := mapRuneFormat[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a formatting char", fch))
	}
	inp.Next() // read 2nd formatting character
	if inp.Ch != fch {
		return nil, false
	}
	inp.Next()
	fn := &ast.FormatNode{Kind: kind}
	for {
		if inp.Ch == input.EOS {
			return nil, false
		}
		if inp.Ch == fch {
			inp.Next()
			if inp.Ch == fch {
................................................................................
				return nil, false
			}
			fn.Inlines = append(fn.Inlines, in)
		}
	}
}

var mapRuneLiteral = map[rune]ast.LiteralKind{
	'`':          ast.LiteralProg,
	runeModGrave: ast.LiteralProg,
	'+':          ast.LiteralKeyb,
	'=':          ast.LiteralOutput,
}

func (cp *zmkP) parseLiteral() (res ast.InlineNode, success bool) {
	inp := cp.inp
	fch := inp.Ch
	kind, ok := mapRuneLiteral[fch]
	if !ok {
		panic(fmt.Sprintf("%q is not a formatting char", fch))
	}
	inp.Next() // read 2nd formatting character
	if inp.Ch != fch {
		return nil, false
	}
	fn := &ast.LiteralNode{Kind: kind}
	inp.Next()
	var sb strings.Builder
	for {
		if inp.Ch == input.EOS {
			return nil, false
		}
		if inp.Ch == fch {

Changes to parser/zettelmark/node.go.

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
// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package zettelmark provides a parser for zettelmarkup.
package zettelmark

import (
	"zettelstore.de/z/ast"
)

// Internal nodes for parsing zettelmark. These will be removed in
// post-processing.

// nullItemNode specifies a removable placeholder for an item node.
type nullItemNode struct {
	ast.ItemNode
}

// Accept a visitor and visit the node.
func (nn *nullItemNode) Accept(v ast.Visitor) {}

// nullDescriptionNode specifies a removable placeholder.
type nullDescriptionNode struct {
	ast.DescriptionNode
}

// Accept a visitor and visit the node.
func (nn *nullDescriptionNode) Accept(v ast.Visitor) {}







<
|
<









<
<
<




<
<
<
7
8
9
10
11
12
13

14

15
16
17
18
19
20
21
22
23



24
25
26
27



// Public License). Please see file LICENSE.txt for your rights and obligations
// under this license.
//-----------------------------------------------------------------------------

// Package zettelmark provides a parser for zettelmarkup.
package zettelmark


import "zettelstore.de/z/ast"


// Internal nodes for parsing zettelmark. These will be removed in
// post-processing.

// nullItemNode specifies a removable placeholder for an item node.
type nullItemNode struct {
	ast.ItemNode
}




// nullDescriptionNode specifies a removable placeholder.
type nullDescriptionNode struct {
	ast.DescriptionNode
}



Changes to parser/zettelmark/post-processor.go.

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
...
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
251
252
253
254
255
256
257
258
259
260
261
262
263

264
265
266
267
268
269
270
...
281
282
283
284
285
286
287



288
289

290
291
292
293
294
295
296
...
308
309
310
311
312
313
314



315
316

317
318
319
320
321
322
323
...
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
}

// postProcessor is a visitor that cleans the abstract syntax tree.
type postProcessor struct {
	inVerse bool
}

// VisitPara post-processes a paragraph.
func (pp *postProcessor) VisitPara(pn *ast.ParaNode) {
	if pn != nil {
		pn.Inlines = pp.processInlineSlice(pn.Inlines)
	}
}

// VisitVerbatim does nothing, no post-processing needed.
func (pp *postProcessor) VisitVerbatim(vn *ast.VerbatimNode) {}

// VisitRegion post-processes a region.
func (pp *postProcessor) VisitRegion(rn *ast.RegionNode) {
	oldVerse := pp.inVerse
	if rn.Code == ast.RegionVerse {
		pp.inVerse = true
	}
	rn.Blocks = pp.processBlockSlice(rn.Blocks)
	pp.inVerse = oldVerse
	rn.Inlines = pp.processInlineSlice(rn.Inlines)
}

// VisitHeading post-processes a heading.
func (pp *postProcessor) VisitHeading(hn *ast.HeadingNode) {
	hn.Inlines = pp.processInlineSlice(hn.Inlines)
}

// VisitHRule does nothing, no post-processing needed.
func (pp *postProcessor) VisitHRule(hn *ast.HRuleNode) {}

// VisitList post-processes a list.
func (pp *postProcessor) VisitNestedList(ln *ast.NestedListNode) {
	for i, item := range ln.Items {
		ln.Items[i] = pp.processItemSlice(item)
	}
}

// VisitDescriptionList post-processes a description list.
func (pp *postProcessor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	for i, def := range dn.Descriptions {
		dn.Descriptions[i].Term = pp.processInlineSlice(def.Term)
		for j, b := range def.Descriptions {
			dn.Descriptions[i].Descriptions[j] = pp.processDescriptionSlice(b)
		}
	}
}

// VisitTable post-processes a table.
func (pp *postProcessor) VisitTable(tn *ast.TableNode) {
	width := tableWidth(tn)
	tn.Align = make([]ast.Alignment, width)
	for i := 0; i < width; i++ {
		tn.Align[i] = ast.AlignDefault
	}
	if len(tn.Rows) > 0 && isHeaderRow(tn.Rows[0]) {
		tn.Header = tn.Rows[0]
		tn.Rows = tn.Rows[1:]
		pp.visitTableHeader(tn)
	}
	if len(tn.Header) > 0 {
		tn.Header = appendCells(tn.Header, width, tn.Align)
		for i, cell := range tn.Header {
			pp.processCell(cell, tn.Align[i])
		}
	}
	pp.visitTableRows(tn, width)
}

func (pp *postProcessor) visitTableHeader(tn *ast.TableNode) {
	for pos, cell := range tn.Header {
		inlines := cell.Inlines
		if len(inlines) == 0 {
			continue
................................................................................
		}
	} else {
		cell.Align = colAlign
	}
	cell.Inlines = pp.processInlineSlice(cell.Inlines)
}

// VisitBLOB does nothing.
func (pp *postProcessor) VisitBLOB(bn *ast.BLOBNode) {}

// VisitText does nothing.
func (pp *postProcessor) VisitText(tn *ast.TextNode) {}

// VisitTag does nothing.
func (pp *postProcessor) VisitTag(tn *ast.TagNode) {}

// VisitSpace does nothing.
func (pp *postProcessor) VisitSpace(sn *ast.SpaceNode) {}

// VisitBreak does nothing.
func (pp *postProcessor) VisitBreak(bn *ast.BreakNode) {}

// VisitLink post-processes a link.
func (pp *postProcessor) VisitLink(ln *ast.LinkNode) {
	ln.Inlines = pp.processInlineSlice(ln.Inlines)
}

// VisitImage post-processes an image.
func (pp *postProcessor) VisitImage(in *ast.ImageNode) {
	if len(in.Inlines) > 0 {
		in.Inlines = pp.processInlineSlice(in.Inlines)
	}
}

// VisitCite post-processes a citation.
func (pp *postProcessor) VisitCite(cn *ast.CiteNode) {
	cn.Inlines = pp.processInlineSlice(cn.Inlines)
}

// VisitFootnote post-processes a footnote.
func (pp *postProcessor) VisitFootnote(fn *ast.FootnoteNode) {
	fn.Inlines = pp.processInlineSlice(fn.Inlines)
}

// VisitMark post-processes a mark.
func (pp *postProcessor) VisitMark(mn *ast.MarkNode) {}

var mapSemantic = map[ast.FormatCode]ast.FormatCode{
	ast.FormatItalic: ast.FormatEmph,
	ast.FormatBold:   ast.FormatStrong,
	ast.FormatUnder:  ast.FormatInsert,
	ast.FormatStrike: ast.FormatDelete,
}

// VisitFormat post-processes formatted inline nodes.
func (pp *postProcessor) VisitFormat(fn *ast.FormatNode) {
	if fn.Attrs != nil && fn.Attrs.HasDefault() {
		if newCode, ok := mapSemantic[fn.Code]; ok {
			fn.Attrs.RemoveDefault()
			fn.Code = newCode
		}
	}
	fn.Inlines = pp.processInlineSlice(fn.Inlines)
}

// VisitLiteral post-processes an inline literal.
func (pp *postProcessor) VisitLiteral(cn *ast.LiteralNode) {}

// processBlockSlice post-processes a slice of blocks.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processBlockSlice(bns ast.BlockSlice) ast.BlockSlice {
	for _, bn := range bns {
		bn.Accept(pp)
	}

	fromPos, toPos := 0, 0
	for fromPos < len(bns) {
		bns[toPos] = bns[fromPos]
		fromPos++
		switch bn := bns[toPos].(type) {
		case *ast.ParaNode:
			if len(bn.Inlines) > 0 {
................................................................................
	}
	return bns[:toPos:toPos]
}

// processItemSlice post-processes a slice of items.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice {



	for _, in := range ins {
		in.Accept(pp)

	}
	fromPos, toPos := 0, 0
	for fromPos < len(ins) {
		ins[toPos] = ins[fromPos]
		fromPos++
		switch in := ins[toPos].(type) {
		case *ast.ParaNode:
................................................................................
	}
	return ins[:toPos:toPos]
}

// processDescriptionSlice post-processes a slice of descriptions.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processDescriptionSlice(dns ast.DescriptionSlice) ast.DescriptionSlice {



	for _, dn := range dns {
		dn.Accept(pp)

	}
	fromPos, toPos := 0, 0
	for fromPos < len(dns) {
		dns[toPos] = dns[fromPos]
		fromPos++
		switch dn := dns[toPos].(type) {
		case *ast.ParaNode:
................................................................................

// processInlineSlice post-processes a slice of inline nodes.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processInlineSlice(ins ast.InlineSlice) ast.InlineSlice {
	if len(ins) == 0 {
		return nil
	}
	for _, in := range ins {
		in.Accept(pp)
	}

	if !pp.inVerse {
		ins = processInlineSliceHead(ins)
	}
	toPos := pp.processInlineSliceCopy(ins)
	toPos = pp.processInlineSliceTail(ins, toPos)
	ins = ins[:toPos:toPos]







|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<







 







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|






<
<
<
<
<
<
<
<
<
<
<
<
<
<



|
|

>







 







>
>
>

<
>







 







>
>
>

<
>







 







|
<
<







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
...
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
...
224
225
226
227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242
...
254
255
256
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272
...
286
287
288
289
290
291
292
293


294
295
296
297
298
299
300
}

// postProcessor is a visitor that cleans the abstract syntax tree.
type postProcessor struct {
	inVerse bool
}

func (pp *postProcessor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.RegionNode:
		oldVerse := pp.inVerse
		if n.Kind == ast.RegionVerse {
			pp.inVerse = true
		}
		n.Blocks = pp.processBlockSlice(n.Blocks)
		pp.inVerse = oldVerse
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.HeadingNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.NestedListNode:
		for i, item := range n.Items {
			n.Items[i] = pp.processItemSlice(item)
		}
	case *ast.DescriptionListNode:
		for i, def := range n.Descriptions {
			n.Descriptions[i].Term = pp.processInlineSlice(def.Term)
			for j, b := range def.Descriptions {
				n.Descriptions[i].Descriptions[j] = pp.processDescriptionSlice(b)
			}
		}
	case *ast.TableNode:
		width := tableWidth(n)
		n.Align = make([]ast.Alignment, width)
		for i := 0; i < width; i++ {
			n.Align[i] = ast.AlignDefault
		}
		if len(n.Rows) > 0 && isHeaderRow(n.Rows[0]) {
			n.Header = n.Rows[0]
			n.Rows = n.Rows[1:]
			pp.visitTableHeader(n)
		}
		if len(n.Header) > 0 {
			n.Header = appendCells(n.Header, width, n.Align)
			for i, cell := range n.Header {
				pp.processCell(cell, n.Align[i])
			}
		}
		pp.visitTableRows(n, width)
	case *ast.LinkNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.ImageNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.CiteNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.FootnoteNode:
		n.Inlines = pp.processInlineSlice(n.Inlines)
	case *ast.FormatNode:
		if n.Attrs != nil && n.Attrs.HasDefault() {
			if newKind, ok := mapSemantic[n.Kind]; ok {
				n.Attrs.RemoveDefault()
				n.Kind = newKind
			}
		}
		n.Inlines = pp.processInlineSlice(n.Inlines)
	}
	return nil




}

func (pp *postProcessor) visitTableHeader(tn *ast.TableNode) {
	for pos, cell := range tn.Header {
		inlines := cell.Inlines
		if len(inlines) == 0 {
			continue
................................................................................
		}
	} else {
		cell.Align = colAlign
	}
	cell.Inlines = pp.processInlineSlice(cell.Inlines)
}









































var mapSemantic = map[ast.FormatKind]ast.FormatKind{
	ast.FormatItalic: ast.FormatEmph,
	ast.FormatBold:   ast.FormatStrong,
	ast.FormatUnder:  ast.FormatInsert,
	ast.FormatStrike: ast.FormatDelete,
}















// processBlockSlice post-processes a slice of blocks.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processBlockSlice(bns ast.BlockSlice) ast.BlockSlice {
	if len(bns) == 0 {
		return nil
	}
	ast.WalkBlockSlice(pp, bns)
	fromPos, toPos := 0, 0
	for fromPos < len(bns) {
		bns[toPos] = bns[fromPos]
		fromPos++
		switch bn := bns[toPos].(type) {
		case *ast.ParaNode:
			if len(bn.Inlines) > 0 {
................................................................................
	}
	return bns[:toPos:toPos]
}

// processItemSlice post-processes a slice of items.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice {
	if len(ins) == 0 {
		return nil
	}
	for _, in := range ins {

		ast.Walk(pp, in)
	}
	fromPos, toPos := 0, 0
	for fromPos < len(ins) {
		ins[toPos] = ins[fromPos]
		fromPos++
		switch in := ins[toPos].(type) {
		case *ast.ParaNode:
................................................................................
	}
	return ins[:toPos:toPos]
}

// processDescriptionSlice post-processes a slice of descriptions.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processDescriptionSlice(dns ast.DescriptionSlice) ast.DescriptionSlice {
	if len(dns) == 0 {
		return nil
	}
	for _, dn := range dns {

		ast.Walk(pp, dn)
	}
	fromPos, toPos := 0, 0
	for fromPos < len(dns) {
		dns[toPos] = dns[fromPos]
		fromPos++
		switch dn := dns[toPos].(type) {
		case *ast.ParaNode:
................................................................................

// processInlineSlice post-processes a slice of inline nodes.
// It is one of the working horses for post-processing.
func (pp *postProcessor) processInlineSlice(ins ast.InlineSlice) ast.InlineSlice {
	if len(ins) == 0 {
		return nil
	}
	ast.WalkInlineSlice(pp, ins)



	if !pp.inVerse {
		ins = processInlineSliceHead(ins)
	}
	toPos := pp.processInlineSliceCopy(ins)
	toPos = pp.processInlineSliceTail(ins, toPos)
	ins = ins[:toPos:toPos]

Changes to parser/zettelmark/zettelmark_test.go.

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
...
612
613
614
615
616
617
618

619


620
621
622




623




624
625





















































































































































626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870

871
872

873
874
875
876
877
878
879

	for tcn, tc := range tcs {
		t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) {
			st.Helper()
			inp := input.NewInput(tc.source)
			bns := parser.ParseBlocks(inp, nil, meta.ValueSyntaxZmk)
			var tv TestVisitor
			tv.visitBlockSlice(bns)
			got := tv.String()
			if tc.want != got {
				st.Errorf("\nwant=%q\n got=%q", tc.want, got)
			}
		})
	}
}
................................................................................

		// A HRule creates a new list
		{"* abc\n---\n* def", "(UL {(PARA abc)})(HR)(UL {(PARA def)})"},

		// Changing list type adds a new list
		{"* abc\n# def", "(UL {(PARA abc)})(OL {(PARA def)})"},

		// Quotation lists mayx have empty items
		{">", "(QL {})"},
	})
}

func TestEnumAfterPara(t *testing.T) {
	checkTcs(t, TestCases{
		{"abc\n* def", "(PARA abc)(UL {(PARA def)})"},
................................................................................

// TestVisitor serializes the abstract syntax tree to a string.
type TestVisitor struct {
	b strings.Builder
}

func (tv *TestVisitor) String() string { return tv.b.String() }

func (tv *TestVisitor) VisitPara(pn *ast.ParaNode) {


	tv.b.WriteString("(PARA")
	tv.visitInlineSlice(pn.Inlines)
	tv.b.WriteByte(')')




}





var mapVerbatimCode = map[ast.VerbatimCode]string{





















































































































































	ast.VerbatimProg: "(PROG",
}

func (tv *TestVisitor) VisitVerbatim(vn *ast.VerbatimNode) {
	code, ok := mapVerbatimCode[vn.Code]
	if !ok {
		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
	}
	tv.b.WriteString(code)
	for _, line := range vn.Lines {
		tv.b.WriteByte('\n')
		tv.b.WriteString(line)
	}
	tv.b.WriteByte(')')
	tv.visitAttributes(vn.Attrs)
}

var mapRegionCode = map[ast.RegionCode]string{
	ast.RegionSpan:  "(SPAN",
	ast.RegionQuote: "(QUOTE",
	ast.RegionVerse: "(VERSE",
}

// VisitRegion stores information about a region.
func (tv *TestVisitor) VisitRegion(rn *ast.RegionNode) {
	code, ok := mapRegionCode[rn.Code]
	if !ok {
		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
	}
	tv.b.WriteString(code)
	if rn.Blocks != nil {
		tv.b.WriteByte(' ')
		tv.visitBlockSlice(rn.Blocks)
	}
	if len(rn.Inlines) > 0 {
		tv.b.WriteString(" (LINE")
		tv.visitInlineSlice(rn.Inlines)
		tv.b.WriteByte(')')
	}
	tv.b.WriteByte(')')
	tv.visitAttributes(rn.Attrs)
}

func (tv *TestVisitor) VisitHeading(hn *ast.HeadingNode) {
	fmt.Fprintf(&tv.b, "(H%d", hn.Level)
	tv.visitInlineSlice(hn.Inlines)
	tv.b.WriteByte(')')
	tv.visitAttributes(hn.Attrs)
}
func (tv *TestVisitor) VisitHRule(hn *ast.HRuleNode) {
	tv.b.WriteString("(HR)")
	tv.visitAttributes(hn.Attrs)
}

var mapNestedListCode = map[ast.NestedListCode]string{
	ast.NestedListOrdered:   "(OL",
	ast.NestedListUnordered: "(UL",
	ast.NestedListQuote:     "(QL",
}

func (tv *TestVisitor) VisitNestedList(ln *ast.NestedListNode) {
	tv.b.WriteString(mapNestedListCode[ln.Code])
	for _, item := range ln.Items {
		tv.b.WriteString(" {")
		tv.visitItemSlice(item)
		tv.b.WriteByte('}')
	}
	tv.b.WriteByte(')')
}
func (tv *TestVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
	tv.b.WriteString("(DL")
	for _, def := range dn.Descriptions {
		tv.b.WriteString(" (DT")
		tv.visitInlineSlice(def.Term)
		tv.b.WriteByte(')')
		for _, b := range def.Descriptions {
			tv.b.WriteString(" (DD ")
			tv.visitDescriptionSlice(b)
			tv.b.WriteByte(')')
		}
	}
	tv.b.WriteByte(')')
}

var alignString = map[ast.Alignment]string{
	ast.AlignDefault: "",
	ast.AlignLeft:    "l",
	ast.AlignCenter:  "c",
	ast.AlignRight:   "r",
}

// VisitTable emits a HTML table.
func (tv *TestVisitor) VisitTable(tn *ast.TableNode) {
	tv.b.WriteString("(TAB")
	if len(tn.Header) > 0 {
		tv.b.WriteString(" (TR")
		for _, cell := range tn.Header {
			tv.b.WriteString(" (TH")
			tv.b.WriteString(alignString[cell.Align])
			tv.visitInlineSlice(cell.Inlines)
			tv.b.WriteString(")")
		}
		tv.b.WriteString(")")
	}
	if len(tn.Rows) > 0 {
		tv.b.WriteString(" ")
		for _, row := range tn.Rows {
			tv.b.WriteString("(TR")
			for i, cell := range row {
				if i == 0 {
					tv.b.WriteString(" ")
				}
				tv.b.WriteString("(TD")
				tv.b.WriteString(alignString[cell.Align])
				tv.visitInlineSlice(cell.Inlines)
				tv.b.WriteString(")")
			}
			tv.b.WriteString(")")
		}
	}
	tv.b.WriteString(")")
}

func (tv *TestVisitor) VisitBLOB(bn *ast.BLOBNode) {
	tv.b.WriteString("(BLOB ")
	tv.b.WriteString(bn.Syntax)
	tv.b.WriteString(")")
}

func (tv *TestVisitor) VisitText(tn *ast.TextNode) {
	tv.b.WriteString(tn.Text)
}
func (tv *TestVisitor) VisitTag(tn *ast.TagNode) {
	tv.b.WriteByte('#')
	tv.b.WriteString(tn.Tag)
	tv.b.WriteByte('#')
}
func (tv *TestVisitor) VisitSpace(sn *ast.SpaceNode) {
	if len(sn.Lexeme) == 1 {
		tv.b.WriteString("SP")
	} else {
		fmt.Fprintf(&tv.b, "SP%d", len(sn.Lexeme))
	}
}
func (tv *TestVisitor) VisitBreak(bn *ast.BreakNode) {
	if bn.Hard {
		tv.b.WriteString("HB")
	} else {
		tv.b.WriteString("SB")
	}
}
func (tv *TestVisitor) VisitLink(tn *ast.LinkNode) {
	fmt.Fprintf(&tv.b, "(LINK %s", tn.Ref)
	tv.visitInlineSlice(tn.Inlines)
	tv.b.WriteByte(')')
	tv.visitAttributes(tn.Attrs)
}
func (tv *TestVisitor) VisitImage(in *ast.ImageNode) {
	fmt.Fprintf(&tv.b, "(IMAGE %s", in.Ref)
	tv.visitInlineSlice(in.Inlines)
	tv.b.WriteByte(')')
	tv.visitAttributes(in.Attrs)
}
func (tv *TestVisitor) VisitCite(cn *ast.CiteNode) {
	fmt.Fprintf(&tv.b, "(CITE %s", cn.Key)
	tv.visitInlineSlice(cn.Inlines)
	tv.b.WriteByte(')')
	tv.visitAttributes(cn.Attrs)
}
func (tv *TestVisitor) VisitFootnote(fn *ast.FootnoteNode) {
	tv.b.WriteString("(FN")
	tv.visitInlineSlice(fn.Inlines)
	tv.b.WriteByte(')')
	tv.visitAttributes(fn.Attrs)
}
func (tv *TestVisitor) VisitMark(mn *ast.MarkNode) {
	tv.b.WriteString("(MARK")
	if len(mn.Text) > 0 {
		tv.b.WriteByte(' ')
		tv.b.WriteString(mn.Text)
	}
	tv.b.WriteByte(')')
}

var mapCode = map[ast.FormatCode]rune{
	ast.FormatItalic:    '/',
	ast.FormatBold:      '*',
	ast.FormatUnder:     '_',
	ast.FormatStrike:    '~',
	ast.FormatMonospace: '\'',
	ast.FormatSuper:     '^',
	ast.FormatSub:       ',',
	ast.FormatQuote:     '"',
	ast.FormatQuotation: '<',
	ast.FormatSmall:     ';',
	ast.FormatSpan:      ':',
}

func (tv *TestVisitor) VisitFormat(fn *ast.FormatNode) {
	fmt.Fprintf(&tv.b, "{%c", mapCode[fn.Code])
	tv.visitInlineSlice(fn.Inlines)
	tv.b.WriteByte('}')
	tv.visitAttributes(fn.Attrs)
}

var mapLiteralCode = map[ast.LiteralCode]rune{
	ast.LiteralProg:    '`',
	ast.LiteralKeyb:    '+',
	ast.LiteralOutput:  '=',
	ast.LiteralComment: '%',
}

func (tv *TestVisitor) VisitLiteral(ln *ast.LiteralNode) {
	code, ok := mapLiteralCode[ln.Code]
	if !ok {
		panic(fmt.Sprintf("No element for code %v", ln.Code))
	}
	tv.b.WriteByte('{')
	tv.b.WriteRune(code)
	if len(ln.Text) > 0 {
		tv.b.WriteByte(' ')
		tv.b.WriteString(ln.Text)
	}
	tv.b.WriteByte('}')
	tv.visitAttributes(ln.Attrs)
}
func (tv *TestVisitor) visitBlockSlice(bns ast.BlockSlice) {
	for _, bn := range bns {
		bn.Accept(tv)
	}
}
func (tv *TestVisitor) visitItemSlice(ins ast.ItemSlice) {
	for _, in := range ins {
		in.Accept(tv)
	}
}
func (tv *TestVisitor) visitDescriptionSlice(dns ast.DescriptionSlice) {
	for _, dn := range dns {
		dn.Accept(tv)
	}
}
func (tv *TestVisitor) visitInlineSlice(ins ast.InlineSlice) {
	for _, in := range ins {
		tv.b.WriteByte(' ')
		in.Accept(tv)

	}
}

func (tv *TestVisitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return
	}
	tv.b.WriteString("[ATTR")

	keys := make([]string, 0, len(a.Attrs))







|







 







|







 







>
|
>
>
|
|
|
>
>
>
>
|
>
>
>
>
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|
<
<
<
<
<
<
<
<
<
<
<
<
<
<





<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|





<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|













<
<
<
<
<
<
<
|






<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<



<
>


>







46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
...
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788














789
790
791
792
793































794
795
796
797
798
799
























800
801
802
803
804
805
806





























































































807
808
809
810
811
812
813
814
815
816
817
818
819
820







821
822
823
824
825
826
827





























828
829
830

831
832
833
834
835
836
837
838
839
840
841

	for tcn, tc := range tcs {
		t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) {
			st.Helper()
			inp := input.NewInput(tc.source)
			bns := parser.ParseBlocks(inp, nil, meta.ValueSyntaxZmk)
			var tv TestVisitor
			ast.WalkBlockSlice(&tv, bns)
			got := tv.String()
			if tc.want != got {
				st.Errorf("\nwant=%q\n got=%q", tc.want, got)
			}
		})
	}
}
................................................................................

		// A HRule creates a new list
		{"* abc\n---\n* def", "(UL {(PARA abc)})(HR)(UL {(PARA def)})"},

		// Changing list type adds a new list
		{"* abc\n# def", "(UL {(PARA abc)})(OL {(PARA def)})"},

		// Quotation lists may have empty items
		{">", "(QL {})"},
	})
}

func TestEnumAfterPara(t *testing.T) {
	checkTcs(t, TestCases{
		{"abc\n* def", "(PARA abc)(UL {(PARA def)})"},
................................................................................

// TestVisitor serializes the abstract syntax tree to a string.
type TestVisitor struct {
	b strings.Builder
}

func (tv *TestVisitor) String() string { return tv.b.String() }

func (tv *TestVisitor) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.ParaNode:
		tv.b.WriteString("(PARA")
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
	case *ast.VerbatimNode:
		code, ok := mapVerbatimKind[n.Kind]
		if !ok {
			panic(fmt.Sprintf("Unknown verbatim code %v", n.Kind))
		}
		tv.b.WriteString(code)
		for _, line := range n.Lines {
			tv.b.WriteByte('\n')
			tv.b.WriteString(line)
		}

		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.RegionNode:
		code, ok := mapRegionKind[n.Kind]
		if !ok {
			panic(fmt.Sprintf("Unknown region code %v", n.Kind))
		}
		tv.b.WriteString(code)
		if n.Blocks != nil {
			tv.b.WriteByte(' ')
			ast.WalkBlockSlice(tv, n.Blocks)
		}
		if len(n.Inlines) > 0 {
			tv.b.WriteString(" (LINE")
			tv.visitInlineSlice(n.Inlines)
			tv.b.WriteByte(')')
		}
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.HeadingNode:
		fmt.Fprintf(&tv.b, "(H%d", n.Level)
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.HRuleNode:
		tv.b.WriteString("(HR)")
		tv.visitAttributes(n.Attrs)
	case *ast.NestedListNode:
		tv.b.WriteString(mapNestedListKind[n.Kind])
		for _, item := range n.Items {
			tv.b.WriteString(" {")
			ast.WalkItemSlice(tv, item)
			tv.b.WriteByte('}')
		}
		tv.b.WriteByte(')')
	case *ast.DescriptionListNode:
		tv.b.WriteString("(DL")
		for _, def := range n.Descriptions {
			tv.b.WriteString(" (DT")
			tv.visitInlineSlice(def.Term)
			tv.b.WriteByte(')')
			for _, b := range def.Descriptions {
				tv.b.WriteString(" (DD ")
				ast.WalkDescriptionSlice(tv, b)
				tv.b.WriteByte(')')
			}
		}
		tv.b.WriteByte(')')
	case *ast.TableNode:
		tv.b.WriteString("(TAB")
		if len(n.Header) > 0 {
			tv.b.WriteString(" (TR")
			for _, cell := range n.Header {
				tv.b.WriteString(" (TH")
				tv.b.WriteString(alignString[cell.Align])
				tv.visitInlineSlice(cell.Inlines)
				tv.b.WriteString(")")
			}
			tv.b.WriteString(")")
		}
		if len(n.Rows) > 0 {
			tv.b.WriteString(" ")
			for _, row := range n.Rows {
				tv.b.WriteString("(TR")
				for i, cell := range row {
					if i == 0 {
						tv.b.WriteString(" ")
					}
					tv.b.WriteString("(TD")
					tv.b.WriteString(alignString[cell.Align])
					tv.visitInlineSlice(cell.Inlines)
					tv.b.WriteString(")")
				}
				tv.b.WriteString(")")
			}
		}
		tv.b.WriteString(")")
	case *ast.BLOBNode:
		tv.b.WriteString("(BLOB ")
		tv.b.WriteString(n.Syntax)
		tv.b.WriteString(")")
	case *ast.TextNode:
		tv.b.WriteString(n.Text)
	case *ast.TagNode:
		tv.b.WriteByte('#')
		tv.b.WriteString(n.Tag)
		tv.b.WriteByte('#')
	case *ast.SpaceNode:
		if len(n.Lexeme) == 1 {
			tv.b.WriteString("SP")
		} else {
			fmt.Fprintf(&tv.b, "SP%d", len(n.Lexeme))
		}
	case *ast.BreakNode:
		if n.Hard {
			tv.b.WriteString("HB")
		} else {
			tv.b.WriteString("SB")
		}
	case *ast.LinkNode:
		fmt.Fprintf(&tv.b, "(LINK %s", n.Ref)
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.ImageNode:
		fmt.Fprintf(&tv.b, "(IMAGE %s", n.Ref)
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.CiteNode:
		fmt.Fprintf(&tv.b, "(CITE %s", n.Key)
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.FootnoteNode:
		tv.b.WriteString("(FN")
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte(')')
		tv.visitAttributes(n.Attrs)
	case *ast.MarkNode:
		tv.b.WriteString("(MARK")
		if len(n.Text) > 0 {
			tv.b.WriteByte(' ')
			tv.b.WriteString(n.Text)
		}
		tv.b.WriteByte(')')
	case *ast.FormatNode:
		fmt.Fprintf(&tv.b, "{%c", mapFormatKind[n.Kind])
		tv.visitInlineSlice(n.Inlines)
		tv.b.WriteByte('}')
		tv.visitAttributes(n.Attrs)
	case *ast.LiteralNode:
		code, ok := mapLiteralKind[n.Kind]
		if !ok {
			panic(fmt.Sprintf("No element for code %v", n.Kind))
		}
		tv.b.WriteByte('{')
		tv.b.WriteRune(code)
		if len(n.Text) > 0 {
			tv.b.WriteByte(' ')
			tv.b.WriteString(n.Text)
		}
		tv.b.WriteByte('}')
		tv.visitAttributes(n.Attrs)
	}
	return nil
}

var mapVerbatimKind = map[ast.VerbatimKind]string{
	ast.VerbatimProg: "(PROG",
}

var mapRegionKind = map[ast.RegionKind]string{














	ast.RegionSpan:  "(SPAN",
	ast.RegionQuote: "(QUOTE",
	ast.RegionVerse: "(VERSE",
}
































var mapNestedListKind = map[ast.NestedListKind]string{
	ast.NestedListOrdered:   "(OL",
	ast.NestedListUnordered: "(UL",
	ast.NestedListQuote:     "(QL",
}

























var alignString = map[ast.Alignment]string{
	ast.AlignDefault: "",
	ast.AlignLeft:    "l",
	ast.AlignCenter:  "c",
	ast.AlignRight:   "r",
}






























































































var mapFormatKind = map[ast.FormatKind]rune{
	ast.FormatItalic:    '/',
	ast.FormatBold:      '*',
	ast.FormatUnder:     '_',
	ast.FormatStrike:    '~',
	ast.FormatMonospace: '\'',
	ast.FormatSuper:     '^',
	ast.FormatSub:       ',',
	ast.FormatQuote:     '"',
	ast.FormatQuotation: '<',
	ast.FormatSmall:     ';',
	ast.FormatSpan:      ':',
}








var mapLiteralKind = map[ast.LiteralKind]rune{
	ast.LiteralProg:    '`',
	ast.LiteralKeyb:    '+',
	ast.LiteralOutput:  '=',
	ast.LiteralComment: '%',
}






























func (tv *TestVisitor) visitInlineSlice(ins ast.InlineSlice) {
	for _, in := range ins {
		tv.b.WriteByte(' ')

		ast.Walk(tv, in)
	}
}

func (tv *TestVisitor) visitAttributes(a *ast.Attributes) {
	if a == nil || len(a.Attrs) == 0 {
		return
	}
	tv.b.WriteString("[ATTR")

	keys := make([]string, 0, len(a.Attrs))

Changes to place/constplace/constplace.go.

24
25
26
27
28
29
30



31

32
33
34
35
36
37
38
..
44
45
46
47
48
49
50

51
52
53
54
55
56
57
..
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
	"zettelstore.de/z/search"
)

func init() {
	manager.Register(
		" const",
		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {



			return &constPlace{zettel: constZettelMap, enricher: cdata.Enricher}, nil

		})
}

type constHeader map[string]string

func makeMeta(zid id.Zid, h constHeader) *meta.Meta {
	m := meta.New(zid)
................................................................................

type constZettel struct {
	header  constHeader
	content domain.Content
}

type constPlace struct {

	zettel   map[id.Zid]constZettel
	enricher place.Enricher
}

func (cp *constPlace) Location() string {
	return "const:"
}
................................................................................
	}
	return result, nil
}

func (cp *constPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
	for zid, zettel := range cp.zettel {
		m := makeMeta(zid, zettel.header)
		cp.enricher.Enrich(ctx, m)
		if match(m) {
			res = append(res, m)
		}
	}
	return res, nil
}








>
>
>
|
>







 







>







 







|







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
..
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
	"zettelstore.de/z/search"
)

func init() {
	manager.Register(
		" const",
		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
			return &constPlace{
				number:   cdata.Number,
				zettel:   constZettelMap,
				enricher: cdata.Enricher,
			}, nil
		})
}

type constHeader map[string]string

func makeMeta(zid id.Zid, h constHeader) *meta.Meta {
	m := meta.New(zid)
................................................................................

type constZettel struct {
	header  constHeader
	content domain.Content
}

type constPlace struct {
	number   int
	zettel   map[id.Zid]constZettel
	enricher place.Enricher
}

func (cp *constPlace) Location() string {
	return "const:"
}
................................................................................
	}
	return result, nil
}

func (cp *constPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
	for zid, zettel := range cp.zettel {
		m := makeMeta(zid, zettel.header)
		cp.enricher.Enrich(ctx, m, cp.number)
		if match(m) {
			res = append(res, m)
		}
	}
	return res, nil
}

Changes to place/constplace/listzettel.mustache.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<nav>
<header>
<h1>{{Title}}</h1>
</header>
<ul>
{{#Metas}}<li><a href="{{{URL}}}">{{{Text}}}</a></li>
{{/Metas}}</ul>
{{#HasPrevNext}}
<p>
{{#HasPrev}}
<a href="{{{PrevURL}}}" rel="prev">Prev</a>
{{#HasNext}},{{/HasNext}}
{{/HasPrev}}
{{#HasNext}}
<a href="{{{NextURL}}}" rel="next">Next</a>
{{/HasNext}}
</p>
{{/HasPrevNext}}
</nav>
<






<
<
<
<
<
<
<
<
<
<
<
<

1
2
3
4
5
6













<header>
<h1>{{Title}}</h1>
</header>
<ul>
{{#Metas}}<li><a href="{{{URL}}}">{{{Text}}}</a></li>
{{/Metas}}</ul>












Changes to place/dirplace/dirplace.go.

36
37
38
39
40
41
42

43
44
45
46
47
48
49
..
89
90
91
92
93
94
95

96
97
98
99
100
101
102
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
...
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
	manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
		path := getDirPath(u)
		if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		dirSrvSpec, defWorker, maxWorker := getDirSrvInfo(u.Query().Get("type"))
		dp := dirPlace{

			location:   u.String(),
			readonly:   getQueryBool(u, "readonly"),
			cdata:      *cdata,
			dir:        path,
			dirRescan:  time.Duration(getQueryInt(u, "rescan", 60, 3600, 30*24*60*60)) * time.Second,
			dirSrvSpec: dirSrvSpec,
			fSrvs:      uint32(getQueryInt(u, "worker", 1, defWorker, maxWorker)),
................................................................................
		return max
	}
	return iVal
}

// dirPlace uses a directory to store zettel as files.
type dirPlace struct {

	location   string
	readonly   bool
	cdata      manager.ConnectData
	dir        string
	dirRescan  time.Duration
	dirSrvSpec directoryServiceSpec
	dirSrv     directory.Service
................................................................................
			chci <- place.UpdateInfo{Reason: reason, Zid: zid}
		}
	}
}

func (dp *dirPlace) getFileChan(zid id.Zid) chan fileCmd {
	// Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
	var sum uint32 = 2166136261 ^ uint32(zid)
	sum *= 16777619
	sum ^= uint32(zid >> 32)
	sum *= 16777619

	dp.mxCmds.RLock()
	defer dp.mxCmds.RUnlock()
	return dp.fCmds[sum%dp.fSrvs]
................................................................................
	for _, entry := range entries {
		m, err1 := getMeta(dp, entry, entry.Zid)
		err = err1
		if err != nil {
			continue
		}
		dp.cleanupMeta(ctx, m)
		dp.cdata.Enricher.Enrich(ctx, m)

		if match(m) {
			res = append(res, m)
		}
	}
	if err != nil {
		return nil, err







>







 







>







 







|







 







|







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
..
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
	manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
		path := getDirPath(u)
		if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
			return nil, err
		}
		dirSrvSpec, defWorker, maxWorker := getDirSrvInfo(u.Query().Get("type"))
		dp := dirPlace{
			number:     cdata.Number,
			location:   u.String(),
			readonly:   getQueryBool(u, "readonly"),
			cdata:      *cdata,
			dir:        path,
			dirRescan:  time.Duration(getQueryInt(u, "rescan", 60, 3600, 30*24*60*60)) * time.Second,
			dirSrvSpec: dirSrvSpec,
			fSrvs:      uint32(getQueryInt(u, "worker", 1, defWorker, maxWorker)),
................................................................................
		return max
	}
	return iVal
}

// dirPlace uses a directory to store zettel as files.
type dirPlace struct {
	number     int
	location   string
	readonly   bool
	cdata      manager.ConnectData
	dir        string
	dirRescan  time.Duration
	dirSrvSpec directoryServiceSpec
	dirSrv     directory.Service
................................................................................
			chci <- place.UpdateInfo{Reason: reason, Zid: zid}
		}
	}
}

func (dp *dirPlace) getFileChan(zid id.Zid) chan fileCmd {
	// Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
	sum := 2166136261 ^ uint32(zid)
	sum *= 16777619
	sum ^= uint32(zid >> 32)
	sum *= 16777619

	dp.mxCmds.RLock()
	defer dp.mxCmds.RUnlock()
	return dp.fCmds[sum%dp.fSrvs]
................................................................................
	for _, entry := range entries {
		m, err1 := getMeta(dp, entry, entry.Zid)
		err = err1
		if err != nil {
			continue
		}
		dp.cleanupMeta(ctx, m)
		dp.cdata.Enricher.Enrich(ctx, m, dp.number)

		if match(m) {
			res = append(res, m)
		}
	}
	if err != nil {
		return nil, err

Changes to place/fileplace/fileplace.go.

26
27
28
29
30
31
32



33

34
35
36
37
38
39
40
func init() {
	manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
		path := getFilepathFromURL(u)
		ext := strings.ToLower(filepath.Ext(path))
		if ext != ".zip" {
			return nil, errors.New("unknown extension '" + ext + "' in place URL: " + u.String())
		}



		return &zipPlace{name: path, enricher: cdata.Enricher}, nil

	})
}

func getFilepathFromURL(u *url.URL) string {
	name := u.Opaque
	if name == "" {
		name = u.Path







>
>
>
|
>







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
func init() {
	manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
		path := getFilepathFromURL(u)
		ext := strings.ToLower(filepath.Ext(path))
		if ext != ".zip" {
			return nil, errors.New("unknown extension '" + ext + "' in place URL: " + u.String())
		}
		return &zipPlace{
			number:   cdata.Number,
			name:     path,
			enricher: cdata.Enricher,
		}, nil
	})
}

func getFilepathFromURL(u *url.URL) string {
	name := u.Opaque
	if name == "" {
		name = u.Path

Changes to place/fileplace/zipplace.go.

36
37
38
39
40
41
42

43
44
45
46
47
48
49
...
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
	metaName     string
	contentName  string
	contentExt   string // (normalized) file extension of zettel content
	metaInHeader bool
}

type zipPlace struct {

	name     string
	enricher place.Enricher
	zettel   map[id.Zid]*zipEntry // no lock needed, because read-only after creation
}

func (zp *zipPlace) Location() string {
	if strings.HasPrefix(zp.name, "/") {
................................................................................
	}
	defer reader.Close()
	for zid, entry := range zp.zettel {
		m, err := readZipMeta(reader, zid, entry)
		if err != nil {
			continue
		}
		zp.enricher.Enrich(ctx, m)
		if match(m) {
			res = append(res, m)
		}
	}
	return res, nil
}








>







 







|







36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
	metaName     string
	contentName  string
	contentExt   string // (normalized) file extension of zettel content
	metaInHeader bool
}

type zipPlace struct {
	number   int
	name     string
	enricher place.Enricher
	zettel   map[id.Zid]*zipEntry // no lock needed, because read-only after creation
}

func (zp *zipPlace) Location() string {
	if strings.HasPrefix(zp.name, "/") {
................................................................................
	}
	defer reader.Close()
	for zid, entry := range zp.zettel {
		m, err := readZipMeta(reader, zid, entry)
		if err != nil {
			continue
		}
		zp.enricher.Enrich(ctx, m, zp.number)
		if match(m) {
			res = append(res, m)
		}
	}
	return res, nil
}

Changes to place/manager/collect.go.

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
func (data *collectData) initialize() {
	data.refs = id.NewSet()
	data.words = store.NewWordSet()
	data.urls = store.NewWordSet()
}

func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
	ast.NewTopDownTraverser(data).VisitBlockSlice(zn.Ast)
}

func collectInlineIndexData(ins ast.InlineSlice, data *collectData) {
	ast.NewTopDownTraverser(data).VisitInlineSlice(ins)
}

// VisitVerbatim collects the verbatim text in the word set.

func (data *collectData) VisitVerbatim(vn *ast.VerbatimNode) {
	for _, line := range vn.Lines {
		data.addText(line)
	}
}


// VisitRegion does nothing.
func (data *collectData) VisitRegion(rn *ast.RegionNode) {}


// VisitHeading does nothing.
func (data *collectData) VisitHeading(hn *ast.HeadingNode) {}


// VisitHRule does nothing.
func (data *collectData) VisitHRule(hn *ast.HRuleNode) {}

// VisitList does nothing.
func (data *collectData) VisitNestedList(ln *ast.NestedListNode) {}

// VisitDescriptionList does nothing.
func (data *collectData) VisitDescriptionList(dn *ast.DescriptionListNode) {}

// VisitPara does nothing.
func (data *collectData) VisitPara(pn *ast.ParaNode) {}

// VisitTable does nothing.
func (data *collectData) VisitTable(tn *ast.TableNode) {}

// VisitBLOB does nothing.
func (data *collectData) VisitBLOB(bn *ast.BLOBNode) {}

// VisitText collects the text in the word set.
func (data *collectData) VisitText(tn *ast.TextNode) {
	data.addText(tn.Text)
}


// VisitTag collects the tag name in the word set.
func (data *collectData) VisitTag(tn *ast.TagNode) {
	data.addText(tn.Tag)
}




// VisitSpace does nothing.
func (data *collectData) VisitSpace(sn *ast.SpaceNode) {}

// VisitBreak does nothing.
func (data *collectData) VisitBreak(bn *ast.BreakNode) {}

// VisitLink collects the given link as a reference.
func (data *collectData) VisitLink(ln *ast.LinkNode) {
	ref := ln.Ref
	if ref == nil {
		return
	}
	if ref.IsExternal() {
		data.urls.Add(strings.ToLower(ref.Value))
	}
	if !ref.IsZettel() {
		return
	}
	if zid, err := id.Parse(ref.URL.Path); err == nil {
		data.refs[zid] = true
	}
}

// VisitImage collects the image links as a reference.
func (data *collectData) VisitImage(in *ast.ImageNode) {
	ref := in.Ref
	if ref == nil {
		return
	}
	if ref.IsExternal() {
		data.urls.Add(strings.ToLower(ref.Value))
	}
	if !ref.IsZettel() {
		return
	}
	if zid, err := id.Parse(ref.URL.Path); err == nil {
		data.refs[zid] = true
	}
}

// VisitCite does nothing.
func (data *collectData) VisitCite(cn *ast.CiteNode) {}

// VisitFootnote does nothing.
func (data *collectData) VisitFootnote(fn *ast.FootnoteNode) {}

// VisitMark does nothing.
func (data *collectData) VisitMark(mn *ast.MarkNode) {}

// VisitFormat does nothing.
func (data *collectData) VisitFormat(fn *ast.FormatNode) {}

// VisitLiteral collects the literal words in the word set.
func (data *collectData) VisitLiteral(ln *ast.LiteralNode) {
	data.addText(ln.Text)
}

func (data *collectData) addText(s string) {
	for _, word := range strfun.NormalizeWords(s) {
		data.words.Add(word)
	}
}







|



|


|
>
|
|
|
|
<
<
>
|
|
<
>
|
|
<
>
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
>
|
<
<
<
|
>
>
>
|
<
<
|
<
<

<
|
<













<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
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








































func (data *collectData) initialize() {
	data.refs = id.NewSet()
	data.words = store.NewWordSet()
	data.urls = store.NewWordSet()
}

func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
	ast.WalkBlockSlice(data, zn.Ast)
}

func collectInlineIndexData(ins ast.InlineSlice, data *collectData) {
	ast.WalkInlineSlice(data, ins)
}

func (data *collectData) Visit(node ast.Node) ast.Visitor {
	switch n := node.(type) {
	case *ast.VerbatimNode:
		for _, line := range n.Lines {
			data.addText(line)
		}


	case *ast.TextNode:
		data.addText(n.Text)
	case *ast.TagNode:

		data.addText(n.Tag)
	case *ast.LinkNode:
		data.addRef(n.Ref)

	case *ast.ImageNode:
		data.addRef(n.Ref)
	case *ast.LiteralNode:


















		data.addText(n.Text)
	}
	return data
}




func (data *collectData) addText(s string) {
	for _, word := range strfun.NormalizeWords(s) {
		data.words.Add(word)
	}


}




func (data *collectData) addRef(ref *ast.Reference) {

	if ref == nil {
		return
	}
	if ref.IsExternal() {
		data.urls.Add(strings.ToLower(ref.Value))
	}
	if !ref.IsZettel() {
		return
	}
	if zid, err := id.Parse(ref.URL.Path); err == nil {
		data.refs[zid] = true
	}
}








































Changes to place/manager/enrich.go.

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
//-----------------------------------------------------------------------------

// Package manager coordinates the various places and indexes of a Zettelstore.
package manager

import (
	"context"


	"zettelstore.de/z/domain/meta"
	"zettelstore.de/z/place"
)

// Enrich computes additional properties and updates the given metadata.
func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta) {
	if place.DoNotEnrich(ctx) {
		// Enrich is called indirectly via indexer or enrichment is not requested
		// because of other reasons -> ignore this call, do not update meta data
		return
	}

	computePublished(m)
	mgr.idxStore.Enrich(ctx, m)
}

func computePublished(m *meta.Meta) {
	if _, ok := m.Get(meta.KeyPublished); ok {
		return







>






|





>







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
//-----------------------------------------------------------------------------

// Package manager coordinates the various places and indexes of a Zettelstore.
package manager

import (
	"context"
	"strconv"

	"zettelstore.de/z/domain/meta"
	"zettelstore.de/z/place"
)

// Enrich computes additional properties and updates the given metadata.
func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, placeNumber int) {
	if place.DoNotEnrich(ctx) {
		// Enrich is called indirectly via indexer or enrichment is not requested
		// because of other reasons -> ignore this call, do not update meta data
		return
	}
	m.Set(meta.KeyPlaceNumber, strconv.Itoa(placeNumber))
	computePublished(m)
	mgr.idxStore.Enrich(ctx, m)
}

func computePublished(m *meta.Meta) {
	if _, ok := m.Get(meta.KeyPublished); ok {
		return

Changes to place/manager/indexer.go.

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
				mgr.idxAr.Reload(nil, zids)
				mgr.idxMx.Lock()
				mgr.idxLastReload = time.Now()
				mgr.idxSinceReload = 0
				mgr.idxMx.Unlock()
			}
		case arUpdate:
			changed = true
			mgr.idxMx.Lock()
			mgr.idxSinceReload++
			mgr.idxMx.Unlock()
			zettel, err := mgr.GetZettel(ctx, zid)
			if err != nil {
				// TODO: on some errors put the zid into a "try later" set
				continue
			}




			mgr.idxUpdateZettel(ctx, zettel)
		case arDelete:







			changed = true
			mgr.idxMx.Lock()
			mgr.idxSinceReload++
			mgr.idxMx.Unlock()
			mgr.idxDeleteZettel(zid)
		}
	}







<
<
<
<





>
>
>
>


>
>
>
>
>
>
>







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
				mgr.idxAr.Reload(nil, zids)
				mgr.idxMx.Lock()
				mgr.idxLastReload = time.Now()
				mgr.idxSinceReload = 0
				mgr.idxMx.Unlock()
			}
		case arUpdate:




			zettel, err := mgr.GetZettel(ctx, zid)
			if err != nil {
				// TODO: on some errors put the zid into a "try later" set
				continue
			}
			changed = true
			mgr.idxMx.Lock()
			mgr.idxSinceReload++
			mgr.idxMx.Unlock()
			mgr.idxUpdateZettel(ctx, zettel)
		case arDelete:
			if _, err := mgr.GetMeta(ctx, zid); err == nil {
				// Zettel was not deleted. This might occur, if zettel was
				// deleted in secondary dirplace, but is still present in
				// first dirplace (or vice versa). Re-index zettel in case
				// a hidden zettel was recovered
				mgr.idxAr.Enqueue(zid, arUpdate)
			}
			changed = true
			mgr.idxMx.Lock()
			mgr.idxSinceReload++
			mgr.idxMx.Unlock()
			mgr.idxDeleteZettel(zid)
		}
	}

Changes to place/manager/manager.go.

28
29
30
31
32
33
34

35
36
37
38
39
40
41
...
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
	"zettelstore.de/z/place"
	"zettelstore.de/z/place/manager/memstore"
	"zettelstore.de/z/place/manager/store"
)

// ConnectData contains all administration related values.
type ConnectData struct {

	Config   config.Config
	Enricher place.Enricher
	Notify   chan<- place.UpdateInfo
}

// Connect returns a handle to the specified place
func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (place.ManagedPlace, error) {
................................................................................
		infos:        make(chan place.UpdateInfo, len(placeURIs)*10),
		propertyKeys: propertyKeys,

		idxStore: memstore.New(),
		idxAr:    newAnterooms(10),
		idxReady: make(chan struct{}, 1),
	}
	cdata := ConnectData{Config: rtConfig, Enricher: mgr, Notify: mgr.infos}
	subplaces := make([]place.ManagedPlace, 0, len(placeURIs)+2)
	for _, uri := range placeURIs {
		p, err := Connect(uri, authManager, &cdata)
		if err != nil {
			return nil, err
		}
		if p != nil {
			subplaces = append(subplaces, p)

		}
	}
	constplace, err := registry[" const"](nil, &cdata)
	if err != nil {
		return nil, err
	}

	progplace, err := registry[" prog"](nil, &cdata)
	if err != nil {
		return nil, err
	}

	subplaces = append(subplaces, constplace, progplace)
	mgr.subplaces = subplaces
	return mgr, nil
}

// RegisterObserver registers an observer that will be notified
// if a zettel was found to be changed.







>







 







|








>






>




>







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
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
	"zettelstore.de/z/place"
	"zettelstore.de/z/place/manager/memstore"
	"zettelstore.de/z/place/manager/store"
)

// ConnectData contains all administration related values.
type ConnectData struct {
	Number   int // number of the place, starting with 1.
	Config   config.Config
	Enricher place.Enricher
	Notify   chan<- place.UpdateInfo
}

// Connect returns a handle to the specified place
func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (place.ManagedPlace, error) {
................................................................................
		infos:        make(chan place.UpdateInfo, len(placeURIs)*10),
		propertyKeys: propertyKeys,

		idxStore: memstore.New(),
		idxAr:    newAnterooms(10),
		idxReady: make(chan struct{}, 1),
	}
	cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos}
	subplaces := make([]place.ManagedPlace, 0, len(placeURIs)+2)
	for _, uri := range placeURIs {
		p, err := Connect(uri, authManager, &cdata)
		if err != nil {
			return nil, err
		}
		if p != nil {
			subplaces = append(subplaces, p)
			cdata.Number++
		}
	}
	constplace, err := registry[" const"](nil, &cdata)
	if err != nil {
		return nil, err
	}
	cdata.Number++
	progplace, err := registry[" prog"](nil, &cdata)
	if err != nil {
		return nil, err
	}
	cdata.Number++
	subplaces = append(subplaces, constplace, progplace)
	mgr.subplaces = subplaces
	return mgr, nil
}

// RegisterObserver registers an observer that will be notified
// if a zettel was found to be changed.

Changes to place/manager/place.go.

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
// GetZettel retrieves a specific zettel.
func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if !mgr.started {
		return domain.Zettel{}, place.ErrStopped
	}
	for _, p := range mgr.subplaces {
		if z, err := p.GetZettel(ctx, zid); err != place.ErrNotFound {
			if err == nil {
				mgr.Enrich(ctx, z.Meta)
			}
			return z, err
		}
	}
	return domain.Zettel{}, place.ErrNotFound
}

................................................................................
// GetMeta retrieves just the meta data of a specific zettel.
func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if !mgr.started {
		return nil, place.ErrStopped
	}
	for _, p := range mgr.subplaces {
		if m, err := p.GetMeta(ctx, zid); err != place.ErrNotFound {
			if err == nil {
				mgr.Enrich(ctx, m)
			}
			return m, err
		}
	}
	return nil, place.ErrNotFound
}








|


|







 







|


|







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
// GetZettel retrieves a specific zettel.
func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if !mgr.started {
		return domain.Zettel{}, place.ErrStopped
	}
	for i, p := range mgr.subplaces {
		if z, err := p.GetZettel(ctx, zid); err != place.ErrNotFound {
			if err == nil {
				mgr.Enrich(ctx, z.Meta, i+1)
			}
			return z, err
		}
	}
	return domain.Zettel{}, place.ErrNotFound
}

................................................................................
// GetMeta retrieves just the meta data of a specific zettel.
func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
	mgr.mgrMx.RLock()
	defer mgr.mgrMx.RUnlock()
	if !mgr.started {
		return nil, place.ErrStopped
	}
	for i, p := range mgr.subplaces {
		if m, err := p.GetMeta(ctx, zid); err != place.ErrNotFound {
			if err == nil {
				mgr.Enrich(ctx, m, i+1)
			}
			return m, err
		}
	}
	return nil, place.ErrNotFound
}

Changes to place/manager/store/store.go.

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
34
35
36
37
38
39
40
41
42



43
44
45
46
47
48
49
package store

import (
	"context"
	"io"

	"zettelstore.de/z/domain/id"
	"zettelstore.de/z/place"
	"zettelstore.de/z/search"
)

// Stats records statistics about the store.
type Stats struct {
	// Zettel is the number of zettel managed by the indexer.
	Zettel int
................................................................................
	// Urls count the different URLs stored in the store.
	Urls uint64
}

// Store all relevant zettel data. There may be multiple implementations, i.e.
// memory-based, file-based, based on SQLite, ...
type Store interface {
	place.Enricher
	search.Selector




	// UpdateReferences for a specific zettel.
	// Returns set of zettel identifier that must also be checked for changes.
	UpdateReferences(context.Context, *ZettelIndex) id.Set

	// DeleteZettel removes index data for given zettel.
	// Returns set of zettel identifier that must also be checked for changes.







|







 







<

>
>
>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
..
34
35
36
37
38
39
40

41
42
43
44
45
46
47
48
49
50
51
package store

import (
	"context"
	"io"

	"zettelstore.de/z/domain/id"
	"zettelstore.de/z/domain/meta"
	"zettelstore.de/z/search"
)

// Stats records statistics about the store.
type Stats struct {
	// Zettel is the number of zettel managed by the indexer.
	Zettel int
................................................................................
	// Urls count the different URLs stored in the store.
	Urls uint64
}

// Store all relevant zettel data. There may be multiple implementations, i.e.
// memory-based, file-based, based on SQLite, ...
type Store interface {

	search.Selector

	// Entrich metadata with data from store.
	Enrich(ctx context.Context, m *meta.Meta)

	// UpdateReferences for a specific zettel.
	// Returns set of zettel identifier that must also be checked for changes.
	UpdateReferences(context.Context, *ZettelIndex) id.Set

	// DeleteZettel removes index data for given zettel.
	// Returns set of zettel identifier that must also be checked for changes.

Changes to place/memplace/memplace.go.

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
}

func (mp *memPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) {
	result := make([]*meta.Meta, 0, len(mp.zettel))
	mp.mx.RLock()
	for _, zettel := range mp.zettel {
		m := zettel.Meta.Clone()
		mp.cdata.Enricher.Enrich(ctx, m)
		if match(m) {
			result = append(result, m)
		}
	}
	mp.mx.RUnlock()
	return result, nil
}







|







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
}

func (mp *memPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) {
	result := make([]*meta.Meta, 0, len(mp.zettel))
	mp.mx.RLock()
	for _, zettel := range mp.zettel {
		m := zettel.Meta.Clone()
		mp.cdata.Enricher.Enrich(ctx, m, mp.cdata.Number)
		if match(m) {
			result = append(result, m)
		}
	}
	mp.mx.RUnlock()
	return result, nil
}

Changes to place/place.go.

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
	RegisterObserver(UpdateFunc)
}

// Enricher is used to update metadata by adding new properties.
type Enricher interface {
	// Enrich computes additional properties and updates the given metadata.
	// It is typically called by zettel reading methods.
	Enrich(ctx context.Context, m *meta.Meta)
}

// NoEnrichContext will signal an enricher that nothing has to be done.
// This is useful for an Indexer, but also for some place.Place calls, when
// just the plain metadata is needed.
func NoEnrichContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey)







|







179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
	RegisterObserver(UpdateFunc)
}

// Enricher is used to update metadata by adding new properties.
type Enricher interface {
	// Enrich computes additional properties and updates the given metadata.
	// It is typically called by zettel reading methods.
	Enrich(ctx context.Context, m *meta.Meta, placeNumber int)
}

// NoEnrichContext will signal an enricher that nothing has to be done.
// This is useful for an Indexer, but also for some place.Place calls, when
// just the plain metadata is needed.
func NoEnrichContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey)

Changes to place/progplace/progplace.go.

24
25
26
27
28
29
30
31
32
33
34
35

36
37
38
39
40
41
42
..
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
...
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
	"zettelstore.de/z/search"
)

func init() {
	manager.Register(
		" prog",
		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
			return getPlace(cdata.Enricher), nil
		})
}

type progPlace struct {

	filter place.Enricher
}

var myConfig *meta.Meta
var myZettel = map[id.Zid]struct {
	meta    func(id.Zid) *meta.Meta
	content func(*meta.Meta) string
................................................................................
	id.OperatingSystemZid:      {genVersionOSM, genVersionOSC},
	id.PlaceManagerZid:         {genManagerM, genManagerC},
	id.MetadataKeyZid:          {genKeysM, genKeysC},
	id.StartupConfigurationZid: {genConfigZettelM, genConfigZettelC},
}

// Get returns the one program place.
func getPlace(mf place.Enricher) place.ManagedPlace {
	return &progPlace{filter: mf}
}

// Setup remembers important values.
func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() }

func (pp *progPlace) Location() string { return "" }

................................................................................
}

func (pp *progPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
	for zid, gen := range myZettel {
		if genMeta := gen.meta; genMeta != nil {
			if m := genMeta(zid); m != nil {
				updateMeta(m)
				pp.filter.Enrich(ctx, m)
				if match(m) {
					res = append(res, m)
				}
			}
		}
	}
	return res, nil







|




>







 







|
|







 







|







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
..
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
...
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
	"zettelstore.de/z/search"
)

func init() {
	manager.Register(
		" prog",
		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
			return getPlace(cdata.Number, cdata.Enricher), nil
		})
}

type progPlace struct {
	number int
	filter place.Enricher
}

var myConfig *meta.Meta
var myZettel = map[id.Zid]struct {
	meta    func(id.Zid) *meta.Meta
	content func(*meta.Meta) string
................................................................................
	id.OperatingSystemZid:      {genVersionOSM, genVersionOSC},
	id.PlaceManagerZid:         {genManagerM, genManagerC},
	id.MetadataKeyZid:          {genKeysM, genKeysC},
	id.StartupConfigurationZid: {genConfigZettelM, genConfigZettelC},
}

// Get returns the one program place.
func getPlace(placeNumber int, mf place.Enricher) place.ManagedPlace {
	return &progPlace{number: placeNumber, filter: mf}
}

// Setup remembers important values.
func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() }

func (pp *progPlace) Location() string { return "" }

................................................................................
}

func (pp *progPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
	for zid, gen := range myZettel {
		if genMeta := gen.meta; genMeta != nil {
			if m := genMeta(zid); m != nil {
				updateMeta(m)
				pp.filter.Enrich(ctx, m, pp.number)
				if match(m) {
					res = append(res, m)
				}
			}
		}
	}
	return res, nil

Changes to tests/regression_test.go.

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
		}
	}
	return root, places
}

type noEnrich struct{}

func (nf *noEnrich) Enrich(ctx context.Context, m *meta.Meta) {}
func (nf *noEnrich) Remove(ctx context.Context, m *meta.Meta) {}

type noAuth struct{}

func (na *noAuth) IsReadonly() bool { return false }

func trimLastEOL(s string) string {
	if lastPos := len(s) - 1; lastPos >= 0 && s[lastPos] == '\n' {







|
|







65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
		}
	}
	return root, places
}

type noEnrich struct{}

func (nf *noEnrich) Enrich(context.Context, *meta.Meta, int) {}
func (nf *noEnrich) Remove(context.Context, *meta.Meta)      {}

type noAuth struct{}

func (na *noAuth) IsReadonly() bool { return false }

func trimLastEOL(s string) string {
	if lastPos := len(s) - 1; lastPos >= 0 && s[lastPos] == '\n' {

Changes to web/adapter/encoding.go.

69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
		zid, err := id.Parse(origRef.URL.Path)
		if err != nil {
			panic(err)
		}
		_, err = getMeta.Run(place.NoEnrichContext(ctx), zid)
		if errors.Is(err, &place.ErrNotAllowed{}) {
			return &ast.FormatNode{
				Code:    ast.FormatSpan,
				Attrs:   origLink.Attrs,
				Inlines: origLink.Inlines,
			}
		}
		var newRef *ast.Reference
		if err == nil {
			ub := b.NewURLBuilder(key).SetZid(zid)







|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
		zid, err := id.Parse(origRef.URL.Path)
		if err != nil {
			panic(err)
		}
		_, err = getMeta.Run(place.NoEnrichContext(ctx), zid)
		if errors.Is(err, &place.ErrNotAllowed{}) {
			return &ast.FormatNode{
				Kind:    ast.FormatSpan,
				Attrs:   origLink.Attrs,
				Inlines: origLink.Inlines,
			}
		}
		var newRef *ast.Reference
		if err == nil {
			ub := b.NewURLBuilder(key).SetZid(zid)

Changes to web/adapter/webui/htmlmeta.go.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
	case meta.TypeID:
		wui.writeIdentifier(w, m.GetDefault(key, "???i"), getTitle)
	case meta.TypeIDSet:
		if l, ok := m.GetList(key); ok {
			wui.writeIdentifierSet(w, l, getTitle)
		}
	case meta.TypeNumber:
		writeNumber(w, m.GetDefault(key, "???n"))
	case meta.TypeString:
		writeString(w, m.GetDefault(key, "???s"))
	case meta.TypeTagSet:
		if l, ok := m.GetList(key); ok {
			wui.writeTagSet(w, key, l)
		}
	case meta.TypeTimestamp:
................................................................................
		if i > 0 {
			w.Write(space)
		}
		wui.writeIdentifier(w, val, getTitle)
	}
}

func writeNumber(w io.Writer, val string) {
	strfun.HTMLEscape(w, val, false)
}

func writeString(w io.Writer, val string) {
	strfun.HTMLEscape(w, val, false)
}

func writeUnknown(w io.Writer, val string) {







|







 







|
|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
	case meta.TypeID:
		wui.writeIdentifier(w, m.GetDefault(key, "???i"), getTitle)
	case meta.TypeIDSet:
		if l, ok := m.GetList(key); ok {
			wui.writeIdentifierSet(w, l, getTitle)
		}
	case meta.TypeNumber:
		wui.writeNumber(w, key, m.GetDefault(key, "???n"))
	case meta.TypeString:
		writeString(w, m.GetDefault(key, "???s"))
	case meta.TypeTagSet:
		if l, ok := m.GetList(key); ok {
			wui.writeTagSet(w, key, l)
		}
	case meta.TypeTimestamp:
................................................................................
		if i > 0 {
			w.Write(space)
		}
		wui.writeIdentifier(w, val, getTitle)
	}
}

func (wui *WebUI) writeNumber(w io.Writer, key, val string) {
	wui.writeLink(w, key, val, val)
}

func writeString(w io.Writer, val string) {
	strfun.HTMLEscape(w, val, false)
}

func writeUnknown(w io.Writer, val string) {

Changes to web/adapter/webui/lists.go.

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
	ctx context.Context,
	w http.ResponseWriter,
	title string,
	s *search.Search,
	ucMetaList func(sorter *search.Search) ([]*meta.Meta, error),
	pageURL func(int) string) {

	var metaList []*meta.Meta
	var err error
	var prevURL, nextURL string
	if lps := wui.rtConfig.GetListPageSize(); lps > 0 {
		if s.GetLimit() < lps {
			s.SetLimit(lps + 1)
		}

		metaList, err = ucMetaList(s)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
		if offset := s.GetOffset(); offset > 0 {
			offset -= lps
			if offset < 0 {
				offset = 0
			}
			prevURL = pageURL(offset)
		}
		if len(metaList) >= s.GetLimit() {
			nextURL = pageURL(s.GetOffset() + lps)
			metaList = metaList[:len(metaList)-1]
		}
	} else {
		metaList, err = ucMetaList(s)
		if err != nil {
			wui.reportError(ctx, w, err)
			return
		}
	}
	user := wui.getUser(ctx)
	metas, err := wui.buildHTMLMetaList(metaList)
	if err != nil {
		wui.reportError(ctx, w, err)
		return
	}
	var base baseData
	wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base)
	wui.renderTemplate(ctx, w, id.ListTemplateZid, &base, struct {
		Title       string
		Metas       []simpleLink
		HasPrevNext bool
		HasPrev     bool
		PrevURL     string
		HasNext     bool
		NextURL     string
	}{
		Title:       title,
		Metas:       metas,
		HasPrevNext: len(prevURL) > 0 || len(nextURL) > 0,
		HasPrev:     len(prevURL) > 0,
		PrevURL:     prevURL,
		HasNext:     len(nextURL) > 0,
		NextURL:     nextURL,
	})
}

func (wui *WebUI) listTitleSearch(prefix string, s *search.Search) string {
	if s == nil {
		return wui.rtConfig.GetSiteName()
	}







<
<
<
<
<
<
<
<
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










|
|
<
<
<
<
<

|
|
<
<
<
<
<







267
268
269
270
271
272
273








274
275
276
277


















278
279
280
281
282
283
284
285
286
287
288
289





290
291
292





293
294
295
296
297
298
299
	ctx context.Context,
	w http.ResponseWriter,
	title string,
	s *search.Search,
	ucMetaList func(sorter *search.Search) ([]*meta.Meta, error),
	pageURL func(int) string) {









	metaList, err := ucMetaList(s)
	if err != nil {
		wui.reportError(ctx, w, err)
		return


















	}
	user := wui.getUser(ctx)
	metas, err := wui.buildHTMLMetaList(metaList)
	if err != nil {
		wui.reportError(ctx, w, err)
		return
	}
	var base baseData
	wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base)
	wui.renderTemplate(ctx, w, id.ListTemplateZid, &base, struct {
		Title string
		Metas []simpleLink





	}{
		Title: title,
		Metas: metas,





	})
}

func (wui *WebUI) listTitleSearch(prefix string, s *search.Search) string {
	if s == nil {
		return wui.rtConfig.GetSiteName()
	}

Changes to www/changes.wiki.

1
2
3
4






5
6
7
8
9
10
11
<title>Change Log</title>

<a name="0_0_14"></a>
<h2>Changes for Version 0.0.14 (pending)</h2>







<a name="0_0_13"></a>
<h2>Changes for Version 0.0.13 (2021-06-01)</h2>
  *  Startup configuration <tt>place-<em>X</em>-uri</tt> (where <em>X</em> is a
     number greater than zero) has been renamed to
     <tt>place-uri-<em>X</em></tt>.
     (breaking)




>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<title>Change Log</title>

<a name="0_0_14"></a>
<h2>Changes for Version 0.0.14 (pending)</h2>
  *  Remove support for paging of WebUI list. Runtime configuration key
     <tt>list-page-size</tt> is removed.
     (major: webui)
  *  New suported metadata key <tt>place-number</tt>, which gives an indication
     from which place the zettel was loaded.
     (minor)

<a name="0_0_13"></a>
<h2>Changes for Version 0.0.13 (2021-06-01)</h2>
  *  Startup configuration <tt>place-<em>X</em>-uri</tt> (where <em>X</em> is a
     number greater than zero) has been renamed to
     <tt>place-uri-<em>X</em></tt>.
     (breaking)